From 1f95d7903d4015e39dc3abd97b35166944ab70ac Mon Sep 17 00:00:00 2001 From: secustor Date: Wed, 3 Sep 2025 17:07:02 +0200 Subject: [PATCH 001/202] feat(devtools): add extension to allow providing tabs Signed-off-by: secustor --- package.json | 3 + packages/app-next/package.json | 1 + packages/app-next/src/App.tsx | 5 +- packages/backend/package.json | 3 + .../catalog-unprocessed-entities/package.json | 1 + .../src/alpha/devToolsRoute.ts | 32 ++++++++ .../src/alpha/index.ts | 1 + .../src/alpha/devToolsRouteBlueprint.tsx | 81 +++++++++++++++++++ .../src/alpha/devToolsRouteDataRef.ts | 37 +++++++++ plugins/devtools/src/alpha/index.ts | 4 + plugins/devtools/src/alpha/plugin.tsx | 25 ++++-- .../DefaultDevToolsPage.tsx | 16 +++- .../components/DevToolsPage/DevToolsPage.tsx | 9 ++- yarn.lock | 2 + 14 files changed, 208 insertions(+), 12 deletions(-) create mode 100644 plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts create mode 100644 plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx create mode 100644 plugins/devtools/src/alpha/devToolsRouteDataRef.ts diff --git a/package.json b/package.json index d4ebc22bc1..88238a1a21 100644 --- a/package.json +++ b/package.json @@ -185,5 +185,8 @@ } }, "tsConfig": "./tsconfig.json" + }, + "volta": { + "node": "22.19.0" } } diff --git a/packages/app-next/package.json b/packages/app-next/package.json index 44602b2438..0a39471e34 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -56,6 +56,7 @@ "@backstage/plugin-catalog-import": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-catalog-unprocessed-entities": "workspace:^", + "@backstage/plugin-devtools": "workspace:^", "@backstage/plugin-home": "workspace:^", "@backstage/plugin-kubernetes": "workspace:^", "@backstage/plugin-kubernetes-cluster": "workspace:^", diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index 6c567f3fe1..92d1f06d6a 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -45,6 +45,8 @@ import { convertLegacyPageExtension } from '@backstage/core-compat-api'; import { convertLegacyEntityContentExtension } from '@backstage/plugin-catalog-react/alpha'; import { pluginInfoResolver } from './pluginInfoResolver'; import { appModuleNav } from './modules/appModuleNav'; +import devtoolsPlugin from '@backstage/plugin-devtools/alpha'; +import { unprocessedEntitiesDevToolsRoute } from '@backstage/plugin-catalog-unprocessed-entities/alpha'; /* @@ -113,7 +115,7 @@ const customHomePageModule = createFrontendModule({ const notFoundErrorPageModule = createFrontendModule({ pluginId: 'app', - extensions: [notFoundErrorPage], + extensions: [notFoundErrorPage, unprocessedEntitiesDevToolsRoute], }); const collectedLegacyPlugins = convertLegacyAppRoot( @@ -133,6 +135,7 @@ const app = createApp({ notFoundErrorPageModule, appModuleNav, customHomePageModule, + devtoolsPlugin, ...collectedLegacyPlugins, ], advanced: { diff --git a/packages/backend/package.json b/packages/backend/package.json index a60b6d3940..0d03b12d19 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -71,5 +71,8 @@ }, "devDependencies": { "@backstage/cli": "workspace:^" + }, + "volta": { + "node": "22.19.0" } } diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index 004eb96208..c48f290d25 100644 --- a/plugins/catalog-unprocessed-entities/package.json +++ b/plugins/catalog-unprocessed-entities/package.json @@ -56,6 +56,7 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", + "@backstage/plugin-devtools": "workspace:^", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.60", diff --git a/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts b/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts new file mode 100644 index 0000000000..c90a6b8270 --- /dev/null +++ b/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DevToolsRouteBlueprint } from '@backstage/plugin-devtools/alpha'; + +/** + * DevTools route extension for catalog unprocessed entities + * This demonstrates how to create extensions at the app level using the new DevTools extension system + */ +export const unprocessedEntitiesDevToolsRoute = DevToolsRouteBlueprint.make({ + params: { + path: 'unprocessed-entities', + title: 'Unprocessed Entities', + loader: () => + import('../components/UnprocessedEntities').then(m => ({ + default: m.UnprocessedEntitiesContent, + })), + }, +}); diff --git a/plugins/catalog-unprocessed-entities/src/alpha/index.ts b/plugins/catalog-unprocessed-entities/src/alpha/index.ts index 2f137f09ee..5738bb8a59 100644 --- a/plugins/catalog-unprocessed-entities/src/alpha/index.ts +++ b/plugins/catalog-unprocessed-entities/src/alpha/index.ts @@ -15,3 +15,4 @@ */ export { default } from './plugin'; +export { unprocessedEntitiesDevToolsRoute } from './devToolsRoute'; diff --git a/plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx b/plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx new file mode 100644 index 0000000000..fec93854ae --- /dev/null +++ b/plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx @@ -0,0 +1,81 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createExtensionBlueprint } from '@backstage/frontend-plugin-api'; +import { devToolsRouteDataRef } from './devToolsRouteDataRef'; +import { lazy, Suspense, JSX } from 'react'; + +/** + * Parameters for creating a DevTools route extension + * @alpha + */ +export interface DevToolsRouteBlueprintParams { + path: string; + title: string; + loader: () => Promise<{ default: () => JSX.Element }> | (() => JSX.Element); +} + +/** + * Extension blueprint for creating DevTools routes + * + * @example + * ```tsx + * const myDevToolsRoute = DevToolsRouteBlueprint.make({ + * params: { + * path: 'my-feature', + * title: 'My Feature', + * loader: () => import('./MyContent').then(m => ({ default: m.MyContent })) + * } + * }); + * ``` + * + * @alpha + */ +export const DevToolsRouteBlueprint = createExtensionBlueprint({ + kind: 'devtools-route', + attachTo: { id: 'page:devtools', input: 'routes' }, + output: [devToolsRouteDataRef], + factory(params: DevToolsRouteBlueprintParams) { + // Handle both sync and async component loading + const Component = typeof params.loader === 'function' + ? (() => { + const result = params.loader(); + if (result instanceof Promise) { + // Create a lazy component for async loading + return lazy(() => result); + } + // Direct component function + return result; + + })() + : params.loader; + + const children = typeof Component === 'function' + ? Loading...}> + : Component; + + return [ + devToolsRouteDataRef({ + path: params.path, + title: params.title, + children, + }), + ]; + }, + dataRefs: { + route: devToolsRouteDataRef, + }, +}); \ No newline at end of file diff --git a/plugins/devtools/src/alpha/devToolsRouteDataRef.ts b/plugins/devtools/src/alpha/devToolsRouteDataRef.ts new file mode 100644 index 0000000000..f4533f13e5 --- /dev/null +++ b/plugins/devtools/src/alpha/devToolsRouteDataRef.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { JSX } from 'react'; + +/** + * Represents a DevTools route that can be contributed by extensions + * @alpha + */ +export interface DevToolsRouteData { + path: string; + title: string; + children: JSX.Element; +} + +/** + * Extension data reference for DevTools routes + * @alpha + */ +export const devToolsRouteDataRef = createExtensionDataRef() + .with({ + id: 'devtools.route', + }); \ No newline at end of file diff --git a/plugins/devtools/src/alpha/index.ts b/plugins/devtools/src/alpha/index.ts index 2f137f09ee..d326cac1be 100644 --- a/plugins/devtools/src/alpha/index.ts +++ b/plugins/devtools/src/alpha/index.ts @@ -15,3 +15,7 @@ */ export { default } from './plugin'; +export { DevToolsRouteBlueprint } from './devToolsRouteBlueprint'; +export { devToolsRouteDataRef } from './devToolsRouteDataRef'; +export type { DevToolsRouteData } from './devToolsRouteDataRef'; +export type { DevToolsRouteBlueprintParams } from './devToolsRouteBlueprint'; diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index 0f6d9ec4a1..02dca0141a 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -21,6 +21,7 @@ import { ApiBlueprint, PageBlueprint, NavItemBlueprint, + createExtensionInput, } from '@backstage/frontend-plugin-api'; import { devToolsApiRef, DevToolsClient } from '../api'; @@ -30,6 +31,7 @@ import { } from '@backstage/core-compat-api'; import BuildIcon from '@material-ui/icons/Build'; import { rootRouteRef } from '../routes'; +import { devToolsRouteDataRef } from './devToolsRouteDataRef'; /** @alpha */ export const devToolsApi = ApiBlueprint.make({ @@ -46,14 +48,21 @@ export const devToolsApi = ApiBlueprint.make({ }); /** @alpha */ -export const devToolsPage = PageBlueprint.make({ - params: { - path: '/devtools', - routeRef: convertLegacyRouteRef(rootRouteRef), - loader: () => - import('../components/DevToolsPage').then(m => - compatWrapper(), - ), +export const devToolsPage = PageBlueprint.makeWithOverrides({ + inputs: { + routes: createExtensionInput([devToolsRouteDataRef], { + optional: true, + }), + }, + factory(originalFactory, { inputs }) { + return originalFactory({ + path: '/devtools', + routeRef: convertLegacyRouteRef(rootRouteRef), + loader: () => + import('../components/DevToolsPage').then(m => + compatWrapper( route.get(devToolsRouteDataRef))} />), + ), + }); }, }); diff --git a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx index b4e5d3db5b..6375e562f4 100644 --- a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx +++ b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx @@ -23,9 +23,14 @@ import { ConfigContent } from '../Content/ConfigContent'; import { DevToolsLayout } from '../DevToolsLayout'; import { InfoContent } from '../Content/InfoContent'; import { RequirePermission } from '@backstage/plugin-permission-react'; +import { DevToolsRouteData } from '../../alpha/devToolsRouteDataRef'; + +export interface DefaultDevToolsPageProps { + extensionRoutes?: DevToolsRouteData[]; +} /** @public */ -export const DefaultDevToolsPage = () => ( +export const DefaultDevToolsPage = ({ extensionRoutes }: DefaultDevToolsPageProps) => ( @@ -37,5 +42,14 @@ export const DefaultDevToolsPage = () => ( + {extensionRoutes?.map((route, index) => ( + + {route.children} + + ))} ); diff --git a/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx index 07a549f8eb..6c351bbf96 100644 --- a/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx +++ b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx @@ -16,9 +16,14 @@ import { useOutlet } from 'react-router-dom'; import { DefaultDevToolsPage } from '../DefaultDevToolsPage'; +import { DevToolsRouteData } from '../../alpha/devToolsRouteDataRef'; -export const DevToolsPage = () => { +export interface DevToolsPageProps { + extensionRoutes?: DevToolsRouteData[]; +} + +export const DevToolsPage = ({ extensionRoutes }: DevToolsPageProps) => { const outlet = useOutlet(); - return <>{outlet || }; + return <>{outlet || }; }; diff --git a/yarn.lock b/yarn.lock index 853749b99e..b90990c0b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4938,6 +4938,7 @@ __metadata: "@backstage/dev-utils": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/plugin-devtools": "workspace:^" "@material-ui/core": "npm:^4.9.13" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:^4.0.0-alpha.60" @@ -29922,6 +29923,7 @@ __metadata: "@backstage/plugin-catalog-import": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/plugin-catalog-unprocessed-entities": "workspace:^" + "@backstage/plugin-devtools": "workspace:^" "@backstage/plugin-home": "workspace:^" "@backstage/plugin-kubernetes": "workspace:^" "@backstage/plugin-kubernetes-cluster": "workspace:^" From 42d31b0a03b16e6afab54661b5285d48b78367ee Mon Sep 17 00:00:00 2001 From: secustor Date: Wed, 3 Sep 2025 17:18:16 +0200 Subject: [PATCH 002/202] feat(devtools): working example Signed-off-by: secustor --- .../src/alpha/devToolsRouteBlueprint.tsx | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx b/plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx index fec93854ae..21f25a013a 100644 --- a/plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx +++ b/plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx @@ -25,12 +25,12 @@ import { lazy, Suspense, JSX } from 'react'; export interface DevToolsRouteBlueprintParams { path: string; title: string; - loader: () => Promise<{ default: () => JSX.Element }> | (() => JSX.Element); + loader: () => Promise<{ default: () => JSX.Element }>; } /** * Extension blueprint for creating DevTools routes - * + * * @example * ```tsx * const myDevToolsRoute = DevToolsRouteBlueprint.make({ @@ -41,7 +41,7 @@ export interface DevToolsRouteBlueprintParams { * } * }); * ``` - * + * * @alpha */ export const DevToolsRouteBlueprint = createExtensionBlueprint({ @@ -49,23 +49,14 @@ export const DevToolsRouteBlueprint = createExtensionBlueprint({ attachTo: { id: 'page:devtools', input: 'routes' }, output: [devToolsRouteDataRef], factory(params: DevToolsRouteBlueprintParams) { - // Handle both sync and async component loading - const Component = typeof params.loader === 'function' - ? (() => { - const result = params.loader(); - if (result instanceof Promise) { - // Create a lazy component for async loading - return lazy(() => result); - } - // Direct component function - return result; - - })() - : params.loader; + // Create a lazy component for async loading + const LazyComponent = lazy(params.loader); - const children = typeof Component === 'function' - ? Loading...}> - : Component; + const children = ( + Loading...}> + + + ); return [ devToolsRouteDataRef({ @@ -78,4 +69,4 @@ export const DevToolsRouteBlueprint = createExtensionBlueprint({ dataRefs: { route: devToolsRouteDataRef, }, -}); \ No newline at end of file +}); From 45c814961972de8c38eee31cd478da4bd3f5cd23 Mon Sep 17 00:00:00 2001 From: secustor Date: Wed, 3 Sep 2025 17:43:22 +0200 Subject: [PATCH 003/202] add prettier and remove volta references Signed-off-by: secustor --- package.json | 3 --- packages/backend/package.json | 3 --- plugins/devtools/src/alpha/devToolsRouteDataRef.ts | 6 +++--- plugins/devtools/src/alpha/plugin.tsx | 8 +++++++- .../DefaultDevToolsPage/DefaultDevToolsPage.tsx | 8 +++++--- .../devtools/src/components/DevToolsPage/DevToolsPage.tsx | 4 +++- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 88238a1a21..d4ebc22bc1 100644 --- a/package.json +++ b/package.json @@ -185,8 +185,5 @@ } }, "tsConfig": "./tsconfig.json" - }, - "volta": { - "node": "22.19.0" } } diff --git a/packages/backend/package.json b/packages/backend/package.json index 0d03b12d19..a60b6d3940 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -71,8 +71,5 @@ }, "devDependencies": { "@backstage/cli": "workspace:^" - }, - "volta": { - "node": "22.19.0" } } diff --git a/plugins/devtools/src/alpha/devToolsRouteDataRef.ts b/plugins/devtools/src/alpha/devToolsRouteDataRef.ts index f4533f13e5..5cea0931c6 100644 --- a/plugins/devtools/src/alpha/devToolsRouteDataRef.ts +++ b/plugins/devtools/src/alpha/devToolsRouteDataRef.ts @@ -31,7 +31,7 @@ export interface DevToolsRouteData { * Extension data reference for DevTools routes * @alpha */ -export const devToolsRouteDataRef = createExtensionDataRef() - .with({ +export const devToolsRouteDataRef = + createExtensionDataRef().with({ id: 'devtools.route', - }); \ No newline at end of file + }); diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index 02dca0141a..d4c4b5d755 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -60,7 +60,13 @@ export const devToolsPage = PageBlueprint.makeWithOverrides({ routeRef: convertLegacyRouteRef(rootRouteRef), loader: () => import('../components/DevToolsPage').then(m => - compatWrapper( route.get(devToolsRouteDataRef))} />), + compatWrapper( + + route.get(devToolsRouteDataRef), + )} + />, + ), ), }); }, diff --git a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx index 6375e562f4..e34c48bb0f 100644 --- a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx +++ b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx @@ -30,7 +30,9 @@ export interface DefaultDevToolsPageProps { } /** @public */ -export const DefaultDevToolsPage = ({ extensionRoutes }: DefaultDevToolsPageProps) => ( +export const DefaultDevToolsPage = ({ + extensionRoutes, +}: DefaultDevToolsPageProps) => ( @@ -43,9 +45,9 @@ export const DefaultDevToolsPage = ({ extensionRoutes }: DefaultDevToolsPageProp {extensionRoutes?.map((route, index) => ( - {route.children} diff --git a/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx index 6c351bbf96..341fa29094 100644 --- a/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx +++ b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx @@ -25,5 +25,7 @@ export interface DevToolsPageProps { export const DevToolsPage = ({ extensionRoutes }: DevToolsPageProps) => { const outlet = useOutlet(); - return <>{outlet || }; + return ( + <>{outlet || } + ); }; From f4d90863319d3a68d8fa0117eb9c66606c1a40bb Mon Sep 17 00:00:00 2001 From: secustor Date: Wed, 3 Sep 2025 17:57:03 +0200 Subject: [PATCH 004/202] some simplification Signed-off-by: secustor --- .../src/alpha/devToolsRoute.ts | 8 +++++--- .../src/alpha/devToolsRouteBlueprint.tsx | 20 ++++++++----------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts b/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts index c90a6b8270..e2479341c8 100644 --- a/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts +++ b/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { createElement } from 'react'; import { DevToolsRouteBlueprint } from '@backstage/plugin-devtools/alpha'; /** @@ -25,8 +26,9 @@ export const unprocessedEntitiesDevToolsRoute = DevToolsRouteBlueprint.make({ path: 'unprocessed-entities', title: 'Unprocessed Entities', loader: () => - import('../components/UnprocessedEntities').then(m => ({ - default: m.UnprocessedEntitiesContent, - })), + import('../components/UnprocessedEntities').then( + ({ UnprocessedEntitiesContent }) => + createElement(UnprocessedEntitiesContent), + ), }, }); diff --git a/plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx b/plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx index 21f25a013a..150ce43fb3 100644 --- a/plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx +++ b/plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx @@ -14,9 +14,12 @@ * limitations under the License. */ -import { createExtensionBlueprint } from '@backstage/frontend-plugin-api'; +import { + createExtensionBlueprint, + ExtensionBoundary, +} from '@backstage/frontend-plugin-api'; import { devToolsRouteDataRef } from './devToolsRouteDataRef'; -import { lazy, Suspense, JSX } from 'react'; +import { JSX } from 'react'; /** * Parameters for creating a DevTools route extension @@ -25,7 +28,7 @@ import { lazy, Suspense, JSX } from 'react'; export interface DevToolsRouteBlueprintParams { path: string; title: string; - loader: () => Promise<{ default: () => JSX.Element }>; + loader: () => Promise; } /** @@ -48,15 +51,8 @@ export const DevToolsRouteBlueprint = createExtensionBlueprint({ kind: 'devtools-route', attachTo: { id: 'page:devtools', input: 'routes' }, output: [devToolsRouteDataRef], - factory(params: DevToolsRouteBlueprintParams) { - // Create a lazy component for async loading - const LazyComponent = lazy(params.loader); - - const children = ( - Loading...}> - - - ); + factory(params: DevToolsRouteBlueprintParams, { node }) { + const children = ExtensionBoundary.lazy(node, params.loader); return [ devToolsRouteDataRef({ From be6cef5dab18b99fe6a9dbd140f286e4e7633598 Mon Sep 17 00:00:00 2001 From: secustor Date: Wed, 3 Sep 2025 18:21:53 +0200 Subject: [PATCH 005/202] refactor: move blueprints to `react` package Signed-off-by: secustor --- .changeset/slimy-ghosts-rescue.md | 7 + .../catalog-unprocessed-entities/package.json | 2 +- .../src/alpha/devToolsRoute.ts | 2 +- plugins/devtools-react/.eslintrc.js | 1 + plugins/devtools-react/README.md | 5 + plugins/devtools-react/catalog-info.yaml | 10 ++ plugins/devtools-react/package.json | 46 +++++++ plugins/devtools-react/report.api.md | 54 ++++++++ .../src}/devToolsRouteBlueprint.tsx | 0 .../src}/devToolsRouteDataRef.ts | 0 plugins/devtools-react/src/index.ts | 29 ++++ plugins/devtools/package.json | 1 + plugins/devtools/src/alpha/index.ts | 4 - plugins/devtools/src/alpha/plugin.tsx | 2 +- .../DefaultDevToolsPage.tsx | 2 +- .../components/DevToolsPage/DevToolsPage.tsx | 2 +- yarn.lock | 125 +++++++++++++++--- 17 files changed, 262 insertions(+), 30 deletions(-) create mode 100644 .changeset/slimy-ghosts-rescue.md create mode 100644 plugins/devtools-react/.eslintrc.js create mode 100644 plugins/devtools-react/README.md create mode 100644 plugins/devtools-react/catalog-info.yaml create mode 100644 plugins/devtools-react/package.json create mode 100644 plugins/devtools-react/report.api.md rename plugins/{devtools/src/alpha => devtools-react/src}/devToolsRouteBlueprint.tsx (100%) rename plugins/{devtools/src/alpha => devtools-react/src}/devToolsRouteDataRef.ts (100%) create mode 100644 plugins/devtools-react/src/index.ts diff --git a/.changeset/slimy-ghosts-rescue.md b/.changeset/slimy-ghosts-rescue.md new file mode 100644 index 0000000000..e519411341 --- /dev/null +++ b/.changeset/slimy-ghosts-rescue.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-unprocessed-entities': patch +'@backstage/plugin-devtools-react': patch +'@backstage/plugin-devtools': patch +--- + +Add support for adding `unprocessed-entities` and other tabs to `devtools` diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index c48f290d25..d220dd5b79 100644 --- a/plugins/catalog-unprocessed-entities/package.json +++ b/plugins/catalog-unprocessed-entities/package.json @@ -56,7 +56,7 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", - "@backstage/plugin-devtools": "workspace:^", + "@backstage/plugin-devtools-react": "workspace:^", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.60", diff --git a/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts b/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts index e2479341c8..7e4faa0ac3 100644 --- a/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts +++ b/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts @@ -15,7 +15,7 @@ */ import { createElement } from 'react'; -import { DevToolsRouteBlueprint } from '@backstage/plugin-devtools/alpha'; +import { DevToolsRouteBlueprint } from '@backstage/plugin-devtools-react'; /** * DevTools route extension for catalog unprocessed entities diff --git a/plugins/devtools-react/.eslintrc.js b/plugins/devtools-react/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/devtools-react/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/devtools-react/README.md b/plugins/devtools-react/README.md new file mode 100644 index 0000000000..a6ca2b4723 --- /dev/null +++ b/plugins/devtools-react/README.md @@ -0,0 +1,5 @@ +# @backstage/plugin-devtools-react + +Welcome to the web library package for the devtools plugin! + +_This plugin was created through the Backstage CLI_ diff --git a/plugins/devtools-react/catalog-info.yaml b/plugins/devtools-react/catalog-info.yaml new file mode 100644 index 0000000000..98b847bee3 --- /dev/null +++ b/plugins/devtools-react/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-devtools-react + title: '@backstage/plugin-devtools-react' + description: Web library for the devtools plugin +spec: + lifecycle: experimental + type: backstage-web-library + owner: maintainers diff --git a/plugins/devtools-react/package.json b/plugins/devtools-react/package.json new file mode 100644 index 0000000000..78f7167c04 --- /dev/null +++ b/plugins/devtools-react/package.json @@ -0,0 +1,46 @@ +{ + "name": "@backstage/plugin-devtools-react", + "version": "0.1.0", + "license": "Apache-2.0", + "private": true, + "description": "Web library for the devtools plugin", + "main": "src/index.ts", + "types": "src/index.ts", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "web-library", + "pluginId": "devtools" + }, + "sideEffects": false, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/core-plugin-api": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", + "@material-ui/core": "^4.9.13" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/test-utils": "workspace:^", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^14.0.0", + "react": "^16.13.1 || ^17.0.0 || ^18.0.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/devtools-react/report.api.md b/plugins/devtools-react/report.api.md new file mode 100644 index 0000000000..dccd40f75f --- /dev/null +++ b/plugins/devtools-react/report.api.md @@ -0,0 +1,54 @@ +## API Report File for "@backstage/plugin-devtools-react" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { JSX as JSX_2 } from 'react'; + +// @alpha +export const DevToolsRouteBlueprint: ExtensionBlueprint<{ + kind: 'devtools-route'; + params: DevToolsRouteBlueprintParams; + output: ExtensionDataRef; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + route: ConfigurableExtensionDataRef< + DevToolsRouteData, + 'devtools.route', + {} + >; + }; +}>; + +// @alpha +export interface DevToolsRouteBlueprintParams { + // (undocumented) + loader: () => Promise; + // (undocumented) + path: string; + // (undocumented) + title: string; +} + +// @alpha +export interface DevToolsRouteData { + // (undocumented) + children: JSX_2.Element; + // (undocumented) + path: string; + // (undocumented) + title: string; +} + +// @alpha +export const devToolsRouteDataRef: ConfigurableExtensionDataRef< + DevToolsRouteData, + 'devtools.route', + {} +>; +``` diff --git a/plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx b/plugins/devtools-react/src/devToolsRouteBlueprint.tsx similarity index 100% rename from plugins/devtools/src/alpha/devToolsRouteBlueprint.tsx rename to plugins/devtools-react/src/devToolsRouteBlueprint.tsx diff --git a/plugins/devtools/src/alpha/devToolsRouteDataRef.ts b/plugins/devtools-react/src/devToolsRouteDataRef.ts similarity index 100% rename from plugins/devtools/src/alpha/devToolsRouteDataRef.ts rename to plugins/devtools-react/src/devToolsRouteDataRef.ts diff --git a/plugins/devtools-react/src/index.ts b/plugins/devtools-react/src/index.ts new file mode 100644 index 0000000000..4adcfb39ad --- /dev/null +++ b/plugins/devtools-react/src/index.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Web library for the devtools plugin. + * + * @packageDocumentation + */ +export { + type DevToolsRouteData, + devToolsRouteDataRef, +} from './devToolsRouteDataRef'; +export { + type DevToolsRouteBlueprintParams, + DevToolsRouteBlueprint, +} from './devToolsRouteBlueprint.tsx'; diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index f05ec985d2..0fe2f413c7 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -57,6 +57,7 @@ "@backstage/errors": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-devtools-common": "workspace:^", + "@backstage/plugin-devtools-react": "workspace:^", "@backstage/plugin-permission-react": "workspace:^", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", diff --git a/plugins/devtools/src/alpha/index.ts b/plugins/devtools/src/alpha/index.ts index d326cac1be..2f137f09ee 100644 --- a/plugins/devtools/src/alpha/index.ts +++ b/plugins/devtools/src/alpha/index.ts @@ -15,7 +15,3 @@ */ export { default } from './plugin'; -export { DevToolsRouteBlueprint } from './devToolsRouteBlueprint'; -export { devToolsRouteDataRef } from './devToolsRouteDataRef'; -export type { DevToolsRouteData } from './devToolsRouteDataRef'; -export type { DevToolsRouteBlueprintParams } from './devToolsRouteBlueprint'; diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index d4c4b5d755..fb7c627bdc 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -31,7 +31,7 @@ import { } from '@backstage/core-compat-api'; import BuildIcon from '@material-ui/icons/Build'; import { rootRouteRef } from '../routes'; -import { devToolsRouteDataRef } from './devToolsRouteDataRef'; +import { devToolsRouteDataRef } from '@backstage/plugin-devtools-react'; /** @alpha */ export const devToolsApi = ApiBlueprint.make({ diff --git a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx index e34c48bb0f..53ba1fd0dd 100644 --- a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx +++ b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx @@ -23,7 +23,7 @@ import { ConfigContent } from '../Content/ConfigContent'; import { DevToolsLayout } from '../DevToolsLayout'; import { InfoContent } from '../Content/InfoContent'; import { RequirePermission } from '@backstage/plugin-permission-react'; -import { DevToolsRouteData } from '../../alpha/devToolsRouteDataRef'; +import { DevToolsRouteData } from '@backstage/plugin-devtools-react'; export interface DefaultDevToolsPageProps { extensionRoutes?: DevToolsRouteData[]; diff --git a/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx index 341fa29094..04db0665a3 100644 --- a/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx +++ b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx @@ -16,7 +16,7 @@ import { useOutlet } from 'react-router-dom'; import { DefaultDevToolsPage } from '../DefaultDevToolsPage'; -import { DevToolsRouteData } from '../../alpha/devToolsRouteDataRef'; +import { DevToolsRouteData } from '@backstage/plugin-devtools-react'; export interface DevToolsPageProps { extensionRoutes?: DevToolsRouteData[]; diff --git a/yarn.lock b/yarn.lock index b90990c0b6..15078530d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4938,7 +4938,7 @@ __metadata: "@backstage/dev-utils": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" - "@backstage/plugin-devtools": "workspace:^" + "@backstage/plugin-devtools-react": "workspace:^" "@material-ui/core": "npm:^4.9.13" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:^4.0.0-alpha.60" @@ -5100,6 +5100,23 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-devtools-react@workspace:^, @backstage/plugin-devtools-react@workspace:plugins/devtools-react": + version: 0.0.0-use.local + resolution: "@backstage/plugin-devtools-react@workspace:plugins/devtools-react" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@material-ui/core": "npm:^4.9.13" + "@testing-library/jest-dom": "npm:^6.0.0" + "@testing-library/react": "npm:^14.0.0" + react: "npm:^16.13.1 || ^17.0.0 || ^18.0.0" + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + languageName: unknown + linkType: soft + "@backstage/plugin-devtools@workspace:^, @backstage/plugin-devtools@workspace:plugins/devtools": version: 0.0.0-use.local resolution: "@backstage/plugin-devtools@workspace:plugins/devtools" @@ -5112,6 +5129,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" "@backstage/plugin-devtools-common": "workspace:^" + "@backstage/plugin-devtools-react": "workspace:^" "@backstage/plugin-permission-react": "workspace:^" "@material-ui/core": "npm:^4.9.13" "@material-ui/icons": "npm:^4.9.1" @@ -19484,6 +19502,22 @@ __metadata: languageName: node linkType: hard +"@testing-library/dom@npm:^9.0.0": + version: 9.3.4 + resolution: "@testing-library/dom@npm:9.3.4" + dependencies: + "@babel/code-frame": "npm:^7.10.4" + "@babel/runtime": "npm:^7.12.5" + "@types/aria-query": "npm:^5.0.1" + aria-query: "npm:5.1.3" + chalk: "npm:^4.1.0" + dom-accessibility-api: "npm:^0.5.9" + lz-string: "npm:^1.5.0" + pretty-format: "npm:^27.0.2" + checksum: 10/510da752ea76f4a10a0a4e3a77917b0302cf03effe576cd3534cab7e796533ee2b0e9fb6fb11b911a1ebd7c70a0bb6f235bf4f816c9b82b95b8fe0cddfd10975 + languageName: node + linkType: hard + "@testing-library/jest-dom@npm:6.5.0": version: 6.5.0 resolution: "@testing-library/jest-dom@npm:6.5.0" @@ -19536,6 +19570,20 @@ __metadata: languageName: node linkType: hard +"@testing-library/react@npm:^14.0.0": + version: 14.3.1 + resolution: "@testing-library/react@npm:14.3.1" + dependencies: + "@babel/runtime": "npm:^7.12.5" + "@testing-library/dom": "npm:^9.0.0" + "@types/react-dom": "npm:^18.0.0" + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 10/83359dcdf9eaf067839f34604e1a181cbc14fc09f3a07672403700fcc6a900c4b8054ad1114fc24b4b9f89d84e2a09e1b7c9afce2306b1d4b4c9e30eb1cb12de + languageName: node + linkType: hard + "@testing-library/react@npm:^16.0.0": version: 16.3.0 resolution: "@testing-library/react@npm:16.3.0" @@ -23938,6 +23986,15 @@ __metadata: languageName: node linkType: hard +"aria-query@npm:5.1.3": + version: 5.1.3 + resolution: "aria-query@npm:5.1.3" + dependencies: + deep-equal: "npm:^2.0.5" + checksum: 10/e5da608a7c4954bfece2d879342b6c218b6b207e2d9e5af270b5e38ef8418f02d122afdc948b68e32649b849a38377785252059090d66fa8081da95d1609c0d2 + languageName: node + linkType: hard + "aria-query@npm:5.3.0": version: 5.3.0 resolution: "aria-query@npm:5.3.0" @@ -23954,7 +24011,7 @@ __metadata: languageName: node linkType: hard -"array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2": +"array-buffer-byte-length@npm:^1.0.0, array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2": version: 1.0.2 resolution: "array-buffer-byte-length@npm:1.0.2" dependencies: @@ -27714,6 +27771,32 @@ __metadata: languageName: node linkType: hard +"deep-equal@npm:^2.0.5": + version: 2.2.3 + resolution: "deep-equal@npm:2.2.3" + dependencies: + array-buffer-byte-length: "npm:^1.0.0" + call-bind: "npm:^1.0.5" + es-get-iterator: "npm:^1.1.3" + get-intrinsic: "npm:^1.2.2" + is-arguments: "npm:^1.1.1" + is-array-buffer: "npm:^3.0.2" + is-date-object: "npm:^1.0.5" + is-regex: "npm:^1.1.4" + is-shared-array-buffer: "npm:^1.0.2" + isarray: "npm:^2.0.5" + object-is: "npm:^1.1.5" + object-keys: "npm:^1.1.1" + object.assign: "npm:^4.1.4" + regexp.prototype.flags: "npm:^1.5.1" + side-channel: "npm:^1.0.4" + which-boxed-primitive: "npm:^1.0.2" + which-collection: "npm:^1.0.1" + which-typed-array: "npm:^1.1.13" + checksum: 10/1ce49d0b71d0f14d8ef991a742665eccd488dfc9b3cada069d4d7a86291e591c92d2589c832811dea182b4015736b210acaaebce6184be356c1060d176f5a05f + languageName: node + linkType: hard + "deep-equal@npm:~1.0.1": version: 1.0.1 resolution: "deep-equal@npm:1.0.1" @@ -28922,7 +29005,7 @@ __metadata: languageName: node linkType: hard -"es-get-iterator@npm:^1.0.2": +"es-get-iterator@npm:^1.0.2, es-get-iterator@npm:^1.1.3": version: 1.1.3 resolution: "es-get-iterator@npm:1.1.3" dependencies: @@ -31517,7 +31600,7 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0": +"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0": version: 1.3.0 resolution: "get-intrinsic@npm:1.3.0" dependencies: @@ -33450,7 +33533,7 @@ __metadata: languageName: node linkType: hard -"is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5": +"is-array-buffer@npm:^3.0.2, is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5": version: 3.0.5 resolution: "is-array-buffer@npm:3.0.5" dependencies: @@ -33923,7 +34006,7 @@ __metadata: languageName: node linkType: hard -"is-regex@npm:^1.2.1": +"is-regex@npm:^1.1.4, is-regex@npm:^1.2.1": version: 1.2.1 resolution: "is-regex@npm:1.2.1" dependencies: @@ -33972,7 +34055,7 @@ __metadata: languageName: node linkType: hard -"is-shared-array-buffer@npm:^1.0.4": +"is-shared-array-buffer@npm:^1.0.2, is-shared-array-buffer@npm:^1.0.4": version: 1.0.4 resolution: "is-shared-array-buffer@npm:1.0.4" dependencies: @@ -43395,6 +43478,15 @@ __metadata: languageName: node linkType: hard +"react@npm:^16.13.1 || ^17.0.0 || ^18.0.0, react@npm:^18.0.2": + version: 18.3.1 + resolution: "react@npm:18.3.1" + dependencies: + loose-envify: "npm:^1.1.0" + checksum: 10/261137d3f3993eaa2368a83110466fc0e558bc2c7f7ae7ca52d94f03aac945f45146bd85e5f481044db1758a1dbb57879e2fcdd33924e2dde1bdc550ce73f7bf + languageName: node + linkType: hard + "react@npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0": version: 19.1.1 resolution: "react@npm:19.1.1" @@ -43412,15 +43504,6 @@ __metadata: languageName: node linkType: hard -"react@npm:^18.0.2": - version: 18.3.1 - resolution: "react@npm:18.3.1" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10/261137d3f3993eaa2368a83110466fc0e558bc2c7f7ae7ca52d94f03aac945f45146bd85e5f481044db1758a1dbb57879e2fcdd33924e2dde1bdc550ce73f7bf - languageName: node - linkType: hard - "read-cmd-shim@npm:^2.0.0": version: 2.0.0 resolution: "read-cmd-shim@npm:2.0.0" @@ -43702,7 +43785,7 @@ __metadata: languageName: node linkType: hard -"regexp.prototype.flags@npm:^1.5.3": +"regexp.prototype.flags@npm:^1.5.1, regexp.prototype.flags@npm:^1.5.3": version: 1.5.4 resolution: "regexp.prototype.flags@npm:1.5.4" dependencies: @@ -45206,7 +45289,7 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.0.6, side-channel@npm:^1.1.0": +"side-channel@npm:^1.0.4, side-channel@npm:^1.0.6, side-channel@npm:^1.1.0": version: 1.1.0 resolution: "side-channel@npm:1.1.0" dependencies: @@ -49373,7 +49456,7 @@ __metadata: languageName: node linkType: hard -"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": +"which-boxed-primitive@npm:^1.0.2, which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": version: 1.1.1 resolution: "which-boxed-primitive@npm:1.1.1" dependencies: @@ -49407,7 +49490,7 @@ __metadata: languageName: node linkType: hard -"which-collection@npm:^1.0.2": +"which-collection@npm:^1.0.1, which-collection@npm:^1.0.2": version: 1.0.2 resolution: "which-collection@npm:1.0.2" dependencies: @@ -49429,7 +49512,7 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18, which-typed-array@npm:^1.1.2": +"which-typed-array@npm:^1.1.13, which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18, which-typed-array@npm:^1.1.2": version: 1.1.19 resolution: "which-typed-array@npm:1.1.19" dependencies: From e405ca9b7a34dc0eb98799374caaad693b9a4424 Mon Sep 17 00:00:00 2001 From: secustor Date: Wed, 3 Sep 2025 18:43:36 +0200 Subject: [PATCH 006/202] chore: fix api reports Signed-off-by: secustor --- plugins/devtools-backend/package.json | 3 +- plugins/devtools-common/package.json | 3 +- plugins/devtools-react/package.json | 45 ++++++++++++------- plugins/devtools-react/report.api.md | 8 ++-- .../src/devToolsRouteBlueprint.tsx | 5 +-- .../src/devToolsRouteDataRef.ts | 4 +- plugins/devtools/package.json | 3 +- plugins/devtools/report-alpha.api.md | 17 +++++-- plugins/devtools/report.api.md | 11 ++++- .../DefaultDevToolsPage.tsx | 10 +---- .../components/DevToolsPage/DevToolsPage.tsx | 3 ++ .../src/components/DevToolsPage/index.ts | 2 +- plugins/devtools/src/components/index.ts | 1 + 13 files changed, 73 insertions(+), 42 deletions(-) diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index 038fcbce82..6fafab972a 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -7,7 +7,8 @@ "pluginPackages": [ "@backstage/plugin-devtools", "@backstage/plugin-devtools-backend", - "@backstage/plugin-devtools-common" + "@backstage/plugin-devtools-common", + "@backstage/plugin-devtools-react" ] }, "publishConfig": { diff --git a/plugins/devtools-common/package.json b/plugins/devtools-common/package.json index a5056edf41..ec39a0809d 100644 --- a/plugins/devtools-common/package.json +++ b/plugins/devtools-common/package.json @@ -8,7 +8,8 @@ "pluginPackages": [ "@backstage/plugin-devtools", "@backstage/plugin-devtools-backend", - "@backstage/plugin-devtools-common" + "@backstage/plugin-devtools-common", + "@backstage/plugin-devtools-react" ] }, "publishConfig": { diff --git a/plugins/devtools-react/package.json b/plugins/devtools-react/package.json index 78f7167c04..4e96fdb29c 100644 --- a/plugins/devtools-react/package.json +++ b/plugins/devtools-react/package.json @@ -1,38 +1,49 @@ { "name": "@backstage/plugin-devtools-react", "version": "0.1.0", - "license": "Apache-2.0", - "private": true, "description": "Web library for the devtools plugin", - "main": "src/index.ts", - "types": "src/index.ts", + "backstage": { + "role": "web-library", + "pluginId": "devtools", + "pluginPackages": [ + "@backstage/plugin-devtools", + "@backstage/plugin-devtools-backend", + "@backstage/plugin-devtools-common", + "@backstage/plugin-devtools-react" + ] + }, "publishConfig": { "access": "public", "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, - "backstage": { - "role": "web-library", - "pluginId": "devtools" + "private": true, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/devtools-react" }, + "license": "Apache-2.0", "sideEffects": false, + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], "scripts": { - "start": "backstage-cli package start", "build": "backstage-cli package build", - "lint": "backstage-cli package lint", - "test": "backstage-cli package test", "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", "prepack": "backstage-cli package prepack", - "postpack": "backstage-cli package postpack" + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" }, "dependencies": { "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@material-ui/core": "^4.9.13" }, - "peerDependencies": { - "react": "^16.13.1 || ^17.0.0 || ^18.0.0" - }, "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/test-utils": "workspace:^", @@ -40,7 +51,7 @@ "@testing-library/react": "^14.0.0", "react": "^16.13.1 || ^17.0.0 || ^18.0.0" }, - "files": [ - "dist" - ] + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0" + } } diff --git a/plugins/devtools-react/report.api.md b/plugins/devtools-react/report.api.md index dccd40f75f..a6a6b6e69b 100644 --- a/plugins/devtools-react/report.api.md +++ b/plugins/devtools-react/report.api.md @@ -8,7 +8,7 @@ import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; -// @alpha +// @public export const DevToolsRouteBlueprint: ExtensionBlueprint<{ kind: 'devtools-route'; params: DevToolsRouteBlueprintParams; @@ -25,7 +25,7 @@ export const DevToolsRouteBlueprint: ExtensionBlueprint<{ }; }>; -// @alpha +// @public export interface DevToolsRouteBlueprintParams { // (undocumented) loader: () => Promise; @@ -35,7 +35,7 @@ export interface DevToolsRouteBlueprintParams { title: string; } -// @alpha +// @public export interface DevToolsRouteData { // (undocumented) children: JSX_2.Element; @@ -45,7 +45,7 @@ export interface DevToolsRouteData { title: string; } -// @alpha +// @public export const devToolsRouteDataRef: ConfigurableExtensionDataRef< DevToolsRouteData, 'devtools.route', diff --git a/plugins/devtools-react/src/devToolsRouteBlueprint.tsx b/plugins/devtools-react/src/devToolsRouteBlueprint.tsx index 150ce43fb3..a048641418 100644 --- a/plugins/devtools-react/src/devToolsRouteBlueprint.tsx +++ b/plugins/devtools-react/src/devToolsRouteBlueprint.tsx @@ -23,7 +23,7 @@ import { JSX } from 'react'; /** * Parameters for creating a DevTools route extension - * @alpha + * @public */ export interface DevToolsRouteBlueprintParams { path: string; @@ -44,8 +44,7 @@ export interface DevToolsRouteBlueprintParams { * } * }); * ``` - * - * @alpha + * @public */ export const DevToolsRouteBlueprint = createExtensionBlueprint({ kind: 'devtools-route', diff --git a/plugins/devtools-react/src/devToolsRouteDataRef.ts b/plugins/devtools-react/src/devToolsRouteDataRef.ts index 5cea0931c6..d6c506505a 100644 --- a/plugins/devtools-react/src/devToolsRouteDataRef.ts +++ b/plugins/devtools-react/src/devToolsRouteDataRef.ts @@ -19,7 +19,7 @@ import { JSX } from 'react'; /** * Represents a DevTools route that can be contributed by extensions - * @alpha + * @public */ export interface DevToolsRouteData { path: string; @@ -29,7 +29,7 @@ export interface DevToolsRouteData { /** * Extension data reference for DevTools routes - * @alpha + * @public */ export const devToolsRouteDataRef = createExtensionDataRef().with({ diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 0fe2f413c7..5da086b0dc 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -7,7 +7,8 @@ "pluginPackages": [ "@backstage/plugin-devtools", "@backstage/plugin-devtools-backend", - "@backstage/plugin-devtools-common" + "@backstage/plugin-devtools-common", + "@backstage/plugin-devtools-react" ] }, "publishConfig": { diff --git a/plugins/devtools/report-alpha.api.md b/plugins/devtools/report-alpha.api.md index 7696267c35..d5b87ce96b 100644 --- a/plugins/devtools/report-alpha.api.md +++ b/plugins/devtools/report-alpha.api.md @@ -6,9 +6,12 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { DevToolsRouteData } from '@backstage/plugin-devtools-react'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; @@ -58,8 +61,6 @@ const _default: OverridableFrontendPlugin< }; }>; 'page:devtools': ExtensionDefinition<{ - kind: 'page'; - name: undefined; config: { path: string | undefined; }; @@ -76,7 +77,17 @@ const _default: OverridableFrontendPlugin< optional: true; } >; - inputs: {}; + inputs: { + routes: ExtensionInput< + ConfigurableExtensionDataRef, + { + singleton: false; + optional: true; + } + >; + }; + kind: 'page'; + name: undefined; params: { defaultPath?: [Error: `Use the 'path' param instead`]; path: string; diff --git a/plugins/devtools/report.api.md b/plugins/devtools/report.api.md index e7a42f4377..c3434c0219 100644 --- a/plugins/devtools/report.api.md +++ b/plugins/devtools/report.api.md @@ -4,6 +4,7 @@ ```ts import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { DevToolsRouteData } from '@backstage/plugin-devtools-react'; import { ElementType } from 'react'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { ReactNode } from 'react'; @@ -27,7 +28,15 @@ export type DevToolsLayoutProps = { }; // @public (undocumented) -export const DevToolsPage: () => JSX_2.Element; +export const DevToolsPage: ({ + extensionRoutes, +}: DevToolsPageProps) => JSX_2.Element; + +// @public (undocumented) +export interface DevToolsPageProps { + // (undocumented) + extensionRoutes?: DevToolsRouteData[]; +} // @public (undocumented) export const devToolsPlugin: BackstagePlugin< diff --git a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx index 53ba1fd0dd..682983f46f 100644 --- a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx +++ b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx @@ -23,16 +23,10 @@ import { ConfigContent } from '../Content/ConfigContent'; import { DevToolsLayout } from '../DevToolsLayout'; import { InfoContent } from '../Content/InfoContent'; import { RequirePermission } from '@backstage/plugin-permission-react'; -import { DevToolsRouteData } from '@backstage/plugin-devtools-react'; - -export interface DefaultDevToolsPageProps { - extensionRoutes?: DevToolsRouteData[]; -} +import { DevToolsPageProps } from '../DevToolsPage'; /** @public */ -export const DefaultDevToolsPage = ({ - extensionRoutes, -}: DefaultDevToolsPageProps) => ( +export const DefaultDevToolsPage = ({ extensionRoutes }: DevToolsPageProps) => ( diff --git a/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx index 04db0665a3..f76017b89e 100644 --- a/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx +++ b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx @@ -18,6 +18,9 @@ import { useOutlet } from 'react-router-dom'; import { DefaultDevToolsPage } from '../DefaultDevToolsPage'; import { DevToolsRouteData } from '@backstage/plugin-devtools-react'; +/** + @public + */ export interface DevToolsPageProps { extensionRoutes?: DevToolsRouteData[]; } diff --git a/plugins/devtools/src/components/DevToolsPage/index.ts b/plugins/devtools/src/components/DevToolsPage/index.ts index 88d8bc06fa..743559f282 100644 --- a/plugins/devtools/src/components/DevToolsPage/index.ts +++ b/plugins/devtools/src/components/DevToolsPage/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { DevToolsPage } from './DevToolsPage'; +export { DevToolsPage, type DevToolsPageProps } from './DevToolsPage'; diff --git a/plugins/devtools/src/components/index.ts b/plugins/devtools/src/components/index.ts index 9938866bcc..f79797d65a 100644 --- a/plugins/devtools/src/components/index.ts +++ b/plugins/devtools/src/components/index.ts @@ -16,3 +16,4 @@ export * from './Content'; export * from './DevToolsLayout'; +export { type DevToolsPageProps } from './DevToolsPage'; From 9e4729ad956b9c319aa8d06f5c9b6544b78ff374 Mon Sep 17 00:00:00 2001 From: secustor Date: Wed, 3 Sep 2025 19:07:48 +0200 Subject: [PATCH 007/202] docs: add instructions to readmes Signed-off-by: secustor --- .../catalog-unprocessed-entities/README.md | 10 +++++-- plugins/devtools/README.md | 27 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-unprocessed-entities/README.md b/plugins/catalog-unprocessed-entities/README.md index ebadbb7054..3918d97e50 100644 --- a/plugins/catalog-unprocessed-entities/README.md +++ b/plugins/catalog-unprocessed-entities/README.md @@ -52,16 +52,22 @@ Import `catalogUnprocessedEntitiesPlugin` in your `App.tsx` and add it to your a ```typescript import catalogUnprocessedEntitiesPlugin from '@backstage/plugin-catalog-unprocessed-entities'; - -// ... +import { unprocessedEntitiesDevToolsRoute } from '@backstage/plugin-catalog-unprocessed-entities/alpha'; export const app = createApp({ features: [ + appFeature, // ... catalogUnprocessedEntitiesPlugin, // ... ], }); + +// Optionally add unprocessed entities route to devtools +const appFeature = createFrontendModule({ + pluginId: 'app', + extensions: [unprocessedEntitiesDevToolsRoute], +}); ``` ## Customization diff --git a/plugins/devtools/README.md b/plugins/devtools/README.md index 3d82190c2f..1112de0d36 100644 --- a/plugins/devtools/README.md +++ b/plugins/devtools/README.md @@ -160,6 +160,33 @@ You can also add tabs to show content from other plugins that fit well with the #### Catalog Unprocessed Entities Tab +##### New Frontend System + +Create an extension and/or load a 3rd party extension to add additional tabs. + +```tsx +import { DevToolsRouteBlueprint } from '@backstage/plugin-devtools-react'; + +export const unprocessedEntitiesDevToolsRoute = DevToolsRouteBlueprint.make({ + params: { + path: 'unprocessed-entities', + title: 'Unprocessed Entities', + loader: () => + import('../components/UnprocessedEntities').then( + ({ UnprocessedEntitiesContent }) => + createElement(UnprocessedEntitiesContent), + ), + }, +}); + +const appFeature = createFrontendModule({ + pluginId: 'app', + extensions: [unprocessedEntitiesDevToolsRoute], +}); +``` + +##### Old System + Here's how to add the Catalog Unprocessed Entities tab: 1. Install and setup the [Catalog Unprocessed Entities plugin](https://github.com/backstage/backstage/tree/master/plugins/catalog-unprocessed-entities) as per its documentation From 8ff1cac1d103a2bbf136795c1bd8ded594ad6bf8 Mon Sep 17 00:00:00 2001 From: secustor Date: Wed, 3 Sep 2025 19:12:01 +0200 Subject: [PATCH 008/202] chore: remove private Signed-off-by: secustor --- plugins/devtools-react/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/devtools-react/package.json b/plugins/devtools-react/package.json index 4e96fdb29c..d6ed74c1e5 100644 --- a/plugins/devtools-react/package.json +++ b/plugins/devtools-react/package.json @@ -17,7 +17,6 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, - "private": true, "repository": { "type": "git", "url": "https://github.com/backstage/backstage", From 314f48635df1c78b5cedb2270596329801d86b0e Mon Sep 17 00:00:00 2001 From: secustor Date: Wed, 3 Sep 2025 19:19:10 +0200 Subject: [PATCH 009/202] fix: peer dependencies Signed-off-by: secustor --- plugins/devtools-react/package.json | 8 +++++--- yarn.lock | 24 +++++++++++++----------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/plugins/devtools-react/package.json b/plugins/devtools-react/package.json index d6ed74c1e5..7d0bcaf8ac 100644 --- a/plugins/devtools-react/package.json +++ b/plugins/devtools-react/package.json @@ -47,10 +47,12 @@ "@backstage/cli": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "react": "^16.13.1 || ^17.0.0 || ^18.0.0" + "@testing-library/react": "^14.0.0" }, "peerDependencies": { - "react": "^16.13.1 || ^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0", + "react-router-dom": "^6.3.0" } } diff --git a/yarn.lock b/yarn.lock index 15078530d8..2123b5e26b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5111,9 +5111,11 @@ __metadata: "@material-ui/core": "npm:^4.9.13" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^14.0.0" - react: "npm:^16.13.1 || ^17.0.0 || ^18.0.0" peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + react-router-dom: ^6.3.0 languageName: unknown linkType: soft @@ -43478,15 +43480,6 @@ __metadata: languageName: node linkType: hard -"react@npm:^16.13.1 || ^17.0.0 || ^18.0.0, react@npm:^18.0.2": - version: 18.3.1 - resolution: "react@npm:18.3.1" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10/261137d3f3993eaa2368a83110466fc0e558bc2c7f7ae7ca52d94f03aac945f45146bd85e5f481044db1758a1dbb57879e2fcdd33924e2dde1bdc550ce73f7bf - languageName: node - linkType: hard - "react@npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0": version: 19.1.1 resolution: "react@npm:19.1.1" @@ -43504,6 +43497,15 @@ __metadata: languageName: node linkType: hard +"react@npm:^18.0.2": + version: 18.3.1 + resolution: "react@npm:18.3.1" + dependencies: + loose-envify: "npm:^1.1.0" + checksum: 10/261137d3f3993eaa2368a83110466fc0e558bc2c7f7ae7ca52d94f03aac945f45146bd85e5f481044db1758a1dbb57879e2fcdd33924e2dde1bdc550ce73f7bf + languageName: node + linkType: hard + "read-cmd-shim@npm:^2.0.0": version: 2.0.0 resolution: "read-cmd-shim@npm:2.0.0" From 4c688ff6426ad48a0430084f967eb90fc54c0d14 Mon Sep 17 00:00:00 2001 From: secustor Date: Tue, 9 Sep 2025 14:55:39 +0200 Subject: [PATCH 010/202] fixup peerdependencies Signed-off-by: secustor --- plugins/devtools-react/package.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/devtools-react/package.json b/plugins/devtools-react/package.json index 7d0bcaf8ac..d5c57f7657 100644 --- a/plugins/devtools-react/package.json +++ b/plugins/devtools-react/package.json @@ -47,12 +47,21 @@ "@backstage/cli": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0" + "@testing-library/react": "^14.0.0", + "@types/react": "^18.0.0", + "react": "^18.0.2", + "react-dom": "^18.0.2", + "react-router-dom": "^6.3.0" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0", "react-router-dom": "^6.3.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } } From efee6ad09b8f1739d0f6dcf55b97de525b186067 Mon Sep 17 00:00:00 2001 From: secustor Date: Tue, 9 Sep 2025 15:09:05 +0200 Subject: [PATCH 011/202] run yarn install Signed-off-by: secustor --- yarn.lock | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/yarn.lock b/yarn.lock index 2123b5e26b..24708a5699 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5111,11 +5111,18 @@ __metadata: "@material-ui/core": "npm:^4.9.13" "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^14.0.0" + "@types/react": "npm:^18.0.0" + react: "npm:^18.0.2" + react-dom: "npm:^18.0.2" + react-router-dom: "npm:^6.3.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 react-router-dom: ^6.3.0 + peerDependenciesMeta: + "@types/react": + optional: true languageName: unknown linkType: soft From ed6daceda80db4374bb3e277cd8df2a8ba6f415f Mon Sep 17 00:00:00 2001 From: secustor Date: Tue, 9 Sep 2025 15:23:08 +0200 Subject: [PATCH 012/202] add missing api report Signed-off-by: secustor --- .../report-alpha.api.md | 13 +++++++++++++ .../src/alpha/devToolsRoute.ts | 2 ++ 2 files changed, 15 insertions(+) diff --git a/plugins/catalog-unprocessed-entities/report-alpha.api.md b/plugins/catalog-unprocessed-entities/report-alpha.api.md index 17d28ee165..2786100e1b 100644 --- a/plugins/catalog-unprocessed-entities/report-alpha.api.md +++ b/plugins/catalog-unprocessed-entities/report-alpha.api.md @@ -6,6 +6,8 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { DevToolsRouteBlueprintParams } from '@backstage/plugin-devtools-react'; +import { DevToolsRouteData } from '@backstage/plugin-devtools-react'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; @@ -88,5 +90,16 @@ const _default: OverridableFrontendPlugin< >; export default _default; +// @alpha +export const unprocessedEntitiesDevToolsRoute: ExtensionDefinition<{ + kind: 'devtools-route'; + name: undefined; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: DevToolsRouteBlueprintParams; +}>; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts b/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts index 7e4faa0ac3..27a3eb0d13 100644 --- a/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts +++ b/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts @@ -20,6 +20,8 @@ import { DevToolsRouteBlueprint } from '@backstage/plugin-devtools-react'; /** * DevTools route extension for catalog unprocessed entities * This demonstrates how to create extensions at the app level using the new DevTools extension system + * + * @alpha */ export const unprocessedEntitiesDevToolsRoute = DevToolsRouteBlueprint.make({ params: { From 871888a3a10f1416e115afc3a06ad9260ae048f7 Mon Sep 17 00:00:00 2001 From: Sebastian Poxhofer Date: Sat, 13 Sep 2025 08:45:02 +0200 Subject: [PATCH 013/202] Apply suggestions from code review Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Signed-off-by: Sebastian Poxhofer --- .changeset/slimy-ghosts-rescue.md | 2 +- plugins/devtools-react/README.md | 2 +- plugins/devtools-react/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/slimy-ghosts-rescue.md b/.changeset/slimy-ghosts-rescue.md index e519411341..c732293c54 100644 --- a/.changeset/slimy-ghosts-rescue.md +++ b/.changeset/slimy-ghosts-rescue.md @@ -1,6 +1,6 @@ --- '@backstage/plugin-catalog-unprocessed-entities': patch -'@backstage/plugin-devtools-react': patch +'@backstage/plugin-devtools-react': minor '@backstage/plugin-devtools': patch --- diff --git a/plugins/devtools-react/README.md b/plugins/devtools-react/README.md index a6ca2b4723..fe08b182a7 100644 --- a/plugins/devtools-react/README.md +++ b/plugins/devtools-react/README.md @@ -1,5 +1,5 @@ # @backstage/plugin-devtools-react -Welcome to the web library package for the devtools plugin! +Welcome to the web library package for the `devtools` plugin! _This plugin was created through the Backstage CLI_ diff --git a/plugins/devtools-react/package.json b/plugins/devtools-react/package.json index d5c57f7657..bec1564d80 100644 --- a/plugins/devtools-react/package.json +++ b/plugins/devtools-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-react", - "version": "0.1.0", + "version": "0.0.0", "description": "Web library for the devtools plugin", "backstage": { "role": "web-library", From 579f2ad003bd65359449798d24d2b8229e4b2199 Mon Sep 17 00:00:00 2001 From: secustor Date: Mon, 15 Sep 2025 05:24:18 +0200 Subject: [PATCH 014/202] implement suggestions Signed-off-by: secustor --- plugins/catalog-unprocessed-entities/README.md | 12 ++++++------ plugins/devtools/README.md | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-unprocessed-entities/README.md b/plugins/catalog-unprocessed-entities/README.md index 3918d97e50..65466f978c 100644 --- a/plugins/catalog-unprocessed-entities/README.md +++ b/plugins/catalog-unprocessed-entities/README.md @@ -54,6 +54,12 @@ Import `catalogUnprocessedEntitiesPlugin` in your `App.tsx` and add it to your a import catalogUnprocessedEntitiesPlugin from '@backstage/plugin-catalog-unprocessed-entities'; import { unprocessedEntitiesDevToolsRoute } from '@backstage/plugin-catalog-unprocessed-entities/alpha'; +// Optionally add unprocessed entities route to devtools +const appFeature = createFrontendModule({ + pluginId: 'app', + extensions: [unprocessedEntitiesDevToolsRoute], +}); + export const app = createApp({ features: [ appFeature, @@ -62,12 +68,6 @@ export const app = createApp({ // ... ], }); - -// Optionally add unprocessed entities route to devtools -const appFeature = createFrontendModule({ - pluginId: 'app', - extensions: [unprocessedEntitiesDevToolsRoute], -}); ``` ## Customization diff --git a/plugins/devtools/README.md b/plugins/devtools/README.md index 1112de0d36..f7aff3bd34 100644 --- a/plugins/devtools/README.md +++ b/plugins/devtools/README.md @@ -164,6 +164,10 @@ You can also add tabs to show content from other plugins that fit well with the Create an extension and/or load a 3rd party extension to add additional tabs. +```shell +yarn --cwd plugins/ add @backstage/plugin-devtools-react +``` + ```tsx import { DevToolsRouteBlueprint } from '@backstage/plugin-devtools-react'; From eda37c161dbd3ad175da165486eaf9beb4255d4e Mon Sep 17 00:00:00 2001 From: secustor Date: Tue, 16 Sep 2025 22:22:58 +0200 Subject: [PATCH 015/202] implement suggestions Signed-off-by: secustor --- packages/app-next/src/App.tsx | 4 +- .../{devToolsRoute.ts => devToolsRoute.tsx} | 28 +++++----- .../src/alpha/index.ts | 2 +- ...print.tsx => devToolsContentBlueprint.tsx} | 53 +++++++++++++------ ...vToolsRouteDataRef.ts => extensionData.ts} | 26 ++------- plugins/devtools-react/src/index.ts | 8 +-- plugins/devtools/src/alpha/plugin.tsx | 23 ++++---- .../DefaultDevToolsPage.tsx | 16 +++--- .../components/DevToolsPage/DevToolsPage.tsx | 9 ++-- 9 files changed, 83 insertions(+), 86 deletions(-) rename plugins/catalog-unprocessed-entities/src/alpha/{devToolsRoute.ts => devToolsRoute.tsx} (50%) rename plugins/devtools-react/src/{devToolsRouteBlueprint.tsx => devToolsContentBlueprint.tsx} (56%) rename plugins/devtools-react/src/{devToolsRouteDataRef.ts => extensionData.ts} (59%) diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index 92d1f06d6a..af0c3a972d 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -46,7 +46,7 @@ import { convertLegacyEntityContentExtension } from '@backstage/plugin-catalog-r import { pluginInfoResolver } from './pluginInfoResolver'; import { appModuleNav } from './modules/appModuleNav'; import devtoolsPlugin from '@backstage/plugin-devtools/alpha'; -import { unprocessedEntitiesDevToolsRoute } from '@backstage/plugin-catalog-unprocessed-entities/alpha'; +import { unprocessedEntitiesDevToolsContent } from '@backstage/plugin-catalog-unprocessed-entities/alpha'; /* @@ -115,7 +115,7 @@ const customHomePageModule = createFrontendModule({ const notFoundErrorPageModule = createFrontendModule({ pluginId: 'app', - extensions: [notFoundErrorPage, unprocessedEntitiesDevToolsRoute], + extensions: [notFoundErrorPage, unprocessedEntitiesDevToolsContent], }); const collectedLegacyPlugins = convertLegacyAppRoot( diff --git a/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts b/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.tsx similarity index 50% rename from plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts rename to plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.tsx index 27a3eb0d13..e2c652f21a 100644 --- a/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.ts +++ b/plugins/catalog-unprocessed-entities/src/alpha/devToolsRoute.tsx @@ -14,23 +14,23 @@ * limitations under the License. */ -import { createElement } from 'react'; -import { DevToolsRouteBlueprint } from '@backstage/plugin-devtools-react'; +import { DevToolsContentBlueprint } from '@backstage/plugin-devtools-react'; +import { compatWrapper } from '@backstage/core-compat-api'; /** - * DevTools route extension for catalog unprocessed entities - * This demonstrates how to create extensions at the app level using the new DevTools extension system + * DevTools content for catalog unprocessed entities. * * @alpha */ -export const unprocessedEntitiesDevToolsRoute = DevToolsRouteBlueprint.make({ - params: { - path: 'unprocessed-entities', - title: 'Unprocessed Entities', - loader: () => - import('../components/UnprocessedEntities').then( - ({ UnprocessedEntitiesContent }) => - createElement(UnprocessedEntitiesContent), - ), +export const unprocessedEntitiesDevToolsContent = DevToolsContentBlueprint.make( + { + params: { + path: 'unprocessed-entities', + title: 'Unprocessed Entities', + loader: () => + import('../components/UnprocessedEntities').then(m => + compatWrapper(), + ), + }, }, -}); +); diff --git a/plugins/catalog-unprocessed-entities/src/alpha/index.ts b/plugins/catalog-unprocessed-entities/src/alpha/index.ts index 5738bb8a59..13c527949f 100644 --- a/plugins/catalog-unprocessed-entities/src/alpha/index.ts +++ b/plugins/catalog-unprocessed-entities/src/alpha/index.ts @@ -15,4 +15,4 @@ */ export { default } from './plugin'; -export { unprocessedEntitiesDevToolsRoute } from './devToolsRoute'; +export { unprocessedEntitiesDevToolsContent } from './devToolsRoute'; diff --git a/plugins/devtools-react/src/devToolsRouteBlueprint.tsx b/plugins/devtools-react/src/devToolsContentBlueprint.tsx similarity index 56% rename from plugins/devtools-react/src/devToolsRouteBlueprint.tsx rename to plugins/devtools-react/src/devToolsContentBlueprint.tsx index a048641418..835599dff3 100644 --- a/plugins/devtools-react/src/devToolsRouteBlueprint.tsx +++ b/plugins/devtools-react/src/devToolsContentBlueprint.tsx @@ -15,11 +15,13 @@ */ import { + coreExtensionData, createExtensionBlueprint, ExtensionBoundary, + RouteRef, } from '@backstage/frontend-plugin-api'; -import { devToolsRouteDataRef } from './devToolsRouteDataRef'; import { JSX } from 'react'; +import { contentTitleDataRef } from './extensionData'; /** * Parameters for creating a DevTools route extension @@ -29,6 +31,7 @@ export interface DevToolsRouteBlueprintParams { path: string; title: string; loader: () => Promise; + routeRef?: RouteRef; } /** @@ -46,22 +49,38 @@ export interface DevToolsRouteBlueprintParams { * ``` * @public */ -export const DevToolsRouteBlueprint = createExtensionBlueprint({ - kind: 'devtools-route', - attachTo: { id: 'page:devtools', input: 'routes' }, - output: [devToolsRouteDataRef], - factory(params: DevToolsRouteBlueprintParams, { node }) { - const children = ExtensionBoundary.lazy(node, params.loader); - - return [ - devToolsRouteDataRef({ - path: params.path, - title: params.title, - children, - }), - ]; - }, +export const DevToolsContentBlueprint = createExtensionBlueprint({ + kind: 'devtools-content', + attachTo: { id: 'page:devtools', input: 'contents' }, + output: [ + coreExtensionData.reactElement, + coreExtensionData.routePath, + coreExtensionData.routeRef.optional(), + contentTitleDataRef, + ], dataRefs: { - route: devToolsRouteDataRef, + title: contentTitleDataRef, + }, + config: { + schema: { + path: z => z.string().optional(), + title: z => z.string().optional(), + }, + }, + *factory(params: DevToolsRouteBlueprintParams, { node, config }) { + const path = config.path ?? params.path; + const title = config.title ?? params.title; + + yield coreExtensionData.reactElement( + ExtensionBoundary.lazy(node, params.loader), + ); + + yield coreExtensionData.routePath(path); + + yield contentTitleDataRef(title); + + if (params.routeRef) { + yield coreExtensionData.routeRef(params.routeRef); + } }, }); diff --git a/plugins/devtools-react/src/devToolsRouteDataRef.ts b/plugins/devtools-react/src/extensionData.ts similarity index 59% rename from plugins/devtools-react/src/devToolsRouteDataRef.ts rename to plugins/devtools-react/src/extensionData.ts index d6c506505a..62821c3e12 100644 --- a/plugins/devtools-react/src/devToolsRouteDataRef.ts +++ b/plugins/devtools-react/src/extensionData.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,25 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { createExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { JSX } from 'react'; -/** - * Represents a DevTools route that can be contributed by extensions - * @public - */ -export interface DevToolsRouteData { - path: string; - title: string; - children: JSX.Element; -} - -/** - * Extension data reference for DevTools routes - * @public - */ -export const devToolsRouteDataRef = - createExtensionDataRef().with({ - id: 'devtools.route', - }); +/** @internal */ +export const contentTitleDataRef = createExtensionDataRef().with({ + id: 'devtools.content-title', +}); diff --git a/plugins/devtools-react/src/index.ts b/plugins/devtools-react/src/index.ts index 4adcfb39ad..18b17387c1 100644 --- a/plugins/devtools-react/src/index.ts +++ b/plugins/devtools-react/src/index.ts @@ -19,11 +19,7 @@ * * @packageDocumentation */ -export { - type DevToolsRouteData, - devToolsRouteDataRef, -} from './devToolsRouteDataRef'; export { type DevToolsRouteBlueprintParams, - DevToolsRouteBlueprint, -} from './devToolsRouteBlueprint.tsx'; + DevToolsContentBlueprint, +} from './devToolsContentBlueprint.tsx'; diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index fb7c627bdc..7c020639bf 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -22,6 +22,7 @@ import { PageBlueprint, NavItemBlueprint, createExtensionInput, + coreExtensionData, } from '@backstage/frontend-plugin-api'; import { devToolsApiRef, DevToolsClient } from '../api'; @@ -31,7 +32,6 @@ import { } from '@backstage/core-compat-api'; import BuildIcon from '@material-ui/icons/Build'; import { rootRouteRef } from '../routes'; -import { devToolsRouteDataRef } from '@backstage/plugin-devtools-react'; /** @alpha */ export const devToolsApi = ApiBlueprint.make({ @@ -50,9 +50,16 @@ export const devToolsApi = ApiBlueprint.make({ /** @alpha */ export const devToolsPage = PageBlueprint.makeWithOverrides({ inputs: { - routes: createExtensionInput([devToolsRouteDataRef], { - optional: true, - }), + contents: createExtensionInput( + [ + coreExtensionData.reactElement, + coreExtensionData.routePath, + coreExtensionData.routeRef.optional(), + ], + { + optional: true, + }, + ), }, factory(originalFactory, { inputs }) { return originalFactory({ @@ -60,13 +67,7 @@ export const devToolsPage = PageBlueprint.makeWithOverrides({ routeRef: convertLegacyRouteRef(rootRouteRef), loader: () => import('../components/DevToolsPage').then(m => - compatWrapper( - - route.get(devToolsRouteDataRef), - )} - />, - ), + compatWrapper(), ), }); }, diff --git a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx index 682983f46f..b2c1457161 100644 --- a/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx +++ b/plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx @@ -19,14 +19,14 @@ import { devToolsInfoReadPermission, } from '@backstage/plugin-devtools-common'; -import { ConfigContent } from '../Content/ConfigContent'; +import { ConfigContent } from '../Content'; import { DevToolsLayout } from '../DevToolsLayout'; -import { InfoContent } from '../Content/InfoContent'; +import { InfoContent } from '../Content'; import { RequirePermission } from '@backstage/plugin-permission-react'; import { DevToolsPageProps } from '../DevToolsPage'; /** @public */ -export const DefaultDevToolsPage = ({ extensionRoutes }: DevToolsPageProps) => ( +export const DefaultDevToolsPage = ({ extensions }: DevToolsPageProps) => ( @@ -38,13 +38,13 @@ export const DefaultDevToolsPage = ({ extensionRoutes }: DevToolsPageProps) => ( - {extensionRoutes?.map((route, index) => ( + {extensions?.map((extension, index) => ( - {route.children} + {extension.children} ))} diff --git a/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx index f76017b89e..8f84ed4a9b 100644 --- a/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx +++ b/plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx @@ -16,19 +16,16 @@ import { useOutlet } from 'react-router-dom'; import { DefaultDevToolsPage } from '../DefaultDevToolsPage'; -import { DevToolsRouteData } from '@backstage/plugin-devtools-react'; /** @public */ export interface DevToolsPageProps { - extensionRoutes?: DevToolsRouteData[]; + extensions?: any[]; } -export const DevToolsPage = ({ extensionRoutes }: DevToolsPageProps) => { +export const DevToolsPage = ({ extensions }: DevToolsPageProps) => { const outlet = useOutlet(); - return ( - <>{outlet || } - ); + return <>{outlet || }; }; From 81242cb5d987e9a155fc0a0b64b87cc893e1b5f2 Mon Sep 17 00:00:00 2001 From: secustor Date: Fri, 3 Oct 2025 13:16:03 +0200 Subject: [PATCH 016/202] add missing export Signed-off-by: secustor --- .../src/devToolsContentBlueprint.tsx | 1 - plugins/devtools-react/src/extensionData.ts | 2 +- plugins/devtools-react/src/index.ts | 1 + plugins/devtools/src/alpha/plugin.tsx | 16 ++++++++++++---- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/plugins/devtools-react/src/devToolsContentBlueprint.tsx b/plugins/devtools-react/src/devToolsContentBlueprint.tsx index e1c4c4ebfb..835599dff3 100644 --- a/plugins/devtools-react/src/devToolsContentBlueprint.tsx +++ b/plugins/devtools-react/src/devToolsContentBlueprint.tsx @@ -68,7 +68,6 @@ export const DevToolsContentBlueprint = createExtensionBlueprint({ }, }, *factory(params: DevToolsRouteBlueprintParams, { node, config }) { - console.log('DevToolsContentBlueprint', params, config); const path = config.path ?? params.path; const title = config.title ?? params.title; diff --git a/plugins/devtools-react/src/extensionData.ts b/plugins/devtools-react/src/extensionData.ts index 62821c3e12..33754f6556 100644 --- a/plugins/devtools-react/src/extensionData.ts +++ b/plugins/devtools-react/src/extensionData.ts @@ -15,7 +15,7 @@ */ import { createExtensionDataRef } from '@backstage/frontend-plugin-api'; -/** @internal */ +/** @public */ export const contentTitleDataRef = createExtensionDataRef().with({ id: 'devtools.content-title', }); diff --git a/plugins/devtools-react/src/index.ts b/plugins/devtools-react/src/index.ts index 18b17387c1..1a31aa4f09 100644 --- a/plugins/devtools-react/src/index.ts +++ b/plugins/devtools-react/src/index.ts @@ -23,3 +23,4 @@ export { type DevToolsRouteBlueprintParams, DevToolsContentBlueprint, } from './devToolsContentBlueprint.tsx'; +export { contentTitleDataRef } from './extensionData'; diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index 7c020639bf..5628492b90 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -32,6 +32,7 @@ import { } from '@backstage/core-compat-api'; import BuildIcon from '@material-ui/icons/Build'; import { rootRouteRef } from '../routes'; +import { contentTitleDataRef } from '@backstage/plugin-devtools-react'; /** @alpha */ export const devToolsApi = ApiBlueprint.make({ @@ -55,6 +56,7 @@ export const devToolsPage = PageBlueprint.makeWithOverrides({ coreExtensionData.reactElement, coreExtensionData.routePath, coreExtensionData.routeRef.optional(), + contentTitleDataRef, ], { optional: true, @@ -65,10 +67,16 @@ export const devToolsPage = PageBlueprint.makeWithOverrides({ return originalFactory({ path: '/devtools', routeRef: convertLegacyRouteRef(rootRouteRef), - loader: () => - import('../components/DevToolsPage').then(m => - compatWrapper(), - ), + loader: () => { + const extensions = inputs.contents.map(content => ({ + path: content.get(coreExtensionData.routePath), + title: content.get(contentTitleDataRef), + children: content.get(coreExtensionData.reactElement), + })); + return import('../components/DevToolsPage').then(m => + compatWrapper(), + ); + }, }); }, }); From 2fe3b3b91a68eb6f2936dbead6ab2c90d4fcb181 Mon Sep 17 00:00:00 2001 From: secustor Date: Mon, 6 Oct 2025 13:06:24 +0200 Subject: [PATCH 017/202] update api reports Signed-off-by: secustor --- .../report-alpha.api.md | 27 +++++++-- plugins/devtools-react/report.api.md | 58 ++++++++++--------- plugins/devtools/report-alpha.api.md | 14 ++++- plugins/devtools/report.api.md | 7 +-- 4 files changed, 66 insertions(+), 40 deletions(-) diff --git a/plugins/catalog-unprocessed-entities/report-alpha.api.md b/plugins/catalog-unprocessed-entities/report-alpha.api.md index 2786100e1b..1f41fb4081 100644 --- a/plugins/catalog-unprocessed-entities/report-alpha.api.md +++ b/plugins/catalog-unprocessed-entities/report-alpha.api.md @@ -7,7 +7,6 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/frontend-plugin-api'; import { DevToolsRouteBlueprintParams } from '@backstage/plugin-devtools-react'; -import { DevToolsRouteData } from '@backstage/plugin-devtools-react'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; @@ -91,12 +90,28 @@ const _default: OverridableFrontendPlugin< export default _default; // @alpha -export const unprocessedEntitiesDevToolsRoute: ExtensionDefinition<{ - kind: 'devtools-route'; +export const unprocessedEntitiesDevToolsContent: ExtensionDefinition<{ + kind: 'devtools-content'; name: undefined; - config: {}; - configInput: {}; - output: ExtensionDataRef; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + title?: string | undefined; + path?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef; inputs: {}; params: DevToolsRouteBlueprintParams; }>; diff --git a/plugins/devtools-react/report.api.md b/plugins/devtools-react/report.api.md index a6a6b6e69b..b1b6711bab 100644 --- a/plugins/devtools-react/report.api.md +++ b/plugins/devtools-react/report.api.md @@ -3,25 +3,46 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; +import { RouteRef } from '@backstage/frontend-plugin-api'; + +// @public (undocumented) +export const contentTitleDataRef: ConfigurableExtensionDataRef< + string, + 'devtools.content-title', + {} +>; // @public -export const DevToolsRouteBlueprint: ExtensionBlueprint<{ - kind: 'devtools-route'; +export const DevToolsContentBlueprint: ExtensionBlueprint<{ + kind: 'devtools-content'; params: DevToolsRouteBlueprintParams; - output: ExtensionDataRef; + output: + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef; inputs: {}; - config: {}; - configInput: {}; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + title?: string | undefined; + path?: string | undefined; + }; dataRefs: { - route: ConfigurableExtensionDataRef< - DevToolsRouteData, - 'devtools.route', - {} - >; + title: ConfigurableExtensionDataRef; }; }>; @@ -32,23 +53,8 @@ export interface DevToolsRouteBlueprintParams { // (undocumented) path: string; // (undocumented) - title: string; -} - -// @public -export interface DevToolsRouteData { - // (undocumented) - children: JSX_2.Element; - // (undocumented) - path: string; + routeRef?: RouteRef; // (undocumented) title: string; } - -// @public -export const devToolsRouteDataRef: ConfigurableExtensionDataRef< - DevToolsRouteData, - 'devtools.route', - {} ->; ``` diff --git a/plugins/devtools/report-alpha.api.md b/plugins/devtools/report-alpha.api.md index d5b87ce96b..42d5d3a26c 100644 --- a/plugins/devtools/report-alpha.api.md +++ b/plugins/devtools/report-alpha.api.md @@ -7,7 +7,6 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { DevToolsRouteData } from '@backstage/plugin-devtools-react'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; @@ -78,8 +77,17 @@ const _default: OverridableFrontendPlugin< } >; inputs: { - routes: ExtensionInput< - ConfigurableExtensionDataRef, + contents: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef, { singleton: false; optional: true; diff --git a/plugins/devtools/report.api.md b/plugins/devtools/report.api.md index c3434c0219..70fbcaadb7 100644 --- a/plugins/devtools/report.api.md +++ b/plugins/devtools/report.api.md @@ -4,7 +4,6 @@ ```ts import { BackstagePlugin } from '@backstage/core-plugin-api'; -import { DevToolsRouteData } from '@backstage/plugin-devtools-react'; import { ElementType } from 'react'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { ReactNode } from 'react'; @@ -28,14 +27,12 @@ export type DevToolsLayoutProps = { }; // @public (undocumented) -export const DevToolsPage: ({ - extensionRoutes, -}: DevToolsPageProps) => JSX_2.Element; +export const DevToolsPage: ({ extensions }: DevToolsPageProps) => JSX_2.Element; // @public (undocumented) export interface DevToolsPageProps { // (undocumented) - extensionRoutes?: DevToolsRouteData[]; + extensions?: any[]; } // @public (undocumented) From 45c2618e1f8a58e6c61751fac1653f9e07df6712 Mon Sep 17 00:00:00 2001 From: secustor Date: Wed, 22 Oct 2025 22:32:29 +0200 Subject: [PATCH 018/202] implement change requests Signed-off-by: secustor --- .../catalog-unprocessed-entities/README.md | 4 +-- .../report-alpha.api.md | 6 ++--- plugins/devtools-react/report.api.md | 18 +++---------- .../src/devToolsContentBlueprint.tsx | 16 +++++------- plugins/devtools-react/src/extensionData.ts | 21 --------------- plugins/devtools-react/src/index.ts | 3 +-- plugins/devtools/README.md | 26 ++++++++++--------- plugins/devtools/report-alpha.api.md | 7 ++--- plugins/devtools/src/alpha/plugin.tsx | 5 ++-- 9 files changed, 36 insertions(+), 70 deletions(-) delete mode 100644 plugins/devtools-react/src/extensionData.ts diff --git a/plugins/catalog-unprocessed-entities/README.md b/plugins/catalog-unprocessed-entities/README.md index 65466f978c..98bdf953d2 100644 --- a/plugins/catalog-unprocessed-entities/README.md +++ b/plugins/catalog-unprocessed-entities/README.md @@ -52,12 +52,12 @@ Import `catalogUnprocessedEntitiesPlugin` in your `App.tsx` and add it to your a ```typescript import catalogUnprocessedEntitiesPlugin from '@backstage/plugin-catalog-unprocessed-entities'; -import { unprocessedEntitiesDevToolsRoute } from '@backstage/plugin-catalog-unprocessed-entities/alpha'; +import { unprocessedEntitiesDevToolsContent } from '@backstage/plugin-catalog-unprocessed-entities/alpha'; // Optionally add unprocessed entities route to devtools const appFeature = createFrontendModule({ pluginId: 'app', - extensions: [unprocessedEntitiesDevToolsRoute], + extensions: [unprocessedEntitiesDevToolsContent], }); export const app = createApp({ diff --git a/plugins/catalog-unprocessed-entities/report-alpha.api.md b/plugins/catalog-unprocessed-entities/report-alpha.api.md index 1f41fb4081..a5d73ab4fa 100644 --- a/plugins/catalog-unprocessed-entities/report-alpha.api.md +++ b/plugins/catalog-unprocessed-entities/report-alpha.api.md @@ -6,7 +6,7 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/frontend-plugin-api'; -import { DevToolsRouteBlueprintParams } from '@backstage/plugin-devtools-react'; +import { DevToolsContentBlueprintParams } from '@backstage/plugin-devtools-react'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; @@ -111,9 +111,9 @@ export const unprocessedEntitiesDevToolsContent: ExtensionDefinition<{ optional: true; } > - | ExtensionDataRef; + | ExtensionDataRef; inputs: {}; - params: DevToolsRouteBlueprintParams; + params: DevToolsContentBlueprintParams; }>; // (No @packageDocumentation comment for this package) diff --git a/plugins/devtools-react/report.api.md b/plugins/devtools-react/report.api.md index b1b6711bab..d1b1a42250 100644 --- a/plugins/devtools-react/report.api.md +++ b/plugins/devtools-react/report.api.md @@ -4,23 +4,15 @@ ```ts import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; -// @public (undocumented) -export const contentTitleDataRef: ConfigurableExtensionDataRef< - string, - 'devtools.content-title', - {} ->; - // @public export const DevToolsContentBlueprint: ExtensionBlueprint<{ kind: 'devtools-content'; - params: DevToolsRouteBlueprintParams; + params: DevToolsContentBlueprintParams; output: | ExtensionDataRef | ExtensionDataRef @@ -31,7 +23,7 @@ export const DevToolsContentBlueprint: ExtensionBlueprint<{ optional: true; } > - | ExtensionDataRef; + | ExtensionDataRef; inputs: {}; config: { path: string | undefined; @@ -41,13 +33,11 @@ export const DevToolsContentBlueprint: ExtensionBlueprint<{ title?: string | undefined; path?: string | undefined; }; - dataRefs: { - title: ConfigurableExtensionDataRef; - }; + dataRefs: never; }>; // @public -export interface DevToolsRouteBlueprintParams { +export interface DevToolsContentBlueprintParams { // (undocumented) loader: () => Promise; // (undocumented) diff --git a/plugins/devtools-react/src/devToolsContentBlueprint.tsx b/plugins/devtools-react/src/devToolsContentBlueprint.tsx index 835599dff3..9c11916a77 100644 --- a/plugins/devtools-react/src/devToolsContentBlueprint.tsx +++ b/plugins/devtools-react/src/devToolsContentBlueprint.tsx @@ -21,13 +21,12 @@ import { RouteRef, } from '@backstage/frontend-plugin-api'; import { JSX } from 'react'; -import { contentTitleDataRef } from './extensionData'; /** * Parameters for creating a DevTools route extension * @public */ -export interface DevToolsRouteBlueprintParams { +export interface DevToolsContentBlueprintParams { path: string; title: string; loader: () => Promise; @@ -35,11 +34,11 @@ export interface DevToolsRouteBlueprintParams { } /** - * Extension blueprint for creating DevTools routes + * Extension blueprint for creating DevTools content pages (appearing as tabs) * * @example * ```tsx - * const myDevToolsRoute = DevToolsRouteBlueprint.make({ + * const myDevToolsRoute = DevToolsContentBlueprint.make({ * params: { * path: 'my-feature', * title: 'My Feature', @@ -56,18 +55,15 @@ export const DevToolsContentBlueprint = createExtensionBlueprint({ coreExtensionData.reactElement, coreExtensionData.routePath, coreExtensionData.routeRef.optional(), - contentTitleDataRef, + coreExtensionData.title, ], - dataRefs: { - title: contentTitleDataRef, - }, config: { schema: { path: z => z.string().optional(), title: z => z.string().optional(), }, }, - *factory(params: DevToolsRouteBlueprintParams, { node, config }) { + *factory(params: DevToolsContentBlueprintParams, { node, config }) { const path = config.path ?? params.path; const title = config.title ?? params.title; @@ -77,7 +73,7 @@ export const DevToolsContentBlueprint = createExtensionBlueprint({ yield coreExtensionData.routePath(path); - yield contentTitleDataRef(title); + yield coreExtensionData.title(title); if (params.routeRef) { yield coreExtensionData.routeRef(params.routeRef); diff --git a/plugins/devtools-react/src/extensionData.ts b/plugins/devtools-react/src/extensionData.ts deleted file mode 100644 index 33754f6556..0000000000 --- a/plugins/devtools-react/src/extensionData.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { createExtensionDataRef } from '@backstage/frontend-plugin-api'; - -/** @public */ -export const contentTitleDataRef = createExtensionDataRef().with({ - id: 'devtools.content-title', -}); diff --git a/plugins/devtools-react/src/index.ts b/plugins/devtools-react/src/index.ts index 1a31aa4f09..822b885769 100644 --- a/plugins/devtools-react/src/index.ts +++ b/plugins/devtools-react/src/index.ts @@ -20,7 +20,6 @@ * @packageDocumentation */ export { - type DevToolsRouteBlueprintParams, + type DevToolsContentBlueprintParams, DevToolsContentBlueprint, } from './devToolsContentBlueprint.tsx'; -export { contentTitleDataRef } from './extensionData'; diff --git a/plugins/devtools/README.md b/plugins/devtools/README.md index f7aff3bd34..aef25ae21c 100644 --- a/plugins/devtools/README.md +++ b/plugins/devtools/README.md @@ -169,23 +169,25 @@ yarn --cwd plugins/ add @backstage/plugin-devtools-react ``` ```tsx -import { DevToolsRouteBlueprint } from '@backstage/plugin-devtools-react'; +import { DevToolsContentBlueprint } from '@backstage/plugin-devtools-react'; -export const unprocessedEntitiesDevToolsRoute = DevToolsRouteBlueprint.make({ - params: { - path: 'unprocessed-entities', - title: 'Unprocessed Entities', - loader: () => - import('../components/UnprocessedEntities').then( - ({ UnprocessedEntitiesContent }) => - createElement(UnprocessedEntitiesContent), - ), +export const unprocessedEntitiesDevToolsContent = DevToolsContentBlueprint.make( + { + params: { + path: 'unprocessed-entities', + title: 'Unprocessed Entities', + loader: () => + import('../components/UnprocessedEntities').then( + ({ UnprocessedEntitiesContent }) => + createElement(UnprocessedEntitiesContent), + ), + }, }, -}); +); const appFeature = createFrontendModule({ pluginId: 'app', - extensions: [unprocessedEntitiesDevToolsRoute], + extensions: [unprocessedEntitiesDevToolsContent], }); ``` diff --git a/plugins/devtools/report-alpha.api.md b/plugins/devtools/report-alpha.api.md index 42d5d3a26c..07c881215c 100644 --- a/plugins/devtools/report-alpha.api.md +++ b/plugins/devtools/report-alpha.api.md @@ -78,6 +78,7 @@ const _default: OverridableFrontendPlugin< >; inputs: { contents: ExtensionInput< + | ConfigurableExtensionDataRef | ConfigurableExtensionDataRef | ConfigurableExtensionDataRef | ConfigurableExtensionDataRef< @@ -86,8 +87,7 @@ const _default: OverridableFrontendPlugin< { optional: true; } - > - | ConfigurableExtensionDataRef, + >, { singleton: false; optional: true; @@ -99,7 +99,8 @@ const _default: OverridableFrontendPlugin< params: { defaultPath?: [Error: `Use the 'path' param instead`]; path: string; - loader: () => Promise; + loader: () => Promise; routeRef?: RouteRef; }; }>; diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index 5628492b90..2cb72498b4 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -32,7 +32,6 @@ import { } from '@backstage/core-compat-api'; import BuildIcon from '@material-ui/icons/Build'; import { rootRouteRef } from '../routes'; -import { contentTitleDataRef } from '@backstage/plugin-devtools-react'; /** @alpha */ export const devToolsApi = ApiBlueprint.make({ @@ -56,7 +55,7 @@ export const devToolsPage = PageBlueprint.makeWithOverrides({ coreExtensionData.reactElement, coreExtensionData.routePath, coreExtensionData.routeRef.optional(), - contentTitleDataRef, + coreExtensionData.title, ], { optional: true, @@ -70,7 +69,7 @@ export const devToolsPage = PageBlueprint.makeWithOverrides({ loader: () => { const extensions = inputs.contents.map(content => ({ path: content.get(coreExtensionData.routePath), - title: content.get(contentTitleDataRef), + title: content.get(coreExtensionData.title), children: content.get(coreExtensionData.reactElement), })); return import('../components/DevToolsPage').then(m => From 5b32df5fd8de46286f9ce6cb23e3549d40dec77e Mon Sep 17 00:00:00 2001 From: secustor Date: Wed, 22 Oct 2025 23:34:39 +0200 Subject: [PATCH 019/202] trigger release for common and backend package because of pluginPackages field Signed-off-by: secustor --- .changeset/slimy-ghosts-rescue.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/slimy-ghosts-rescue.md b/.changeset/slimy-ghosts-rescue.md index c732293c54..620214e0a6 100644 --- a/.changeset/slimy-ghosts-rescue.md +++ b/.changeset/slimy-ghosts-rescue.md @@ -2,6 +2,8 @@ '@backstage/plugin-catalog-unprocessed-entities': patch '@backstage/plugin-devtools-react': minor '@backstage/plugin-devtools': patch +'@backstage/plugin-devtools-backend': patch +'@backstage/plugin-devtools-common': patch --- Add support for adding `unprocessed-entities` and other tabs to `devtools` From 68d88173d8bf60ef19af5d5ef37a6f8fe0d151eb Mon Sep 17 00:00:00 2001 From: secustor Date: Wed, 22 Oct 2025 23:41:23 +0200 Subject: [PATCH 020/202] update example Signed-off-by: secustor --- .../src/devToolsContentBlueprint.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/plugins/devtools-react/src/devToolsContentBlueprint.tsx b/plugins/devtools-react/src/devToolsContentBlueprint.tsx index 9c11916a77..c10d8dade0 100644 --- a/plugins/devtools-react/src/devToolsContentBlueprint.tsx +++ b/plugins/devtools-react/src/devToolsContentBlueprint.tsx @@ -38,12 +38,17 @@ export interface DevToolsContentBlueprintParams { * * @example * ```tsx - * const myDevToolsRoute = DevToolsContentBlueprint.make({ - * params: { - * path: 'my-feature', - * title: 'My Feature', - * loader: () => import('./MyContent').then(m => ({ default: m.MyContent })) - * } + * const myDevToolsContent = DevToolsContentBlueprint.make({ + * { + * params: { + * path: 'my-dev-tools', + * title: 'My DevTools', + * loader: () => + * import('../components/MyDevTools').then(m => + * compatWrapper(), + * ), + * }, + * }, * }); * ``` * @public From 7ffc873b78db6dbdfd7099ed909d9d3538241d92 Mon Sep 17 00:00:00 2001 From: Justin Bryant Date: Mon, 17 Nov 2025 13:05:25 -0500 Subject: [PATCH 021/202] fix(auth): user_created_at migration non constant defaults to remedy SQLiteError Signed-off-by: Justin Bryant --- .changeset/mighty-flowers-travel.md | 5 +++++ .../migrations/20250707164600_user_created_at.js | 14 +++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 .changeset/mighty-flowers-travel.md diff --git a/.changeset/mighty-flowers-travel.md b/.changeset/mighty-flowers-travel.md new file mode 100644 index 0000000000..e79af94d99 --- /dev/null +++ b/.changeset/mighty-flowers-travel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +Fix user_created_at migration causing SQLiteError regarding use of non-constants for defaults diff --git a/plugins/auth-backend/migrations/20250707164600_user_created_at.js b/plugins/auth-backend/migrations/20250707164600_user_created_at.js index eb78cb21b0..386ff577cf 100644 --- a/plugins/auth-backend/migrations/20250707164600_user_created_at.js +++ b/plugins/auth-backend/migrations/20250707164600_user_created_at.js @@ -22,7 +22,19 @@ exports.up = async function up(knex) { await knex.schema.alterTable('user_info', table => { table.renameColumn('exp', 'updated_at'); - table.timestamp('created_at').notNullable().defaultTo(knex.fn.now()); + table.timestamp('created_at').nullable(); + }); + + await knex('user_info').update({ + created_at: knex.fn.now(), + }); + + await knex.schema.alterTable('user_info', table => { + table + .timestamp('created_at') + .notNullable() + .defaultTo(knex.fn.now()) + .alter(); }); }; From 4fb08a1b31b831d7be25f5173a49604271d4a09c Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 3 Dec 2025 23:17:31 +0100 Subject: [PATCH 022/202] techdocs: add support for integrations.awsS3 Signed-off-by: Vincenzo Scamporlino --- .../src/stages/publish/awsS3.test.ts | 164 +++++++++++++++++- .../techdocs-node/src/stages/publish/awsS3.ts | 98 +++++++++-- 2 files changed, 241 insertions(+), 21 deletions(-) diff --git a/plugins/techdocs-node/src/stages/publish/awsS3.test.ts b/plugins/techdocs-node/src/stages/publish/awsS3.test.ts index 6fb855d199..f1133a1f60 100644 --- a/plugins/techdocs-node/src/stages/publish/awsS3.test.ts +++ b/plugins/techdocs-node/src/stages/publish/awsS3.test.ts @@ -270,25 +270,177 @@ describe('AwsS3Publish', () => { expect(getCredProviderMock).toHaveBeenCalledTimes(1); }); - it('should fall back to deprecated method of retrieving credentials', async () => { + it('should use awsS3.credentials if they are provided', async () => { + await jest.isolateModulesAsync(async () => { + jest.doMock('@aws-sdk/client-s3', () => ({ + ...jest.requireActual('@aws-sdk/client-s3'), + S3Client: jest.fn(), + })); + + const { S3Client: MockS3Client } = require('@aws-sdk/client-s3'); + const { AwsS3Publish: IsolatedAwsS3Publish } = require('./awsS3'); + const mockConfig = new ConfigReader({ + techdocs: { + publisher: { + type: 'awsS3', + awsS3: { + credentials: { + accessKeyId: 'accessKeyId', + secretAccessKey: 'secretAccessKey', + }, + bucketName: 'bucketName', + bucketRootPath: '/', + }, + }, + }, + integrations: { + awsS3: [ + { + accessKeyId: 'access-key-from-integrations', + secretAccessKey: 'secret-access-key-from-integrations', + }, + ], + }, + }); + + await IsolatedAwsS3Publish.fromConfig(mockConfig, logger); + expect(getCredProviderMock).toHaveBeenCalledTimes(0); + expect(MockS3Client).toHaveBeenCalledTimes(1); + await expect( + MockS3Client.mock.calls[0][0]!.credentialDefaultProvider!( + undefined!, + )(), + ).resolves.toEqual({ + accessKeyId: 'accessKeyId', + secretAccessKey: 'secretAccessKey', + }); + }); + }); + + it('should use credentials from integrations if awsS3.credentials is not provided', async () => { + await jest.isolateModulesAsync(async () => { + jest.doMock('@aws-sdk/client-s3', () => ({ + ...jest.requireActual('@aws-sdk/client-s3'), + S3Client: jest.fn(), + })); + + const { S3Client: MockS3Client } = require('@aws-sdk/client-s3'); + const { AwsS3Publish: IsolatedAwsS3Publish } = require('./awsS3'); + + const mockConfig = new ConfigReader({ + techdocs: { + publisher: { + type: 'awsS3', + awsS3: { + credentials: {}, + bucketName: 'bucketName', + bucketRootPath: '/', + }, + }, + }, + integrations: { + awsS3: [ + { + accessKeyId: 'access-key-from-integrations', + secretAccessKey: 'secret-access-key-from-integrations', + }, + ], + }, + }); + + await IsolatedAwsS3Publish.fromConfig(mockConfig, logger); + expect(getCredProviderMock).toHaveBeenCalledTimes(0); + expect(MockS3Client).toHaveBeenCalledTimes(1); + await expect( + MockS3Client.mock.calls[0][0]!.credentialDefaultProvider!( + undefined!, + )(), + ).resolves.toEqual({ + accessKeyId: 'access-key-from-integrations', + secretAccessKey: 'secret-access-key-from-integrations', + }); + }); + }); + + it('should retrieve default credentials if multiple integrations are present', async () => { const mockConfig = new ConfigReader({ techdocs: { publisher: { type: 'awsS3', awsS3: { - credentials: { - accessKeyId: 'accessKeyId', - secretAccessKey: 'secretAccessKey', - }, + credentials: {}, bucketName: 'bucketName', bucketRootPath: '/', }, }, }, + integrations: { + awsS3: [ + { + accessKeyId: 'access-key-from-integrations', + secretAccessKey: 'secret-access-key-from-integrations', + }, + { + accessKeyId: 'access-key-from-integrations-2', + secretAccessKey: 'secret-access-key-from-integrations-2', + }, + ], + }, }); await AwsS3Publish.fromConfig(mockConfig, logger); - expect(getCredProviderMock).toHaveBeenCalledTimes(0); + expect(getCredProviderMock).toHaveBeenCalledTimes(1); + }); + + it('should retrieve the target integration if multiple integrations are provided and credentials are not provided', async () => { + await jest.isolateModulesAsync(async () => { + jest.doMock('@aws-sdk/client-s3', () => ({ + ...jest.requireActual('@aws-sdk/client-s3'), + S3Client: jest.fn(), + })); + + const { S3Client: MockS3Client } = require('@aws-sdk/client-s3'); + const { AwsS3Publish: IsolatedAwsS3Publish } = require('./awsS3'); + + const mockConfig = new ConfigReader({ + techdocs: { + publisher: { + type: 'awsS3', + awsS3: { + credentials: { + accessKeyId: 'access-key-from-integrations-2', + }, + bucketName: 'bucketName', + bucketRootPath: '/', + }, + }, + }, + integrations: { + awsS3: [ + { + accessKeyId: 'access-key-from-integrations', + secretAccessKey: 'secret-access-key-from-integrations', + }, + { + accessKeyId: 'access-key-from-integrations-2', + secretAccessKey: 'secret-access-key-from-integrations-2', + }, + ], + }, + }); + + await IsolatedAwsS3Publish.fromConfig(mockConfig, logger); + expect(getCredProviderMock).toHaveBeenCalledTimes(0); + expect(MockS3Client).toHaveBeenCalledTimes(1); + await expect( + MockS3Client.mock.calls[0][0]!.credentialDefaultProvider!( + undefined!, + )(), + ).resolves.toEqual({ + accessKeyId: 'access-key-from-integrations-2', + secretAccessKey: 'secret-access-key-from-integrations-2', + }); + }); }); }); diff --git a/plugins/techdocs-node/src/stages/publish/awsS3.ts b/plugins/techdocs-node/src/stages/publish/awsS3.ts index bd8b96ee14..97aab3d2d9 100644 --- a/plugins/techdocs-node/src/stages/publish/awsS3.ts +++ b/plugins/techdocs-node/src/stages/publish/awsS3.ts @@ -139,12 +139,19 @@ export class AwsS3Publish implements PublisherBase { const credentialsConfig = config.getOptionalConfig( 'techdocs.publisher.awsS3.credentials', ); + const credsManager = DefaultAwsCredentialsManager.fromConfig(config); + + const awsS3IntegrationConfig = + config.getOptionalConfigArray('integrations.awsS3'); + const sdkCredentialProvider = await AwsS3Publish.buildCredentials( credsManager, accountId, credentialsConfig, region, + awsS3IntegrationConfig, + logger, ); // AWS endpoint is an optional config. If missing, the default endpoint is built from @@ -213,8 +220,10 @@ export class AwsS3Publish implements PublisherBase { private static async buildCredentials( credsManager: AwsCredentialsManager, accountId?: string, - config?: Config, + credentialsConfig?: Config, region?: string, + awsS3IntegrationConfig?: Config[], + logger: LoggerService, ): Promise { // Pull credentials for the specified account ID from the 'aws' config section if (accountId) { @@ -222,21 +231,14 @@ export class AwsS3Publish implements PublisherBase { .sdkCredentialProvider; } - // Fall back to the default credential chain if neither account ID - // nor explicit credentials are provided - if (!config) { - return (await credsManager.getCredentialProvider()).sdkCredentialProvider; - } + const explicitCredentials = await AwsS3Publish.getExplicitCredentials({ + credsManager, + credentialsConfig, + awsS3IntegrationConfig, + logger, + }); - // Pull credentials from the techdocs config section (deprecated) - const accessKeyId = config.getOptionalString('accessKeyId'); - const secretAccessKey = config.getOptionalString('secretAccessKey'); - const explicitCredentials: AwsCredentialIdentityProvider = - accessKeyId && secretAccessKey - ? AwsS3Publish.buildStaticCredentials(accessKeyId, secretAccessKey) - : (await credsManager.getCredentialProvider()).sdkCredentialProvider; - - const roleArn = config.getOptionalString('roleArn'); + const roleArn = credentialsConfig?.getOptionalString('roleArn'); if (roleArn) { return fromTemporaryCredentials({ masterCredentials: explicitCredentials, @@ -251,6 +253,72 @@ export class AwsS3Publish implements PublisherBase { return explicitCredentials; } + private static async getExplicitCredentials({ + credentialsConfig, + awsS3IntegrationConfig, + credsManager, + logger, + }: { + credentialsConfig?: Config; + awsS3IntegrationConfig?: Config[]; + credsManager: AwsCredentialsManager; + logger: LoggerService; + }): Promise { + const accessKeyId = credentialsConfig?.getOptionalString('accessKeyId'); + const secretAccessKey = + credentialsConfig?.getOptionalString('secretAccessKey'); + + if (accessKeyId && secretAccessKey) { + return AwsS3Publish.buildStaticCredentials(accessKeyId, secretAccessKey); + } + if (awsS3IntegrationConfig && awsS3IntegrationConfig.length > 0) { + if (awsS3IntegrationConfig.length === 1) { + const singleAwsS3IntegrationConfig = awsS3IntegrationConfig[0]; + + const singleAwsS3IntegrationAccessKeyId = + singleAwsS3IntegrationConfig.getOptionalString('accessKeyId'); + const singleAwsS3IntegrationSecretAccessKey = + singleAwsS3IntegrationConfig.getOptionalString('secretAccessKey'); + + if ( + singleAwsS3IntegrationAccessKeyId && + singleAwsS3IntegrationSecretAccessKey + ) { + return AwsS3Publish.buildStaticCredentials( + singleAwsS3IntegrationAccessKeyId, + singleAwsS3IntegrationSecretAccessKey, + ); + } + } else { + if (accessKeyId) { + const targetAwsS3IntegrationConfig = awsS3IntegrationConfig.find( + c => c.getOptionalString('accessKeyId') === accessKeyId, + ); + + if (!targetAwsS3IntegrationConfig) { + logger.warn( + `No AWS S3 integration config under integrations.awsS3 found for access key id ${accessKeyId}.`, + ); + } + const targetAwsS3IntegrationAccessKeyId = + targetAwsS3IntegrationConfig?.getOptionalString('accessKeyId'); + const targetAwsS3IntegrationSecretAccessKey = + targetAwsS3IntegrationConfig?.getOptionalString('secretAccessKey'); + if ( + targetAwsS3IntegrationAccessKeyId && + targetAwsS3IntegrationSecretAccessKey + ) { + return AwsS3Publish.buildStaticCredentials( + targetAwsS3IntegrationAccessKeyId, + targetAwsS3IntegrationSecretAccessKey, + ); + } + } + } + } + return (await credsManager.getCredentialProvider()).sdkCredentialProvider; + } + /** * Check if the defined bucket exists. Being able to connect means the configuration is good * and the storage client will work. From 63c459c961173d02011cade1327a63658156271a Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 3 Dec 2025 23:32:03 +0100 Subject: [PATCH 023/202] techdocs: changeset integrations Signed-off-by: Vincenzo Scamporlino --- .changeset/whole-dodos-matter.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .changeset/whole-dodos-matter.md diff --git a/.changeset/whole-dodos-matter.md b/.changeset/whole-dodos-matter.md new file mode 100644 index 0000000000..1d055d306f --- /dev/null +++ b/.changeset/whole-dodos-matter.md @@ -0,0 +1,12 @@ +--- +'@backstage/plugin-techdocs-node': minor +--- + +It's now possible to use the credentials from the `integrations.awsS3` config to authenticate with AWS S3. The new priority is: + +1. `aws.accounts` +2. `techdocs.publisher.awsS3.credentials` +3. `integrations.awsS3` +4. Default credential chain + +In case of multiple `integrations.awsS3` are present, the target integration is determined by the `accessKeyId` in `techdocs.publisher.awsS3.credentials` if provided. Otherwise, the default credential chain is used. From b5914dfe6df826e268ecd70669e2af0ecb438b79 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 4 Dec 2025 10:07:22 +0100 Subject: [PATCH 024/202] docs: clarify techdocs s3 buckets integration Signed-off-by: Vincenzo Scamporlino --- docs/features/techdocs/using-cloud-storage.md | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index 42024ffbb2..2d372dc48f 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -172,15 +172,17 @@ TechDocs will publish documentation to this bucket and will fetch files from here to serve documentation in Backstage. Note that the bucket names are globally unique. -Set the config `techdocs.publisher.awsS3.bucketName` in your `app-config.yaml` -to the name of the bucket you just created. +Set the bucket name and region in your `app-config.yaml` to the name of the bucket you just created: ```yaml techdocs: publisher: type: 'awsS3' +/* highlight-add-start */ awsS3: bucketName: 'name-of-techdocs-storage-bucket' + region: 'us-east-1' +/* highlight-add-end */ ``` **3. Create minimal AWS IAM policies to manage TechDocs** @@ -266,7 +268,7 @@ environment automatically by defining appropriate IAM role with access to the bucket. Read more in the [official AWS documentation for using IAM roles](https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html#use-roles). -**4b. Authentication using app-config.yaml** +**4b. Authentication using app-config.yaml via aws.accounts** AWS credentials and region can be provided to the AWS SDK via `app-config.yaml`. If the configs below are present, they will be used over existing `AWS_*` @@ -290,7 +292,45 @@ aws: Refer to the [official AWS documentation for obtaining the credentials](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html). -**4c. Authentication using an assumed role** Users with multiple AWS accounts +**4c. Authentication using app-config.yaml via integrations.awsS3** + +If you already have an [AWS S3 integration](../../integrations/aws-s3/locations.md), you can use it to authenticate with AWS S3: + +```yaml +techdocs: + publisher: + type: 'awsS3' + awsS3: + bucketName: 'name-of-techdocs-storage-bucket' + region: 'eu-west-1' +integrations: + awsS3: + - accessKeyId: ${AWS_ACCESS_KEY_ID} + secretAccessKey: ${AWS_SECRET_ACCESS_KEY} +``` + +This will use the credentials from the integration to authenticate with AWS S3 and it does not require any additional configuration in the `app-config.yaml`. However, **if you have multiple S3 integrations**, you **must** specify the target integration by setting the `accessKeyId` in the `techdocs.publisher.awsS3.credentials` config: + +```yaml +techdocs: + publisher: + type: 'awsS3' + awsS3: + bucketName: 'name-of-techdocs-storage-bucket' + region: 'eu-west-1' +/* highlight-add-start */ + credentials: + accessKeyId: ${AWS_ACCESS_KEY_ID_1} +/* highlight-add-end */ +integrations: + awsS3: + - accessKeyId: ${AWS_ACCESS_KEY_ID_1} + secretAccessKey: ${AWS_SECRET_ACCESS_KEY_1} + - accessKeyId: ${AWS_ACCESS_KEY_ID_2} + secretAccessKey: ${AWS_SECRET_ACCESS_KEY_2} +``` + +**4d. Authentication using an assumed role** Users with multiple AWS accounts may want to use a role for S3 storage that is in a different AWS account. Using the `roleArn` parameter as seen below, you can instruct the TechDocs publisher to assume a role before accessing S3. From 09af4483380e9fb81fc3745e615a6460a85a6223 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 4 Dec 2025 10:19:34 +0100 Subject: [PATCH 025/202] techdocs: moar config tests Signed-off-by: Vincenzo Scamporlino --- .../src/stages/publish/awsS3.test.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/plugins/techdocs-node/src/stages/publish/awsS3.test.ts b/plugins/techdocs-node/src/stages/publish/awsS3.test.ts index f1133a1f60..ae3c732b7a 100644 --- a/plugins/techdocs-node/src/stages/publish/awsS3.test.ts +++ b/plugins/techdocs-node/src/stages/publish/awsS3.test.ts @@ -270,6 +270,59 @@ describe('AwsS3Publish', () => { expect(getCredProviderMock).toHaveBeenCalledTimes(1); }); + it('should use aws.accounts over integrations.awsS3 if both are provided', async () => { + await jest.isolateModulesAsync(async () => { + jest.doMock('@aws-sdk/client-s3', () => ({ + ...jest.requireActual('@aws-sdk/client-s3'), + S3Client: jest.fn(), + })); + + const { S3Client: MockS3Client } = require('@aws-sdk/client-s3'); + const { AwsS3Publish: IsolatedAwsS3Publish } = require('./awsS3'); + const mockConfig = new ConfigReader({ + techdocs: { + publisher: { + type: 'awsS3', + awsS3: { + accountId: '111111111111', + bucketName: 'bucketName', + bucketRootPath: '/', + }, + }, + }, + integrations: { + awsS3: [ + { + accessKeyId: 'access-key-from-integrations', + secretAccessKey: 'secret-access-key-from-integrations', + }, + ], + }, + aws: { + accounts: [ + { + accountId: '111111111111', + accessKeyId: 'access-key-from-aws', + secretAccessKey: 'secret-access-key-from-aws', + }, + ], + }, + }); + + await IsolatedAwsS3Publish.fromConfig(mockConfig, logger); + expect(getCredProviderMock).toHaveBeenCalledTimes(0); + expect(MockS3Client).toHaveBeenCalledTimes(1); + await expect( + MockS3Client.mock.calls[0][0]!.credentialDefaultProvider!( + undefined!, + )(), + ).resolves.toEqual({ + accessKeyId: 'access-key-from-aws', + secretAccessKey: 'secret-access-key-from-aws', + }); + }); + }); + it('should use awsS3.credentials if they are provided', async () => { await jest.isolateModulesAsync(async () => { jest.doMock('@aws-sdk/client-s3', () => ({ From 5a3965849b967918037426faf2cd98864b926fc3 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 4 Dec 2025 10:19:56 +0100 Subject: [PATCH 026/202] techdocs: fix logger param Signed-off-by: Vincenzo Scamporlino --- plugins/techdocs-node/src/stages/publish/awsS3.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs-node/src/stages/publish/awsS3.ts b/plugins/techdocs-node/src/stages/publish/awsS3.ts index 97aab3d2d9..cb5360f037 100644 --- a/plugins/techdocs-node/src/stages/publish/awsS3.ts +++ b/plugins/techdocs-node/src/stages/publish/awsS3.ts @@ -147,11 +147,11 @@ export class AwsS3Publish implements PublisherBase { const sdkCredentialProvider = await AwsS3Publish.buildCredentials( credsManager, + logger, accountId, credentialsConfig, region, awsS3IntegrationConfig, - logger, ); // AWS endpoint is an optional config. If missing, the default endpoint is built from @@ -219,11 +219,11 @@ export class AwsS3Publish implements PublisherBase { private static async buildCredentials( credsManager: AwsCredentialsManager, + logger: LoggerService, accountId?: string, credentialsConfig?: Config, region?: string, awsS3IntegrationConfig?: Config[], - logger: LoggerService, ): Promise { // Pull credentials for the specified account ID from the 'aws' config section if (accountId) { From 0216090b36a66044cefab22fe908d4b9e46907e0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 16:08:03 +0000 Subject: [PATCH 027/202] chore(deps): update dependency @types/swagger-ui-react to v5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .changeset/renovate-ccf3e66.md | 5 +++++ plugins/api-docs/package.json | 2 +- yarn.lock | 10 +++++----- 3 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 .changeset/renovate-ccf3e66.md diff --git a/.changeset/renovate-ccf3e66.md b/.changeset/renovate-ccf3e66.md new file mode 100644 index 0000000000..920da9b987 --- /dev/null +++ b/.changeset/renovate-ccf3e66.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-api-docs': patch +--- + +Updated dependency `@types/swagger-ui-react` to `^5.0.0`. diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index e8f7392e87..423432e12e 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -83,7 +83,7 @@ "@testing-library/user-event": "^14.0.0", "@types/highlightjs": "^10.1.0", "@types/react": "^18.0.0", - "@types/swagger-ui-react": "^4.18.0", + "@types/swagger-ui-react": "^5.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", "react-router-dom": "^6.3.0" diff --git a/yarn.lock b/yarn.lock index 7fd7f16edb..82d0f7f7fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3980,7 +3980,7 @@ __metadata: "@testing-library/user-event": "npm:^14.0.0" "@types/highlightjs": "npm:^10.1.0" "@types/react": "npm:^18.0.0" - "@types/swagger-ui-react": "npm:^4.18.0" + "@types/swagger-ui-react": "npm:^5.0.0" graphiql: "npm:3.1.1" graphql: "npm:^16.0.0" graphql-config: "npm:^5.0.2" @@ -21698,12 +21698,12 @@ __metadata: languageName: node linkType: hard -"@types/swagger-ui-react@npm:^4.18.0": - version: 4.18.3 - resolution: "@types/swagger-ui-react@npm:4.18.3" +"@types/swagger-ui-react@npm:^5.0.0": + version: 5.18.0 + resolution: "@types/swagger-ui-react@npm:5.18.0" dependencies: "@types/react": "npm:*" - checksum: 10/4927314f1b0d68edf200ef15bca7555f12ec1bb8cc699fa397ef2f4e1e1873d17880b663bbc6d70e30149e50c3e7055178c1e0aad110520dfb31d781ab326dcb + checksum: 10/002bb3e38eb5917f5da942affa357811ccb078816bf53ba9007e43603f9b9a839eb930380b08051b9cc9a95ca2ebec06599a825e4d0542e436ea2c2027fc3a0a languageName: node linkType: hard From 59934881727e0eba474ef9870c423bdb6553f1ef Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Tue, 9 Dec 2025 13:13:27 +0100 Subject: [PATCH 028/202] chore: fix breaking change for swagger type Signed-off-by: benjdlambert --- .../components/OpenApiDefinitionWidget/OpenApiDefinition.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx index 8fcc29b829..7279575842 100644 --- a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx +++ b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx @@ -16,7 +16,7 @@ import { makeStyles } from '@material-ui/core/styles'; import { useEffect, useState } from 'react'; -import SwaggerUI, { SwaggerUIProps } from 'swagger-ui-react'; +import SwaggerUI from 'swagger-ui-react'; import 'swagger-ui-react/swagger-ui.css'; const useStyles = makeStyles(theme => ({ @@ -179,7 +179,7 @@ const useStyles = makeStyles(theme => ({ export type OpenApiDefinitionProps = { definition: string; -} & Omit; +} & Omit, 'spec'>; export const OpenApiDefinition = ({ definition, From abeba2b11fd60c23f748638e8435634889cf0a13 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Tue, 9 Dec 2025 13:14:13 +0100 Subject: [PATCH 029/202] chore: fix changeset Signed-off-by: benjdlambert --- .changeset/slow-clouds-tie.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/slow-clouds-tie.md diff --git a/.changeset/slow-clouds-tie.md b/.changeset/slow-clouds-tie.md new file mode 100644 index 0000000000..5528ddd4bd --- /dev/null +++ b/.changeset/slow-clouds-tie.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-api-docs': patch +--- + +Fix types with new bumped dependency From 5ece3c4f2b4b898a4aa71671fb0c12bbfcc667de Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Tue, 9 Dec 2025 14:48:32 +0100 Subject: [PATCH 030/202] techdocs: clarify change Signed-off-by: Vincenzo Scamporlino --- .changeset/whole-dodos-matter.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.changeset/whole-dodos-matter.md b/.changeset/whole-dodos-matter.md index 1d055d306f..df65f33ed7 100644 --- a/.changeset/whole-dodos-matter.md +++ b/.changeset/whole-dodos-matter.md @@ -2,7 +2,7 @@ '@backstage/plugin-techdocs-node': minor --- -It's now possible to use the credentials from the `integrations.awsS3` config to authenticate with AWS S3. The new priority is: +**BREAKING:** It's now possible to use the credentials from the `integrations.awsS3` config to authenticate with AWS S3. The new priority is: 1. `aws.accounts` 2. `techdocs.publisher.awsS3.credentials` @@ -10,3 +10,11 @@ It's now possible to use the credentials from the `integrations.awsS3` config to 4. Default credential chain In case of multiple `integrations.awsS3` are present, the target integration is determined by the `accessKeyId` in `techdocs.publisher.awsS3.credentials` if provided. Otherwise, the default credential chain is used. + +This means that depending on your setup, this feature may break your existing setup. +In general: + +- if you are configuring `aws.accounts`, no action is required +- if you are configuring `techdocs.publisher.awsS3.credentials`, no action is required +- if you are configuring multiple integrations under `integrations.awsS3`, no action is required +- if you are configuring a single integration under `integrations.awsS3`, make sure that the integration has access to the bucket you are using for TechDocs From 0afb8a63ec2b68ba93252e91f8bac8ba4b25383d Mon Sep 17 00:00:00 2001 From: Thomas Cardonne Date: Tue, 9 Dec 2025 15:26:40 +0100 Subject: [PATCH 031/202] fix(techdocs): correct colors calculation Signed-off-by: Thomas Cardonne --- .changeset/rude-moose-dig.md | 5 +++++ .../src/reader/transformers/styles/rules/typeset.ts | 4 ++-- .../src/reader/transformers/styles/rules/variables.ts | 9 +++------ 3 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 .changeset/rude-moose-dig.md diff --git a/.changeset/rude-moose-dig.md b/.changeset/rude-moose-dig.md new file mode 100644 index 0000000000..722b847992 --- /dev/null +++ b/.changeset/rude-moose-dig.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Corrected color of some elements such as Grid cards and Tables. diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts b/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts index 680e58e182..f19c4ebdac 100644 --- a/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts +++ b/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts @@ -110,7 +110,7 @@ ${headings.reduce((style, heading) => { .md-typeset table:not([class]) { font-size: var(--md-typeset-font-size); - border: 1px solid var(--md-default-fg-color); + border: 1px solid var(--md-typeset-table-color); border-bottom: none; border-collapse: collapse; border-radius: ${theme.shape.borderRadius}px; @@ -119,7 +119,7 @@ ${headings.reduce((style, heading) => { font-weight: bold; } .md-typeset table:not([class]) td, .md-typeset table:not([class]) th { - border-bottom: 1px solid var(--md-default-fg-color); + border-bottom: 1px solid var(--md-typeset-table-color); } .md-typeset pre > code::-webkit-scrollbar-thumb { diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/variables.ts b/plugins/techdocs/src/reader/transformers/styles/rules/variables.ts index c419d5f71b..f6d9cd7ea7 100644 --- a/plugins/techdocs/src/reader/transformers/styles/rules/variables.ts +++ b/plugins/techdocs/src/reader/transformers/styles/rules/variables.ts @@ -28,11 +28,8 @@ export default ({ theme }: RuleOptions) => ` /* FONT */ --md-default-fg-color: ${theme.palette.text.primary}; --md-default-fg-color--light: ${theme.palette.text.secondary}; - --md-default-fg-color--lighter: ${lighten(theme.palette.text.secondary, 0.7)}; - --md-default-fg-color--lightest: ${lighten( - theme.palette.text.secondary, - 0.3, - )}; + --md-default-fg-color--lighter: ${alpha(theme.palette.text.secondary, 0.3)}; + --md-default-fg-color--lightest: ${alpha(theme.palette.text.secondary, 0.15)}; /* BACKGROUND */ --md-default-bg-color:${theme.palette.background.default}; @@ -146,7 +143,7 @@ export default ({ theme }: RuleOptions) => ` --md-typeset-font-size: 1rem; --md-typeset-color: var(--md-default-fg-color); --md-typeset-a-color: ${theme.palette.link}; - --md-typeset-table-color: ${theme.palette.text.primary}; + --md-typeset-table-color: ${alpha(theme.palette.text.primary, 0.15)}; --md-typeset-table-color--light: ${alpha(theme.palette.text.primary, 0.05)}; --md-typeset-del-color: ${ theme.palette.type === 'dark' From 9abc06d33588ff737d32caa6414232559f4a3311 Mon Sep 17 00:00:00 2001 From: secustor Date: Tue, 9 Dec 2025 15:36:51 +0100 Subject: [PATCH 032/202] fix:linting issue Signed-off-by: secustor --- plugins/devtools/src/alpha/plugin.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index 6f4bfeed89..9a1b4b0af0 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -68,9 +68,9 @@ export const devToolsPage = PageBlueprint.makeWithOverrides({ title: content.get(coreExtensionData.title), children: content.get(coreExtensionData.reactElement), })); - return import('../components/DevToolsPage').then(m => + return import('../components/DevToolsPage').then(m => ( - ); + )); }, }); }, From bd096c93b120cf0fedfa2d764217ccf177b731c5 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Tue, 9 Dec 2025 16:27:29 +0100 Subject: [PATCH 033/202] techdocs: use ScmIntegrations instead of using raw config Signed-off-by: Vincenzo Scamporlino --- .../techdocs-node/src/stages/publish/awsS3.ts | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/plugins/techdocs-node/src/stages/publish/awsS3.ts b/plugins/techdocs-node/src/stages/publish/awsS3.ts index cb5360f037..d257de4f72 100644 --- a/plugins/techdocs-node/src/stages/publish/awsS3.ts +++ b/plugins/techdocs-node/src/stages/publish/awsS3.ts @@ -61,6 +61,7 @@ import { TechDocsMetadata, } from './types'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { AwsS3Integration, ScmIntegrations } from '@backstage/integration'; const streamToBuffer = (stream: Readable): Promise => { return new Promise((resolve, reject) => { @@ -142,16 +143,16 @@ export class AwsS3Publish implements PublisherBase { const credsManager = DefaultAwsCredentialsManager.fromConfig(config); - const awsS3IntegrationConfig = - config.getOptionalConfigArray('integrations.awsS3'); + const scmIntegrations = ScmIntegrations.fromConfig(config); + const awsS3Integrations = scmIntegrations.awsS3.list(); const sdkCredentialProvider = await AwsS3Publish.buildCredentials( credsManager, logger, + awsS3Integrations, accountId, credentialsConfig, region, - awsS3IntegrationConfig, ); // AWS endpoint is an optional config. If missing, the default endpoint is built from @@ -220,10 +221,10 @@ export class AwsS3Publish implements PublisherBase { private static async buildCredentials( credsManager: AwsCredentialsManager, logger: LoggerService, + awsS3Integrations: AwsS3Integration[], accountId?: string, credentialsConfig?: Config, region?: string, - awsS3IntegrationConfig?: Config[], ): Promise { // Pull credentials for the specified account ID from the 'aws' config section if (accountId) { @@ -234,7 +235,7 @@ export class AwsS3Publish implements PublisherBase { const explicitCredentials = await AwsS3Publish.getExplicitCredentials({ credsManager, credentialsConfig, - awsS3IntegrationConfig, + awsS3Integrations, logger, }); @@ -255,12 +256,12 @@ export class AwsS3Publish implements PublisherBase { private static async getExplicitCredentials({ credentialsConfig, - awsS3IntegrationConfig, + awsS3Integrations, credsManager, logger, }: { credentialsConfig?: Config; - awsS3IntegrationConfig?: Config[]; + awsS3Integrations: AwsS3Integration[]; credsManager: AwsCredentialsManager; logger: LoggerService; }): Promise { @@ -271,14 +272,16 @@ export class AwsS3Publish implements PublisherBase { if (accessKeyId && secretAccessKey) { return AwsS3Publish.buildStaticCredentials(accessKeyId, secretAccessKey); } - if (awsS3IntegrationConfig && awsS3IntegrationConfig.length > 0) { - if (awsS3IntegrationConfig.length === 1) { - const singleAwsS3IntegrationConfig = awsS3IntegrationConfig[0]; + + if (awsS3Integrations.length > 0) { + if (awsS3Integrations.length === 1) { + const singleAwsS3IntegrationConfig = awsS3Integrations[0].config; const singleAwsS3IntegrationAccessKeyId = - singleAwsS3IntegrationConfig.getOptionalString('accessKeyId'); + singleAwsS3IntegrationConfig.accessKeyId; + const singleAwsS3IntegrationSecretAccessKey = - singleAwsS3IntegrationConfig.getOptionalString('secretAccessKey'); + singleAwsS3IntegrationConfig.secretAccessKey; if ( singleAwsS3IntegrationAccessKeyId && @@ -291,8 +294,8 @@ export class AwsS3Publish implements PublisherBase { } } else { if (accessKeyId) { - const targetAwsS3IntegrationConfig = awsS3IntegrationConfig.find( - c => c.getOptionalString('accessKeyId') === accessKeyId, + const targetAwsS3IntegrationConfig = awsS3Integrations.find( + c => c.config.accessKeyId === accessKeyId, ); if (!targetAwsS3IntegrationConfig) { @@ -301,9 +304,9 @@ export class AwsS3Publish implements PublisherBase { ); } const targetAwsS3IntegrationAccessKeyId = - targetAwsS3IntegrationConfig?.getOptionalString('accessKeyId'); + targetAwsS3IntegrationConfig?.config.accessKeyId; const targetAwsS3IntegrationSecretAccessKey = - targetAwsS3IntegrationConfig?.getOptionalString('secretAccessKey'); + targetAwsS3IntegrationConfig?.config.secretAccessKey; if ( targetAwsS3IntegrationAccessKeyId && targetAwsS3IntegrationSecretAccessKey From db3cfd749279b1648cdd494d23cca095d83da578 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Mon, 15 Dec 2025 15:48:39 +0100 Subject: [PATCH 034/202] Adds the Resource kind to the useGetEntities fallback options so that Resources are also returned by default on the group ownership card Signed-off-by: Peter Macdonald --- .changeset/five-bananas-retire.md | 5 +++++ .../org/src/components/Cards/OwnershipCard/useGetEntities.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/five-bananas-retire.md diff --git a/.changeset/five-bananas-retire.md b/.changeset/five-bananas-retire.md new file mode 100644 index 0000000000..88b1d3d6dd --- /dev/null +++ b/.changeset/five-bananas-retire.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +Adds the Resource kind to the `useGetEntities` fallback so that Resources are included by default when no specific kinds are provided in the group ownership card. diff --git a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts index a5a5363192..56c1e8cebd 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts @@ -198,7 +198,7 @@ export function useGetEntities( error?: Error; } { const catalogApi = useApi(catalogApiRef); - const kinds = entityFilterKind ?? ['Component', 'API', 'System']; + const kinds = entityFilterKind ?? ['Component', 'API', 'System', 'Resource']; const { loading, From bdda543d0083c7eda9a04d30d13f51de0250d376 Mon Sep 17 00:00:00 2001 From: "Marcucella, Henry" Date: Tue, 4 Nov 2025 17:25:41 -0500 Subject: [PATCH 035/202] fix(widgetoverlay): light mode wrong background color Signed-off-by: Marcucella, Henry --- .changeset/yummy-clowns-fetch.md | 5 +++++ .../components/CustomHomepage/WidgetSettingsOverlay.tsx | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .changeset/yummy-clowns-fetch.md diff --git a/.changeset/yummy-clowns-fetch.md b/.changeset/yummy-clowns-fetch.md new file mode 100644 index 0000000000..3042490d0e --- /dev/null +++ b/.changeset/yummy-clowns-fetch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-home': patch +--- + +Updated WidgetOverlay color to use `alpha(theme.palette.background.paper, 0.93)` for better theme alignment instead of hardcoded RGBA diff --git a/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx b/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx index e6aa448ca0..41c8ce1176 100644 --- a/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx +++ b/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx @@ -21,7 +21,12 @@ import DialogContent from '@material-ui/core/DialogContent'; import Grid from '@material-ui/core/Grid'; import IconButton from '@material-ui/core/IconButton'; import Tooltip from '@material-ui/core/Tooltip'; -import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; +import { + alpha, + createStyles, + makeStyles, + Theme, +} from '@material-ui/core/styles'; import DeleteIcon from '@material-ui/icons/Delete'; import SettingsIcon from '@material-ui/icons/Settings'; import { useState } from 'react'; @@ -44,7 +49,7 @@ const useStyles = makeStyles((theme: Theme) => }, settingsOverlay: { position: 'absolute', - backgroundColor: 'rgba(40, 40, 40, 0.93)', + backgroundColor: alpha(theme.palette.background.paper, 0.93), width: '100%', height: '100%', top: 0, From dee48ab53779dcfa46bbdcdf1ecb4d713a27ef31 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Tue, 16 Dec 2025 10:30:28 +0100 Subject: [PATCH 036/202] Fix test case Signed-off-by: Peter Macdonald --- .../OwnershipCard/OwnershipCard.test.tsx | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index 6ea0f0cbc9..57ca206cc8 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -44,6 +44,26 @@ const items = [ }, ], }, + { + kind: 'Resource', + metadata: { + name: 'my-resource', + }, + spec: { + type: 'database', + }, + relations: [ + { + type: 'ownedBy', + targetRef: 'group:default/my-team', + target: { + name: 'my-team', + namespace: 'default', + kind: 'group', + }, + }, + ], + }, { kind: 'Component', metadata: { @@ -160,7 +180,7 @@ describe('OwnershipCard', () => { expect(mockedGetEntities).toHaveBeenCalledWith({ filter: [ { - kind: ['Component', 'API', 'System'], + kind: ['Component', 'API', 'System', 'Resource'], 'relations.ownedBy': ['group:default/my-team'], }, ], From c724eaa902c88a02e3d04617fe1dba1e8c9bc26f Mon Sep 17 00:00:00 2001 From: Jamal Sebbari Date: Thu, 18 Dec 2025 13:20:22 +0100 Subject: [PATCH 037/202] fix(puppetdb): handle undefined latest_report_status in transformer Fixes #25224 The defaultResourceTransformer crashes when latest_report_status is undefined because it's not present in /factsets endpoint response (only in /nodes). This adds optional chaining (?.) to safely handle undefined values. Signed-off-by: Jamal Sebbari --- .../src/puppet/transformers.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-backend-module-puppetdb/src/puppet/transformers.ts b/plugins/catalog-backend-module-puppetdb/src/puppet/transformers.ts index 5394041724..fac3771279 100644 --- a/plugins/catalog-backend-module-puppetdb/src/puppet/transformers.ts +++ b/plugins/catalog-backend-module-puppetdb/src/puppet/transformers.ts @@ -51,12 +51,10 @@ export const defaultResourceTransformer: ResourceTransformer = async ( description: node.facts?.data ?.find(e => e.name === 'ipaddress') ?.value?.toString(), - tags: kernel - ? [ - kernel.toString().toLocaleLowerCase('en-US'), - latest_report_status.toString().toLocaleLowerCase('en-US'), - ] - : [], + tags: [ + kernel?.toString().toLocaleLowerCase('en-US'), + latest_report_status?.toString().toLocaleLowerCase('en-US'), + ].filter((tag): tag is string => Boolean(tag)), }, spec: { type: type, From a307700a8075bbc95157cce2e9952df19cf1a7ee Mon Sep 17 00:00:00 2001 From: Jamal Sebbari Date: Thu, 18 Dec 2025 15:24:52 +0100 Subject: [PATCH 038/202] chore: add changeset Signed-off-by: Jamal Sebbari --- .changeset/fluffy-bushes-remain.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fluffy-bushes-remain.md diff --git a/.changeset/fluffy-bushes-remain.md b/.changeset/fluffy-bushes-remain.md new file mode 100644 index 0000000000..970bd0f2da --- /dev/null +++ b/.changeset/fluffy-bushes-remain.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-puppetdb': patch +--- + +Fixed crash when latest_report_status is undefined From f7d8c02a8b2616a39ee275c6d3d95fa2c1abe4b1 Mon Sep 17 00:00:00 2001 From: Vivek Hipparkar Date: Thu, 18 Dec 2025 17:22:38 +0530 Subject: [PATCH 039/202] docs: add example for disabling individual Select options Signed-off-by: Vivek Hipparkar --- docs-ui/src/content/select.mdx | 16 ++++++++++++++++ docs-ui/src/content/select.props.ts | 13 +++++++++++++ 2 files changed, 29 insertions(+) diff --git a/docs-ui/src/content/select.mdx b/docs-ui/src/content/select.mdx index 436129d749..45aa9d1f26 100644 --- a/docs-ui/src/content/select.mdx +++ b/docs-ui/src/content/select.mdx @@ -14,6 +14,7 @@ import { selectSearchableSnippet, selectMultipleSnippet, selectSearchableMultipleSnippet, + selectDisabledOptionsSnippet, } from './select.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; @@ -91,6 +92,19 @@ Here's a view when the select is disabled. code={selectDisabledSnippet} /> +### Disabled options + +You can disable specific options within the Select component using `disabledKeys`. + +} + code={selectDisabledOptionsSnippet} +/> + + ### Searchable Here's a view when the select has searchable filtering. @@ -127,6 +141,7 @@ Here's a view when the select combines search and multiple selection. code={selectSearchableMultipleSnippet} /> + ### Responsive Here's a view when the select is responsive. @@ -136,3 +151,4 @@ Here's a view when the select is responsive. + diff --git a/docs-ui/src/content/select.props.ts b/docs-ui/src/content/select.props.ts index 05f0d895c9..5c5dfb7fc8 100644 --- a/docs-ui/src/content/select.props.ts +++ b/docs-ui/src/content/select.props.ts @@ -209,3 +209,16 @@ export const selectSearchableMultipleSnippet = ``; From 0bea7650c902b0b2d88413ab1e7a8f0477b78fa9 Mon Sep 17 00:00:00 2001 From: Vivek Hipparkar Date: Thu, 18 Dec 2025 19:47:46 +0530 Subject: [PATCH 040/202] docs: format Select docs with Prettier Signed-off-by: Vivek Hipparkar --- docs-ui/src/content/select.mdx | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs-ui/src/content/select.mdx b/docs-ui/src/content/select.mdx index 45aa9d1f26..757a79effe 100644 --- a/docs-ui/src/content/select.mdx +++ b/docs-ui/src/content/select.mdx @@ -104,7 +104,6 @@ You can disable specific options within the Select component using `disabledKeys code={selectDisabledOptionsSnippet} /> - ### Searchable Here's a view when the select has searchable filtering. @@ -141,7 +140,6 @@ Here's a view when the select combines search and multiple selection. code={selectSearchableMultipleSnippet} /> - ### Responsive Here's a view when the select is responsive. @@ -151,4 +149,3 @@ Here's a view when the select is responsive. - From cb4b907748eaa1dcf76123896944ada670b49c22 Mon Sep 17 00:00:00 2001 From: Rogerio Angeliski Date: Thu, 18 Dec 2025 14:36:50 -0300 Subject: [PATCH 041/202] chore: update how GithubOrgEntityProvider handle membership Signed-off-by: Rogerio Angeliski --- .changeset/ten-vans-agree.md | 5 + .../providers/GithubOrgEntityProvider.test.ts | 191 ++---------------- .../src/providers/GithubOrgEntityProvider.ts | 138 +++++++------ 3 files changed, 102 insertions(+), 232 deletions(-) create mode 100644 .changeset/ten-vans-agree.md diff --git a/.changeset/ten-vans-agree.md b/.changeset/ten-vans-agree.md new file mode 100644 index 0000000000..69e1d85e02 --- /dev/null +++ b/.changeset/ten-vans-agree.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +Improved efficiency of `GithubOrgEntityProvider` membership event handling and edit team. The provider now fetches only the specific user's teams instead of all organization users when processing membership events, and uses `addEntitiesOperation` instead of `replaceEntitiesOperation` to avoid unnecessary entity deletions. diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts index 9be5a3e161..86b0090263 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts @@ -840,6 +840,9 @@ describe('GithubOrgEntityProvider', () => { name: { from: 'mygroup with spaces', }, + description: { + from: 'old description', + }, }, team: { node_id: 'xpto', @@ -956,21 +959,21 @@ describe('GithubOrgEntityProvider', () => { 'url:https://github.com/orgs/backstage/teams/mygroup-with-spaces', 'backstage.io/managed-by-origin-location': 'url:https://github.com/orgs/backstage/teams/mygroup-with-spaces', + 'github.com/team-slug': 'backstage/mygroup-with-spaces', }, name: 'mygroup-with-spaces', - description: 'description-from-the-new-team', + description: 'old description', }, apiVersion: 'backstage.io/v1alpha1', kind: 'Group', spec: { type: 'team', profile: { - displayName: 'Team', - picture: 'http://example.com/team.jpeg', + displayName: 'mygroup with spaces', }, children: [], - parent: 'parent', - members: ['a', 'githubuser'], + parent: 'father-team', + members: [], }, }, }, @@ -1012,6 +1015,7 @@ describe('GithubOrgEntityProvider', () => { const mockClient = jest.fn(); mockClient + // getOrganizationTeam .mockResolvedValueOnce({ organization: { team: { @@ -1032,31 +1036,7 @@ describe('GithubOrgEntityProvider', () => { }, }, }) - .mockResolvedValueOnce({ - organization: { - membersWithRole: { - pageInfo: { hasNextPage: false }, - nodes: [ - { - login: 'a', - id: 'f', - name: 'b', - bio: 'c', - email: 'd', - avatarUrl: 'e', - }, - { - login: 'githubuser', - id: 'githubuserId', - name: 'githubuser', - bio: 'githubuser', - email: 'd', - avatarUrl: 'e', - }, - ], - }, - }, - }) + // getOrganizationTeamsForUser .mockResolvedValueOnce({ organization: { teams: { @@ -1070,12 +1050,6 @@ describe('GithubOrgEntityProvider', () => { avatarUrl: 'http://example.com/team.jpeg', parentTeam: { slug: 'parent', - combinedSlug: '', - members: { pageInfo: { hasNextPage: false }, nodes: [] }, - }, - members: { - pageInfo: { hasNextPage: false }, - nodes: [{ login: 'a' }, { login: 'githubuser' }], }, }, ], @@ -1101,6 +1075,10 @@ describe('GithubOrgEntityProvider', () => { }, member: { login: 'githubuser', + avatar_url: 'e', + email: 'd', + name: 'githubuser', + node_id: 'githubuserId', }, organization: { login: 'test-org', @@ -1114,33 +1092,6 @@ describe('GithubOrgEntityProvider', () => { expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ added: [ - { - locationKey: 'github-org-provider:my-id', - entity: { - metadata: { - annotations: { - 'backstage.io/managed-by-location': - 'url:https://github.com/githubuser', - 'backstage.io/managed-by-origin-location': - 'url:https://github.com/githubuser', - 'github.com/user-login': 'githubuser', - 'github.com/user-id': 'githubuserId', - }, - name: 'githubuser', - description: 'githubuser', - }, - apiVersion: 'backstage.io/v1alpha1', - kind: 'User', - spec: { - profile: { - displayName: 'githubuser', - email: 'd', - picture: 'e', - }, - memberOf: ['team'], - }, - }, - }, { locationKey: 'github-org-provider:my-id', entity: { @@ -1169,8 +1120,6 @@ describe('GithubOrgEntityProvider', () => { }, }, }, - ], - removed: [ { locationKey: 'github-org-provider:my-id', entity: { @@ -1184,7 +1133,6 @@ describe('GithubOrgEntityProvider', () => { 'github.com/user-id': 'githubuserId', }, name: 'githubuser', - description: 'githubuser', }, apiVersion: 'backstage.io/v1alpha1', kind: 'User', @@ -1198,35 +1146,8 @@ describe('GithubOrgEntityProvider', () => { }, }, }, - { - locationKey: 'github-org-provider:my-id', - entity: { - metadata: { - annotations: { - 'backstage.io/managed-by-location': - 'url:https://github.com/orgs/backstage/teams/team', - 'backstage.io/managed-by-origin-location': - 'url:https://github.com/orgs/backstage/teams/team', - 'github.com/team-slug': 'blah/team', - }, - name: 'team', - description: 'The one and only team', - }, - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - spec: { - type: 'team', - profile: { - displayName: 'Team', - picture: 'http://example.com/team.jpeg', - }, - children: [], - parent: 'parent', - members: ['a', 'githubuser'], - }, - }, - }, ], + removed: [], type: 'delta', }); }); @@ -1264,6 +1185,7 @@ describe('GithubOrgEntityProvider', () => { const mockClient = jest.fn(); mockClient + // getOrganizationTeam .mockResolvedValueOnce({ organization: { team: { @@ -1284,23 +1206,7 @@ describe('GithubOrgEntityProvider', () => { }, }, }) - .mockResolvedValueOnce({ - organization: { - membersWithRole: { - pageInfo: { hasNextPage: false }, - nodes: [ - { - login: 'githubuser', - id: 'githubuserId', - name: 'githubuser', - bio: 'githubuser', - email: 'd', - avatarUrl: 'e', - }, - ], - }, - }, - }) + // getOrganizationTeamsForUser - returns empty teams since user was removed .mockResolvedValueOnce({ organization: { teams: { @@ -1328,6 +1234,10 @@ describe('GithubOrgEntityProvider', () => { }, member: { login: 'githubuser', + avatar_url: 'e', + email: 'd', + name: 'githubuser', + node_id: 'githubuserId', }, organization: { login: 'test-org', @@ -1341,33 +1251,6 @@ describe('GithubOrgEntityProvider', () => { expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ added: [ - { - locationKey: 'github-org-provider:my-id', - entity: { - metadata: { - annotations: { - 'backstage.io/managed-by-location': - 'url:https://github.com/githubuser', - 'backstage.io/managed-by-origin-location': - 'url:https://github.com/githubuser', - 'github.com/user-login': 'githubuser', - 'github.com/user-id': 'githubuserId', - }, - name: 'githubuser', - description: 'githubuser', - }, - apiVersion: 'backstage.io/v1alpha1', - kind: 'User', - spec: { - profile: { - displayName: 'githubuser', - email: 'd', - picture: 'e', - }, - memberOf: [], - }, - }, - }, { locationKey: 'github-org-provider:my-id', entity: { @@ -1396,8 +1279,6 @@ describe('GithubOrgEntityProvider', () => { }, }, }, - ], - removed: [ { locationKey: 'github-org-provider:my-id', entity: { @@ -1411,7 +1292,6 @@ describe('GithubOrgEntityProvider', () => { 'github.com/user-id': 'githubuserId', }, name: 'githubuser', - description: 'githubuser', }, apiVersion: 'backstage.io/v1alpha1', kind: 'User', @@ -1425,35 +1305,8 @@ describe('GithubOrgEntityProvider', () => { }, }, }, - { - locationKey: 'github-org-provider:my-id', - entity: { - metadata: { - annotations: { - 'backstage.io/managed-by-location': - 'url:https://github.com/orgs/backstage/teams/team', - 'backstage.io/managed-by-origin-location': - 'url:https://github.com/orgs/backstage/teams/team', - 'github.com/team-slug': 'blah/team', - }, - name: 'team', - description: 'The one and only team', - }, - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - spec: { - type: 'team', - profile: { - displayName: 'Team', - picture: 'http://example.com/team.jpeg', - }, - children: [], - parent: 'parent', - members: ['a'], - }, - }, - }, ], + removed: [], type: 'delta', }); }); diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts index bb4b268e62..d13520f112 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts @@ -18,7 +18,7 @@ import { LoggerService, SchedulerServiceTaskRunner, } from '@backstage/backend-plugin-api'; -import { Entity, isGroupEntity } from '@backstage/catalog-model'; +import { Entity, isGroupEntity, isUserEntity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { DefaultGithubCredentialsProvider, @@ -52,18 +52,22 @@ import { createAddEntitiesOperation, createGraphqlClient, createRemoveEntitiesOperation, - createReplaceEntitiesOperation, DEFAULT_PAGE_SIZES, DeferredEntitiesBuilder, getOrganizationTeam, getOrganizationTeams, + getOrganizationTeamsForUser, getOrganizationTeamsFromUsers, getOrganizationUsers, GithubPageSizes, GithubTeam, } from '../lib/github'; import { areGroupEntities, areUserEntities } from '../lib/guards'; -import { assignGroupsToUsers, buildOrgHierarchy } from '../lib/org'; +import { + assignGroupsToUser, + assignGroupsToUsers, + buildOrgHierarchy, +} from '../lib/org'; import { parseGithubOrgUrl } from '../lib/util'; import { withLocations } from '../lib/withLocations'; @@ -311,11 +315,6 @@ export class GithubOrgEntityProvider implements EntityProvider { this.options.gitHubConfig.host, ); - const replaceEntitiesOperation = createReplaceEntitiesOperation( - this.options.id, - this.options.gitHubConfig.host, - ); - // handle change users in the org // https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#organization if (params.topic.includes('organization')) { @@ -346,7 +345,8 @@ export class GithubOrgEntityProvider implements EntityProvider { } else if (teamEvent.action === 'edited') { await this.onTeamEditedInOrganization( teamEvent, - replaceEntitiesOperation, + addEntitiesOperation, + removeEntitiesOperation, ); } } @@ -355,9 +355,9 @@ export class GithubOrgEntityProvider implements EntityProvider { // https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#membership if (params.topic.includes('membership')) { const membershipEvent = params.eventPayload as MembershipEvent; - this.onMembershipChangedInOrganization( + await this.onMembershipChangedInOrganization( membershipEvent, - replaceEntitiesOperation, + addEntitiesOperation, ); } @@ -366,7 +366,8 @@ export class GithubOrgEntityProvider implements EntityProvider { private async onTeamEditedInOrganization( event: TeamEditedEvent, - createDeltaOperation: DeferredEntitiesBuilder, + addEntitiesOperation: DeferredEntitiesBuilder, + removeEntitiesOperation: DeferredEntitiesBuilder, ) { if (!this.connection) { throw new Error('Not initialized'); @@ -424,25 +425,33 @@ export class GithubOrgEntityProvider implements EntityProvider { } } - const oldName = event.changes.name?.from || event.team.name; + const teamTransformer = + this.options.teamTransformer || defaultOrganizationTeamTransformer; + + const oldName = event.changes.name?.from || ''; const oldSlug = oldName.toLowerCase().replaceAll(/\s/gi, '-'); - - const oldDescription = - event.changes.description?.from || event.team.description; - const oldDescriptionSlug = oldDescription - ?.toLowerCase() - .replaceAll(/\s/gi, '-'); - - const { removed } = createDeltaOperation(org, [ + const oldGroup = (await teamTransformer( { - ...team, - metadata: { - name: oldSlug, - description: oldDescriptionSlug, - }, + name: event.changes.name?.from, + slug: oldSlug, + combinedSlug: `${org}/${oldSlug}`, + description: event.changes.description?.from, + parentTeam: event.team?.parent?.slug + ? ({ slug: event.team.parent.slug } as GithubTeam) + : undefined, + // entity will be removed + members: [], }, - ]); - const { added } = createDeltaOperation(org, [...usersToRebuild, ...teams]); + { + org, + client, + query: '', + }, + )) as Entity; + + // Remove the old group entity in case the entity ref is now different + const { removed } = removeEntitiesOperation(org, [oldGroup]); + const { added } = addEntitiesOperation(org, [...usersToRebuild, team]); await this.connection.applyMutation({ type: 'delta', removed, @@ -467,11 +476,9 @@ export class GithubOrgEntityProvider implements EntityProvider { } const teamSlug = event.team.slug; - const userLogin = event.member.login; - const { headers, type: tokenType } = - await this.credentialsProvider.getCredentials({ - url: this.options.orgUrl, - }); + const { headers } = await this.credentialsProvider.getCredentials({ + url: this.options.orgUrl, + }); const client = graphql.defaults({ baseUrl: this.options.gitHubConfig.apiBaseUrl, headers, @@ -484,43 +491,48 @@ export class GithubOrgEntityProvider implements EntityProvider { org, teamSlug, this.options.teamTransformer, - ); - - const { users } = await getOrganizationUsers( - client, - org, - tokenType, - this.options.userTransformer, - pageSizes, - this.options.excludeSuspendedUsers, - ); - - const usersToRebuild = users.filter(u => u.metadata.name === userLogin); - - const { teams } = await getOrganizationTeamsFromUsers( - client, - org, - [userLogin], - this.options.teamTransformer, pageSizes, ); - // we include group because the removed event need to update the old group too - if (!teams.some(t => t.metadata.name === team.metadata.name)) { - teams.push(team); - } + const userTransformer = + this.options.userTransformer || defaultUserTransformer; + const { name, avatar_url: avatarUrl, email, login, node_id } = event.member; + const user = await userTransformer( + { + name, + avatarUrl, + login, + email: email ?? undefined, + id: node_id, + }, + { + org, + client, + query: '', + }, + ); - if (areGroupEntities(teams)) { - buildOrgHierarchy(teams); - if (areUserEntities(usersToRebuild)) { - assignGroupsToUsers(usersToRebuild, teams); + const mutationEntities: Entity[] = [team]; + + if (user && isUserEntity(user)) { + const teamTransformer = + this.options.teamTransformer || defaultOrganizationTeamTransformer; + const { teams } = await getOrganizationTeamsForUser( + client, + org, + login, + teamTransformer, + pageSizes, + ); + + if (areGroupEntities(teams)) { + assignGroupsToUser(user, teams); } + + mutationEntities.push(user); } - const { added, removed } = createDeltaOperation(org, [ - ...usersToRebuild, - ...teams, - ]); + const { added, removed } = createDeltaOperation(org, mutationEntities); await this.connection.applyMutation({ type: 'delta', removed, From 2b361f51984603fcd024618dba33a1d6e6456767 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 18 Dec 2025 12:40:19 +0000 Subject: [PATCH 042/202] Add Storybook MCP Signed-off-by: Charles de Dreuille --- .storybook/main.ts | 6 + .storybook/preview.tsx | 7 + package.json | 15 +- packages/ui/package.json | 4 +- yarn.lock | 290 ++++++++++++++++++++++++++++----------- 5 files changed, 236 insertions(+), 86 deletions(-) diff --git a/.storybook/main.ts b/.storybook/main.ts index 5453627f34..6a13b1b0a7 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,3 +1,4 @@ +// This file has been automatically migrated to valid ESM format by Storybook. import { defineMain } from '@storybook/react-vite/node'; import { fileURLToPath } from 'node:url'; import { createRequire } from 'node:module'; @@ -49,11 +50,16 @@ export default defineMain({ getAbsolutePath('@storybook/addon-docs'), getAbsolutePath('@storybook/addon-a11y'), getAbsolutePath('@storybook/addon-vitest'), + getAbsolutePath('@storybook/addon-mcp'), ], framework: { name: getAbsolutePath('@storybook/react-vite'), options: {}, }, + features: { + experimentalComponentsManifest: true, + experimentalCodeExamples: true, // optional + }, viteFinal: async (config, { configType }) => { // Add Node.js polyfills for browser compatibility // diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 10200ae12d..00aba69398 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -107,6 +107,13 @@ export default definePreview({ // 'dark spotify': allModes['dark spotify'], }, }, + + a11y: { + // 'todo' - show a11y violations in the test UI only + // 'error' - fail CI on a11y violations + // 'off' - skip a11y checks entirely + test: 'todo', + }, }, decorators: [ diff --git a/package.json b/package.json index f39c8f1905..909bd6efc9 100644 --- a/package.json +++ b/package.json @@ -137,12 +137,13 @@ "@octokit/rest": "^19.0.3", "@playwright/test": "^1.32.3", "@spotify/eslint-plugin": "^15.0.0", - "@storybook/addon-a11y": "^10.1.9", - "@storybook/addon-docs": "^10.1.9", - "@storybook/addon-links": "^10.1.9", - "@storybook/addon-themes": "^10.1.9", - "@storybook/addon-vitest": "^10.1.9", - "@storybook/react-vite": "^10.1.9", + "@storybook/addon-a11y": "^10.2.0-alpha.7", + "@storybook/addon-docs": "^10.2.0-alpha.7", + "@storybook/addon-links": "^10.2.0-alpha.7", + "@storybook/addon-mcp": "0.1.4-next.2", + "@storybook/addon-themes": "^10.2.0-alpha.7", + "@storybook/addon-vitest": "^10.2.0-alpha.7", + "@storybook/react-vite": "^10.2.0-alpha.7", "@techdocs/cli": "workspace:*", "@types/cacheable-request": "^8.3.6", "@types/memjs": "^1.3.3", @@ -168,7 +169,7 @@ "shx": "^0.4.0", "sloc": "^0.3.1", "sort-package-json": "^2.8.0", - "storybook": "^10.1.10", + "storybook": "^10.2.0-alpha.7", "ts-morph": "^24.0.0", "typedoc": "^0.28.0", "typescript": "~5.7.0", diff --git a/packages/ui/package.json b/packages/ui/package.json index 00b51295fd..8b83650426 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -50,7 +50,7 @@ "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "chalk": "^5.4.1", - "eslint-plugin-storybook": "^10.1.9", + "eslint-plugin-storybook": "^10.2.0-alpha.7", "glob": "^11.0.1", "globals": "^15.11.0", "lightningcss": "^1.29.1", @@ -58,7 +58,7 @@ "react": "^18.0.2", "react-dom": "^18.0.2", "react-router-dom": "^6.3.0", - "storybook": "^10.1.10" + "storybook": "^10.2.0-alpha.7" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", diff --git a/yarn.lock b/yarn.lock index 393cd37bd7..78e29a459f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7943,7 +7943,7 @@ __metadata: "@types/react-dom": "npm:^18.0.0" chalk: "npm:^5.4.1" clsx: "npm:^2.1.1" - eslint-plugin-storybook: "npm:^10.1.9" + eslint-plugin-storybook: "npm:^10.2.0-alpha.7" glob: "npm:^11.0.1" globals: "npm:^15.11.0" lightningcss: "npm:^1.29.1" @@ -7952,7 +7952,7 @@ __metadata: react-aria-components: "npm:^1.13.0" react-dom: "npm:^18.0.2" react-router-dom: "npm:^6.3.0" - storybook: "npm:^10.1.10" + storybook: "npm:^10.2.0-alpha.7" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 @@ -18828,6 +18828,13 @@ __metadata: languageName: node linkType: hard +"@standard-schema/spec@npm:^1.0.0": + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10/a209615c9e8b2ea535d7db0a5f6aa0f962fd4ab73ee86a46c100fb78116964af1f55a27c1794d4801e534a196794223daa25ff5135021e03c7828aa3d95e1763 + languageName: node + linkType: hard + "@stoplight/better-ajv-errors@npm:1.0.3": version: 1.0.3 resolution: "@stoplight/better-ajv-errors@npm:1.0.3" @@ -19103,64 +19110,79 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-a11y@npm:^10.1.9": - version: 10.1.9 - resolution: "@storybook/addon-a11y@npm:10.1.9" +"@storybook/addon-a11y@npm:^10.2.0-alpha.7": + version: 10.2.0-alpha.7 + resolution: "@storybook/addon-a11y@npm:10.2.0-alpha.7" dependencies: "@storybook/global": "npm:^5.0.0" axe-core: "npm:^4.2.0" peerDependencies: - storybook: ^10.1.9 - checksum: 10/d004f9975d20368ce58bb6c25bc421920ed6898ae2342a821776c41b6ed7597e41f7bfe932fc6be73f7611ba053cdcd63b18cf7c9ec0e5375a0c01fe45635b0f + storybook: ^10.2.0-alpha.7 + checksum: 10/f531e8d2bbde53343734ac1b1e0a36aef65188b8acfa188eb1b3feda603932a525f1fa76503e04e300cb5953ff8a58c810f88d6f9d56cfdd0c8dd23973dfa110 languageName: node linkType: hard -"@storybook/addon-docs@npm:^10.1.9": - version: 10.1.9 - resolution: "@storybook/addon-docs@npm:10.1.9" +"@storybook/addon-docs@npm:^10.2.0-alpha.7": + version: 10.2.0-alpha.7 + resolution: "@storybook/addon-docs@npm:10.2.0-alpha.7" dependencies: "@mdx-js/react": "npm:^3.0.0" - "@storybook/csf-plugin": "npm:10.1.9" + "@storybook/csf-plugin": "npm:10.2.0-alpha.7" "@storybook/icons": "npm:^2.0.0" - "@storybook/react-dom-shim": "npm:10.1.9" + "@storybook/react-dom-shim": "npm:10.2.0-alpha.7" react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^10.1.9 - checksum: 10/4f24aa255b2cc3c5ad1560730e40aba5f2019a8069456e1f03043cf67de14cbb9ac26650eb805fddb6395182db62d850df127fc310a1fff49c39cf166a63aa02 + storybook: ^10.2.0-alpha.7 + checksum: 10/cc7142cd187f1f0a50ad7277ba2555e10e6d0ff57e29d699a5c260237c96b4a246aad1d51d960c56ed814a2d80638dcf67318d57644f0e4b63f0b869e8dbe379 languageName: node linkType: hard -"@storybook/addon-links@npm:^10.1.9": - version: 10.1.9 - resolution: "@storybook/addon-links@npm:10.1.9" +"@storybook/addon-links@npm:^10.2.0-alpha.7": + version: 10.2.0-alpha.7 + resolution: "@storybook/addon-links@npm:10.2.0-alpha.7" dependencies: "@storybook/global": "npm:^5.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.1.9 + storybook: ^10.2.0-alpha.7 peerDependenciesMeta: react: optional: true - checksum: 10/c0f9c97b64dd50e0ea0642eb75e2f7af81548958aeef35e1d44992c7d85ee8796cc034088aebba5e81b760932e064e39b7bdc64f572bbd68d237f6870c95f52e + checksum: 10/bc868268f21766fdc274399558598d6c95400f00c15b964cd2a239581a4b8cd4ad1454d2ba018471df5c7a372f66ee1724e32f99f246d0d852995782e01e4631 languageName: node linkType: hard -"@storybook/addon-themes@npm:^10.1.9": - version: 10.1.9 - resolution: "@storybook/addon-themes@npm:10.1.9" +"@storybook/addon-mcp@npm:0.1.4-next.2": + version: 0.1.4-next.2 + resolution: "@storybook/addon-mcp@npm:0.1.4-next.2" + dependencies: + "@storybook/mcp": "npm:0.0.7-next.0" + "@tmcp/adapter-valibot": "npm:^0.1.4" + "@tmcp/transport-http": "npm:^0.8.0" + tmcp: "npm:^1.16.0" + valibot: "npm:^1.1.0" + peerDependencies: + storybook: ^9.1.16 || ^10.0.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 + checksum: 10/b4d2ab875183b5edccd3a9531f385c6a4d628662d6f976d3adfb1ce483510563befa491192eb78867c5a2345a8f7b071d142a3a1c8b5274477e5c66554fa952a + languageName: node + linkType: hard + +"@storybook/addon-themes@npm:^10.2.0-alpha.7": + version: 10.2.0-alpha.7 + resolution: "@storybook/addon-themes@npm:10.2.0-alpha.7" dependencies: ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^10.1.9 - checksum: 10/66d231efb94919aa89bbb92291925d96519c993d6d75326850caad432a1425c4aa93b4249bf46fffc5b41724ef007d01477045a74e55e937410d8fa7be7037f6 + storybook: ^10.2.0-alpha.7 + checksum: 10/85d3be25d89ae1b66702093fafd14f3b4ea810b6875540017d40b8d67febd2f3a6e139d9a5fa1fae38271d6a7c387ff625d0c023482d0ca39b7500fca71f23b7 languageName: node linkType: hard -"@storybook/addon-vitest@npm:^10.1.9": - version: 10.1.9 - resolution: "@storybook/addon-vitest@npm:10.1.9" +"@storybook/addon-vitest@npm:^10.2.0-alpha.7": + version: 10.2.0-alpha.7 + resolution: "@storybook/addon-vitest@npm:10.2.0-alpha.7" dependencies: "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^2.0.0" @@ -19168,7 +19190,7 @@ __metadata: "@vitest/browser": ^3.0.0 || ^4.0.0 "@vitest/browser-playwright": ^4.0.0 "@vitest/runner": ^3.0.0 || ^4.0.0 - storybook: ^10.1.9 + storybook: ^10.2.0-alpha.7 vitest: ^3.0.0 || ^4.0.0 peerDependenciesMeta: "@vitest/browser": @@ -19179,33 +19201,33 @@ __metadata: optional: true vitest: optional: true - checksum: 10/8d1be8902a243d6c42a84d0acd1550014e5fa6cb509cdfbec6603e6b3d34def5f28d483f8d4289ef7be87d48f41a47f34f4f7028eef7add49cec5d98cf477a0d + checksum: 10/983b887326ab7fe21c3eb6c017659edbdf06f710327b060a15d45f9769759071a011b6f0e33bf8e02714334091fa5b715936cb23caa61050636e2691373e07fc languageName: node linkType: hard -"@storybook/builder-vite@npm:10.1.9": - version: 10.1.9 - resolution: "@storybook/builder-vite@npm:10.1.9" +"@storybook/builder-vite@npm:10.2.0-alpha.7": + version: 10.2.0-alpha.7 + resolution: "@storybook/builder-vite@npm:10.2.0-alpha.7" dependencies: - "@storybook/csf-plugin": "npm:10.1.9" + "@storybook/csf-plugin": "npm:10.2.0-alpha.7" "@vitest/mocker": "npm:3.2.4" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^10.1.9 + storybook: ^10.2.0-alpha.7 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10/4dc086193b45219b24e528404ed51c4dbc240a9ac20feeef88a1b051ae286264006741b402f4936745f5733c8b568e090b029aaea73bf5e3a078047aa957f8ba + checksum: 10/df83a6d775fa5f6e9e8e84bad4b66fb2039f534ad95b6d7643eff02285ad77367c16a0b9122281344a8ac16ec2bed1b140820be672807e7358630d0576d3ed41 languageName: node linkType: hard -"@storybook/csf-plugin@npm:10.1.9": - version: 10.1.9 - resolution: "@storybook/csf-plugin@npm:10.1.9" +"@storybook/csf-plugin@npm:10.2.0-alpha.7": + version: 10.2.0-alpha.7 + resolution: "@storybook/csf-plugin@npm:10.2.0-alpha.7" dependencies: unplugin: "npm:^2.3.5" peerDependencies: esbuild: "*" rollup: "*" - storybook: ^10.1.9 + storybook: ^10.2.0-alpha.7 vite: "*" webpack: "*" peerDependenciesMeta: @@ -19217,7 +19239,7 @@ __metadata: optional: true webpack: optional: true - checksum: 10/604339107f7bd57f64b5b0d9ddc16a447fc0987d348cb9add5bb2fc75b568760afb3689cd51c73df48cc13bf2d82386f89da57e53f500687434d403cd7395e49 + checksum: 10/7efb9d56a21eaf26ffd0971b0d3b8d525d0227fbced7e7391cf8329eb354f9d717cdc04a156391c62b57d53ebdba6ef3e3897014cad029725dc5c68fdbf3ce3e languageName: node linkType: hard @@ -19238,25 +19260,37 @@ __metadata: languageName: node linkType: hard -"@storybook/react-dom-shim@npm:10.1.9": - version: 10.1.9 - resolution: "@storybook/react-dom-shim@npm:10.1.9" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.1.9 - checksum: 10/2b6e598eb8ddac80fd6cd4d888b3a8da1c352e9ffc666a1bcce6f4ac404c77cc75571a57b433e1a12f34cfd255db31fb399a7f38e70bde5d197de2d072eeac85 +"@storybook/mcp@npm:0.0.7-next.0": + version: 0.0.7-next.0 + resolution: "@storybook/mcp@npm:0.0.7-next.0" + dependencies: + "@tmcp/adapter-valibot": "npm:^0.1.4" + "@tmcp/transport-http": "npm:^0.8.0" + tmcp: "npm:^1.16.0" + valibot: "npm:^1.1.0" + checksum: 10/8a70f0f16305fcc291fb248bdd09f818a0f1960416e27ab29ea7f4ed59878f3eae0865b407f7fbdba159727a9e9820a7ea0085fba948094cb1d946f63eeb18f4 languageName: node linkType: hard -"@storybook/react-vite@npm:^10.1.9": - version: 10.1.9 - resolution: "@storybook/react-vite@npm:10.1.9" +"@storybook/react-dom-shim@npm:10.2.0-alpha.7": + version: 10.2.0-alpha.7 + resolution: "@storybook/react-dom-shim@npm:10.2.0-alpha.7" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.2.0-alpha.7 + checksum: 10/088262c61b2a138e02ae3619df3f3a62328dd7fc764b1d843bc1464e613d4d34b16c83d24aea54771fe90eb7882f9699d9d045f206a4f6302a4b7b6b5f3f5080 + languageName: node + linkType: hard + +"@storybook/react-vite@npm:^10.2.0-alpha.7": + version: 10.2.0-alpha.7 + resolution: "@storybook/react-vite@npm:10.2.0-alpha.7" dependencies: "@joshwooding/vite-plugin-react-docgen-typescript": "npm:^0.6.3" "@rollup/pluginutils": "npm:^5.0.2" - "@storybook/builder-vite": "npm:10.1.9" - "@storybook/react": "npm:10.1.9" + "@storybook/builder-vite": "npm:10.2.0-alpha.7" + "@storybook/react": "npm:10.2.0-alpha.7" empathic: "npm:^2.0.0" magic-string: "npm:^0.30.0" react-docgen: "npm:^8.0.0" @@ -19265,28 +19299,28 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.1.9 + storybook: ^10.2.0-alpha.7 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10/e55978b8eaee2f5d9a7b30c2547d3f29497468c0bac5f20308633554f381d066d2f0549a4997d9ec29693f018d6889d9b9c4bfb4b2e822c4730999fa1605c573 + checksum: 10/495c2955fe68549f9be3a39dbf60b68cd9e0ed016de5eabd98c40725bd3c3cfd1a8c493ac5906bf8119ce16b75a1f997d721492cad4110673e62da8d8e6fe1a3 languageName: node linkType: hard -"@storybook/react@npm:10.1.9": - version: 10.1.9 - resolution: "@storybook/react@npm:10.1.9" +"@storybook/react@npm:10.2.0-alpha.7": + version: 10.2.0-alpha.7 + resolution: "@storybook/react@npm:10.2.0-alpha.7" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/react-dom-shim": "npm:10.1.9" + "@storybook/react-dom-shim": "npm:10.2.0-alpha.7" react-docgen: "npm:^8.0.2" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.1.9 + storybook: ^10.2.0-alpha.7 typescript: ">= 4.9.x" peerDependenciesMeta: typescript: optional: true - checksum: 10/964f5a3b77638597289c72be0991323fcbe0f5d7451b5c7bceac7e998dd819897936fb486e38d0b70e42507ca6895850d9a484acc8d171a6ecbd9ce41a0b19ad + checksum: 10/f0240fa8d72fc9b54380a9f50145b66a4285fdf3b69c51b042e45d2080f845580a4473614f2cd3ee472022cacb6a4ccc71b54a2a61382d86767d57604075b5ae languageName: node linkType: hard @@ -20133,6 +20167,45 @@ __metadata: languageName: node linkType: hard +"@tmcp/adapter-valibot@npm:^0.1.4": + version: 0.1.5 + resolution: "@tmcp/adapter-valibot@npm:0.1.5" + dependencies: + "@standard-schema/spec": "npm:^1.0.0" + "@valibot/to-json-schema": "npm:^1.3.0" + valibot: "npm:^1.1.0" + peerDependencies: + tmcp: ^1.17.0 + valibot: ^1.1.0 + checksum: 10/3affc173d69dd4e4bc502d9af1cc62951baf7505442883f888b4cf03278a9b50308dfce118adbbfa617a589976c0daf57e5043190f2d44595816dc98c0a26d66 + languageName: node + linkType: hard + +"@tmcp/session-manager@npm:^0.2.1": + version: 0.2.1 + resolution: "@tmcp/session-manager@npm:0.2.1" + peerDependencies: + tmcp: ^1.16.3 + checksum: 10/fc063ae9adaecb799c806c683f9ac4946ccf9c54de61f41825e6967473971da99545a061396c9d0ec40506d8f6eedcf7f134df42125476f2d76a4890f5dc0462 + languageName: node + linkType: hard + +"@tmcp/transport-http@npm:^0.8.0": + version: 0.8.3 + resolution: "@tmcp/transport-http@npm:0.8.3" + dependencies: + "@tmcp/session-manager": "npm:^0.2.1" + esm-env: "npm:^1.2.2" + peerDependencies: + "@tmcp/auth": ^0.3.3 || ^0.4.0 + tmcp: ^1.18.0 + peerDependenciesMeta: + "@tmcp/auth": + optional: true + checksum: 10/049516cd3525948bb2b40a400ceef9b4fb3ed0668e67cb88cf355644e8cf1743fa57fc5370de0fa3e085cd82ee39093752f2e503af22b04a55a28a70f644be9c + languageName: node + linkType: hard + "@tokenizer/inflate@npm:^0.3.1": version: 0.3.1 resolution: "@tokenizer/inflate@npm:0.3.1" @@ -22844,6 +22917,15 @@ __metadata: languageName: node linkType: hard +"@valibot/to-json-schema@npm:^1.3.0": + version: 1.5.0 + resolution: "@valibot/to-json-schema@npm:1.5.0" + peerDependencies: + valibot: ^1.2.0 + checksum: 10/ea6285b258775f9112ad9b6a00bc6c7ab1af6491937e65edf43232a1ecf490cde3d14cd8474103684097ddcd9230bcd16d08805f3e0b62ecffa9d283f977c9b5 + languageName: node + linkType: hard + "@vitest/expect@npm:3.2.4, @vitest/expect@npm:>1.6.0": version: 3.2.4 resolution: "@vitest/expect@npm:3.2.4" @@ -30107,15 +30189,15 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-storybook@npm:^10.1.9": - version: 10.1.9 - resolution: "eslint-plugin-storybook@npm:10.1.9" +"eslint-plugin-storybook@npm:^10.2.0-alpha.7": + version: 10.2.0-alpha.7 + resolution: "eslint-plugin-storybook@npm:10.2.0-alpha.7" dependencies: "@typescript-eslint/utils": "npm:^8.8.1" peerDependencies: eslint: ">=8" - storybook: ^10.1.9 - checksum: 10/65ea272cf450373d8b1f718f6c1ab878309f88b7cb2b27071b941d2b9d3402b0a102c1a83565c3bec64a9bc4a99ccbc8c25860fdf3112fed4dbfbe56361b0213 + storybook: ^10.2.0-alpha.7 + checksum: 10/69e45c19a3d9e83a9e2454382dee4fe8f02ca8708d0bb485eb5ed6f55d1a854b106b9c2c280e56886be97913d8d468fda24e6c147de5a9bd385a3c597f205211 languageName: node linkType: hard @@ -30257,6 +30339,13 @@ __metadata: languageName: node linkType: hard +"esm-env@npm:^1.2.2": + version: 1.2.2 + resolution: "esm-env@npm:1.2.2" + checksum: 10/caf5f3cc2bc7107494585b4e38835787f48ef77b670aeb2d765a5b6b64c41102d20bbdd34bda32474291b6b8d819d4d02ce92570a0886baca6cef70f5fe689f3 + languageName: node + linkType: hard + "esm@npm:^3.2.25": version: 3.2.25 resolution: "esm@npm:3.2.25" @@ -35914,6 +36003,13 @@ __metadata: languageName: node linkType: hard +"json-rpc-2.0@npm:^1.7.1": + version: 1.7.1 + resolution: "json-rpc-2.0@npm:1.7.1" + checksum: 10/d5b91fc7fede0ff3f1fde915fa9b142cf0980fa01282572cf9373a45184b34b377e0738d05b41d3859eaebdd20f692073eebc0c5da4990a3c4633132eab52f08 + languageName: node + linkType: hard + "json-schema-compare@npm:^0.2.2": version: 0.2.2 resolution: "json-schema-compare@npm:0.2.2" @@ -45114,12 +45210,13 @@ __metadata: "@octokit/rest": "npm:^19.0.3" "@playwright/test": "npm:^1.32.3" "@spotify/eslint-plugin": "npm:^15.0.0" - "@storybook/addon-a11y": "npm:^10.1.9" - "@storybook/addon-docs": "npm:^10.1.9" - "@storybook/addon-links": "npm:^10.1.9" - "@storybook/addon-themes": "npm:^10.1.9" - "@storybook/addon-vitest": "npm:^10.1.9" - "@storybook/react-vite": "npm:^10.1.9" + "@storybook/addon-a11y": "npm:^10.2.0-alpha.7" + "@storybook/addon-docs": "npm:^10.2.0-alpha.7" + "@storybook/addon-links": "npm:^10.2.0-alpha.7" + "@storybook/addon-mcp": "npm:0.1.4-next.2" + "@storybook/addon-themes": "npm:^10.2.0-alpha.7" + "@storybook/addon-vitest": "npm:^10.2.0-alpha.7" + "@storybook/react-vite": "npm:^10.2.0-alpha.7" "@techdocs/cli": "workspace:*" "@types/cacheable-request": "npm:^8.3.6" "@types/global-agent": "npm:^2.1.3" @@ -45147,7 +45244,7 @@ __metadata: shx: "npm:^0.4.0" sloc: "npm:^0.3.1" sort-package-json: "npm:^2.8.0" - storybook: "npm:^10.1.10" + storybook: "npm:^10.2.0-alpha.7" ts-morph: "npm:^24.0.0" typedoc: "npm:^0.28.0" typescript: "npm:~5.7.0" @@ -46430,6 +46527,13 @@ __metadata: languageName: node linkType: hard +"sqids@npm:^0.3.0": + version: 0.3.0 + resolution: "sqids@npm:0.3.0" + checksum: 10/68f30434dae278a3ec030c2b16447cefd32129f73350aa3312ca55df48a8ba36bac0d9d2bef625020f13d57568586b6fa2bbbffa3051e55b6a3654caec31a8b6 + languageName: node + linkType: hard + "sqlstring@npm:^2.3.2": version: 2.3.2 resolution: "sqlstring@npm:2.3.2" @@ -46614,9 +46718,9 @@ __metadata: languageName: node linkType: hard -"storybook@npm:^10.1.10": - version: 10.1.10 - resolution: "storybook@npm:10.1.10" +"storybook@npm:^10.2.0-alpha.7": + version: 10.2.0-alpha.8 + resolution: "storybook@npm:10.2.0-alpha.8" dependencies: "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^2.0.0" @@ -46637,7 +46741,7 @@ __metadata: optional: true bin: storybook: ./dist/bin/dispatcher.js - checksum: 10/c1f01c7ab57e80d2f2ef3a5c49baad5904e77c8e079199ad134e98a7ae455d52422390cd704e64142b36668874c055670dbffe0e334e1f4d541ebd4384052dd7 + checksum: 10/bf7c2797b832ccb21efa68ae901ca3202e572762bd66b4a51ae2b2505dcc259402b7c81363b67675a7c81086bb90ca6e234965e10c4237f5cea1ab6739c88f39 languageName: node linkType: hard @@ -47927,6 +48031,19 @@ __metadata: languageName: node linkType: hard +"tmcp@npm:^1.16.0": + version: 1.19.0 + resolution: "tmcp@npm:1.19.0" + dependencies: + "@standard-schema/spec": "npm:^1.0.0" + json-rpc-2.0: "npm:^1.7.1" + sqids: "npm:^0.3.0" + uri-template-matcher: "npm:^1.1.1" + valibot: "npm:^1.1.0" + checksum: 10/f5a3f0ae48b6fcccc7b1bd0261706c92c43941f4b9498048736b2fa847da6d1547ca425f9dc296af1bd37a0bbd6f0190eee3d7f89555325056ade8d84d97d68b + languageName: node + linkType: hard + "tmp@npm:^0.2.5": version: 0.2.5 resolution: "tmp@npm:0.2.5" @@ -49259,6 +49376,13 @@ __metadata: languageName: node linkType: hard +"uri-template-matcher@npm:^1.1.1": + version: 1.1.2 + resolution: "uri-template-matcher@npm:1.1.2" + checksum: 10/fe9214a1cd3a6769ea9d1e16fdb1edcc74f3aa24cbdde4da02fbf6dbc358645cd08b818ec94eff7a15310e08e9dbfb48f73659bf121a9f6517ca9182defcb0ba + languageName: node + linkType: hard + "uri-template@npm:^2.0.0": version: 2.0.0 resolution: "uri-template@npm:2.0.0" @@ -49575,6 +49699,18 @@ __metadata: languageName: node linkType: hard +"valibot@npm:^1.1.0": + version: 1.2.0 + resolution: "valibot@npm:1.2.0" + peerDependencies: + typescript: ">=5" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/5f9c15e6f5a2b8eae75332a3317e46e995a1763efe1b91e57bc5064e36f0feba734367c88013d53255bdf09fb9204bf3598d2ca0c3f468c8726095b1c3551926 + languageName: node + linkType: hard + "valid-url@npm:^1.0.9": version: 1.0.9 resolution: "valid-url@npm:1.0.9" From 147364b4a8d8437e1dec95e75f516681d4d0db7f Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 18 Dec 2025 14:15:28 +0000 Subject: [PATCH 043/202] Add Storybook manifest Signed-off-by: Charles de Dreuille --- .storybook/preview.tsx | 1 + .../components/home/templates/CustomizableTemplate.stories.tsx | 1 + .../src/components/home/templates/DefaultTemplate.stories.tsx | 1 + .../src/components/AutoLogout/StillTherePrompt.stories.tsx | 1 + .../core-components/src/components/Avatar/Avatar.stories.tsx | 1 + packages/core-components/src/components/Chip/Chip.stories.tsx | 1 + .../src/components/CodeSnippet/CodeSnippet.stories.tsx | 1 + .../src/components/CopyTextButton/CopyTextButton.stories.tsx | 1 + .../src/components/DependencyGraph/DependencyGraph.stories.tsx | 1 + .../core-components/src/components/Dialog/Dialog.stories.tsx | 1 + .../components/DismissableBanner/DismissableBanner.stories.tsx | 1 + .../core-components/src/components/Drawer/Drawer.stories.tsx | 1 + .../src/components/EmptyState/EmptyState.stories.tsx | 1 + .../src/components/FavoriteToggle/FavoriteToggle.stories.tsx | 1 + .../components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx | 1 + .../components/HeaderIconLinkRow/IconLinkVertical.stories.tsx | 1 + .../HorizontalScrollGrid/HorizontalScrollGrid.stories.tsx | 1 + .../src/components/Lifecycle/Lifecycle.stories.tsx | 1 + packages/core-components/src/components/Link/Link.stories.tsx | 1 + .../src/components/LinkButton/LinkButton.stories.tsx | 1 + .../src/components/LogViewer/LogViewer.stories.tsx | 1 + .../src/components/MarkdownContent/MarkdownContent.stories.tsx | 1 + .../src/components/OverflowTooltip/OverflowTooltip.stories.tsx | 1 + .../core-components/src/components/Progress/Progress.stories.tsx | 1 + .../src/components/ProgressBars/Gauge.stories.tsx | 1 + .../src/components/ProgressBars/GaugeCard.stories.tsx | 1 + .../src/components/ProgressBars/LinearGauge.stories.tsx | 1 + .../components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx | 1 + .../core-components/src/components/Select/Select.stories.tsx | 1 + .../src/components/SimpleStepper/SimpleStepper.stories.tsx | 1 + .../core-components/src/components/Status/Status.stories.tsx | 1 + .../StructuredMetadataTable/StructuredMetadataTable.stories.tsx | 1 + .../src/components/TabbedLayout/TabbedLayout.stories.tsx | 1 + packages/core-components/src/components/Table/Table.stories.tsx | 1 + .../src/components/TrendLine/TrendLine.stories.tsx | 1 + .../src/components/WarningPanel/WarningPanel.stories.tsx | 1 + .../core-components/src/layout/BottomLink/BottomLink.stories.tsx | 1 + .../src/layout/Breadcrumbs/Breadcrumbs.stories.tsx | 1 + packages/core-components/src/layout/Content/Content.stories.tsx | 1 + .../src/layout/ContentHeader/ContentHeader.stories.tsx | 1 + packages/core-components/src/layout/Header/Header.stories.tsx | 1 + .../src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx | 1 + .../src/layout/HeaderLabel/HeaderLabel.stories.tsx | 1 + .../core-components/src/layout/HeaderTabs/HeaderTabs.stories.tsx | 1 + .../core-components/src/layout/InfoCard/InfoCard.stories.tsx | 1 + .../core-components/src/layout/ItemCard/ItemCard.stories.tsx | 1 + packages/core-components/src/layout/Page/Page.stories.tsx | 1 + packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx | 1 + .../core-components/src/layout/TabbedCard/TabbedCard.stories.tsx | 1 + .../CatalogAutocomplete/CatalogAutocomplete.stories.tsx | 1 + .../components/EntityDisplayName/EntityDisplayName.stories.tsx | 1 + .../EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx | 1 + .../src/components/EntityRefLink/EntityRefLink.stories.tsx | 1 + .../src/components/EntityRefLink/EntityRefLinks.stories.tsx | 1 + .../src/homePageComponents/CompanyLogo/CompanyLogo.stories.tsx | 1 + .../FeaturedDocsCard/FeaturedDocsCard.stories.tsx | 1 + .../HeaderWorldClock/HeaderWorldClock.stories.tsx | 1 + .../src/homePageComponents/QuickStart/QuickStart.stories.tsx | 1 + .../StarredEntities/StarredEntities.stories.tsx | 1 + plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx | 1 + .../VisitedByType/HomePageVisitedByType.stories.tsx | 1 + .../src/homePageComponents/WelcomeTitle/WelcomeTitle.stories.tsx | 1 + .../src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx | 1 + .../Cards/User/UserProfileCard/UserProfileCard.stories.tsx | 1 + .../DefaultResultListItem/DefaultResultListItem.stories.tsx | 1 + .../components/SearchAutocomplete/SearchAutocomplete.stories.tsx | 1 + .../SearchAutocompleteDefaultOption.stories.tsx | 1 + .../search-react/src/components/SearchBar/SearchBar.stories.tsx | 1 + .../src/components/SearchFilter/SearchFilter.stories.tsx | 1 + .../src/components/SearchPagination/SearchPagination.stories.tsx | 1 + .../src/components/SearchResult/SearchResult.stories.tsx | 1 + .../components/SearchResultGroup/SearchResultGroup.stories.tsx | 1 + .../src/components/SearchResultList/SearchResultList.stories.tsx | 1 + .../components/HomePageComponent/HomePageSearchBar.stories.tsx | 1 + .../search/src/components/SearchModal/SearchModal.stories.tsx | 1 + plugins/search/src/components/SearchType/SearchType.stories.tsx | 1 + 76 files changed, 76 insertions(+) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 00aba69398..d22fd04bee 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -21,6 +21,7 @@ import './storybook.css'; import './themes/spotify.css'; export default definePreview({ + tags: ['manifest'], globalTypes: { themeMode: { name: 'Theme Mode', diff --git a/packages/app/src/components/home/templates/CustomizableTemplate.stories.tsx b/packages/app/src/components/home/templates/CustomizableTemplate.stories.tsx index 325fee4ca4..2711a95bf5 100644 --- a/packages/app/src/components/home/templates/CustomizableTemplate.stories.tsx +++ b/packages/app/src/components/home/templates/CustomizableTemplate.stories.tsx @@ -78,6 +78,7 @@ starredEntitiesApi.toggleStarred('component:default/example-starred-entity-4'); export default { title: 'Plugins/Home/Templates', + tags: ['!manifest'], decorators: [ (Story: ComponentType<{}>) => wrapInTestApp( diff --git a/packages/app/src/components/home/templates/DefaultTemplate.stories.tsx b/packages/app/src/components/home/templates/DefaultTemplate.stories.tsx index 9ac8bdaaf8..d01c6aabdf 100644 --- a/packages/app/src/components/home/templates/DefaultTemplate.stories.tsx +++ b/packages/app/src/components/home/templates/DefaultTemplate.stories.tsx @@ -86,6 +86,7 @@ starredEntitiesApi.toggleStarred('component:default/example-starred-entity-4'); export default { title: 'Plugins/Home/Templates', + tags: ['!manifest'], decorators: [ (Story: ComponentType>) => wrapInTestApp( diff --git a/packages/core-components/src/components/AutoLogout/StillTherePrompt.stories.tsx b/packages/core-components/src/components/AutoLogout/StillTherePrompt.stories.tsx index 7de1bef509..a55f4031a6 100644 --- a/packages/core-components/src/components/AutoLogout/StillTherePrompt.stories.tsx +++ b/packages/core-components/src/components/AutoLogout/StillTherePrompt.stories.tsx @@ -18,6 +18,7 @@ import { StillTherePrompt, StillTherePromptProps } from './StillTherePrompt'; export default { title: 'Data Display/StillTherePrompt', component: StillTherePrompt, + tags: ['!manifest'], }; export const Open = (args: StillTherePromptProps) => ( diff --git a/packages/core-components/src/components/Avatar/Avatar.stories.tsx b/packages/core-components/src/components/Avatar/Avatar.stories.tsx index 4843e2bb73..b51da0acb4 100644 --- a/packages/core-components/src/components/Avatar/Avatar.stories.tsx +++ b/packages/core-components/src/components/Avatar/Avatar.stories.tsx @@ -28,6 +28,7 @@ const useStyles = makeStyles({ export default { title: 'Data Display/Avatar', component: Avatar, + tags: ['!manifest'], }; export const Default = () => ( diff --git a/packages/core-components/src/components/Chip/Chip.stories.tsx b/packages/core-components/src/components/Chip/Chip.stories.tsx index a20a6d5699..b981d34ec7 100644 --- a/packages/core-components/src/components/Chip/Chip.stories.tsx +++ b/packages/core-components/src/components/Chip/Chip.stories.tsx @@ -53,6 +53,7 @@ export default { }, }, }, + tags: ['!manifest'], }; export const Default = (args: ChipProps) => ; diff --git a/packages/core-components/src/components/CodeSnippet/CodeSnippet.stories.tsx b/packages/core-components/src/components/CodeSnippet/CodeSnippet.stories.tsx index bea5cf778e..d5dacced13 100644 --- a/packages/core-components/src/components/CodeSnippet/CodeSnippet.stories.tsx +++ b/packages/core-components/src/components/CodeSnippet/CodeSnippet.stories.tsx @@ -20,6 +20,7 @@ import { InfoCard } from '../../layout/InfoCard'; export default { title: 'Data Display/CodeSnippet', component: CodeSnippet, + tags: ['!manifest'], }; const containerStyle = { width: 300 }; diff --git a/packages/core-components/src/components/CopyTextButton/CopyTextButton.stories.tsx b/packages/core-components/src/components/CopyTextButton/CopyTextButton.stories.tsx index 39c6782207..233c41aff8 100644 --- a/packages/core-components/src/components/CopyTextButton/CopyTextButton.stories.tsx +++ b/packages/core-components/src/components/CopyTextButton/CopyTextButton.stories.tsx @@ -19,6 +19,7 @@ import { CopyTextButton } from './CopyTextButton'; export default { title: 'Inputs/CopyTextButton', component: CopyTextButton, + tags: ['!manifest'], }; export const Default = () => ( diff --git a/packages/core-components/src/components/DependencyGraph/DependencyGraph.stories.tsx b/packages/core-components/src/components/DependencyGraph/DependencyGraph.stories.tsx index 3ed4a25730..ae6c235638 100644 --- a/packages/core-components/src/components/DependencyGraph/DependencyGraph.stories.tsx +++ b/packages/core-components/src/components/DependencyGraph/DependencyGraph.stories.tsx @@ -20,6 +20,7 @@ import { DependencyGraphTypes as Types } from './types'; export default { title: 'Data Display/DependencyGraph', component: DependencyGraph, + tags: ['!manifest'], }; const containerStyle = { width: '100%' }; diff --git a/packages/core-components/src/components/Dialog/Dialog.stories.tsx b/packages/core-components/src/components/Dialog/Dialog.stories.tsx index 7171e9c543..d80fc51773 100644 --- a/packages/core-components/src/components/Dialog/Dialog.stories.tsx +++ b/packages/core-components/src/components/Dialog/Dialog.stories.tsx @@ -27,6 +27,7 @@ import CloseIcon from '@material-ui/icons/Close'; const meta = { title: 'Layout/Dialog', component: Dialog, + tags: ['!manifest'], }; export default meta; diff --git a/packages/core-components/src/components/DismissableBanner/DismissableBanner.stories.tsx b/packages/core-components/src/components/DismissableBanner/DismissableBanner.stories.tsx index 86c7270719..c2340c913a 100644 --- a/packages/core-components/src/components/DismissableBanner/DismissableBanner.stories.tsx +++ b/packages/core-components/src/components/DismissableBanner/DismissableBanner.stories.tsx @@ -30,6 +30,7 @@ export default { control: { type: 'select' }, }, }, + tags: ['!manifest'], }; const containerStyle = { width: '70%' }; diff --git a/packages/core-components/src/components/Drawer/Drawer.stories.tsx b/packages/core-components/src/components/Drawer/Drawer.stories.tsx index 0c7e8dbfdb..abf0ab9082 100644 --- a/packages/core-components/src/components/Drawer/Drawer.stories.tsx +++ b/packages/core-components/src/components/Drawer/Drawer.stories.tsx @@ -25,6 +25,7 @@ import Close from '@material-ui/icons/Close'; export default { title: 'Layout/Drawer', component: Drawer, + tags: ['!manifest'], }; const useDrawerStyles = makeStyles((theme: Theme) => diff --git a/packages/core-components/src/components/EmptyState/EmptyState.stories.tsx b/packages/core-components/src/components/EmptyState/EmptyState.stories.tsx index 07f6ea8fa1..a185b95e88 100644 --- a/packages/core-components/src/components/EmptyState/EmptyState.stories.tsx +++ b/packages/core-components/src/components/EmptyState/EmptyState.stories.tsx @@ -21,6 +21,7 @@ import { MissingAnnotationEmptyState } from './MissingAnnotationEmptyState'; export default { title: 'Feedback/EmptyState', component: EmptyState, + tags: ['!manifest'], }; const containerStyle = { width: '100%', height: '100vh' }; diff --git a/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.stories.tsx b/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.stories.tsx index af3787023a..aec3739e6c 100644 --- a/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.stories.tsx +++ b/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.stories.tsx @@ -29,6 +29,7 @@ export default { decorators: [ (Story: ComponentType>) => wrapInTestApp(), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx b/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx index a02944a827..25e0b407f9 100644 --- a/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx +++ b/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx @@ -24,6 +24,7 @@ type Props = { export default { title: 'Data Display/HeaderIconLinkRow', component: HeaderIconLinkRow, + tags: ['!manifest'], }; export const Default = (args: Props) => ; diff --git a/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.stories.tsx b/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.stories.tsx index 956ee6fd0e..c2d630e842 100644 --- a/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.stories.tsx +++ b/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.stories.tsx @@ -19,6 +19,7 @@ import LinkIcon from '@material-ui/icons/Link'; export default { title: 'Core/IconLinkVertical', component: IconLinkVertical, + tags: ['!manifest'], }; export const Primary = (args: IconLinkVerticalProps) => ( diff --git a/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.stories.tsx b/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.stories.tsx index 8f5904bc1d..36b4dd2026 100644 --- a/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.stories.tsx +++ b/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.stories.tsx @@ -22,6 +22,7 @@ const opacityArray = [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]; export default { title: 'Layout/HorizontalScrollGrid', component: HorizontalScrollGrid, + tags: ['!manifest'], }; export const Default = () => ( diff --git a/packages/core-components/src/components/Lifecycle/Lifecycle.stories.tsx b/packages/core-components/src/components/Lifecycle/Lifecycle.stories.tsx index 1e7db283c8..9812d0b19a 100644 --- a/packages/core-components/src/components/Lifecycle/Lifecycle.stories.tsx +++ b/packages/core-components/src/components/Lifecycle/Lifecycle.stories.tsx @@ -18,6 +18,7 @@ import { Lifecycle } from './Lifecycle'; export default { title: 'Feedback/Lifecycle', component: Lifecycle, + tags: ['!manifest'], }; export const AlphaDefault = () => ( diff --git a/packages/core-components/src/components/Link/Link.stories.tsx b/packages/core-components/src/components/Link/Link.stories.tsx index cd9cb825e5..36a73b1ef4 100644 --- a/packages/core-components/src/components/Link/Link.stories.tsx +++ b/packages/core-components/src/components/Link/Link.stories.tsx @@ -48,6 +48,7 @@ export default { { mountedRoutes: { '/hello': routeRef } }, ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/packages/core-components/src/components/LinkButton/LinkButton.stories.tsx b/packages/core-components/src/components/LinkButton/LinkButton.stories.tsx index 9d33ad3fc1..a333d2f789 100644 --- a/packages/core-components/src/components/LinkButton/LinkButton.stories.tsx +++ b/packages/core-components/src/components/LinkButton/LinkButton.stories.tsx @@ -63,6 +63,7 @@ export default { { mountedRoutes: { '/hello': routeRef } }, ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx b/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx index 984477a6f9..dba406a7c8 100644 --- a/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx +++ b/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx @@ -24,6 +24,7 @@ export default { decorators: [ (Story: ComponentType>) => wrapInTestApp(), ], + tags: ['!manifest'], }; const exampleLog = `Starting up task with 3 steps diff --git a/packages/core-components/src/components/MarkdownContent/MarkdownContent.stories.tsx b/packages/core-components/src/components/MarkdownContent/MarkdownContent.stories.tsx index b6770e7d37..ca45841b25 100644 --- a/packages/core-components/src/components/MarkdownContent/MarkdownContent.stories.tsx +++ b/packages/core-components/src/components/MarkdownContent/MarkdownContent.stories.tsx @@ -19,6 +19,7 @@ import { MarkdownContent } from './MarkdownContent'; export default { title: 'Data Display/MarkdownContent', component: MarkdownContent, + tags: ['!manifest'], }; const markdownGithubFlavored = diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx index 3b5df0e358..f48002d018 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx @@ -19,6 +19,7 @@ import { OverflowTooltip } from './OverflowTooltip'; export default { title: 'Data Display/OverflowTooltip', component: OverflowTooltip, + tags: ['!manifest'], }; const text = diff --git a/packages/core-components/src/components/Progress/Progress.stories.tsx b/packages/core-components/src/components/Progress/Progress.stories.tsx index 7c7932101e..7120f699bf 100644 --- a/packages/core-components/src/components/Progress/Progress.stories.tsx +++ b/packages/core-components/src/components/Progress/Progress.stories.tsx @@ -19,6 +19,7 @@ import { Progress } from './Progress'; export default { title: 'Feedback/Progress', component: Progress, + tags: ['!manifest'], }; export const progress = () => ; diff --git a/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx b/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx index 4dcb788289..9578a35864 100644 --- a/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx @@ -21,6 +21,7 @@ const containerStyle = { width: 300 }; export default { title: 'Data Display/Gauge', component: Gauge, + tags: ['!manifest'], }; export const Default = () => ( diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx index 0377c37ef0..6b673a65d5 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx @@ -34,6 +34,7 @@ const Wrapper = ({ children }: PropsWithChildren<{}>) => ( export default { title: 'Data Display/Progress Card', component: GaugeCard, + tags: ['!manifest'], }; export const Default = () => ( diff --git a/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx b/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx index 295856bd51..eebfb7356c 100644 --- a/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx @@ -21,6 +21,7 @@ const containerStyle = { width: 300 }; export default { title: 'Data Display/LinearGauge', component: LinearGauge, + tags: ['!manifest'], }; export const Default = () => ( diff --git a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx index 7eab08f55f..050ae2ce29 100644 --- a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx +++ b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx @@ -20,6 +20,7 @@ import { ErrorPanelProps } from '../ErrorPanel'; export default { title: 'Data Display/ResponseErrorPanel', component: ResponseErrorPanel, + tags: ['!manifest'], }; export const Default = (args: ErrorPanelProps) => ( diff --git a/packages/core-components/src/components/Select/Select.stories.tsx b/packages/core-components/src/components/Select/Select.stories.tsx index f3eaea1021..d6a11244a4 100644 --- a/packages/core-components/src/components/Select/Select.stories.tsx +++ b/packages/core-components/src/components/Select/Select.stories.tsx @@ -19,6 +19,7 @@ import { SelectComponent as Select, SelectProps } from './Select'; export default { title: 'Inputs/Select', component: Select, + tags: ['!manifest'], }; const SELECT_ITEMS = [ diff --git a/packages/core-components/src/components/SimpleStepper/SimpleStepper.stories.tsx b/packages/core-components/src/components/SimpleStepper/SimpleStepper.stories.tsx index a50183b179..d99e1b0a39 100644 --- a/packages/core-components/src/components/SimpleStepper/SimpleStepper.stories.tsx +++ b/packages/core-components/src/components/SimpleStepper/SimpleStepper.stories.tsx @@ -22,6 +22,7 @@ import { SimpleStepperStep } from './SimpleStepperStep'; export default { title: 'Navigation/SimpleStepper', component: SimpleStepper, + tags: ['!manifest'], }; const defaultArgs = { diff --git a/packages/core-components/src/components/Status/Status.stories.tsx b/packages/core-components/src/components/Status/Status.stories.tsx index 1237a02432..1404ae4373 100644 --- a/packages/core-components/src/components/Status/Status.stories.tsx +++ b/packages/core-components/src/components/Status/Status.stories.tsx @@ -28,6 +28,7 @@ import { InfoCard } from '../../layout/InfoCard'; export default { title: 'Data Display/Status', component: StatusOK, + tags: ['!manifest'], }; const data = [ diff --git a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx index 8a30300685..2ee11fd23d 100644 --- a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx +++ b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx @@ -41,6 +41,7 @@ const metadata = { export default { title: 'Data Display/Structured Metadata Table', component: StructuredMetadataTable, + tags: ['!manifest'], }; const Wrapper = ({ children }: PropsWithChildren<{}>) => ( diff --git a/packages/core-components/src/components/TabbedLayout/TabbedLayout.stories.tsx b/packages/core-components/src/components/TabbedLayout/TabbedLayout.stories.tsx index a4060d9d43..b0a6754fd2 100644 --- a/packages/core-components/src/components/TabbedLayout/TabbedLayout.stories.tsx +++ b/packages/core-components/src/components/TabbedLayout/TabbedLayout.stories.tsx @@ -20,6 +20,7 @@ import { TabbedLayout } from './TabbedLayout'; export default { title: 'Navigation/TabbedLayout', component: TabbedLayout, + tags: ['!manifest'], }; const Wrapper = ({ children }: PropsWithChildren<{}>) => ( diff --git a/packages/core-components/src/components/Table/Table.stories.tsx b/packages/core-components/src/components/Table/Table.stories.tsx index ac8303cde7..c17726d5fd 100644 --- a/packages/core-components/src/components/Table/Table.stories.tsx +++ b/packages/core-components/src/components/Table/Table.stories.tsx @@ -23,6 +23,7 @@ import { Table, TableColumn, TableFilter } from './Table'; export default { title: 'Data Display/Table', component: Table, + tags: ['!manifest'], }; const useStyles = makeStyles(theme => ({ diff --git a/packages/core-components/src/components/TrendLine/TrendLine.stories.tsx b/packages/core-components/src/components/TrendLine/TrendLine.stories.tsx index 2f1e9bc4f1..710c0448f4 100644 --- a/packages/core-components/src/components/TrendLine/TrendLine.stories.tsx +++ b/packages/core-components/src/components/TrendLine/TrendLine.stories.tsx @@ -21,6 +21,7 @@ import { InfoCard } from '../../layout/InfoCard'; export default { title: 'Data Display/TrendLine', component: TrendLine, + tags: ['!manifest'], }; const containerStyle = { width: 700 }; diff --git a/packages/core-components/src/components/WarningPanel/WarningPanel.stories.tsx b/packages/core-components/src/components/WarningPanel/WarningPanel.stories.tsx index 7e45f3c398..9fa950dc0c 100644 --- a/packages/core-components/src/components/WarningPanel/WarningPanel.stories.tsx +++ b/packages/core-components/src/components/WarningPanel/WarningPanel.stories.tsx @@ -22,6 +22,7 @@ import { Link } from '../Link'; export default { title: 'Feedback/Warning Panel', component: WarningPanel, + tags: ['!manifest'], }; export const Default = () => ( diff --git a/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx b/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx index 93607c35f5..345030b80c 100644 --- a/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx +++ b/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx @@ -19,6 +19,7 @@ import { BottomLink } from '../BottomLink'; export default { title: 'Layout/BottomLink', component: BottomLink, + tags: ['!manifest'], }; export const Default = (args: { link: string; title: string }) => ( diff --git a/packages/core-components/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx b/packages/core-components/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx index f962e4673e..cec62304ee 100644 --- a/packages/core-components/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx +++ b/packages/core-components/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx @@ -31,6 +31,7 @@ import { Breadcrumbs } from './Breadcrumbs'; export default { title: 'Layout/Breadcrumbs', component: Breadcrumbs, + tags: ['!manifest'], }; export const InHeader = () => ( diff --git a/packages/core-components/src/layout/Content/Content.stories.tsx b/packages/core-components/src/layout/Content/Content.stories.tsx index 8879c3c51d..eb6947b3f7 100644 --- a/packages/core-components/src/layout/Content/Content.stories.tsx +++ b/packages/core-components/src/layout/Content/Content.stories.tsx @@ -18,6 +18,7 @@ import { Content } from './Content'; export default { title: 'Layout/Content', component: Content, + tags: ['!manifest'], }; type Props = { diff --git a/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx b/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx index 6b4ced0edd..989fba81ce 100644 --- a/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx +++ b/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx @@ -20,6 +20,7 @@ import { ContentHeader } from '../ContentHeader'; export default { title: 'Layout/ContentHeader', component: ContentHeader, + tags: ['!manifest'], }; type ContentHeaderProps = { diff --git a/packages/core-components/src/layout/Header/Header.stories.tsx b/packages/core-components/src/layout/Header/Header.stories.tsx index 3b9c67fce5..a87c89544a 100644 --- a/packages/core-components/src/layout/Header/Header.stories.tsx +++ b/packages/core-components/src/layout/Header/Header.stories.tsx @@ -36,6 +36,7 @@ export default { control: { type: 'select' }, }, }, + tags: ['!manifest'], }; const labels = ( diff --git a/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx b/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx index 0fc4139dfb..b087c08d06 100644 --- a/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx +++ b/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx @@ -18,6 +18,7 @@ import { HeaderActionMenu, HeaderActionMenuProps } from './HeaderActionMenu'; export default { title: 'Layout/HeaderActionMenu', component: HeaderActionMenu, + tags: ['!manifest'], }; export const Default = (args: HeaderActionMenuProps) => ( diff --git a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx index dd93b0768c..60028eb89c 100644 --- a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx +++ b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx @@ -18,6 +18,7 @@ import { HeaderLabel } from './HeaderLabel'; export default { title: 'Layout/HeaderLabel', component: HeaderLabel, + tags: ['!manifest'], }; type HeaderLabelProps = { diff --git a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.stories.tsx b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.stories.tsx index 08ea2a0648..b551bb1d04 100644 --- a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.stories.tsx +++ b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.stories.tsx @@ -18,6 +18,7 @@ import { HeaderTabs } from './HeaderTabs'; export default { title: 'Layout/HeaderTabs', component: HeaderTabs, + tags: ['!manifest'], }; export const SingleTab = (args: any) => ; diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.stories.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.stories.tsx index 72bc79b674..10400412c6 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.stories.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.stories.tsx @@ -23,6 +23,7 @@ import { InfoCard, Props } from './InfoCard'; export default { title: 'Layout/Information Card', component: InfoCard, + tags: ['!manifest'], }; const text = ( diff --git a/packages/core-components/src/layout/ItemCard/ItemCard.stories.tsx b/packages/core-components/src/layout/ItemCard/ItemCard.stories.tsx index def451d418..4ab03c305f 100644 --- a/packages/core-components/src/layout/ItemCard/ItemCard.stories.tsx +++ b/packages/core-components/src/layout/ItemCard/ItemCard.stories.tsx @@ -27,6 +27,7 @@ import { ItemCardHeader } from './ItemCardHeader'; export default { title: 'Layout/Item Cards', + tags: ['!manifest'], }; const text = diff --git a/packages/core-components/src/layout/Page/Page.stories.tsx b/packages/core-components/src/layout/Page/Page.stories.tsx index aa0aedbe5d..2ec3afebb7 100644 --- a/packages/core-components/src/layout/Page/Page.stories.tsx +++ b/packages/core-components/src/layout/Page/Page.stories.tsx @@ -40,6 +40,7 @@ import { Page } from '../Page'; export default { title: 'Plugins/Examples', component: Page, + tags: ['!manifest'], }; interface TableData { diff --git a/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx b/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx index f02fad5095..d5720d00a7 100644 --- a/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx +++ b/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx @@ -48,6 +48,7 @@ export default { (Story: ComponentType>) => wrapInTestApp(, { mountedRoutes: { '/': routeRef } }), ], + tags: ['!manifest'], }; const handleSearch = (input: string) => { diff --git a/packages/core-components/src/layout/TabbedCard/TabbedCard.stories.tsx b/packages/core-components/src/layout/TabbedCard/TabbedCard.stories.tsx index 7c83dad083..c9019107ec 100644 --- a/packages/core-components/src/layout/TabbedCard/TabbedCard.stories.tsx +++ b/packages/core-components/src/layout/TabbedCard/TabbedCard.stories.tsx @@ -36,6 +36,7 @@ export default { ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.stories.tsx b/plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.stories.tsx index d4328b3bae..20168e5b38 100644 --- a/plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.stories.tsx +++ b/plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.stories.tsx @@ -20,6 +20,7 @@ import { CatalogAutocomplete } from './CatalogAutocomplete'; export default { title: 'Catalog /CatalogAutocomplete', decorators: [(Story: ComponentType<{}>) => wrapInTestApp()], + tags: ['!manifest'], }; export const Default = (args: any) => { diff --git a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.stories.tsx b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.stories.tsx index 8ae4e8fd50..07faaae45a 100644 --- a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.stories.tsx +++ b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.stories.tsx @@ -25,6 +25,7 @@ const defaultArgs = { export default { title: 'Catalog /EntityDisplayName', decorators: [(Story: ComponentType<{}>) => wrapInTestApp()], + tags: ['!manifest'], }; export const Default = (args: EntityDisplayNameProps) => ( diff --git a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx index 7c663de0d6..a9cf501728 100644 --- a/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx +++ b/plugins/catalog-react/src/components/EntityPeekAheadPopover/EntityPeekAheadPopover.stories.tsx @@ -89,6 +89,7 @@ export default { }, ), ], + tags: ['!manifest'], }; export const Default = (args: EntityPeekAheadPopoverProps) => ( diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.stories.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.stories.tsx index f75c3e743b..9bdb077228 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.stories.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.stories.tsx @@ -33,6 +33,7 @@ export default { }, }), ], + tags: ['!manifest'], }; export const Default = (args: EntityRefLinkProps) => ( diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.stories.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.stories.tsx index b34b5db8d7..ade279b1b5 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.stories.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.stories.tsx @@ -34,6 +34,7 @@ export default { }, }), ], + tags: ['!manifest'], }; export const Default = ( diff --git a/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.stories.tsx b/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.stories.tsx index 6c97dfe558..55710e6c1c 100644 --- a/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.stories.tsx +++ b/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.stories.tsx @@ -41,6 +41,7 @@ export default { }, ), ], + tags: ['!manifest'], }; const useLogoStyles = makeStyles(theme => ({ diff --git a/plugins/home/src/homePageComponents/FeaturedDocsCard/FeaturedDocsCard.stories.tsx b/plugins/home/src/homePageComponents/FeaturedDocsCard/FeaturedDocsCard.stories.tsx index 25f02e5f4e..ad51472e59 100644 --- a/plugins/home/src/homePageComponents/FeaturedDocsCard/FeaturedDocsCard.stories.tsx +++ b/plugins/home/src/homePageComponents/FeaturedDocsCard/FeaturedDocsCard.stories.tsx @@ -54,6 +54,7 @@ export default { }, ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/home/src/homePageComponents/HeaderWorldClock/HeaderWorldClock.stories.tsx b/plugins/home/src/homePageComponents/HeaderWorldClock/HeaderWorldClock.stories.tsx index fbc5a96c57..53b9a390e6 100644 --- a/plugins/home/src/homePageComponents/HeaderWorldClock/HeaderWorldClock.stories.tsx +++ b/plugins/home/src/homePageComponents/HeaderWorldClock/HeaderWorldClock.stories.tsx @@ -24,6 +24,7 @@ export default { decorators: [ (Story: ComponentType>) => wrapInTestApp(), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/home/src/homePageComponents/QuickStart/QuickStart.stories.tsx b/plugins/home/src/homePageComponents/QuickStart/QuickStart.stories.tsx index c0963b88a1..0f83f7915e 100644 --- a/plugins/home/src/homePageComponents/QuickStart/QuickStart.stories.tsx +++ b/plugins/home/src/homePageComponents/QuickStart/QuickStart.stories.tsx @@ -26,6 +26,7 @@ export default { decorators: [ (Story: ComponentType>) => wrapInTestApp(), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/home/src/homePageComponents/StarredEntities/StarredEntities.stories.tsx b/plugins/home/src/homePageComponents/StarredEntities/StarredEntities.stories.tsx index f2c294caf7..980d09c89b 100644 --- a/plugins/home/src/homePageComponents/StarredEntities/StarredEntities.stories.tsx +++ b/plugins/home/src/homePageComponents/StarredEntities/StarredEntities.stories.tsx @@ -89,6 +89,7 @@ export default { }, ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx b/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx index 6858982306..60ea9fea53 100644 --- a/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx +++ b/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx @@ -27,6 +27,7 @@ export default { decorators: [ (Story: ComponentType>) => wrapInTestApp(), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/home/src/homePageComponents/VisitedByType/HomePageVisitedByType.stories.tsx b/plugins/home/src/homePageComponents/VisitedByType/HomePageVisitedByType.stories.tsx index 85d7dbf9cc..b06f38598a 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/HomePageVisitedByType.stories.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/HomePageVisitedByType.stories.tsx @@ -109,6 +109,7 @@ export default { , ), ], + tags: ['!manifest'], }; export const RecentlyDefault = () => { diff --git a/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.stories.tsx b/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.stories.tsx index b1eb689d1d..c189c0d4ac 100644 --- a/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.stories.tsx +++ b/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.stories.tsx @@ -24,6 +24,7 @@ export default { decorators: [ (Story: ComponentType>) => wrapInTestApp(), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx index 67d1073dab..ce0712f076 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx @@ -29,6 +29,7 @@ import { OwnershipCard } from './OwnershipCard'; export default { title: 'Plugins/Org/Ownership Card', component: OwnershipCard, + tags: ['!manifest'], }; const defaultEntity: GroupEntity = { diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx index f485a4e92d..0463cc78ad 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx @@ -99,6 +99,7 @@ export default { }, }), ], + tags: ['!manifest'], }; const extraDetailsEntity: UserEntity = { diff --git a/plugins/search-react/src/components/DefaultResultListItem/DefaultResultListItem.stories.tsx b/plugins/search-react/src/components/DefaultResultListItem/DefaultResultListItem.stories.tsx index 9ea575139d..616dc3a4fe 100644 --- a/plugins/search-react/src/components/DefaultResultListItem/DefaultResultListItem.stories.tsx +++ b/plugins/search-react/src/components/DefaultResultListItem/DefaultResultListItem.stories.tsx @@ -38,6 +38,7 @@ export default { ), ], + tags: ['!manifest'], }; const mockSearchResult = { diff --git a/plugins/search-react/src/components/SearchAutocomplete/SearchAutocomplete.stories.tsx b/plugins/search-react/src/components/SearchAutocomplete/SearchAutocomplete.stories.tsx index da504497e0..961acfac03 100644 --- a/plugins/search-react/src/components/SearchAutocomplete/SearchAutocomplete.stories.tsx +++ b/plugins/search-react/src/components/SearchAutocomplete/SearchAutocomplete.stories.tsx @@ -44,6 +44,7 @@ export default { , ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/search-react/src/components/SearchAutocomplete/SearchAutocompleteDefaultOption.stories.tsx b/plugins/search-react/src/components/SearchAutocomplete/SearchAutocompleteDefaultOption.stories.tsx index ca22974cc7..da5065752e 100644 --- a/plugins/search-react/src/components/SearchAutocomplete/SearchAutocompleteDefaultOption.stories.tsx +++ b/plugins/search-react/src/components/SearchAutocomplete/SearchAutocompleteDefaultOption.stories.tsx @@ -45,6 +45,7 @@ export default { ), ], + tags: ['!manifest'], }; export const Default = () => ( diff --git a/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx b/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx index e2cbfc6bc8..8b96811666 100644 --- a/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx +++ b/plugins/search-react/src/components/SearchBar/SearchBar.stories.tsx @@ -45,6 +45,7 @@ export default { , ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.stories.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.stories.tsx index 4d66bd073f..de9f137ebd 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.stories.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.stories.tsx @@ -40,6 +40,7 @@ export default { ), ], + tags: ['!manifest'], }; export const CheckBoxFilter = () => { diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx index a8318f8789..8618639462 100644 --- a/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx @@ -40,6 +40,7 @@ export default { ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/search-react/src/components/SearchResult/SearchResult.stories.tsx b/plugins/search-react/src/components/SearchResult/SearchResult.stories.tsx index 9bf39a5069..3dfc4c18ef 100644 --- a/plugins/search-react/src/components/SearchResult/SearchResult.stories.tsx +++ b/plugins/search-react/src/components/SearchResult/SearchResult.stories.tsx @@ -80,6 +80,7 @@ export default { , ), ], + tags: ['!manifest'], }; const CustomResultListItem = (props: { result: SearchDocument }) => { diff --git a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx index 37c6a4b8fb..16cfbdfccc 100644 --- a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx +++ b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx @@ -82,6 +82,7 @@ export default { { mountedRoutes: { '/': routeRef } }, ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx b/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx index a006dc89d6..39c2461350 100644 --- a/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx +++ b/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx @@ -74,6 +74,7 @@ export default { { mountedRoutes: { '/': routeRef } }, ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx b/plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx index a8331396a0..a42eb93468 100644 --- a/plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx +++ b/plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx @@ -40,6 +40,7 @@ export default { }, ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/search/src/components/SearchModal/SearchModal.stories.tsx b/plugins/search/src/components/SearchModal/SearchModal.stories.tsx index 266fadeb87..5847904f51 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.stories.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.stories.tsx @@ -85,6 +85,7 @@ export default { { mountedRoutes: { '/search': rootRouteRef } }, ), ], + tags: ['!manifest'], }; export const Default = () => { diff --git a/plugins/search/src/components/SearchType/SearchType.stories.tsx b/plugins/search/src/components/SearchType/SearchType.stories.tsx index 7ace231b40..d892d2da7a 100644 --- a/plugins/search/src/components/SearchType/SearchType.stories.tsx +++ b/plugins/search/src/components/SearchType/SearchType.stories.tsx @@ -44,6 +44,7 @@ export default { ), ], + tags: ['!manifest'], }; const values = ['value-1', 'value-2', 'value-3']; From a970457e605c472b5c5d363ce67e9e2314e00ee3 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 18 Dec 2025 15:43:31 +0000 Subject: [PATCH 044/202] Fixes stories for manifest Signed-off-by: Charles de Dreuille --- packages/ui/src/components/Button/Button.tsx | 31 ++++++++++++++++++- .../ui/src/components/Card/Card.stories.tsx | 3 +- .../src/components/Dialog/Dialog.stories.tsx | 18 ++++------- .../ui/src/components/Grid/Grid.stories.tsx | 2 ++ .../src/components/Header/Header.stories.tsx | 5 +-- .../PasswordField/PasswordField.stories.tsx | 4 +-- .../components/TagGroup/TagGroup.stories.tsx | 22 ++++++------- .../TextField/TextField.stories.tsx | 4 +-- 8 files changed, 53 insertions(+), 36 deletions(-) diff --git a/packages/ui/src/components/Button/Button.tsx b/packages/ui/src/components/Button/Button.tsx index f010e5e57d..2ead32fccd 100644 --- a/packages/ui/src/components/Button/Button.tsx +++ b/packages/ui/src/components/Button/Button.tsx @@ -23,7 +23,36 @@ import { useStyles } from '../../hooks/useStyles'; import { ButtonDefinition } from './definition'; import styles from './Button.module.css'; -/** @public */ +/** + * A button component built on React Aria Components that provides accessible + * interactive elements for triggering actions. + * + * @remarks + * The Button component supports multiple variants (primary, secondary, tertiary, danger), + * sizes (small, medium), and states including loading and disabled. It automatically + * handles keyboard navigation, focus management, and ARIA attributes for accessibility. + * + * @example + * Basic usage: + * ```tsx + * + * ``` + * + * @example + * With icons and loading state: + * ```tsx + * + * ``` + * + * @public + */ export const Button = forwardRef( (props: ButtonProps, ref: Ref) => { const { classNames, dataAttributes, cleanedProps } = useStyles( diff --git a/packages/ui/src/components/Card/Card.stories.tsx b/packages/ui/src/components/Card/Card.stories.tsx index f2a9c9bf07..438dd0e353 100644 --- a/packages/ui/src/components/Card/Card.stories.tsx +++ b/packages/ui/src/components/Card/Card.stories.tsx @@ -33,14 +33,13 @@ export const Default = meta.story({ ), }); -export const CustomSize = meta.story({ +export const CustomSize = Default.extend({ args: { style: { width: '300px', height: '200px', }, }, - render: Default.input.render, }); export const WithLongBody = meta.story({ diff --git a/packages/ui/src/components/Dialog/Dialog.stories.tsx b/packages/ui/src/components/Dialog/Dialog.stories.tsx index 7c7edc1801..a91fecdbd6 100644 --- a/packages/ui/src/components/Dialog/Dialog.stories.tsx +++ b/packages/ui/src/components/Dialog/Dialog.stories.tsx @@ -143,30 +143,26 @@ export const FixedWidth = meta.story({ ), }); -export const FixedHeight = meta.story({ +export const FixedHeight = FixedWidth.extend({ args: { defaultOpen: true, + width: undefined, height: 500, }, - render: FixedWidth.input.render, }); -export const FixedWidthAndHeight = meta.story({ +export const FixedWidthAndHeight = FixedWidth.extend({ args: { - defaultOpen: true, - width: 600, height: 400, }, - render: FixedWidth.input.render, }); -export const FullWidthAndHeight = meta.story({ +export const FullWidthAndHeight = FixedWidth.extend({ args: { defaultOpen: true, width: '100%', height: '100%', }, - render: FixedWidth.input.render, }); export const Confirmation = meta.story({ @@ -233,18 +229,16 @@ export const WithForm = meta.story({ ), }); -export const PreviewFixedWidthAndHeight = meta.story({ +export const PreviewFixedWidthAndHeight = FixedWidth.extend({ args: { defaultOpen: undefined, width: 600, height: 400, }, - render: FixedWidth.input.render, }); -export const PreviewWithForm = meta.story({ +export const PreviewWithForm = WithForm.extend({ args: { defaultOpen: undefined, }, - render: WithForm.input.render, }); diff --git a/packages/ui/src/components/Grid/Grid.stories.tsx b/packages/ui/src/components/Grid/Grid.stories.tsx index 56251b6085..1a71310d29 100644 --- a/packages/ui/src/components/Grid/Grid.stories.tsx +++ b/packages/ui/src/components/Grid/Grid.stories.tsx @@ -22,6 +22,8 @@ import { Flex } from '../Flex'; const meta = preview.meta({ title: 'Backstage UI/Grid', component: Grid.Root, + // We will add this story in the manifest when the component is not composed. + tags: ['!manifest'], }); const FakeBox = () => ( diff --git a/packages/ui/src/components/Header/Header.stories.tsx b/packages/ui/src/components/Header/Header.stories.tsx index 7d1aa3a00a..ba2c5b9abf 100644 --- a/packages/ui/src/components/Header/Header.stories.tsx +++ b/packages/ui/src/components/Header/Header.stories.tsx @@ -240,13 +240,10 @@ export const WithCustomActions = meta.story({ ), }); -export const WithAllOptionsAndTabs = meta.story({ +export const WithAllOptionsAndTabs = WithCustomActions.extend({ args: { - ...WithCustomActions.input.args, tabs, }, - decorators: [withRouter], - render: WithCustomActions.input.render, }); export const WithHeaderPage = meta.story({ diff --git a/packages/ui/src/components/PasswordField/PasswordField.stories.tsx b/packages/ui/src/components/PasswordField/PasswordField.stories.tsx index 5f6a74d992..8025ce7217 100644 --- a/packages/ui/src/components/PasswordField/PasswordField.stories.tsx +++ b/packages/ui/src/components/PasswordField/PasswordField.stories.tsx @@ -99,12 +99,10 @@ export const WithIcon = meta.story({ ), }); -export const DisabledWithIcon = meta.story({ +export const DisabledWithIcon = WithIcon.extend({ args: { - ...WithIcon.input.args, isDisabled: true, }, - render: WithIcon.input.render, }); export const ShowError = meta.story({ diff --git a/packages/ui/src/components/TagGroup/TagGroup.stories.tsx b/packages/ui/src/components/TagGroup/TagGroup.stories.tsx index d33eed8bf1..7d4ee4173e 100644 --- a/packages/ui/src/components/TagGroup/TagGroup.stories.tsx +++ b/packages/ui/src/components/TagGroup/TagGroup.stories.tsx @@ -37,7 +37,7 @@ export interface ListItem { const meta = preview.meta({ title: 'Backstage UI/TagGroup', - component: TagGroup, + component: TagGroup, argTypes: { selectionMode: { control: { type: 'inline-radio' }, @@ -117,11 +117,11 @@ export const SelectionModeSingle = meta.story({ const [selected, setSelected] = useState(new Set(['travel'])); return ( - + {...args} items={initialList} selectedKeys={selected} onSelectionChange={setSelected} - {...args} > {item => {item.name}} @@ -140,11 +140,11 @@ export const SelectionModeMultiple = meta.story({ ); return ( - + {...args} items={initialList} selectedKeys={selected} onSelectionChange={setSelected} - {...args} > {item => {item.name}} @@ -198,17 +198,17 @@ export const RemovingTags = meta.story({ render: args => { const [selected, setSelected] = useState(new Set(['travel'])); - const list = useListData({ + const list = useListData({ initialItems: initialList, }); return ( - + {...args} items={list.items} onRemove={keys => list.remove(...keys)} selectedKeys={selected} onSelectionChange={setSelected} - {...args} > {item => {item.name}} @@ -223,17 +223,17 @@ export const WithIconAndRemoveButton = meta.story({ render: args => { const [selected, setSelected] = useState(new Set(['travel'])); - const list = useListData({ + const list = useListData({ initialItems: initialList, }); return ( - + {...args} items={list.items} onRemove={keys => list.remove(...keys)} selectedKeys={selected} onSelectionChange={setSelected} - {...args} > {item => ( {item.name} diff --git a/packages/ui/src/components/TextField/TextField.stories.tsx b/packages/ui/src/components/TextField/TextField.stories.tsx index 54457f351b..03c5f35b11 100644 --- a/packages/ui/src/components/TextField/TextField.stories.tsx +++ b/packages/ui/src/components/TextField/TextField.stories.tsx @@ -104,12 +104,10 @@ export const WithIcon = meta.story({ ), }); -export const DisabledWithIcon = meta.story({ +export const DisabledWithIcon = WithIcon.extend({ args: { - ...WithIcon.input.args, isDisabled: true, }, - render: WithIcon.input.render, }); export const ShowError = meta.story({ From 08c46a264f529c7be410c8bcbff393bf218b2e15 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 18 Dec 2025 15:54:56 +0000 Subject: [PATCH 045/202] Update report.api.md Signed-off-by: Charles de Dreuille --- packages/ui/report.api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 0a7e289d0a..1aa3f8df2e 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -229,7 +229,7 @@ export interface BoxProps extends SpaceProps { // @public (undocumented) export type Breakpoint = 'initial' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; -// @public (undocumented) +// @public export const Button: ForwardRefExoticComponent< ButtonProps & RefAttributes >; From 1c1d4b6f05eb1ba23807fdb283d2ae0b2e4ad927 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 18 Dec 2025 16:04:51 +0000 Subject: [PATCH 046/202] Update Dialog.stories.tsx Signed-off-by: Charles de Dreuille --- packages/ui/src/components/Dialog/Dialog.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/Dialog/Dialog.stories.tsx b/packages/ui/src/components/Dialog/Dialog.stories.tsx index a91fecdbd6..0392959a59 100644 --- a/packages/ui/src/components/Dialog/Dialog.stories.tsx +++ b/packages/ui/src/components/Dialog/Dialog.stories.tsx @@ -239,6 +239,6 @@ export const PreviewFixedWidthAndHeight = FixedWidth.extend({ export const PreviewWithForm = WithForm.extend({ args: { - defaultOpen: undefined, + isOpen: undefined, }, }); From 243e5e71394cbb0edbd532a448c5accc0414215e Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sat, 6 Dec 2025 20:23:21 +0000 Subject: [PATCH 047/202] feat(ui)!: redesign Table component with new useTable hook API Redesigns the Table component to provide a better developer experience with a new useTable hook supporting three pagination modes: complete (all data loaded upfront), offset (server-side), and cursor (server-side). BREAKING CHANGES: - Table component (React Aria wrapper) is renamed to TableRoot - New high-level Table component handles data display, pagination, sorting, and selection - useTable hook completely redesigned with new API New features: - Unified useTable hook with mode discriminator for all pagination patterns (complete, offset, cursor) - Custom page caching for server-side pagination with bidirectional navigation and request cancellation - Debounced query changes to reduce backend load - Stale data preservation with visual indicator during reloads - Row selection with toggle/replace behaviors - Per-row disable control via getIsDisabled MIGRATION GUIDE: 1. Update imports and use the new useTable hook: ```diff -import { Table, useTable } from '@backstage/ui'; -const { data, paginationProps } = useTable({ data: items, pagination: {...} }); +import { Table, useTable, type ColumnConfig } from '@backstage/ui'; +const { tableProps } = useTable({ + mode: 'complete', + getData: () => items, +}); ``` 2. Define columns and render with the new Table API: ```diff - - ... - ... -
- +const columns: ColumnConfig[] = [ + { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, + { id: 'type', label: 'Type', cell: item => }, +]; + + ``` Signed-off-by: Johan Persson --- .changeset/every-lions-pay.md | 44 + .../config/vocabularies/Backstage/accept.txt | 1 + docs-ui/src/snippets/stories-snippets.tsx | 2 +- packages/ui/report.api.md | 359 ++++++- .../ui/src/components/Table/Table.module.css | 5 + .../ui/src/components/Table/Table.stories.tsx | 910 ----------------- .../src/components/Table/components/Table.tsx | 207 +++- .../components/Table/components/TableRoot.tsx | 40 + .../ui/src/components/Table/definition.ts | 3 + .../ui/src/components/Table/hooks/types.ts | 177 +++- .../Table/hooks/useCompletePagination.ts | 150 +++ .../Table/hooks/useCursorPagination.ts | 92 ++ .../Table/hooks/useDebouncedReload.ts | 42 + .../Table/hooks/useOffsetPagination.ts | 105 ++ .../components/Table/hooks/usePageCache.ts | 280 +++++ .../components/Table/hooks/useQueryState.ts | 68 ++ .../Table/hooks/useStableCallback.ts | 31 + .../ui/src/components/Table/hooks/useTable.ts | 221 ++-- packages/ui/src/components/Table/index.ts | 28 +- .../Table/stories/Table.dev.stories.tsx | 965 ++++++++++++++++++ .../Table/stories/Table.docs.stories.tsx | 198 ++++ .../Table/stories/Table.visual.stories.tsx | 340 ++++++ .../Table/{ => stories}/mocked-data1.ts | 101 ++ .../Table/{ => stories}/mocked-data2.ts | 0 .../Table/{ => stories}/mocked-data3.ts | 0 .../Table/{ => stories}/mocked-data4.ts | 11 + .../ui/src/components/Table/stories/utils.tsx | 54 + packages/ui/src/components/Table/types.ts | 89 +- .../TablePagination.stories.tsx | 103 +- .../TablePagination/TablePagination.tsx | 103 +- .../src/components/TablePagination/types.ts | 22 +- 31 files changed, 3497 insertions(+), 1254 deletions(-) create mode 100644 .changeset/every-lions-pay.md delete mode 100644 packages/ui/src/components/Table/Table.stories.tsx create mode 100644 packages/ui/src/components/Table/components/TableRoot.tsx create mode 100644 packages/ui/src/components/Table/hooks/useCompletePagination.ts create mode 100644 packages/ui/src/components/Table/hooks/useCursorPagination.ts create mode 100644 packages/ui/src/components/Table/hooks/useDebouncedReload.ts create mode 100644 packages/ui/src/components/Table/hooks/useOffsetPagination.ts create mode 100644 packages/ui/src/components/Table/hooks/usePageCache.ts create mode 100644 packages/ui/src/components/Table/hooks/useQueryState.ts create mode 100644 packages/ui/src/components/Table/hooks/useStableCallback.ts create mode 100644 packages/ui/src/components/Table/stories/Table.dev.stories.tsx create mode 100644 packages/ui/src/components/Table/stories/Table.docs.stories.tsx create mode 100644 packages/ui/src/components/Table/stories/Table.visual.stories.tsx rename packages/ui/src/components/Table/{ => stories}/mocked-data1.ts (93%) rename packages/ui/src/components/Table/{ => stories}/mocked-data2.ts (100%) rename packages/ui/src/components/Table/{ => stories}/mocked-data3.ts (100%) rename packages/ui/src/components/Table/{ => stories}/mocked-data4.ts (99%) create mode 100644 packages/ui/src/components/Table/stories/utils.tsx diff --git a/.changeset/every-lions-pay.md b/.changeset/every-lions-pay.md new file mode 100644 index 0000000000..9c5a32f4e4 --- /dev/null +++ b/.changeset/every-lions-pay.md @@ -0,0 +1,44 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: Redesigned Table component with new `useTable` hook API. + +- The `Table` component (React Aria wrapper) is renamed to `TableRoot` +- New high-level `Table` component that handles data display, pagination, sorting, and selection +- The `useTable` hook is completely redesigned with a new API supporting three pagination modes (complete, offset, cursor) +- New types: `ColumnConfig`, `TableProps`, `TableItem`, `UseTableOptions`, `UseTableResult` + +New features include unified pagination modes, debounced query changes, stale data preservation during reloads, and row selection with toggle/replace behaviors. + +**Migration guide:** + +1. Update imports and use the new `useTable` hook: + +```diff +-import { Table, useTable } from '@backstage/ui'; +-const { data, paginationProps } = useTable({ data: items, pagination: {...} }); ++import { Table, useTable, type ColumnConfig } from '@backstage/ui'; ++const { tableProps } = useTable({ ++ mode: 'complete', ++ getData: () => items, ++}); +``` + +2. Define columns and render with the new Table API: + +```diff +-
+- ... +- ... +-
+- ++const columns: ColumnConfig[] = [ ++ { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, ++ { id: 'type', label: 'Type', cell: item => }, ++]; ++ ++ +``` + +Affected components: Table, TableRoot, TablePagination diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 44ee42a25a..d8c25565ee 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -99,6 +99,7 @@ dataflow dataloader dayjs debounce +debounced debounces debuggability declaratively diff --git a/docs-ui/src/snippets/stories-snippets.tsx b/docs-ui/src/snippets/stories-snippets.tsx index 0cb6119ce6..0507c85c9f 100644 --- a/docs-ui/src/snippets/stories-snippets.tsx +++ b/docs-ui/src/snippets/stories-snippets.tsx @@ -26,7 +26,7 @@ import * as SkeletonStories from '../../../packages/ui/src/components/Skeleton/S import * as CardStories from '../../../packages/ui/src/components/Card/Card.stories'; import * as HeaderStories from '../../../packages/ui/src/components/Header/Header.stories'; import * as HeaderPageStories from '../../../packages/ui/src/components/HeaderPage/HeaderPage.stories'; -import * as TableStories from '../../../packages/ui/src/components/Table/Table.stories'; +import * as TableStories from '../../../packages/ui/src/components/Table/stories/Table.docs.stories'; import * as TagGroupStories from '../../../packages/ui/src/components/TagGroup/TagGroup.stories'; import * as PasswordFieldStories from '../../../packages/ui/src/components/PasswordField/PasswordField.stories'; import * as VisuallyHiddenStories from '../../../packages/ui/src/components/VisuallyHidden/VisuallyHidden.stories'; diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 0a7e289d0a..b7b4d4dc05 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -37,11 +37,12 @@ import { RowProps } from 'react-aria-components'; import type { SearchFieldProps as SearchFieldProps_2 } from 'react-aria-components'; import type { SelectProps as SelectProps_2 } from 'react-aria-components'; import type { SeparatorProps } from 'react-aria-components'; +import type { SortDescriptor as SortDescriptor_2 } from 'react-stately'; import type { SubmenuTriggerProps as SubmenuTriggerProps_2 } from 'react-aria-components'; import type { SwitchProps as SwitchProps_2 } from 'react-aria-components'; import { TableBodyProps } from 'react-aria-components'; import { TableHeaderProps } from 'react-aria-components'; -import { TableProps } from 'react-aria-components'; +import { TableProps as TableProps_2 } from 'react-aria-components'; import type { TabListProps as TabListProps_2 } from 'react-aria-components'; import type { TabPanelProps as TabPanelProps_2 } from 'react-aria-components'; import type { TabProps as TabProps_2 } from 'react-aria-components'; @@ -456,6 +457,26 @@ export type ClassNamesMap = Record; // @public (undocumented) export const Column: (props: ColumnProps) => JSX_2.Element; +// @public (undocumented) +export interface ColumnConfig { + // (undocumented) + cell: (item: T) => ReactNode; + // (undocumented) + header?: () => ReactNode; + // (undocumented) + id: string; + // (undocumented) + isHidden?: boolean; + // (undocumented) + isRowHeader?: boolean; + // (undocumented) + isSortable?: boolean; + // (undocumented) + label: string; + // (undocumented) + width?: number | string; +} + // @public (undocumented) export interface ColumnProps extends Omit { // (undocumented) @@ -523,6 +544,34 @@ export interface ContainerProps { style?: React.CSSProperties; } +// @public (undocumented) +export interface CursorParams { + // (undocumented) + cursor: string | undefined; + // (undocumented) + filter: TFilter | undefined; + // (undocumented) + pageSize: number; + // (undocumented) + search: string; + // (undocumented) + signal: AbortSignal; + // (undocumented) + sort: SortDescriptor | null; +} + +// @public (undocumented) +export interface CursorResponse { + // (undocumented) + data: T[]; + // (undocumented) + nextCursor?: string; + // (undocumented) + prevCursor?: string; + // (undocumented) + totalCount?: number; +} + // @public export type DataAttributesMap = Record; @@ -627,6 +676,14 @@ export interface FieldLabelProps secondaryLabel?: string | null; } +// @public (undocumented) +export interface FilterState { + // (undocumented) + onFilterChange: (filter: TFilter) => void; + // (undocumented) + value: TFilter | undefined; +} + // @public (undocumented) export const Flex: ForwardRefExoticComponent< FlexProps & RefAttributes @@ -1038,6 +1095,36 @@ export const MenuTrigger: (props: MenuTriggerProps) => JSX_2.Element; // @public (undocumented) export interface MenuTriggerProps extends MenuTriggerProps_2 {} +// @public (undocumented) +export interface NoPagination { + // (undocumented) + type: 'none'; +} + +// @public (undocumented) +export interface OffsetParams { + // (undocumented) + filter: TFilter | undefined; + // (undocumented) + offset: number; + // (undocumented) + pageSize: number; + // (undocumented) + search: string; + // (undocumented) + signal: AbortSignal; + // (undocumented) + sort: SortDescriptor | null; +} + +// @public (undocumented) +export interface OffsetResponse { + // (undocumented) + data: T[]; + // (undocumented) + totalCount: number; +} + // @public (undocumented) type Option_2 = { value: string; @@ -1046,6 +1133,46 @@ type Option_2 = { }; export { Option_2 as Option }; +// @public (undocumented) +export interface PagePagination extends TablePaginationProps { + // (undocumented) + type: 'page'; +} + +// @public (undocumented) +export interface PaginationOptions { + // (undocumented) + getLabel?: TablePaginationProps['getLabel']; + // (undocumented) + initialOffset?: number; + // (undocumented) + pageSize?: number; + // (undocumented) + showPageSizeOptions?: boolean; +} + +// @public (undocumented) +export interface QueryOptions { + // (undocumented) + filter?: TFilter; + // (undocumented) + initialFilter?: TFilter; + // (undocumented) + initialSearch?: string; + // (undocumented) + initialSort?: SortDescriptor; + // (undocumented) + onFilterChange?: (filter: TFilter) => void; + // (undocumented) + onSearchChange?: (search: string) => void; + // (undocumented) + onSortChange?: (sort: SortDescriptor) => void; + // (undocumented) + search?: string; + // (undocumented) + sort?: SortDescriptor | null; +} + // @public (undocumented) export const Radio: ForwardRefExoticComponent< RadioProps & RefAttributes @@ -1082,6 +1209,22 @@ export type Responsive = T | Partial>; // @public (undocumented) export function Row(props: RowProps): JSX_2.Element; +// @public (undocumented) +export interface RowConfig { + // (undocumented) + getHref?: (item: T) => string | undefined; + // (undocumented) + getIsDisabled?: (item: T) => boolean; + // (undocumented) + onClick?: (item: T) => void; +} + +// @public (undocumented) +export type RowRenderFn = (params: { + item: T; + index: number; +}) => ReactNode; + // @public (undocumented) export const SearchField: ForwardRefExoticComponent< SearchFieldProps & RefAttributes @@ -1112,6 +1255,14 @@ export interface SearchFieldProps startCollapsed?: boolean; } +// @public (undocumented) +export interface SearchState { + // (undocumented) + onSearchChange: (value: string) => void; + // (undocumented) + value: string; +} + // @public (undocumented) export const Select: ForwardRefExoticComponent< SelectProps<'multiple' | 'single'> & RefAttributes @@ -1171,6 +1322,17 @@ export interface SkeletonProps extends ComponentProps<'div'> { width?: number | string; } +// @public (undocumented) +export type SortDescriptor = SortDescriptor_2; + +// @public (undocumented) +export interface SortState { + // (undocumented) + descriptor: SortDescriptor | null; + // (undocumented) + onSortChange: (descriptor: SortDescriptor) => void; +} + // @public (undocumented) export type Space = | '0.5' @@ -1251,7 +1413,18 @@ export interface SwitchProps extends SwitchProps_2 { export const Tab: (props: TabProps) => JSX_2.Element; // @public (undocumented) -export const Table: (props: TableProps) => JSX_2.Element; +export function Table({ + columnConfig, + data, + loading, + isStale, + error, + pagination, + sort, + rowConfig, + selection, + emptyState, +}: TableProps): JSX_2.Element; // @public (undocumented) export const TableBody: ( @@ -1281,6 +1454,9 @@ export const TableDefinition: { readonly headSelection: 'bui-TableHeadSelection'; readonly cellSelection: 'bui-TableCellSelection'; }; + readonly dataAttributes: { + readonly stale: readonly [true, false]; + }; }; // @public (undocumented) @@ -1288,8 +1464,25 @@ export const TableHeader: ( props: TableHeaderProps, ) => JSX_2.Element; +// @public (undocumented) +export interface TableItem { + // (undocumented) + id: string | number; +} + // @public -export function TablePagination(props: TablePaginationProps): JSX_2.Element; +export function TablePagination({ + pageSize, + offset, + totalCount, + hasNextPage, + hasPreviousPage, + onNextPage, + onPreviousPage, + onPageSizeChange, + showPageSizeOptions, + getLabel, +}: TablePaginationProps): JSX_2.Element; // @public export const TablePaginationDefinition: { @@ -1302,26 +1495,79 @@ export const TablePaginationDefinition: { }; // @public (undocumented) -export interface TablePaginationProps - extends React.HTMLAttributes { +export interface TablePaginationProps { + // (undocumented) + getLabel?: (params: { + pageSize: number; + offset?: number; + totalCount?: number; + }) => string; + // (undocumented) + hasNextPage: boolean; + // (undocumented) + hasPreviousPage: boolean; // (undocumented) offset?: number; // (undocumented) - onNextPage?: () => void; + onNextPage: () => void; // (undocumented) - onPageSizeChange?: (pageSize: number) => void; + onPageSizeChange?: (size: number) => void; // (undocumented) - onPreviousPage?: () => void; + onPreviousPage: () => void; // (undocumented) - pageSize?: number; - // (undocumented) - rowCount?: number; - // (undocumented) - setOffset?: (offset: number) => void; - // (undocumented) - setPageSize?: (pageSize: number) => void; + pageSize: number; // (undocumented) showPageSizeOptions?: boolean; + // (undocumented) + totalCount?: number; +} + +// @public (undocumented) +export type TablePaginationType = NoPagination | PagePagination; + +// @public (undocumented) +export interface TableProps { + // (undocumented) + columnConfig: readonly ColumnConfig[]; + // (undocumented) + data: T[] | undefined; + // (undocumented) + emptyState?: ReactNode; + // (undocumented) + error?: Error; + // (undocumented) + isStale?: boolean; + // (undocumented) + loading?: boolean; + // (undocumented) + pagination: TablePaginationType; + // (undocumented) + rowConfig?: RowConfig | RowRenderFn; + // (undocumented) + selection?: TableSelection; + // (undocumented) + sort?: SortState; +} + +// @public (undocumented) +export const TableRoot: (props: TableRootProps) => JSX_2.Element; + +// @public (undocumented) +export interface TableRootProps extends TableProps_2 { + // (undocumented) + stale?: boolean; +} + +// @public (undocumented) +export interface TableSelection { + // (undocumented) + behavior?: TableProps_2['selectionBehavior']; + // (undocumented) + mode?: TableProps_2['selectionMode']; + // (undocumented) + onSelectionChange?: TableProps_2['onSelectionChange']; + // (undocumented) + selected?: TableProps_2['selectedKeys']; } // @public @@ -1535,48 +1781,69 @@ export const useBreakpoint: () => { down: (key: Breakpoint) => boolean; }; -// @public -export function useTable( - config?: UseTableConfig, -): UseTableResult; +// @public (undocumented) +export function useTable( + options: UseTableOptions, +): UseTableResult; // @public (undocumented) -export interface UseTableConfig { - data?: T[]; - pagination?: UseTablePaginationConfig; +export interface UseTableCompleteOptions + extends QueryOptions { + // (undocumented) + filterFn?: (data: T[], filter: TFilter) => T[]; + // (undocumented) + getData: () => T[] | Promise; + // (undocumented) + mode: 'complete'; + // (undocumented) + paginationOptions?: PaginationOptions; + // (undocumented) + searchFn?: (data: T[], search: string) => T[]; + // (undocumented) + sortFn?: (data: T[], sort: SortDescriptor) => T[]; } // @public (undocumented) -export interface UseTablePagination { - data?: T[]; - nextPage: () => void; - offset: number; - pageSize: number; - paginationProps: TablePaginationProps; - previousPage: () => void; - setOffset: (offset: number) => void; - setPageSize: (pageSize: number) => void; +export interface UseTableCursorOptions + extends QueryOptions { + // (undocumented) + getData: (params: CursorParams) => Promise>; + // (undocumented) + mode: 'cursor'; + // (undocumented) + paginationOptions?: Omit; } // @public (undocumented) -export interface UseTablePaginationConfig { - defaultOffset?: number; - defaultPageSize?: number; - offset?: number; - onNextPage?: () => void; - onOffsetChange?: (offset: number) => void; - onPageSizeChange?: (pageSize: number) => void; - onPreviousPage?: () => void; - pageSize?: number; - rowCount?: number; - showPageSizeOptions?: boolean; +export interface UseTableOffsetOptions + extends QueryOptions { + // (undocumented) + getData: (params: OffsetParams) => Promise>; + // (undocumented) + mode: 'offset'; + // (undocumented) + paginationOptions?: PaginationOptions; } // @public (undocumented) -export interface UseTableResult { - data?: T[]; - pagination: UseTablePagination; - paginationProps: TablePaginationProps; +export type UseTableOptions = + | UseTableCompleteOptions + | UseTableOffsetOptions + | UseTableCursorOptions; + +// @public (undocumented) +export interface UseTableResult { + // (undocumented) + filter: FilterState; + // (undocumented) + reload: () => void; + // (undocumented) + search: SearchState; + // (undocumented) + tableProps: Omit< + TableProps, + 'columnConfig' | 'rowConfig' | 'selection' | 'emptyState' + >; } // @public (undocumented) diff --git a/packages/ui/src/components/Table/Table.module.css b/packages/ui/src/components/Table/Table.module.css index 001d5b21d1..ea148ae893 100644 --- a/packages/ui/src/components/Table/Table.module.css +++ b/packages/ui/src/components/Table/Table.module.css @@ -22,6 +22,11 @@ caption-side: bottom; border-collapse: collapse; table-layout: fixed; + transition: opacity 0.2s ease-in-out; + + &[data-stale='true'] { + opacity: 0.6; + } } .bui-TableHeader { diff --git a/packages/ui/src/components/Table/Table.stories.tsx b/packages/ui/src/components/Table/Table.stories.tsx deleted file mode 100644 index f7687af1c3..0000000000 --- a/packages/ui/src/components/Table/Table.stories.tsx +++ /dev/null @@ -1,910 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import preview from '../../../../../.storybook/preview'; -import { useState } from 'react'; -import type { StoryFn } from '@storybook/react-vite'; -import { type Selection } from 'react-aria-components'; -import { - Table, - TableHeader, - Column, - TableBody, - Row, - Cell, - CellText, - CellProfile, - useTable, -} from '.'; -import { RadioGroup, Radio } from '../RadioGroup'; -import { Flex } from '../Flex'; -import { MemoryRouter } from 'react-router-dom'; -import { data as data1Raw } from './mocked-data1'; -import { data as data2 } from './mocked-data2'; -import { data as data3 } from './mocked-data3'; -import { data as data4 } from './mocked-data4'; -import { RiCactusLine } from '@remixicon/react'; -import { TablePagination } from '../TablePagination'; -import { Text } from '../Text'; - -const meta = preview.meta({ - title: 'Backstage UI/Table', - decorators: [ - (Story: StoryFn) => ( - - - - ), - ], -}); - -// Added this fix to fix Chromatic timeout error. This bug is due to rerendering the table with too many rows. -// Work in progress to fix it here - https://github.com/backstage/backstage/pull/30687 -const data1 = data1Raw.slice(0, 10); - -export const TableOnly = meta.story({ - render: () => { - return ( -
- - Name - Owner - Type - Lifecycle - - - {data1.map(item => ( - - } - description={item.description} - /> - - - - - ))} - -
- ); - }, -}); - -export const WithPaginationUncontrolled = meta.story({ - render: () => { - const { data, paginationProps } = useTable({ data: data1 }); - - return ( - <> - - - Name - Owner - Type - Lifecycle - - - {data?.map(item => ( - - } - description={item.description} - /> - - - - - ))} - -
- - - ); - }, -}); - -export const WithPaginationControlled = meta.story({ - render: () => { - const [offset, setOffset] = useState(0); - const [pageSize, setPageSize] = useState(5); - - const { data, paginationProps } = useTable({ - data: data4, - pagination: { - offset, - pageSize, - onOffsetChange: setOffset, - onPageSizeChange: setPageSize, - onNextPage: () => console.log('Next page analytics'), - onPreviousPage: () => console.log('Previous page analytics'), - }, - }); - - return ( - <> - - - Band name - Genre - Year formed - Albums - - - {data?.map(item => ( - - - - - - - ))} - -
- -
- Current state: offset={offset}, pageSize={pageSize} -
- - ); - }, -}); - -export const Sorting = meta.story({ - render: () => { - return ( - - - - Name - - Owner - Type - Lifecycle - - - {data1.map(item => ( - - } - description={item.description} - /> - - - - - ))} - -
- ); - }, -}); - -export const TableRockBand = meta.story({ - render: () => { - const { data, paginationProps } = useTable({ - data: data4, - pagination: { - defaultPageSize: 5, - }, - }); - - return ( - <> - - - Band name - Genre - Year formed - Albums - - - {data?.map(item => ( - - - - - - - ))} - -
- - - ); - }, -}); - -export const RowClick = meta.story({ - render: () => { - const { data, paginationProps } = useTable({ - data: data4, - pagination: { - defaultPageSize: 5, - }, - }); - - return ( - <> - - - Band name - Genre - Year formed - Albums - - - {data?.map(item => ( - alert('Row clicked')}> - - - - - - ))} - -
- - - ); - }, -}); - -export const RowLink = meta.story({ - render: () => { - const { data, paginationProps } = useTable({ - data: data4, - pagination: { - defaultPageSize: 5, - }, - }); - - return ( - <> - - - Band name - Genre - Year formed - Albums - - - {data?.map(item => ( - - - - - - - ))} - -
- - - ); - }, -}); - -export const CellComponent = meta.story({ - name: 'Cell', - render: () => { - return ( - - - Name - - - - Hello world - - - - This is a very long text that demonstrates how the Cell component - handles lengthy content. It should wrap appropriately and maintain - proper styling even when the text extends beyond the normal cell - width. This helps ensure that the table remains readable and - visually consistent regardless of the content length. - - - - Hello world - - -
- ); - }, -}); - -export const CellTextComponent = meta.story({ - name: 'CellText', - render: () => { - return ( - - - Name - - - {data2.map(item => ( - - - - ))} - -
- ); - }, -}); - -export const CellProfileComponent = meta.story({ - name: 'CellProfile', - render: () => { - return ( - - - Name - - - {data3.map(item => ( - - - - ))} - -
- ); - }, -}); - -export const SelectionSingleToggle = meta.story({ - render: () => { - const [selectedKeys, setSelectedKeys] = useState(new Set([])); - - return ( - - - Name - Owner - Type - - - - - - - - - - - - - - - - - - -
- ); - }, -}); - -export const SelectionMultiToggle = meta.story({ - render: () => { - const [selectedKeys, setSelectedKeys] = useState(new Set([])); - - return ( - - - Name - Owner - Type - - - - - - - - - - - - - - - - - - -
- ); - }, -}); - -export const SelectionSingleReplace = meta.story({ - render: () => { - const [selectedKeys, setSelectedKeys] = useState(new Set([])); - - return ( - - - Name - Owner - Type - - - - - - - - - - - - - - - - - - -
- ); - }, -}); - -export const SelectionMultiReplace = meta.story({ - render: () => { - const [selectedKeys, setSelectedKeys] = useState(new Set([])); - - return ( - - - Name - Owner - Type - - - - - - - - - - - - - - - - - - -
- ); - }, -}); - -export const SelectionToggleWithActions = meta.story({ - render: () => { - const [selectedKeys, setSelectedKeys] = useState(new Set([])); - - return ( - alert(`Opening ${key}`)} - > - - Name - Owner - Type - - - - - - - - - - - - - - - - - - -
- ); - }, -}); - -export const SelectionReplaceWithActions = meta.story({ - render: () => { - const [selectedKeys, setSelectedKeys] = useState(new Set([])); - - return ( - alert(`Opening ${key}`)} - > - - Name - Owner - Type - - - - - - - - - - - - - - - - - - -
- ); - }, -}); - -export const SelectionToggleWithLinks = meta.story({ - render: () => { - const [selectedKeys, setSelectedKeys] = useState(new Set([])); - - return ( - - - Name - Owner - Type - - - - - - - - - - - - - - - - - - -
- ); - }, -}); - -export const SelectionReplaceWithLinks = meta.story({ - render: () => { - const [selectedKeys, setSelectedKeys] = useState(new Set([])); - - return ( - - - Name - Owner - Type - - - - - - - - - - - - - - - - - - -
- ); - }, -}); - -export const SelectionWithDisabledRows = meta.story({ - render: () => { - const [selectedKeys, setSelectedKeys] = useState(new Set([])); - - return ( - - - Name - Owner - Type - - - - - - - - - - - - - - - - - - -
- ); - }, -}); - -export const SelectionWithPagination = meta.story({ - render: () => { - const [selectedKeys, setSelectedKeys] = useState(new Set([])); - - const { data, paginationProps } = useTable({ - data: data1, - pagination: { - defaultPageSize: 5, - }, - }); - - return ( - <> - - - Name - Owner - Type - - - {data?.map(item => ( - - - - - - ))} - -
- - - ); - }, -}); - -export const SelectionModePlayground = meta.story({ - render: () => { - const [selectionMode, setSelectionMode] = useState<'single' | 'multiple'>( - 'multiple', - ); - const [selectedKeys, setSelectedKeys] = useState(new Set([])); - - return ( - - - - Name - Owner - Type - - - - - - - - - - - - - - - - - - -
-
- - Selection mode: - - { - setSelectionMode(value as 'single' | 'multiple'); - setSelectedKeys(new Set([])); - }} - > - single - multiple - -
-
- ); - }, -}); - -export const SelectionBehaviorPlayground = meta.story({ - render: () => { - const [selectionBehavior, setSelectionBehavior] = useState< - 'toggle' | 'replace' - >('toggle'); - const [selectedKeys, setSelectedKeys] = useState(new Set([])); - - return ( - - - - Name - Owner - Type - - - - - - - - - - - - - - - - - - -
-
- - Selection behavior: - - { - setSelectionBehavior(value as 'toggle' | 'replace'); - setSelectedKeys(new Set([])); - }} - > - toggle - replace - -
-
- ); - }, -}); diff --git a/packages/ui/src/components/Table/components/Table.tsx b/packages/ui/src/components/Table/components/Table.tsx index ffd0ab231d..59020dc2b0 100644 --- a/packages/ui/src/components/Table/components/Table.tsx +++ b/packages/ui/src/components/Table/components/Table.tsx @@ -14,24 +14,199 @@ * limitations under the License. */ -import { useStyles } from '../../../hooks/useStyles'; -import { TableDefinition } from '../definition'; -import { - Table as ReactAriaTable, - type TableProps, -} from 'react-aria-components'; -import styles from '../Table.module.css'; -import clsx from 'clsx'; +import type { Key } from 'react-aria-components'; +import { TableRoot } from './TableRoot'; +import { TableHeader } from './TableHeader'; +import { TableBody } from './TableBody'; +import { Row } from './Row'; +import { Column } from './Column'; +import { TablePagination } from '../../TablePagination'; +import type { + TableProps, + TableItem, + RowConfig, + RowRenderFn, + TablePaginationType, +} from '../types'; +import { Fragment, useId, useMemo } from 'react'; +import { VisuallyHidden } from '../../VisuallyHidden'; +import { Flex } from '../../Flex'; + +function isRowRenderFn( + rowConfig: RowConfig | RowRenderFn | undefined, +): rowConfig is RowRenderFn { + return typeof rowConfig === 'function'; +} + +function useDisabledRows({ + data, + rowConfig, +}: Pick, 'data' | 'rowConfig'>): Set | undefined { + return useMemo(() => { + if (!data || typeof rowConfig === 'function' || !rowConfig?.getIsDisabled) { + return; + } + + return data.reduce>((set, item) => { + const isDisabled = rowConfig.getIsDisabled?.(item); + if (isDisabled) { + set.add(String(item.id)); + } + return set; + }, new Set()); + }, [data, rowConfig]); +} + +function useLiveRegionLabel( + pagination: TablePaginationType, + isStale: boolean, + hasData: boolean, +): string { + if (!hasData || pagination.type === 'none') { + return ''; + } + + const { pageSize, offset, totalCount, getLabel } = pagination; + + if (isStale) { + return 'Loading table data.'; + } + + let liveRegionLabel = 'Table page loaded. '; + + if (getLabel) { + liveRegionLabel += getLabel({ pageSize, offset, totalCount }); + } else if (offset !== undefined) { + const fromCount = offset + 1; + const toCount = Math.min(offset + pageSize, totalCount ?? 0); + liveRegionLabel += `Showing ${fromCount} to ${toCount} of ${totalCount}`; + } + return liveRegionLabel; +} /** @public */ -export const Table = (props: TableProps) => { - const { classNames, cleanedProps } = useStyles(TableDefinition, props); +export function Table({ + columnConfig, + data, + loading = false, + isStale = false, + error, + pagination, + sort, + rowConfig, + selection, + emptyState, +}: TableProps) { + const liveRegionId = useId(); + + const visibleColumns = useMemo( + () => columnConfig.filter(col => !col.isHidden), + [columnConfig], + ); + const disabledRows = useDisabledRows({ data, rowConfig }); + + const { + mode: selectionMode, + selected: selectedKeys, + behavior: selectionBehavior, + onSelectionChange, + } = selection || {}; + + if (loading && !data) { + return
Loading...
; + } + + if (error) { + return
Error: {error.message}
; + } + + const liveRegionLabel = useLiveRegionLabel( + pagination, + isStale, + data !== undefined, + ); return ( - +
+ + {liveRegionLabel} + + + + + {column => + column.header ? ( + <>{column.header()} + ) : ( + + {column.label} + + ) + } + + {emptyState} : undefined + } + > + {item => { + const itemIndex = data?.indexOf(item) ?? -1; + + if (isRowRenderFn(rowConfig)) { + return rowConfig({ + item, + index: itemIndex, + }); + } + + return ( + rowConfig?.onClick?.(item) + : undefined + } + > + {column => ( + {column.cell(item)} + )} + + ); + }} + + + {pagination.type === 'page' && ( + + )} +
); -}; +} diff --git a/packages/ui/src/components/Table/components/TableRoot.tsx b/packages/ui/src/components/Table/components/TableRoot.tsx new file mode 100644 index 0000000000..95e80dcf51 --- /dev/null +++ b/packages/ui/src/components/Table/components/TableRoot.tsx @@ -0,0 +1,40 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useStyles } from '../../../hooks/useStyles'; +import { TableDefinition } from '../definition'; +import { Table as ReactAriaTable } from 'react-aria-components'; +import styles from '../Table.module.css'; +import clsx from 'clsx'; +import { TableRootProps } from '../types'; + +/** @public */ +export const TableRoot = (props: TableRootProps) => { + const { classNames, dataAttributes, cleanedProps } = useStyles( + TableDefinition, + props, + ); + + return ( + + ); +}; diff --git a/packages/ui/src/components/Table/definition.ts b/packages/ui/src/components/Table/definition.ts index 313604902c..ca4fca999a 100644 --- a/packages/ui/src/components/Table/definition.ts +++ b/packages/ui/src/components/Table/definition.ts @@ -42,4 +42,7 @@ export const TableDefinition = { headSelection: 'bui-TableHeadSelection', cellSelection: 'bui-TableCellSelection', }, + dataAttributes: { + stale: [true, false] as const, + }, } as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Table/hooks/types.ts b/packages/ui/src/components/Table/hooks/types.ts index 8c93d678fc..f6311b7ebe 100644 --- a/packages/ui/src/components/Table/hooks/types.ts +++ b/packages/ui/src/components/Table/hooks/types.ts @@ -15,73 +15,144 @@ */ import type { TablePaginationProps } from '../../TablePagination/types'; +import type { SortDescriptor, TableItem, TableProps } from '../types'; /** @public */ -export interface UseTablePaginationConfig { - /** Total number of rows in the dataset - only needed when data is not provided at the top level */ - rowCount?: number; +export interface FilterState { + value: TFilter | undefined; + onFilterChange: (filter: TFilter) => void; +} - // Controlled pagination with offset/pageSize (Backstage style) - /** Current offset. When provided, pagination is controlled */ - offset?: number; - /** Current page size. When provided, pagination is controlled */ +/** @public */ +export interface SearchState { + value: string; + onSearchChange: (value: string) => void; +} + +/** @public */ +export interface QueryOptions { + initialSort?: SortDescriptor; + sort?: SortDescriptor | null; + onSortChange?: (sort: SortDescriptor) => void; + + initialFilter?: TFilter; + filter?: TFilter; + onFilterChange?: (filter: TFilter) => void; + + initialSearch?: string; + search?: string; + onSearchChange?: (search: string) => void; +} + +/** @public */ +export interface PaginationOptions { pageSize?: number; - /** Callback when offset changes */ - onOffsetChange?: (offset: number) => void; - /** Callback when page size changes */ - onPageSizeChange?: (pageSize: number) => void; - - // Uncontrolled pagination defaults - /** Default page size for uncontrolled mode */ - defaultPageSize?: number; - /** Default offset for uncontrolled mode */ - defaultOffset?: number; - - // Analytics callbacks - /** Callback when next page is clicked */ - onNextPage?: () => void; - /** Callback when previous page is clicked */ - onPreviousPage?: () => void; - - // UI options - /** Whether to show page size options */ + initialOffset?: number; showPageSizeOptions?: boolean; + getLabel?: TablePaginationProps['getLabel']; } /** @public */ -export interface UseTablePagination { - /** Props to pass to TablePagination component */ - paginationProps: TablePaginationProps; - /** Current offset */ +export interface OffsetParams { offset: number; - /** Current page size */ pageSize: number; - /** Sliced data for current page - only available when data is provided to useTable */ - data?: T[]; - /** Go to next page */ - nextPage: () => void; - /** Go to previous page */ - previousPage: () => void; - /** Set specific offset */ - setOffset: (offset: number) => void; - /** Set page size */ - setPageSize: (pageSize: number) => void; + sort: SortDescriptor | null; + filter: TFilter | undefined; + search: string; + signal: AbortSignal; } /** @public */ -export interface UseTableConfig { - /** Full dataset - when provided, rowCount is calculated automatically and sliced data is returned */ - data?: T[]; - /** Pagination configuration */ - pagination?: UseTablePaginationConfig; +export interface CursorParams { + cursor: string | undefined; + pageSize: number; + sort: SortDescriptor | null; + filter: TFilter | undefined; + search: string; + signal: AbortSignal; } /** @public */ -export interface UseTableResult { - /** Sliced data for current page */ - data?: T[]; - /** Props to pass to TablePagination component */ - paginationProps: TablePaginationProps; - /** Pagination utilities */ - pagination: UseTablePagination; +export interface OffsetResponse { + data: T[]; + totalCount: number; +} + +/** @public */ +export interface CursorResponse { + data: T[]; + nextCursor?: string; + prevCursor?: string; + totalCount?: number; +} + +/** @public */ +export interface UseTableCompleteOptions + extends QueryOptions { + mode: 'complete'; + getData: () => T[] | Promise; + paginationOptions?: PaginationOptions; + sortFn?: (data: T[], sort: SortDescriptor) => T[]; + filterFn?: (data: T[], filter: TFilter) => T[]; + searchFn?: (data: T[], search: string) => T[]; +} + +/** @public */ +export interface UseTableOffsetOptions + extends QueryOptions { + mode: 'offset'; + getData: (params: OffsetParams) => Promise>; + paginationOptions?: PaginationOptions; +} + +/** @public */ +export interface UseTableCursorOptions + extends QueryOptions { + mode: 'cursor'; + getData: (params: CursorParams) => Promise>; + paginationOptions?: Omit; +} + +/** @public */ +export type UseTableOptions = + | UseTableCompleteOptions + | UseTableOffsetOptions + | UseTableCursorOptions; + +/** @public */ +export interface UseTableResult { + tableProps: Omit< + TableProps, + 'columnConfig' | 'rowConfig' | 'selection' | 'emptyState' + >; + reload: () => void; + filter: FilterState; + search: SearchState; +} + +/** @internal */ +export interface PaginationResult { + data: T[] | undefined; + loading: boolean; + error: Error | undefined; + totalCount: number | undefined; + offset?: number; + pageSize: number; + hasNextPage: boolean; + hasPreviousPage: boolean; + onNextPage: () => void; + onPreviousPage: () => void; + onPageSizeChange: (size: number) => void; +} + +/** @internal */ +export interface QueryState { + sort: SortDescriptor | null; + setSort: (sort: SortDescriptor) => void; + + filter: TFilter | undefined; + setFilter: (filter: TFilter) => void; + + search: string; + setSearch: (search: string) => void; } diff --git a/packages/ui/src/components/Table/hooks/useCompletePagination.ts b/packages/ui/src/components/Table/hooks/useCompletePagination.ts new file mode 100644 index 0000000000..3f199335d5 --- /dev/null +++ b/packages/ui/src/components/Table/hooks/useCompletePagination.ts @@ -0,0 +1,150 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useState, useCallback, useMemo, useEffect, useRef } from 'react'; +import type { TableItem } from '../types'; +import type { + PaginationResult, + QueryState, + UseTableCompleteOptions, +} from './types'; +import { useStableCallback } from './useStableCallback'; + +/** @internal */ +export function useCompletePagination( + options: UseTableCompleteOptions, + query: QueryState, +): PaginationResult & { reload: () => void } { + const { + getData: getDataProp, + paginationOptions = {}, + sortFn, + filterFn, + searchFn, + } = options; + const { pageSize: defaultPageSize = 20, initialOffset = 0 } = + paginationOptions; + + const getData = useStableCallback(getDataProp); + const { sort, filter, search } = query; + + const [items, setItems] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(undefined); + const [loadCount, setLoadCount] = useState(0); + + const [offset, setOffset] = useState(initialOffset); + const [pageSize, setPageSize] = useState(defaultPageSize); + + // Load data on mount and when loadCount changes (reload trigger) + useEffect(() => { + let cancelled = false; + setIsLoading(true); + setError(undefined); + + (async () => { + try { + const result = getData(); + const data = result instanceof Promise ? await result : result; + if (!cancelled) { + setItems(data); + setIsLoading(false); + } + } catch (err) { + if (!cancelled) { + setError(err instanceof Error ? err : new Error(String(err))); + setIsLoading(false); + } + } + })(); + + return () => { + cancelled = true; + }; + }, [getData, loadCount]); + + // Reset offset when query changes (query object is memoized) + const prevQueryRef = useRef(query); + useEffect(() => { + if (prevQueryRef.current !== query) { + prevQueryRef.current = query; + setOffset(0); + } + }, [query]); + + // Process data client-side (filter, search, sort) + const processedData = useMemo(() => { + let result = [...items]; + if (filter !== undefined && filterFn) { + result = filterFn(result, filter); + } + if (search && searchFn) { + result = searchFn(result, search); + } + if (sort && sortFn) { + result = sortFn(result, sort); + } + return result; + }, [items, sort, filter, search, filterFn, searchFn, sortFn]); + + const totalCount = processedData.length; + + // Paginate the processed data + const paginatedData = useMemo( + () => processedData.slice(offset, offset + pageSize), + [processedData, offset, pageSize], + ); + + const hasNextPage = offset + pageSize < totalCount; + const hasPreviousPage = offset > 0; + + const onNextPage = useCallback(() => { + if (offset + pageSize < totalCount) { + setOffset(offset + pageSize); + } + }, [offset, pageSize, totalCount]); + + const onPreviousPage = useCallback(() => { + if (offset > 0) { + setOffset(Math.max(0, offset - pageSize)); + } + }, [offset, pageSize]); + + const onPageSizeChange = useCallback((newSize: number) => { + setPageSize(newSize); + setOffset(0); + }, []); + + const reload = useCallback(() => { + setOffset(0); + setLoadCount(c => c + 1); + }, []); + + return { + data: paginatedData, + loading: isLoading, + error, + totalCount, + offset, + pageSize, + hasNextPage, + hasPreviousPage, + onNextPage, + onPreviousPage, + onPageSizeChange, + reload, + }; +} diff --git a/packages/ui/src/components/Table/hooks/useCursorPagination.ts b/packages/ui/src/components/Table/hooks/useCursorPagination.ts new file mode 100644 index 0000000000..007f39023f --- /dev/null +++ b/packages/ui/src/components/Table/hooks/useCursorPagination.ts @@ -0,0 +1,92 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useState, useCallback } from 'react'; +import type { TableItem } from '../types'; +import type { + UseTableCursorOptions, + CursorParams, + QueryState, + PaginationResult, +} from './types'; +import { usePageCache } from './usePageCache'; +import { useStableCallback } from './useStableCallback'; +import { useDebouncedReload } from './useDebouncedReload'; + +export function useCursorPagination( + options: UseTableCursorOptions, + query: QueryState, +): PaginationResult & { reload: () => void } { + const { getData: getDataProp, paginationOptions = {} } = options; + const { pageSize: defaultPageSize = 20 } = paginationOptions; + + const getData = useStableCallback(getDataProp); + const { sort, filter, search } = query; + + const [pageSize, setPageSize] = useState(defaultPageSize); + + const wrappedGetData = useCallback( + async ({ + cursor, + signal, + }: { + cursor: string | undefined; + signal: AbortSignal; + }) => { + const params: CursorParams = { + cursor, + pageSize, + sort, + filter, + search, + signal, + }; + + const response = await getData(params); + + return { + data: response.data, + prevCursor: response.prevCursor, + nextCursor: response.nextCursor, + totalCount: response.totalCount, + }; + }, + [getData, pageSize, sort, filter, search], + ); + + const cache = usePageCache({ getData: wrappedGetData }); + + useDebouncedReload(query, pageSize, cache.reload); + + const onPageSizeChange = useCallback( + (newSize: number) => setPageSize(newSize), + [], + ); + + return { + data: cache.data, + loading: cache.loading, + error: cache.error, + totalCount: cache.totalCount, + offset: undefined, + pageSize, + hasNextPage: cache.hasNextPage, + hasPreviousPage: cache.hasPreviousPage, + onNextPage: cache.onNextPage, + onPreviousPage: cache.onPreviousPage, + onPageSizeChange, + reload: cache.reload, + }; +} diff --git a/packages/ui/src/components/Table/hooks/useDebouncedReload.ts b/packages/ui/src/components/Table/hooks/useDebouncedReload.ts new file mode 100644 index 0000000000..d90afa8861 --- /dev/null +++ b/packages/ui/src/components/Table/hooks/useDebouncedReload.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useEffect, useRef } from 'react'; +import type { QueryState } from './types'; + +/** + * Triggers a debounced reload when query or pageSize changes. + * Debouncing reduces backend load during rapid changes (e.g., typing in search). + */ +/** @internal */ +export function useDebouncedReload( + query: QueryState, + pageSize: number, + reload: () => void, + delay: number = 200, +): void { + const prevDepsRef = useRef({ query, pageSize }); + + useEffect(() => { + const prev = prevDepsRef.current; + if (prev.query !== query || prev.pageSize !== pageSize) { + prevDepsRef.current = { query, pageSize }; + const timer = setTimeout(reload, delay); + return () => clearTimeout(timer); + } + return undefined; + }, [query, pageSize, reload, delay]); +} diff --git a/packages/ui/src/components/Table/hooks/useOffsetPagination.ts b/packages/ui/src/components/Table/hooks/useOffsetPagination.ts new file mode 100644 index 0000000000..c5844ec018 --- /dev/null +++ b/packages/ui/src/components/Table/hooks/useOffsetPagination.ts @@ -0,0 +1,105 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useState, useCallback } from 'react'; +import type { TableItem } from '../types'; +import type { + UseTableOffsetOptions, + OffsetParams, + QueryState, + PaginationResult, +} from './types'; +import { usePageCache } from './usePageCache'; +import { useStableCallback } from './useStableCallback'; +import { useDebouncedReload } from './useDebouncedReload'; + +export function useOffsetPagination( + options: UseTableOffsetOptions, + query: QueryState, +): PaginationResult & { reload: () => void } { + const { getData: getDataProp, paginationOptions = {} } = options; + const { pageSize: defaultPageSize = 20, initialOffset = 0 } = + paginationOptions; + + const getData = useStableCallback(getDataProp); + const { sort, filter, search } = query; + + const [pageSize, setPageSize] = useState(defaultPageSize); + + const wrappedGetData = useCallback( + async ({ + cursor, + signal, + }: { + cursor: number | undefined; + signal: AbortSignal; + }) => { + const currentOffset = cursor ?? 0; + + const params: OffsetParams = { + offset: currentOffset, + pageSize, + sort, + filter, + search, + signal, + }; + + const response = await getData(params); + + const prevCursor = + currentOffset > 0 ? Math.max(0, currentOffset - pageSize) : undefined; + const nextCursor = + currentOffset + pageSize < response.totalCount + ? currentOffset + pageSize + : undefined; + + return { + data: response.data, + prevCursor, + nextCursor, + totalCount: response.totalCount, + }; + }, + [getData, pageSize, sort, filter, search], + ); + + const cache = usePageCache({ + getData: wrappedGetData, + initialCurrentCursor: initialOffset > 0 ? initialOffset : undefined, + }); + + useDebouncedReload(query, pageSize, cache.reload); + + const onPageSizeChange = useCallback( + (newSize: number) => setPageSize(newSize), + [], + ); + + return { + data: cache.data, + loading: cache.loading, + error: cache.error, + totalCount: cache.totalCount, + offset: cache.currentCursor ?? 0, + pageSize, + hasNextPage: cache.hasNextPage, + hasPreviousPage: cache.hasPreviousPage, + onNextPage: cache.onNextPage, + onPreviousPage: cache.onPreviousPage, + onPageSizeChange, + reload: cache.reload, + }; +} diff --git a/packages/ui/src/components/Table/hooks/usePageCache.ts b/packages/ui/src/components/Table/hooks/usePageCache.ts new file mode 100644 index 0000000000..9bf864749b --- /dev/null +++ b/packages/ui/src/components/Table/hooks/usePageCache.ts @@ -0,0 +1,280 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useState, useCallback, useRef, useEffect } from 'react'; + +const FIRST_PAGE_CURSOR = Symbol('firstPage'); + +type CursorType = string | number; + +type InternalCursor = + | TCursor + | typeof FIRST_PAGE_CURSOR; + +interface PageEntry { + data: T[] | undefined; + nextCursor: InternalCursor | undefined; + prevCursor: InternalCursor | undefined; +} + +interface GetDataResult { + data: T[]; + nextCursor?: TCursor; + prevCursor?: TCursor; + totalCount?: number; +} + +/** @internal */ +export interface UsePageCacheOptions { + getData: (params: { + cursor: TCursor | undefined; + signal: AbortSignal; + }) => Promise>; + initialCurrentCursor?: TCursor; +} + +/** @internal */ +export interface UsePageCacheResult { + loading: boolean; + error: Error | undefined; + data: T[] | undefined; + totalCount: number | undefined; + currentCursor: TCursor | undefined; + hasPreviousPage: boolean; + onPreviousPage: () => void; + hasNextPage: boolean; + onNextPage: () => void; + reload: (options?: { keepCurrentCursor?: boolean }) => void; +} + +type Direction = 'mount' | 'reset' | 'refresh' | 'next' | 'prev'; + +class PageCacheStore { + private cache = new Map, PageEntry>(); + + get(cursor: InternalCursor): PageEntry | undefined { + return this.cache.get(cursor); + } + + getOrCreate(cursor: InternalCursor): PageEntry { + const existing = this.cache.get(cursor); + if (existing) { + return existing; + } + const entry: PageEntry = { + data: undefined, + nextCursor: undefined, + prevCursor: undefined, + }; + this.cache.set(cursor, entry); + return entry; + } + + clear() { + this.cache.clear(); + } + + getTargetCursor( + direction: Direction, + currentCursor: InternalCursor, + initialCurrentCursor: TCursor | undefined, + ): InternalCursor | undefined { + if (direction === 'mount') { + return toInternalCursor(initialCurrentCursor); + } + if (direction === 'reset') { + return FIRST_PAGE_CURSOR; + } + if (direction === 'refresh') { + return currentCursor; + } + const currentEntry = this.cache.get(currentCursor); + + if (!currentEntry) { + return; + } + + return direction === 'next' + ? currentEntry.nextCursor + : currentEntry.prevCursor; + } + + linkEntryToSource( + entry: PageEntry, + direction: Direction, + currentCursor: InternalCursor, + ) { + if (direction === 'next') { + entry.prevCursor = currentCursor; + } else if (direction === 'prev') { + entry.nextCursor = currentCursor; + } + } +} + +function toInternalCursor( + cursor: TCursor | undefined, +): InternalCursor { + return cursor === undefined ? FIRST_PAGE_CURSOR : cursor; +} + +function toExternalCursor( + cursor: InternalCursor, +): TCursor | undefined { + return cursor === FIRST_PAGE_CURSOR ? undefined : cursor; +} + +/** @internal */ +export function usePageCache( + options: UsePageCacheOptions, +): UsePageCacheResult { + const { getData, initialCurrentCursor } = options; + + const [currentCursor, setCurrentCursor] = useState>( + () => toInternalCursor(initialCurrentCursor), + ); + + const cacheStore = useRef(new PageCacheStore()).current; + + const [loading, setLoading] = useState(true); + const [error, setError] = useState(undefined); + const [totalCount, setTotalCount] = useState(undefined); + + const abortControllerRef = useRef(null); + + const currentPage = cacheStore.get(currentCursor); + const data = currentPage?.data; + const hasNextPage = currentPage?.nextCursor !== undefined; + const hasPreviousPage = currentPage?.prevCursor !== undefined; + + const goToPage = useCallback( + async (direction: Direction) => { + const targetCursor = cacheStore.getTargetCursor( + direction, + currentCursor, + initialCurrentCursor, + ); + + if (!targetCursor) { + return; + } + + const existingEntry = cacheStore.get(targetCursor); + if (existingEntry?.data !== undefined) { + setCurrentCursor(targetCursor); + return; + } + + const entry = cacheStore.getOrCreate(targetCursor); + cacheStore.linkEntryToSource(entry, direction, currentCursor); + setCurrentCursor(targetCursor); + + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + + const abortController = new AbortController(); + abortControllerRef.current = abortController; + + setLoading(true); + setError(undefined); + + try { + const result = await getData({ + cursor: toExternalCursor(targetCursor), + signal: abortController.signal, + }); + + if (abortController.signal.aborted) { + return; + } + + entry.data = result.data; + + if (entry.nextCursor === undefined && result.nextCursor !== undefined) { + entry.nextCursor = result.nextCursor; + } + if (entry.prevCursor === undefined && result.prevCursor !== undefined) { + entry.prevCursor = result.prevCursor; + } + + if (result.totalCount !== undefined) { + setTotalCount(result.totalCount); + } + + setLoading(false); + } catch (err) { + if (abortController.signal.aborted) { + return; + } + + setError(err instanceof Error ? err : new Error(String(err))); + setLoading(false); + } + }, + [getData, initialCurrentCursor, currentCursor, cacheStore], + ); + + useEffect(() => { + goToPage('mount'); + + return () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + }; + }, []); + + const onNextPage = useCallback(() => { + if (loading) return; + const page = cacheStore.get(currentCursor); + if (!page?.nextCursor) return; + goToPage('next'); + }, [loading, currentCursor, goToPage, cacheStore]); + + const onPreviousPage = useCallback(() => { + if (loading) return; + const page = cacheStore.get(currentCursor); + if (!page?.prevCursor) return; + goToPage('prev'); + }, [loading, currentCursor, goToPage, cacheStore]); + + const reload = useCallback( + (reloadOptions?: { keepCurrentCursor?: boolean }) => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + + cacheStore.clear(); + + goToPage(reloadOptions?.keepCurrentCursor ? 'refresh' : 'reset'); + }, + [goToPage, cacheStore], + ); + + return { + loading, + error, + data, + totalCount, + currentCursor: toExternalCursor(currentCursor), + hasPreviousPage, + onPreviousPage, + hasNextPage, + onNextPage, + reload, + }; +} diff --git a/packages/ui/src/components/Table/hooks/useQueryState.ts b/packages/ui/src/components/Table/hooks/useQueryState.ts new file mode 100644 index 0000000000..308d9fd385 --- /dev/null +++ b/packages/ui/src/components/Table/hooks/useQueryState.ts @@ -0,0 +1,68 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useMemo, useState, useCallback } from 'react'; +import type { QueryOptions, QueryState } from './types'; + +function useControlledStateHelper( + initialValue: TInitial, + controlledValue: TControlled | undefined, + onChange: ((value: T) => void) | undefined, +) { + const [internalValue, setInternalValue] = useState(initialValue); + + const value = controlledValue !== undefined ? controlledValue : internalValue; + + const setValue = useCallback( + (newValue: T) => { + if (controlledValue === undefined) { + setInternalValue(newValue as unknown as TInitial); + } + if (onChange) { + onChange(newValue); + } + }, + [controlledValue, onChange], + ); + + return [value, setValue] as const; +} + +/** @internal */ +export function useQueryState( + options: QueryOptions, +): QueryState { + const [sort, setSort] = useControlledStateHelper( + options.initialSort ?? null, + options.sort, + options.onSortChange, + ); + const [filter, setFilter] = useControlledStateHelper( + options.initialFilter, + options.filter, + options.onFilterChange, + ); + const [search, setSearch] = useControlledStateHelper( + options.initialSearch ?? '', + options.search, + options.onSearchChange, + ); + + return useMemo( + () => ({ sort, setSort, filter, setFilter, search, setSearch }), + [sort, setSort, filter, setFilter, search, setSearch], + ); +} diff --git a/packages/ui/src/components/Table/hooks/useStableCallback.ts b/packages/ui/src/components/Table/hooks/useStableCallback.ts new file mode 100644 index 0000000000..1764e07dd0 --- /dev/null +++ b/packages/ui/src/components/Table/hooks/useStableCallback.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useRef, useCallback } from 'react'; + +/** + * Returns a stable callback reference that always calls the latest version + * of the provided function. Useful for callbacks passed as props that may + * change on every render but shouldn't trigger effect re-runs. + * + * @internal + */ +export function useStableCallback any>(fn: T): T { + const ref = useRef(fn); + ref.current = fn; + + return useCallback((...args: Parameters) => ref.current(...args), []) as T; +} diff --git a/packages/ui/src/components/Table/hooks/useTable.ts b/packages/ui/src/components/Table/hooks/useTable.ts index cd1ddd5c9c..e024f8a335 100644 --- a/packages/ui/src/components/Table/hooks/useTable.ts +++ b/packages/ui/src/components/Table/hooks/useTable.ts @@ -13,154 +13,119 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { useState, useMemo, useCallback } from 'react'; -import type { TablePaginationProps } from '../../TablePagination/types'; +import { useMemo, useRef } from 'react'; +import type { SortState, TableItem, TableProps } from '../types'; import type { - UseTableConfig, + PaginationOptions, + PaginationResult, + UseTableOptions, UseTableResult, - UseTablePagination, } from './types'; +import { useQueryState } from './useQueryState'; +import { useCompletePagination } from './useCompletePagination'; +import { useCursorPagination } from './useCursorPagination'; +import { useOffsetPagination } from './useOffsetPagination'; -/** - * Hook for managing table state including pagination and future features like sorting. - * Supports both controlled and uncontrolled modes using offset/pageSize pattern (Backstage style). - * - * @public - */ -export function useTable( - config: UseTableConfig = {}, -): UseTableResult { - const { data, pagination: paginationConfig = {} } = config; +function useTableProps( + paginationResult: PaginationResult, + sortState: SortState, + paginationOptions: PaginationOptions = {}, +): Omit< + TableProps, + 'columnConfig' | 'rowConfig' | 'selection' | 'emptyState' +> { + const { showPageSizeOptions = true, getLabel } = paginationOptions; - const { - rowCount: providedRowCount, - offset: controlledOffset, - pageSize: controlledPageSize, - onOffsetChange, - onPageSizeChange, - defaultPageSize = 10, - defaultOffset = 0, - onNextPage, - onPreviousPage, - showPageSizeOptions = true, - } = paginationConfig; + const previousDataRef = useRef(paginationResult.data); + if (paginationResult.data) { + previousDataRef.current = paginationResult.data; + } - // Determine if we're in controlled mode - const isControlled = - controlledOffset !== undefined || controlledPageSize !== undefined; + const displayData = paginationResult.data ?? previousDataRef.current; + const isStale = paginationResult.loading && displayData !== undefined; - // Use providedRowCount if passed, otherwise fallback to data length - const rowCount = providedRowCount ?? data?.length ?? 0; - - // Internal state for uncontrolled mode - const [internalOffset, setInternalOffset] = useState(defaultOffset); - const [internalPageSize, setInternalPageSize] = useState(defaultPageSize); - - // Calculate current values - const currentOffset = controlledOffset ?? internalOffset; - const currentPageSize = controlledPageSize ?? internalPageSize; - - // Calculate sliced data if data array is provided - const currentData = useMemo(() => { - if (!data) return undefined; - return data.slice(currentOffset, currentOffset + currentPageSize); - }, [data, currentOffset, currentPageSize]); - - // Update functions - const setOffset = useCallback( - (newOffset: number) => { - if (isControlled) { - onOffsetChange?.(newOffset); - } else { - setInternalOffset(newOffset); - } - }, - [isControlled, onOffsetChange], - ); - - const setPageSize = useCallback( - (newPageSize: number) => { - // When changing page size, reset to first page to avoid showing empty results - const newOffset = 0; - - if (isControlled) { - onPageSizeChange?.(newPageSize); - onOffsetChange?.(newOffset); - } else { - setInternalPageSize(newPageSize); - setInternalOffset(newOffset); - } - }, - [isControlled, onPageSizeChange, onOffsetChange], - ); - - const nextPage = useCallback(() => { - const nextOffset = currentOffset + currentPageSize; - if (nextOffset < rowCount) { - onNextPage?.(); - setOffset(nextOffset); - } - }, [currentOffset, currentPageSize, rowCount, onNextPage, setOffset]); - - const previousPage = useCallback(() => { - if (currentOffset > 0) { - onPreviousPage?.(); - const prevOffset = Math.max(0, currentOffset - currentPageSize); - setOffset(prevOffset); - } - }, [currentOffset, currentPageSize, onPreviousPage, setOffset]); - - // Pagination props for TablePagination component - const paginationProps: TablePaginationProps = useMemo( + const pagination = useMemo( () => ({ - offset: currentOffset, - pageSize: currentPageSize, - rowCount, - setOffset, - setPageSize, - onNextPage, - onPreviousPage, + type: 'page' as const, + pageSize: paginationResult.pageSize, + offset: paginationResult.offset, + totalCount: paginationResult.totalCount, + hasNextPage: paginationResult.hasNextPage, + hasPreviousPage: paginationResult.hasPreviousPage, + onNextPage: paginationResult.onNextPage, + onPreviousPage: paginationResult.onPreviousPage, + onPageSizeChange: paginationResult.onPageSizeChange, showPageSizeOptions, + getLabel, }), [ - currentOffset, - currentPageSize, - rowCount, - setOffset, - setPageSize, - onNextPage, - onPreviousPage, - showPageSizeOptions, + paginationResult.pageSize, + paginationResult.offset, + paginationResult.totalCount, + paginationResult.hasNextPage, + paginationResult.hasPreviousPage, + paginationResult.onNextPage, + paginationResult.onPreviousPage, + paginationResult.onPageSizeChange, ], ); - const pagination: UseTablePagination = useMemo( + return useMemo( () => ({ - paginationProps, - offset: currentOffset, - pageSize: currentPageSize, - data: currentData, - nextPage, - previousPage, - setOffset, - setPageSize, + data: displayData, + loading: paginationResult.loading, + isStale, + error: paginationResult.error, + pagination, + sort: sortState, }), [ - paginationProps, - currentOffset, - currentPageSize, - currentData, - nextPage, - previousPage, - setOffset, - setPageSize, + displayData, + paginationResult.loading, + isStale, + paginationResult.error, + pagination, + showPageSizeOptions, + getLabel, + sortState, ], ); +} + +/** @public */ +export function useTable( + options: UseTableOptions, +): UseTableResult { + const query = useQueryState(options); + + let pagination: PaginationResult & { reload: () => void }; + + // Conditional hooks - mode is stable for lifetime of component + if (options.mode === 'complete') { + pagination = useCompletePagination(options, query); + } else if (options.mode === 'offset') { + pagination = useOffsetPagination(options, query); + } else if (options.mode === 'cursor') { + pagination = useCursorPagination(options, query); + } else { + throw new Error('Invalid mode'); + } + + const sortState: SortState = useMemo( + () => ({ descriptor: query.sort, onSortChange: query.setSort }), + [query.sort, query.setSort], + ); + + const tableProps = useTableProps( + pagination, + sortState, + options.paginationOptions ?? {}, + ); return { - data: currentData, - paginationProps, - pagination, + tableProps, + reload: pagination.reload, + filter: { value: query.filter, onFilterChange: query.setFilter }, + search: { value: query.search, onSearchChange: query.setSearch }, }; } diff --git a/packages/ui/src/components/Table/index.ts b/packages/ui/src/components/Table/index.ts index 42ba6ff9c9..0d3d5ff8eb 100644 --- a/packages/ui/src/components/Table/index.ts +++ b/packages/ui/src/components/Table/index.ts @@ -15,6 +15,7 @@ */ export { Table } from './components/Table'; +export { TableRoot } from './components/TableRoot'; export { TableHeader } from './components/TableHeader'; export { TableBody } from './components/TableBody'; export { Column } from './components/Column'; @@ -29,12 +30,33 @@ export type { CellTextProps, CellProfileProps, ColumnProps, + TableProps, + TableRootProps, + TableItem, + ColumnConfig, + RowConfig, + RowRenderFn, + TableSelection, + SortState, + SortDescriptor, + NoPagination, + PagePagination, + TablePaginationType, } from './types'; export type { - UseTableConfig, + UseTableOptions, UseTableResult, - UseTablePagination, - UseTablePaginationConfig, + UseTableCompleteOptions, + UseTableOffsetOptions, + UseTableCursorOptions, + OffsetParams, + OffsetResponse, + CursorParams, + CursorResponse, + FilterState, + SearchState, + QueryOptions, + PaginationOptions, } from './hooks/types'; export { TableDefinition } from './definition'; diff --git a/packages/ui/src/components/Table/stories/Table.dev.stories.tsx b/packages/ui/src/components/Table/stories/Table.dev.stories.tsx new file mode 100644 index 0000000000..5be055cd01 --- /dev/null +++ b/packages/ui/src/components/Table/stories/Table.dev.stories.tsx @@ -0,0 +1,965 @@ +/* eslint-disable no-restricted-syntax */ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useState, Fragment } from 'react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { + Table, + TableRoot, + TableHeader, + TableBody, + Column, + Row, + CellText, + CellProfile, + useTable, + type ColumnConfig, +} from '..'; +import { Button } from '../../Button'; +import { TextField } from '../../TextField'; +import { Select } from '../../Select'; +import { Flex } from '../../Flex'; +import { data as data1 } from './mocked-data1'; +import { data as data4 } from './mocked-data4'; +import { selectionData, selectionColumns, tableStoriesMeta } from './utils'; + +const meta = { + title: 'Backstage UI/Table/dev', + ...tableStoriesMeta, +} satisfies Meta; + +export default meta; +type Story = StoryObj; +type Data1Item = (typeof data1)[0]; +type Data4Item = (typeof data4)[0]; + +export const BasicLocalData: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => ( + + ), + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + { + id: 'lifecycle', + label: 'Lifecycle', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { pageSize: 5 }, + }); + + return ; + }, +}; + +export const Sorting: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + isSortable: true, + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + isSortable: true, + }, + { + id: 'type', + label: 'Type', + cell: item => , + isSortable: true, + }, + { + id: 'lifecycle', + label: 'Lifecycle', + cell: item => , + isSortable: true, + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { pageSize: 5 }, + initialSort: { column: 'name', direction: 'ascending' }, + sortFn: (items, { column, direction }) => { + return [...items].sort((a, b) => { + let aVal: string; + let bVal: string; + if (column === 'name') { + aVal = a.name; + bVal = b.name; + } else if (column === 'owner') { + aVal = a.owner.name; + bVal = b.owner.name; + } else if (column === 'type') { + aVal = a.type; + bVal = b.type; + } else { + aVal = a.lifecycle; + bVal = b.lifecycle; + } + const cmp = aVal.localeCompare(bVal); + return direction === 'descending' ? -cmp : cmp; + }); + }, + }); + + return
; + }, +}; + +export const Search: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + isSortable: true, + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + const { tableProps, search } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { pageSize: 5 }, + searchFn: (items, query) => { + const lowerQuery = query.toLowerCase(); + return items.filter( + item => + item.name.toLowerCase().includes(lowerQuery) || + item.owner.name.toLowerCase().includes(lowerQuery) || + item.type.toLowerCase().includes(lowerQuery), + ); + }, + }); + + return ( +
+ search.onSearchChange(value)} + style={{ marginBottom: '16px' }} + /> +
No results found + ) : ( +
No data available
+ ) + } + {...tableProps} + /> + + ); + }, +}; + +export const Selection: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { pageSize: 5 }, + }); + + return ( +
+ ); + }, +}; + +export const RowLinks: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Band name', + isRowHeader: true, + cell: item => , + }, + { + id: 'genre', + label: 'Genre', + cell: item => , + }, + { + id: 'yearFormed', + label: 'Year formed', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data4, + paginationOptions: { pageSize: 5 }, + }); + + return ( +
`/bands/${item.id}` }} + /> + ); + }, +}; + +export const Reload: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + const { tableProps, reload } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { pageSize: 5 }, + }); + + return ( +
+ +
+ + ); + }, +}; + +export const ServerSidePaginationOffset: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'offset', + getData: async ({ offset, pageSize }) => { + await new Promise(resolve => setTimeout(resolve, 500)); + return { + data: data1.slice(offset, offset + pageSize), + totalCount: data1.length, + }; + }, + paginationOptions: { pageSize: 5 }, + }); + + return
; + }, +}; + +export const ServerSidePaginationCursor: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Band name', + isRowHeader: true, + cell: item => , + }, + { + id: 'genre', + label: 'Genre', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'cursor', + getData: async ({ cursor, pageSize }) => { + await new Promise(resolve => setTimeout(resolve, 500)); + const startIndex = cursor ? parseInt(cursor, 10) : 0; + const nextIndex = startIndex + pageSize; + return { + data: data4.slice(startIndex, nextIndex), + totalCount: data4.length, + nextCursor: nextIndex < data4.length ? String(nextIndex) : undefined, + prevCursor: + startIndex > 0 + ? String(Math.max(0, startIndex - pageSize)) + : undefined, + }; + }, + paginationOptions: { pageSize: 5 }, + }); + + return
; + }, +}; + +export const CustomRowRender: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + { + id: 'lifecycle', + label: 'Lifecycle', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { pageSize: 5 }, + }); + + return ( +
( + + {column => ( + + {column.id === 'name' ? ( + + ) : ( + column.cell(item) + )} + + )} + + )} + /> + ); + }, +}; + +export const AtomicComponents: Story = { + render: () => { + const displayData = data1.slice(0, 5); + + return ( + + + Name + Owner + Type + + + {displayData.map(item => ( + + + + + + ))} + + + ); + }, +}; + +export const RowClick: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Band name', + isRowHeader: true, + cell: item => ( + + ), + }, + { + id: 'genre', + label: 'Genre', + cell: item => , + }, + { + id: 'yearFormed', + label: 'Year formed', + cell: item => , + }, + { + id: 'albums', + label: 'Albums', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data4, + paginationOptions: { pageSize: 5 }, + }); + + return ( +
alert(`Clicked: ${item.name}`) }} + /> + ); + }, +}; + +export const SelectionSingleToggle: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
+ ); + }, +}; + +export const SelectionMultiToggle: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
+ ); + }, +}; + +export const SelectionWithRowClick: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
alert(`Clicked: ${item.name}`) }} + /> + ); + }, +}; + +export const SelectionWithRowLinks: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
`/items/${item.id}` }} + /> + ); + }, +}; + +export const SelectionWithPagination: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { pageSize: 5 }, + }); + + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + return ( +
+ ); + }, +}; + +export const SelectionSingleReplace: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
+ ); + }, +}; + +export const SelectionMultiReplace: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
+ ); + }, +}; + +export const SelectionReplaceWithRowClick: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
alert(`Opening ${item.name}`) }} + /> + ); + }, +}; + +export const SelectionReplaceWithRowLinks: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
`/items/${item.id}` }} + /> + ); + }, +}; + +// Type filter interface for ComprehensiveServerSide story +interface TypeFilter { + type: string | null; +} + +/** + * Comprehensive example showcasing a common complex use case: + * - Server-side offset pagination + * - Search/filtering + * - Sorting + * - Multi-selection + * - Type filter dropdown + */ +export const ComprehensiveServerSide: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const typeOptions = [ + { value: '', label: 'All types' }, + { value: 'service', label: 'Service' }, + { value: 'website', label: 'Website' }, + { value: 'library', label: 'Library' }, + { value: 'documentation', label: 'Documentation' }, + { value: 'other', label: 'Other' }, + ]; + + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + isSortable: true, + cell: item => ( + + ), + }, + { + id: 'owner', + label: 'Owner', + isSortable: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + isSortable: true, + cell: item => , + }, + { + id: 'lifecycle', + label: 'Lifecycle', + isSortable: true, + cell: item => , + }, + ]; + + const { tableProps, search, filter } = useTable({ + mode: 'offset', + initialSort: { column: 'name', direction: 'ascending' }, + getData: async ({ + offset, + pageSize, + sort, + filter: typeFilter, + search: searchQuery, + }) => { + // Simulate server-side filtering, sorting, and pagination + // with slower and slower responses + const page = Math.floor(offset / pageSize) + 1; + await new Promise(resolve => setTimeout(resolve, 300 * page)); + + let filtered = [...data1]; + + // Apply search filter + if (searchQuery) { + const query = searchQuery.toLowerCase(); + filtered = filtered.filter( + item => + item.name.toLowerCase().includes(query) || + item.owner.name.toLowerCase().includes(query) || + item.description?.toLowerCase().includes(query), + ); + } + + // Apply type filter + if (typeFilter?.type) { + filtered = filtered.filter(item => item.type === typeFilter.type); + } + + // Apply sorting + if (sort) { + filtered.sort((a, b) => { + let aVal: string; + let bVal: string; + switch (sort.column) { + case 'owner': + aVal = a.owner.name; + bVal = b.owner.name; + break; + case 'type': + aVal = a.type; + bVal = b.type; + break; + case 'lifecycle': + aVal = a.lifecycle; + bVal = b.lifecycle; + break; + default: + aVal = a.name; + bVal = b.name; + } + const cmp = aVal.localeCompare(bVal); + return sort.direction === 'descending' ? -cmp : cmp; + }); + } + + return { + data: filtered.slice(offset, offset + pageSize), + totalCount: filtered.length, + }; + }, + paginationOptions: { pageSize: 10 }, + }); + + return ( + + + +
No results match your filters + ) : ( +
No data available
+ ) + } + /> + + ); + }, +}; diff --git a/packages/ui/src/components/Table/stories/Table.docs.stories.tsx b/packages/ui/src/components/Table/stories/Table.docs.stories.tsx new file mode 100644 index 0000000000..49e0b44885 --- /dev/null +++ b/packages/ui/src/components/Table/stories/Table.docs.stories.tsx @@ -0,0 +1,198 @@ +/* eslint-disable no-restricted-syntax */ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useState } from 'react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Table, CellText, CellProfile, useTable, type ColumnConfig } from '..'; +import { Flex } from '../../Flex'; +import { Text } from '../../Text'; +import { RadioGroup, Radio } from '../../RadioGroup'; +import { data as data4 } from './mocked-data4'; +import { selectionData, selectionColumns, tableStoriesMeta } from './utils'; + +const meta = { + title: 'Backstage UI/Table/docs', + ...tableStoriesMeta, +} satisfies Meta; + +export default meta; +type Story = StoryObj; +type Data4Item = (typeof data4)[0]; + +export const TableRockBand: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Band name', + isRowHeader: true, + cell: item => ( + + ), + }, + { + id: 'genre', + label: 'Genre', + cell: item => , + }, + { + id: 'yearFormed', + label: 'Year formed', + cell: item => , + }, + { + id: 'albums', + label: 'Albums', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data4, + paginationOptions: { pageSize: 5 }, + }); + + return
; + }, +}; + +export const SelectionToggleWithActions: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
alert(`Clicked: ${item.name}`) }} + /> + ); + }, +}; + +export const SelectionModePlayground: Story = { + render: () => { + const [selectionMode, setSelectionMode] = useState<'single' | 'multiple'>( + 'multiple', + ); + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( + +
+
+ + Selection mode: + + { + setSelectionMode(value as 'single' | 'multiple'); + setSelected(new Set()); + }} + > + single + multiple + +
+ + ); + }, +}; + +export const SelectionBehaviorPlayground: Story = { + render: () => { + const [selectionBehavior, setSelectionBehavior] = useState< + 'toggle' | 'replace' + >('toggle'); + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( + +
+
+ + Selection behavior: + + { + setSelectionBehavior(value as 'toggle' | 'replace'); + setSelected(new Set()); + }} + > + toggle + replace + +
+ + ); + }, +}; diff --git a/packages/ui/src/components/Table/stories/Table.visual.stories.tsx b/packages/ui/src/components/Table/stories/Table.visual.stories.tsx new file mode 100644 index 0000000000..34259dde96 --- /dev/null +++ b/packages/ui/src/components/Table/stories/Table.visual.stories.tsx @@ -0,0 +1,340 @@ +/* eslint-disable no-restricted-syntax */ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useState } from 'react'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Table, CellText, CellProfile, useTable, type ColumnConfig } from '..'; +import { data as data1 } from './mocked-data1'; +import { data as data4 } from './mocked-data4'; +import { selectionData, selectionColumns, tableStoriesMeta } from './utils'; + +const meta = { + title: 'Backstage UI/Table/visual', + ...tableStoriesMeta, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +type Data1Item = (typeof data1)[0]; +type Data4Item = (typeof data4)[0]; +type CellTextVariantsItem = (typeof cellTextVariantsData)[0]; + +export const ProfileCells: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Band name', + isRowHeader: true, + cell: item => ( + + ), + }, + { + id: 'genre', + label: 'Genre', + cell: item => , + }, + { + id: 'yearFormed', + label: 'Year formed', + cell: item => , + }, + { + id: 'albums', + label: 'Albums', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data4, + paginationOptions: { pageSize: 5 }, + }); + + return
; + }, +}; + +export const EmptyState: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => [], + paginationOptions: { pageSize: 5 }, + }); + + return ( +
No data available} + /> + ); + }, +}; + +export const NoPagination: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + return ( +
+ ); + }, +}; + +export const SelectionWithDisabledRows: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( +
item.id === 2, + }} + /> + ); + }, +}; + +// Data for CellTextVariants story showcasing multiple features +const cellTextVariantsData = [ + { + id: 1, + name: 'Authentication Service', + description: 'Handles user login and session management', + type: 'service', + owner: 'Platform Team', + }, + { + id: 2, + name: 'A very long component name that should be truncated when it exceeds the available column width', + description: + 'This is also a very long description that demonstrates text truncation behavior in the table cells', + type: 'library', + owner: 'Frontend Team', + }, + { + id: 3, + name: 'API Gateway', + description: 'Routes and validates API requests', + type: 'service', + owner: 'Backend Team', + }, +]; + +export const CellTextVariants: Story = { + render: () => { + const [selected, setSelected] = useState | 'all'>( + new Set(['1', '3']), + ); + const [sortDescriptor, setSortDescriptor] = useState<{ + column: string; + direction: 'ascending' | 'descending'; + }>({ column: 'name', direction: 'ascending' }); + + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + isSortable: true, + cell: item => ( + + ), + }, + { + id: 'type', + label: 'Type', + isSortable: true, + cell: item => ( + 📦} + /> + ), + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + ]; + + return ( +
+ setSortDescriptor({ + column: String(descriptor.column), + direction: descriptor.direction, + }), + }} + /> + ); + }, +}; + +export const LoadingState: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + return ( +
+ ); + }, +}; + +export const ErrorState: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + return ( +
+ ); + }, +}; + +export const StaleState: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + return ( +
+ ); + }, +}; diff --git a/packages/ui/src/components/Table/mocked-data1.ts b/packages/ui/src/components/Table/stories/mocked-data1.ts similarity index 93% rename from packages/ui/src/components/Table/mocked-data1.ts rename to packages/ui/src/components/Table/stories/mocked-data1.ts index ea5a9f2db8..b508bce387 100644 --- a/packages/ui/src/components/Table/mocked-data1.ts +++ b/packages/ui/src/components/Table/stories/mocked-data1.ts @@ -15,6 +15,7 @@ */ export interface DataProps { + id: string; name: string; owner: { name: string; @@ -29,6 +30,7 @@ export interface DataProps { export const data: DataProps[] = [ { + id: 'authentication-and-authorization-service', name: 'authentication-and-authorization-service', owner: { name: 'security-team', @@ -42,6 +44,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-interface-dashboard-and-analytics-platform', name: 'user-interface-dashboard-and-analytics-platform', owner: { name: 'frontend-team', @@ -55,6 +58,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'payment-gateway', name: 'payment-gateway', owner: { name: 'finance-team', @@ -68,6 +72,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'real-time-analytics-processing-and-visualization-engine', name: 'real-time-analytics-processing-and-visualization-engine', owner: { name: 'data-team', @@ -81,6 +86,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'notification-center', name: 'notification-center', owner: { name: 'platform-team', @@ -94,6 +100,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'administrative-control-panel-and-user-management-interface', name: 'administrative-control-panel-and-user-management-interface', owner: { name: 'frontend-team', @@ -107,6 +114,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'search-indexer', name: 'search-indexer', owner: { name: 'search-team', @@ -120,6 +128,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'cross-platform-mobile-application-framework', name: 'cross-platform-mobile-application-framework', owner: { name: 'mobile-team', @@ -133,6 +142,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'database-migration', name: 'database-migration', owner: { name: 'devops-team', @@ -146,6 +156,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'api-gateway', name: 'api-gateway', owner: { name: 'platform-team', @@ -159,6 +170,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'content-management', name: 'content-management', owner: { name: 'content-team', @@ -172,6 +184,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'enterprise-reporting-and-analytics-dashboard', name: 'enterprise-reporting-and-analytics-dashboard', owner: { name: 'analytics-team', @@ -185,6 +198,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'image-processing-and-optimization-service', name: 'image-processing-and-optimization-service', owner: { name: 'media-team', @@ -198,6 +212,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'customer-portal', name: 'customer-portal', owner: { name: 'frontend-team', @@ -211,6 +226,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'log-aggregator', name: 'log-aggregator', owner: { name: 'devops-team', @@ -224,6 +240,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'identity-provider', name: 'identity-provider', owner: { name: 'security-team', @@ -237,6 +254,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'document-storage', name: 'document-storage', owner: { name: 'storage-team', @@ -250,6 +268,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'workflow-engine', name: 'workflow-engine', owner: { name: 'platform-team', @@ -263,6 +282,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'mobile-backend', name: 'mobile-backend', owner: { name: 'mobile-team', @@ -276,6 +296,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'system-monitoring-and-alerting-dashboard', name: 'system-monitoring-and-alerting-dashboard', owner: { name: 'devops-team', @@ -289,6 +310,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'email-service', name: 'email-service', owner: { name: 'communication-team', @@ -302,6 +324,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-pipeline', name: 'data-pipeline', owner: { name: 'data-team', @@ -315,6 +338,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'configuration-manager', name: 'configuration-manager', owner: { name: 'platform-team', @@ -328,6 +352,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'testing-framework', name: 'testing-framework', owner: { name: 'qa-team', @@ -341,6 +366,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'cache-service', name: 'cache-service', owner: { name: 'platform-team', @@ -354,6 +380,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'billing-system', name: 'billing-system', owner: { name: 'finance-team', @@ -367,6 +394,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'comprehensive-product-documentation-and-api-reference', name: 'comprehensive-product-documentation-and-api-reference', owner: { name: 'docs-team', @@ -380,6 +408,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'queue-manager', name: 'queue-manager', owner: { name: 'platform-team', @@ -393,6 +422,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'security-scanner', name: 'security-scanner', owner: { name: 'security-team', @@ -406,6 +436,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'user-profile', name: 'user-profile', owner: { name: 'frontend-team', @@ -419,6 +450,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-warehouse', name: 'data-warehouse', owner: { name: 'data-team', @@ -432,6 +464,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'deployment-automation', name: 'deployment-automation', owner: { name: 'devops-team', @@ -445,6 +478,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'chat-service', name: 'chat-service', owner: { name: 'communication-team', @@ -458,6 +492,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'analytics-dashboard', name: 'analytics-dashboard', owner: { name: 'analytics-team', @@ -471,6 +506,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'file-uploader', name: 'file-uploader', owner: { name: 'storage-team', @@ -484,6 +520,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'search-service', name: 'search-service', owner: { name: 'search-team', @@ -497,6 +534,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'mobile-sdk', name: 'mobile-sdk', owner: { name: 'mobile-team', @@ -510,6 +548,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'performance-monitor', name: 'performance-monitor', owner: { name: 'devops-team', @@ -523,6 +562,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'content-delivery', name: 'content-delivery', owner: { name: 'media-team', @@ -536,6 +576,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-authentication', name: 'user-authentication', owner: { name: 'security-team', @@ -549,6 +590,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-export', name: 'data-export', owner: { name: 'data-team', @@ -562,6 +604,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'admin-api', name: 'admin-api', owner: { name: 'platform-team', @@ -575,6 +618,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'testing-dashboard', name: 'testing-dashboard', owner: { name: 'qa-team', @@ -587,6 +631,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'message-broker', name: 'message-broker', owner: { name: 'platform-team', @@ -600,6 +645,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'payment-processor', name: 'payment-processor', owner: { name: 'finance-team', @@ -613,6 +659,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'document-viewer', name: 'document-viewer', owner: { name: 'frontend-team', @@ -625,6 +672,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'load-balancer', name: 'load-balancer', owner: { name: 'devops-team', @@ -638,6 +686,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'security-audit', name: 'security-audit', owner: { name: 'security-team', @@ -651,6 +700,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-settings', name: 'user-settings', owner: { name: 'frontend-team', @@ -664,6 +714,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-import', name: 'data-import', owner: { name: 'data-team', @@ -677,6 +728,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'infrastructure-monitor', name: 'infrastructure-monitor', owner: { name: 'devops-team', @@ -690,6 +742,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'notification-manager', name: 'notification-manager', owner: { name: 'communication-team', @@ -703,6 +756,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'analytics-processor', name: 'analytics-processor', owner: { name: 'analytics-team', @@ -716,6 +770,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'file-manager', name: 'file-manager', owner: { name: 'storage-team', @@ -728,6 +783,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'search-index', name: 'search-index', owner: { name: 'search-team', @@ -740,6 +796,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'mobile-authentication', name: 'mobile-authentication', owner: { name: 'mobile-team', @@ -753,6 +810,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'system-monitor', name: 'system-monitor', owner: { name: 'devops-team', @@ -766,6 +824,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'media-processor', name: 'media-processor', owner: { name: 'media-team', @@ -778,6 +837,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-management', name: 'user-management', owner: { name: 'security-team', @@ -790,6 +850,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-transformer', name: 'data-transformer', owner: { name: 'data-team', @@ -803,6 +864,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'admin-dashboard', name: 'admin-dashboard', owner: { name: 'platform-team', @@ -816,6 +878,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'test-automation', name: 'test-automation', owner: { name: 'qa-team', @@ -828,6 +891,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'event-bus', name: 'event-bus', owner: { name: 'platform-team', @@ -840,6 +904,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'invoice-generator', name: 'invoice-generator', owner: { name: 'finance-team', @@ -852,6 +917,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'document-editor', name: 'document-editor', owner: { name: 'frontend-team', @@ -864,6 +930,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'service-discovery', name: 'service-discovery', owner: { name: 'devops-team', @@ -876,6 +943,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'security-monitor', name: 'security-monitor', owner: { name: 'security-team', @@ -888,6 +956,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-preferences', name: 'user-preferences', owner: { name: 'frontend-team', @@ -900,6 +969,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-validator', name: 'data-validator', owner: { name: 'data-team', @@ -912,6 +982,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'infrastructure-automation', name: 'infrastructure-automation', owner: { name: 'devops-team', @@ -925,6 +996,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'notification-dispatcher', name: 'notification-dispatcher', owner: { name: 'communication-team', @@ -938,6 +1010,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'analytics-collector', name: 'analytics-collector', owner: { name: 'analytics-team', @@ -950,6 +1023,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'file-processor', name: 'file-processor', owner: { name: 'storage-team', @@ -962,6 +1036,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'search-analyzer', name: 'search-analyzer', owner: { name: 'search-team', @@ -974,6 +1049,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'mobile-notifications', name: 'mobile-notifications', owner: { name: 'mobile-team', @@ -986,6 +1062,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'system-alerts', name: 'system-alerts', owner: { name: 'devops-team', @@ -998,6 +1075,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'media-encoder', name: 'media-encoder', owner: { name: 'media-team', @@ -1010,6 +1088,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-authorization', name: 'user-authorization', owner: { name: 'security-team', @@ -1022,6 +1101,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-aggregator', name: 'data-aggregator', owner: { name: 'data-team', @@ -1034,6 +1114,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'admin-authentication', name: 'admin-authentication', owner: { name: 'platform-team', @@ -1046,6 +1127,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'test-coverage', name: 'test-coverage', owner: { name: 'qa-team', @@ -1058,6 +1140,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'event-processor', name: 'event-processor', owner: { name: 'platform-team', @@ -1070,6 +1153,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'payment-validator', name: 'payment-validator', owner: { name: 'finance-team', @@ -1082,6 +1166,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'document-converter', name: 'document-converter', owner: { name: 'frontend-team', @@ -1094,6 +1179,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'service-health', name: 'service-health', owner: { name: 'devops-team', @@ -1106,6 +1192,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'security-logger', name: 'security-logger', owner: { name: 'security-team', @@ -1118,6 +1205,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-analytics', name: 'user-analytics', owner: { name: 'frontend-team', @@ -1131,6 +1219,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'data-cleaner', name: 'data-cleaner', owner: { name: 'data-team', @@ -1143,6 +1232,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'infrastructure-deployer', name: 'infrastructure-deployer', owner: { name: 'devops-team', @@ -1155,6 +1245,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'notification-queue', name: 'notification-queue', owner: { name: 'communication-team', @@ -1167,6 +1258,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'analytics-exporter', name: 'analytics-exporter', owner: { name: 'analytics-team', @@ -1179,6 +1271,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'file-validator', name: 'file-validator', owner: { name: 'storage-team', @@ -1191,6 +1284,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'search-optimizer', name: 'search-optimizer', owner: { name: 'search-team', @@ -1203,6 +1297,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'mobile-analytics', name: 'mobile-analytics', owner: { name: 'mobile-team', @@ -1215,6 +1310,7 @@ export const data: DataProps[] = [ lifecycle: 'experimental', }, { + id: 'system-logger', name: 'system-logger', owner: { name: 'devops-team', @@ -1227,6 +1323,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'media-validator', name: 'media-validator', owner: { name: 'media-team', @@ -1239,6 +1336,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'user-audit', name: 'user-audit', owner: { name: 'security-team', @@ -1251,6 +1349,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'data-normalizer', name: 'data-normalizer', owner: { name: 'data-team', @@ -1263,6 +1362,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'admin-authorization', name: 'admin-authorization', owner: { name: 'platform-team', @@ -1275,6 +1375,7 @@ export const data: DataProps[] = [ lifecycle: 'production', }, { + id: 'test-reporting', name: 'test-reporting', owner: { name: 'qa-team', diff --git a/packages/ui/src/components/Table/mocked-data2.ts b/packages/ui/src/components/Table/stories/mocked-data2.ts similarity index 100% rename from packages/ui/src/components/Table/mocked-data2.ts rename to packages/ui/src/components/Table/stories/mocked-data2.ts diff --git a/packages/ui/src/components/Table/mocked-data3.ts b/packages/ui/src/components/Table/stories/mocked-data3.ts similarity index 100% rename from packages/ui/src/components/Table/mocked-data3.ts rename to packages/ui/src/components/Table/stories/mocked-data3.ts diff --git a/packages/ui/src/components/Table/mocked-data4.ts b/packages/ui/src/components/Table/stories/mocked-data4.ts similarity index 99% rename from packages/ui/src/components/Table/mocked-data4.ts rename to packages/ui/src/components/Table/stories/mocked-data4.ts index b86d4c903f..5cedd2cbae 100644 --- a/packages/ui/src/components/Table/mocked-data4.ts +++ b/packages/ui/src/components/Table/stories/mocked-data4.ts @@ -15,6 +15,7 @@ */ export interface RockBandDataProps { + id: string; name: string; image: string; description: string; @@ -31,6 +32,7 @@ export interface RockBandDataProps { export const data: RockBandDataProps[] = [ { + id: 'The Beatles', name: 'The Beatles', image: 'https://upload.wikimedia.org/wikipedia/en/thumb/4/42/Beatles_-_Abbey_Road.jpg/250px-Beatles_-_Abbey_Road.jpg', @@ -57,6 +59,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.thebeatles.com', }, { + id: 'Led Zeppelin', name: 'Led Zeppelin', image: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxMTEhUSExMWFRUXGBcYFxgYGBcXGBgXGBUYFhgYFhgYHSggGBolHRcXITEhJSkrLi4uGB8zODMtNygtLysBCgoKBQUFDgUFDisZExkrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrK//AABEIALwBDAMBIgACEQEDEQH/xAAcAAABBQEBAQAAAAAAAAAAAAAGAQIEBQcAAwj/xABCEAACAQIEBAQDBQYFBAEFAQABAhEAAwQSITEFBkFREyJhcTKBkQdCobHwFCNScsHRM2KC4fEkg5KiQxdTc6OyFf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDZzS0gpaBK6urqDq6urqDq6krqBa6krqDjSVxrqDprqSuoFmuFJSigWaWkFdQdS0grqBa411JQcaQmuNV/FMVlUgHUg/Kg8+Kcat2d5J7Dt3J6CgXiX2nFLoVbQKGAGLFNZgk5htU7ia3C9uyozggEkgEmQRm1I2jodMwq24fyxYyL41pHYDqJGpkmDufU0HYTnFAiteAAZgilD4gk6DNG1FStQVxjhdq0gVhlsh1dGk/unVsyySdLZOnpMbHS14HxwXGyaHyzI1HyNARUtMFOoOpaSloENIwp1IaBa6urqDjSV1dQdSV1NJoFmummzXTQOmups0s0C00murzuXANaB80mas/5u+0uxhly2Ct+6egMqv8AOR19KAb/ANquPZgQ1tB/CEBB9y0mg37NSg1mvLP2rWLxS3iVNm4YGfQ2idt5lJ9RA71ottwdZoJE11eeanTQPpKQGloFrqSaaTQI7UL8UZ7twqNEUEluuYEZQo7DUyav8ZdgGgbGY4XRdt+IbfSREn/bSJ9aCnt8cvJde5cVFbNaQKznKLbZir5gCczHy6CdYANGvBOLXWA8ZbYVhKFPEB9mW4oI+cH0FZ7wJ7jYgC4ZHiIkn7xV7t4R1O2Yeh13FaPjQ0L4YUkEEyxUR1JIU9KCvxXBXvNcLvnVwR+8NwhQWMqltWVAMvWJmJmq7lu0bd5VyeGgQrl3lywOYHqIHyJb3orw+IUyuZSd8oIJH4z86EP2/NiblvT93d9hHhp17h80j1HpIaHhrkivcVU8KxIIg79R2q0BoHUtJS0HUhNdXRQLXV1JQcaSuNJNB01E4jjUs22uOYVRJqSxrNPtI4mbpOHRiAozN2LfdX8Z+QoKHmT7QMRec28OSqzpl/239++3QlmBw3GLdv8AaPGugAzkZ2JIjcqdI9Pwq0+zfhVhE8a5ke6zsqgssrl8p8p3Myd9BEAdT3H4lVGp37KWj1IUGB6nSgqeR+cRjEyuAt5PiXoY0zL/AG6UVrdFfN9jibYTGi6mkPJH+Q7r66Vu3D+IB1V1MhhI9qC7a7WZfbJx69ZtW7Vpiq3S6uwMGAF8oO+snUfwx1oz4zjclm4wmcpC5d8x0UCeskV8+80cZxN5hYxDqxsMy+UAeYHKSdPT060FITVry/y/fxj5LKgxGZmMKs9+p9gDUPhPD2xF1bKQGadTsIE6kdK2HlfhRwFpxoSVYkobgBI+9lZ2Ab1AG3yoM85v5QuYEW2JLo2jMBoH3AjoCJj2o1+yfm27cP7JdYMESbbHeAYyk9YG3tULnzh998O7sXyIM+t5rgYSCcyPOQjUgqemwnTPeFYtrbhlYqRsRvPSg+pUea9laqPgGJL2LRZgWKISRtmKifxq2VqCQDTpryBp4NA4mvN2pSa8bjUFNzFfXIVZsoI1PaszFyzbwl5XuFyZJZN7h+6oIGg9Onzo15o4rZC3EuifKTHcCAfzrL8JxbC2UvhJAurGQnNqCCGXSFKwdTQQsFxh7dxbuY5sNcLNaJMOpyWXZT/EoCAg9NRs1azwfjWExoUAqxWDkY6g9MyHSR3rEOJ4sXGzARmMvMeZtQDp6GfcmtK5G4Vh71pWa3bdlGudA2nY5pmgM+N8wYfBWWZmWVHltrGZmOygD5a9BrWT8Hxd/wAR7l7zLdJuFQ0f4p302AD9egoo+0kWksC2iKh3VUXKJJCzCiOtBXBsViLcMqgquYCQJYspABPWPK3+kUG38HxS5c/whjOu8DQfgKIsPckVlvK9u81xrjHLaMMqGSdAAzDspaSAe4o54NxIMPhI1iOo96C/paYhp1AtLSUtAlJTqaaBDTTSk01jQeOJuQpPpWScxXApu3mbLmMDX7ijOzR10P4VovM3EVs2WdjA0/E/2k/KsB5y4hcuuB/8YUZOxDksD81y/IUG2YLFWLGFth3ChUtlpkwzgHUDqSfxqEDbxGIuqtycqqDkdkZT7qQw9CPX51+C5lwYwqXFJ8RUgbBx8OYHMIIOVTqCDAPrVBzBxixaU4y04OIYgJoiEZRqGVAM0yJMaiKAU5ow+W9clizJedGY7nyZhPcwN+5o/wCQOJZsKoJ1Qlfl0/Csrs4trmdn8zPdLn1LW7gY6fzfhV9yZxXwwyk/rSPzP0oNS5lxwTC3LhjyZW120YHUQfbbrWB8Rv8AiXXeZzOzTrrLEzrrrWkcx4hsVhGtoTPlYgSZykHLHWdNO461l7CgJ/s+41awuJL3pylSJABg7++u3zo1v8Y4hfQ3USzYRklA5ctcRirL5x5dVn/MCdgNayKp1ji99MgW84Fv4BmJC+gU6R6UGhcyc3h8A9l7fhYhv3TWt4UqDnkwYKnqJk1m1kjMJMCRJ3gTrp1pL99nZndizMSWYmSSdyaaKD6Q5XwapbDKB5gDIEAyN46TqT6zRAKoOVOIW79hHtHywAPSAJHrG3uCOlXyig9lp001a4mgVjUTFv5TUkmvC+ukUGRcxcRuG4ywrqJbKfhJGaJH3ogwD1A3ql4lwSzcRbiIUzDMf4VAEkKOu/6g1ofE+XrbM8j4o/D/AJqoxfALp8NSwyJoCB5gNDrB1EjWgyhbAZoE5Zj1rUOTeEPaRWtvBI+8Mw19ARTuD8nAXCWiFJmRM6LB+ep9oo4wmECwBsAP+KAC5l5fvMRca9cueYEjKoVR3CqJb5k1R30gPbRXuWoU6+RkZSTIEHTzEQd962XwxXi/DbRbNkE9436UATyxZNxQrMdRAEnYAkAmNxP5UYcPwnhBQO+skknytqSfap+HwqJqqKvcgAflXnxHEpbALsFB2nqQCx/AGgn4a6c0dI1+pA/XpU8GqPB3wxLDbQT7CfrJP0qzw9/7p+VBLpaQUooENNNKahcQx6WhLddgN/8Aig9cTfVFLMYA/WnrWe89c1ObFy1ZDKWAh1kMokE6g+U6HbvTeZOPNdMAwBMDoNvqaEb1wtMmd567bigGuM8wYm6uW7ddx/mZm2/mPtVH4jvCST2BO3TSdhRRisJsQBG4Y/D6T2Hrt6jerLlTB4ZHS5i8Oly1cOXPJYI3lynytEHMfYQdtSE//wClmI8PTEoJE5HQ6EjUSGP1HahDm/lq5gjbF24rs+YgKDoBEyT7ivodro1Pb8omsa+0HjFrFOCFHh22Kh9nfMN0/wAmgMddD6AAKziisQBoSfckR+U/WiH7PeFjE4ko2qpbZwpJAd5CorEahTmO3b3oev213EgSAJ1O25rT/so4Kf2S9fH+JcaLZ9LJlZ7TczA+goJ3OGFY4VXwwVMmVskBf3Z006AqdwR/FWR47Ctbco4hhBOs7iRr863TjjCEuR+6ulAw/h8WEefQqxP8yetCHE+UlxNws1xrbrC3AFBnIsErJ0mBrrQD/LuHstaS04Du7m6Vg/4eUI6M3RjlFwAa/uukiZXFfs+a0j3hfTw1BMFWzR0Gkyx0FGvC+UbNi2PDktoczHWYIEwBpJmKUXmYxeAW3bcC2Bq125EiFHRJPzE6BaDJOKcKewLef4nWSNfKdDlPrDA/OvPhuDa7cW2ilmYwACB0k6nTp1rQMZyi2JutcuO6eKxcqCDlAGVIkQDGUewjoCLDgfKdvC3ZR2ZyCudoELoXygCJlrag+re1AW8gcMfD4S3auMrMC5OWcozOzZRIG0xtGlFKmqDgjhRk7Ex7EkwPQaiOgirpHoPeaQmmFq82ujvQSAaj3moM419p2DsFkXNeYaeQDLP87aH5UM437XJPkw5y9Qza/Uf2oD+83nPy96W0vUQR+torOcL9pSFpuW2UekGPfXX5UacL4vavIHQyDtoQfnIoLGw2VmUiIiNtQVGv1DD5VNtvVLxe9HhOBtcAY7EK3liBvLFNNuvSrOxcmgmA0tu2SNyD+u4pgbpUix8I+v1oFt2QNdSfUz9BsPkKoua+XmxeSLuQIHhcshmYAAsZkACR/qNEIr0VaCl4RFqbLCHXUn+Kdc49CZPvI6VazpTMfgM4BU5XX4W/NT/lOn0mq4cTCSt3yMIkN6mBB6gnqKC+wt/7p+R71KmqA4odNatcNiQV8xg0DOL4s2rRuCNCszsAWAJPsDWecZ4sWlmJnY+laXiEBBBEgiCKyzifKn/VpZGIdLNzNAgMysAWCI3Y6xmBiPWgE8fjiWCICWPQDMdTGw9qm4fl/FqGuXFyWlU5VkTJZYZguh6mSe2laZwXgdmwMiWwon1M+rMSSx96sOK4fPZuIBup/vQYoWI9+2leZJXJcRRNu5mgroWIUgHTY+F+fep2PtQxgfnXjgbIuP4Wvn3iSVyqWzDuQAw101Oo3AWHH+actj9ntMzZoMEzFsqDldt41mNzMHy7gWKZmMsZP5egHQVPvBDDAtJALkx8XULH3YiJqDiWiggXDr7VvnI2F8HAYdNj4Ydvd5uN+LGsDW0W8o3chV9zoPxNfR6IEXINguUR0gQKCm4tbDW72HJjOjZD2YhmUj1Dbfy164i0M5uxAdDmHYxp+cfKq3nS6bdpbw+4wk+nQ/WfrVt+0BrfplH6ig9cJflOxA27VEt4YZ5gljmAmNM2rR6Qv/se9ehgFSN9BvpBIG20yRSJdVrwUhlORmWZX7+Vtj6CPT50Eu6AuwkxHX8e1QMEQ965AMWYtz0Nxl8R8vsH19Y7VPuXcsNAIGrSYhRqW13jtSYDD5LQB+M5nf8Ancl217AmB6KKBtsw5jsP1+NW9m9pNDpvnNU3D4oxFBa3MVWd/anxe7+5wdtsgvSbjElQVBChWP8ABuW9FHSiC/j4eJqk5j4et+/Zus8KiXBllwWlkIAKCQIDT6DtNAG8d5VWzZzC4ouIMxBYnOvUrplX0HpQjW3eHcZllZQKRaAKxbYiCSFBDfX86yvm/g5w19hkyIxJQTsOqj0BP5UFLNHX2a8UIZrBOnxL/UfrvQGKteWcUbeJtttrB+Yj84oNyxFkXLTITGZSPaRuPak4FeLWkLfEVBb001+h0qFhsf5O57VX8vcSRQ6vcUXrl14QkSqZyEUD2gn1mgOMPqJ7n8NhUpRUSy20bDb5VLWgeKkrUW3UpTQegoV57u22W1YKhrjuGXuioQWYdp0X1k9qJr95UVnc5VUFmJ2AAkk/IVnPCL7Yu/cxTA+YwojRbY0VTrv39Se9AUcNtwoM9KtbdhmEgabVEwNjMwXtq39vc1foABA2oEcUIc3JkC3hvadX+SsCY+Q/GjA1W8Yso1tg4kEHTqfQTQOuoD5h/wAivPEN5GMxode2m9VnKuM/d/szyLlpRAYam1sp7Er8BjsN5BM7iVwKjMSIg77GdAPmYHzoMr42hBPlOpJ9Iih5mIIMAnTcAjfsdJoq4phgAcp8saDeOunYabUL4xhqDp7dNd9qCDxTOrZXQIQF8oED4QQTGkkEEnuTVFibpqfiXWYTMVAGrKFMkCdNes1W4jvQX3I2A8XG4VYkK3it7W5uL/7BB863PF4dyv7sqG7MCQT7g6fjQ1ynyxbwqC/ZJN65Ztgi4QVBgMwQgArJjedhVxhuKJcIBORx8SMYdT2I6jsRoaAb5gxrXMPibN63lZbbbSRKjMNx6Aio3J2PN20M2oWBPcif9q9uc7muIU7tbOU9SCka99QfrVHy3jLduyqsSInodaA3fzPbj4VbMfWFOWfnB+QpLd/96zCJACDtMlj77ih/D4+9fRzZT92cyhsxVmOxKkfDBkT71ZcKRrazeBL6gAGQxmZAn9RQSOLYoJ4aEy967bTX+Gc7COgKIwqxv4omVUZmjpsB3YnT+tZXzNzIRi7RGosXA7dZOgYD5SPnWg47iioMudA5+6WURP3m13oPRrMCZk9fWa8bWKAJBNQcLxBQ+UMGVo2OYZge47iqLmDjARzlNBacUx4FwQfpVNxdP2m9ZSFcKGm2zZVbMVElsrBTEicp3NDmN4z9ajcG4qqXc10ZgdJ1OX101oNV5P4TdwwuBiPCIXJbztc8JhOYB2UEqZ/Cs9+03F+JjIj4EUfUlv60V43m8WcNmQrcJ0GUkiTPxem1ZljMU1241xzLMZP9PlQRJp6XCCCNxB+lNakoNW5Yxy3kzZoYgSp6mIJXvVqeB2bhlhmPykf71mvL3EnSESJkkT+IPejfC4nEnzeGI7qcw3/h+L6A0Fth+H4qwf3Fw5f4XOZfoduuxq2tcw31Ui5hiXAMZSAGMaDXQCfXSqvD8ZdIDqf7+mVgG/Cp1njNljBgbbgr89aCVY5sRR+9VlMAkBLmk6aGIb3U1f3uMWLaq73VCsQqkSxYnYAKCT9NOtVOCxdh9FZT6ZlP9amXcGhGqj5/regHuYuLjG3v2Oy/7pSPFZTOdgZyAj7gMT3PoNb7h/DxaRURdf1rUSzwayrZwvm/i1zD2JP5UQcGuFlJKt/MVKzBjYgdtxoaCZgsPkWOu5Pc1JFNFOFA014XbQJn0j+ule9NNAKcewFwMl+wVW7bn4vhZW0ZHjUqdPYgHpQxzXzOL9r9ju2GtX7jaqScpFuX8SxeAhiHW3uARJMaVomLAkA9dB79qDeab4Ft2bDftFq2Zu29r9ob+JabZl0kQRsdZUgBn+H4xIZWYGNCSMreoddg3quhImBVbj7hzSNuka+tTeN4SyQuLw19bttWUFbhW3fTNMI4YDOp6HrG53qpxOLcg6BgeogkfIRH5UEfHsxbzlZhdEyx8Ag+TSYj51U4oyrH0NWN/DlSpKlAVBOYr3IJ8vwiR8JEiK8+E4fxL9q2R5bl60nuGuqp/A0H0Fh3EJMLKiAYGpAge/SKj8XQBczKDGxYAr7E6ZT06b1D5yN39lveCoa5kbKuXNM6EBCCGOWYWNag8ENq9ZS7YvX7OYee0HNwI+zoVuq+WCCIEdO9BT8YdMQz21bwrqrlyOPKc2iFLm2p+6SD6daDcFgrpmXYDqBbBIiZHmmKvudsO9gm/afK4QL5ZTyscpyptImTBj4TAgznpxVyCPEcf6m/vrQaxwu02HtW5IS2tsl80A52hsxYbQSwiOtQ+K8yeIMti9YCkHPd8VAyjsoIOXfeZ30FZ3juKXLpYFmCEyLZdmUdgMxqCaC8CWXxCIhlA652118wDHMd+uvWtQXjZtOoCqLTkgHKUYPuBkgZs2sHrH1x7hX+JHcR9WFbHcw7C35VAYZIYk9GElvlOg9utBR8538gDoCs9IykEkT5TrHyoMym55p0kLPqWA/rNXnNElGOUAMyLmGn3gIK/d2JionA+GFrQIOp1K+qkH2BgR7xQQOKctvbGYNmMarB/A9a9eXeXS48e8wt2l18w0ZYIMmfIPU1od7l13BZLmXPuGGYHSOhBG3eq+3hLuHBXEAtaMDOsshHZ7cSu280HpwLCgZ7NxRnt/ECBDp9y4NNZGhjqCKEOdb9lCMPaQAhizkAAeYaARvp9KPsfbDWwRJYwEZIzjNoHTTza6lOonfQUNpyTce8t7GMi5iGKWzmBiBlZtInbT11oM+t4VmRnAkIRm7jNMGO2kV4CtVvco2lvM6TbUrlKiChkRAUqc07mZk7a1T8L5HW7b8VmNt80ZEYELBgyTJB30n6bUAzwqwqkG4vlJgHNlAPow2NaVy5j7RK2i03DOVxKh4EkEbZgO41iYr34RwFLByICzvvOuW3J8zNuFkNoIzEr0oT5vt3sFiB4eUWn89s5ElG6qGiQQdQR0PoaDVrFto/xCB7LPygVHx3B7V8BbigrmkLAInKwlgZDfFOs6gHpQZytzsjEJigoOwuiQv/AHF+5/MNN9q0PxBAA+Ua/QDegjjhFtVCqqBRoPux7RpUjAcHWSVZ0jQLmlCO5ToZnYiq/H8zYa02Rjnuf/btjxHB7NlkJ00Jq65cx4voXCZCDlKkox9zkJjrQe2G4Y33yInZZ1Gu50irW2gAAGgFItPFAopRXV00CUhpTSGgrOMt5MobI7SLbdnymD9YrMsFzDcFlTdn9osrCNBPjps1i9HUxpc1WQGncMVc+JduRbt2ncASSpUan+fRumlZRxzCXrJzMGtj/Pbu2lJPa4pNon3oIXFbai9dOHz27JabaSMyAqJUrJGXMWgTERVa7SZZbbR3UK31iKk3MYQAXUr6nzIf9S7H2+lNbFr8TCR7Bh/5D8jBoK+86tAyqkD7uxMnU9zrv6CrTk+yf2/CCZ/fI3/ic5/AGqq54ZmJHbWf6Cin7L8Nnx6tEi1bd56AkC2J9YdvpQH/ANofF2w+Hz22y3AylTvqD1HUdx1E1L5c5js4q012VtOCBfGgAuEAAhj8QIWAd/LHShH7Vmnwl9SxGuwHShfk3jxwuLV2I8N/JcBgLrORj0GVoM9AWoNE554X4tpoOuUjrvuP171ih37enat05lxRQEZz77bjQSABuND3rF+MpF5zr5jOoAJneY/PrQQq6kmumgtOWMH4uLsW+9wE+yec/gpra8VoNfnWP8kqfHLzGVY9yxA+QAzE+3rV9f4ybRdkuTAZo1ALAEgMDowMD110jegg8531d0tjKWUElh1DEZR9BPzFXPAmTJroGk5p0Vog6ERBgn9CgLx8zl2GZmM5RIHsOsDaOwo74Jg3Nk3CwZ2AK2wIVcuqqC2k+u0/WgLeEYksqq7DMqgDXoPKD5T1I9fep37RnMDKwjbQgjudCIrNOXOYbKljiGImR8LEwWkCVGnQfKi/C80cNyx4oUdQVuSffMutBGSw9vH4dLfltXc+mwR1RmJXfLIG1EvEHa3GYMVgyQczLtqVHxjfuaruGY3D3bymxiEbKjnLmMgmF2LCNGI0irl8ShcI28DUnfU9PlQVVu95wzEKg+F2IysIBkdI1ivcWlS6riDbYy8fdfUAj0J0PqR3q1S0sELlB2BjT9fPWqXm3ia4WwxUKb7EIgEAs9xsoJURmiZIO8dJoLZ8WgkrqWbKoG7kDX5AddhQp9o3DmOEe8TNxWtkxstsMVKL6AvmJ3JXoAALngOANm0nimWVAvsBv8ydT/sKkcSxVl7bWXZIug2wJEnMMug3nWgxe20Ixyroh7nXfXT9RU/B8ZusoVrz5AAIZ2VIHSJiPeK8cFoSjbjMp94yn8jVFhrpEake29AcW8cq+W1Zb/8AJkuZT/IAskesijj7NeJE3Gtk6Ms6oysSNRqzsYAJ0rKsClsnMwYt3PmJ95o25RxS271sgtow3002PptIoNoBp4piU+gUUtJSg0CGmk0401qCr4hbknb5iRt1HUUD8awNsEz5AetsujAeuVoiY0MjTUUd4vrQrxskLMaid9ent60Ge4LhNv8AaTh2cHOha2UhVuATqogqriDmUAqdxl2PhxTljww2UoxbLAYm0SCwOWDpOkzp023qHxPHvbZWQDyP4ls75X3bSIIOxH9aJuAYq/cwoOQNdA3V0W6UMshOaQBBgFtTQZ9//k3GIAVC7GFtrcVrjR2RSSev0q+4KcXgfFZMLdD3cmyXCqLmbKolWnUxq0wBrua0Dk7h4F97jtfZ0QALejyZpkrlRVaQsZhOxqy5r4g1i290jyJbcnWMzQFVfQksevT1oMd49x67iro8VAhthhABBBmCGzHp8qo7zRJ7U/CJDQR0b/8AkkfiKseFcJN65Blbaea68E5VB/AnYToN+lAV4zH30wdvDXgQ9u1DkDPlEzbz6GCoyg/6tdKCOJl2MtLHvoflI3/OjrG8Ww902ma0pN1ibjSVJzMQkDqWEnXuO9DGMSzN1AgkO+SGaAF8pOp69qAbrqm4+woVHUyGmfQiOn1qCaA05J4OWttegmTAWYBVeraTvOlJxu0c5WJnYBSPcep9KXifM13DpYsWMqZbQz+QEydANdNhO3Wm8O422MC4e4AL/wD8VwHKHYfdcbAnXUb7dpCtwWHyz+7QGZzvnBXplAVlI1nr32qdhbruHsWXzEgzlRzkzGCVIJIEe5B1BGteJtXTeew4HjDMSJ8rjLJjuf7V78OsXiuUFcPZBIJGjXCNCZ/rQVa8LuI2S5bKMIkaEazGqkjodJ6HtRPwzgKtAIzMw20ED85qwTh+GdALhe6qmQpJVc38RC6t7tNPxGENwHwQtjI0LkAUlhIIjqP7Ggfwjkq0fF8S2D5lC66iFJMHf7w+lJj+Rbk/usSUGkA5jEfMn8qtOW+MeGht33BcMSzBWKZYABLCQug1n366E9vFoVDBlIOxDAg+x0mgBOA8uYlC4vY24iD4QsSx7+eY9ory45ytiPEt3rd5rz22DLbuwAxUz5GELP8AlMe9aEgDCRHsYrksINCqiewAP4b0AVg8ab8HF4V0EwSXvFAe7WvLlH1/rRbwjhlm2ueylqDrKAa+s71YG0GETI9fpt+B+deNjBhI8MkbyCSRPqTrQY3ibYTE31P3b11f/wBjChG2aL+ZGUYzFZdvGef5p8//ALzQgtBcYI7UV8Dchh39PX9fjQfg3os4K5kGO1BvODuZkVu6g/UVIqt4DcnD2j/lH9qsgaBRSikpaBDTGp9Nagrr4kGhXmMgKZOnf301oqudaFebbOa2wzZWYeQn4c28HtO1BjvHD5nAWHBIyjXXpA9f61peD4euRPKSEUIrKzB1CjLKsPPGgMTQbyuhvYsK6k+Cl9ySfgi2UUEAeaLjWyNf99H4YhyggevlH4Mh1B9s1BEuWrtpCA7XbWXVkAGJtiZlQBF0AdIzRPxbVU8x8T8SxZsu63/EFwhlUhbiCCGJ+FXA0K99QANiHHsVUsIEbGTIPp+ulZtxbEtcxgNoQVDXbgB8sqpDXAOkggGN/nQDF+wbT6yTbYBvVdCD/qX+tGdjhrJg7OEtmLmOuN4pGpGGtEgwegIBIn+IivS9wYYpPESBdC5WUiQ6jUabyJ0ZQTqQQdCKvCX72GuECBcS2FMqSApGylgG6yRA1JoPTjHEFxHFMNbRAtuzetLlABkrcUsNNCAFCj2NRuI3VfigDWpBDKV7kq7A9iNqr1z4a6l9RLo4cFpKsZJObUb6zHeop45c/bBjSFNwPnywcm2XLEzGXTegh4xAouJEEXjodwIaNPlUKP11r3xl43He4d3ZmMbDMZgemteMUEniOL8W61yIBPlG+VQIUT10Ak9TJrwXTUaEagjQgjUEHoQfypy+kep/XvTxFBa8R4utxbV4ZkxaMM5gZHAHxzM5iYkerdKkPxOSr5wQybZvhM+ZSJ0GbbuINUfh5tqjssGgNsBx62qw9wDp5QTA3MiDP161Z4XmnDAGbgMzodJG2ummnoeu9Z3YidSB7/8ANXdviVlFg5WP8K2RqfV7jkAeuU+1AXNxFcSviJcsnLmNu2TbzSDoBbd1CtI+Jg/QiJp2FLElmLh2AkwtxWO0NlzJ0qmwWMZbOuDs3s5Z8hZDdRAgljbW2PLFsnTXXUCdUwVzCuQYGHYnLleHtZu2aM6n309qA04fx1hAcT0BXbt7qfeiTCcQVhv9dJ+Y0P4UM4Hl0AA6LOxVio36DUfQ1Kfgt2ZRzPWMqMfU6FWPyFAV2o3HXf1ry4hiVso91jCopdvZVk/lQviMfewuXPDzBMeVgCSNwSJAj3g0Bc0c93sXbNkILNsnzAMXZ4MgFoELIGgGsbxpQUj4gsWdviclj7sSx/E1Cx+E8Mp2e1auA/z21LfR84/00oOmhqbxd1fDYNwIZVvWG/7d3xVP/jiKCPgjRVwYyV66x+VCGFOtFXBr3wqoJYmPruaDdOVz/wBNb9AR9GNW4qk5SYeAFBnIxQ+4AJ/EmrxaBaWkNcKBaa1OppoK27pNDvNGHDWXAGaekwdp8poljpQhxrEMl1rI1QoXg65SI+E9BQBXIKBr+MJWX8NLfbRrozA++QT7Vo2Ew4AAnXaT+TAdaCfsqsgvinMyWtr8hnI+fmNHVyRcIBgEA9N5igGua8ettXMhX0BBMGAJGn3hrv6Gqj7N+EB0v4t1kXSbSTrNtf8AEPsWhf8Atmon2nEhh1IBUE75dNPxP1rR+C4JLWGs20EKttI76rJJ9SSSfegAcLhmtm5a/hMAkTtsY9oPzoc4+5GJDMCZAE+cht9s066jqRptNaLxrDqLuYDVlE/JoH4flQVzi2V7QG2RjHSQU1oInHnlIkEgCR2nXTvpQdiySS3ei61YBw9tjqXzM099dvpQhNB4FKTLXvFNig8gKeq16RXCgl2oUZj9P1+tar7gJJNXPD7Cu1tG1GW4x1OpRCwB9NOlRsSgg+8foUFYVpakuIpjLrQIl1pBk6TGvff61Iu4u41vwyZXNm1EmYjft/Ydq8Ipy0Htw3iN6ySbN17ZJk5WgE92U6N8waKcDz7jFGVstyNSYFt49CAVX/woSRAQTXuEgAjTWg1XDXbWOti9bus8KFZHyhrZBJ1CiCddxoRFZbxnBCzfu29wrmPY+YD5Ax8qNPsiuE4m8k+VrBYjpmW7bUH6O31ql+0jCqnELuWfOttz7lADHp5Z+ZoBWKsywOByx5kxRM9ku2BofdrOnsag5KmYCwGTEzP7uyLiwYGYYmxa1HXy3X+tBBsrrRDwXFZGU9R/Yg1Q2jrU/COZFBvn2cD/AKJZ3LuT7sZ/rRUKEfs1cnCx2On0ouoOFOpBXUH/2Q==', @@ -74,6 +77,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.ledzeppelin.com', }, { + id: 'Pink Floyd', name: 'Pink Floyd', image: 'https://upload.wikimedia.org/wikipedia/en/d/d6/Pink_Floyd_-_all_members.jpg', @@ -97,6 +101,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.pinkfloyd.com', }, { + id: 'The Rolling Stones', name: 'The Rolling Stones', image: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxMTEhUTExIWFhUXGBgXGBcYFx0YHhoYHRgXFxgdFxcdHSggGBolHhcWIjEhJSkrLi4uGB8zODMtNygtLisBCgoKDg0OGhAQGy0lICUtLS0tLS0tLS0tNS0tLy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLf/AABEIAL8BCAMBIgACEQEDEQH/xAAbAAACAwEBAQAAAAAAAAAAAAAEBQIDBgEAB//EAEMQAAIBAgQDBgMGBAUCBQUAAAECEQADBBIhMQVBUQYTImFxgTKRoSNCscHR8BRSYuEHcoKS8SRTFTNDstIWc5Oiw//EABoBAAIDAQEAAAAAAAAAAAAAAAIDAAEEBQb/xAAvEQACAgIBAwIEBQQDAAAAAAAAAQIRAyESBDFBIlETMmFxBRSBkbHB0eHwQmKh/9oADAMBAAIRAxEAPwDN8F7GqozYskSoZVTlqPj+e1aO5igAEUqAsADKBpoBVV6+S0TJKk/Dpz2PXSgsRYklo1yxofXlXNnmcjZGHuF2nm6AygTppGs/jXLzBEvWQd7V0AHmCjfnSjE44LoWhgQQJj9ipcZxYa2l5GHeIYj+ZGEEHqKLEnotrwaS9jCpytBDKCp1112nrpUQ5zaBgOupjSkOG4qLyZHEaGD57j0qdvieZe6RjnOk7/8AJ0rTGDboF6Qn7QIBeeDOv150lY1o73DV1LP1mevnSvH4S2oOViSACJ59Yrb2VGenYLhsQVMjpFWWVkgdaEtbxTq1w0qM+YGIO1VzS7lqEpK0LcTaIOoijuHYoW7YZVAuK4KtGscweoq3FOtx1zSBzqvH4busoncaj3qOW0icNN+Bl20wqzh8Qog4i0HYaRmAUGBy3rR9m8f3tgLmhlABB8tOlYVLgIhszQIHkJnToN6YcGx4s3QwBA2InlRNWqE1oY9ucH4bd0AfDlYjryn61h7tfUOK2hdtXEEHMDl33BkV8zxFhhqVIExtzpaeqGRKVorD0KtE2KKJbGto6VReFW2DpVd6m+AUX4a73im0xHVSeumlLbikEg7ipFoM0Vi1FxO8HxD4h+dKemGLLgqmKvaqiKplFls0ysNIpYlH4VqOILF2PXelZFPMetJnQztS59wolRrhq4WDUhYFLsYkyqydaINdW1pIGg5+sxr7H5VwtQtjYQ3TOg1yog612qcku4+OGb+Vn1dDopMRrrp5Ug47x/u0HdDUyuYkQD5Dek/F+0DGVUnQmByH6n6Uhe4WJLamsmLpm3chE5JKhlxC0fC5JbOAZP1prxvD23weHvI3iSLdwE6zGkD96GgsPdzYcSPgPTkfyqItd8MqkCNdRG1bIypbM8LnryDYMkxqd6Y4S/kYMpggyD6VT/Cd1IZgdjpRGDVOk+9PxtPaLmmtMbcSxFu6BdLAXGJDrEDbQge31oNbqRBAOnQV5sIh1Jj0il2ItsDCmRNXNJ6JCTiWXrxYgC2qwdxzkVYhuvCDUnz3q/hQP3kVtjqaeYVkS6GFhUIDAEsSM0aT06+1LSSLc23RS/C7YAFxwptmGZfEpOUtAI32I051dj+zs2+9tXluECWA1gbiDOp5nSB1Ok5Tity8JlwZMkAjnIB3mCBty08q7wniF22YVo011OvrVcnZNdg5EM76iaji38Rjy/Ciu5B8QgBoMA7TIMTrQmJSDWiIuWkDXcfdH/qN86XX8Qx3Ynnvzom/QN2hkSJ5WphwvCPduLbQSzEAClqV9Q4Hg7WAyXLsm+yjL90IzbAjeADqQPKOqpT4oYo2Hp2NsYayWuMLrD4yzFUXSSEC+InbUz6Cdc3xfhVgoLli6Mx1a0fuzsAenrzonG9qc7MFhVk+OCSeh1JgkzzPLUxSW3h2N/MskMCzdCJhiRsBJny0pKyzvYzhES3gQSDuKlg8TlbXY6H0q/iFhpDkEBxmUnmJKz8waDNoVptNC62WY+wEbQyp1BoQqavZ6iTVE4kVSi8OtULV9s0SKo5jBzpZcOtMsQJpXfSgmMiRJFWWSJ2oYOf+akt2OVIlFtG7DnxRabQVecxHImflIH4mg7lWrczMq7SQJOwkxJ+c0x4rwwW2IXvsqgS3dhuUkhgwAXlrEQd6uEGkL6jPGeXkuwnAjU16r8VhQsyWzCNGABMx0c8jXqtr3FfFfgrNdAp3iuy+IU+Be8UiQysNR6EivWOzGIPxKEG8swH4Gg+Pjq+SM/Bg3B7oDFJgOCp6a7H51PDXjZYwQTqNRPlRtjs/4ot4iy1wa5Ad/Q0v4th2S54lgnWOh2NSM4ylQCUoStE8Ri2uEFgNBGmlWWEoG29GWLla8aS0iTbbthF2QKEa8w50Y9zSg7rijkgERt49h5/uaNGOzq4EhspYD0gnX0B+VLHIo3AYG45DIhiQAzeFZOkZjoSZ+ESTOgNAsbk6SDslhcA7IhMHMYRZ1MgmSJ2gR70fc4Y4t3IScoJMfdgyd9fhnT5itFjOErZZEdQQoDITrlBGYToJIEbxFOuNXUFm/csqi95bZs4XKWYwozSIaWI16b9azyUlLiaYwi1Z85tAwJ/etWttV13COhAdSsiRI3B5qdmHmNK8V0rdFUjG9sV36CuIelNb4oC6elJmMigdE9Px/wCa2uL4r3uGs96+YnRm3KEGJb1kGB1rN4vg92zBv/ZEx4WnPHUoASo9YpzwO9YCXVyFityzLsAQbcqXhdQpBB6/CNSJBVODaDjNJ0aHCdn07nPeDu7eLwyW8oXWZ31FRxvAFto7v3oQoXXPACnVBmI2Mx4RJ0GlObeMCAg6soP+316ab+RpN2u44HtW7AaWMOw8Pw65JymCCTI/y+dIXqeja4JaMrj7i5VRSSqAhTqNSQSYPL5a0rd6IvN9NBQbGtCVIV1PBZKx9lr7/X/fBwmpKarJrqmrEF6VcgqhDRVsUSKqz13alN5efXb0p06yPWluMXWpIIXGo1NqiaSQ5FdR9QST0mdYrwUmvMuk1CNEWGpivV5n6aV6oFQTYxlxNEuOo6BiB8gYrl3Eu/xu7f5mJ/E1TR/C+EXLpEDKv8zTHt1qS4x2xKth/Z3xvbRbYzK2c3JM5eYI2pn23uKxBG+/tEGiLeJs4NMttgWM5jzJ18tKzOJ4q7OXLQxEDXYVlheTLz7JDJqo0C2zRVg1EYm42uaZ3NXWieZroRYlotY6ULcBphIih2WTqYHM9ABJPsJNG7ekSMS/A/YqHyg3HErInKs/d6O0aHcCI3Na7sqq3Li3GLMxkh7hDFVEZ4ERbX7sKCWZgNjFYji2Jm6xGgBIA/lVdIHlAplwbFOshWIJIVY5Rvz5ZgR5zXchwx43Apxsf9pOI5MVcuSVW4FClgcgdfjDKCzK3jU+UwRKmVXEePX8SoQ5AoXN4J8XiCSZAJ1YQIFHYrEEL3EHuTowYCGAzLM7r4iTMg6b6zSvCOllhIJ7t7ZCwG+zXvM4M6Ez3fkZO0VyJRcZPL+x0/yuTFhk5rSSr9WbfsdxX+Cs/wAPjjbe1cV2t2rkO1pwCQGQghUePYxpLGF/aq7gQB3CEXCTBQnJEKQWV5OoOhWAd4qj/EazatFQloLcyoCytoIUBbYta5IUA6nYjfllBi2uubjtmYkSfRQo0Hko+VM/CoLqMHxJ+TnqPGVojiDNG9nQLbPimH/kwUkTN1pyH/TBb1y0HdplxDCm3grQaFDsbkE+JyYAKrvkCwCx0zCBOsKjG3sVOWgG/wAUuYhwbzG42sFtY/cUBfwgMgTBO06eU1LhyeNj0Ee53/L50aLf79qU9go1/AMTgjZQ38UGuIB4bhyFSBqsEw8HSdZrHYuwLmKu3bbZlYk5uk6ZYO4EASNOm1LL25PUk/WnXBhAEckWfrM/Ks6xqLtGueZzSTAMRbOpIjU8+UkaeVCNVrYrOTz8/wB/6vpVT00XF2VmvVIWya9EUNjVjlV1ovw5otaX2m1p/wBn+FNiby2gYG7NEwo3PTmPnV2asLXFwfZgy0BilrdrZwVgtbyC9Hha5ccLrromoWfTXz5Uk43wMLb7+202y5XKRDLzGsnMOU6fpfKzGlsxjprVgsACalid/Kp3fhH75f3rPkbTSOjgwQ+HKcvC0V3BoPQH8aouLIorEb+w/OqbR3/ysPmpqsbtWL6yPDI4+yBGrlTG3nA19thXqYZr0aD/AMZRJ7rD216EiTpVGN41iH+JoBAMKI05UoNw1diCwySDqgIkcpMR5b0CwxW6FczjsTrua+5dk7mGs2baKMv2eaVUksQpLsSoObY6n9K+Fo06RvX1zhnGLy2U7xhba7DAagSdCFhWEmTKbieVH2G44pq7M326wFkXzdtEBWVGIUQDcLXATHIlVDbanN1rOExp+5pv2o4ybx7yQS7vB/pXwj6s8eUCkCGihb2NzTxxx8EvV5f9AwGajeEo58go9WP/AMQ1dtmq8WYQeZJ/AD5eL51t6aPLIvoZI6QPfxcXg6/dZXHrIfbpPKn3DnyvaKgkC8yqJjVRcKknqSfoKzmFXPdQciyz0ALAT6ain3B0DHDo40d7jFddf/MCg+R19prU53yZUPmVjfjNi+hzEBckEHMRo0tBOUCQdI31jWBVWCD4lyzKbrF8hMBfs1A+NgugIbVmGgUU+7V4q2uGcZSAzhI2kBcylIMFZiZ6AaVT/h3dJu20Ughg6lSYbKc0uvUrIEdLk/d0wdTPjBtHU6jM3iS/t9/5Be0VlsRjLeHZcjMWLgNm1VCJk8zlby0nnWOwtwwpHRZ+k0/7SY//AKvE3gTKq6CdCGKm3I10IZyfYUiUj7uo29q6nRRccaX/AFX7vZzZPbDrFnO6p/MQNNdzBgCmfaQozlst57oHiZyiqqjRVtWlJZEAEAN4uoBmruxrqMUpIJhLhGUEtmCMRlA1J00iD5jcbPtl/h5duYf+IDs95VBe2TnOUfEEfcsBy5xv15raSMeRtM+UYIwp89fnqPpFGYlgqMeiyPXYfWKL4FgA16GEgSxHLoPaSKH7V5VJAjV4AGmm506SBVSVQsqM05UZ43SBrH78qc8Fb7F25nNH/tH50jxbbCKZ4K6ww+UCTmI9pJNINDZTgkGQGNf3v5n8q61FWcA6KqlWzMfDzzbDwxuZIEfnVtrgmIuXGtLabMrFXnQKQdQxOgPlv0mqbSVsfgVuNeQGyYk0Xwbgd7EsBbU5Jhrh0VRz1OjH+ka1sMF2Vw9i33mJdWjfN4bYPSN31+fSl/Ge3MfZ4RcsQBcYRH/27ewHmem1ZPiubaxr9fB1OqnFqMU9Jf8ArG9jsph7S+IZiIksdyAy6HQiQ0kTEqDyqLcVw9q4LFtMxbQhdQCQqw25Ywig7/CJ2rA3r97EXBndrj6wWO3XfRR8hTHh2FVMtw3/ABgyq21zfDqCzkgKNOQOlFHppS+eTZil1EIPtRteGfZwVVZ0Bk8idYP719SaV9s7xOVSArHUhWzAgSFJ01Op18/lRwnjiG2JuKG5gnJB1JgkRloPGu+Jz4hYKLA1ZQQOXhJBJMToDvT4RfK2DKcVHRmr6eIVC/uP3z/SjLieIaVVxTA3LeVmWAdB8p8XTTl7bg0vJ86OpFpdMn7tfyDYjcegoXPXmbz9/wB7CoVIR4qjB1WZZcjkiA2Ferw2r1GZl2Clwo5t8qsu2AYAY6CNfn5da4t1SN6IvNPT28hEAbxp0qxdIf8AALtlUVABmjxSACTuSCTr5Aa6Vo8FhA0MWZbKXEZzOg1AkTpmg6/XSK+eWbbNoqk+g/E8q244kxs2rTQcigMBsxA+9G7fvc1LvQp41CXxG3RHjvAbN0zbAtZAU8LZ1JDHxEQILbmOdYkKQSDuDB9RvWvxnEgEheZA/T22pAmEFzEtbL5CzeE5S0sxBA01G++tGlRUcjnJv9ihDpRl2wDh1aBJLaxyGgk8hvRvGeAGy6LaLXg6kyEIMgw3g1gQVO533qg4fLZUZpJcyFZTAhdG10g8v7Gt/QNfE/QfJNKjOJKsx8tD55hz9dacYbiBV7VyDNsxvuCH05QfE306VYOFG6C4OVFILMxAgaDbdtWG3PnQzYIyFyuoBJLMCARAjKCAxnWNIMjbWmZZY4SlFNWSFKSb7DHtLxlb3dqilAsswJBl23Og8hTvs7xdLVpXj7REfI0CCzLcTxGZGXMToDMLqvMHhOVSFgMihmcQBnRQXfOefhBAkwOVKcLdN2zbs2Q/fbZSwKlswK5RplmNSx5Vzs0Fl0/ua3nWV8IKlvv7A2IAZVOYlmuuW13ACEfUsCec/O+5hTAYBvlptyIFNMVwu1bTvs7F0dw6BGKJqTla4wA7wZGmAdfYnlh2W6t62pTIwPe5oWQ7owBchTECVEnQ/Fz6X5+EZNRjf1MUnQHgsRcS4r2yy3FIKldweVfYeznbG42GVkwzPcU90QLiqmZQp3MsPCy7yZr5RgUe+6Wgc9y4fuSgLZi2Yu2wCyT4ObanSvomP7NLawjW7Fy6tyRccq7KGYKFbKAZjKAACSPDsK5OfqseN1N1YMsLyepLsI8bjpxV0d3ZwrAkXG+4RLFRIYhLhBXQblD5gr73Zu3i7qpbvlshY3XyBcoDQFXQatJAmQYLTuKL7JpYTEnvrQcIveOGQPLeNcpmdSSpExrOmmuut27F3D3bah8Ozs1xb9lUgTJUHKBAG2UmehE1WbJKUHHG9lYMUV6uOgGz2dw6J3a2EyRBlQ2b/Mx1J86+dLgblvFXsPYQtl7xQdws+K2WY7QG1k60fwfhf26m9fuOe8e2PtGEXbZkLcaZhkAYaiZjyOk41x2zYBGly4ToshQWmOerxpMSND6HmYcWXpsjXLk3/Jvm4ZsdtUkT4fwO2zW7txyWRgVYHdhlCZyfFPgzDkSxoPtR2l/hW7pUzXSAxZtFGadYBlj5GPWg8D2wVLTlkY3c3hUGEykAROvwlQdROvqRjuK425fuNcuGWbpoAOQUclA/ZMmt/wADlTyeBMJKEVDEU8T4ldvtnuuXPKdgP6VGij0oFevyqbVFqbSWkXdL6kht60wvrCe0fgKXMeXtTDFnw+4H50+Gos52d3OKADTjhVzImh1aZMD0IncHKfeTSnLtp++lOuE2hlZn+DU67GAc3sBE/wDNDGajtl5ccpxqIZwqyEtC/e0n4JE6bgjqZ5aaQdjNIuPcTa8/MIPhXptJMcz/AGqeO4ibrjeIMA8h1PKTp+xS++KzrHvnLv8AwdBZLjxXZA5rhrxqJNWUcr1RZ69UoBSVEmSKua2cobkDE+Zkj8D8qnYwdx2CKjMSYAAmT5fMfOjF4deYLbW2xbMZWD8X3RG2xO/nR0KKbTuLcqSoBIJBiZ/MGfmae8Nuwm0j5dDp+NVjABbSrcDyAQwEmMzGJ5D9aEGJy+HKQRuW0nptP6VdCZXLQysd3dxNlG2Z4bWNgSNj1jnzNazE8IT+ON7CLItkIzOZRHCrDKJLOYMa6Ag76RkcPwMXmX/qbFtiVgfasdRpGVGBPQA/pWt4Bg+4GW5eRiULKwDAMMxGXxAHMMv3gOUTSc03GLce5r6eCtKRscVwwMtm8FTMgacqSzfzAsxMzAOw1iZGlfIOO2Yu3O7T7JcsH4cxMCYUeew6edfTuI9prFvCd2rZnZu7G4gsDJk81UMY8h1rCX5ZlCiJgAbzv5+dN6aWRJtvx9vuVmcU6RruzuGt2cP3VxB3h7tLxMwzP4IgaZJOXXeTsIAy/HrVk3fCSIL2wNxNtipHXUaj1A56WYziuK+MIiozspJ2B5A+LMxkM20grI3qHch7lxngq5FwGMniCiT7meQmTQQg07DnKMo8UD2mJBFu3q66hgNnQiOQClWInprSfC8TXD4m3mIGUsDlGYKGOWSdiAJ1UGRtyq/jvGblh2bDTZzaAosRbA2B2YEzy0IaSTtlWVjJ1LHUkmST1JO9NkZk3Z9e4dxrBtbc3rlp2gHKCLhBAKAgqGJYA5RcMGIB51iePcS7y8dz8KJmLHwgBiQ5EvsonoBzpDwuywJJ5a++9HTz/t9Nh6VMcVFqXsMcnLuaj/Dq8q4szEm04X/NKkx/oFz619WVywBHyr4bwFj/ABNmHCfaL4iYgTrr5iR719m4XxFLltWQhhtKmRI0aD0ma8/+NY5KccnjsbelapxBMHg0GJ7q6p7thKkNlHPQneBty3XU5oGT43irlhbjQGFu81vVmtkMAACwXw3ATPhIBiYI3G944gWwbjwI1SZJZgJhANS0AxHODWE4pcxmLAtraUWnAYsTrGYghjPgbw7AMYYSRMVo6PO4YovhS8yel/lg5I8pP1X9O5hMfxW6S/j0uFGYQIJT4JEbid+fOajcxlvEX85t3mvXGDQjqdZB8EoWAHJTMARm50xxmHw+D8Fy2uIxB1YM027QOy/1tH7HOlO091QRZSzYB/7VsKfnrXQeR5PVCP2bdf5oyUo6k/0J8bwfc3Xtfy5eYMEqrFSRoSpJE84mlF0miQ5YSSSdZJ1J5yTzOtDXq0pPirBvegZmNQza1J6qNCXYTaWWUjXX1+VOMNw67fRzatlwvQgagTAkiT5DWlvBe8NwC0md9SFAn/j1Omtb3h2Im7ctBvHbPiCtoOba7GIjTmI30oZZJRVR7lRwRySTbqjJYPBLcdVkhFA7xx1LGAojeI9IbpFbfBYBMhBFthlKeCYyERDToTvtB1rPdqMSchAYK1xpaB8QgAkECJgLLedJcLdvOcvf3SVAyjOYgHaCY08/Khgnk2MnkjhTvZbxTgZw5kNmWSAdjOpgjYmAdRppy2pPdNN+LY5rrAsIyjKANhrJgcvx6k0puim062UmvAMxqoirWqpqCi3IjdWD7da9UsRbKmCNYB+YBH0NeqwDe8JbCqVLXh4CMoa4RoSQ2QBtNYaN/wAaYDAIMViLasfuOoDEfcBktMzOb6e1vZqxZt3GhF+FYlSTJA5mSOf0q8OqYq82YDwWhr/KEf8AetcaWZuUuLfby/sdJY6Suu/9wDiGCzuqIHNx9PiY6jUliW2G+um+ort3smpH2l4kR4WtiCSZ0GaZkQY684ElxhGtWLZysAsS5mSCxzAEiTAmff8App92bwwuxcM5RooPyPMzJBJPpWzpZTfoV/Vv/e4jOor1MR4Ts41yFSzbtyJkpM+ZLBh00j8zV2J7H4gFYuIx1ISFykCJzAW1EbDnuvtrcVh7puyjBRAhoJy8oC5gDILHUGIHU0WLXhAukXCDMlAPkOXOugsaRjeRnyzjFxmy9+pW6jTbIA7twQEhdIGksCJiI1kwtlRJHxArrJ05llPPzG40r6zxnCpftlLgkH5g8iDyYbzXyDtBh/4dij+JlOkeGRyJnaRBgTuKOqBbsqxllgZdiZ8QLGRyBJM7gQPlVnEcfaFle6vsb4OiKqukRqXLSOe2s+0hDxzEG4QM5ygDMsZfGJB2+LSNTVdhQBsNP0FC5eCkrGWH46yW7lt0W4WXwkwkNJIYhRFzX7p00HSkFm6DvvTS+nhWRzGVhsdfErf1Azr09QaUmyAY13oWWN+HgSAZgnX05xT3FYXAZT3dy+H+6XKFdxuAgJ0nY7xWfwY1AqR2ol2GRVk72HgE50MdDr8orSf4f9oRYdrdwgWm8WY/caQs+hkA+x61kLhq2yn2N1+htp/uLP8A/wAvrSs2GGaDhNaCU3B2j7jxK/buFGNwMNMqkiJH3kHMx+FZrtT2ot2Qbdhs93bqE828+i1l+wBJN204hL9oqhOmnwsU9S6CRoSI1jTOKCJBEEGCPMaGsS/C/leSTkl2X2Gfm9NRVAnEmJOYkkk6k8ydST5zVdtTRF8SSPT8qHUwYro0ZBhhwctVXhV+GOhqGIWi8BJC96ngcKbt1LYMF2Cz011MeQk+1cuCp8PxRtXUuDdGDQefUHpIke9KldOgl3Pq/D8DZwtrwAIvMndjsCx5knQeukV8+4hh2wd1mtz8UHMc28kBiIzAgEz5V9HVbWLw4PxW3HuD+TA/uKXXcIMSf4eT3KZRdcjW4wUBArNOw1Jjp1153T5VHk5d/Jrywuq/QyPDb9xhYuYllSzb+AlQblxQZyW15rpGYgCOZoK7iwb5uqgQFmIUbAGYHTpVfaLG95ibjgeFT3aDYBV8IA8pk+9ApePSK34lXq7GOdNOL7BbtMk7nWhbtWBqquU5lWDPVRq1xUCKAlkLvL0r1ducvSvVRZ9I4TehoQeMqiidiSM2Y9FUMJ/Uiq+K3Ft4y4rZmW4EnXlladDMaqI5gA76g08dweLsZrtlXSwsKrrdMxoBnIadWMCeQUbjXOX+JXbrG47sWA3LMTGukknQFzp/Uazx6bhK78b0aZZuWqNh/GIDc8BU5kj7sAKogrHpzERX0Dsef+ksnmUE+vP618jNxLq+BiWQA7eJrQnNKzq6jXciBE7Ct72F4qP4e2pZgrXntpK/EzMXAkE5NWO/tTOnxcJyfuBmnyijVccxDrYud2ftMukbjUSR0IWTWE7D47FG46/FpmZS5yfENc2pV4mNNdZ202tjEgmJJB8Y0A8M6CefLfWqeI3GttmRSQZJUQJjc+ZitdCEzq3MUTrasqvQ3GJPXUJA/tXz/t5YL3lF1MjFdMjhpiAZLKP6YOn6fR8LjFcSD5+28+lfMe1nE0v4hgpAAcAsdRAEctQBzAG6g8hVS8IiMxxcpP2awM7TJDEyF57ldDGgG/nVNs/Leu8StqhyhpIIaI11AOhBII5htjOlSsXcqloBjkRIOvMHcUsgItw7GfizR0MdPf6VS4+E+X4afpRBtlmJUEzsAJJ9I3OoNVusIP8AM3yhajKDsGdZqeIWPQ6g+X68varuF2SLffEjKGiOe0gxERy60fxPDIbdtldSLubKNAVdSAcyyYB0EzroaB5OM1Frv5+vsbceKLwSyKStP5fNe/7mcuNU8Jjrlok23KkiDGxG8EHQiQDrzAPKqrggkHccqqJphhex92UxDtjEcu5bLcliHuHS04XQAsYbKfKKu7SWl743UIKXibiwZgk+IHQQQZkVb2URrNt8SrWhcIy2kd7amJ8T5bhgrIKiASfF8OjUHj+I3cQ695llcwAUBR4nZzoNPiY/SnV6Ng/8tEreGUqCVExvr+tLuJYfKwIGhH12pyqbD2qnjljwIehj5ifyrMpbNMo+kC4ZcAdcwBEwZ6HStKcJbCv9khIAYSOQMN+M+xrLYO3LAdSB9YrS3L7C6ioJMiZjUE5SJOmoJ1OlTInQfTZVDurAHNv/ALFr/bNUME/7Vv8A2ivXpUkHlVDPSuHsbfzEWNuE8ffDnwomQ/EgBWY0BBkwfOP7E43tlmsMti33RJy6kSMwZiygaciJnQkdazmbXXUdKAShjgg3bRkz5m+xWN4HpFSTeoXSAxI21I9/Pn6+VeQ1oRlkX1FqKwqoT4yQP6QD+JFRvEchp5ijBAXWo5KMt2C85RtvVBoLV0O+FKMVOS0+z9wa5XqZcJwK3XYMSIA29wfy+depU80YumSONtWiWDu6fOrWtlw2VZIjNCCMo1ktGjSQJ3jTWlNtz1q9S/Ikct4pzBSfsOcHYSy6kvcS6JbLkETIyycw0OpkbaRtTjC8RCW5shlNprt5JUBBchWQATp8MZTM1jW7z+Y/7jXZcAnOfTMaFJ33D8dj6ta7WIhDNbYE27awxAGoLl5I+DaNZMHQVb/9UPcjIbdsoGYLdB9J0YAL6718jF1v5j86tVz1PzqTi5O02gFJLwfTOLcTfuvs7tlpCyEBBkkA6i4cv3jMiNuhrInBLqSQCQfCrLAbWNt1/X5p0arre4+fy1o4xpbdlOdvsDYtJunY68gI9v39KNt3MqkNa7wGNSSIg8iOeny9aBsakmvYxvF7D9fzqeCgjC4wWyJtAwP5iJPXQ6eggVDGXbbCVQJEaTMzO3U6fvSgwaItEhSRpqBsPM8/T8KhTbGXCwVBYgHQ6Osj4TGeNtJgGhjYU73AD7n0JgE/KfSieGAHrMHXQaRJlYg+tBsoo/BS7kMYASDmBMDNvv8ALpFD4fDZ7iJPxMFkcgTBOvQa+1XMBRfCbYktzAyjyLTt7Bh71SVui3pWH9ocWrlURQEtjKvkAIAB6aD5VXwi0Ac52B/5oJ9d/ei7mPAtLljMQdOmsEmrzyb7eSsSXkaDCkOTQfaKQqCBqzHzlQNuX3/w6Uk74jUk/sURefMlv0Y+5Yj8FFKhDdjZ5LVFvCVm4nWfw1rR3kncCPUVlbBiTVrXfWjlGwIT4odYjBNdVGWMwlHHmvwn0ykfKqTwJ+b2x6t/alN8+Genn5afKgHcdKU4P3GLKvYf4vg5RGY3bfhVjAaSYBMDqdKz1pajm8ql91vSrjGgZysqcADeTP0rtqqUoixuZPI1aBYbhADM6jLcOvUWnK//ALBaruj9iooPCfb8YqSmjKSK+8YfDInQxOvr1qplNEmq7209KEO3VN6Qy7NLDuP6V/P9a9XezgIusDvkn6j9a9XM6m/iM14vlM1YuUfaelCtRdq/XRsVhyKL2MiK4mh/GgzjfKojFE8qqx8s2Nl7W4MV1a53kivTRJmOaV6CLZr1y9FV54FCX2Jq3IBIMwtzlVd1pJPUk0NbJFWW2mqTIyYoiwdI8/y6/Oq0sFtAQTyExPpPOmNmwq4Yl7b53uwrZdAttSGAmJlrmsbd2vpRIFhnDhlRyInI+5ifCSY/D1IoF1B22onCXyisJEZGBJOokEDKp5zGup9KATEsohTFMe0ik6ONbrQ47hRw1vCq3x3M11x0ByhF9QBr5saV8I729etpmYguM0NrlGrx55QxojieLdnZndmaTowzRrPxNJgSRvVwpbJJ3oX4q5lB1A5Cfr9KFDA7H+1V4u6GM/ONvYVSL0bGKTKVsbF1DjQRiGAG/wC4oC5j3010GgHQSTp7mrOJ3yzfEWACgE+QH0Bml1DYLG2F4mPhfqDmHvuPenC2xGkmsjNaDgZd7ZADHKY0nY68vejjL3Kqwx15a6yPoI+v40I+H8j8qZXcIxtgd22bMTm12gaRGhkbz5VTh+Ggkm65tAbHuw8nplLL+dRsP4ckrpi42Y5VW48Jp1fwtnwzigR4RHdMDAAXSHbWBVvFr2FKFbCQ4y5WyuJMjNO8+U9POqRHFVoy6mrbGrCTAJirLOEc7Lz3On4xpUwvcurvlOVlbJM5oYGDGwMRQpkcHV0E2rYyP1An5EflNV2W8vkK7a7SZHlcLYynQo6s+ZSZILFs0HbSKs4XfS7cCIvduxyqC2YEnZQxAIPITM6a0y0ArI3GjcH5VSXB0E6+lau92Jx7CP4W9/8Aiaq7fYnGpr/C3uf/AKftzFC37B8ZCbs8x78zzRv/AHJXqMHDb1vEhSrq5ttoQJgFBtG23KvVizYnKVo0Y3oxIqxGqqpA1qMiZcgk1bmiqbVyK7cuzUoKy23LGBTG3wx+elL8DizbbMpIPlROK4zdfe45/wBRoklWyWcxS5TlqzC5R8e1LTcJOprly4aolhuOxCT4Nuh/WgQ9Vk14GqKsIV61XY60MXiLeHv3LgBBVGXUyJYKJ0UHxa8idtax4anfZ7Gi3dR+asDzEwZiRqKuPcvlqj6d2l7DrZw9w2cSMhIYrc0OVZMWyCFdyYjQEhSBOtYM8LH85/2f3r7HwHtXZxVt2vYeQBsWDSdAI8AKnz69KwHGeI4dLjNlIUk5RqZHKOnSneAYuN0xZ2f4cq3JZtAGMFd9MsDXeGJ/00o49iwzQp0Gg+ZOp5nX8Ko4rxkufAMg9daSNcPWgc9UE0rtFzETvrUe6HWqFbnXWuUsll6kVXdtgnSqWapLdqUS0Ra0aKwXEblkEJpJk7+nI0OLprszUInW0Gnj97+YfX9aqbi94/eoJhXKspyb7sL/APE7v/cP0/SotxG6fvn6fpQteqWUXnF3P52+dSvXGgAkk+etDTVl061CJkM1dDVGvVCWb7sJxPE4lzh+8zEIWUvcC6AgEZmMHfbfStbjMDetAlzbaNTF4NAG5LAECPWvlnZO3mxKAgEQxIIkHwkbe9P+1lu1bsgpZtqxcDMEAIEM2h9hV/cOMqQTf44TfS6tsHIjJBaJkgztIGm34V6sX/GHrXqlILmf/9k=', @@ -119,6 +124,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.rollingstones.com', }, { + id: 'Queen', name: 'Queen', image: 'https://i.scdn.co/image/af2b8e57f6d7b5d43a616bd1e27ba552cd8bfd42', description: @@ -134,6 +140,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.queenonline.com', }, { + id: 'The Who', name: 'The Who', image: 'https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcQoKRCDsHx1DyrnXBxOgpPUHZZIwebCq91Ge8oQylUJXlWzlOwtzQtepNBzfbT_497Ymm1sU1Oq2OV0qGmxa6igziOxHJX4Eo8oyk088RpF', @@ -155,6 +162,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.thewho.com', }, { + id: 'Black Sabbath', name: 'Black Sabbath', image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ8qFdD0l8GNdMw30NkViSV7BNfUab-7wZXWs1jC3qYIt6llh-WVpJ9MLmgv4QKo9Gq-FOOYJ3-aOr9jQjDhcToYWj2VO9LbBIh7MO6e34PNw', @@ -171,6 +179,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.blacksabbath.com', }, { + id: 'The Doors', name: 'The Doors', image: 'https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcQVPqssHHK-u-EaYkVo8YuiCBINkJYVsAkqIlhojm2swhdc1Xn9lWkzokObi1_lAcr1CZ_dEbKnBTD9hfkUcPJ9vrwg-ARlgurH2rA5I4iwXQ', @@ -188,6 +197,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.thedoors.com', }, { + id: 'Jimi Hendrix Experience', name: 'Jimi Hendrix Experience', image: 'https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcQkDuNxy0xKisGP4lUk7N0QqrOQpyHNy9U2lpQhKBH5BqnRn6drBm8HYWVwcFfea1brVTxs7jSSVH1hjyrwTlzGRZCgfC91C-YgEStSzuvMwg', @@ -205,6 +215,7 @@ export const data: RockBandDataProps[] = [ website: 'https://www.jimihendrix.com', }, { + id: 'Cream', name: 'Cream', image: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxMTEhUTExMWFhUXGBoYGRgYGB0XGhodGhcXFxcdGRgaHiggGB0lGxgYITEhJSkrLi4uGh8zODMtNygtLisBCgoKBQUFDgUFDisZExkrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrK//AABEIALcBFAMBIgACEQEDEQH/xAAcAAACAgMBAQAAAAAAAAAAAAAFBgQHAAIDAQj/xAA/EAABAgQEAwYDCAIBAgcBAAABAhEAAwQhBRIxQQZRYRMicYGRoQcysRRCUsHR4fDxI2IWU6IkMzRDRHKyF//EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwAlV4xnUSm3m0Da7EUgEG5Hh7mF2uxm7JBvYk6wOTVAuTb3JeAMzq9GUlS7GwEB62uCu7cjkLDzJ3iPPmJFspcX2AjlUZiB3SX0/m8AawbGJKRkVLSQOjnreDdQUyimdKPcUWKf1hew2SmnR2kwZpig6U/h5ExGm4kpZbMEOfSAaqnGJcwORtfn4QCk4gEqBJAJ1HK/9RlM5Uy2J1CxorxjtW4P94A9QesA0UdehUrurS/59YZcIrzkZmPTSKuoxlLeGjw6YPiSUsSG2LwDZRYktKmNwPKGCmr0qhORVJV8uvS39x2TPIuGcHzgHlJj2I1Gt0JPSO5MB7AzFsYlyR3jfYCMxLEMoISxV9PGEWtnqWs951M6l8nfSAnV3FExRZNuQH7XjlT1izeYoWDtqRyd4Bzq2XKLJLqOj8yHBJiCawiWpzc3J8NYA9WY8EghLliz8yenK0c1YqEh1Eg+Jt06mE04mElg5O5015fzSOdXWTVDMyUpGj+zB2/uAdRxRMT8pLdf3gvhfFhXqb8jb05xTypyz8xUU/6gv/POJuH4jKll0yVK2da+WjszeDmAupePlIfUfvE2nxkHlFXorMycyWlga5SWIF9FCJUviGUkOFt0LsfNoCxp2JKN0B9j/UBq6tn5TuL7NAPBsd7RJVpcszN5h4PU9ao6F+adx4c4ABPrZirAMrx1bl16Rzn4mUtmNyQD0P8AcHauUlQKmuDe1x1DQicQzFSyQrKS/e6/hPQ6QB//AJHmsSwFucdp2P8AduoDVyduUVlIxElTA/UfnHWZXldlajQtYwDyeIjnzBbhtIKYdjqlr12ZoqgVRK+6bDfaGnBavIkXuq4L9d4C2sIW6nd7fxoPIhBwfEcqQxDn7x/mkPFGXSLuYCUBGR6IyA+PRiBdhBjC6JayCx56c7RC4OwwzqlKWcC5i78K4fSlLsHf6QCdR4B3MygM2ge+XrGtTgqmD947bCLLGGjlbwj1dAlrjSApDEXSshYN7+HhEOmuoMwP3XZvAvDzxhhySoM1x/PeEcpax2MATRLUpRSUhChe3yny2g3h1aFDKb2b02eA+G1+ZJlqdx3knfqI6005KFlOxVmv1gN59NmXmQSPxAlm8+UF5FQMrZhb8Jf9jEZS0JfQuN9NN4DqqkhVgBfbT94BwpArLmChby9oKS6gljv1DGFg1qclrkjb9YlYbiCkkA94PaAtCgq2QATEPEeIUpdKWJvp0+g6wl1+OFMs5DdlEnYAa/31hGreJZjlKH6nc+PIdIB2xTiJrFQCXc9Tf+NtCzUY7MWciDlBNxqb8+sA5BUogrJMxVgCLIG5bnyiSqYJScsvvLIueT7DrAZWVOU8iLku5c6frHGdPWoJcs4MdZGHZiQW0zKJ2Yf2Yj4nND2By6DZ20YbCA3TMALAFh6nwjAlc0upVh6AX9TtHGikqX31gJCdBt19PrE6oKUnIksEgW5kh/eA0OQCwZPM6n8z9Ik0NOlQKilkjSzdXflA5EkrWl+g6XIAgjjWIFLpT3QLWGwtbrADsVrVqOUem9+mwt5xF7FYQUvYEC+gJd/pEnDmSRMNyo2HU8+Z0iUkBQPJ2bmdz5WgC/DeaUBd0bjn5cxrDpQpCklctRUBs7keI/OEbDVlMzIN7j1D225wYw2pXJmKUjQEFtilQdj4G3lAH5NW5UEuCLF/HrrCLxiDmJmF1E7eFriLBNOiekTZasr+j8i3X6QkcVYbNQFKUXBfKAXfSzne8Ah9oXygteJilrSDd1EX5geEQ1ylJAWNXLHw+t40plrzBg5J994DvTKWo5QCx1tDGiYJZCQrYWO0RKOpQGBUytLAfWNKlAzOltXfk3WAaqLEFuGudRsB67Q+8O8QKAaZc6vpFQ0lQc1j69fHrB2gxYhkJJcfNs+2vKAuVHEErctGRXdNjCcrZQW1L/tHsAk/CSQ9Qo/6xedMkBLRR3whm99Y3tF6UibB4D1IMaVEtTW1ialIjZSAYCr+N6dQUCE23OvWECdSla+4Rn3SSz72P5ReOP4N2oZ2in+KMCMqbADqrD5iUpUzKTqG28IhKrDnzaGCCMWUhGVaiWte9uTmB85KVkqTz/ggJ8mqz2OsbJlqfvDu+/7RGprDkfaJPapBIU/XrAczMIUQLX0P5QbpVZXubByoNZ7nzv7wuqSkHMlyH8Wjeoqj2YB0J7zWzAaOfGAl1tbnJN8pYeQ0/WIktGdQRJSMx3Op5noBHCjSZyr+XIf1DHRolUcszFEKWQyR01PqW9IDmvD+zGVJdTDMd9DYeNzHOXQ9jMJJCrP0FiSSfLXpHKXXqKHUe8pbq8wH9BaOGO4iCpSU/Kf+5mYeFoDtV16CjKPl15FXU/68h1iGkpSDMVfpoE+fP3iIqaAHd1FPu+p5ty0jgqYZhAJZKTpzO56k84Av9oBT2irJDMNi10j19hETDZKlrJPzKc6aPqY3z5hpYBkp2H6mJ0hIQQ5Ys55trARUz8qybMOnLSI2KLClBWymP89faMxioGgIcjTlENNS6HVZiwP1gJBUUpBF7FmO5LP0tEmVUgBIGrW8SdW8oHSKoKdO5unx6RIoLKc/sGgGCnAlhKn75UD4XEF8PqUqm9mVauB4Akp87n0hTm1Lrz3azHYc7xCRiikzlLBuF5h5O/8AOsBZ2F1/YrKVOxJH9/UGOuLze4op7yVbgsfMFwqFWRjaZlwNRpyYXHhHcYiycuo5A3G501gBfEElAloAIHfUSAlKctkhrWOhPnCzWU5llKg/MFm6xNr67/IVEnVgCHy32JjnVTjMsolntz9ICBLSp7wWo0FnUH5dYhrpTLPe0EETNeUCMxI2a0B0ky+85Ntn+jx0lYhkLgMef0jSZWyuzSnIQvUqBP0iGs9HHv5wB6kkKWnNn16tHsc6KvyoACj6CMgJHwhXLliZNmFgC1/aLFmcdSA4Cgw3e0Uhh8qZ9jJQ7FZdojqw6cQe6pwCo2sB1PPpAXPM+JNOC2d35CC+D8ZSpxYKHrHzgmSo6A89ILcPT5iJ0tnZSgm3UtAfRC8cQV5X2eEri6dKmKJUpiNImcRcMLlyFTpSzmSl2PQXilKvFJi1OVEwBqsmIch3jhQlKFH8JgGuoUdY2RUQDWlaD/Nf3iHiOVixci46iB0ueSloiT56nDwE/D68BTH5SbvEmslJKmQ7E2bTz/SBtPhkyYnPLGcD5kpupN906t1EMWEUKyHSgSkjVawSfU/SA1lICVJALZUuS25P9RErJ5nLB2+VI5AQQx0oQCkKuWJV0Y5f50iHgi0JSFki2nkXP5QG2LzwhfZJ0QkAn/fU/wD6gMqcTdt7eH9GOVZXFUxbksSfcvHKXO0G+0BNIJOUcmj2T3bk/rEZM0gO+8b0ySSnxgDmFd8uBYecT1U65s05Q+UeF+UdsEwwsTnygn84cMKn00hDWJ5loCt8cwlaSCxJe5ZmtAyokpTTkk9/MwHT7xizMZxymynMgN/NIXpdbhs0FClFJVa4sPPaAV8IwRc1IXte/hEqTQzJa2mS1EHkHB6iH/hnC5MpJyTgtB2sb8x5Qco6NOZSkkMn6wCDW4SpclMyWkswDMWfTSEOaopUQdQbx9NSJSFIuxilvidgYlT+1lhkqd+TwADCKllDlBmbUM5HL+4U6dZEF6Sozam7f1AR61ehs/Qfy8e0s5h3nJOh5RrOlEnxFvWO0qRoHHnAd5s0lICi42iZhGICWCDe9v0jmqjOU3fe0bYRhqllmJ8BARq6YVqUo78oyjngHvaQWxfCMic0Ly1DSAMrSglwsgGPIHInBt4yAtL4Z4QldAhwLkn3iRjvDc8LzyDlJFwdD4coj/CrFAKVCH6e5EPa57nlAV9K4bqG/wAqpbaMkFzBzD+GJacuZKQ12YawzLCUubPEGmrHU2rmAKYtLenUDplL+kUP/wATCyoJbciPoCvDylDofpFSYROH2lSQ+pgEmvwLs0uZaj1Fx7aQKp6HMe6CIveZhKVaecDZmDyU6pD+EBWNNg5QCoi0AK9LFotPH1y0y2AiqsTX3z4wBDApYzBRmZALsLk+ABt5wUrMTzqUVOUBXdD2ADDQed4V6MnMAPOOsyeQL/zlASa3EjNzHcq9gGHtEIzlANsIm8M0nbLUj73zN9Y1x2hMpYS2odoAdMuXjwRsmUoaiNkJfm8Bsg2hh4cw5SyFAWGkDsMoysgNvFtcLYQkBL2GmkApVtYZQZaSBC7V8RqLhILmL2qOHpE0ATJaVeIhfxbg6UhQmS5CVAfdTY+VoCk66qUq6kt0e+m4/OIqG5RYfEGEyJy85lTUqZiEpZ25hjfqGjrwzwUmbMKjKmBHJbAdG3gF/hGimrmASyT4GLRxaWulpMrFzdSuu94KcPcIy6UhSR3oNcW4aqoop8lFlqlqCfFnHvAUwr4iLR3UhwOcZM4qTXS1yZqAFEd1Q2O0JeK0C5KglTuUhWnP67jyjKCc12uICMzEjcWghIAa+kQAoqUTzL+8EKmeyAlhd3/L6+0B0RNzKH86QVpsORnuXhbppzG4eD+H1jkWvANuG4alSFvytEnA8MXKfKM3QEC3R7RArcQXKQlWUsQ1x6QJHFtQn5CAOTPAPVdgvbSy6QjxIcluloq7GaIyZhSfKCqOJqgllzG8Rb0jTiNKpktMwsoP8w6wC7n6R7GmSMgCfC2OKkJYHQn3ixsK4uzh1RSlPN2EPXA+E/aDZTAG8BaIxRcwMgOG1ELdb8R5NMUyEoUVILLJG738YcaJMillncgX3cxVvFNAiqnrmS0Mo3tv+sA4V/xQp+ytctpCpgONionBSUsoEk+B0hEnYVND9xTDW0PfwwTKlqUJllK3P0gH6XigGsDcTxYMb+cSMbwsqSVSSH1MV5iQmpPeJgOWO1xUVQl1CnVB6rXaF2ZMZUBMw+U2ZXIN6t+TxGqJo19OggpKrAJKgwc2SwZzufIfy8CpySBdiYDpgmJmnnonC+U3HMGyh6RalVw3LxFMuokzBla25G5SRzEUyowX4b4hqKReaSsgH5km6VeI59YBgx7AjImKcqb/AGFz6QDopZVMCQLkwYxvi9VSO8gAnzuwFoH8NozT5Y5qHteAsTAMDSlAIF936w34cgjKwsI8oqU9mGETqSjKS5Pj+cAXpakGJoIMBZCSNb9YJUyoDFUgVqPbWOolpQLMI2XMYPC/i2JjOkFQSh79fOAPy5gVpHdOkBFY5SywM05A6Zh9Iky8epzpNT6wFW8UYHMRNUJkjt0Z1FKg2ZIJJAO8V5xFh5p1BLNnct0BsI+jqkIWokKBsHa/80ijvi4B9uCRomUn3Kj+kAqUaXvyiZNpsyCp77DnE7BsMURobsPUtDQOFVEM9vCAryVTqfQwy8JUSu3Q4cOHhooOEkgZlAmDuCYOEmw3gD/FfDaZ9IlKAAUlJty0MIkvhNMlYK8x6ZXA8TFyyEPLSOgjqqmSRcCAqb/iNNPmZk5iSPkTYPzPIRLxPgXsaOakKe2YdGvFny5aRoAPKIGPqAkTH/CfpAfMpjIxZubbxkAuyjeLBwCpVSALSf8AzEuR4Qgy5Z1a3OH+skhciVMQzBLH+bQHSdxhVKCwmQVDmAbRAwzF8RzPKkqLl+6kGJmHSqoI/wALH94GVUvEUKzCXMSf9d/IQBfEOI8SWCn7EUAjvf4TfqXEA5OMTELBVJUC2gG72LR0VVYspwpNQedjG2E0dYpdpKgdyoX94BmwPjVQIStJCSWL2Ie0dsflCY5S2kBMdw6ZLAz6n2grS0p7EqUdt4BExNTEsekBQrvc4n4grvEdYidhcMYArhaAsh02HUt6aeUQ8TcqOw5RKkVRCcocP7RGqkn2gBZESKGSVKCRqbRqUwzfD6g7WqAIskPAR8UwUyUJUX73tZ4I8ASQupS+wLRY/F+AImSfldhy03tCHwfJMmqUDbx8YC6KFACRE9AG0BsPqwUi8FJU02Zm3/m0B2cuwTyvtHfM2unOPZXgGiQpDwEQoMw2cJ+sRMe4el1EkylOncKGoPPrHTE8YlyRdQHmIHHi2nSM02ehA6m/kNYCq8e+G8ySFzDMC23Yv5wCwCjnz1iQlRSAHcubPtFqYjx/QLHZd9SFd0zAGA6sbn0jlQYhhypwRTTAZiQ4swIGoB3IF2gCWFyBTUwQFE/iUrUneKXxuu+1VsyYbgqYf/VPdHqz+cWj8SMSEikmKGqmSm+6rW8A58opXC5zLBPOAsvh+nKlJUQGSzDqLOYdqOnKr7Qm4FNsG35Q9YWhw0BNVSJCLQJoFoSQtawkBZs+sMiZGYMYqrjnhiaZylJmHIS4F7c4C5qatlEAhaWOlxEiRNBfKoEdC8fOWHS5+dMoqWz5dTF7cF4MimpwlJJKu8okuSTAF52kJ3GdaRTzsp0QfpDlOTYxXfH8zJTTE7qtAUsEmMjuERkAQrqCXLp0y8veK1F/VvpAOTWlA7Mu3KLF4jwQqkpWLKSSfQl4Q8bw4gBY3gLH4QxqQmSHZ0/WDtXxPJ+UMTbyeKFkVakGxIjFYosFwoudYC8qnjCVLcaveJMniWnUAqzNc8niglYksi5JjpLxRaQADblAWZxfj0orAQoEa87wrY1xUVS+zQIWBPJudTGqZTmAlYbh8yepkpKlE7QRpEopzNRUSiZjd0HY84tX4ZYJKRTImBlKU5UdweXSDHE3CFPVsZiWUNFCx/eAoCUXUw3P5xPxaiUQWD5ACttnFreRPlFtYD8L6aWsLUpayDZyw/eGap4XlZ86EgBUvs1pYd4Bwk31IClC/OA+YFGLP+CuGuZ048wgeQc/WBmLfDmb2j04JSqbMQAoMU5FZb9LEv4Ra/BnDwo5CZQ1F1HmTrAFKimduW4bWK641wnslJmoDNYt1i1UyrQF4koBNllJG0BXWEY6zObi0NmHYySRsOTi8VTjFIqSop22idg2NZWCngLnpcQsCD5R4cWKiUuB53hRwbHkqDD00PW0MaKRM4XAt6uPCAhV/C9PUOZ+db6d4oI8MrPC9P4EwlbgTZwUP9ySOjHUw8DD5jd1rDkxH6whcV8JVSiZkqWSrcg38oAPXfClSnNLVoWNkrsfAlP6QoLoavD6hHaSyFJLpI7yTqLEW5xInUtdIUX7VHqH84YpOJLXTkVC3LedruTAAuPscXORTSj91HaEdVWT6JH/AHQrUyrx0xSr7aapexskckgMn2Eb0aQ+kA78IYkQoJMWlhM0BtYqXAZyEkKa4h5w/FXcPaAbarHQh7gAWeF/EMfpSHnTkjoLn0EC5OFJmzM09ZUl7JdvpePKnD8MlFzJY9XUPeAk0/EGFA9o68ydm16w04dx7QlLIm5T+FQI9IR5OO4clX/okE88oMPHDlbSTQCmnQnqUAeloBqoq5E5AUgggxTvxMxN5ikDQFv1i06dcuWshAYHYaRUHH8xE2rMuUB3dW3UdYAJhYlZP8iXL82sw/eMi7eCeHBJo5aJ0qUpd1EsFFlFw5I1vHsBFThHaJWldg5b6veEDiPAhJzIJzC5SdPERatNOJJBhe43w4zJRUgXTfx5wHzzXU2VZfR4jECGDGKcOS0CFoHKAgTCI8SmJhljlG6ZUByRLjqnu3jqmXHtNQTahWSTLUshnYWD2BUo2SOpIgLU+CtcVS50vUJIUOjxZE1BMVRwLKOGzFdqtKysBxKOYDRhmIAPzDp1i2J2KSkmShTpVPAyAAm5G/IdYDemtEHiLFZskyEyhLzTZmR5j5QMpVtuWtBaUZYmdmT32zNzHMDk5gdxVhqZ0q6M+QlWQ6KsXHjuOoEARRKGpYnm0a9neAfANb2lMEEKGRsuZnKDdBLE3A7p6pMMi0QGZbRHqKdwYmoTHikwFWcZ4JmJIDxWFbSZFaNH0NjVGFQgY9w2lb7QFbU09aC6SYb+HeNFSy0z1hcxTClyj0gQqbeAvjDuNJZ19Xhgk8QSVJfMI+cKacQQyiIdsK4LxGakLHdBDgrVlcG4tqIC1Z1VT1CSnuqG/SKW+LEyTJUKeTqrvL/1D2A5Al7dI6Y7VV1AQicCl/lIuFeChY/WEKumzJ8xcxRKlG5JgIcrWCqKcgAwMpqdS1hCElSibAaw4UVDMSOzmoKFgOx3EAIp55SYKUuJKcMq8Qq+jymIiC0BaGAI7axUz9YcKThWQR3zm8YpfCsbmSS6TDJI46nNAWhSYBSJLCUjxa8G0YNJbuhvCKY/5fN1zGC+GcW1MyySYB+rqaYcyKcAr0zEsEPuf2hJ/wD53WypnaDJNcuSlTG5vZTRY3CkkiTmV8yy5P8APODiRAQ6NU1SASgS9spOY2G5FoyJ4EZAKeG1ecJUxAWkEOGfy25x0xWR2iCgFs1oCY5xOino/tKxcMAgG+c6J6X9o84H4iVWSkzJiAklRZtxqk+n0gK242wE083K7hQcH6iEqfKaL8+JeFCdSlYHelHN5fe9r+UUtIwybUTOzkIVMUT90aPzOiR1MAGTLiZTUylqCEJUpR0ADk+QiyuH/hCsgKq5uQf9OWxPms2Hk8WVgnDlNSJaRKSl2dWqi3NRvAVbwx8KZk1l1ilSkf8ATS2c+JuEj1PhB/i6gpqSmTR08sIStQdjctclRJzLLPryiyMsIXxKpWVJnEsgKyENpmSpObUaPvs/OARqanM2clCZikhSkJY2IcgDzPd03i5ZtbJE1Ehjn0T3VMGD2WzAsNHG0VdhakyKwKKgpl5iLF0HL3ksByQphtpFiq4iRL7k0BBKVLlqBdEwAP3Dza7HrrACOJKycVidJkTCZLtODHdlJMpwpSbEP7wz4diCZ0tMxNgpKVB/9khX5t5QCqcRM+np6qVYqUApOZu6p0zGcOopDlmctChxbNrO0lmhkTkokgZTkUm4GV8pHecOCGe8A210gUdSmpDiTMOSYHOVBUfmyizFTEk6X5mG6WoEAguIR8K4iFdTrp5yTT1SkKGRaSl3Hzywsd4dLke8bcB41/8ADVKyTJIZd99Ab3Vm1fqOcA9iNFxpnjhMqdWBtAcK7rCxi6mL7QdqVKOunjCvjgUxvaATuIpgUDziu6uT3rQ14ytTnUwDkyCtWkAb+HuA9vUS83ygufK5j6JQLMNIrr4W0ASlc1uSQfc/l6xYIWWtAV78bMdTKpRTAArn3vfKlJDkdSbDzikAsiWWbvW1v6RYfxwlNUylkuVSyG5ZVbesJNTgcxFMKhQDKUAL3DuzpaAMfDaZSS5kxdRNEs5WSSPVrawa4u4rpJhlIp0qOU3mqDODsBrrCDJw6cqWqamWrs0h1KawGmsNHBnAy65ImdoEIz5TZyANTrYvYCA6VNJnBPOF2ppikxZGKcMLo2Tmzo0CyGPgRzhexSgcPAKiBHeU8brkMYkU0l4DtQ0ylG0WDgeHZAkAX3hdwqUEM9yYeaCYEyzMVokE+0B0oeNewrTImEdgwS/4F7nw5iLKlLBuI+WZ1Ypc1SzqpRV6mLm+H/GKFy5cib3VJAQlWx2D8uUBYjxkax5AfPPG2KSp1AQgkE1JN9CA+VudiDG3wvx9CP8AEtQSQoW5gnukeCrEcjAXjFXaCVKTlupSzlYXygOw08OkTOCeA500pnTGSM3+JKrdo3zKO4QPc+4XvUBCwQohlJLjpu/6wJwSpoZSuwkKloJZkggFXg91GEnG+IDKlfZUKzF3mrQfnUSxSlTfKlgH3ZrR3wOX9hNNOrEjtJ6iAVf+ylhtsouH5CAs949eNEKSwIIY3BdwX0bnrHQCAyAnF1B21NMTuzjy19oO5Y0WgHaAoudMQqnC1ghScwKmICW+ZGYfdUoWewdW7GJ9FP8At1IKW+cf5KZRUxP45b82Nn1tpaJfE9OKWtXLPdlVSQAcrhKg+UhLMSCwOtrb3FTqg9lLWJYSUn5kM0sgAyyAL5goKYfhcXIDBvwcaxK0qBqBSyipKjLSFEH5lAoUCSHLmxIdosTFlTKiSj7JPl9qRmGeyZiWILM5DP8Ad0Ni0cuEMaTMQtBZFR86wLAkgDtUjkbO2hvvHLDa5NTTzZapGdcmapMwIZMwXOWcgczckDcKZ9CHvEc6mT2AqUlKc4SibtLmAuO+C6C6WfTV44cS0cn7TKVLnS0VZy9zNlVOQ+njYsYTKmlVU/8AhlVCxlX3O2KgklTsm93N73D8nu3pwtCpkmZX0k4TacJCJiM01ByF0k9m6tbsp/HWAdKGqExL5SkiyknUFn2sbEaRtNQ8K+MzZlNVy6tz9lWgJWLpY97vKSdCxs4GhGrQ3SVhSQpOhAI216HSAEVyikQkY5XAlosTE5Kciio5QASTyADkxUNLiCKpauzCgzOFi7HQ2MAOr0BR0jlJw5g7Q4UfD6iXYARrWUffypGnpAZwridTTz0UypYVTzAFJUBdJUNSdxmBB5RZVLdA8IrHAElWLyWKyg0aZmUEhKSXBJDsXLXD6iG3hzG/80yjmghUtaky1nRaQXSH/Fl9WgFj444ZmppU8B+zmMfBX7gRWvEvE6KiVLky5SpSEsVjM4UQBbwF2i/eOMGNVRTpKSApQBBOgIUCPpHz9xZwpMo8hUUqSpxmT+IXNjpYwHPEOJVLkimlJySgGN7qALjN+kMXw444lUEqbLnS1rzKC05G1ZiC5HIR0+HvCUtaDU1DFLEoSbC2/XTSAODYBUYlUTOxQhOqlFsktPIWBYnl4wBvij4lTaoIQmUmUhK8x72YqZwASwYXjaXUiagKTofWFHHsJXS1EyRMYqQWcaGzgh9oe6niOlqZcmVTSJiFy5YCiyWYAA6F1X36wC7V0/KOCJagQwgpNN7xIpcgLmAIYNQrIClQQ4xxDsqXsx8y7eW8dqesGUFmAhN4mqzMXrYbQEXh+iE6oRLP3vXy6xcdJwnLk1EifLITLSwWFaWFj0JPPdopPDp65cxMxBZSSFA9RpFkzOPJ66YpMi0wFJmXyuR3sobXo9oCz5+PU6FFKpoBGo5RkUxSz1FO56xkBy4C4cNZMNbNAElDS5aHvMVYd4jRLm51P1ZeOcZ+yp7FNpi0s6bdnLe+U81nlpblGRkAK4WwiWhMysnB5FOSoJFytYygOCdE+6i8NOP05xCTQ5XTLnHOpRCSUvLJ8XYqZt2J0jIyAYcGwpVORJCiuUzpKiMySGGVmuLghuRgvLSrMrMzfdI16gjxjyMgO2WPCiMjICrfjfKKZUiaNQsj2zD6H1gJSkz6chBexVMb7ic2d0hYAV3nOr2trGRkBDwqonWnBf8AmlHMk6Ol150qGjES1H152tHhyQmZUKq5bhFVToPIhUtRSoePfF+kZGQEHE+FpyqCaifPM9ctRmyln5wlL91SjYkpcbXMMHBlaZ9JKWp8zFJe5OUlIJO5IAPnHkZAFK+gROQqXMSFJVqD6g+IN4HYGhaQuRMY9mQEEaqQQMpVyLghukZGQHnFtCZlHUpT8ypKwL2+UmKi+FuEzlVSlobsRaY5vldg3XMPQHnGRkBbWNzkU9PNmAfIhSgG5BxAXAuHZiM6ps0ThMAWhWXIRmBdOVywulrmMjIDhw/gBkVkuZMvM7Jco5VdwBIlGWkDe2cu2vlC/wAdSlSa3tEFjMSJqCCRdLi7cmfxjIyAeeG8cFVTBSwywEpmACzqsCOhPpFMfE/E5cyYinlgnsMwUo/eV0HRtYyMgBcniucJPZZU2RkQoWKbMot94kekS+CuMV4fLnJRKStcwghSiQAwa4HzD0j2MgAWN1kyomKnzGzKN2DD0jng8+ZLmpmSlZVpuOvMEbg8oyMgDCsYE6YSpCZebZD5Qd2G14J0WHFREZGQB+tpChIRuYVsVpCkttGRkAyUvC0nsaGao3mzMq03ZQdxcacofOK/s9PTdgadIlLSoJIA7q2sw1HjGRkAg4T2aUNMzu7hm0YNGRkZAf/Z', diff --git a/packages/ui/src/components/Table/stories/utils.tsx b/packages/ui/src/components/Table/stories/utils.tsx new file mode 100644 index 0000000000..45bc6341ad --- /dev/null +++ b/packages/ui/src/components/Table/stories/utils.tsx @@ -0,0 +1,54 @@ +/* eslint-disable no-restricted-syntax */ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta } from '@storybook/react-vite'; +import { MemoryRouter } from 'react-router-dom'; +import { CellText, type ColumnConfig } from '..'; + +// Selection demo data +export const selectionData = [ + { id: 1, name: 'Component Library', owner: 'Design System', type: 'library' }, + { id: 2, name: 'API Gateway', owner: 'Platform', type: 'service' }, + { id: 3, name: 'Documentation Site', owner: 'DevEx', type: 'website' }, +]; + +// Selection demo columns +export const selectionColumns: ColumnConfig<(typeof selectionData)[0]>[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { id: 'type', label: 'Type', cell: item => }, +]; + +// Shared meta config for Table stories +export const tableStoriesMeta = { + decorators: [ + (Story: () => JSX.Element) => ( + + + + ), + ], +} satisfies Partial; diff --git a/packages/ui/src/components/Table/types.ts b/packages/ui/src/components/Table/types.ts index 1ac477e3ff..5fec5355e5 100644 --- a/packages/ui/src/components/Table/types.ts +++ b/packages/ui/src/components/Table/types.ts @@ -16,9 +16,24 @@ import { CellProps as ReactAriaCellProps, - ColumnProps as AriaColumnProps, + ColumnProps as ReactAriaColumnProps, + TableProps as ReactAriaTableProps, } from 'react-aria-components'; +import type { ReactNode } from 'react'; +import type { SortDescriptor as ReactStatelySortDescriptor } from 'react-stately'; import type { TextColors } from '../../types'; +import { TablePaginationProps } from '../TablePagination'; + +/** + * @public + */ +export type SortDescriptor = ReactStatelySortDescriptor; + +/** @public */ +export interface SortState { + descriptor: SortDescriptor | null; + onSortChange: (descriptor: SortDescriptor) => void; +} /** @public */ export interface CellProps extends ReactAriaCellProps {} @@ -42,6 +57,76 @@ export interface CellProfileProps extends ReactAriaCellProps { } /** @public */ -export interface ColumnProps extends Omit { +export interface ColumnProps extends Omit { children?: React.ReactNode; } + +/** @public */ +export interface TableRootProps extends ReactAriaTableProps { + stale?: boolean; +} + +/** @public */ +export interface TableItem { + id: string | number; +} + +/** @public */ +export interface NoPagination { + type: 'none'; +} + +/** @public */ +export interface PagePagination extends TablePaginationProps { + type: 'page'; +} + +/** @public */ +export type TablePaginationType = NoPagination | PagePagination; + +/** @public */ +export interface ColumnConfig { + id: string; + label: string; + cell: (item: T) => ReactNode; + header?: () => ReactNode; + isSortable?: boolean; + isHidden?: boolean; + width?: number | string; + isRowHeader?: boolean; +} + +/** @public */ +export interface RowConfig { + getHref?: (item: T) => string | undefined; + onClick?: (item: T) => void; + getIsDisabled?: (item: T) => boolean; +} + +/** @public */ +export type RowRenderFn = (params: { + item: T; + index: number; +}) => ReactNode; + +/** @public */ +export interface TableSelection { + mode?: ReactAriaTableProps['selectionMode']; + behavior?: ReactAriaTableProps['selectionBehavior']; + selected?: ReactAriaTableProps['selectedKeys']; + onSelectionChange?: ReactAriaTableProps['onSelectionChange']; +} + +/** @public */ +export interface TableProps { + columnConfig: readonly ColumnConfig[]; + data: T[] | undefined; + loading?: boolean; + isStale?: boolean; + error?: Error; + pagination: TablePaginationType; + sort?: SortState; + rowConfig?: RowConfig | RowRenderFn; + selection?: TableSelection; + emptyState?: ReactNode; +} diff --git a/packages/ui/src/components/TablePagination/TablePagination.stories.tsx b/packages/ui/src/components/TablePagination/TablePagination.stories.tsx index a7d949a31d..dd76f7c55a 100644 --- a/packages/ui/src/components/TablePagination/TablePagination.stories.tsx +++ b/packages/ui/src/components/TablePagination/TablePagination.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,41 +13,96 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import preview from '../../../../../.storybook/preview'; + +import type { Meta, StoryObj } from '@storybook/react-vite'; import { TablePagination } from './TablePagination'; -const meta = preview.meta({ +const noop = () => {}; + +const meta = { title: 'Backstage UI/TablePagination', component: TablePagination, argTypes: { offset: { control: 'number' }, pageSize: { control: 'radio', options: [5, 10, 20, 30, 40, 50] }, - rowCount: { control: 'number' }, - showPageSizeOptions: { control: 'boolean', defaultValue: true }, - setOffset: { action: 'setOffset' }, - setPageSize: { action: 'setPageSize' }, + totalCount: { control: 'number' }, + hasNextPage: { control: 'boolean' }, + hasPreviousPage: { control: 'boolean' }, + showPageSizeOptions: { control: 'boolean' }, }, -}); +} satisfies Meta; -export const Default = meta.story({ +export default meta; +type Story = StoryObj; + +export const Default: Story = { args: { offset: 0, pageSize: 10, - rowCount: 100, + totalCount: 100, + hasNextPage: true, + hasPreviousPage: false, + onNextPage: noop, + onPreviousPage: noop, + onPageSizeChange: noop, + showPageSizeOptions: true, }, - render: args => { - // const [{}, updateArgs] = useArgs(); +}; - return ( - { - // updateArgs({ offset: value }); - // }} - // setPageSize={value => { - // updateArgs({ pageSize: value }); - // }} - /> - ); +export const FirstPage: Story = { + args: { + ...Default.args, }, -}); +}; + +export const LastPage: Story = { + args: { + ...Default.args, + offset: 90, + hasNextPage: false, + hasPreviousPage: true, + }, +}; + +export const MiddlePage: Story = { + args: { + ...Default.args, + offset: 40, + hasPreviousPage: true, + }, +}; + +export const WithoutPageSizeOptions: Story = { + args: { + ...Default.args, + showPageSizeOptions: false, + }, +}; + +export const CursorPagination: Story = { + args: { + ...Default.args, + offset: undefined, + }, +}; + +export const CustomLabel: Story = { + args: { + ...Default.args, + offset: 20, + hasPreviousPage: true, + getLabel: ({ offset, pageSize, totalCount }) => { + const page = Math.floor((offset ?? 0) / pageSize) + 1; + const totalPages = Math.ceil((totalCount ?? 0) / pageSize); + return `Page ${page} of ${totalPages}`; + }, + }, +}; + +export const EmptyState: Story = { + args: { + ...Default.args, + totalCount: 0, + hasNextPage: false, + }, +}; diff --git a/packages/ui/src/components/TablePagination/TablePagination.tsx b/packages/ui/src/components/TablePagination/TablePagination.tsx index fd891be073..de9234afa5 100644 --- a/packages/ui/src/components/TablePagination/TablePagination.tsx +++ b/packages/ui/src/components/TablePagination/TablePagination.tsx @@ -21,67 +21,47 @@ import { useStyles } from '../../hooks/useStyles'; import { TablePaginationDefinition } from './definition'; import styles from './TablePagination.module.css'; import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react'; +import { useId } from 'react'; /** * Pagination controls for Table components with page navigation and size selection. * * @public */ -export function TablePagination(props: TablePaginationProps) { - const { classNames, cleanedProps } = useStyles(TablePaginationDefinition, { - showPageSizeOptions: true, - ...props, - }); - const { - className, - offset, - pageSize, - rowCount, - onNextPage, - onPreviousPage, - onPageSizeChange, - setOffset, - setPageSize, - showPageSizeOptions, - ...rest - } = cleanedProps; +export function TablePagination({ + pageSize, + offset, + totalCount, + hasNextPage, + hasPreviousPage, + onNextPage, + onPreviousPage, + onPageSizeChange, + showPageSizeOptions = true, + getLabel, +}: TablePaginationProps) { + const { classNames } = useStyles(TablePaginationDefinition, {}); + const labelId = useId(); - const currentOffset = offset ?? 0; - const currentPageSize = pageSize ?? 10; + const hasItems = totalCount !== undefined && totalCount !== 0; - const fromCount = currentOffset + 1; - const toCount = Math.min(currentOffset + currentPageSize, rowCount ?? 0); - - const nextPage = () => { - const totalRows = rowCount ?? 0; - const nextOffset = currentOffset + currentPageSize; - - // Check if there are more items to navigate to - if (nextOffset < totalRows) { - onNextPage?.(); // Analytics tracking - setOffset?.(nextOffset); // Navigate to next page - } - }; - - const previousPage = () => { - // Check if we can go to previous page - if (currentOffset > 0) { - onPreviousPage?.(); // Analytics tracking - const prevOffset = Math.max(0, currentOffset - currentPageSize); - setOffset?.(prevOffset); // Navigate to previous page - } - }; + let label = `${totalCount} items`; + if (getLabel) { + label = getLabel({ pageSize, offset, totalCount }); + } else if (offset !== undefined) { + const fromCount = offset + 1; + const toCount = Math.min(offset + pageSize, totalCount ?? 0); + label = `${fromCount} - ${toCount} of ${totalCount}`; + } return ( -
+
{showPageSizeOptions && (
-
+ - ... + - ... + -
+ - + +const columns: ColumnConfig[] = [ + + { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, + + { id: 'type', label: 'Type', cell: item => }, + +]; + + + + + ``` + + Affected components: Table, TableRoot, TablePagination + +- 95246eb: **Breaking** Updating color tokens to match the new neutral style on different surfaces. + + ## Migration notes + + There's no direct replacement for the old tint tokens but you can use the new neutral set of color tokens on surface 0 or 1 as a replacement. + + - `--bui-bg-tint` can be replaced by `--bui-bg-neutral-on-surface-0` + - `--bui-bg-tint-hover` can be replaced by `--bui-bg-neutral-on-surface-0-hover` + - `--bui-bg-tint-pressed` can be replaced by `--bui-bg-neutral-on-surface-0-pressed` + - `--bui-bg-tint-disabled` can be replaced by `--bui-bg-neutral-on-surface-0-disabled` + +- ea0c6d8: Introduce new `ToggleButton` & `ToggleButtonGroup` components in Backstage UI + +### Patch Changes + +- 21c87cc: Fixes disabled state in primary and secondary buttons in Backstage UI. +- b3253b6: Fixed `Link` component causing hard page refreshes for internal routes. The component now properly uses React Router's navigation instead of full page reloads. + +## @backstage/plugin-auth-backend@0.26.0-next.0 + +### Minor Changes + +- 7ffc873: Fix `user_created_at` migration causing `SQLiteError` regarding use of non-constants for defaults + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + +## @backstage/plugin-events-backend-module-kafka@0.3.0-next.0 + +### Minor Changes + +- ef5bbd8: Add support for Kafka offset configuration (`fromBeginning`) and `autoCommit` + +## @backstage/plugin-home@0.9.0-next.2 + +### Minor Changes + +- e091a83: Widget configurations are now only saved to storage when the Save button is explicitly clicked. Added a Cancel button that allows users to discard unsaved changes and revert to the last saved state. + +## @backstage/backend-dynamic-feature-service@0.7.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-catalog-backend@3.3.1-next.1 + - @backstage/plugin-events-backend@0.5.10-next.0 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-search-backend-node@1.4.0 + +## @backstage/backend-test-utils@1.10.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/backend-app-api@1.4.0 + - @backstage/plugin-auth-node@0.6.10 + +## @backstage/create-app@0.7.8-next.3 + +### Patch Changes + +- Bumped create-app version. + +## @techdocs/cli@1.10.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## @backstage/plugin-api-docs@0.13.3-next.2 + +### Patch Changes + +- 0216090: Updated dependency `@types/swagger-ui-react` to `^5.0.0`. +- abeba2b: Fix types with new bumped dependency +- Updated dependencies + - @backstage/plugin-catalog@1.32.2-next.2 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.26.0-next.0 + - @backstage/plugin-auth-node@0.6.10 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.26.0-next.0 + - @backstage/plugin-auth-node@0.6.10 + +## @backstage/plugin-catalog@1.32.2-next.2 + +### Patch Changes + +- 7ca91e8: Header in EntityLayout should always be shown. + Monitoring the loading status caused flickering when the refresh() method of the Async Entity was invoked. + +## @backstage/plugin-catalog-backend-module-aws@0.4.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## @backstage/plugin-catalog-backend-module-github@0.12.1-next.1 + +### Patch Changes + +- cb4b907: Improved efficiency of `GithubOrgEntityProvider` membership event handling and edit team. The provider now fetches only the specific user's teams instead of all organization users when processing membership events, and uses `addEntitiesOperation` instead of `replaceEntitiesOperation` to avoid unnecessary entity deletions. + +## @backstage/plugin-catalog-backend-module-gitlab@0.7.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-catalog-backend@3.3.1-next.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.18-next.0 + +### Patch Changes + +- a307700: Fixed crash when `latest_report_status` is undefined + +## @backstage/plugin-devtools-backend@0.5.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-permission-node@0.10.7 + +## @backstage/plugin-mcp-actions-backend@0.1.7-next.1 + +### Patch Changes + +- 4d82a35: build(deps): bump `@modelcontextprotocol/sdk` from 1.24.3 to 1.25.2 +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## @backstage/plugin-scaffolder@1.35.1-next.2 + +### Patch Changes + +- 9d75495: Fixed bug in RepoUrlPickerComponent component where repository names were not being autocompleted. + +## @backstage/plugin-scaffolder-backend@3.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.9.4-next.1 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.4-next.1 + +### Patch Changes + +- bb7088b: Added options to set [workflow access level][access-level] for repositories to `github:repo:create` + + This is useful when creating repositories for GitHub Actions to manage access + to the workflows during creation. + + ```diff + - action: github:repo:create + id: create-repo + input: + repoUrl: github.com?owner=owner&repo=repo + visibility: private + + workflowAccess: organization + ``` + + [access-level]: https://docs.github.com/en/rest/actions/permissions?apiVersion=2022-11-28#set-the-level-of-access-for-workflows-outside-of-the-repository + +## @backstage/plugin-search-backend@2.0.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-search-backend-node@1.4.0 + +## @backstage/plugin-techdocs-backend@2.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + +## @backstage/plugin-user-settings-backend@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-auth-node@0.6.10 + +## example-app@0.2.117-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.1 + - @backstage/plugin-catalog@1.32.2-next.2 + - @backstage/plugin-home@0.9.0-next.2 + - @backstage/plugin-api-docs@0.13.3-next.2 + - @backstage/plugin-scaffolder@1.35.1-next.2 + - @backstage/cli@0.35.2-next.1 + +## example-app-next@0.0.31-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.1 + - @backstage/plugin-catalog@1.32.2-next.2 + - @backstage/plugin-home@0.9.0-next.2 + - @backstage/plugin-api-docs@0.13.3-next.2 + - @backstage/plugin-scaffolder@1.35.1-next.2 + - @backstage/cli@0.35.2-next.1 + +## example-backend@0.0.46-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-mcp-actions-backend@0.1.7-next.1 + - @backstage/plugin-auth-backend@0.26.0-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.4-next.1 + - @backstage/plugin-app-backend@0.5.9 + - @backstage/plugin-auth-backend-module-github-provider@0.4.0 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-catalog-backend@3.3.1-next.1 + - @backstage/plugin-devtools-backend@0.5.13-next.1 + - @backstage/plugin-events-backend@0.5.10-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.7 + - @backstage/plugin-kubernetes-backend@0.21.0 + - @backstage/plugin-notifications-backend@0.6.1 + - @backstage/plugin-permission-backend@0.7.7 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-proxy-backend@0.6.9 + - @backstage/plugin-scaffolder-backend@3.1.1-next.2 + - @backstage/plugin-search-backend@2.0.10-next.1 + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-signals-backend@0.3.11 + - @backstage/plugin-techdocs-backend@2.1.4-next.2 diff --git a/package.json b/package.json index 2cf4c6e0e3..bab0113d24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.47.0-next.2", + "version": "1.47.0-next.3", "backstage": { "cli": { "new": { diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index 320e3a5b29..6652b1423e 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,17 @@ # example-app-next +## 0.0.31-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.1 + - @backstage/plugin-catalog@1.32.2-next.2 + - @backstage/plugin-home@0.9.0-next.2 + - @backstage/plugin-api-docs@0.13.3-next.2 + - @backstage/plugin-scaffolder@1.35.1-next.2 + - @backstage/cli@0.35.2-next.1 + ## 0.0.31-next.2 ### Patch Changes diff --git a/packages/app-next/package.json b/packages/app-next/package.json index aca8ad1719..839e2acc74 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.31-next.2", + "version": "0.0.31-next.3", "backstage": { "role": "frontend" }, diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index a0f77df488..09d1483a7d 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,17 @@ # example-app +## 0.2.117-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.11.0-next.1 + - @backstage/plugin-catalog@1.32.2-next.2 + - @backstage/plugin-home@0.9.0-next.2 + - @backstage/plugin-api-docs@0.13.3-next.2 + - @backstage/plugin-scaffolder@1.35.1-next.2 + - @backstage/cli@0.35.2-next.1 + ## 0.2.117-next.2 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 5e54857fd8..ebe5cc22aa 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.117-next.2", + "version": "0.2.117-next.3", "backstage": { "role": "frontend" }, diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index dea4639dfd..50aa6abf09 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/backend-defaults +## 0.15.0-next.2 + +### Minor Changes + +- 6fc00e6: Added action filtering support with glob patterns and attribute constraints. + + The `ActionsService` now supports filtering actions based on configuration. This allows controlling which actions are exposed to consumers like the MCP backend. + + Configuration example: + + ```yaml + backend: + actions: + pluginSources: + - catalog + - scaffolder + filter: + include: + - id: 'catalog:*' + attributes: + destructive: false + - id: 'scaffolder:*' + exclude: + - id: '*:delete-*' + - attributes: + readOnly: false + ``` + + Filtering logic: + + - `include`: Rules for actions to include. Each rule can specify an `id` glob pattern and/or `attributes` constraints. An action must match at least one rule to be included. If no include rules are specified, all actions are included by default. + - `exclude`: Rules for actions to exclude. Takes precedence over include rules. + - Each rule combines `id` and `attributes` with AND logic (both must match if specified). + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@1.4.0 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + ## 0.14.1-next.1 ### Patch Changes diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 84df549e6a..c4f154dfb7 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-defaults", - "version": "0.14.1-next.1", + "version": "0.15.0-next.2", "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index b98fa78082..1d95c145be 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/backend-dynamic-feature-service +## 0.7.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-catalog-backend@3.3.1-next.1 + - @backstage/plugin-events-backend@0.5.10-next.0 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-search-backend-node@1.4.0 + ## 0.7.8-next.1 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 1cabe17923..68fa93a8ad 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-dynamic-feature-service", - "version": "0.7.8-next.1", + "version": "0.7.8-next.2", "description": "Backstage dynamic feature service", "backstage": { "role": "node-library" diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index ca456b8491..944d4419d7 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-test-utils +## 1.10.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/backend-app-api@1.4.0 + - @backstage/plugin-auth-node@0.6.10 + ## 1.10.3-next.0 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 1f6df8457d..c08deb6986 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-test-utils", - "version": "1.10.3-next.0", + "version": "1.10.3-next.1", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index b3eab5806b..f4df8ad888 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,33 @@ # example-backend +## 0.0.46-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-mcp-actions-backend@0.1.7-next.1 + - @backstage/plugin-auth-backend@0.26.0-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.9.4-next.1 + - @backstage/plugin-app-backend@0.5.9 + - @backstage/plugin-auth-backend-module-github-provider@0.4.0 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.3 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-catalog-backend@3.3.1-next.1 + - @backstage/plugin-devtools-backend@0.5.13-next.1 + - @backstage/plugin-events-backend@0.5.10-next.0 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.7 + - @backstage/plugin-kubernetes-backend@0.21.0 + - @backstage/plugin-notifications-backend@0.6.1 + - @backstage/plugin-permission-backend@0.7.7 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-proxy-backend@0.6.9 + - @backstage/plugin-scaffolder-backend@3.1.1-next.2 + - @backstage/plugin-search-backend@2.0.10-next.1 + - @backstage/plugin-search-backend-node@1.4.0 + - @backstage/plugin-signals-backend@0.3.11 + - @backstage/plugin-techdocs-backend@2.1.4-next.2 + ## 0.0.46-next.1 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 918d809d2e..14a4f157f9 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.46-next.1", + "version": "0.0.46-next.2", "backstage": { "role": "backend" }, diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index c1edc24f45..cc4da6383c 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/create-app +## 0.7.8-next.3 + +### Patch Changes + +- Bumped create-app version. + ## 0.7.8-next.2 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 61f3812864..827a6f5a59 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/create-app", - "version": "0.7.8-next.2", + "version": "0.7.8-next.3", "description": "A CLI that helps you create your own Backstage app", "backstage": { "role": "cli" diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index 3d47d9b0c5..7a5ae6ef1a 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,12 @@ # @techdocs/cli +## 1.10.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + ## 1.10.4-next.1 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index a1cb6fa4ec..db1f1a51b4 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,6 +1,6 @@ { "name": "@techdocs/cli", - "version": "1.10.4-next.1", + "version": "1.10.4-next.2", "description": "Utility CLI for managing TechDocs sites in Backstage.", "backstage": { "role": "cli" diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index c692731a10..8278565d1e 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -1,5 +1,68 @@ # @backstage/ui +## 0.11.0-next.1 + +### Minor Changes + +- 243e5e7: **BREAKING**: Redesigned Table component with new `useTable` hook API. + + - The `Table` component (React Aria wrapper) is renamed to `TableRoot` + - New high-level `Table` component that handles data display, pagination, sorting, and selection + - The `useTable` hook is completely redesigned with a new API supporting three pagination modes (complete, offset, cursor) + - New types: `ColumnConfig`, `TableProps`, `TableItem`, `UseTableOptions`, `UseTableResult` + + New features include unified pagination modes, debounced query changes, stale data preservation during reloads, and row selection with toggle/replace behaviors. + + **Migration guide:** + + 1. Update imports and use the new `useTable` hook: + + ```diff + -import { Table, useTable } from '@backstage/ui'; + -const { data, paginationProps } = useTable({ data: items, pagination: {...} }); + +import { Table, useTable, type ColumnConfig } from '@backstage/ui'; + +const { tableProps } = useTable({ + + mode: 'complete', + + getData: () => items, + +}); + ``` + + 2. Define columns and render with the new Table API: + + ```diff + -
+ - ... + - ... + -
+ - + +const columns: ColumnConfig[] = [ + + { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, + + { id: 'type', label: 'Type', cell: item => }, + +]; + + + + + ``` + + Affected components: Table, TableRoot, TablePagination + +- 95246eb: **Breaking** Updating color tokens to match the new neutral style on different surfaces. + + ## Migration notes + + There's no direct replacement for the old tint tokens but you can use the new neutral set of color tokens on surface 0 or 1 as a replacement. + + - `--bui-bg-tint` can be replaced by `--bui-bg-neutral-on-surface-0` + - `--bui-bg-tint-hover` can be replaced by `--bui-bg-neutral-on-surface-0-hover` + - `--bui-bg-tint-pressed` can be replaced by `--bui-bg-neutral-on-surface-0-pressed` + - `--bui-bg-tint-disabled` can be replaced by `--bui-bg-neutral-on-surface-0-disabled` + +- ea0c6d8: Introduce new `ToggleButton` & `ToggleButtonGroup` components in Backstage UI + +### Patch Changes + +- 21c87cc: Fixes disabled state in primary and secondary buttons in Backstage UI. +- b3253b6: Fixed `Link` component causing hard page refreshes for internal routes. The component now properly uses React Router's navigation instead of full page reloads. + ## 0.11.0-next.0 ### Minor Changes diff --git a/packages/ui/package.json b/packages/ui/package.json index 8a4a8b4573..7b4870fc72 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/ui", - "version": "0.11.0-next.0", + "version": "0.11.0-next.1", "backstage": { "role": "web-library" }, diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 1f42f0699a..65fae69fb6 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-api-docs +## 0.13.3-next.2 + +### Patch Changes + +- 0216090: Updated dependency `@types/swagger-ui-react` to `^5.0.0`. +- abeba2b: Fix types with new bumped dependency +- Updated dependencies + - @backstage/plugin-catalog@1.32.2-next.2 + ## 0.13.3-next.1 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index a788f38959..9a0fabf6b2 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.13.3-next.1", + "version": "0.13.3-next.2", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin", diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md index aff6333c3c..533bfcfe32 100644 --- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-aws-alb-provider +## 0.4.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.26.0-next.0 + - @backstage/plugin-auth-node@0.6.10 + ## 0.4.10 ### Patch Changes diff --git a/plugins/auth-backend-module-aws-alb-provider/package.json b/plugins/auth-backend-module-aws-alb-provider/package.json index 65ef39f0d5..b69ea9492e 100644 --- a/plugins/auth-backend-module-aws-alb-provider/package.json +++ b/plugins/auth-backend-module-aws-alb-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-aws-alb-provider", - "version": "0.4.10", + "version": "0.4.11-next.0", "description": "The aws-alb provider module for the Backstage auth backend.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md index 591f1139e9..fbe82d45a5 100644 --- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-oidc-provider +## 0.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.26.0-next.0 + - @backstage/plugin-auth-node@0.6.10 + ## 0.4.11-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json index 933182d80d..ed271f69b6 100644 --- a/plugins/auth-backend-module-oidc-provider/package.json +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oidc-provider", - "version": "0.4.11-next.0", + "version": "0.4.11-next.1", "description": "The oidc-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 7eaef6e066..0ef138ac1f 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-auth-backend +## 0.26.0-next.0 + +### Minor Changes + +- 7ffc873: Fix `user_created_at` migration causing `SQLiteError` regarding use of non-constants for defaults + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.6.10 + ## 0.25.7 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 9db17b1a93..10fa0d3f58 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.25.7", + "version": "0.26.0-next.0", "description": "A Backstage backend plugin that handles authentication", "backstage": { "role": "backend-plugin", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 03ec97083a..b86f2cde34 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.4.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + ## 0.4.19-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index acdec6390e..87b9e43c8e 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", - "version": "0.4.19-next.1", + "version": "0.4.19-next.2", "description": "A Backstage catalog backend module that helps integrate towards AWS", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index de1a33fc6d..5ddd7df766 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-catalog-backend-module-github +## 0.12.1-next.1 + +### Patch Changes + +- cb4b907: Improved efficiency of `GithubOrgEntityProvider` membership event handling and edit team. The provider now fetches only the specific user's teams instead of all organization users when processing membership events, and uses `addEntitiesOperation` instead of `replaceEntitiesOperation` to avoid unnecessary entity deletions. + ## 0.12.1-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index cb61e06e9e..93a81cf58b 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github", - "version": "0.12.1-next.0", + "version": "0.12.1-next.1", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index ffda3d9f15..d96730ea82 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.7.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + ## 0.7.7-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 5b4e9a0a16..527dfc56d6 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", - "version": "0.7.7-next.1", + "version": "0.7.7-next.2", "description": "A Backstage catalog backend module that helps integrate towards GitLab", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index f1e8c36d89..851b386cf6 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.7.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-catalog-backend@3.3.1-next.1 + ## 0.7.8-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 133cae849e..22b4b812b6 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-incremental-ingestion", - "version": "0.7.8-next.0", + "version": "0.7.8-next.1", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md index c270f87cb5..abeb7b22b6 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.2.18-next.0 + +### Patch Changes + +- a307700: Fixed crash when `latest_report_status` is undefined + ## 0.2.17 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index f2cd6115d1..aea732f363 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-puppetdb", - "version": "0.2.17", + "version": "0.2.18-next.0", "description": "A Backstage catalog backend module that helps integrate towards PuppetDB", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 7e781e1138..4068de5002 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog +## 1.32.2-next.2 + +### Patch Changes + +- 7ca91e8: Header in EntityLayout should always be shown. + Monitoring the loading status caused flickering when the refresh() method of the Async Entity was invoked. + ## 1.32.2-next.1 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 737922ba44..592605b95c 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.32.2-next.1", + "version": "1.32.2-next.2", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin", diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index 0dc5503884..04285d4fb1 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-devtools-backend +## 0.5.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-permission-node@0.10.7 + ## 0.5.13-next.0 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index 23c41c6d9d..6dd6f19d62 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.5.13-next.0", + "version": "0.5.13-next.1", "backstage": { "role": "backend-plugin", "pluginId": "devtools", diff --git a/plugins/events-backend-module-kafka/CHANGELOG.md b/plugins/events-backend-module-kafka/CHANGELOG.md index 3edfcf9487..a5225a2920 100644 --- a/plugins/events-backend-module-kafka/CHANGELOG.md +++ b/plugins/events-backend-module-kafka/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-events-backend-module-kafka +## 0.3.0-next.0 + +### Minor Changes + +- ef5bbd8: Add support for Kafka offset configuration (`fromBeginning`) and `autoCommit` + ## 0.2.0 ### Minor Changes diff --git a/plugins/events-backend-module-kafka/package.json b/plugins/events-backend-module-kafka/package.json index d6e7b1ac0e..22669eabe1 100644 --- a/plugins/events-backend-module-kafka/package.json +++ b/plugins/events-backend-module-kafka/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-kafka", - "version": "0.2.0", + "version": "0.3.0-next.0", "description": "The kafka backend module for the events plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 322614de2c..d79647f9c5 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-home +## 0.9.0-next.2 + +### Minor Changes + +- e091a83: Widget configurations are now only saved to storage when the Save button is explicitly clicked. Added a Cancel button that allows users to discard unsaved changes and revert to the last saved state. + ## 0.8.16-next.1 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index b2c5ef75ac..3c5e491b59 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.8.16-next.1", + "version": "0.9.0-next.2", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin", diff --git a/plugins/mcp-actions-backend/CHANGELOG.md b/plugins/mcp-actions-backend/CHANGELOG.md index c91b47584c..432201d943 100644 --- a/plugins/mcp-actions-backend/CHANGELOG.md +++ b/plugins/mcp-actions-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-mcp-actions-backend +## 0.1.7-next.1 + +### Patch Changes + +- 4d82a35: build(deps): bump `@modelcontextprotocol/sdk` from 1.24.3 to 1.25.2 +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + ## 0.1.7-next.0 ### Patch Changes diff --git a/plugins/mcp-actions-backend/package.json b/plugins/mcp-actions-backend/package.json index 34ed2a6210..5ae1027eed 100644 --- a/plugins/mcp-actions-backend/package.json +++ b/plugins/mcp-actions-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-mcp-actions-backend", - "version": "0.1.7-next.0", + "version": "0.1.7-next.1", "backstage": { "role": "backend-plugin", "pluginId": "mcp-actions", diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index 4c47975b75..2747cd21c0 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.3.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + ## 0.3.19-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 5f16124d88..1f3841cc05 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.3.19-next.1", + "version": "0.3.19-next.2", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index 7cd9b4b101..b256bbed1e 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.9.4-next.1 + +### Patch Changes + +- bb7088b: Added options to set [workflow access level][access-level] for repositories to `github:repo:create` + + This is useful when creating repositories for GitHub Actions to manage access + to the workflows during creation. + + ```diff + - action: github:repo:create + id: create-repo + input: + repoUrl: github.com?owner=owner&repo=repo + visibility: private + + workflowAccess: organization + ``` + + [access-level]: https://docs.github.com/en/rest/actions/permissions?apiVersion=2022-11-28#set-the-level-of-access-for-workflows-outside-of-the-repository + ## 0.9.4-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index 789b77e278..c4e76f56ba 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.9.4-next.0", + "version": "0.9.4-next.1", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 0509a055b1..2c2cc0ab2b 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend +## 3.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.9.4-next.1 + - @backstage/plugin-auth-node@0.6.10 + - @backstage/plugin-permission-node@0.10.7 + ## 3.1.1-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 970c7b00e3..8b8093f380 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "3.1.1-next.1", + "version": "3.1.1-next.2", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index d021f9d657..7935cc6636 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-scaffolder +## 1.35.1-next.2 + +### Patch Changes + +- 9d75495: Fixed bug in RepoUrlPickerComponent component where repository names were not being autocompleted. + ## 1.35.1-next.1 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index df9547b3c8..b6dad69fb9 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.35.1-next.1", + "version": "1.35.1-next.2", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin", diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index 899359c1d2..32e5bda05c 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-search-backend +## 2.0.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-permission-node@0.10.7 + - @backstage/plugin-search-backend-node@1.4.0 + ## 2.0.10-next.0 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 541a77e158..ab9090f773 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "2.0.10-next.0", + "version": "2.0.10-next.1", "description": "The Backstage backend plugin that provides your backstage app with search", "backstage": { "role": "backend-plugin", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index f51e4c5526..9b528eda64 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-techdocs-backend +## 2.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + ## 2.1.4-next.1 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index ddbe467f3b..1330778010 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "2.1.4-next.1", + "version": "2.1.4-next.2", "description": "The Backstage backend plugin that renders technical documentation for your components", "backstage": { "role": "backend-plugin", diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index 166490f4e9..acb80d4fe4 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-user-settings-backend +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.15.0-next.2 + - @backstage/plugin-auth-node@0.6.10 + ## 0.3.10-next.0 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 53689197fc..cd2dcfe46b 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings-backend", - "version": "0.3.10-next.0", + "version": "0.3.10-next.1", "description": "The Backstage backend plugin to manage user settings", "backstage": { "role": "backend-plugin", From 498f9ddca5eb2c59cb63a202cfea04295000b0e0 Mon Sep 17 00:00:00 2001 From: Gabriel Dugny Date: Tue, 13 Jan 2026 18:12:04 +0100 Subject: [PATCH 127/202] fix(repo-tools): Invalid --help for backstage-repo-tools package schema openapi generate (#32305) Signed-off-by: Gabriel Dugny --- .changeset/slow-bobcats-lose.md | 5 +++++ packages/repo-tools/cli-report.md | 4 ++++ packages/repo-tools/src/commands/index.ts | 22 ++++++++++++---------- 3 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 .changeset/slow-bobcats-lose.md diff --git a/.changeset/slow-bobcats-lose.md b/.changeset/slow-bobcats-lose.md new file mode 100644 index 0000000000..d837bc32cf --- /dev/null +++ b/.changeset/slow-bobcats-lose.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Fixed help text for `backstage-repo-tools package schema openapi generate` command. diff --git a/packages/repo-tools/cli-report.md b/packages/repo-tools/cli-report.md index acde92d742..6b6f2ae710 100644 --- a/packages/repo-tools/cli-report.md +++ b/packages/repo-tools/cli-report.md @@ -160,7 +160,11 @@ Options: Usage: backstage-repo-tools package schema openapi generate [options] Options: + --client-additional-properties [properties] --client-package [package] + --server + --server-additional-properties [properties] + --watch -h, --help ``` diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 01288056c7..dab7d6df0d 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -44,24 +44,26 @@ function registerPackageCommand(program: Command) { openApiCommand .command('generate') + .description( + 'Command to generate a client and/or a server stub from an OpenAPI spec.', + ) .option( '--client-package [package]', 'Top-level path to where the client should be generated, ie packages/catalog-client.', ) - .option('--server') - .description( - 'Command to generate a client and/or a server stub from an OpenAPI spec.', - ) - .option('--client-additional-properties [properties]') - .description( + .option('--server', 'Generate server stub') + .option( + '--client-additional-properties [properties]', 'Additional properties that can be passed to @openapitools/openapi-generator-cli', ) - .option('--server-additional-properties [properties]') - .description( + .option( + '--server-additional-properties [properties]', 'Additional properties that can be passed to @openapitools/openapi-generator-cli', ) - .option('--watch') - .description('Watch the OpenAPI spec for changes and regenerate on save.') + .option( + '--watch', + 'Watch the OpenAPI spec for changes and regenerate on save.', + ) .action(lazy(() => import('./package/schema/openapi/generate'), 'command')); openApiCommand From 133d5c61c6828f3df6488a62955f16fbe4662085 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 14 Jan 2026 11:11:09 +0100 Subject: [PATCH 128/202] Add new Popover component Signed-off-by: Charles de Dreuille --- .changeset/eager-spies-wish.md | 5 + docs-ui/public/theme-backstage.css | 2 +- packages/ui/css/styles.css | 2 + .../ui/src/components/Menu/Menu.module.css | 1 + .../src/components/Popover/Popover.module.css | 123 +++++++- .../components/Popover/Popover.stories.tsx | 276 ++++++++++++++++++ .../ui/src/components/Popover/Popover.tsx | 101 +++++++ .../ui/src/components/Popover/definition.ts | 4 +- packages/ui/src/components/Popover/index.ts | 4 +- packages/ui/src/components/Popover/types.ts | 38 +++ .../src/components/Tooltip/Tooltip.module.css | 3 +- packages/ui/src/css/tokens.css | 7 + packages/ui/src/index.ts | 1 + 13 files changed, 556 insertions(+), 11 deletions(-) create mode 100644 .changeset/eager-spies-wish.md create mode 100644 packages/ui/src/components/Popover/Popover.stories.tsx create mode 100644 packages/ui/src/components/Popover/Popover.tsx create mode 100644 packages/ui/src/components/Popover/types.ts diff --git a/.changeset/eager-spies-wish.md b/.changeset/eager-spies-wish.md new file mode 100644 index 0000000000..c4aa3be26f --- /dev/null +++ b/.changeset/eager-spies-wish.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Added new Popover component for Backstage UI with automatic overflow handling, and full placement support. Also introduced `--bui-shadow-overlay` token for consistent elevation styling across overlay components (Popover, Tooltip, Menu). diff --git a/docs-ui/public/theme-backstage.css b/docs-ui/public/theme-backstage.css index 59b2e9b5c9..34af02e97b 100644 --- a/docs-ui/public/theme-backstage.css +++ b/docs-ui/public/theme-backstage.css @@ -1 +1 @@ -@layer tokens{:root{--bui-font-regular:system-ui;--bui-font-monospace:ui-monospace,"Menlo","Monaco","Consolas","Liberation Mono","Courier New",monospace;--bui-font-weight-regular:400;--bui-font-weight-bold:600;--bui-font-size-1:.625rem;--bui-font-size-2:.75rem;--bui-font-size-3:.875rem;--bui-font-size-4:1rem;--bui-font-size-5:1.25rem;--bui-font-size-6:1.5rem;--bui-font-size-7:2rem;--bui-font-size-8:3rem;--bui-font-size-9:4rem;--bui-font-size-10:5.75rem;--bui-space:.25rem;--bui-space-0_5:calc(var(--bui-space)*.5);--bui-space-1:var(--bui-space);--bui-space-1_5:calc(var(--bui-space)*1.5);--bui-space-2:calc(var(--bui-space)*2);--bui-space-3:calc(var(--bui-space)*3);--bui-space-4:calc(var(--bui-space)*4);--bui-space-5:calc(var(--bui-space)*5);--bui-space-6:calc(var(--bui-space)*6);--bui-space-7:calc(var(--bui-space)*7);--bui-space-8:calc(var(--bui-space)*8);--bui-space-9:calc(var(--bui-space)*9);--bui-space-10:calc(var(--bui-space)*10);--bui-space-11:calc(var(--bui-space)*11);--bui-space-12:calc(var(--bui-space)*12);--bui-space-13:calc(var(--bui-space)*13);--bui-space-14:calc(var(--bui-space)*14);--bui-radius-1:calc(.125rem);--bui-radius-2:calc(.25rem);--bui-radius-3:calc(.5rem);--bui-radius-4:calc(.75rem);--bui-radius-5:calc(1rem);--bui-radius-6:calc(1.25rem);--bui-radius-full:9999px;--bui-black:#000;--bui-white:#fff;--bui-gray-1:#f8f8f8;--bui-gray-2:#ececec;--bui-gray-3:#d9d9d9;--bui-gray-4:#c1c1c1;--bui-gray-5:#9e9e9e;--bui-gray-6:#8c8c8c;--bui-gray-7:#757575;--bui-gray-8:#595959;--bui-bg-surface-0:var(--bui-gray-1);--bui-bg-surface-1:var(--bui-white);--bui-bg-surface-2:var(--bui-gray-1);--bui-bg-surface-3:var(--bui-gray-2);--bui-bg-solid:#1f5493;--bui-bg-solid-hover:#163a66;--bui-bg-solid-pressed:#0f2b4e;--bui-bg-solid-disabled:#163a66;--bui-bg-neutral-on-surface-0:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-0-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-0-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-0-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-1:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-1-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-1-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-1-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-2:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-2-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-2-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-2-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-3:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-3-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-3-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-3-disabled:oklch(0% 0 0/.06);--bui-bg-danger:#feebe7;--bui-bg-warning:#fff2b2;--bui-bg-success:#e6f6eb;--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#1f5493;--bui-fg-link-hover:#1f2d5c;--bui-fg-disabled:#9e9e9e;--bui-fg-solid:var(--bui-white);--bui-fg-solid-disabled:#98a8bc;--bui-fg-tint:#1f5493;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#0000001a;--bui-border-hover:#0003;--bui-border-pressed:#0006;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#a0a0a03b;--bui-scrollbar-thumb:#a0a0a0;--bui-animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite}[data-theme-mode=dark]{--bui-gray-1:#191919;--bui-gray-2:#242424;--bui-gray-3:#373737;--bui-gray-4:#464646;--bui-gray-5:#575757;--bui-gray-6:#7b7b7b;--bui-gray-7:#9e9e9e;--bui-gray-8:#b4b4b4;--bui-bg-surface-0:#333;--bui-bg-surface-1:var(--bui-gray-1);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-surface-3:var(--bui-gray-3);--bui-bg-solid:#9cc9ff;--bui-bg-solid-hover:#83b9fd;--bui-bg-solid-pressed:#83b9fd;--bui-bg-solid-disabled:#1b3d68;--bui-bg-neutral-on-surface-0:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-0-hover:oklch(100% 0 0/.14);--bui-bg-neutral-on-surface-0-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-0-disabled:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-1:oklch(100% 0 0/.06);--bui-bg-neutral-on-surface-1-hover:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-1-pressed:oklch(100% 0 0/.16);--bui-bg-neutral-on-surface-1-disabled:oklch(100% 0 0/.06);--bui-bg-neutral-on-surface-2:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-2-hover:oklch(100% 0 0/.12);--bui-bg-neutral-on-surface-2-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-2-disabled:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-3:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-3-hover:oklch(100% 0 0/.12);--bui-bg-neutral-on-surface-3-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-3-disabled:oklch(100% 0 0/.08);--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#9cc9ff;--bui-fg-link-hover:#7eb5f7;--bui-fg-disabled:var(--bui-gray-7);--bui-fg-solid:#101821;--bui-fg-solid-disabled:#6191cc;--bui-fg-tint:#9cc9ff;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#ffffff1f;--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#3636363a;--bui-scrollbar-thumb:#575757}}@layer base{*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:100%;tab-size:4;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;line-height:1.15}body{margin:0}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{border-color:currentColor}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}:where(a){color:inherit;text-decoration:none}@keyframes pulse{50%{opacity:.5}}body{background-color:var(--bui-bg-surface-0);color:var(--bui-fg-primary);font-family:var(--bui-font-regular);font-weight:var(--bui-font-weight-regular);font-size:var(--bui-font-size-3);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}[data-theme-mode=dark]{color-scheme:dark}[data-theme-mode=light]{color-scheme:light}}@layer components;@layer utilities{.bui-p{padding:var(--p)}.bui-p-0\.5{padding:var(--bui-space-0_5)}.bui-p-1{padding:var(--bui-space-1)}.bui-p-1\.5{padding:var(--bui-space-1_5)}.bui-p-2{padding:var(--bui-space-2)}.bui-p-3{padding:var(--bui-space-3)}.bui-p-4{padding:var(--bui-space-4)}.bui-p-5{padding:var(--bui-space-5)}.bui-p-6{padding:var(--bui-space-6)}.bui-p-7{padding:var(--bui-space-7)}.bui-p-8{padding:var(--bui-space-8)}.bui-p-9{padding:var(--bui-space-9)}.bui-p-10{padding:var(--bui-space-10)}.bui-p-11{padding:var(--bui-space-11)}.bui-p-12{padding:var(--bui-space-12)}.bui-p-13{padding:var(--bui-space-13)}.bui-p-14{padding:var(--bui-space-14)}@media (width>=640px){.xs\:bui-p{padding:var(--p-xs)}.xs\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xs\:bui-p-1{padding:var(--bui-space-1)}.xs\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xs\:bui-p-2{padding:var(--bui-space-2)}.xs\:bui-p-3{padding:var(--bui-space-3)}.xs\:bui-p-4{padding:var(--bui-space-4)}.xs\:bui-p-5{padding:var(--bui-space-5)}.xs\:bui-p-6{padding:var(--bui-space-6)}.xs\:bui-p-7{padding:var(--bui-space-7)}.xs\:bui-p-8{padding:var(--bui-space-8)}.xs\:bui-p-9{padding:var(--bui-space-9)}.xs\:bui-p-10{padding:var(--bui-space-10)}.xs\:bui-p-11{padding:var(--bui-space-11)}.xs\:bui-p-12{padding:var(--bui-space-12)}.xs\:bui-p-13{padding:var(--bui-space-13)}.xs\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-p{padding:var(--p-sm)}.sm\:bui-p-0\.5{padding:var(--bui-space-0_5)}.sm\:bui-p-1{padding:var(--bui-space-1)}.sm\:bui-p-1\.5{padding:var(--bui-space-1_5)}.sm\:bui-p-2{padding:var(--bui-space-2)}.sm\:bui-p-3{padding:var(--bui-space-3)}.sm\:bui-p-4{padding:var(--bui-space-4)}.sm\:bui-p-5{padding:var(--bui-space-5)}.sm\:bui-p-6{padding:var(--bui-space-6)}.sm\:bui-p-7{padding:var(--bui-space-7)}.sm\:bui-p-8{padding:var(--bui-space-8)}.sm\:bui-p-9{padding:var(--bui-space-9)}.sm\:bui-p-10{padding:var(--bui-space-10)}.sm\:bui-p-11{padding:var(--bui-space-11)}.sm\:bui-p-12{padding:var(--bui-space-12)}.sm\:bui-p-13{padding:var(--bui-space-13)}.sm\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-p{padding:var(--p-md)}.md\:bui-p-0\.5{padding:var(--bui-space-0_5)}.md\:bui-p-1{padding:var(--bui-space-1)}.md\:bui-p-1\.5{padding:var(--bui-space-1_5)}.md\:bui-p-2{padding:var(--bui-space-2)}.md\:bui-p-3{padding:var(--bui-space-3)}.md\:bui-p-4{padding:var(--bui-space-4)}.md\:bui-p-5{padding:var(--bui-space-5)}.md\:bui-p-6{padding:var(--bui-space-6)}.md\:bui-p-7{padding:var(--bui-space-7)}.md\:bui-p-8{padding:var(--bui-space-8)}.md\:bui-p-9{padding:var(--bui-space-9)}.md\:bui-p-10{padding:var(--bui-space-10)}.md\:bui-p-11{padding:var(--bui-space-11)}.md\:bui-p-12{padding:var(--bui-space-12)}.md\:bui-p-13{padding:var(--bui-space-13)}.md\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-p{padding:var(--p-lg)}.lg\:bui-p-0\.5{padding:var(--bui-space-0_5)}.lg\:bui-p-1{padding:var(--bui-space-1)}.lg\:bui-p-1\.5{padding:var(--bui-space-1_5)}.lg\:bui-p-2{padding:var(--bui-space-2)}.lg\:bui-p-3{padding:var(--bui-space-3)}.lg\:bui-p-4{padding:var(--bui-space-4)}.lg\:bui-p-5{padding:var(--bui-space-5)}.lg\:bui-p-6{padding:var(--bui-space-6)}.lg\:bui-p-7{padding:var(--bui-space-7)}.lg\:bui-p-8{padding:var(--bui-space-8)}.lg\:bui-p-9{padding:var(--bui-space-9)}.lg\:bui-p-10{padding:var(--bui-space-10)}.lg\:bui-p-11{padding:var(--bui-space-11)}.lg\:bui-p-12{padding:var(--bui-space-12)}.lg\:bui-p-13{padding:var(--bui-space-13)}.lg\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-p{padding:var(--p-xl)}.xl\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xl\:bui-p-1{padding:var(--bui-space-1)}.xl\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xl\:bui-p-2{padding:var(--bui-space-2)}.xl\:bui-p-3{padding:var(--bui-space-3)}.xl\:bui-p-4{padding:var(--bui-space-4)}.xl\:bui-p-5{padding:var(--bui-space-5)}.xl\:bui-p-6{padding:var(--bui-space-6)}.xl\:bui-p-7{padding:var(--bui-space-7)}.xl\:bui-p-8{padding:var(--bui-space-8)}.xl\:bui-p-9{padding:var(--bui-space-9)}.xl\:bui-p-10{padding:var(--bui-space-10)}.xl\:bui-p-11{padding:var(--bui-space-11)}.xl\:bui-p-12{padding:var(--bui-space-12)}.xl\:bui-p-13{padding:var(--bui-space-13)}.xl\:bui-p-14{padding:var(--bui-space-14)}}.bui-pl{padding-left:var(--pl)}.bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.bui-pl-1{padding-left:var(--bui-space-1)}.bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.bui-pl-2{padding-left:var(--bui-space-2)}.bui-pl-3{padding-left:var(--bui-space-3)}.bui-pl-4{padding-left:var(--bui-space-4)}.bui-pl-5{padding-left:var(--bui-space-5)}.bui-pl-6{padding-left:var(--bui-space-6)}.bui-pl-7{padding-left:var(--bui-space-7)}.bui-pl-8{padding-left:var(--bui-space-8)}.bui-pl-9{padding-left:var(--bui-space-9)}.bui-pl-10{padding-left:var(--bui-space-10)}.bui-pl-11{padding-left:var(--bui-space-11)}.bui-pl-12{padding-left:var(--bui-space-12)}.bui-pl-13{padding-left:var(--bui-space-13)}.bui-pl-14{padding-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pl{padding-left:var(--pl-xs)}.xs\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xs\:bui-pl-1{padding-left:var(--bui-space-1)}.xs\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xs\:bui-pl-2{padding-left:var(--bui-space-2)}.xs\:bui-pl-3{padding-left:var(--bui-space-3)}.xs\:bui-pl-4{padding-left:var(--bui-space-4)}.xs\:bui-pl-5{padding-left:var(--bui-space-5)}.xs\:bui-pl-6{padding-left:var(--bui-space-6)}.xs\:bui-pl-7{padding-left:var(--bui-space-7)}.xs\:bui-pl-8{padding-left:var(--bui-space-8)}.xs\:bui-pl-9{padding-left:var(--bui-space-9)}.xs\:bui-pl-10{padding-left:var(--bui-space-10)}.xs\:bui-pl-11{padding-left:var(--bui-space-11)}.xs\:bui-pl-12{padding-left:var(--bui-space-12)}.xs\:bui-pl-13{padding-left:var(--bui-space-13)}.xs\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pl{padding-left:var(--pl-sm)}.sm\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.sm\:bui-pl-1{padding-left:var(--bui-space-1)}.sm\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.sm\:bui-pl-2{padding-left:var(--bui-space-2)}.sm\:bui-pl-3{padding-left:var(--bui-space-3)}.sm\:bui-pl-4{padding-left:var(--bui-space-4)}.sm\:bui-pl-5{padding-left:var(--bui-space-5)}.sm\:bui-pl-6{padding-left:var(--bui-space-6)}.sm\:bui-pl-7{padding-left:var(--bui-space-7)}.sm\:bui-pl-8{padding-left:var(--bui-space-8)}.sm\:bui-pl-9{padding-left:var(--bui-space-9)}.sm\:bui-pl-10{padding-left:var(--bui-space-10)}.sm\:bui-pl-11{padding-left:var(--bui-space-11)}.sm\:bui-pl-12{padding-left:var(--bui-space-12)}.sm\:bui-pl-13{padding-left:var(--bui-space-13)}.sm\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pl{padding-left:var(--pl-md)}.md\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.md\:bui-pl-1{padding-left:var(--bui-space-1)}.md\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.md\:bui-pl-2{padding-left:var(--bui-space-2)}.md\:bui-pl-3{padding-left:var(--bui-space-3)}.md\:bui-pl-4{padding-left:var(--bui-space-4)}.md\:bui-pl-5{padding-left:var(--bui-space-5)}.md\:bui-pl-6{padding-left:var(--bui-space-6)}.md\:bui-pl-7{padding-left:var(--bui-space-7)}.md\:bui-pl-8{padding-left:var(--bui-space-8)}.md\:bui-pl-9{padding-left:var(--bui-space-9)}.md\:bui-pl-10{padding-left:var(--bui-space-10)}.md\:bui-pl-11{padding-left:var(--bui-space-11)}.md\:bui-pl-12{padding-left:var(--bui-space-12)}.md\:bui-pl-13{padding-left:var(--bui-space-13)}.md\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pl{padding-left:var(--pl-lg)}.lg\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.lg\:bui-pl-1{padding-left:var(--bui-space-1)}.lg\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.lg\:bui-pl-2{padding-left:var(--bui-space-2)}.lg\:bui-pl-3{padding-left:var(--bui-space-3)}.lg\:bui-pl-4{padding-left:var(--bui-space-4)}.lg\:bui-pl-5{padding-left:var(--bui-space-5)}.lg\:bui-pl-6{padding-left:var(--bui-space-6)}.lg\:bui-pl-7{padding-left:var(--bui-space-7)}.lg\:bui-pl-8{padding-left:var(--bui-space-8)}.lg\:bui-pl-9{padding-left:var(--bui-space-9)}.lg\:bui-pl-10{padding-left:var(--bui-space-10)}.lg\:bui-pl-11{padding-left:var(--bui-space-11)}.lg\:bui-pl-12{padding-left:var(--bui-space-12)}.lg\:bui-pl-13{padding-left:var(--bui-space-13)}.lg\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pl{padding-left:var(--pl-xl)}.xl\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xl\:bui-pl-1{padding-left:var(--bui-space-1)}.xl\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xl\:bui-pl-2{padding-left:var(--bui-space-2)}.xl\:bui-pl-3{padding-left:var(--bui-space-3)}.xl\:bui-pl-4{padding-left:var(--bui-space-4)}.xl\:bui-pl-5{padding-left:var(--bui-space-5)}.xl\:bui-pl-6{padding-left:var(--bui-space-6)}.xl\:bui-pl-7{padding-left:var(--bui-space-7)}.xl\:bui-pl-8{padding-left:var(--bui-space-8)}.xl\:bui-pl-9{padding-left:var(--bui-space-9)}.xl\:bui-pl-10{padding-left:var(--bui-space-10)}.xl\:bui-pl-11{padding-left:var(--bui-space-11)}.xl\:bui-pl-12{padding-left:var(--bui-space-12)}.xl\:bui-pl-13{padding-left:var(--bui-space-13)}.xl\:bui-pl-14{padding-left:var(--bui-space-14)}}.bui-pr{padding-right:var(--pr)}.bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.bui-pr-1{padding-right:var(--bui-space-1)}.bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.bui-pr-2{padding-right:var(--bui-space-2)}.bui-pr-3{padding-right:var(--bui-space-3)}.bui-pr-4{padding-right:var(--bui-space-4)}.bui-pr-5{padding-right:var(--bui-space-5)}.bui-pr-6{padding-right:var(--bui-space-6)}.bui-pr-7{padding-right:var(--bui-space-7)}.bui-pr-8{padding-right:var(--bui-space-8)}.bui-pr-9{padding-right:var(--bui-space-9)}.bui-pr-10{padding-right:var(--bui-space-10)}.bui-pr-11{padding-right:var(--bui-space-11)}.bui-pr-12{padding-right:var(--bui-space-12)}.bui-pr-13{padding-right:var(--bui-space-13)}.bui-pr-14{padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pr{padding-right:var(--pr-xs)}.xs\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xs\:bui-pr-1{padding-right:var(--bui-space-1)}.xs\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xs\:bui-pr-2{padding-right:var(--bui-space-2)}.xs\:bui-pr-3{padding-right:var(--bui-space-3)}.xs\:bui-pr-4{padding-right:var(--bui-space-4)}.xs\:bui-pr-5{padding-right:var(--bui-space-5)}.xs\:bui-pr-6{padding-right:var(--bui-space-6)}.xs\:bui-pr-7{padding-right:var(--bui-space-7)}.xs\:bui-pr-8{padding-right:var(--bui-space-8)}.xs\:bui-pr-9{padding-right:var(--bui-space-9)}.xs\:bui-pr-10{padding-right:var(--bui-space-10)}.xs\:bui-pr-11{padding-right:var(--bui-space-11)}.xs\:bui-pr-12{padding-right:var(--bui-space-12)}.xs\:bui-pr-13{padding-right:var(--bui-space-13)}.xs\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pr{padding-right:var(--pr-sm)}.sm\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.sm\:bui-pr-1{padding-right:var(--bui-space-1)}.sm\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.sm\:bui-pr-2{padding-right:var(--bui-space-2)}.sm\:bui-pr-3{padding-right:var(--bui-space-3)}.sm\:bui-pr-4{padding-right:var(--bui-space-4)}.sm\:bui-pr-5{padding-right:var(--bui-space-5)}.sm\:bui-pr-6{padding-right:var(--bui-space-6)}.sm\:bui-pr-7{padding-right:var(--bui-space-7)}.sm\:bui-pr-8{padding-right:var(--bui-space-8)}.sm\:bui-pr-9{padding-right:var(--bui-space-9)}.sm\:bui-pr-10{padding-right:var(--bui-space-10)}.sm\:bui-pr-11{padding-right:var(--bui-space-11)}.sm\:bui-pr-12{padding-right:var(--bui-space-12)}.sm\:bui-pr-13{padding-right:var(--bui-space-13)}.sm\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pr{padding-right:var(--pr-md)}.md\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.md\:bui-pr-1{padding-right:var(--bui-space-1)}.md\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.md\:bui-pr-2{padding-right:var(--bui-space-2)}.md\:bui-pr-3{padding-right:var(--bui-space-3)}.md\:bui-pr-4{padding-right:var(--bui-space-4)}.md\:bui-pr-5{padding-right:var(--bui-space-5)}.md\:bui-pr-6{padding-right:var(--bui-space-6)}.md\:bui-pr-7{padding-right:var(--bui-space-7)}.md\:bui-pr-8{padding-right:var(--bui-space-8)}.md\:bui-pr-9{padding-right:var(--bui-space-9)}.md\:bui-pr-10{padding-right:var(--bui-space-10)}.md\:bui-pr-11{padding-right:var(--bui-space-11)}.md\:bui-pr-12{padding-right:var(--bui-space-12)}.md\:bui-pr-13{padding-right:var(--bui-space-13)}.md\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pr{padding-right:var(--pr-lg)}.lg\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.lg\:bui-pr-1{padding-right:var(--bui-space-1)}.lg\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.lg\:bui-pr-2{padding-right:var(--bui-space-2)}.lg\:bui-pr-3{padding-right:var(--bui-space-3)}.lg\:bui-pr-4{padding-right:var(--bui-space-4)}.lg\:bui-pr-5{padding-right:var(--bui-space-5)}.lg\:bui-pr-6{padding-right:var(--bui-space-6)}.lg\:bui-pr-7{padding-right:var(--bui-space-7)}.lg\:bui-pr-8{padding-right:var(--bui-space-8)}.lg\:bui-pr-9{padding-right:var(--bui-space-9)}.lg\:bui-pr-10{padding-right:var(--bui-space-10)}.lg\:bui-pr-11{padding-right:var(--bui-space-11)}.lg\:bui-pr-12{padding-right:var(--bui-space-12)}.lg\:bui-pr-13{padding-right:var(--bui-space-13)}.lg\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pr{padding-right:var(--pr-xl)}.xl\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xl\:bui-pr-1{padding-right:var(--bui-space-1)}.xl\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xl\:bui-pr-2{padding-right:var(--bui-space-2)}.xl\:bui-pr-3{padding-right:var(--bui-space-3)}.xl\:bui-pr-4{padding-right:var(--bui-space-4)}.xl\:bui-pr-5{padding-right:var(--bui-space-5)}.xl\:bui-pr-6{padding-right:var(--bui-space-6)}.xl\:bui-pr-7{padding-right:var(--bui-space-7)}.xl\:bui-pr-8{padding-right:var(--bui-space-8)}.xl\:bui-pr-9{padding-right:var(--bui-space-9)}.xl\:bui-pr-10{padding-right:var(--bui-space-10)}.xl\:bui-pr-11{padding-right:var(--bui-space-11)}.xl\:bui-pr-12{padding-right:var(--bui-space-12)}.xl\:bui-pr-13{padding-right:var(--bui-space-13)}.xl\:bui-pr-14{padding-right:var(--bui-space-14)}}.bui-pt{padding-top:var(--pt)}.bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.bui-pt-1{padding-top:var(--bui-space-1)}.bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.bui-pt-2{padding-top:var(--bui-space-2)}.bui-pt-3{padding-top:var(--bui-space-3)}.bui-pt-4{padding-top:var(--bui-space-4)}.bui-pt-5{padding-top:var(--bui-space-5)}.bui-pt-6{padding-top:var(--bui-space-6)}.bui-pt-7{padding-top:var(--bui-space-7)}.bui-pt-8{padding-top:var(--bui-space-8)}.bui-pt-9{padding-top:var(--bui-space-9)}.bui-pt-10{padding-top:var(--bui-space-10)}.bui-pt-11{padding-top:var(--bui-space-11)}.bui-pt-12{padding-top:var(--bui-space-12)}.bui-pt-13{padding-top:var(--bui-space-13)}.bui-pt-14{padding-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pt{padding-top:var(--pt-xs)}.xs\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xs\:bui-pt-1{padding-top:var(--bui-space-1)}.xs\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xs\:bui-pt-2{padding-top:var(--bui-space-2)}.xs\:bui-pt-3{padding-top:var(--bui-space-3)}.xs\:bui-pt-4{padding-top:var(--bui-space-4)}.xs\:bui-pt-5{padding-top:var(--bui-space-5)}.xs\:bui-pt-6{padding-top:var(--bui-space-6)}.xs\:bui-pt-7{padding-top:var(--bui-space-7)}.xs\:bui-pt-8{padding-top:var(--bui-space-8)}.xs\:bui-pt-9{padding-top:var(--bui-space-9)}.xs\:bui-pt-10{padding-top:var(--bui-space-10)}.xs\:bui-pt-11{padding-top:var(--bui-space-11)}.xs\:bui-pt-12{padding-top:var(--bui-space-12)}.xs\:bui-pt-13{padding-top:var(--bui-space-13)}.xs\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pt{padding-top:var(--pt-sm)}.sm\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.sm\:bui-pt-1{padding-top:var(--bui-space-1)}.sm\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.sm\:bui-pt-2{padding-top:var(--bui-space-2)}.sm\:bui-pt-3{padding-top:var(--bui-space-3)}.sm\:bui-pt-4{padding-top:var(--bui-space-4)}.sm\:bui-pt-5{padding-top:var(--bui-space-5)}.sm\:bui-pt-6{padding-top:var(--bui-space-6)}.sm\:bui-pt-7{padding-top:var(--bui-space-7)}.sm\:bui-pt-8{padding-top:var(--bui-space-8)}.sm\:bui-pt-9{padding-top:var(--bui-space-9)}.sm\:bui-pt-10{padding-top:var(--bui-space-10)}.sm\:bui-pt-11{padding-top:var(--bui-space-11)}.sm\:bui-pt-12{padding-top:var(--bui-space-12)}.sm\:bui-pt-13{padding-top:var(--bui-space-13)}.sm\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pt{padding-top:var(--pt-md)}.md\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.md\:bui-pt-1{padding-top:var(--bui-space-1)}.md\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.md\:bui-pt-2{padding-top:var(--bui-space-2)}.md\:bui-pt-3{padding-top:var(--bui-space-3)}.md\:bui-pt-4{padding-top:var(--bui-space-4)}.md\:bui-pt-5{padding-top:var(--bui-space-5)}.md\:bui-pt-6{padding-top:var(--bui-space-6)}.md\:bui-pt-7{padding-top:var(--bui-space-7)}.md\:bui-pt-8{padding-top:var(--bui-space-8)}.md\:bui-pt-9{padding-top:var(--bui-space-9)}.md\:bui-pt-10{padding-top:var(--bui-space-10)}.md\:bui-pt-11{padding-top:var(--bui-space-11)}.md\:bui-pt-12{padding-top:var(--bui-space-12)}.md\:bui-pt-13{padding-top:var(--bui-space-13)}.md\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pt{padding-top:var(--pt-lg)}.lg\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.lg\:bui-pt-1{padding-top:var(--bui-space-1)}.lg\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.lg\:bui-pt-2{padding-top:var(--bui-space-2)}.lg\:bui-pt-3{padding-top:var(--bui-space-3)}.lg\:bui-pt-4{padding-top:var(--bui-space-4)}.lg\:bui-pt-5{padding-top:var(--bui-space-5)}.lg\:bui-pt-6{padding-top:var(--bui-space-6)}.lg\:bui-pt-7{padding-top:var(--bui-space-7)}.lg\:bui-pt-8{padding-top:var(--bui-space-8)}.lg\:bui-pt-9{padding-top:var(--bui-space-9)}.lg\:bui-pt-10{padding-top:var(--bui-space-10)}.lg\:bui-pt-11{padding-top:var(--bui-space-11)}.lg\:bui-pt-12{padding-top:var(--bui-space-12)}.lg\:bui-pt-13{padding-top:var(--bui-space-13)}.lg\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pt{padding-top:var(--pt-xl)}.xl\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xl\:bui-pt-1{padding-top:var(--bui-space-1)}.xl\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xl\:bui-pt-2{padding-top:var(--bui-space-2)}.xl\:bui-pt-3{padding-top:var(--bui-space-3)}.xl\:bui-pt-4{padding-top:var(--bui-space-4)}.xl\:bui-pt-5{padding-top:var(--bui-space-5)}.xl\:bui-pt-6{padding-top:var(--bui-space-6)}.xl\:bui-pt-7{padding-top:var(--bui-space-7)}.xl\:bui-pt-8{padding-top:var(--bui-space-8)}.xl\:bui-pt-9{padding-top:var(--bui-space-9)}.xl\:bui-pt-10{padding-top:var(--bui-space-10)}.xl\:bui-pt-11{padding-top:var(--bui-space-11)}.xl\:bui-pt-12{padding-top:var(--bui-space-12)}.xl\:bui-pt-13{padding-top:var(--bui-space-13)}.xl\:bui-pt-14{padding-top:var(--bui-space-14)}}.bui-pb{padding-bottom:var(--pb)}.bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.bui-pb-1{padding-bottom:var(--bui-space-1)}.bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.bui-pb-2{padding-bottom:var(--bui-space-2)}.bui-pb-3{padding-bottom:var(--bui-space-3)}.bui-pb-4{padding-bottom:var(--bui-space-4)}.bui-pb-5{padding-bottom:var(--bui-space-5)}.bui-pb-6{padding-bottom:var(--bui-space-6)}.bui-pb-7{padding-bottom:var(--bui-space-7)}.bui-pb-8{padding-bottom:var(--bui-space-8)}.bui-pb-9{padding-bottom:var(--bui-space-9)}.bui-pb-10{padding-bottom:var(--bui-space-10)}.bui-pb-11{padding-bottom:var(--bui-space-11)}.bui-pb-12{padding-bottom:var(--bui-space-12)}.bui-pb-13{padding-bottom:var(--bui-space-13)}.bui-pb-14{padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pb{padding-bottom:var(--pb-xs)}.xs\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xs\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xs\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xs\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xs\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xs\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xs\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xs\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xs\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xs\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xs\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xs\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xs\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xs\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xs\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xs\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pb{padding-bottom:var(--pb-sm)}.sm\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.sm\:bui-pb-1{padding-bottom:var(--bui-space-1)}.sm\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.sm\:bui-pb-2{padding-bottom:var(--bui-space-2)}.sm\:bui-pb-3{padding-bottom:var(--bui-space-3)}.sm\:bui-pb-4{padding-bottom:var(--bui-space-4)}.sm\:bui-pb-5{padding-bottom:var(--bui-space-5)}.sm\:bui-pb-6{padding-bottom:var(--bui-space-6)}.sm\:bui-pb-7{padding-bottom:var(--bui-space-7)}.sm\:bui-pb-8{padding-bottom:var(--bui-space-8)}.sm\:bui-pb-9{padding-bottom:var(--bui-space-9)}.sm\:bui-pb-10{padding-bottom:var(--bui-space-10)}.sm\:bui-pb-11{padding-bottom:var(--bui-space-11)}.sm\:bui-pb-12{padding-bottom:var(--bui-space-12)}.sm\:bui-pb-13{padding-bottom:var(--bui-space-13)}.sm\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pb{padding-bottom:var(--pb-md)}.md\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.md\:bui-pb-1{padding-bottom:var(--bui-space-1)}.md\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.md\:bui-pb-2{padding-bottom:var(--bui-space-2)}.md\:bui-pb-3{padding-bottom:var(--bui-space-3)}.md\:bui-pb-4{padding-bottom:var(--bui-space-4)}.md\:bui-pb-5{padding-bottom:var(--bui-space-5)}.md\:bui-pb-6{padding-bottom:var(--bui-space-6)}.md\:bui-pb-7{padding-bottom:var(--bui-space-7)}.md\:bui-pb-8{padding-bottom:var(--bui-space-8)}.md\:bui-pb-9{padding-bottom:var(--bui-space-9)}.md\:bui-pb-10{padding-bottom:var(--bui-space-10)}.md\:bui-pb-11{padding-bottom:var(--bui-space-11)}.md\:bui-pb-12{padding-bottom:var(--bui-space-12)}.md\:bui-pb-13{padding-bottom:var(--bui-space-13)}.md\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pb{padding-bottom:var(--pb-lg)}.lg\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.lg\:bui-pb-1{padding-bottom:var(--bui-space-1)}.lg\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.lg\:bui-pb-2{padding-bottom:var(--bui-space-2)}.lg\:bui-pb-3{padding-bottom:var(--bui-space-3)}.lg\:bui-pb-4{padding-bottom:var(--bui-space-4)}.lg\:bui-pb-5{padding-bottom:var(--bui-space-5)}.lg\:bui-pb-6{padding-bottom:var(--bui-space-6)}.lg\:bui-pb-7{padding-bottom:var(--bui-space-7)}.lg\:bui-pb-8{padding-bottom:var(--bui-space-8)}.lg\:bui-pb-9{padding-bottom:var(--bui-space-9)}.lg\:bui-pb-10{padding-bottom:var(--bui-space-10)}.lg\:bui-pb-11{padding-bottom:var(--bui-space-11)}.lg\:bui-pb-12{padding-bottom:var(--bui-space-12)}.lg\:bui-pb-13{padding-bottom:var(--bui-space-13)}.lg\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pb{padding-bottom:var(--pb-xl)}.xl\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xl\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xl\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xl\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xl\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xl\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xl\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xl\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xl\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xl\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xl\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xl\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xl\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xl\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xl\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xl\:bui-pb-14{padding-bottom:var(--bui-space-14)}}.bui-py{padding-top:var(--py);padding-bottom:var(--py)}.bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-py{padding-top:var(--py-xs);padding-bottom:var(--py-xs)}.xs\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xs\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xs\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xs\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xs\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xs\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xs\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xs\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xs\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xs\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xs\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xs\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xs\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xs\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xs\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xs\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-py{padding-top:var(--py-sm);padding-bottom:var(--py-sm)}.sm\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.sm\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.sm\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.sm\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.sm\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.sm\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.sm\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.sm\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.sm\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.sm\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.sm\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.sm\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.sm\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.sm\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.sm\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.sm\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-py{padding-top:var(--py-md);padding-bottom:var(--py-md)}.md\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.md\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.md\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.md\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.md\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.md\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.md\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.md\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.md\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.md\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.md\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.md\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.md\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.md\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.md\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.md\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-py{padding-top:var(--py-lg);padding-bottom:var(--py-lg)}.lg\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.lg\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.lg\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.lg\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.lg\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.lg\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.lg\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.lg\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.lg\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.lg\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.lg\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.lg\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.lg\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.lg\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.lg\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.lg\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-py{padding-top:var(--py-xl);padding-bottom:var(--py-xl)}.xl\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xl\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xl\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xl\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xl\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xl\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xl\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xl\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xl\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xl\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xl\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xl\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xl\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xl\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xl\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xl\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}.bui-px{padding-left:var(--px);padding-right:var(--px)}.bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-px{padding-left:var(--px-xs);padding-right:var(--px-xs)}.xs\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xs\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xs\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xs\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xs\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xs\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xs\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xs\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xs\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xs\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xs\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xs\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xs\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xs\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xs\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xs\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-px{padding-left:var(--px-sm);padding-right:var(--px-sm)}.sm\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.sm\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.sm\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.sm\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.sm\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.sm\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.sm\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.sm\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.sm\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.sm\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.sm\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.sm\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.sm\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.sm\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.sm\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.sm\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-px{padding-left:var(--px-md);padding-right:var(--px-md)}.md\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.md\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.md\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.md\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.md\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.md\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.md\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.md\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.md\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.md\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.md\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.md\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.md\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.md\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.md\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.md\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-px{padding-left:var(--px-lg);padding-right:var(--px-lg)}.lg\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.lg\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.lg\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.lg\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.lg\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.lg\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.lg\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.lg\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.lg\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.lg\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.lg\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.lg\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.lg\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.lg\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.lg\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.lg\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-px{padding-left:var(--px-xl);padding-right:var(--px-xl)}.xl\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xl\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xl\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xl\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xl\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xl\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xl\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xl\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xl\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xl\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xl\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xl\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xl\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xl\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xl\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xl\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}.bui-m{margin:var(--m)}.bui-m-0\.5{margin:var(--bui-space-0_5)}.bui-m-1{margin:var(--bui-space-1)}.bui-m-1\.5{margin:var(--bui-space-1_5)}.bui-m-2{margin:var(--bui-space-2)}.bui-m-3{margin:var(--bui-space-3)}.bui-m-4{margin:var(--bui-space-4)}.bui-m-5{margin:var(--bui-space-5)}.bui-m-6{margin:var(--bui-space-6)}.bui-m-7{margin:var(--bui-space-7)}.bui-m-8{margin:var(--bui-space-8)}.bui-m-9{margin:var(--bui-space-9)}.bui-m-10{margin:var(--bui-space-10)}.bui-m-11{margin:var(--bui-space-11)}.bui-m-12{margin:var(--bui-space-12)}.bui-m-13{margin:var(--bui-space-13)}.bui-m-14{margin:var(--bui-space-14)}@media (width>=640px){.xs\:bui-m{margin:var(--p-xs)}.xs\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xs\:bui-m-1{margin:var(--bui-space-1)}.xs\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xs\:bui-m-2{margin:var(--bui-space-2)}.xs\:bui-m-3{margin:var(--bui-space-3)}.xs\:bui-m-4{margin:var(--bui-space-4)}.xs\:bui-m-5{margin:var(--bui-space-5)}.xs\:bui-m-6{margin:var(--bui-space-6)}.xs\:bui-m-7{margin:var(--bui-space-7)}.xs\:bui-m-8{margin:var(--bui-space-8)}.xs\:bui-m-9{margin:var(--bui-space-9)}.xs\:bui-m-10{margin:var(--bui-space-10)}.xs\:bui-m-11{margin:var(--bui-space-11)}.xs\:bui-m-12{margin:var(--bui-space-12)}.xs\:bui-m-13{margin:var(--bui-space-13)}.xs\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-m{margin:var(--p-sm)}.sm\:bui-m-0\.5{margin:var(--bui-space-0_5)}.sm\:bui-m-1{margin:var(--bui-space-1)}.sm\:bui-m-1\.5{margin:var(--bui-space-1_5)}.sm\:bui-m-2{margin:var(--bui-space-2)}.sm\:bui-m-3{margin:var(--bui-space-3)}.sm\:bui-m-4{margin:var(--bui-space-4)}.sm\:bui-m-5{margin:var(--bui-space-5)}.sm\:bui-m-6{margin:var(--bui-space-6)}.sm\:bui-m-7{margin:var(--bui-space-7)}.sm\:bui-m-8{margin:var(--bui-space-8)}.sm\:bui-m-9{margin:var(--bui-space-9)}.sm\:bui-m-10{margin:var(--bui-space-10)}.sm\:bui-m-11{margin:var(--bui-space-11)}.sm\:bui-m-12{margin:var(--bui-space-12)}.sm\:bui-m-13{margin:var(--bui-space-13)}.sm\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-m{margin:var(--p-md)}.md\:bui-m-0\.5{margin:var(--bui-space-0_5)}.md\:bui-m-1{margin:var(--bui-space-1)}.md\:bui-m-1\.5{margin:var(--bui-space-1_5)}.md\:bui-m-2{margin:var(--bui-space-2)}.md\:bui-m-3{margin:var(--bui-space-3)}.md\:bui-m-4{margin:var(--bui-space-4)}.md\:bui-m-5{margin:var(--bui-space-5)}.md\:bui-m-6{margin:var(--bui-space-6)}.md\:bui-m-7{margin:var(--bui-space-7)}.md\:bui-m-8{margin:var(--bui-space-8)}.md\:bui-m-9{margin:var(--bui-space-9)}.md\:bui-m-10{margin:var(--bui-space-10)}.md\:bui-m-11{margin:var(--bui-space-11)}.md\:bui-m-12{margin:var(--bui-space-12)}.md\:bui-m-13{margin:var(--bui-space-13)}.md\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-m{margin:var(--p-lg)}.lg\:bui-m-0\.5{margin:var(--bui-space-0_5)}.lg\:bui-m-1{margin:var(--bui-space-1)}.lg\:bui-m-1\.5{margin:var(--bui-space-1_5)}.lg\:bui-m-2{margin:var(--bui-space-2)}.lg\:bui-m-3{margin:var(--bui-space-3)}.lg\:bui-m-4{margin:var(--bui-space-4)}.lg\:bui-m-5{margin:var(--bui-space-5)}.lg\:bui-m-6{margin:var(--bui-space-6)}.lg\:bui-m-7{margin:var(--bui-space-7)}.lg\:bui-m-8{margin:var(--bui-space-8)}.lg\:bui-m-9{margin:var(--bui-space-9)}.lg\:bui-m-10{margin:var(--bui-space-10)}.lg\:bui-m-11{margin:var(--bui-space-11)}.lg\:bui-m-12{margin:var(--bui-space-12)}.lg\:bui-m-13{margin:var(--bui-space-13)}.lg\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-m{margin:var(--p-xl)}.xl\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xl\:bui-m-1{margin:var(--bui-space-1)}.xl\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xl\:bui-m-2{margin:var(--bui-space-2)}.xl\:bui-m-3{margin:var(--bui-space-3)}.xl\:bui-m-4{margin:var(--bui-space-4)}.xl\:bui-m-5{margin:var(--bui-space-5)}.xl\:bui-m-6{margin:var(--bui-space-6)}.xl\:bui-m-7{margin:var(--bui-space-7)}.xl\:bui-m-8{margin:var(--bui-space-8)}.xl\:bui-m-9{margin:var(--bui-space-9)}.xl\:bui-m-10{margin:var(--bui-space-10)}.xl\:bui-m-11{margin:var(--bui-space-11)}.xl\:bui-m-12{margin:var(--bui-space-12)}.xl\:bui-m-13{margin:var(--bui-space-13)}.xl\:bui-m-14{margin:var(--bui-space-14)}}.bui-ml{margin-left:var(--ml)}.bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.bui-ml-1{margin-left:var(--bui-space-1)}.bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.bui-ml-2{margin-left:var(--bui-space-2)}.bui-ml-3{margin-left:var(--bui-space-3)}.bui-ml-4{margin-left:var(--bui-space-4)}.bui-ml-5{margin-left:var(--bui-space-5)}.bui-ml-6{margin-left:var(--bui-space-6)}.bui-ml-7{margin-left:var(--bui-space-7)}.bui-ml-8{margin-left:var(--bui-space-8)}.bui-ml-9{margin-left:var(--bui-space-9)}.bui-ml-10{margin-left:var(--bui-space-10)}.bui-ml-11{margin-left:var(--bui-space-11)}.bui-ml-12{margin-left:var(--bui-space-12)}.bui-ml-13{margin-left:var(--bui-space-13)}.bui-ml-14{margin-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-ml{margin-left:var(--ml-xs)}.xs\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xs\:bui-ml-1{margin-left:var(--bui-space-1)}.xs\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xs\:bui-ml-2{margin-left:var(--bui-space-2)}.xs\:bui-ml-3{margin-left:var(--bui-space-3)}.xs\:bui-ml-4{margin-left:var(--bui-space-4)}.xs\:bui-ml-5{margin-left:var(--bui-space-5)}.xs\:bui-ml-6{margin-left:var(--bui-space-6)}.xs\:bui-ml-7{margin-left:var(--bui-space-7)}.xs\:bui-ml-8{margin-left:var(--bui-space-8)}.xs\:bui-ml-9{margin-left:var(--bui-space-9)}.xs\:bui-ml-10{margin-left:var(--bui-space-10)}.xs\:bui-ml-11{margin-left:var(--bui-space-11)}.xs\:bui-ml-12{margin-left:var(--bui-space-12)}.xs\:bui-ml-13{margin-left:var(--bui-space-13)}.xs\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-ml{margin-left:var(--ml-sm)}.sm\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.sm\:bui-ml-1{margin-left:var(--bui-space-1)}.sm\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.sm\:bui-ml-2{margin-left:var(--bui-space-2)}.sm\:bui-ml-3{margin-left:var(--bui-space-3)}.sm\:bui-ml-4{margin-left:var(--bui-space-4)}.sm\:bui-ml-5{margin-left:var(--bui-space-5)}.sm\:bui-ml-6{margin-left:var(--bui-space-6)}.sm\:bui-ml-7{margin-left:var(--bui-space-7)}.sm\:bui-ml-8{margin-left:var(--bui-space-8)}.sm\:bui-ml-9{margin-left:var(--bui-space-9)}.sm\:bui-ml-10{margin-left:var(--bui-space-10)}.sm\:bui-ml-11{margin-left:var(--bui-space-11)}.sm\:bui-ml-12{margin-left:var(--bui-space-12)}.sm\:bui-ml-13{margin-left:var(--bui-space-13)}.sm\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-ml{margin-left:var(--ml-md)}.md\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.md\:bui-ml-1{margin-left:var(--bui-space-1)}.md\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.md\:bui-ml-2{margin-left:var(--bui-space-2)}.md\:bui-ml-3{margin-left:var(--bui-space-3)}.md\:bui-ml-4{margin-left:var(--bui-space-4)}.md\:bui-ml-5{margin-left:var(--bui-space-5)}.md\:bui-ml-6{margin-left:var(--bui-space-6)}.md\:bui-ml-7{margin-left:var(--bui-space-7)}.md\:bui-ml-8{margin-left:var(--bui-space-8)}.md\:bui-ml-9{margin-left:var(--bui-space-9)}.md\:bui-ml-10{margin-left:var(--bui-space-10)}.md\:bui-ml-11{margin-left:var(--bui-space-11)}.md\:bui-ml-12{margin-left:var(--bui-space-12)}.md\:bui-ml-13{margin-left:var(--bui-space-13)}.md\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-ml{margin-left:var(--ml-lg)}.lg\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.lg\:bui-ml-1{margin-left:var(--bui-space-1)}.lg\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.lg\:bui-ml-2{margin-left:var(--bui-space-2)}.lg\:bui-ml-3{margin-left:var(--bui-space-3)}.lg\:bui-ml-4{margin-left:var(--bui-space-4)}.lg\:bui-ml-5{margin-left:var(--bui-space-5)}.lg\:bui-ml-6{margin-left:var(--bui-space-6)}.lg\:bui-ml-7{margin-left:var(--bui-space-7)}.lg\:bui-ml-8{margin-left:var(--bui-space-8)}.lg\:bui-ml-9{margin-left:var(--bui-space-9)}.lg\:bui-ml-10{margin-left:var(--bui-space-10)}.lg\:bui-ml-11{margin-left:var(--bui-space-11)}.lg\:bui-ml-12{margin-left:var(--bui-space-12)}.lg\:bui-ml-13{margin-left:var(--bui-space-13)}.lg\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-ml{margin-left:var(--ml-xl)}.xl\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xl\:bui-ml-1{margin-left:var(--bui-space-1)}.xl\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xl\:bui-ml-2{margin-left:var(--bui-space-2)}.xl\:bui-ml-3{margin-left:var(--bui-space-3)}.xl\:bui-ml-4{margin-left:var(--bui-space-4)}.xl\:bui-ml-5{margin-left:var(--bui-space-5)}.xl\:bui-ml-6{margin-left:var(--bui-space-6)}.xl\:bui-ml-7{margin-left:var(--bui-space-7)}.xl\:bui-ml-8{margin-left:var(--bui-space-8)}.xl\:bui-ml-9{margin-left:var(--bui-space-9)}.xl\:bui-ml-10{margin-left:var(--bui-space-10)}.xl\:bui-ml-11{margin-left:var(--bui-space-11)}.xl\:bui-ml-12{margin-left:var(--bui-space-12)}.xl\:bui-ml-13{margin-left:var(--bui-space-13)}.xl\:bui-ml-14{margin-left:var(--bui-space-14)}}.bui-mr{margin-right:var(--mr)}.bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.bui-mr-1{margin-right:var(--bui-space-1)}.bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.bui-mr-2{margin-right:var(--bui-space-2)}.bui-mr-3{margin-right:var(--bui-space-3)}.bui-mr-4{margin-right:var(--bui-space-4)}.bui-mr-5{margin-right:var(--bui-space-5)}.bui-mr-6{margin-right:var(--bui-space-6)}.bui-mr-7{margin-right:var(--bui-space-7)}.bui-mr-8{margin-right:var(--bui-space-8)}.bui-mr-9{margin-right:var(--bui-space-9)}.bui-mr-10{margin-right:var(--bui-space-10)}.bui-mr-11{margin-right:var(--bui-space-11)}.bui-mr-12{margin-right:var(--bui-space-12)}.bui-mr-13{margin-right:var(--bui-space-13)}.bui-mr-14{margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mr{margin-right:var(--mr-xs)}.xs\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xs\:bui-mr-1{margin-right:var(--bui-space-1)}.xs\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xs\:bui-mr-2{margin-right:var(--bui-space-2)}.xs\:bui-mr-3{margin-right:var(--bui-space-3)}.xs\:bui-mr-4{margin-right:var(--bui-space-4)}.xs\:bui-mr-5{margin-right:var(--bui-space-5)}.xs\:bui-mr-6{margin-right:var(--bui-space-6)}.xs\:bui-mr-7{margin-right:var(--bui-space-7)}.xs\:bui-mr-8{margin-right:var(--bui-space-8)}.xs\:bui-mr-9{margin-right:var(--bui-space-9)}.xs\:bui-mr-10{margin-right:var(--bui-space-10)}.xs\:bui-mr-11{margin-right:var(--bui-space-11)}.xs\:bui-mr-12{margin-right:var(--bui-space-12)}.xs\:bui-mr-13{margin-right:var(--bui-space-13)}.xs\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mr{margin-right:var(--mr-sm)}.sm\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.sm\:bui-mr-1{margin-right:var(--bui-space-1)}.sm\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.sm\:bui-mr-2{margin-right:var(--bui-space-2)}.sm\:bui-mr-3{margin-right:var(--bui-space-3)}.sm\:bui-mr-4{margin-right:var(--bui-space-4)}.sm\:bui-mr-5{margin-right:var(--bui-space-5)}.sm\:bui-mr-6{margin-right:var(--bui-space-6)}.sm\:bui-mr-7{margin-right:var(--bui-space-7)}.sm\:bui-mr-8{margin-right:var(--bui-space-8)}.sm\:bui-mr-9{margin-right:var(--bui-space-9)}.sm\:bui-mr-10{margin-right:var(--bui-space-10)}.sm\:bui-mr-11{margin-right:var(--bui-space-11)}.sm\:bui-mr-12{margin-right:var(--bui-space-12)}.sm\:bui-mr-13{margin-right:var(--bui-space-13)}.sm\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mr{margin-right:var(--mr-md)}.md\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.md\:bui-mr-1{margin-right:var(--bui-space-1)}.md\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.md\:bui-mr-2{margin-right:var(--bui-space-2)}.md\:bui-mr-3{margin-right:var(--bui-space-3)}.md\:bui-mr-4{margin-right:var(--bui-space-4)}.md\:bui-mr-5{margin-right:var(--bui-space-5)}.md\:bui-mr-6{margin-right:var(--bui-space-6)}.md\:bui-mr-7{margin-right:var(--bui-space-7)}.md\:bui-mr-8{margin-right:var(--bui-space-8)}.md\:bui-mr-9{margin-right:var(--bui-space-9)}.md\:bui-mr-10{margin-right:var(--bui-space-10)}.md\:bui-mr-11{margin-right:var(--bui-space-11)}.md\:bui-mr-12{margin-right:var(--bui-space-12)}.md\:bui-mr-13{margin-right:var(--bui-space-13)}.md\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mr{margin-right:var(--mr-lg)}.lg\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.lg\:bui-mr-1{margin-right:var(--bui-space-1)}.lg\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.lg\:bui-mr-2{margin-right:var(--bui-space-2)}.lg\:bui-mr-3{margin-right:var(--bui-space-3)}.lg\:bui-mr-4{margin-right:var(--bui-space-4)}.lg\:bui-mr-5{margin-right:var(--bui-space-5)}.lg\:bui-mr-6{margin-right:var(--bui-space-6)}.lg\:bui-mr-7{margin-right:var(--bui-space-7)}.lg\:bui-mr-8{margin-right:var(--bui-space-8)}.lg\:bui-mr-9{margin-right:var(--bui-space-9)}.lg\:bui-mr-10{margin-right:var(--bui-space-10)}.lg\:bui-mr-11{margin-right:var(--bui-space-11)}.lg\:bui-mr-12{margin-right:var(--bui-space-12)}.lg\:bui-mr-13{margin-right:var(--bui-space-13)}.lg\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mr{margin-right:var(--mr-xl)}.xl\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xl\:bui-mr-1{margin-right:var(--bui-space-1)}.xl\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xl\:bui-mr-2{margin-right:var(--bui-space-2)}.xl\:bui-mr-3{margin-right:var(--bui-space-3)}.xl\:bui-mr-4{margin-right:var(--bui-space-4)}.xl\:bui-mr-5{margin-right:var(--bui-space-5)}.xl\:bui-mr-6{margin-right:var(--bui-space-6)}.xl\:bui-mr-7{margin-right:var(--bui-space-7)}.xl\:bui-mr-8{margin-right:var(--bui-space-8)}.xl\:bui-mr-9{margin-right:var(--bui-space-9)}.xl\:bui-mr-10{margin-right:var(--bui-space-10)}.xl\:bui-mr-11{margin-right:var(--bui-space-11)}.xl\:bui-mr-12{margin-right:var(--bui-space-12)}.xl\:bui-mr-13{margin-right:var(--bui-space-13)}.xl\:bui-mr-14{margin-right:var(--bui-space-14)}}.bui-mt{margin-top:var(--mt)}.bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.bui-mt-1{margin-top:var(--bui-space-1)}.bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.bui-mt-2{margin-top:var(--bui-space-2)}.bui-mt-3{margin-top:var(--bui-space-3)}.bui-mt-4{margin-top:var(--bui-space-4)}.bui-mt-5{margin-top:var(--bui-space-5)}.bui-mt-6{margin-top:var(--bui-space-6)}.bui-mt-7{margin-top:var(--bui-space-7)}.bui-mt-8{margin-top:var(--bui-space-8)}.bui-mt-9{margin-top:var(--bui-space-9)}.bui-mt-10{margin-top:var(--bui-space-10)}.bui-mt-11{margin-top:var(--bui-space-11)}.bui-mt-12{margin-top:var(--bui-space-12)}.bui-mt-13{margin-top:var(--bui-space-13)}.bui-mt-14{margin-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mt{margin-top:var(--mt-xs)}.xs\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xs\:bui-mt-1{margin-top:var(--bui-space-1)}.xs\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xs\:bui-mt-2{margin-top:var(--bui-space-2)}.xs\:bui-mt-3{margin-top:var(--bui-space-3)}.xs\:bui-mt-4{margin-top:var(--bui-space-4)}.xs\:bui-mt-5{margin-top:var(--bui-space-5)}.xs\:bui-mt-6{margin-top:var(--bui-space-6)}.xs\:bui-mt-7{margin-top:var(--bui-space-7)}.xs\:bui-mt-8{margin-top:var(--bui-space-8)}.xs\:bui-mt-9{margin-top:var(--bui-space-9)}.xs\:bui-mt-10{margin-top:var(--bui-space-10)}.xs\:bui-mt-11{margin-top:var(--bui-space-11)}.xs\:bui-mt-12{margin-top:var(--bui-space-12)}.xs\:bui-mt-13{margin-top:var(--bui-space-13)}.xs\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mt{margin-top:var(--mt-sm)}.sm\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.sm\:bui-mt-1{margin-top:var(--bui-space-1)}.sm\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.sm\:bui-mt-2{margin-top:var(--bui-space-2)}.sm\:bui-mt-3{margin-top:var(--bui-space-3)}.sm\:bui-mt-4{margin-top:var(--bui-space-4)}.sm\:bui-mt-5{margin-top:var(--bui-space-5)}.sm\:bui-mt-6{margin-top:var(--bui-space-6)}.sm\:bui-mt-7{margin-top:var(--bui-space-7)}.sm\:bui-mt-8{margin-top:var(--bui-space-8)}.sm\:bui-mt-9{margin-top:var(--bui-space-9)}.sm\:bui-mt-10{margin-top:var(--bui-space-10)}.sm\:bui-mt-11{margin-top:var(--bui-space-11)}.sm\:bui-mt-12{margin-top:var(--bui-space-12)}.sm\:bui-mt-13{margin-top:var(--bui-space-13)}.sm\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mt{margin-top:var(--mt-md)}.md\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.md\:bui-mt-1{margin-top:var(--bui-space-1)}.md\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.md\:bui-mt-2{margin-top:var(--bui-space-2)}.md\:bui-mt-3{margin-top:var(--bui-space-3)}.md\:bui-mt-4{margin-top:var(--bui-space-4)}.md\:bui-mt-5{margin-top:var(--bui-space-5)}.md\:bui-mt-6{margin-top:var(--bui-space-6)}.md\:bui-mt-7{margin-top:var(--bui-space-7)}.md\:bui-mt-8{margin-top:var(--bui-space-8)}.md\:bui-mt-9{margin-top:var(--bui-space-9)}.md\:bui-mt-10{margin-top:var(--bui-space-10)}.md\:bui-mt-11{margin-top:var(--bui-space-11)}.md\:bui-mt-12{margin-top:var(--bui-space-12)}.md\:bui-mt-13{margin-top:var(--bui-space-13)}.md\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mt{margin-top:var(--mt-lg)}.lg\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.lg\:bui-mt-1{margin-top:var(--bui-space-1)}.lg\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.lg\:bui-mt-2{margin-top:var(--bui-space-2)}.lg\:bui-mt-3{margin-top:var(--bui-space-3)}.lg\:bui-mt-4{margin-top:var(--bui-space-4)}.lg\:bui-mt-5{margin-top:var(--bui-space-5)}.lg\:bui-mt-6{margin-top:var(--bui-space-6)}.lg\:bui-mt-7{margin-top:var(--bui-space-7)}.lg\:bui-mt-8{margin-top:var(--bui-space-8)}.lg\:bui-mt-9{margin-top:var(--bui-space-9)}.lg\:bui-mt-10{margin-top:var(--bui-space-10)}.lg\:bui-mt-11{margin-top:var(--bui-space-11)}.lg\:bui-mt-12{margin-top:var(--bui-space-12)}.lg\:bui-mt-13{margin-top:var(--bui-space-13)}.lg\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mt{margin-top:var(--mt-xl)}.xl\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xl\:bui-mt-1{margin-top:var(--bui-space-1)}.xl\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xl\:bui-mt-2{margin-top:var(--bui-space-2)}.xl\:bui-mt-3{margin-top:var(--bui-space-3)}.xl\:bui-mt-4{margin-top:var(--bui-space-4)}.xl\:bui-mt-5{margin-top:var(--bui-space-5)}.xl\:bui-mt-6{margin-top:var(--bui-space-6)}.xl\:bui-mt-7{margin-top:var(--bui-space-7)}.xl\:bui-mt-8{margin-top:var(--bui-space-8)}.xl\:bui-mt-9{margin-top:var(--bui-space-9)}.xl\:bui-mt-10{margin-top:var(--bui-space-10)}.xl\:bui-mt-11{margin-top:var(--bui-space-11)}.xl\:bui-mt-12{margin-top:var(--bui-space-12)}.xl\:bui-mt-13{margin-top:var(--bui-space-13)}.xl\:bui-mt-14{margin-top:var(--bui-space-14)}}.bui-mb{margin-bottom:var(--mb)}.bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.bui-mb-1{margin-bottom:var(--bui-space-1)}.bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.bui-mb-2{margin-bottom:var(--bui-space-2)}.bui-mb-3{margin-bottom:var(--bui-space-3)}.bui-mb-4{margin-bottom:var(--bui-space-4)}.bui-mb-5{margin-bottom:var(--bui-space-5)}.bui-mb-6{margin-bottom:var(--bui-space-6)}.bui-mb-7{margin-bottom:var(--bui-space-7)}.bui-mb-8{margin-bottom:var(--bui-space-8)}.bui-mb-9{margin-bottom:var(--bui-space-9)}.bui-mb-10{margin-bottom:var(--bui-space-10)}.bui-mb-11{margin-bottom:var(--bui-space-11)}.bui-mb-12{margin-bottom:var(--bui-space-12)}.bui-mb-13{margin-bottom:var(--bui-space-13)}.bui-mb-14{margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mb{margin-bottom:var(--mb-xs)}.xs\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xs\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xs\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xs\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xs\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xs\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xs\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xs\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xs\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xs\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xs\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xs\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xs\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xs\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xs\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xs\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mb{margin-bottom:var(--mb-sm)}.sm\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.sm\:bui-mb-1{margin-bottom:var(--bui-space-1)}.sm\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.sm\:bui-mb-2{margin-bottom:var(--bui-space-2)}.sm\:bui-mb-3{margin-bottom:var(--bui-space-3)}.sm\:bui-mb-4{margin-bottom:var(--bui-space-4)}.sm\:bui-mb-5{margin-bottom:var(--bui-space-5)}.sm\:bui-mb-6{margin-bottom:var(--bui-space-6)}.sm\:bui-mb-7{margin-bottom:var(--bui-space-7)}.sm\:bui-mb-8{margin-bottom:var(--bui-space-8)}.sm\:bui-mb-9{margin-bottom:var(--bui-space-9)}.sm\:bui-mb-10{margin-bottom:var(--bui-space-10)}.sm\:bui-mb-11{margin-bottom:var(--bui-space-11)}.sm\:bui-mb-12{margin-bottom:var(--bui-space-12)}.sm\:bui-mb-13{margin-bottom:var(--bui-space-13)}.sm\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mb{margin-bottom:var(--mb-md)}.md\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.md\:bui-mb-1{margin-bottom:var(--bui-space-1)}.md\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.md\:bui-mb-2{margin-bottom:var(--bui-space-2)}.md\:bui-mb-3{margin-bottom:var(--bui-space-3)}.md\:bui-mb-4{margin-bottom:var(--bui-space-4)}.md\:bui-mb-5{margin-bottom:var(--bui-space-5)}.md\:bui-mb-6{margin-bottom:var(--bui-space-6)}.md\:bui-mb-7{margin-bottom:var(--bui-space-7)}.md\:bui-mb-8{margin-bottom:var(--bui-space-8)}.md\:bui-mb-9{margin-bottom:var(--bui-space-9)}.md\:bui-mb-10{margin-bottom:var(--bui-space-10)}.md\:bui-mb-11{margin-bottom:var(--bui-space-11)}.md\:bui-mb-12{margin-bottom:var(--bui-space-12)}.md\:bui-mb-13{margin-bottom:var(--bui-space-13)}.md\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mb{margin-bottom:var(--mb-lg)}.lg\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.lg\:bui-mb-1{margin-bottom:var(--bui-space-1)}.lg\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.lg\:bui-mb-2{margin-bottom:var(--bui-space-2)}.lg\:bui-mb-3{margin-bottom:var(--bui-space-3)}.lg\:bui-mb-4{margin-bottom:var(--bui-space-4)}.lg\:bui-mb-5{margin-bottom:var(--bui-space-5)}.lg\:bui-mb-6{margin-bottom:var(--bui-space-6)}.lg\:bui-mb-7{margin-bottom:var(--bui-space-7)}.lg\:bui-mb-8{margin-bottom:var(--bui-space-8)}.lg\:bui-mb-9{margin-bottom:var(--bui-space-9)}.lg\:bui-mb-10{margin-bottom:var(--bui-space-10)}.lg\:bui-mb-11{margin-bottom:var(--bui-space-11)}.lg\:bui-mb-12{margin-bottom:var(--bui-space-12)}.lg\:bui-mb-13{margin-bottom:var(--bui-space-13)}.lg\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mb{margin-bottom:var(--mb-xl)}.xl\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xl\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xl\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xl\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xl\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xl\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xl\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xl\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xl\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xl\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xl\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xl\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xl\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xl\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xl\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xl\:bui-mb-14{margin-bottom:var(--bui-space-14)}}.bui-my{margin-top:var(--my);margin-bottom:var(--my)}.bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-my{margin-top:var(--my-xs);margin-bottom:var(--my-xs)}.xs\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xs\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xs\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xs\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xs\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xs\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xs\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xs\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xs\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xs\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xs\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xs\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xs\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xs\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xs\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xs\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-my{margin-top:var(--my-sm);margin-bottom:var(--my-sm)}.sm\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.sm\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.sm\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.sm\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.sm\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.sm\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.sm\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.sm\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.sm\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.sm\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.sm\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.sm\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.sm\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.sm\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.sm\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.sm\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-my{margin-top:var(--my-md);margin-bottom:var(--my-md)}.md\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.md\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.md\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.md\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.md\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.md\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.md\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.md\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.md\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.md\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.md\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.md\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.md\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.md\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.md\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.md\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-my{margin-top:var(--my-lg);margin-bottom:var(--my-lg)}.lg\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.lg\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.lg\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.lg\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.lg\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.lg\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.lg\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.lg\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.lg\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.lg\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.lg\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.lg\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.lg\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.lg\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.lg\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.lg\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-my{margin-top:var(--my-xl);margin-bottom:var(--my-xl)}.xl\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xl\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xl\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xl\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xl\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xl\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xl\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xl\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xl\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xl\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xl\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xl\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xl\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xl\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xl\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xl\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}.bui-mx{margin-left:var(--mx);margin-right:var(--mx)}.bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mx{margin-left:var(--mx-xs);margin-right:var(--mx-xs)}.xs\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xs\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xs\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xs\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xs\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xs\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xs\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xs\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xs\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xs\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xs\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xs\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xs\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xs\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xs\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xs\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mx{margin-left:var(--mx-sm);margin-right:var(--mx-sm)}.sm\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.sm\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.sm\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.sm\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.sm\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.sm\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.sm\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.sm\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.sm\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.sm\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.sm\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.sm\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.sm\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.sm\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.sm\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.sm\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mx{margin-left:var(--mx-md);margin-right:var(--mx-md)}.md\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.md\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.md\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.md\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.md\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.md\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.md\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.md\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.md\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.md\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.md\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.md\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.md\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.md\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.md\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.md\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mx{margin-left:var(--mx-lg);margin-right:var(--mx-lg)}.lg\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.lg\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.lg\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.lg\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.lg\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.lg\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.lg\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.lg\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.lg\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.lg\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.lg\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.lg\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.lg\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.lg\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.lg\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.lg\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mx{margin-left:var(--mx-xl);margin-right:var(--mx-xl)}.xl\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xl\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xl\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xl\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xl\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xl\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xl\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xl\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xl\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xl\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xl\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xl\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xl\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xl\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xl\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xl\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}.bui-display-none{display:none}.bui-display-inline{display:inline}.bui-display-inline-block{display:inline-block}.bui-display-block{display:block}@media (width>=640px){.xs\:bui-display-none{display:none}.xs\:bui-display-inline{display:inline}.xs\:bui-display-inline-block{display:inline-block}.xs\:bui-display-block{display:block}}@media (width>=768px){.sm\:bui-display-none{display:none}.sm\:bui-display-inline{display:inline}.sm\:bui-display-inline-block{display:inline-block}.sm\:bui-display-block{display:block}}@media (width>=1024px){.md\:bui-display-none{display:none}.md\:bui-display-inline{display:inline}.md\:bui-display-inline-block{display:inline-block}.md\:bui-display-block{display:block}}@media (width>=1280px){.lg\:bui-display-none{display:none}.lg\:bui-display-inline{display:inline}.lg\:bui-display-inline-block{display:inline-block}.lg\:bui-display-block{display:block}}@media (width>=1536px){.xl\:bui-display-none{display:none}.xl\:bui-display-inline{display:inline}.xl\:bui-display-inline-block{display:inline-block}.xl\:bui-display-block{display:block}}.bui-w{width:var(--width)}.bui-min-w{min-width:var(--min-width)}.bui-max-w{max-width:var(--max-width)}@media (width>=640px){.xs\:bui-w{width:var(--width)}.xs\:bui-min-w{min-width:var(--min-width)}.xs\:bui-max-w{max-width:var(--max-width)}}@media (width>=768px){.sm\:bui-w{width:var(--width)}.sm\:bui-min-w{min-width:var(--min-width)}.sm\:bui-max-w{max-width:var(--max-width)}}@media (width>=1024px){.md\:bui-w{width:var(--width)}.md\:bui-min-w{min-width:var(--min-width)}.md\:bui-max-w{max-width:var(--max-width)}}@media (width>=1280px){.lg\:bui-w{width:var(--width)}.lg\:bui-min-w{min-width:var(--min-width)}.lg\:bui-max-w{max-width:var(--max-width)}}@media (width>=1536px){.xl\:bui-w{width:var(--width)}.xl\:bui-min-w{min-width:var(--min-width)}.xl\:bui-max-w{max-width:var(--max-width)}}.bui-h{height:var(--height)}.bui-min-h{min-height:var(--min-height)}.bui-max-h{max-height:var(--max-height)}@media (width>=640px){.xs\:bui-h{height:var(--height)}.xs\:bui-min-h{min-height:var(--min-height)}.xs\:bui-max-h{max-height:var(--max-height)}}@media (width>=768px){.sm\:bui-h{height:var(--height)}.sm\:bui-min-h{min-height:var(--min-height)}.sm\:bui-max-h{max-height:var(--max-height)}}@media (width>=1024px){.md\:bui-h{height:var(--height)}.md\:bui-min-h{min-height:var(--min-height)}.md\:bui-max-h{max-height:var(--max-height)}}@media (width>=1280px){.lg\:bui-h{height:var(--height)}.lg\:bui-min-h{min-height:var(--min-height)}.lg\:bui-max-h{max-height:var(--max-height)}}@media (width>=1536px){.xl\:bui-h{height:var(--height)}.xl\:bui-min-h{min-height:var(--min-height)}.xl\:bui-max-h{max-height:var(--max-height)}}.bui-position-absolute{position:absolute}.bui-position-fixed{position:fixed}.bui-position-sticky{position:sticky}.bui-position-relative{position:relative}.bui-position-static{position:static}@media (width>=640px){.xs\:bui-position-absolute{position:absolute}.xs\:bui-position-fixed{position:fixed}.xs\:bui-position-sticky{position:sticky}.xs\:bui-position-relative{position:relative}.xs\:bui-position-static{position:static}}@media (width>=768px){.sm\:bui-position-absolute{position:absolute}.sm\:bui-position-fixed{position:fixed}.sm\:bui-position-sticky{position:sticky}.sm\:bui-position-relative{position:relative}.sm\:bui-position-static{position:static}}@media (width>=1024px){.md\:bui-position-absolute{position:absolute}.md\:bui-position-fixed{position:fixed}.md\:bui-position-sticky{position:sticky}.md\:bui-position-relative{position:relative}.md\:bui-position-static{position:static}}@media (width>=1280px){.lg\:bui-position-absolute{position:absolute}.lg\:bui-position-fixed{position:fixed}.lg\:bui-position-sticky{position:sticky}.lg\:bui-position-relative{position:relative}.lg\:bui-position-static{position:static}}@media (width>=1536px){.xl\:bui-position-absolute{position:absolute}.xl\:bui-position-fixed{position:fixed}.xl\:bui-position-sticky{position:sticky}.xl\:bui-position-relative{position:relative}.xl\:bui-position-static{position:static}}.bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.bui-col-span-1{grid-column:span 1/span 1}.bui-col-span-2{grid-column:span 2/span 2}.bui-col-span-3{grid-column:span 3/span 3}.bui-col-span-4{grid-column:span 4/span 4}.bui-col-span-5{grid-column:span 5/span 5}.bui-col-span-6{grid-column:span 6/span 6}.bui-col-span-7{grid-column:span 7/span 7}.bui-col-span-8{grid-column:span 8/span 8}.bui-col-span-9{grid-column:span 9/span 9}.bui-col-span-10{grid-column:span 10/span 10}.bui-col-span-11{grid-column:span 11/span 11}.bui-col-span-12{grid-column:span 12/span 12}.bui-col-span-auto{grid-column:span auto/span auto}.bui-col-start-1{grid-column-start:1}.bui-col-start-2{grid-column-start:2}.bui-col-start-3{grid-column-start:3}.bui-col-start-4{grid-column-start:4}.bui-col-start-5{grid-column-start:5}.bui-col-start-6{grid-column-start:6}.bui-col-start-7{grid-column-start:7}.bui-col-start-8{grid-column-start:8}.bui-col-start-9{grid-column-start:9}.bui-col-start-10{grid-column-start:10}.bui-col-start-11{grid-column-start:11}.bui-col-start-12{grid-column-start:12}.bui-col-start-13{grid-column-start:13}.bui-col-start-auto{grid-column-start:auto}.bui-col-end-1{grid-column-end:1}.bui-col-end-2{grid-column-end:2}.bui-col-end-3{grid-column-end:3}.bui-col-end-4{grid-column-end:4}.bui-col-end-5{grid-column-end:5}.bui-col-end-6{grid-column-end:6}.bui-col-end-7{grid-column-end:7}.bui-col-end-8{grid-column-end:8}.bui-col-end-9{grid-column-end:9}.bui-col-end-10{grid-column-end:10}.bui-col-end-11{grid-column-end:11}.bui-col-end-12{grid-column-end:12}.bui-col-end-13{grid-column-end:13}.bui-col-end-auto{grid-column-end:auto}.bui-row-span-1{grid-row:span 1/span 1}.bui-row-span-2{grid-row:span 2/span 2}.bui-row-span-3{grid-row:span 3/span 3}.bui-row-span-4{grid-row:span 4/span 4}.bui-row-span-5{grid-row:span 5/span 5}.bui-row-span-6{grid-row:span 6/span 6}.bui-row-span-7{grid-row:span 7/span 7}.bui-row-span-8{grid-row:span 8/span 8}.bui-row-span-9{grid-row:span 9/span 9}.bui-row-span-10{grid-row:span 10/span 10}.bui-row-span-11{grid-row:span 11/span 11}.bui-row-span-12{grid-row:span 12/span 12}.bui-row-span-auto{grid-row:span auto/span auto}@media (width>=640px){.xs\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xs\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xs\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xs\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xs\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xs\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xs\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xs\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xs\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xs\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xs\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xs\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xs\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xs\:bui-col-span-1{grid-column:span 1/span 1}.xs\:bui-col-span-2{grid-column:span 2/span 2}.xs\:bui-col-span-3{grid-column:span 3/span 3}.xs\:bui-col-span-4{grid-column:span 4/span 4}.xs\:bui-col-span-5{grid-column:span 5/span 5}.xs\:bui-col-span-6{grid-column:span 6/span 6}.xs\:bui-col-span-7{grid-column:span 7/span 7}.xs\:bui-col-span-8{grid-column:span 8/span 8}.xs\:bui-col-span-9{grid-column:span 9/span 9}.xs\:bui-col-span-10{grid-column:span 10/span 10}.xs\:bui-col-span-11{grid-column:span 11/span 11}.xs\:bui-col-span-12{grid-column:span 12/span 12}.xs\:bui-col-span-auto{grid-column:span auto/span auto}.xs\:bui-col-start-1{grid-column-start:1}.xs\:bui-col-start-2{grid-column-start:2}.xs\:bui-col-start-3{grid-column-start:3}.xs\:bui-col-start-4{grid-column-start:4}.xs\:bui-col-start-5{grid-column-start:5}.xs\:bui-col-start-6{grid-column-start:6}.xs\:bui-col-start-7{grid-column-start:7}.xs\:bui-col-start-8{grid-column-start:8}.xs\:bui-col-start-9{grid-column-start:9}.xs\:bui-col-start-10{grid-column-start:10}.xs\:bui-col-start-11{grid-column-start:11}.xs\:bui-col-start-12{grid-column-start:12}.xs\:bui-col-start-13{grid-column-start:13}.xs\:bui-col-start-auto{grid-column-start:auto}.xs\:bui-col-end-1{grid-column-end:1}.xs\:bui-col-end-2{grid-column-end:2}.xs\:bui-col-end-3{grid-column-end:3}.xs\:bui-col-end-4{grid-column-end:4}.xs\:bui-col-end-5{grid-column-end:5}.xs\:bui-col-end-6{grid-column-end:6}.xs\:bui-col-end-7{grid-column-end:7}.xs\:bui-col-end-8{grid-column-end:8}.xs\:bui-col-end-9{grid-column-end:9}.xs\:bui-col-end-10{grid-column-end:10}.xs\:bui-col-end-11{grid-column-end:11}.xs\:bui-col-end-12{grid-column-end:12}.xs\:bui-col-end-13{grid-column-end:13}.xs\:bui-col-end-auto{grid-column-end:auto}.xs\:bui-row-span-1{grid-row:span 1/span 1}.xs\:bui-row-span-2{grid-row:span 2/span 2}.xs\:bui-row-span-3{grid-row:span 3/span 3}.xs\:bui-row-span-4{grid-row:span 4/span 4}.xs\:bui-row-span-5{grid-row:span 5/span 5}.xs\:bui-row-span-6{grid-row:span 6/span 6}.xs\:bui-row-span-7{grid-row:span 7/span 7}.xs\:bui-row-span-8{grid-row:span 8/span 8}.xs\:bui-row-span-9{grid-row:span 9/span 9}.xs\:bui-row-span-10{grid-row:span 10/span 10}.xs\:bui-row-span-11{grid-row:span 11/span 11}.xs\:bui-row-span-12{grid-row:span 12/span 12}.xs\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=768px){.sm\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.sm\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.sm\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.sm\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.sm\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:bui-col-span-1{grid-column:span 1/span 1}.sm\:bui-col-span-2{grid-column:span 2/span 2}.sm\:bui-col-span-3{grid-column:span 3/span 3}.sm\:bui-col-span-4{grid-column:span 4/span 4}.sm\:bui-col-span-5{grid-column:span 5/span 5}.sm\:bui-col-span-6{grid-column:span 6/span 6}.sm\:bui-col-span-7{grid-column:span 7/span 7}.sm\:bui-col-span-8{grid-column:span 8/span 8}.sm\:bui-col-span-9{grid-column:span 9/span 9}.sm\:bui-col-span-10{grid-column:span 10/span 10}.sm\:bui-col-span-11{grid-column:span 11/span 11}.sm\:bui-col-span-12{grid-column:span 12/span 12}.sm\:bui-col-span-auto{grid-column:span auto/span auto}.sm\:bui-col-start-1{grid-column-start:1}.sm\:bui-col-start-2{grid-column-start:2}.sm\:bui-col-start-3{grid-column-start:3}.sm\:bui-col-start-4{grid-column-start:4}.sm\:bui-col-start-5{grid-column-start:5}.sm\:bui-col-start-6{grid-column-start:6}.sm\:bui-col-start-7{grid-column-start:7}.sm\:bui-col-start-8{grid-column-start:8}.sm\:bui-col-start-9{grid-column-start:9}.sm\:bui-col-start-10{grid-column-start:10}.sm\:bui-col-start-11{grid-column-start:11}.sm\:bui-col-start-12{grid-column-start:12}.sm\:bui-col-start-13{grid-column-start:13}.sm\:bui-col-start-auto{grid-column-start:auto}.sm\:bui-col-end-1{grid-column-end:1}.sm\:bui-col-end-2{grid-column-end:2}.sm\:bui-col-end-3{grid-column-end:3}.sm\:bui-col-end-4{grid-column-end:4}.sm\:bui-col-end-5{grid-column-end:5}.sm\:bui-col-end-6{grid-column-end:6}.sm\:bui-col-end-7{grid-column-end:7}.sm\:bui-col-end-8{grid-column-end:8}.sm\:bui-col-end-9{grid-column-end:9}.sm\:bui-col-end-10{grid-column-end:10}.sm\:bui-col-end-11{grid-column-end:11}.sm\:bui-col-end-12{grid-column-end:12}.sm\:bui-col-end-13{grid-column-end:13}.sm\:bui-col-end-auto{grid-column-end:auto}.sm\:bui-row-span-1{grid-row:span 1/span 1}.sm\:bui-row-span-2{grid-row:span 2/span 2}.sm\:bui-row-span-3{grid-row:span 3/span 3}.sm\:bui-row-span-4{grid-row:span 4/span 4}.sm\:bui-row-span-5{grid-row:span 5/span 5}.sm\:bui-row-span-6{grid-row:span 6/span 6}.sm\:bui-row-span-7{grid-row:span 7/span 7}.sm\:bui-row-span-8{grid-row:span 8/span 8}.sm\:bui-row-span-9{grid-row:span 9/span 9}.sm\:bui-row-span-10{grid-row:span 10/span 10}.sm\:bui-row-span-11{grid-row:span 11/span 11}.sm\:bui-row-span-12{grid-row:span 12/span 12}.sm\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1024px){.md\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.md\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.md\:bui-col-span-1{grid-column:span 1/span 1}.md\:bui-col-span-2{grid-column:span 2/span 2}.md\:bui-col-span-3{grid-column:span 3/span 3}.md\:bui-col-span-4{grid-column:span 4/span 4}.md\:bui-col-span-5{grid-column:span 5/span 5}.md\:bui-col-span-6{grid-column:span 6/span 6}.md\:bui-col-span-7{grid-column:span 7/span 7}.md\:bui-col-span-8{grid-column:span 8/span 8}.md\:bui-col-span-9{grid-column:span 9/span 9}.md\:bui-col-span-10{grid-column:span 10/span 10}.md\:bui-col-span-11{grid-column:span 11/span 11}.md\:bui-col-span-12{grid-column:span 12/span 12}.md\:bui-col-span-auto{grid-column:span auto/span auto}.md\:bui-col-start-1{grid-column-start:1}.md\:bui-col-start-2{grid-column-start:2}.md\:bui-col-start-3{grid-column-start:3}.md\:bui-col-start-4{grid-column-start:4}.md\:bui-col-start-5{grid-column-start:5}.md\:bui-col-start-6{grid-column-start:6}.md\:bui-col-start-7{grid-column-start:7}.md\:bui-col-start-8{grid-column-start:8}.md\:bui-col-start-9{grid-column-start:9}.md\:bui-col-start-10{grid-column-start:10}.md\:bui-col-start-11{grid-column-start:11}.md\:bui-col-start-12{grid-column-start:12}.md\:bui-col-start-13{grid-column-start:13}.md\:bui-col-start-auto{grid-column-start:auto}.md\:bui-col-end-1{grid-column-end:1}.md\:bui-col-end-2{grid-column-end:2}.md\:bui-col-end-3{grid-column-end:3}.md\:bui-col-end-4{grid-column-end:4}.md\:bui-col-end-5{grid-column-end:5}.md\:bui-col-end-6{grid-column-end:6}.md\:bui-col-end-7{grid-column-end:7}.md\:bui-col-end-8{grid-column-end:8}.md\:bui-col-end-9{grid-column-end:9}.md\:bui-col-end-10{grid-column-end:10}.md\:bui-col-end-11{grid-column-end:11}.md\:bui-col-end-12{grid-column-end:12}.md\:bui-col-end-13{grid-column-end:13}.md\:bui-col-end-auto{grid-column-end:auto}.md\:bui-row-span-1{grid-row:span 1/span 1}.md\:bui-row-span-2{grid-row:span 2/span 2}.md\:bui-row-span-3{grid-row:span 3/span 3}.md\:bui-row-span-4{grid-row:span 4/span 4}.md\:bui-row-span-5{grid-row:span 5/span 5}.md\:bui-row-span-6{grid-row:span 6/span 6}.md\:bui-row-span-7{grid-row:span 7/span 7}.md\:bui-row-span-8{grid-row:span 8/span 8}.md\:bui-row-span-9{grid-row:span 9/span 9}.md\:bui-row-span-10{grid-row:span 10/span 10}.md\:bui-row-span-11{grid-row:span 11/span 11}.md\:bui-row-span-12{grid-row:span 12/span 12}.md\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1280px){.lg\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.lg\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.lg\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.lg\:bui-col-span-1{grid-column:span 1/span 1}.lg\:bui-col-span-2{grid-column:span 2/span 2}.lg\:bui-col-span-3{grid-column:span 3/span 3}.lg\:bui-col-span-4{grid-column:span 4/span 4}.lg\:bui-col-span-5{grid-column:span 5/span 5}.lg\:bui-col-span-6{grid-column:span 6/span 6}.lg\:bui-col-span-7{grid-column:span 7/span 7}.lg\:bui-col-span-8{grid-column:span 8/span 8}.lg\:bui-col-span-9{grid-column:span 9/span 9}.lg\:bui-col-span-10{grid-column:span 10/span 10}.lg\:bui-col-span-11{grid-column:span 11/span 11}.lg\:bui-col-span-12{grid-column:span 12/span 12}.lg\:bui-col-span-auto{grid-column:span auto/span auto}.lg\:bui-col-start-1{grid-column-start:1}.lg\:bui-col-start-2{grid-column-start:2}.lg\:bui-col-start-3{grid-column-start:3}.lg\:bui-col-start-4{grid-column-start:4}.lg\:bui-col-start-5{grid-column-start:5}.lg\:bui-col-start-6{grid-column-start:6}.lg\:bui-col-start-7{grid-column-start:7}.lg\:bui-col-start-8{grid-column-start:8}.lg\:bui-col-start-9{grid-column-start:9}.lg\:bui-col-start-10{grid-column-start:10}.lg\:bui-col-start-11{grid-column-start:11}.lg\:bui-col-start-12{grid-column-start:12}.lg\:bui-col-start-13{grid-column-start:13}.lg\:bui-col-start-auto{grid-column-start:auto}.lg\:bui-col-end-1{grid-column-end:1}.lg\:bui-col-end-2{grid-column-end:2}.lg\:bui-col-end-3{grid-column-end:3}.lg\:bui-col-end-4{grid-column-end:4}.lg\:bui-col-end-5{grid-column-end:5}.lg\:bui-col-end-6{grid-column-end:6}.lg\:bui-col-end-7{grid-column-end:7}.lg\:bui-col-end-8{grid-column-end:8}.lg\:bui-col-end-9{grid-column-end:9}.lg\:bui-col-end-10{grid-column-end:10}.lg\:bui-col-end-11{grid-column-end:11}.lg\:bui-col-end-12{grid-column-end:12}.lg\:bui-col-end-13{grid-column-end:13}.lg\:bui-col-end-auto{grid-column-end:auto}.lg\:bui-row-span-1{grid-row:span 1/span 1}.lg\:bui-row-span-2{grid-row:span 2/span 2}.lg\:bui-row-span-3{grid-row:span 3/span 3}.lg\:bui-row-span-4{grid-row:span 4/span 4}.lg\:bui-row-span-5{grid-row:span 5/span 5}.lg\:bui-row-span-6{grid-row:span 6/span 6}.lg\:bui-row-span-7{grid-row:span 7/span 7}.lg\:bui-row-span-8{grid-row:span 8/span 8}.lg\:bui-row-span-9{grid-row:span 9/span 9}.lg\:bui-row-span-10{grid-row:span 10/span 10}.lg\:bui-row-span-11{grid-row:span 11/span 11}.lg\:bui-row-span-12{grid-row:span 12/span 12}.lg\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1536px){.xl\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xl\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xl\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xl\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xl\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xl\:bui-col-span-1{grid-column:span 1/span 1}.xl\:bui-col-span-2{grid-column:span 2/span 2}.xl\:bui-col-span-3{grid-column:span 3/span 3}.xl\:bui-col-span-4{grid-column:span 4/span 4}.xl\:bui-col-span-5{grid-column:span 5/span 5}.xl\:bui-col-span-6{grid-column:span 6/span 6}.xl\:bui-col-span-7{grid-column:span 7/span 7}.xl\:bui-col-span-8{grid-column:span 8/span 8}.xl\:bui-col-span-9{grid-column:span 9/span 9}.xl\:bui-col-span-10{grid-column:span 10/span 10}.xl\:bui-col-span-11{grid-column:span 11/span 11}.xl\:bui-col-span-12{grid-column:span 12/span 12}.xl\:bui-col-span-auto{grid-column:span auto/span auto}.xl\:bui-col-start-1{grid-column-start:1}.xl\:bui-col-start-2{grid-column-start:2}.xl\:bui-col-start-3{grid-column-start:3}.xl\:bui-col-start-4{grid-column-start:4}.xl\:bui-col-start-5{grid-column-start:5}.xl\:bui-col-start-6{grid-column-start:6}.xl\:bui-col-start-7{grid-column-start:7}.xl\:bui-col-start-8{grid-column-start:8}.xl\:bui-col-start-9{grid-column-start:9}.xl\:bui-col-start-10{grid-column-start:10}.xl\:bui-col-start-11{grid-column-start:11}.xl\:bui-col-start-12{grid-column-start:12}.xl\:bui-col-start-13{grid-column-start:13}.xl\:bui-col-start-auto{grid-column-start:auto}.xl\:bui-col-end-1{grid-column-end:1}.xl\:bui-col-end-2{grid-column-end:2}.xl\:bui-col-end-3{grid-column-end:3}.xl\:bui-col-end-4{grid-column-end:4}.xl\:bui-col-end-5{grid-column-end:5}.xl\:bui-col-end-6{grid-column-end:6}.xl\:bui-col-end-7{grid-column-end:7}.xl\:bui-col-end-8{grid-column-end:8}.xl\:bui-col-end-9{grid-column-end:9}.xl\:bui-col-end-10{grid-column-end:10}.xl\:bui-col-end-11{grid-column-end:11}.xl\:bui-col-end-12{grid-column-end:12}.xl\:bui-col-end-13{grid-column-end:13}.xl\:bui-col-end-auto{grid-column-end:auto}.xl\:bui-row-span-1{grid-row:span 1/span 1}.xl\:bui-row-span-2{grid-row:span 2/span 2}.xl\:bui-row-span-3{grid-row:span 3/span 3}.xl\:bui-row-span-4{grid-row:span 4/span 4}.xl\:bui-row-span-5{grid-row:span 5/span 5}.xl\:bui-row-span-6{grid-row:span 6/span 6}.xl\:bui-row-span-7{grid-row:span 7/span 7}.xl\:bui-row-span-8{grid-row:span 8/span 8}.xl\:bui-row-span-9{grid-row:span 9/span 9}.xl\:bui-row-span-10{grid-row:span 10/span 10}.xl\:bui-row-span-11{grid-row:span 11/span 11}.xl\:bui-row-span-12{grid-row:span 12/span 12}.xl\:bui-row-span-auto{grid-row:span auto/span auto}}.bui-gap{gap:var(--gap)}.bui-gap-0\.5{gap:var(--bui-space-0_5)}.bui-gap-1{gap:var(--bui-space-1)}.bui-gap-1\.5{gap:var(--bui-space-1_5)}.bui-gap-2{gap:var(--bui-space-2)}.bui-gap-3{gap:var(--bui-space-3)}.bui-gap-4{gap:var(--bui-space-4)}.bui-gap-5{gap:var(--bui-space-5)}.bui-gap-6{gap:var(--bui-space-6)}.bui-gap-7{gap:var(--bui-space-7)}.bui-gap-8{gap:var(--bui-space-8)}.bui-gap-9{gap:var(--bui-space-9)}.bui-gap-10{gap:var(--bui-space-10)}.bui-gap-11{gap:var(--bui-space-11)}.bui-gap-12{gap:var(--bui-space-12)}.bui-gap-13{gap:var(--bui-space-13)}.bui-gap-14{gap:var(--bui-space-14)}@media (width>=640px){.xs\:bui-gap{gap:var(--gap-xs)}.xs\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xs\:bui-gap-1{gap:var(--bui-space-1)}.xs\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xs\:bui-gap-2{gap:var(--bui-space-2)}.xs\:bui-gap-3{gap:var(--bui-space-3)}.xs\:bui-gap-4{gap:var(--bui-space-4)}.xs\:bui-gap-5{gap:var(--bui-space-5)}.xs\:bui-gap-6{gap:var(--bui-space-6)}.xs\:bui-gap-7{gap:var(--bui-space-7)}.xs\:bui-gap-8{gap:var(--bui-space-8)}.xs\:bui-gap-9{gap:var(--bui-space-9)}.xs\:bui-gap-10{gap:var(--bui-space-10)}.xs\:bui-gap-11{gap:var(--bui-space-11)}.xs\:bui-gap-12{gap:var(--bui-space-12)}.xs\:bui-gap-13{gap:var(--bui-space-13)}.xs\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-gap{gap:var(--gap-sm)}.sm\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.sm\:bui-gap-1{gap:var(--bui-space-1)}.sm\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.sm\:bui-gap-2{gap:var(--bui-space-2)}.sm\:bui-gap-3{gap:var(--bui-space-3)}.sm\:bui-gap-4{gap:var(--bui-space-4)}.sm\:bui-gap-5{gap:var(--bui-space-5)}.sm\:bui-gap-6{gap:var(--bui-space-6)}.sm\:bui-gap-7{gap:var(--bui-space-7)}.sm\:bui-gap-8{gap:var(--bui-space-8)}.sm\:bui-gap-9{gap:var(--bui-space-9)}.sm\:bui-gap-10{gap:var(--bui-space-10)}.sm\:bui-gap-11{gap:var(--bui-space-11)}.sm\:bui-gap-12{gap:var(--bui-space-12)}.sm\:bui-gap-13{gap:var(--bui-space-13)}.sm\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-gap{gap:var(--gap-md)}.md\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.md\:bui-gap-1{gap:var(--bui-space-1)}.md\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.md\:bui-gap-2{gap:var(--bui-space-2)}.md\:bui-gap-3{gap:var(--bui-space-3)}.md\:bui-gap-4{gap:var(--bui-space-4)}.md\:bui-gap-5{gap:var(--bui-space-5)}.md\:bui-gap-6{gap:var(--bui-space-6)}.md\:bui-gap-7{gap:var(--bui-space-7)}.md\:bui-gap-8{gap:var(--bui-space-8)}.md\:bui-gap-9{gap:var(--bui-space-9)}.md\:bui-gap-10{gap:var(--bui-space-10)}.md\:bui-gap-11{gap:var(--bui-space-11)}.md\:bui-gap-12{gap:var(--bui-space-12)}.md\:bui-gap-13{gap:var(--bui-space-13)}.md\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-gap{gap:var(--gap-lg)}.lg\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.lg\:bui-gap-1{gap:var(--bui-space-1)}.lg\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.lg\:bui-gap-2{gap:var(--bui-space-2)}.lg\:bui-gap-3{gap:var(--bui-space-3)}.lg\:bui-gap-4{gap:var(--bui-space-4)}.lg\:bui-gap-5{gap:var(--bui-space-5)}.lg\:bui-gap-6{gap:var(--bui-space-6)}.lg\:bui-gap-7{gap:var(--bui-space-7)}.lg\:bui-gap-8{gap:var(--bui-space-8)}.lg\:bui-gap-9{gap:var(--bui-space-9)}.lg\:bui-gap-10{gap:var(--bui-space-10)}.lg\:bui-gap-11{gap:var(--bui-space-11)}.lg\:bui-gap-12{gap:var(--bui-space-12)}.lg\:bui-gap-13{gap:var(--bui-space-13)}.lg\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-gap{gap:var(--gap-xl)}.xl\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xl\:bui-gap-1{gap:var(--bui-space-1)}.xl\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xl\:bui-gap-2{gap:var(--bui-space-2)}.xl\:bui-gap-3{gap:var(--bui-space-3)}.xl\:bui-gap-4{gap:var(--bui-space-4)}.xl\:bui-gap-5{gap:var(--bui-space-5)}.xl\:bui-gap-6{gap:var(--bui-space-6)}.xl\:bui-gap-7{gap:var(--bui-space-7)}.xl\:bui-gap-8{gap:var(--bui-space-8)}.xl\:bui-gap-9{gap:var(--bui-space-9)}.xl\:bui-gap-10{gap:var(--bui-space-10)}.xl\:bui-gap-11{gap:var(--bui-space-11)}.xl\:bui-gap-12{gap:var(--bui-space-12)}.xl\:bui-gap-13{gap:var(--bui-space-13)}.xl\:bui-gap-14{gap:var(--bui-space-14)}}.bui-align-start{align-items:start}.bui-align-center{align-items:center}.bui-align-end{align-items:end}.bui-align-baseline{align-items:baseline}.bui-align-stretch{align-items:stretch}.bui-jc-start{justify-content:start}.bui-jc-center{justify-content:center}.bui-jc-end{justify-content:end}.bui-jc-between{justify-content:space-between}.bui-fd-row{flex-direction:row}.bui-fd-column{flex-direction:column}.bui-fd-row-reverse{flex-direction:row-reverse}.bui-fd-column-reverse{flex-direction:column-reverse}@media (width>=640px){.xs\:bui-align-start{align-items:start}.xs\:bui-align-center{align-items:center}.xs\:bui-align-end{align-items:end}.xs\:bui-align-stretch{align-items:stretch}.xs\:bui-jc-start{justify-content:start}.xs\:bui-jc-center{justify-content:center}.xs\:bui-jc-end{justify-content:end}.xs\:bui-jc-between{justify-content:space-between}.xs\:bui-fd-row{flex-direction:row}.xs\:bui-fd-column{flex-direction:column}.xs\:bui-fd-row-reverse{flex-direction:row-reverse}.xs\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=768px){.sm\:bui-align-start{align-items:start}.sm\:bui-align-center{align-items:center}.sm\:bui-align-end{align-items:end}.sm\:bui-align-stretch{align-items:stretch}.sm\:bui-jc-start{justify-content:start}.sm\:bui-jc-center{justify-content:center}.sm\:bui-jc-end{justify-content:end}.sm\:bui-jc-between{justify-content:space-between}.sm\:bui-fd-row{flex-direction:row}.sm\:bui-fd-column{flex-direction:column}.sm\:bui-fd-row-reverse{flex-direction:row-reverse}.sm\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1024px){.md\:bui-align-start{align-items:start}.md\:bui-align-center{align-items:center}.md\:bui-align-end{align-items:end}.md\:bui-align-stretch{align-items:stretch}.md\:bui-jc-start{justify-content:start}.md\:bui-jc-center{justify-content:center}.md\:bui-jc-end{justify-content:end}.md\:bui-jc-between{justify-content:space-between}.md\:bui-fd-row{flex-direction:row}.md\:bui-fd-column{flex-direction:column}.md\:bui-fd-row-reverse{flex-direction:row-reverse}.md\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1280px){.lg\:bui-align-start{align-items:start}.lg\:bui-align-center{align-items:center}.lg\:bui-align-end{align-items:end}.lg\:bui-align-stretch{align-items:stretch}.lg\:bui-jc-start{justify-content:start}.lg\:bui-jc-center{justify-content:center}.lg\:bui-jc-end{justify-content:end}.lg\:bui-jc-between{justify-content:space-between}.lg\:bui-fd-row{flex-direction:row}.lg\:bui-fd-column{flex-direction:column}.lg\:bui-fd-row-reverse{flex-direction:row-reverse}.lg\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1536px){.xl\:bui-align-start{align-items:start}.xl\:bui-align-center{align-items:center}.xl\:bui-align-end{align-items:end}.xl\:bui-align-stretch{align-items:stretch}.xl\:bui-jc-start{justify-content:start}.xl\:bui-jc-center{justify-content:center}.xl\:bui-jc-end{justify-content:end}.xl\:bui-jc-between{justify-content:space-between}.xl\:bui-fd-row{flex-direction:row}.xl\:bui-fd-column{flex-direction:column}.xl\:bui-fd-row-reverse{flex-direction:row-reverse}.xl\:bui-fd-column-reverse{flex-direction:column-reverse}}} \ No newline at end of file +@layer tokens{:root{--bui-font-regular:system-ui;--bui-font-monospace:ui-monospace,"Menlo","Monaco","Consolas","Liberation Mono","Courier New",monospace;--bui-font-weight-regular:400;--bui-font-weight-bold:600;--bui-font-size-1:.625rem;--bui-font-size-2:.75rem;--bui-font-size-3:.875rem;--bui-font-size-4:1rem;--bui-font-size-5:1.25rem;--bui-font-size-6:1.5rem;--bui-font-size-7:2rem;--bui-font-size-8:3rem;--bui-font-size-9:4rem;--bui-font-size-10:5.75rem;--bui-space:.25rem;--bui-space-0_5:calc(var(--bui-space)*.5);--bui-space-1:var(--bui-space);--bui-space-1_5:calc(var(--bui-space)*1.5);--bui-space-2:calc(var(--bui-space)*2);--bui-space-3:calc(var(--bui-space)*3);--bui-space-4:calc(var(--bui-space)*4);--bui-space-5:calc(var(--bui-space)*5);--bui-space-6:calc(var(--bui-space)*6);--bui-space-7:calc(var(--bui-space)*7);--bui-space-8:calc(var(--bui-space)*8);--bui-space-9:calc(var(--bui-space)*9);--bui-space-10:calc(var(--bui-space)*10);--bui-space-11:calc(var(--bui-space)*11);--bui-space-12:calc(var(--bui-space)*12);--bui-space-13:calc(var(--bui-space)*13);--bui-space-14:calc(var(--bui-space)*14);--bui-radius-1:calc(.125rem);--bui-radius-2:calc(.25rem);--bui-radius-3:calc(.5rem);--bui-radius-4:calc(.75rem);--bui-radius-5:calc(1rem);--bui-radius-6:calc(1.25rem);--bui-radius-full:9999px;--bui-black:#000;--bui-white:#fff;--bui-gray-1:#f8f8f8;--bui-gray-2:#ececec;--bui-gray-3:#d9d9d9;--bui-gray-4:#c1c1c1;--bui-gray-5:#9e9e9e;--bui-gray-6:#8c8c8c;--bui-gray-7:#757575;--bui-gray-8:#595959;--bui-bg-surface-0:var(--bui-gray-1);--bui-bg-surface-1:var(--bui-white);--bui-bg-surface-2:var(--bui-gray-1);--bui-bg-surface-3:var(--bui-gray-2);--bui-bg-solid:#1f5493;--bui-bg-solid-hover:#163a66;--bui-bg-solid-pressed:#0f2b4e;--bui-bg-solid-disabled:#163a66;--bui-bg-neutral-on-surface-0:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-0-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-0-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-0-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-1:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-1-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-1-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-1-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-2:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-2-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-2-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-2-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-3:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-3-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-3-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-3-disabled:oklch(0% 0 0/.06);--bui-bg-danger:#feebe7;--bui-bg-warning:#fff2b2;--bui-bg-success:#e6f6eb;--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#1f5493;--bui-fg-link-hover:#1f2d5c;--bui-fg-disabled:#9e9e9e;--bui-fg-solid:var(--bui-white);--bui-fg-solid-disabled:#98a8bc;--bui-fg-tint:#1f5493;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#0000001a;--bui-border-hover:#0003;--bui-border-pressed:#0006;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#a0a0a03b;--bui-scrollbar-thumb:#a0a0a0;--bui-shadow-overlay:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--bui-animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite}[data-theme-mode=dark]{--bui-gray-1:#191919;--bui-gray-2:#242424;--bui-gray-3:#373737;--bui-gray-4:#464646;--bui-gray-5:#575757;--bui-gray-6:#7b7b7b;--bui-gray-7:#9e9e9e;--bui-gray-8:#b4b4b4;--bui-bg-surface-0:#333;--bui-bg-surface-1:var(--bui-gray-1);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-surface-3:var(--bui-gray-3);--bui-bg-solid:#9cc9ff;--bui-bg-solid-hover:#83b9fd;--bui-bg-solid-pressed:#83b9fd;--bui-bg-solid-disabled:#1b3d68;--bui-bg-neutral-on-surface-0:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-0-hover:oklch(100% 0 0/.14);--bui-bg-neutral-on-surface-0-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-0-disabled:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-1:oklch(100% 0 0/.06);--bui-bg-neutral-on-surface-1-hover:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-1-pressed:oklch(100% 0 0/.16);--bui-bg-neutral-on-surface-1-disabled:oklch(100% 0 0/.06);--bui-bg-neutral-on-surface-2:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-2-hover:oklch(100% 0 0/.12);--bui-bg-neutral-on-surface-2-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-2-disabled:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-3:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-3-hover:oklch(100% 0 0/.12);--bui-bg-neutral-on-surface-3-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-3-disabled:oklch(100% 0 0/.08);--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#9cc9ff;--bui-fg-link-hover:#7eb5f7;--bui-fg-disabled:var(--bui-gray-7);--bui-fg-solid:#101821;--bui-fg-solid-disabled:#6191cc;--bui-fg-tint:#9cc9ff;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#ffffff1f;--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#3636363a;--bui-scrollbar-thumb:#575757;--bui-shadow-overlay:none}}@layer base{*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:100%;tab-size:4;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;line-height:1.15}body{margin:0}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{border-color:currentColor}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}:where(a){color:inherit;text-decoration:none}@keyframes pulse{50%{opacity:.5}}body{background-color:var(--bui-bg-surface-0);color:var(--bui-fg-primary);font-family:var(--bui-font-regular);font-weight:var(--bui-font-weight-regular);font-size:var(--bui-font-size-3);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}[data-theme-mode=dark]{color-scheme:dark}[data-theme-mode=light]{color-scheme:light}}@layer components;@layer utilities{.bui-p{padding:var(--p)}.bui-p-0\.5{padding:var(--bui-space-0_5)}.bui-p-1{padding:var(--bui-space-1)}.bui-p-1\.5{padding:var(--bui-space-1_5)}.bui-p-2{padding:var(--bui-space-2)}.bui-p-3{padding:var(--bui-space-3)}.bui-p-4{padding:var(--bui-space-4)}.bui-p-5{padding:var(--bui-space-5)}.bui-p-6{padding:var(--bui-space-6)}.bui-p-7{padding:var(--bui-space-7)}.bui-p-8{padding:var(--bui-space-8)}.bui-p-9{padding:var(--bui-space-9)}.bui-p-10{padding:var(--bui-space-10)}.bui-p-11{padding:var(--bui-space-11)}.bui-p-12{padding:var(--bui-space-12)}.bui-p-13{padding:var(--bui-space-13)}.bui-p-14{padding:var(--bui-space-14)}@media (width>=640px){.xs\:bui-p{padding:var(--p-xs)}.xs\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xs\:bui-p-1{padding:var(--bui-space-1)}.xs\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xs\:bui-p-2{padding:var(--bui-space-2)}.xs\:bui-p-3{padding:var(--bui-space-3)}.xs\:bui-p-4{padding:var(--bui-space-4)}.xs\:bui-p-5{padding:var(--bui-space-5)}.xs\:bui-p-6{padding:var(--bui-space-6)}.xs\:bui-p-7{padding:var(--bui-space-7)}.xs\:bui-p-8{padding:var(--bui-space-8)}.xs\:bui-p-9{padding:var(--bui-space-9)}.xs\:bui-p-10{padding:var(--bui-space-10)}.xs\:bui-p-11{padding:var(--bui-space-11)}.xs\:bui-p-12{padding:var(--bui-space-12)}.xs\:bui-p-13{padding:var(--bui-space-13)}.xs\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-p{padding:var(--p-sm)}.sm\:bui-p-0\.5{padding:var(--bui-space-0_5)}.sm\:bui-p-1{padding:var(--bui-space-1)}.sm\:bui-p-1\.5{padding:var(--bui-space-1_5)}.sm\:bui-p-2{padding:var(--bui-space-2)}.sm\:bui-p-3{padding:var(--bui-space-3)}.sm\:bui-p-4{padding:var(--bui-space-4)}.sm\:bui-p-5{padding:var(--bui-space-5)}.sm\:bui-p-6{padding:var(--bui-space-6)}.sm\:bui-p-7{padding:var(--bui-space-7)}.sm\:bui-p-8{padding:var(--bui-space-8)}.sm\:bui-p-9{padding:var(--bui-space-9)}.sm\:bui-p-10{padding:var(--bui-space-10)}.sm\:bui-p-11{padding:var(--bui-space-11)}.sm\:bui-p-12{padding:var(--bui-space-12)}.sm\:bui-p-13{padding:var(--bui-space-13)}.sm\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-p{padding:var(--p-md)}.md\:bui-p-0\.5{padding:var(--bui-space-0_5)}.md\:bui-p-1{padding:var(--bui-space-1)}.md\:bui-p-1\.5{padding:var(--bui-space-1_5)}.md\:bui-p-2{padding:var(--bui-space-2)}.md\:bui-p-3{padding:var(--bui-space-3)}.md\:bui-p-4{padding:var(--bui-space-4)}.md\:bui-p-5{padding:var(--bui-space-5)}.md\:bui-p-6{padding:var(--bui-space-6)}.md\:bui-p-7{padding:var(--bui-space-7)}.md\:bui-p-8{padding:var(--bui-space-8)}.md\:bui-p-9{padding:var(--bui-space-9)}.md\:bui-p-10{padding:var(--bui-space-10)}.md\:bui-p-11{padding:var(--bui-space-11)}.md\:bui-p-12{padding:var(--bui-space-12)}.md\:bui-p-13{padding:var(--bui-space-13)}.md\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-p{padding:var(--p-lg)}.lg\:bui-p-0\.5{padding:var(--bui-space-0_5)}.lg\:bui-p-1{padding:var(--bui-space-1)}.lg\:bui-p-1\.5{padding:var(--bui-space-1_5)}.lg\:bui-p-2{padding:var(--bui-space-2)}.lg\:bui-p-3{padding:var(--bui-space-3)}.lg\:bui-p-4{padding:var(--bui-space-4)}.lg\:bui-p-5{padding:var(--bui-space-5)}.lg\:bui-p-6{padding:var(--bui-space-6)}.lg\:bui-p-7{padding:var(--bui-space-7)}.lg\:bui-p-8{padding:var(--bui-space-8)}.lg\:bui-p-9{padding:var(--bui-space-9)}.lg\:bui-p-10{padding:var(--bui-space-10)}.lg\:bui-p-11{padding:var(--bui-space-11)}.lg\:bui-p-12{padding:var(--bui-space-12)}.lg\:bui-p-13{padding:var(--bui-space-13)}.lg\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-p{padding:var(--p-xl)}.xl\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xl\:bui-p-1{padding:var(--bui-space-1)}.xl\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xl\:bui-p-2{padding:var(--bui-space-2)}.xl\:bui-p-3{padding:var(--bui-space-3)}.xl\:bui-p-4{padding:var(--bui-space-4)}.xl\:bui-p-5{padding:var(--bui-space-5)}.xl\:bui-p-6{padding:var(--bui-space-6)}.xl\:bui-p-7{padding:var(--bui-space-7)}.xl\:bui-p-8{padding:var(--bui-space-8)}.xl\:bui-p-9{padding:var(--bui-space-9)}.xl\:bui-p-10{padding:var(--bui-space-10)}.xl\:bui-p-11{padding:var(--bui-space-11)}.xl\:bui-p-12{padding:var(--bui-space-12)}.xl\:bui-p-13{padding:var(--bui-space-13)}.xl\:bui-p-14{padding:var(--bui-space-14)}}.bui-pl{padding-left:var(--pl)}.bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.bui-pl-1{padding-left:var(--bui-space-1)}.bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.bui-pl-2{padding-left:var(--bui-space-2)}.bui-pl-3{padding-left:var(--bui-space-3)}.bui-pl-4{padding-left:var(--bui-space-4)}.bui-pl-5{padding-left:var(--bui-space-5)}.bui-pl-6{padding-left:var(--bui-space-6)}.bui-pl-7{padding-left:var(--bui-space-7)}.bui-pl-8{padding-left:var(--bui-space-8)}.bui-pl-9{padding-left:var(--bui-space-9)}.bui-pl-10{padding-left:var(--bui-space-10)}.bui-pl-11{padding-left:var(--bui-space-11)}.bui-pl-12{padding-left:var(--bui-space-12)}.bui-pl-13{padding-left:var(--bui-space-13)}.bui-pl-14{padding-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pl{padding-left:var(--pl-xs)}.xs\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xs\:bui-pl-1{padding-left:var(--bui-space-1)}.xs\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xs\:bui-pl-2{padding-left:var(--bui-space-2)}.xs\:bui-pl-3{padding-left:var(--bui-space-3)}.xs\:bui-pl-4{padding-left:var(--bui-space-4)}.xs\:bui-pl-5{padding-left:var(--bui-space-5)}.xs\:bui-pl-6{padding-left:var(--bui-space-6)}.xs\:bui-pl-7{padding-left:var(--bui-space-7)}.xs\:bui-pl-8{padding-left:var(--bui-space-8)}.xs\:bui-pl-9{padding-left:var(--bui-space-9)}.xs\:bui-pl-10{padding-left:var(--bui-space-10)}.xs\:bui-pl-11{padding-left:var(--bui-space-11)}.xs\:bui-pl-12{padding-left:var(--bui-space-12)}.xs\:bui-pl-13{padding-left:var(--bui-space-13)}.xs\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pl{padding-left:var(--pl-sm)}.sm\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.sm\:bui-pl-1{padding-left:var(--bui-space-1)}.sm\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.sm\:bui-pl-2{padding-left:var(--bui-space-2)}.sm\:bui-pl-3{padding-left:var(--bui-space-3)}.sm\:bui-pl-4{padding-left:var(--bui-space-4)}.sm\:bui-pl-5{padding-left:var(--bui-space-5)}.sm\:bui-pl-6{padding-left:var(--bui-space-6)}.sm\:bui-pl-7{padding-left:var(--bui-space-7)}.sm\:bui-pl-8{padding-left:var(--bui-space-8)}.sm\:bui-pl-9{padding-left:var(--bui-space-9)}.sm\:bui-pl-10{padding-left:var(--bui-space-10)}.sm\:bui-pl-11{padding-left:var(--bui-space-11)}.sm\:bui-pl-12{padding-left:var(--bui-space-12)}.sm\:bui-pl-13{padding-left:var(--bui-space-13)}.sm\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pl{padding-left:var(--pl-md)}.md\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.md\:bui-pl-1{padding-left:var(--bui-space-1)}.md\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.md\:bui-pl-2{padding-left:var(--bui-space-2)}.md\:bui-pl-3{padding-left:var(--bui-space-3)}.md\:bui-pl-4{padding-left:var(--bui-space-4)}.md\:bui-pl-5{padding-left:var(--bui-space-5)}.md\:bui-pl-6{padding-left:var(--bui-space-6)}.md\:bui-pl-7{padding-left:var(--bui-space-7)}.md\:bui-pl-8{padding-left:var(--bui-space-8)}.md\:bui-pl-9{padding-left:var(--bui-space-9)}.md\:bui-pl-10{padding-left:var(--bui-space-10)}.md\:bui-pl-11{padding-left:var(--bui-space-11)}.md\:bui-pl-12{padding-left:var(--bui-space-12)}.md\:bui-pl-13{padding-left:var(--bui-space-13)}.md\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pl{padding-left:var(--pl-lg)}.lg\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.lg\:bui-pl-1{padding-left:var(--bui-space-1)}.lg\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.lg\:bui-pl-2{padding-left:var(--bui-space-2)}.lg\:bui-pl-3{padding-left:var(--bui-space-3)}.lg\:bui-pl-4{padding-left:var(--bui-space-4)}.lg\:bui-pl-5{padding-left:var(--bui-space-5)}.lg\:bui-pl-6{padding-left:var(--bui-space-6)}.lg\:bui-pl-7{padding-left:var(--bui-space-7)}.lg\:bui-pl-8{padding-left:var(--bui-space-8)}.lg\:bui-pl-9{padding-left:var(--bui-space-9)}.lg\:bui-pl-10{padding-left:var(--bui-space-10)}.lg\:bui-pl-11{padding-left:var(--bui-space-11)}.lg\:bui-pl-12{padding-left:var(--bui-space-12)}.lg\:bui-pl-13{padding-left:var(--bui-space-13)}.lg\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pl{padding-left:var(--pl-xl)}.xl\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xl\:bui-pl-1{padding-left:var(--bui-space-1)}.xl\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xl\:bui-pl-2{padding-left:var(--bui-space-2)}.xl\:bui-pl-3{padding-left:var(--bui-space-3)}.xl\:bui-pl-4{padding-left:var(--bui-space-4)}.xl\:bui-pl-5{padding-left:var(--bui-space-5)}.xl\:bui-pl-6{padding-left:var(--bui-space-6)}.xl\:bui-pl-7{padding-left:var(--bui-space-7)}.xl\:bui-pl-8{padding-left:var(--bui-space-8)}.xl\:bui-pl-9{padding-left:var(--bui-space-9)}.xl\:bui-pl-10{padding-left:var(--bui-space-10)}.xl\:bui-pl-11{padding-left:var(--bui-space-11)}.xl\:bui-pl-12{padding-left:var(--bui-space-12)}.xl\:bui-pl-13{padding-left:var(--bui-space-13)}.xl\:bui-pl-14{padding-left:var(--bui-space-14)}}.bui-pr{padding-right:var(--pr)}.bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.bui-pr-1{padding-right:var(--bui-space-1)}.bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.bui-pr-2{padding-right:var(--bui-space-2)}.bui-pr-3{padding-right:var(--bui-space-3)}.bui-pr-4{padding-right:var(--bui-space-4)}.bui-pr-5{padding-right:var(--bui-space-5)}.bui-pr-6{padding-right:var(--bui-space-6)}.bui-pr-7{padding-right:var(--bui-space-7)}.bui-pr-8{padding-right:var(--bui-space-8)}.bui-pr-9{padding-right:var(--bui-space-9)}.bui-pr-10{padding-right:var(--bui-space-10)}.bui-pr-11{padding-right:var(--bui-space-11)}.bui-pr-12{padding-right:var(--bui-space-12)}.bui-pr-13{padding-right:var(--bui-space-13)}.bui-pr-14{padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pr{padding-right:var(--pr-xs)}.xs\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xs\:bui-pr-1{padding-right:var(--bui-space-1)}.xs\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xs\:bui-pr-2{padding-right:var(--bui-space-2)}.xs\:bui-pr-3{padding-right:var(--bui-space-3)}.xs\:bui-pr-4{padding-right:var(--bui-space-4)}.xs\:bui-pr-5{padding-right:var(--bui-space-5)}.xs\:bui-pr-6{padding-right:var(--bui-space-6)}.xs\:bui-pr-7{padding-right:var(--bui-space-7)}.xs\:bui-pr-8{padding-right:var(--bui-space-8)}.xs\:bui-pr-9{padding-right:var(--bui-space-9)}.xs\:bui-pr-10{padding-right:var(--bui-space-10)}.xs\:bui-pr-11{padding-right:var(--bui-space-11)}.xs\:bui-pr-12{padding-right:var(--bui-space-12)}.xs\:bui-pr-13{padding-right:var(--bui-space-13)}.xs\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pr{padding-right:var(--pr-sm)}.sm\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.sm\:bui-pr-1{padding-right:var(--bui-space-1)}.sm\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.sm\:bui-pr-2{padding-right:var(--bui-space-2)}.sm\:bui-pr-3{padding-right:var(--bui-space-3)}.sm\:bui-pr-4{padding-right:var(--bui-space-4)}.sm\:bui-pr-5{padding-right:var(--bui-space-5)}.sm\:bui-pr-6{padding-right:var(--bui-space-6)}.sm\:bui-pr-7{padding-right:var(--bui-space-7)}.sm\:bui-pr-8{padding-right:var(--bui-space-8)}.sm\:bui-pr-9{padding-right:var(--bui-space-9)}.sm\:bui-pr-10{padding-right:var(--bui-space-10)}.sm\:bui-pr-11{padding-right:var(--bui-space-11)}.sm\:bui-pr-12{padding-right:var(--bui-space-12)}.sm\:bui-pr-13{padding-right:var(--bui-space-13)}.sm\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pr{padding-right:var(--pr-md)}.md\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.md\:bui-pr-1{padding-right:var(--bui-space-1)}.md\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.md\:bui-pr-2{padding-right:var(--bui-space-2)}.md\:bui-pr-3{padding-right:var(--bui-space-3)}.md\:bui-pr-4{padding-right:var(--bui-space-4)}.md\:bui-pr-5{padding-right:var(--bui-space-5)}.md\:bui-pr-6{padding-right:var(--bui-space-6)}.md\:bui-pr-7{padding-right:var(--bui-space-7)}.md\:bui-pr-8{padding-right:var(--bui-space-8)}.md\:bui-pr-9{padding-right:var(--bui-space-9)}.md\:bui-pr-10{padding-right:var(--bui-space-10)}.md\:bui-pr-11{padding-right:var(--bui-space-11)}.md\:bui-pr-12{padding-right:var(--bui-space-12)}.md\:bui-pr-13{padding-right:var(--bui-space-13)}.md\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pr{padding-right:var(--pr-lg)}.lg\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.lg\:bui-pr-1{padding-right:var(--bui-space-1)}.lg\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.lg\:bui-pr-2{padding-right:var(--bui-space-2)}.lg\:bui-pr-3{padding-right:var(--bui-space-3)}.lg\:bui-pr-4{padding-right:var(--bui-space-4)}.lg\:bui-pr-5{padding-right:var(--bui-space-5)}.lg\:bui-pr-6{padding-right:var(--bui-space-6)}.lg\:bui-pr-7{padding-right:var(--bui-space-7)}.lg\:bui-pr-8{padding-right:var(--bui-space-8)}.lg\:bui-pr-9{padding-right:var(--bui-space-9)}.lg\:bui-pr-10{padding-right:var(--bui-space-10)}.lg\:bui-pr-11{padding-right:var(--bui-space-11)}.lg\:bui-pr-12{padding-right:var(--bui-space-12)}.lg\:bui-pr-13{padding-right:var(--bui-space-13)}.lg\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pr{padding-right:var(--pr-xl)}.xl\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xl\:bui-pr-1{padding-right:var(--bui-space-1)}.xl\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xl\:bui-pr-2{padding-right:var(--bui-space-2)}.xl\:bui-pr-3{padding-right:var(--bui-space-3)}.xl\:bui-pr-4{padding-right:var(--bui-space-4)}.xl\:bui-pr-5{padding-right:var(--bui-space-5)}.xl\:bui-pr-6{padding-right:var(--bui-space-6)}.xl\:bui-pr-7{padding-right:var(--bui-space-7)}.xl\:bui-pr-8{padding-right:var(--bui-space-8)}.xl\:bui-pr-9{padding-right:var(--bui-space-9)}.xl\:bui-pr-10{padding-right:var(--bui-space-10)}.xl\:bui-pr-11{padding-right:var(--bui-space-11)}.xl\:bui-pr-12{padding-right:var(--bui-space-12)}.xl\:bui-pr-13{padding-right:var(--bui-space-13)}.xl\:bui-pr-14{padding-right:var(--bui-space-14)}}.bui-pt{padding-top:var(--pt)}.bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.bui-pt-1{padding-top:var(--bui-space-1)}.bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.bui-pt-2{padding-top:var(--bui-space-2)}.bui-pt-3{padding-top:var(--bui-space-3)}.bui-pt-4{padding-top:var(--bui-space-4)}.bui-pt-5{padding-top:var(--bui-space-5)}.bui-pt-6{padding-top:var(--bui-space-6)}.bui-pt-7{padding-top:var(--bui-space-7)}.bui-pt-8{padding-top:var(--bui-space-8)}.bui-pt-9{padding-top:var(--bui-space-9)}.bui-pt-10{padding-top:var(--bui-space-10)}.bui-pt-11{padding-top:var(--bui-space-11)}.bui-pt-12{padding-top:var(--bui-space-12)}.bui-pt-13{padding-top:var(--bui-space-13)}.bui-pt-14{padding-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pt{padding-top:var(--pt-xs)}.xs\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xs\:bui-pt-1{padding-top:var(--bui-space-1)}.xs\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xs\:bui-pt-2{padding-top:var(--bui-space-2)}.xs\:bui-pt-3{padding-top:var(--bui-space-3)}.xs\:bui-pt-4{padding-top:var(--bui-space-4)}.xs\:bui-pt-5{padding-top:var(--bui-space-5)}.xs\:bui-pt-6{padding-top:var(--bui-space-6)}.xs\:bui-pt-7{padding-top:var(--bui-space-7)}.xs\:bui-pt-8{padding-top:var(--bui-space-8)}.xs\:bui-pt-9{padding-top:var(--bui-space-9)}.xs\:bui-pt-10{padding-top:var(--bui-space-10)}.xs\:bui-pt-11{padding-top:var(--bui-space-11)}.xs\:bui-pt-12{padding-top:var(--bui-space-12)}.xs\:bui-pt-13{padding-top:var(--bui-space-13)}.xs\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pt{padding-top:var(--pt-sm)}.sm\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.sm\:bui-pt-1{padding-top:var(--bui-space-1)}.sm\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.sm\:bui-pt-2{padding-top:var(--bui-space-2)}.sm\:bui-pt-3{padding-top:var(--bui-space-3)}.sm\:bui-pt-4{padding-top:var(--bui-space-4)}.sm\:bui-pt-5{padding-top:var(--bui-space-5)}.sm\:bui-pt-6{padding-top:var(--bui-space-6)}.sm\:bui-pt-7{padding-top:var(--bui-space-7)}.sm\:bui-pt-8{padding-top:var(--bui-space-8)}.sm\:bui-pt-9{padding-top:var(--bui-space-9)}.sm\:bui-pt-10{padding-top:var(--bui-space-10)}.sm\:bui-pt-11{padding-top:var(--bui-space-11)}.sm\:bui-pt-12{padding-top:var(--bui-space-12)}.sm\:bui-pt-13{padding-top:var(--bui-space-13)}.sm\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pt{padding-top:var(--pt-md)}.md\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.md\:bui-pt-1{padding-top:var(--bui-space-1)}.md\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.md\:bui-pt-2{padding-top:var(--bui-space-2)}.md\:bui-pt-3{padding-top:var(--bui-space-3)}.md\:bui-pt-4{padding-top:var(--bui-space-4)}.md\:bui-pt-5{padding-top:var(--bui-space-5)}.md\:bui-pt-6{padding-top:var(--bui-space-6)}.md\:bui-pt-7{padding-top:var(--bui-space-7)}.md\:bui-pt-8{padding-top:var(--bui-space-8)}.md\:bui-pt-9{padding-top:var(--bui-space-9)}.md\:bui-pt-10{padding-top:var(--bui-space-10)}.md\:bui-pt-11{padding-top:var(--bui-space-11)}.md\:bui-pt-12{padding-top:var(--bui-space-12)}.md\:bui-pt-13{padding-top:var(--bui-space-13)}.md\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pt{padding-top:var(--pt-lg)}.lg\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.lg\:bui-pt-1{padding-top:var(--bui-space-1)}.lg\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.lg\:bui-pt-2{padding-top:var(--bui-space-2)}.lg\:bui-pt-3{padding-top:var(--bui-space-3)}.lg\:bui-pt-4{padding-top:var(--bui-space-4)}.lg\:bui-pt-5{padding-top:var(--bui-space-5)}.lg\:bui-pt-6{padding-top:var(--bui-space-6)}.lg\:bui-pt-7{padding-top:var(--bui-space-7)}.lg\:bui-pt-8{padding-top:var(--bui-space-8)}.lg\:bui-pt-9{padding-top:var(--bui-space-9)}.lg\:bui-pt-10{padding-top:var(--bui-space-10)}.lg\:bui-pt-11{padding-top:var(--bui-space-11)}.lg\:bui-pt-12{padding-top:var(--bui-space-12)}.lg\:bui-pt-13{padding-top:var(--bui-space-13)}.lg\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pt{padding-top:var(--pt-xl)}.xl\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xl\:bui-pt-1{padding-top:var(--bui-space-1)}.xl\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xl\:bui-pt-2{padding-top:var(--bui-space-2)}.xl\:bui-pt-3{padding-top:var(--bui-space-3)}.xl\:bui-pt-4{padding-top:var(--bui-space-4)}.xl\:bui-pt-5{padding-top:var(--bui-space-5)}.xl\:bui-pt-6{padding-top:var(--bui-space-6)}.xl\:bui-pt-7{padding-top:var(--bui-space-7)}.xl\:bui-pt-8{padding-top:var(--bui-space-8)}.xl\:bui-pt-9{padding-top:var(--bui-space-9)}.xl\:bui-pt-10{padding-top:var(--bui-space-10)}.xl\:bui-pt-11{padding-top:var(--bui-space-11)}.xl\:bui-pt-12{padding-top:var(--bui-space-12)}.xl\:bui-pt-13{padding-top:var(--bui-space-13)}.xl\:bui-pt-14{padding-top:var(--bui-space-14)}}.bui-pb{padding-bottom:var(--pb)}.bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.bui-pb-1{padding-bottom:var(--bui-space-1)}.bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.bui-pb-2{padding-bottom:var(--bui-space-2)}.bui-pb-3{padding-bottom:var(--bui-space-3)}.bui-pb-4{padding-bottom:var(--bui-space-4)}.bui-pb-5{padding-bottom:var(--bui-space-5)}.bui-pb-6{padding-bottom:var(--bui-space-6)}.bui-pb-7{padding-bottom:var(--bui-space-7)}.bui-pb-8{padding-bottom:var(--bui-space-8)}.bui-pb-9{padding-bottom:var(--bui-space-9)}.bui-pb-10{padding-bottom:var(--bui-space-10)}.bui-pb-11{padding-bottom:var(--bui-space-11)}.bui-pb-12{padding-bottom:var(--bui-space-12)}.bui-pb-13{padding-bottom:var(--bui-space-13)}.bui-pb-14{padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pb{padding-bottom:var(--pb-xs)}.xs\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xs\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xs\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xs\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xs\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xs\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xs\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xs\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xs\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xs\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xs\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xs\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xs\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xs\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xs\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xs\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pb{padding-bottom:var(--pb-sm)}.sm\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.sm\:bui-pb-1{padding-bottom:var(--bui-space-1)}.sm\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.sm\:bui-pb-2{padding-bottom:var(--bui-space-2)}.sm\:bui-pb-3{padding-bottom:var(--bui-space-3)}.sm\:bui-pb-4{padding-bottom:var(--bui-space-4)}.sm\:bui-pb-5{padding-bottom:var(--bui-space-5)}.sm\:bui-pb-6{padding-bottom:var(--bui-space-6)}.sm\:bui-pb-7{padding-bottom:var(--bui-space-7)}.sm\:bui-pb-8{padding-bottom:var(--bui-space-8)}.sm\:bui-pb-9{padding-bottom:var(--bui-space-9)}.sm\:bui-pb-10{padding-bottom:var(--bui-space-10)}.sm\:bui-pb-11{padding-bottom:var(--bui-space-11)}.sm\:bui-pb-12{padding-bottom:var(--bui-space-12)}.sm\:bui-pb-13{padding-bottom:var(--bui-space-13)}.sm\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pb{padding-bottom:var(--pb-md)}.md\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.md\:bui-pb-1{padding-bottom:var(--bui-space-1)}.md\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.md\:bui-pb-2{padding-bottom:var(--bui-space-2)}.md\:bui-pb-3{padding-bottom:var(--bui-space-3)}.md\:bui-pb-4{padding-bottom:var(--bui-space-4)}.md\:bui-pb-5{padding-bottom:var(--bui-space-5)}.md\:bui-pb-6{padding-bottom:var(--bui-space-6)}.md\:bui-pb-7{padding-bottom:var(--bui-space-7)}.md\:bui-pb-8{padding-bottom:var(--bui-space-8)}.md\:bui-pb-9{padding-bottom:var(--bui-space-9)}.md\:bui-pb-10{padding-bottom:var(--bui-space-10)}.md\:bui-pb-11{padding-bottom:var(--bui-space-11)}.md\:bui-pb-12{padding-bottom:var(--bui-space-12)}.md\:bui-pb-13{padding-bottom:var(--bui-space-13)}.md\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pb{padding-bottom:var(--pb-lg)}.lg\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.lg\:bui-pb-1{padding-bottom:var(--bui-space-1)}.lg\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.lg\:bui-pb-2{padding-bottom:var(--bui-space-2)}.lg\:bui-pb-3{padding-bottom:var(--bui-space-3)}.lg\:bui-pb-4{padding-bottom:var(--bui-space-4)}.lg\:bui-pb-5{padding-bottom:var(--bui-space-5)}.lg\:bui-pb-6{padding-bottom:var(--bui-space-6)}.lg\:bui-pb-7{padding-bottom:var(--bui-space-7)}.lg\:bui-pb-8{padding-bottom:var(--bui-space-8)}.lg\:bui-pb-9{padding-bottom:var(--bui-space-9)}.lg\:bui-pb-10{padding-bottom:var(--bui-space-10)}.lg\:bui-pb-11{padding-bottom:var(--bui-space-11)}.lg\:bui-pb-12{padding-bottom:var(--bui-space-12)}.lg\:bui-pb-13{padding-bottom:var(--bui-space-13)}.lg\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pb{padding-bottom:var(--pb-xl)}.xl\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xl\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xl\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xl\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xl\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xl\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xl\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xl\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xl\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xl\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xl\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xl\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xl\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xl\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xl\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xl\:bui-pb-14{padding-bottom:var(--bui-space-14)}}.bui-py{padding-top:var(--py);padding-bottom:var(--py)}.bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-py{padding-top:var(--py-xs);padding-bottom:var(--py-xs)}.xs\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xs\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xs\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xs\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xs\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xs\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xs\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xs\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xs\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xs\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xs\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xs\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xs\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xs\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xs\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xs\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-py{padding-top:var(--py-sm);padding-bottom:var(--py-sm)}.sm\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.sm\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.sm\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.sm\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.sm\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.sm\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.sm\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.sm\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.sm\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.sm\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.sm\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.sm\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.sm\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.sm\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.sm\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.sm\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-py{padding-top:var(--py-md);padding-bottom:var(--py-md)}.md\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.md\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.md\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.md\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.md\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.md\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.md\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.md\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.md\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.md\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.md\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.md\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.md\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.md\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.md\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.md\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-py{padding-top:var(--py-lg);padding-bottom:var(--py-lg)}.lg\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.lg\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.lg\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.lg\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.lg\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.lg\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.lg\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.lg\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.lg\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.lg\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.lg\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.lg\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.lg\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.lg\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.lg\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.lg\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-py{padding-top:var(--py-xl);padding-bottom:var(--py-xl)}.xl\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xl\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xl\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xl\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xl\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xl\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xl\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xl\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xl\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xl\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xl\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xl\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xl\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xl\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xl\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xl\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}.bui-px{padding-left:var(--px);padding-right:var(--px)}.bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-px{padding-left:var(--px-xs);padding-right:var(--px-xs)}.xs\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xs\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xs\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xs\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xs\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xs\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xs\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xs\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xs\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xs\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xs\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xs\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xs\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xs\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xs\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xs\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-px{padding-left:var(--px-sm);padding-right:var(--px-sm)}.sm\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.sm\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.sm\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.sm\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.sm\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.sm\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.sm\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.sm\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.sm\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.sm\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.sm\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.sm\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.sm\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.sm\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.sm\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.sm\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-px{padding-left:var(--px-md);padding-right:var(--px-md)}.md\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.md\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.md\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.md\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.md\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.md\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.md\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.md\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.md\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.md\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.md\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.md\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.md\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.md\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.md\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.md\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-px{padding-left:var(--px-lg);padding-right:var(--px-lg)}.lg\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.lg\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.lg\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.lg\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.lg\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.lg\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.lg\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.lg\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.lg\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.lg\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.lg\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.lg\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.lg\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.lg\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.lg\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.lg\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-px{padding-left:var(--px-xl);padding-right:var(--px-xl)}.xl\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xl\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xl\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xl\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xl\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xl\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xl\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xl\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xl\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xl\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xl\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xl\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xl\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xl\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xl\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xl\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}.bui-m{margin:var(--m)}.bui-m-0\.5{margin:var(--bui-space-0_5)}.bui-m-1{margin:var(--bui-space-1)}.bui-m-1\.5{margin:var(--bui-space-1_5)}.bui-m-2{margin:var(--bui-space-2)}.bui-m-3{margin:var(--bui-space-3)}.bui-m-4{margin:var(--bui-space-4)}.bui-m-5{margin:var(--bui-space-5)}.bui-m-6{margin:var(--bui-space-6)}.bui-m-7{margin:var(--bui-space-7)}.bui-m-8{margin:var(--bui-space-8)}.bui-m-9{margin:var(--bui-space-9)}.bui-m-10{margin:var(--bui-space-10)}.bui-m-11{margin:var(--bui-space-11)}.bui-m-12{margin:var(--bui-space-12)}.bui-m-13{margin:var(--bui-space-13)}.bui-m-14{margin:var(--bui-space-14)}@media (width>=640px){.xs\:bui-m{margin:var(--p-xs)}.xs\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xs\:bui-m-1{margin:var(--bui-space-1)}.xs\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xs\:bui-m-2{margin:var(--bui-space-2)}.xs\:bui-m-3{margin:var(--bui-space-3)}.xs\:bui-m-4{margin:var(--bui-space-4)}.xs\:bui-m-5{margin:var(--bui-space-5)}.xs\:bui-m-6{margin:var(--bui-space-6)}.xs\:bui-m-7{margin:var(--bui-space-7)}.xs\:bui-m-8{margin:var(--bui-space-8)}.xs\:bui-m-9{margin:var(--bui-space-9)}.xs\:bui-m-10{margin:var(--bui-space-10)}.xs\:bui-m-11{margin:var(--bui-space-11)}.xs\:bui-m-12{margin:var(--bui-space-12)}.xs\:bui-m-13{margin:var(--bui-space-13)}.xs\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-m{margin:var(--p-sm)}.sm\:bui-m-0\.5{margin:var(--bui-space-0_5)}.sm\:bui-m-1{margin:var(--bui-space-1)}.sm\:bui-m-1\.5{margin:var(--bui-space-1_5)}.sm\:bui-m-2{margin:var(--bui-space-2)}.sm\:bui-m-3{margin:var(--bui-space-3)}.sm\:bui-m-4{margin:var(--bui-space-4)}.sm\:bui-m-5{margin:var(--bui-space-5)}.sm\:bui-m-6{margin:var(--bui-space-6)}.sm\:bui-m-7{margin:var(--bui-space-7)}.sm\:bui-m-8{margin:var(--bui-space-8)}.sm\:bui-m-9{margin:var(--bui-space-9)}.sm\:bui-m-10{margin:var(--bui-space-10)}.sm\:bui-m-11{margin:var(--bui-space-11)}.sm\:bui-m-12{margin:var(--bui-space-12)}.sm\:bui-m-13{margin:var(--bui-space-13)}.sm\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-m{margin:var(--p-md)}.md\:bui-m-0\.5{margin:var(--bui-space-0_5)}.md\:bui-m-1{margin:var(--bui-space-1)}.md\:bui-m-1\.5{margin:var(--bui-space-1_5)}.md\:bui-m-2{margin:var(--bui-space-2)}.md\:bui-m-3{margin:var(--bui-space-3)}.md\:bui-m-4{margin:var(--bui-space-4)}.md\:bui-m-5{margin:var(--bui-space-5)}.md\:bui-m-6{margin:var(--bui-space-6)}.md\:bui-m-7{margin:var(--bui-space-7)}.md\:bui-m-8{margin:var(--bui-space-8)}.md\:bui-m-9{margin:var(--bui-space-9)}.md\:bui-m-10{margin:var(--bui-space-10)}.md\:bui-m-11{margin:var(--bui-space-11)}.md\:bui-m-12{margin:var(--bui-space-12)}.md\:bui-m-13{margin:var(--bui-space-13)}.md\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-m{margin:var(--p-lg)}.lg\:bui-m-0\.5{margin:var(--bui-space-0_5)}.lg\:bui-m-1{margin:var(--bui-space-1)}.lg\:bui-m-1\.5{margin:var(--bui-space-1_5)}.lg\:bui-m-2{margin:var(--bui-space-2)}.lg\:bui-m-3{margin:var(--bui-space-3)}.lg\:bui-m-4{margin:var(--bui-space-4)}.lg\:bui-m-5{margin:var(--bui-space-5)}.lg\:bui-m-6{margin:var(--bui-space-6)}.lg\:bui-m-7{margin:var(--bui-space-7)}.lg\:bui-m-8{margin:var(--bui-space-8)}.lg\:bui-m-9{margin:var(--bui-space-9)}.lg\:bui-m-10{margin:var(--bui-space-10)}.lg\:bui-m-11{margin:var(--bui-space-11)}.lg\:bui-m-12{margin:var(--bui-space-12)}.lg\:bui-m-13{margin:var(--bui-space-13)}.lg\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-m{margin:var(--p-xl)}.xl\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xl\:bui-m-1{margin:var(--bui-space-1)}.xl\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xl\:bui-m-2{margin:var(--bui-space-2)}.xl\:bui-m-3{margin:var(--bui-space-3)}.xl\:bui-m-4{margin:var(--bui-space-4)}.xl\:bui-m-5{margin:var(--bui-space-5)}.xl\:bui-m-6{margin:var(--bui-space-6)}.xl\:bui-m-7{margin:var(--bui-space-7)}.xl\:bui-m-8{margin:var(--bui-space-8)}.xl\:bui-m-9{margin:var(--bui-space-9)}.xl\:bui-m-10{margin:var(--bui-space-10)}.xl\:bui-m-11{margin:var(--bui-space-11)}.xl\:bui-m-12{margin:var(--bui-space-12)}.xl\:bui-m-13{margin:var(--bui-space-13)}.xl\:bui-m-14{margin:var(--bui-space-14)}}.bui-ml{margin-left:var(--ml)}.bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.bui-ml-1{margin-left:var(--bui-space-1)}.bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.bui-ml-2{margin-left:var(--bui-space-2)}.bui-ml-3{margin-left:var(--bui-space-3)}.bui-ml-4{margin-left:var(--bui-space-4)}.bui-ml-5{margin-left:var(--bui-space-5)}.bui-ml-6{margin-left:var(--bui-space-6)}.bui-ml-7{margin-left:var(--bui-space-7)}.bui-ml-8{margin-left:var(--bui-space-8)}.bui-ml-9{margin-left:var(--bui-space-9)}.bui-ml-10{margin-left:var(--bui-space-10)}.bui-ml-11{margin-left:var(--bui-space-11)}.bui-ml-12{margin-left:var(--bui-space-12)}.bui-ml-13{margin-left:var(--bui-space-13)}.bui-ml-14{margin-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-ml{margin-left:var(--ml-xs)}.xs\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xs\:bui-ml-1{margin-left:var(--bui-space-1)}.xs\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xs\:bui-ml-2{margin-left:var(--bui-space-2)}.xs\:bui-ml-3{margin-left:var(--bui-space-3)}.xs\:bui-ml-4{margin-left:var(--bui-space-4)}.xs\:bui-ml-5{margin-left:var(--bui-space-5)}.xs\:bui-ml-6{margin-left:var(--bui-space-6)}.xs\:bui-ml-7{margin-left:var(--bui-space-7)}.xs\:bui-ml-8{margin-left:var(--bui-space-8)}.xs\:bui-ml-9{margin-left:var(--bui-space-9)}.xs\:bui-ml-10{margin-left:var(--bui-space-10)}.xs\:bui-ml-11{margin-left:var(--bui-space-11)}.xs\:bui-ml-12{margin-left:var(--bui-space-12)}.xs\:bui-ml-13{margin-left:var(--bui-space-13)}.xs\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-ml{margin-left:var(--ml-sm)}.sm\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.sm\:bui-ml-1{margin-left:var(--bui-space-1)}.sm\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.sm\:bui-ml-2{margin-left:var(--bui-space-2)}.sm\:bui-ml-3{margin-left:var(--bui-space-3)}.sm\:bui-ml-4{margin-left:var(--bui-space-4)}.sm\:bui-ml-5{margin-left:var(--bui-space-5)}.sm\:bui-ml-6{margin-left:var(--bui-space-6)}.sm\:bui-ml-7{margin-left:var(--bui-space-7)}.sm\:bui-ml-8{margin-left:var(--bui-space-8)}.sm\:bui-ml-9{margin-left:var(--bui-space-9)}.sm\:bui-ml-10{margin-left:var(--bui-space-10)}.sm\:bui-ml-11{margin-left:var(--bui-space-11)}.sm\:bui-ml-12{margin-left:var(--bui-space-12)}.sm\:bui-ml-13{margin-left:var(--bui-space-13)}.sm\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-ml{margin-left:var(--ml-md)}.md\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.md\:bui-ml-1{margin-left:var(--bui-space-1)}.md\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.md\:bui-ml-2{margin-left:var(--bui-space-2)}.md\:bui-ml-3{margin-left:var(--bui-space-3)}.md\:bui-ml-4{margin-left:var(--bui-space-4)}.md\:bui-ml-5{margin-left:var(--bui-space-5)}.md\:bui-ml-6{margin-left:var(--bui-space-6)}.md\:bui-ml-7{margin-left:var(--bui-space-7)}.md\:bui-ml-8{margin-left:var(--bui-space-8)}.md\:bui-ml-9{margin-left:var(--bui-space-9)}.md\:bui-ml-10{margin-left:var(--bui-space-10)}.md\:bui-ml-11{margin-left:var(--bui-space-11)}.md\:bui-ml-12{margin-left:var(--bui-space-12)}.md\:bui-ml-13{margin-left:var(--bui-space-13)}.md\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-ml{margin-left:var(--ml-lg)}.lg\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.lg\:bui-ml-1{margin-left:var(--bui-space-1)}.lg\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.lg\:bui-ml-2{margin-left:var(--bui-space-2)}.lg\:bui-ml-3{margin-left:var(--bui-space-3)}.lg\:bui-ml-4{margin-left:var(--bui-space-4)}.lg\:bui-ml-5{margin-left:var(--bui-space-5)}.lg\:bui-ml-6{margin-left:var(--bui-space-6)}.lg\:bui-ml-7{margin-left:var(--bui-space-7)}.lg\:bui-ml-8{margin-left:var(--bui-space-8)}.lg\:bui-ml-9{margin-left:var(--bui-space-9)}.lg\:bui-ml-10{margin-left:var(--bui-space-10)}.lg\:bui-ml-11{margin-left:var(--bui-space-11)}.lg\:bui-ml-12{margin-left:var(--bui-space-12)}.lg\:bui-ml-13{margin-left:var(--bui-space-13)}.lg\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-ml{margin-left:var(--ml-xl)}.xl\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xl\:bui-ml-1{margin-left:var(--bui-space-1)}.xl\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xl\:bui-ml-2{margin-left:var(--bui-space-2)}.xl\:bui-ml-3{margin-left:var(--bui-space-3)}.xl\:bui-ml-4{margin-left:var(--bui-space-4)}.xl\:bui-ml-5{margin-left:var(--bui-space-5)}.xl\:bui-ml-6{margin-left:var(--bui-space-6)}.xl\:bui-ml-7{margin-left:var(--bui-space-7)}.xl\:bui-ml-8{margin-left:var(--bui-space-8)}.xl\:bui-ml-9{margin-left:var(--bui-space-9)}.xl\:bui-ml-10{margin-left:var(--bui-space-10)}.xl\:bui-ml-11{margin-left:var(--bui-space-11)}.xl\:bui-ml-12{margin-left:var(--bui-space-12)}.xl\:bui-ml-13{margin-left:var(--bui-space-13)}.xl\:bui-ml-14{margin-left:var(--bui-space-14)}}.bui-mr{margin-right:var(--mr)}.bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.bui-mr-1{margin-right:var(--bui-space-1)}.bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.bui-mr-2{margin-right:var(--bui-space-2)}.bui-mr-3{margin-right:var(--bui-space-3)}.bui-mr-4{margin-right:var(--bui-space-4)}.bui-mr-5{margin-right:var(--bui-space-5)}.bui-mr-6{margin-right:var(--bui-space-6)}.bui-mr-7{margin-right:var(--bui-space-7)}.bui-mr-8{margin-right:var(--bui-space-8)}.bui-mr-9{margin-right:var(--bui-space-9)}.bui-mr-10{margin-right:var(--bui-space-10)}.bui-mr-11{margin-right:var(--bui-space-11)}.bui-mr-12{margin-right:var(--bui-space-12)}.bui-mr-13{margin-right:var(--bui-space-13)}.bui-mr-14{margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mr{margin-right:var(--mr-xs)}.xs\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xs\:bui-mr-1{margin-right:var(--bui-space-1)}.xs\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xs\:bui-mr-2{margin-right:var(--bui-space-2)}.xs\:bui-mr-3{margin-right:var(--bui-space-3)}.xs\:bui-mr-4{margin-right:var(--bui-space-4)}.xs\:bui-mr-5{margin-right:var(--bui-space-5)}.xs\:bui-mr-6{margin-right:var(--bui-space-6)}.xs\:bui-mr-7{margin-right:var(--bui-space-7)}.xs\:bui-mr-8{margin-right:var(--bui-space-8)}.xs\:bui-mr-9{margin-right:var(--bui-space-9)}.xs\:bui-mr-10{margin-right:var(--bui-space-10)}.xs\:bui-mr-11{margin-right:var(--bui-space-11)}.xs\:bui-mr-12{margin-right:var(--bui-space-12)}.xs\:bui-mr-13{margin-right:var(--bui-space-13)}.xs\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mr{margin-right:var(--mr-sm)}.sm\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.sm\:bui-mr-1{margin-right:var(--bui-space-1)}.sm\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.sm\:bui-mr-2{margin-right:var(--bui-space-2)}.sm\:bui-mr-3{margin-right:var(--bui-space-3)}.sm\:bui-mr-4{margin-right:var(--bui-space-4)}.sm\:bui-mr-5{margin-right:var(--bui-space-5)}.sm\:bui-mr-6{margin-right:var(--bui-space-6)}.sm\:bui-mr-7{margin-right:var(--bui-space-7)}.sm\:bui-mr-8{margin-right:var(--bui-space-8)}.sm\:bui-mr-9{margin-right:var(--bui-space-9)}.sm\:bui-mr-10{margin-right:var(--bui-space-10)}.sm\:bui-mr-11{margin-right:var(--bui-space-11)}.sm\:bui-mr-12{margin-right:var(--bui-space-12)}.sm\:bui-mr-13{margin-right:var(--bui-space-13)}.sm\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mr{margin-right:var(--mr-md)}.md\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.md\:bui-mr-1{margin-right:var(--bui-space-1)}.md\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.md\:bui-mr-2{margin-right:var(--bui-space-2)}.md\:bui-mr-3{margin-right:var(--bui-space-3)}.md\:bui-mr-4{margin-right:var(--bui-space-4)}.md\:bui-mr-5{margin-right:var(--bui-space-5)}.md\:bui-mr-6{margin-right:var(--bui-space-6)}.md\:bui-mr-7{margin-right:var(--bui-space-7)}.md\:bui-mr-8{margin-right:var(--bui-space-8)}.md\:bui-mr-9{margin-right:var(--bui-space-9)}.md\:bui-mr-10{margin-right:var(--bui-space-10)}.md\:bui-mr-11{margin-right:var(--bui-space-11)}.md\:bui-mr-12{margin-right:var(--bui-space-12)}.md\:bui-mr-13{margin-right:var(--bui-space-13)}.md\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mr{margin-right:var(--mr-lg)}.lg\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.lg\:bui-mr-1{margin-right:var(--bui-space-1)}.lg\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.lg\:bui-mr-2{margin-right:var(--bui-space-2)}.lg\:bui-mr-3{margin-right:var(--bui-space-3)}.lg\:bui-mr-4{margin-right:var(--bui-space-4)}.lg\:bui-mr-5{margin-right:var(--bui-space-5)}.lg\:bui-mr-6{margin-right:var(--bui-space-6)}.lg\:bui-mr-7{margin-right:var(--bui-space-7)}.lg\:bui-mr-8{margin-right:var(--bui-space-8)}.lg\:bui-mr-9{margin-right:var(--bui-space-9)}.lg\:bui-mr-10{margin-right:var(--bui-space-10)}.lg\:bui-mr-11{margin-right:var(--bui-space-11)}.lg\:bui-mr-12{margin-right:var(--bui-space-12)}.lg\:bui-mr-13{margin-right:var(--bui-space-13)}.lg\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mr{margin-right:var(--mr-xl)}.xl\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xl\:bui-mr-1{margin-right:var(--bui-space-1)}.xl\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xl\:bui-mr-2{margin-right:var(--bui-space-2)}.xl\:bui-mr-3{margin-right:var(--bui-space-3)}.xl\:bui-mr-4{margin-right:var(--bui-space-4)}.xl\:bui-mr-5{margin-right:var(--bui-space-5)}.xl\:bui-mr-6{margin-right:var(--bui-space-6)}.xl\:bui-mr-7{margin-right:var(--bui-space-7)}.xl\:bui-mr-8{margin-right:var(--bui-space-8)}.xl\:bui-mr-9{margin-right:var(--bui-space-9)}.xl\:bui-mr-10{margin-right:var(--bui-space-10)}.xl\:bui-mr-11{margin-right:var(--bui-space-11)}.xl\:bui-mr-12{margin-right:var(--bui-space-12)}.xl\:bui-mr-13{margin-right:var(--bui-space-13)}.xl\:bui-mr-14{margin-right:var(--bui-space-14)}}.bui-mt{margin-top:var(--mt)}.bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.bui-mt-1{margin-top:var(--bui-space-1)}.bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.bui-mt-2{margin-top:var(--bui-space-2)}.bui-mt-3{margin-top:var(--bui-space-3)}.bui-mt-4{margin-top:var(--bui-space-4)}.bui-mt-5{margin-top:var(--bui-space-5)}.bui-mt-6{margin-top:var(--bui-space-6)}.bui-mt-7{margin-top:var(--bui-space-7)}.bui-mt-8{margin-top:var(--bui-space-8)}.bui-mt-9{margin-top:var(--bui-space-9)}.bui-mt-10{margin-top:var(--bui-space-10)}.bui-mt-11{margin-top:var(--bui-space-11)}.bui-mt-12{margin-top:var(--bui-space-12)}.bui-mt-13{margin-top:var(--bui-space-13)}.bui-mt-14{margin-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mt{margin-top:var(--mt-xs)}.xs\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xs\:bui-mt-1{margin-top:var(--bui-space-1)}.xs\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xs\:bui-mt-2{margin-top:var(--bui-space-2)}.xs\:bui-mt-3{margin-top:var(--bui-space-3)}.xs\:bui-mt-4{margin-top:var(--bui-space-4)}.xs\:bui-mt-5{margin-top:var(--bui-space-5)}.xs\:bui-mt-6{margin-top:var(--bui-space-6)}.xs\:bui-mt-7{margin-top:var(--bui-space-7)}.xs\:bui-mt-8{margin-top:var(--bui-space-8)}.xs\:bui-mt-9{margin-top:var(--bui-space-9)}.xs\:bui-mt-10{margin-top:var(--bui-space-10)}.xs\:bui-mt-11{margin-top:var(--bui-space-11)}.xs\:bui-mt-12{margin-top:var(--bui-space-12)}.xs\:bui-mt-13{margin-top:var(--bui-space-13)}.xs\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mt{margin-top:var(--mt-sm)}.sm\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.sm\:bui-mt-1{margin-top:var(--bui-space-1)}.sm\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.sm\:bui-mt-2{margin-top:var(--bui-space-2)}.sm\:bui-mt-3{margin-top:var(--bui-space-3)}.sm\:bui-mt-4{margin-top:var(--bui-space-4)}.sm\:bui-mt-5{margin-top:var(--bui-space-5)}.sm\:bui-mt-6{margin-top:var(--bui-space-6)}.sm\:bui-mt-7{margin-top:var(--bui-space-7)}.sm\:bui-mt-8{margin-top:var(--bui-space-8)}.sm\:bui-mt-9{margin-top:var(--bui-space-9)}.sm\:bui-mt-10{margin-top:var(--bui-space-10)}.sm\:bui-mt-11{margin-top:var(--bui-space-11)}.sm\:bui-mt-12{margin-top:var(--bui-space-12)}.sm\:bui-mt-13{margin-top:var(--bui-space-13)}.sm\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mt{margin-top:var(--mt-md)}.md\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.md\:bui-mt-1{margin-top:var(--bui-space-1)}.md\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.md\:bui-mt-2{margin-top:var(--bui-space-2)}.md\:bui-mt-3{margin-top:var(--bui-space-3)}.md\:bui-mt-4{margin-top:var(--bui-space-4)}.md\:bui-mt-5{margin-top:var(--bui-space-5)}.md\:bui-mt-6{margin-top:var(--bui-space-6)}.md\:bui-mt-7{margin-top:var(--bui-space-7)}.md\:bui-mt-8{margin-top:var(--bui-space-8)}.md\:bui-mt-9{margin-top:var(--bui-space-9)}.md\:bui-mt-10{margin-top:var(--bui-space-10)}.md\:bui-mt-11{margin-top:var(--bui-space-11)}.md\:bui-mt-12{margin-top:var(--bui-space-12)}.md\:bui-mt-13{margin-top:var(--bui-space-13)}.md\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mt{margin-top:var(--mt-lg)}.lg\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.lg\:bui-mt-1{margin-top:var(--bui-space-1)}.lg\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.lg\:bui-mt-2{margin-top:var(--bui-space-2)}.lg\:bui-mt-3{margin-top:var(--bui-space-3)}.lg\:bui-mt-4{margin-top:var(--bui-space-4)}.lg\:bui-mt-5{margin-top:var(--bui-space-5)}.lg\:bui-mt-6{margin-top:var(--bui-space-6)}.lg\:bui-mt-7{margin-top:var(--bui-space-7)}.lg\:bui-mt-8{margin-top:var(--bui-space-8)}.lg\:bui-mt-9{margin-top:var(--bui-space-9)}.lg\:bui-mt-10{margin-top:var(--bui-space-10)}.lg\:bui-mt-11{margin-top:var(--bui-space-11)}.lg\:bui-mt-12{margin-top:var(--bui-space-12)}.lg\:bui-mt-13{margin-top:var(--bui-space-13)}.lg\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mt{margin-top:var(--mt-xl)}.xl\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xl\:bui-mt-1{margin-top:var(--bui-space-1)}.xl\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xl\:bui-mt-2{margin-top:var(--bui-space-2)}.xl\:bui-mt-3{margin-top:var(--bui-space-3)}.xl\:bui-mt-4{margin-top:var(--bui-space-4)}.xl\:bui-mt-5{margin-top:var(--bui-space-5)}.xl\:bui-mt-6{margin-top:var(--bui-space-6)}.xl\:bui-mt-7{margin-top:var(--bui-space-7)}.xl\:bui-mt-8{margin-top:var(--bui-space-8)}.xl\:bui-mt-9{margin-top:var(--bui-space-9)}.xl\:bui-mt-10{margin-top:var(--bui-space-10)}.xl\:bui-mt-11{margin-top:var(--bui-space-11)}.xl\:bui-mt-12{margin-top:var(--bui-space-12)}.xl\:bui-mt-13{margin-top:var(--bui-space-13)}.xl\:bui-mt-14{margin-top:var(--bui-space-14)}}.bui-mb{margin-bottom:var(--mb)}.bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.bui-mb-1{margin-bottom:var(--bui-space-1)}.bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.bui-mb-2{margin-bottom:var(--bui-space-2)}.bui-mb-3{margin-bottom:var(--bui-space-3)}.bui-mb-4{margin-bottom:var(--bui-space-4)}.bui-mb-5{margin-bottom:var(--bui-space-5)}.bui-mb-6{margin-bottom:var(--bui-space-6)}.bui-mb-7{margin-bottom:var(--bui-space-7)}.bui-mb-8{margin-bottom:var(--bui-space-8)}.bui-mb-9{margin-bottom:var(--bui-space-9)}.bui-mb-10{margin-bottom:var(--bui-space-10)}.bui-mb-11{margin-bottom:var(--bui-space-11)}.bui-mb-12{margin-bottom:var(--bui-space-12)}.bui-mb-13{margin-bottom:var(--bui-space-13)}.bui-mb-14{margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mb{margin-bottom:var(--mb-xs)}.xs\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xs\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xs\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xs\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xs\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xs\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xs\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xs\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xs\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xs\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xs\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xs\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xs\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xs\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xs\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xs\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mb{margin-bottom:var(--mb-sm)}.sm\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.sm\:bui-mb-1{margin-bottom:var(--bui-space-1)}.sm\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.sm\:bui-mb-2{margin-bottom:var(--bui-space-2)}.sm\:bui-mb-3{margin-bottom:var(--bui-space-3)}.sm\:bui-mb-4{margin-bottom:var(--bui-space-4)}.sm\:bui-mb-5{margin-bottom:var(--bui-space-5)}.sm\:bui-mb-6{margin-bottom:var(--bui-space-6)}.sm\:bui-mb-7{margin-bottom:var(--bui-space-7)}.sm\:bui-mb-8{margin-bottom:var(--bui-space-8)}.sm\:bui-mb-9{margin-bottom:var(--bui-space-9)}.sm\:bui-mb-10{margin-bottom:var(--bui-space-10)}.sm\:bui-mb-11{margin-bottom:var(--bui-space-11)}.sm\:bui-mb-12{margin-bottom:var(--bui-space-12)}.sm\:bui-mb-13{margin-bottom:var(--bui-space-13)}.sm\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mb{margin-bottom:var(--mb-md)}.md\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.md\:bui-mb-1{margin-bottom:var(--bui-space-1)}.md\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.md\:bui-mb-2{margin-bottom:var(--bui-space-2)}.md\:bui-mb-3{margin-bottom:var(--bui-space-3)}.md\:bui-mb-4{margin-bottom:var(--bui-space-4)}.md\:bui-mb-5{margin-bottom:var(--bui-space-5)}.md\:bui-mb-6{margin-bottom:var(--bui-space-6)}.md\:bui-mb-7{margin-bottom:var(--bui-space-7)}.md\:bui-mb-8{margin-bottom:var(--bui-space-8)}.md\:bui-mb-9{margin-bottom:var(--bui-space-9)}.md\:bui-mb-10{margin-bottom:var(--bui-space-10)}.md\:bui-mb-11{margin-bottom:var(--bui-space-11)}.md\:bui-mb-12{margin-bottom:var(--bui-space-12)}.md\:bui-mb-13{margin-bottom:var(--bui-space-13)}.md\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mb{margin-bottom:var(--mb-lg)}.lg\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.lg\:bui-mb-1{margin-bottom:var(--bui-space-1)}.lg\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.lg\:bui-mb-2{margin-bottom:var(--bui-space-2)}.lg\:bui-mb-3{margin-bottom:var(--bui-space-3)}.lg\:bui-mb-4{margin-bottom:var(--bui-space-4)}.lg\:bui-mb-5{margin-bottom:var(--bui-space-5)}.lg\:bui-mb-6{margin-bottom:var(--bui-space-6)}.lg\:bui-mb-7{margin-bottom:var(--bui-space-7)}.lg\:bui-mb-8{margin-bottom:var(--bui-space-8)}.lg\:bui-mb-9{margin-bottom:var(--bui-space-9)}.lg\:bui-mb-10{margin-bottom:var(--bui-space-10)}.lg\:bui-mb-11{margin-bottom:var(--bui-space-11)}.lg\:bui-mb-12{margin-bottom:var(--bui-space-12)}.lg\:bui-mb-13{margin-bottom:var(--bui-space-13)}.lg\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mb{margin-bottom:var(--mb-xl)}.xl\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xl\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xl\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xl\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xl\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xl\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xl\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xl\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xl\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xl\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xl\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xl\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xl\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xl\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xl\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xl\:bui-mb-14{margin-bottom:var(--bui-space-14)}}.bui-my{margin-top:var(--my);margin-bottom:var(--my)}.bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-my{margin-top:var(--my-xs);margin-bottom:var(--my-xs)}.xs\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xs\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xs\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xs\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xs\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xs\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xs\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xs\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xs\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xs\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xs\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xs\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xs\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xs\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xs\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xs\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-my{margin-top:var(--my-sm);margin-bottom:var(--my-sm)}.sm\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.sm\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.sm\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.sm\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.sm\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.sm\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.sm\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.sm\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.sm\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.sm\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.sm\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.sm\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.sm\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.sm\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.sm\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.sm\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-my{margin-top:var(--my-md);margin-bottom:var(--my-md)}.md\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.md\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.md\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.md\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.md\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.md\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.md\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.md\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.md\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.md\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.md\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.md\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.md\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.md\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.md\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.md\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-my{margin-top:var(--my-lg);margin-bottom:var(--my-lg)}.lg\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.lg\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.lg\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.lg\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.lg\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.lg\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.lg\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.lg\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.lg\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.lg\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.lg\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.lg\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.lg\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.lg\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.lg\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.lg\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-my{margin-top:var(--my-xl);margin-bottom:var(--my-xl)}.xl\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xl\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xl\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xl\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xl\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xl\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xl\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xl\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xl\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xl\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xl\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xl\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xl\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xl\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xl\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xl\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}.bui-mx{margin-left:var(--mx);margin-right:var(--mx)}.bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mx{margin-left:var(--mx-xs);margin-right:var(--mx-xs)}.xs\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xs\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xs\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xs\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xs\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xs\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xs\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xs\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xs\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xs\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xs\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xs\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xs\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xs\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xs\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xs\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mx{margin-left:var(--mx-sm);margin-right:var(--mx-sm)}.sm\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.sm\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.sm\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.sm\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.sm\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.sm\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.sm\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.sm\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.sm\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.sm\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.sm\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.sm\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.sm\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.sm\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.sm\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.sm\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mx{margin-left:var(--mx-md);margin-right:var(--mx-md)}.md\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.md\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.md\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.md\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.md\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.md\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.md\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.md\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.md\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.md\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.md\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.md\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.md\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.md\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.md\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.md\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mx{margin-left:var(--mx-lg);margin-right:var(--mx-lg)}.lg\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.lg\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.lg\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.lg\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.lg\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.lg\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.lg\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.lg\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.lg\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.lg\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.lg\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.lg\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.lg\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.lg\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.lg\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.lg\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mx{margin-left:var(--mx-xl);margin-right:var(--mx-xl)}.xl\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xl\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xl\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xl\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xl\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xl\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xl\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xl\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xl\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xl\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xl\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xl\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xl\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xl\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xl\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xl\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}.bui-display-none{display:none}.bui-display-inline{display:inline}.bui-display-inline-block{display:inline-block}.bui-display-block{display:block}@media (width>=640px){.xs\:bui-display-none{display:none}.xs\:bui-display-inline{display:inline}.xs\:bui-display-inline-block{display:inline-block}.xs\:bui-display-block{display:block}}@media (width>=768px){.sm\:bui-display-none{display:none}.sm\:bui-display-inline{display:inline}.sm\:bui-display-inline-block{display:inline-block}.sm\:bui-display-block{display:block}}@media (width>=1024px){.md\:bui-display-none{display:none}.md\:bui-display-inline{display:inline}.md\:bui-display-inline-block{display:inline-block}.md\:bui-display-block{display:block}}@media (width>=1280px){.lg\:bui-display-none{display:none}.lg\:bui-display-inline{display:inline}.lg\:bui-display-inline-block{display:inline-block}.lg\:bui-display-block{display:block}}@media (width>=1536px){.xl\:bui-display-none{display:none}.xl\:bui-display-inline{display:inline}.xl\:bui-display-inline-block{display:inline-block}.xl\:bui-display-block{display:block}}.bui-w{width:var(--width)}.bui-min-w{min-width:var(--min-width)}.bui-max-w{max-width:var(--max-width)}@media (width>=640px){.xs\:bui-w{width:var(--width)}.xs\:bui-min-w{min-width:var(--min-width)}.xs\:bui-max-w{max-width:var(--max-width)}}@media (width>=768px){.sm\:bui-w{width:var(--width)}.sm\:bui-min-w{min-width:var(--min-width)}.sm\:bui-max-w{max-width:var(--max-width)}}@media (width>=1024px){.md\:bui-w{width:var(--width)}.md\:bui-min-w{min-width:var(--min-width)}.md\:bui-max-w{max-width:var(--max-width)}}@media (width>=1280px){.lg\:bui-w{width:var(--width)}.lg\:bui-min-w{min-width:var(--min-width)}.lg\:bui-max-w{max-width:var(--max-width)}}@media (width>=1536px){.xl\:bui-w{width:var(--width)}.xl\:bui-min-w{min-width:var(--min-width)}.xl\:bui-max-w{max-width:var(--max-width)}}.bui-h{height:var(--height)}.bui-min-h{min-height:var(--min-height)}.bui-max-h{max-height:var(--max-height)}@media (width>=640px){.xs\:bui-h{height:var(--height)}.xs\:bui-min-h{min-height:var(--min-height)}.xs\:bui-max-h{max-height:var(--max-height)}}@media (width>=768px){.sm\:bui-h{height:var(--height)}.sm\:bui-min-h{min-height:var(--min-height)}.sm\:bui-max-h{max-height:var(--max-height)}}@media (width>=1024px){.md\:bui-h{height:var(--height)}.md\:bui-min-h{min-height:var(--min-height)}.md\:bui-max-h{max-height:var(--max-height)}}@media (width>=1280px){.lg\:bui-h{height:var(--height)}.lg\:bui-min-h{min-height:var(--min-height)}.lg\:bui-max-h{max-height:var(--max-height)}}@media (width>=1536px){.xl\:bui-h{height:var(--height)}.xl\:bui-min-h{min-height:var(--min-height)}.xl\:bui-max-h{max-height:var(--max-height)}}.bui-position-absolute{position:absolute}.bui-position-fixed{position:fixed}.bui-position-sticky{position:sticky}.bui-position-relative{position:relative}.bui-position-static{position:static}@media (width>=640px){.xs\:bui-position-absolute{position:absolute}.xs\:bui-position-fixed{position:fixed}.xs\:bui-position-sticky{position:sticky}.xs\:bui-position-relative{position:relative}.xs\:bui-position-static{position:static}}@media (width>=768px){.sm\:bui-position-absolute{position:absolute}.sm\:bui-position-fixed{position:fixed}.sm\:bui-position-sticky{position:sticky}.sm\:bui-position-relative{position:relative}.sm\:bui-position-static{position:static}}@media (width>=1024px){.md\:bui-position-absolute{position:absolute}.md\:bui-position-fixed{position:fixed}.md\:bui-position-sticky{position:sticky}.md\:bui-position-relative{position:relative}.md\:bui-position-static{position:static}}@media (width>=1280px){.lg\:bui-position-absolute{position:absolute}.lg\:bui-position-fixed{position:fixed}.lg\:bui-position-sticky{position:sticky}.lg\:bui-position-relative{position:relative}.lg\:bui-position-static{position:static}}@media (width>=1536px){.xl\:bui-position-absolute{position:absolute}.xl\:bui-position-fixed{position:fixed}.xl\:bui-position-sticky{position:sticky}.xl\:bui-position-relative{position:relative}.xl\:bui-position-static{position:static}}.bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.bui-col-span-1{grid-column:span 1/span 1}.bui-col-span-2{grid-column:span 2/span 2}.bui-col-span-3{grid-column:span 3/span 3}.bui-col-span-4{grid-column:span 4/span 4}.bui-col-span-5{grid-column:span 5/span 5}.bui-col-span-6{grid-column:span 6/span 6}.bui-col-span-7{grid-column:span 7/span 7}.bui-col-span-8{grid-column:span 8/span 8}.bui-col-span-9{grid-column:span 9/span 9}.bui-col-span-10{grid-column:span 10/span 10}.bui-col-span-11{grid-column:span 11/span 11}.bui-col-span-12{grid-column:span 12/span 12}.bui-col-span-auto{grid-column:span auto/span auto}.bui-col-start-1{grid-column-start:1}.bui-col-start-2{grid-column-start:2}.bui-col-start-3{grid-column-start:3}.bui-col-start-4{grid-column-start:4}.bui-col-start-5{grid-column-start:5}.bui-col-start-6{grid-column-start:6}.bui-col-start-7{grid-column-start:7}.bui-col-start-8{grid-column-start:8}.bui-col-start-9{grid-column-start:9}.bui-col-start-10{grid-column-start:10}.bui-col-start-11{grid-column-start:11}.bui-col-start-12{grid-column-start:12}.bui-col-start-13{grid-column-start:13}.bui-col-start-auto{grid-column-start:auto}.bui-col-end-1{grid-column-end:1}.bui-col-end-2{grid-column-end:2}.bui-col-end-3{grid-column-end:3}.bui-col-end-4{grid-column-end:4}.bui-col-end-5{grid-column-end:5}.bui-col-end-6{grid-column-end:6}.bui-col-end-7{grid-column-end:7}.bui-col-end-8{grid-column-end:8}.bui-col-end-9{grid-column-end:9}.bui-col-end-10{grid-column-end:10}.bui-col-end-11{grid-column-end:11}.bui-col-end-12{grid-column-end:12}.bui-col-end-13{grid-column-end:13}.bui-col-end-auto{grid-column-end:auto}.bui-row-span-1{grid-row:span 1/span 1}.bui-row-span-2{grid-row:span 2/span 2}.bui-row-span-3{grid-row:span 3/span 3}.bui-row-span-4{grid-row:span 4/span 4}.bui-row-span-5{grid-row:span 5/span 5}.bui-row-span-6{grid-row:span 6/span 6}.bui-row-span-7{grid-row:span 7/span 7}.bui-row-span-8{grid-row:span 8/span 8}.bui-row-span-9{grid-row:span 9/span 9}.bui-row-span-10{grid-row:span 10/span 10}.bui-row-span-11{grid-row:span 11/span 11}.bui-row-span-12{grid-row:span 12/span 12}.bui-row-span-auto{grid-row:span auto/span auto}@media (width>=640px){.xs\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xs\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xs\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xs\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xs\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xs\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xs\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xs\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xs\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xs\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xs\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xs\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xs\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xs\:bui-col-span-1{grid-column:span 1/span 1}.xs\:bui-col-span-2{grid-column:span 2/span 2}.xs\:bui-col-span-3{grid-column:span 3/span 3}.xs\:bui-col-span-4{grid-column:span 4/span 4}.xs\:bui-col-span-5{grid-column:span 5/span 5}.xs\:bui-col-span-6{grid-column:span 6/span 6}.xs\:bui-col-span-7{grid-column:span 7/span 7}.xs\:bui-col-span-8{grid-column:span 8/span 8}.xs\:bui-col-span-9{grid-column:span 9/span 9}.xs\:bui-col-span-10{grid-column:span 10/span 10}.xs\:bui-col-span-11{grid-column:span 11/span 11}.xs\:bui-col-span-12{grid-column:span 12/span 12}.xs\:bui-col-span-auto{grid-column:span auto/span auto}.xs\:bui-col-start-1{grid-column-start:1}.xs\:bui-col-start-2{grid-column-start:2}.xs\:bui-col-start-3{grid-column-start:3}.xs\:bui-col-start-4{grid-column-start:4}.xs\:bui-col-start-5{grid-column-start:5}.xs\:bui-col-start-6{grid-column-start:6}.xs\:bui-col-start-7{grid-column-start:7}.xs\:bui-col-start-8{grid-column-start:8}.xs\:bui-col-start-9{grid-column-start:9}.xs\:bui-col-start-10{grid-column-start:10}.xs\:bui-col-start-11{grid-column-start:11}.xs\:bui-col-start-12{grid-column-start:12}.xs\:bui-col-start-13{grid-column-start:13}.xs\:bui-col-start-auto{grid-column-start:auto}.xs\:bui-col-end-1{grid-column-end:1}.xs\:bui-col-end-2{grid-column-end:2}.xs\:bui-col-end-3{grid-column-end:3}.xs\:bui-col-end-4{grid-column-end:4}.xs\:bui-col-end-5{grid-column-end:5}.xs\:bui-col-end-6{grid-column-end:6}.xs\:bui-col-end-7{grid-column-end:7}.xs\:bui-col-end-8{grid-column-end:8}.xs\:bui-col-end-9{grid-column-end:9}.xs\:bui-col-end-10{grid-column-end:10}.xs\:bui-col-end-11{grid-column-end:11}.xs\:bui-col-end-12{grid-column-end:12}.xs\:bui-col-end-13{grid-column-end:13}.xs\:bui-col-end-auto{grid-column-end:auto}.xs\:bui-row-span-1{grid-row:span 1/span 1}.xs\:bui-row-span-2{grid-row:span 2/span 2}.xs\:bui-row-span-3{grid-row:span 3/span 3}.xs\:bui-row-span-4{grid-row:span 4/span 4}.xs\:bui-row-span-5{grid-row:span 5/span 5}.xs\:bui-row-span-6{grid-row:span 6/span 6}.xs\:bui-row-span-7{grid-row:span 7/span 7}.xs\:bui-row-span-8{grid-row:span 8/span 8}.xs\:bui-row-span-9{grid-row:span 9/span 9}.xs\:bui-row-span-10{grid-row:span 10/span 10}.xs\:bui-row-span-11{grid-row:span 11/span 11}.xs\:bui-row-span-12{grid-row:span 12/span 12}.xs\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=768px){.sm\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.sm\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.sm\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.sm\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.sm\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:bui-col-span-1{grid-column:span 1/span 1}.sm\:bui-col-span-2{grid-column:span 2/span 2}.sm\:bui-col-span-3{grid-column:span 3/span 3}.sm\:bui-col-span-4{grid-column:span 4/span 4}.sm\:bui-col-span-5{grid-column:span 5/span 5}.sm\:bui-col-span-6{grid-column:span 6/span 6}.sm\:bui-col-span-7{grid-column:span 7/span 7}.sm\:bui-col-span-8{grid-column:span 8/span 8}.sm\:bui-col-span-9{grid-column:span 9/span 9}.sm\:bui-col-span-10{grid-column:span 10/span 10}.sm\:bui-col-span-11{grid-column:span 11/span 11}.sm\:bui-col-span-12{grid-column:span 12/span 12}.sm\:bui-col-span-auto{grid-column:span auto/span auto}.sm\:bui-col-start-1{grid-column-start:1}.sm\:bui-col-start-2{grid-column-start:2}.sm\:bui-col-start-3{grid-column-start:3}.sm\:bui-col-start-4{grid-column-start:4}.sm\:bui-col-start-5{grid-column-start:5}.sm\:bui-col-start-6{grid-column-start:6}.sm\:bui-col-start-7{grid-column-start:7}.sm\:bui-col-start-8{grid-column-start:8}.sm\:bui-col-start-9{grid-column-start:9}.sm\:bui-col-start-10{grid-column-start:10}.sm\:bui-col-start-11{grid-column-start:11}.sm\:bui-col-start-12{grid-column-start:12}.sm\:bui-col-start-13{grid-column-start:13}.sm\:bui-col-start-auto{grid-column-start:auto}.sm\:bui-col-end-1{grid-column-end:1}.sm\:bui-col-end-2{grid-column-end:2}.sm\:bui-col-end-3{grid-column-end:3}.sm\:bui-col-end-4{grid-column-end:4}.sm\:bui-col-end-5{grid-column-end:5}.sm\:bui-col-end-6{grid-column-end:6}.sm\:bui-col-end-7{grid-column-end:7}.sm\:bui-col-end-8{grid-column-end:8}.sm\:bui-col-end-9{grid-column-end:9}.sm\:bui-col-end-10{grid-column-end:10}.sm\:bui-col-end-11{grid-column-end:11}.sm\:bui-col-end-12{grid-column-end:12}.sm\:bui-col-end-13{grid-column-end:13}.sm\:bui-col-end-auto{grid-column-end:auto}.sm\:bui-row-span-1{grid-row:span 1/span 1}.sm\:bui-row-span-2{grid-row:span 2/span 2}.sm\:bui-row-span-3{grid-row:span 3/span 3}.sm\:bui-row-span-4{grid-row:span 4/span 4}.sm\:bui-row-span-5{grid-row:span 5/span 5}.sm\:bui-row-span-6{grid-row:span 6/span 6}.sm\:bui-row-span-7{grid-row:span 7/span 7}.sm\:bui-row-span-8{grid-row:span 8/span 8}.sm\:bui-row-span-9{grid-row:span 9/span 9}.sm\:bui-row-span-10{grid-row:span 10/span 10}.sm\:bui-row-span-11{grid-row:span 11/span 11}.sm\:bui-row-span-12{grid-row:span 12/span 12}.sm\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1024px){.md\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.md\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.md\:bui-col-span-1{grid-column:span 1/span 1}.md\:bui-col-span-2{grid-column:span 2/span 2}.md\:bui-col-span-3{grid-column:span 3/span 3}.md\:bui-col-span-4{grid-column:span 4/span 4}.md\:bui-col-span-5{grid-column:span 5/span 5}.md\:bui-col-span-6{grid-column:span 6/span 6}.md\:bui-col-span-7{grid-column:span 7/span 7}.md\:bui-col-span-8{grid-column:span 8/span 8}.md\:bui-col-span-9{grid-column:span 9/span 9}.md\:bui-col-span-10{grid-column:span 10/span 10}.md\:bui-col-span-11{grid-column:span 11/span 11}.md\:bui-col-span-12{grid-column:span 12/span 12}.md\:bui-col-span-auto{grid-column:span auto/span auto}.md\:bui-col-start-1{grid-column-start:1}.md\:bui-col-start-2{grid-column-start:2}.md\:bui-col-start-3{grid-column-start:3}.md\:bui-col-start-4{grid-column-start:4}.md\:bui-col-start-5{grid-column-start:5}.md\:bui-col-start-6{grid-column-start:6}.md\:bui-col-start-7{grid-column-start:7}.md\:bui-col-start-8{grid-column-start:8}.md\:bui-col-start-9{grid-column-start:9}.md\:bui-col-start-10{grid-column-start:10}.md\:bui-col-start-11{grid-column-start:11}.md\:bui-col-start-12{grid-column-start:12}.md\:bui-col-start-13{grid-column-start:13}.md\:bui-col-start-auto{grid-column-start:auto}.md\:bui-col-end-1{grid-column-end:1}.md\:bui-col-end-2{grid-column-end:2}.md\:bui-col-end-3{grid-column-end:3}.md\:bui-col-end-4{grid-column-end:4}.md\:bui-col-end-5{grid-column-end:5}.md\:bui-col-end-6{grid-column-end:6}.md\:bui-col-end-7{grid-column-end:7}.md\:bui-col-end-8{grid-column-end:8}.md\:bui-col-end-9{grid-column-end:9}.md\:bui-col-end-10{grid-column-end:10}.md\:bui-col-end-11{grid-column-end:11}.md\:bui-col-end-12{grid-column-end:12}.md\:bui-col-end-13{grid-column-end:13}.md\:bui-col-end-auto{grid-column-end:auto}.md\:bui-row-span-1{grid-row:span 1/span 1}.md\:bui-row-span-2{grid-row:span 2/span 2}.md\:bui-row-span-3{grid-row:span 3/span 3}.md\:bui-row-span-4{grid-row:span 4/span 4}.md\:bui-row-span-5{grid-row:span 5/span 5}.md\:bui-row-span-6{grid-row:span 6/span 6}.md\:bui-row-span-7{grid-row:span 7/span 7}.md\:bui-row-span-8{grid-row:span 8/span 8}.md\:bui-row-span-9{grid-row:span 9/span 9}.md\:bui-row-span-10{grid-row:span 10/span 10}.md\:bui-row-span-11{grid-row:span 11/span 11}.md\:bui-row-span-12{grid-row:span 12/span 12}.md\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1280px){.lg\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.lg\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.lg\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.lg\:bui-col-span-1{grid-column:span 1/span 1}.lg\:bui-col-span-2{grid-column:span 2/span 2}.lg\:bui-col-span-3{grid-column:span 3/span 3}.lg\:bui-col-span-4{grid-column:span 4/span 4}.lg\:bui-col-span-5{grid-column:span 5/span 5}.lg\:bui-col-span-6{grid-column:span 6/span 6}.lg\:bui-col-span-7{grid-column:span 7/span 7}.lg\:bui-col-span-8{grid-column:span 8/span 8}.lg\:bui-col-span-9{grid-column:span 9/span 9}.lg\:bui-col-span-10{grid-column:span 10/span 10}.lg\:bui-col-span-11{grid-column:span 11/span 11}.lg\:bui-col-span-12{grid-column:span 12/span 12}.lg\:bui-col-span-auto{grid-column:span auto/span auto}.lg\:bui-col-start-1{grid-column-start:1}.lg\:bui-col-start-2{grid-column-start:2}.lg\:bui-col-start-3{grid-column-start:3}.lg\:bui-col-start-4{grid-column-start:4}.lg\:bui-col-start-5{grid-column-start:5}.lg\:bui-col-start-6{grid-column-start:6}.lg\:bui-col-start-7{grid-column-start:7}.lg\:bui-col-start-8{grid-column-start:8}.lg\:bui-col-start-9{grid-column-start:9}.lg\:bui-col-start-10{grid-column-start:10}.lg\:bui-col-start-11{grid-column-start:11}.lg\:bui-col-start-12{grid-column-start:12}.lg\:bui-col-start-13{grid-column-start:13}.lg\:bui-col-start-auto{grid-column-start:auto}.lg\:bui-col-end-1{grid-column-end:1}.lg\:bui-col-end-2{grid-column-end:2}.lg\:bui-col-end-3{grid-column-end:3}.lg\:bui-col-end-4{grid-column-end:4}.lg\:bui-col-end-5{grid-column-end:5}.lg\:bui-col-end-6{grid-column-end:6}.lg\:bui-col-end-7{grid-column-end:7}.lg\:bui-col-end-8{grid-column-end:8}.lg\:bui-col-end-9{grid-column-end:9}.lg\:bui-col-end-10{grid-column-end:10}.lg\:bui-col-end-11{grid-column-end:11}.lg\:bui-col-end-12{grid-column-end:12}.lg\:bui-col-end-13{grid-column-end:13}.lg\:bui-col-end-auto{grid-column-end:auto}.lg\:bui-row-span-1{grid-row:span 1/span 1}.lg\:bui-row-span-2{grid-row:span 2/span 2}.lg\:bui-row-span-3{grid-row:span 3/span 3}.lg\:bui-row-span-4{grid-row:span 4/span 4}.lg\:bui-row-span-5{grid-row:span 5/span 5}.lg\:bui-row-span-6{grid-row:span 6/span 6}.lg\:bui-row-span-7{grid-row:span 7/span 7}.lg\:bui-row-span-8{grid-row:span 8/span 8}.lg\:bui-row-span-9{grid-row:span 9/span 9}.lg\:bui-row-span-10{grid-row:span 10/span 10}.lg\:bui-row-span-11{grid-row:span 11/span 11}.lg\:bui-row-span-12{grid-row:span 12/span 12}.lg\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1536px){.xl\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xl\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xl\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xl\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xl\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xl\:bui-col-span-1{grid-column:span 1/span 1}.xl\:bui-col-span-2{grid-column:span 2/span 2}.xl\:bui-col-span-3{grid-column:span 3/span 3}.xl\:bui-col-span-4{grid-column:span 4/span 4}.xl\:bui-col-span-5{grid-column:span 5/span 5}.xl\:bui-col-span-6{grid-column:span 6/span 6}.xl\:bui-col-span-7{grid-column:span 7/span 7}.xl\:bui-col-span-8{grid-column:span 8/span 8}.xl\:bui-col-span-9{grid-column:span 9/span 9}.xl\:bui-col-span-10{grid-column:span 10/span 10}.xl\:bui-col-span-11{grid-column:span 11/span 11}.xl\:bui-col-span-12{grid-column:span 12/span 12}.xl\:bui-col-span-auto{grid-column:span auto/span auto}.xl\:bui-col-start-1{grid-column-start:1}.xl\:bui-col-start-2{grid-column-start:2}.xl\:bui-col-start-3{grid-column-start:3}.xl\:bui-col-start-4{grid-column-start:4}.xl\:bui-col-start-5{grid-column-start:5}.xl\:bui-col-start-6{grid-column-start:6}.xl\:bui-col-start-7{grid-column-start:7}.xl\:bui-col-start-8{grid-column-start:8}.xl\:bui-col-start-9{grid-column-start:9}.xl\:bui-col-start-10{grid-column-start:10}.xl\:bui-col-start-11{grid-column-start:11}.xl\:bui-col-start-12{grid-column-start:12}.xl\:bui-col-start-13{grid-column-start:13}.xl\:bui-col-start-auto{grid-column-start:auto}.xl\:bui-col-end-1{grid-column-end:1}.xl\:bui-col-end-2{grid-column-end:2}.xl\:bui-col-end-3{grid-column-end:3}.xl\:bui-col-end-4{grid-column-end:4}.xl\:bui-col-end-5{grid-column-end:5}.xl\:bui-col-end-6{grid-column-end:6}.xl\:bui-col-end-7{grid-column-end:7}.xl\:bui-col-end-8{grid-column-end:8}.xl\:bui-col-end-9{grid-column-end:9}.xl\:bui-col-end-10{grid-column-end:10}.xl\:bui-col-end-11{grid-column-end:11}.xl\:bui-col-end-12{grid-column-end:12}.xl\:bui-col-end-13{grid-column-end:13}.xl\:bui-col-end-auto{grid-column-end:auto}.xl\:bui-row-span-1{grid-row:span 1/span 1}.xl\:bui-row-span-2{grid-row:span 2/span 2}.xl\:bui-row-span-3{grid-row:span 3/span 3}.xl\:bui-row-span-4{grid-row:span 4/span 4}.xl\:bui-row-span-5{grid-row:span 5/span 5}.xl\:bui-row-span-6{grid-row:span 6/span 6}.xl\:bui-row-span-7{grid-row:span 7/span 7}.xl\:bui-row-span-8{grid-row:span 8/span 8}.xl\:bui-row-span-9{grid-row:span 9/span 9}.xl\:bui-row-span-10{grid-row:span 10/span 10}.xl\:bui-row-span-11{grid-row:span 11/span 11}.xl\:bui-row-span-12{grid-row:span 12/span 12}.xl\:bui-row-span-auto{grid-row:span auto/span auto}}.bui-gap{gap:var(--gap)}.bui-gap-0\.5{gap:var(--bui-space-0_5)}.bui-gap-1{gap:var(--bui-space-1)}.bui-gap-1\.5{gap:var(--bui-space-1_5)}.bui-gap-2{gap:var(--bui-space-2)}.bui-gap-3{gap:var(--bui-space-3)}.bui-gap-4{gap:var(--bui-space-4)}.bui-gap-5{gap:var(--bui-space-5)}.bui-gap-6{gap:var(--bui-space-6)}.bui-gap-7{gap:var(--bui-space-7)}.bui-gap-8{gap:var(--bui-space-8)}.bui-gap-9{gap:var(--bui-space-9)}.bui-gap-10{gap:var(--bui-space-10)}.bui-gap-11{gap:var(--bui-space-11)}.bui-gap-12{gap:var(--bui-space-12)}.bui-gap-13{gap:var(--bui-space-13)}.bui-gap-14{gap:var(--bui-space-14)}@media (width>=640px){.xs\:bui-gap{gap:var(--gap-xs)}.xs\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xs\:bui-gap-1{gap:var(--bui-space-1)}.xs\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xs\:bui-gap-2{gap:var(--bui-space-2)}.xs\:bui-gap-3{gap:var(--bui-space-3)}.xs\:bui-gap-4{gap:var(--bui-space-4)}.xs\:bui-gap-5{gap:var(--bui-space-5)}.xs\:bui-gap-6{gap:var(--bui-space-6)}.xs\:bui-gap-7{gap:var(--bui-space-7)}.xs\:bui-gap-8{gap:var(--bui-space-8)}.xs\:bui-gap-9{gap:var(--bui-space-9)}.xs\:bui-gap-10{gap:var(--bui-space-10)}.xs\:bui-gap-11{gap:var(--bui-space-11)}.xs\:bui-gap-12{gap:var(--bui-space-12)}.xs\:bui-gap-13{gap:var(--bui-space-13)}.xs\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-gap{gap:var(--gap-sm)}.sm\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.sm\:bui-gap-1{gap:var(--bui-space-1)}.sm\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.sm\:bui-gap-2{gap:var(--bui-space-2)}.sm\:bui-gap-3{gap:var(--bui-space-3)}.sm\:bui-gap-4{gap:var(--bui-space-4)}.sm\:bui-gap-5{gap:var(--bui-space-5)}.sm\:bui-gap-6{gap:var(--bui-space-6)}.sm\:bui-gap-7{gap:var(--bui-space-7)}.sm\:bui-gap-8{gap:var(--bui-space-8)}.sm\:bui-gap-9{gap:var(--bui-space-9)}.sm\:bui-gap-10{gap:var(--bui-space-10)}.sm\:bui-gap-11{gap:var(--bui-space-11)}.sm\:bui-gap-12{gap:var(--bui-space-12)}.sm\:bui-gap-13{gap:var(--bui-space-13)}.sm\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-gap{gap:var(--gap-md)}.md\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.md\:bui-gap-1{gap:var(--bui-space-1)}.md\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.md\:bui-gap-2{gap:var(--bui-space-2)}.md\:bui-gap-3{gap:var(--bui-space-3)}.md\:bui-gap-4{gap:var(--bui-space-4)}.md\:bui-gap-5{gap:var(--bui-space-5)}.md\:bui-gap-6{gap:var(--bui-space-6)}.md\:bui-gap-7{gap:var(--bui-space-7)}.md\:bui-gap-8{gap:var(--bui-space-8)}.md\:bui-gap-9{gap:var(--bui-space-9)}.md\:bui-gap-10{gap:var(--bui-space-10)}.md\:bui-gap-11{gap:var(--bui-space-11)}.md\:bui-gap-12{gap:var(--bui-space-12)}.md\:bui-gap-13{gap:var(--bui-space-13)}.md\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-gap{gap:var(--gap-lg)}.lg\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.lg\:bui-gap-1{gap:var(--bui-space-1)}.lg\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.lg\:bui-gap-2{gap:var(--bui-space-2)}.lg\:bui-gap-3{gap:var(--bui-space-3)}.lg\:bui-gap-4{gap:var(--bui-space-4)}.lg\:bui-gap-5{gap:var(--bui-space-5)}.lg\:bui-gap-6{gap:var(--bui-space-6)}.lg\:bui-gap-7{gap:var(--bui-space-7)}.lg\:bui-gap-8{gap:var(--bui-space-8)}.lg\:bui-gap-9{gap:var(--bui-space-9)}.lg\:bui-gap-10{gap:var(--bui-space-10)}.lg\:bui-gap-11{gap:var(--bui-space-11)}.lg\:bui-gap-12{gap:var(--bui-space-12)}.lg\:bui-gap-13{gap:var(--bui-space-13)}.lg\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-gap{gap:var(--gap-xl)}.xl\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xl\:bui-gap-1{gap:var(--bui-space-1)}.xl\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xl\:bui-gap-2{gap:var(--bui-space-2)}.xl\:bui-gap-3{gap:var(--bui-space-3)}.xl\:bui-gap-4{gap:var(--bui-space-4)}.xl\:bui-gap-5{gap:var(--bui-space-5)}.xl\:bui-gap-6{gap:var(--bui-space-6)}.xl\:bui-gap-7{gap:var(--bui-space-7)}.xl\:bui-gap-8{gap:var(--bui-space-8)}.xl\:bui-gap-9{gap:var(--bui-space-9)}.xl\:bui-gap-10{gap:var(--bui-space-10)}.xl\:bui-gap-11{gap:var(--bui-space-11)}.xl\:bui-gap-12{gap:var(--bui-space-12)}.xl\:bui-gap-13{gap:var(--bui-space-13)}.xl\:bui-gap-14{gap:var(--bui-space-14)}}.bui-align-start{align-items:start}.bui-align-center{align-items:center}.bui-align-end{align-items:end}.bui-align-baseline{align-items:baseline}.bui-align-stretch{align-items:stretch}.bui-jc-start{justify-content:start}.bui-jc-center{justify-content:center}.bui-jc-end{justify-content:end}.bui-jc-between{justify-content:space-between}.bui-fd-row{flex-direction:row}.bui-fd-column{flex-direction:column}.bui-fd-row-reverse{flex-direction:row-reverse}.bui-fd-column-reverse{flex-direction:column-reverse}@media (width>=640px){.xs\:bui-align-start{align-items:start}.xs\:bui-align-center{align-items:center}.xs\:bui-align-end{align-items:end}.xs\:bui-align-stretch{align-items:stretch}.xs\:bui-jc-start{justify-content:start}.xs\:bui-jc-center{justify-content:center}.xs\:bui-jc-end{justify-content:end}.xs\:bui-jc-between{justify-content:space-between}.xs\:bui-fd-row{flex-direction:row}.xs\:bui-fd-column{flex-direction:column}.xs\:bui-fd-row-reverse{flex-direction:row-reverse}.xs\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=768px){.sm\:bui-align-start{align-items:start}.sm\:bui-align-center{align-items:center}.sm\:bui-align-end{align-items:end}.sm\:bui-align-stretch{align-items:stretch}.sm\:bui-jc-start{justify-content:start}.sm\:bui-jc-center{justify-content:center}.sm\:bui-jc-end{justify-content:end}.sm\:bui-jc-between{justify-content:space-between}.sm\:bui-fd-row{flex-direction:row}.sm\:bui-fd-column{flex-direction:column}.sm\:bui-fd-row-reverse{flex-direction:row-reverse}.sm\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1024px){.md\:bui-align-start{align-items:start}.md\:bui-align-center{align-items:center}.md\:bui-align-end{align-items:end}.md\:bui-align-stretch{align-items:stretch}.md\:bui-jc-start{justify-content:start}.md\:bui-jc-center{justify-content:center}.md\:bui-jc-end{justify-content:end}.md\:bui-jc-between{justify-content:space-between}.md\:bui-fd-row{flex-direction:row}.md\:bui-fd-column{flex-direction:column}.md\:bui-fd-row-reverse{flex-direction:row-reverse}.md\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1280px){.lg\:bui-align-start{align-items:start}.lg\:bui-align-center{align-items:center}.lg\:bui-align-end{align-items:end}.lg\:bui-align-stretch{align-items:stretch}.lg\:bui-jc-start{justify-content:start}.lg\:bui-jc-center{justify-content:center}.lg\:bui-jc-end{justify-content:end}.lg\:bui-jc-between{justify-content:space-between}.lg\:bui-fd-row{flex-direction:row}.lg\:bui-fd-column{flex-direction:column}.lg\:bui-fd-row-reverse{flex-direction:row-reverse}.lg\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1536px){.xl\:bui-align-start{align-items:start}.xl\:bui-align-center{align-items:center}.xl\:bui-align-end{align-items:end}.xl\:bui-align-stretch{align-items:stretch}.xl\:bui-jc-start{justify-content:start}.xl\:bui-jc-center{justify-content:center}.xl\:bui-jc-end{justify-content:end}.xl\:bui-jc-between{justify-content:space-between}.xl\:bui-fd-row{flex-direction:row}.xl\:bui-fd-column{flex-direction:column}.xl\:bui-fd-row-reverse{flex-direction:row-reverse}.xl\:bui-fd-column-reverse{flex-direction:column-reverse}}} \ No newline at end of file diff --git a/packages/ui/css/styles.css b/packages/ui/css/styles.css index 23bed60b87..2db8988873 100644 --- a/packages/ui/css/styles.css +++ b/packages/ui/css/styles.css @@ -100,6 +100,7 @@ --bui-ring: #1f5493; --bui-scrollbar: #a0a0a03b; --bui-scrollbar-thumb: #a0a0a0; + --bui-shadow-overlay: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a; --bui-animate-pulse: pulse 2s cubic-bezier(.4, 0, .6, 1) infinite; } @@ -161,6 +162,7 @@ --bui-ring: #1f5493; --bui-scrollbar: #3636363a; --bui-scrollbar-thumb: #575757; + --bui-shadow-overlay: none; } } diff --git a/packages/ui/src/components/Menu/Menu.module.css b/packages/ui/src/components/Menu/Menu.module.css index 606714583e..ef86eaba6d 100644 --- a/packages/ui/src/components/Menu/Menu.module.css +++ b/packages/ui/src/components/Menu/Menu.module.css @@ -20,6 +20,7 @@ .bui-MenuPopover { display: flex; flex-direction: column; + box-shadow: var(--bui-shadow-overlay); border: 1px solid var(--bui-border); border-radius: var(--bui-radius-2); background: var(--bui-bg-surface-1); diff --git a/packages/ui/src/components/Popover/Popover.module.css b/packages/ui/src/components/Popover/Popover.module.css index 910c2b8c76..5c94446771 100644 --- a/packages/ui/src/components/Popover/Popover.module.css +++ b/packages/ui/src/components/Popover/Popover.module.css @@ -18,13 +18,124 @@ @layer components { .bui-Popover { - margin-right: 12px; + box-shadow: var(--bui-shadow-overlay); + border-radius: var(--bui-radius-3); + background: var(--bui-bg-surface-1); + border: 1px solid var(--bui-gray-3); + forced-color-adjust: none; + outline: none; + max-height: inherit; + display: flex; + flex-direction: column; + /* fixes FF gap */ + transform: translate3d(0, 0, 0); + transition: transform 200ms, opacity 200ms; + + &[data-entering], + &[data-exiting] { + transform: var(--origin); + opacity: 0; + } + + &[data-placement='top'] { + --origin: translateY(4px); + + &:has(.bui-PopoverArrow) { + margin-bottom: var(--bui-space-3); + } + } + + &[data-placement='right'] { + --origin: translateX(-4px); + + &:has(.bui-PopoverArrow) { + margin-left: var(--bui-space-3); + } + } + + &[data-placement='bottom'] { + --origin: translateY(-4px); + + &:has(.bui-PopoverArrow) { + margin-top: var(--bui-space-3); + } + } + + &[data-placement='left'] { + --origin: translateX(4px); + + &:has(.bui-PopoverArrow) { + margin-right: var(--bui-space-3); + } + } + } + + .bui-PopoverContent { overflow-x: hidden; overflow-y: auto; - background-color: var(--bui-bg-surface-1); - border: 1px solid var(--bui-border); - border-radius: var(--bui-radius-3); - padding-block: var(--bui-space-1); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + padding: var(--bui-space-4); + flex: 1 1 auto; + min-height: 0; + } + + .bui-PopoverArrow { + & svg { + display: block; + + /* The popover is rendered overlaying the main + popover element by 1px. This causes the borders + to overlap, which causes minor visual artifacts + with transparent border colors. To mitigate this, + we split the stroke and fill across separate + elements in order to guarantee that the stroke is + always overlaying a consistent color. */ + path:nth-child(1) { + fill: var(--bui-bg-surface-1); + } + + path:nth-child(2) { + fill: var(--bui-gray-3); + } + + /* The arrow svg overlaps the popover by 2px, so we + need to adjust the margins accordingly. */ + --popover-arrow-overlap: -2px; + } + + &[data-placement='top'] svg { + margin-top: var(--popover-arrow-overlap); + } + + &[data-placement='bottom'] svg { + margin-bottom: var(--popover-arrow-overlap); + transform: rotate(180deg); + } + + &[data-placement='right'] svg { + margin-right: var(--popover-arrow-overlap); + transform: rotate(90deg); + } + + &[data-placement='left'] svg { + margin-left: var(--popover-arrow-overlap); + transform: rotate(-90deg); + } + } + + [data-theme='dark'] { + .bui-Popover { + background: var(--bui-bg-surface-2); + border: 1px solid var(--bui-gray-4); + } + + .bui-PopoverArrow { + svg path:nth-child(1) { + fill: var(--bui-bg-surface-2); + } + + svg path:nth-child(2) { + fill: var(--bui-gray-4); + } + } } } diff --git a/packages/ui/src/components/Popover/Popover.stories.tsx b/packages/ui/src/components/Popover/Popover.stories.tsx new file mode 100644 index 0000000000..5ae02cf03a --- /dev/null +++ b/packages/ui/src/components/Popover/Popover.stories.tsx @@ -0,0 +1,276 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import preview from '../../../../../.storybook/preview'; +import { Popover } from './Popover'; +import { Button } from '../Button/Button'; +import { DialogTrigger } from '../Dialog/Dialog'; +import { Text } from '../Text/Text'; +import { Flex } from '../Flex/Flex'; + +const meta = preview.meta({ + title: 'Backstage UI/Popover', + component: Popover, + parameters: { layout: 'centered' }, + argTypes: { + isOpen: { + control: { type: 'boolean' }, + }, + hideArrow: { + control: { type: 'boolean' }, + }, + placement: { + control: { type: 'select' }, + options: [ + 'top', + 'top start', + 'top end', + 'bottom', + 'bottom start', + 'bottom end', + 'left', + 'left start', + 'left end', + 'right', + 'right start', + 'right end', + ], + }, + offset: { + control: { type: 'number' }, + }, + }, + render: ({ children, isOpen, hideArrow, placement, offset }) => ( + + + + {children ?? This is a popover} + + + ), +}); + +export const Default = meta.story({ + args: {}, +}); + +export const IsOpen = meta.story({ + args: { + isOpen: true, + }, +}); + +export const HideArrow = meta.story({ + args: { + isOpen: true, + hideArrow: true, + }, +}); + +export const TopPlacement = meta.story({ + args: { + isOpen: true, + placement: 'top', + }, +}); + +export const RightPlacement = meta.story({ + args: { + isOpen: true, + placement: 'right', + }, +}); + +export const BottomPlacement = meta.story({ + args: { + isOpen: true, + placement: 'bottom', + }, +}); + +export const LeftPlacement = meta.story({ + args: { + isOpen: true, + placement: 'left', + }, +}); + +export const AllPlacements = meta.story({ + parameters: { + controls: { + exclude: ['placement'], + }, + }, + args: { + isOpen: true, + }, + render: ({ isOpen, hideArrow }) => { + return ( + + + + Top placement + + + Right placement + + + Bottom placement + + + Left placement + + + ); + }, +}); + +export const AllPlacementsNoArrow = meta.story({ + parameters: { + controls: { + exclude: ['placement', 'hideArrow'], + }, + }, + args: { + isOpen: true, + hideArrow: true, + }, + render: ({ isOpen }) => { + return ( + + + + Top placement + + + Right placement + + + Bottom placement + + + Left placement + + + ); + }, +}); + +export const WithRichContent = meta.story({ + args: { + isOpen: true, + }, + render: ({ isOpen, hideArrow, placement }) => ( + + + + + Popover Title + + This is a popover with rich content. It can contain multiple + elements and formatted text. + + + + + + + + + ), +}); + +export const CustomOffset = meta.story({ + args: { + isOpen: true, + offset: 20, + placement: 'bottom', + }, +}); + +export const NonModal = meta.story({ + args: { + isOpen: true, + }, + render: ({ isOpen, hideArrow, placement }) => ( + + + + + This is a non-modal popover. You can interact with other elements on + the page while it's open. + + + + ), +}); + +export const WithLongContent = meta.story({ + args: { + isOpen: true, + }, + render: ({ isOpen, hideArrow, placement }) => ( + + + + + Long Content Example + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim + ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip ex ea commodo consequat. + + + Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat + cupidatat non proident, sunt in culpa qui officia deserunt mollit + anim id est laborum. + + + Sed ut perspiciatis unde omnis iste natus error sit voluptatem + accusantium doloremque laudantium, totam rem aperiam, eaque ipsa + quae ab illo inventore veritatis et quasi architecto beatae vitae + dicta sunt explicabo. + + + Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut + fugit, sed quia consequuntur magni dolores eos qui ratione + voluptatem sequi nesciunt. + + + Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, + consectetur, adipisci velit, sed quia non numquam eius modi tempora + incidunt ut labore et dolore magnam aliquam quaerat voluptatem. + + + + + ), +}); diff --git a/packages/ui/src/components/Popover/Popover.tsx b/packages/ui/src/components/Popover/Popover.tsx new file mode 100644 index 0000000000..18a711b0ba --- /dev/null +++ b/packages/ui/src/components/Popover/Popover.tsx @@ -0,0 +1,101 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { forwardRef } from 'react'; +import { OverlayArrow, Popover as AriaPopover } from 'react-aria-components'; +import clsx from 'clsx'; +import { PopoverProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; +import { PopoverDefinition } from './definition'; +import styles from './Popover.module.css'; + +/** + * A popover component built on React Aria Components that displays floating + * content anchored to a trigger element. + * + * @remarks + * The Popover component supports multiple placements (top, right, bottom, left), + * automatic viewport-constrained scrolling, and conditional arrow rendering. It + * automatically handles positioning, collision detection, and ARIA attributes for + * accessibility. Content is automatically padded and scrollable when it exceeds + * available space. + * + * @example + * Basic usage with DialogTrigger: + * ```tsx + * + * + * + * Popover content + * + * + * ``` + * + * @example + * With custom placement and no arrow: + * ```tsx + * + * + * + * Content without arrow + * + * + * ``` + * + * @public + */ +export const Popover = forwardRef( + (props, ref) => { + const { classNames, cleanedProps } = useStyles(PopoverDefinition, props); + const { className, children, hideArrow, ...rest } = cleanedProps; + + return ( + + {({ trigger }) => ( + <> + {!hideArrow && + trigger !== 'MenuTrigger' && + trigger !== 'SubmenuTrigger' && ( + + + + + + + )} +
+ {children} +
+ + )} +
+ ); + }, +); + +Popover.displayName = 'Popover'; diff --git a/packages/ui/src/components/Popover/definition.ts b/packages/ui/src/components/Popover/definition.ts index 8b85180973..185592b20f 100644 --- a/packages/ui/src/components/Popover/definition.ts +++ b/packages/ui/src/components/Popover/definition.ts @@ -22,6 +22,8 @@ import type { ComponentDefinition } from '../../types'; */ export const PopoverDefinition = { classNames: { - root: 'bui-Popover', + popover: 'bui-Popover', + arrow: 'bui-PopoverArrow', + content: 'bui-PopoverContent', }, } as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Popover/index.ts b/packages/ui/src/components/Popover/index.ts index 1e31b8657b..e97be21931 100644 --- a/packages/ui/src/components/Popover/index.ts +++ b/packages/ui/src/components/Popover/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,4 +14,6 @@ * limitations under the License. */ +export { Popover } from './Popover'; +export type { PopoverProps } from './types'; export { PopoverDefinition } from './definition'; diff --git a/packages/ui/src/components/Popover/types.ts b/packages/ui/src/components/Popover/types.ts new file mode 100644 index 0000000000..fd04eef321 --- /dev/null +++ b/packages/ui/src/components/Popover/types.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PopoverProps as AriaPopoverProps } from 'react-aria-components'; + +/** + * Properties for {@link Popover} + * + * @public + */ +export interface PopoverProps extends Omit { + /** + * The content to display inside the popover. + * Content is automatically wrapped with padding and scroll behavior. + */ + children: React.ReactNode; + + /** + * Whether to hide the arrow pointing to the trigger element. + * Arrow is also automatically hidden for MenuTrigger and SubmenuTrigger contexts. + * + * @defaultValue false + */ + hideArrow?: boolean; +} diff --git a/packages/ui/src/components/Tooltip/Tooltip.module.css b/packages/ui/src/components/Tooltip/Tooltip.module.css index 47fedff82d..b8f2fe0c1a 100644 --- a/packages/ui/src/components/Tooltip/Tooltip.module.css +++ b/packages/ui/src/components/Tooltip/Tooltip.module.css @@ -18,8 +18,7 @@ @layer components { .bui-Tooltip { - box-shadow: 0 10px 15px -3px rgba(0 0 0 / 0.1), - 0 4px 6px -4px rgba(0 0 0 / 0.1); + box-shadow: var(--bui-shadow-overlay); border-radius: 4px; background: var(--bui-bg-surface-1); border: 1px solid var(--bui-gray-3); diff --git a/packages/ui/src/css/tokens.css b/packages/ui/src/css/tokens.css index 6a4a783d96..89292e3954 100644 --- a/packages/ui/src/css/tokens.css +++ b/packages/ui/src/css/tokens.css @@ -146,6 +146,10 @@ --bui-scrollbar: #a0a0a03b; --bui-scrollbar-thumb: #a0a0a0; + /* Shadows */ + --bui-shadow-overlay: 0 10px 15px -3px rgba(0 0 0 / 0.1), + 0 4px 6px -4px rgba(0 0 0 / 0.1); + --bui-animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } @@ -226,5 +230,8 @@ --bui-ring: #1f5493; --bui-scrollbar: #3636363a; --bui-scrollbar-thumb: #575757; + + /* Shadows */ + --bui-shadow-overlay: none; } } diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 1c8529f1e6..a721b856a9 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -47,6 +47,7 @@ export * from './components/Text'; export * from './components/TextField'; export * from './components/Tooltip'; export * from './components/Menu'; +export * from './components/Popover'; export * from './components/SearchField'; export * from './components/Link'; export * from './components/Select'; From 638fe306105de59103f6a1ddf9be856a44eead15 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 14 Jan 2026 11:23:57 +0100 Subject: [PATCH 129/202] Update report Signed-off-by: Charles de Dreuille --- packages/ui/report.api.md | 30 +++++++++++++++---- .../components/Popover/Popover.stories.tsx | 28 +++++++++-------- .../ui/src/components/Popover/Popover.tsx | 6 +--- .../ui/src/components/Popover/definition.ts | 2 +- 4 files changed, 42 insertions(+), 24 deletions(-) diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index b7b6604421..8c56f040c2 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -27,7 +27,7 @@ import type { MenuProps as MenuProps_2 } from 'react-aria-components'; import type { MenuSectionProps as MenuSectionProps_2 } from 'react-aria-components'; import type { MenuTriggerProps as MenuTriggerProps_2 } from 'react-aria-components'; import type { ModalOverlayProps } from 'react-aria-components'; -import type { PopoverProps } from 'react-aria-components'; +import { PopoverProps as PopoverProps_2 } from 'react-aria-components'; import type { RadioGroupProps as RadioGroupProps_2 } from 'react-aria-components'; import type { RadioProps as RadioProps_2 } from 'react-aria-components'; import { ReactElement } from 'react'; @@ -1025,7 +1025,7 @@ export interface MenuAutocompleteListBoxProps // (undocumented) placeholder?: string; // (undocumented) - placement?: PopoverProps['placement']; + placement?: PopoverProps_2['placement']; // (undocumented) virtualized?: boolean; } @@ -1041,7 +1041,7 @@ export interface MenuAutocompleteProps // (undocumented) placeholder?: string; // (undocumented) - placement?: PopoverProps['placement']; + placement?: PopoverProps_2['placement']; // (undocumented) virtualized?: boolean; } @@ -1106,7 +1106,7 @@ export interface MenuListBoxProps // (undocumented) maxWidth?: string; // (undocumented) - placement?: PopoverProps['placement']; + placement?: PopoverProps_2['placement']; // (undocumented) virtualized?: boolean; } @@ -1120,7 +1120,7 @@ export interface MenuProps // (undocumented) maxWidth?: string; // (undocumented) - placement?: PopoverProps['placement']; + placement?: PopoverProps_2['placement']; // (undocumented) virtualized?: boolean; } @@ -1206,6 +1206,26 @@ export interface PaginationOptions { showPageSizeOptions?: boolean; } +// @public +export const Popover: ForwardRefExoticComponent< + PopoverProps & RefAttributes +>; + +// @public +export const PopoverDefinition: { + readonly classNames: { + readonly root: 'bui-Popover'; + readonly arrow: 'bui-PopoverArrow'; + readonly content: 'bui-PopoverContent'; + }; +}; + +// @public +export interface PopoverProps extends Omit { + children: React.ReactNode; + hideArrow?: boolean; +} + // @public (undocumented) export interface QueryOptions { // (undocumented) diff --git a/packages/ui/src/components/Popover/Popover.stories.tsx b/packages/ui/src/components/Popover/Popover.stories.tsx index 5ae02cf03a..ea92bf9d5c 100644 --- a/packages/ui/src/components/Popover/Popover.stories.tsx +++ b/packages/ui/src/components/Popover/Popover.stories.tsx @@ -68,51 +68,53 @@ const meta = preview.meta({ }); export const Default = meta.story({ - args: {}, + args: { + children: This is a popover, + }, }); -export const IsOpen = meta.story({ +export const IsOpen = Default.extend({ args: { isOpen: true, }, }); -export const HideArrow = meta.story({ +export const HideArrow = Default.extend({ args: { isOpen: true, hideArrow: true, }, }); -export const TopPlacement = meta.story({ +export const TopPlacement = Default.extend({ args: { isOpen: true, placement: 'top', }, }); -export const RightPlacement = meta.story({ +export const RightPlacement = Default.extend({ args: { isOpen: true, placement: 'right', }, }); -export const BottomPlacement = meta.story({ +export const BottomPlacement = Default.extend({ args: { isOpen: true, placement: 'bottom', }, }); -export const LeftPlacement = meta.story({ +export const LeftPlacement = Default.extend({ args: { isOpen: true, placement: 'left', }, }); -export const AllPlacements = meta.story({ +export const AllPlacements = Default.extend({ parameters: { controls: { exclude: ['placement'], @@ -142,7 +144,7 @@ export const AllPlacements = meta.story({ }, }); -export const AllPlacementsNoArrow = meta.story({ +export const AllPlacementsNoArrow = Default.extend({ parameters: { controls: { exclude: ['placement', 'hideArrow'], @@ -173,7 +175,7 @@ export const AllPlacementsNoArrow = meta.story({ }, }); -export const WithRichContent = meta.story({ +export const WithRichContent = Default.extend({ args: { isOpen: true, }, @@ -201,7 +203,7 @@ export const WithRichContent = meta.story({ ), }); -export const CustomOffset = meta.story({ +export const CustomOffset = Default.extend({ args: { isOpen: true, offset: 20, @@ -209,7 +211,7 @@ export const CustomOffset = meta.story({ }, }); -export const NonModal = meta.story({ +export const NonModal = Default.extend({ args: { isOpen: true, }, @@ -231,7 +233,7 @@ export const NonModal = meta.story({ ), }); -export const WithLongContent = meta.story({ +export const WithLongContent = Default.extend({ args: { isOpen: true, }, diff --git a/packages/ui/src/components/Popover/Popover.tsx b/packages/ui/src/components/Popover/Popover.tsx index 18a711b0ba..1e38e98375 100644 --- a/packages/ui/src/components/Popover/Popover.tsx +++ b/packages/ui/src/components/Popover/Popover.tsx @@ -64,11 +64,7 @@ export const Popover = forwardRef( return ( diff --git a/packages/ui/src/components/Popover/definition.ts b/packages/ui/src/components/Popover/definition.ts index 185592b20f..1361d4bbcc 100644 --- a/packages/ui/src/components/Popover/definition.ts +++ b/packages/ui/src/components/Popover/definition.ts @@ -22,7 +22,7 @@ import type { ComponentDefinition } from '../../types'; */ export const PopoverDefinition = { classNames: { - popover: 'bui-Popover', + root: 'bui-Popover', arrow: 'bui-PopoverArrow', content: 'bui-PopoverContent', }, From 872eb91313a57390146115881cb31c43e66a0b41 Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Wed, 14 Jan 2026 12:03:47 +0100 Subject: [PATCH 130/202] Upgrade `zod-to-json-schema` to latest version Signed-off-by: Jon Koops --- .changeset/whole-yaks-repair.md | 14 ++++++++++++ packages/backend-defaults/package.json | 2 +- packages/backend-test-utils/package.json | 2 +- packages/frontend-plugin-api/package.json | 2 +- plugins/auth-node/package.json | 2 +- plugins/permission-common/package.json | 2 +- plugins/permission-node/package.json | 2 +- plugins/scaffolder-backend/package.json | 2 +- plugins/scaffolder-node/package.json | 2 +- plugins/scaffolder-react/package.json | 2 +- plugins/scaffolder/package.json | 2 +- yarn.lock | 28 +++++++++++------------ 12 files changed, 38 insertions(+), 24 deletions(-) create mode 100644 .changeset/whole-yaks-repair.md diff --git a/.changeset/whole-yaks-repair.md b/.changeset/whole-yaks-repair.md new file mode 100644 index 0000000000..b23fdaa285 --- /dev/null +++ b/.changeset/whole-yaks-repair.md @@ -0,0 +1,14 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/backend-test-utils': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/backend-defaults': patch +'@backstage/plugin-permission-common': patch +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-permission-node': patch +'@backstage/plugin-scaffolder-node': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-auth-node': patch +--- + +Upgrade `zod-to-json-schema` to latest version diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index c4f154dfb7..12f1eee944 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -195,7 +195,7 @@ "yauzl": "^3.0.0", "yn": "^4.0.0", "zod": "^3.22.4", - "zod-to-json-schema": "^3.20.4" + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@aws-sdk/util-stream-node": "^3.350.0", diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index c08deb6986..8647bc7d89 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -80,7 +80,7 @@ "uuid": "^11.0.0", "yn": "^4.0.0", "zod": "^3.22.4", - "zod-to-json-schema": "^3.20.4" + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index bda034ac78..358977bad4 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -44,7 +44,7 @@ "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", "zod": "^3.22.4", - "zod-to-json-schema": "^3.21.4" + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 3e095caa91..c07aefe30e 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -51,7 +51,7 @@ "lodash": "^4.17.21", "passport": "^0.7.0", "zod": "^3.22.4", - "zod-to-json-schema": "^3.21.4", + "zod-to-json-schema": "^3.25.1", "zod-validation-error": "^3.4.0" }, "devDependencies": { diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index 9464d3c8f3..971790acb2 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -54,7 +54,7 @@ "cross-fetch": "^4.0.0", "uuid": "^11.0.0", "zod": "^3.22.4", - "zod-to-json-schema": "^3.20.4" + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 1ae3935b4c..7e1709817b 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -65,7 +65,7 @@ "express": "^4.22.0", "express-promise-router": "^4.1.0", "zod": "^3.22.4", - "zod-to-json-schema": "^3.20.4" + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 8b8093f380..1ce7a983b2 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -112,7 +112,7 @@ "yaml": "^2.0.0", "zen-observable": "^0.10.0", "zod": "^3.22.4", - "zod-to-json-schema": "^3.20.4" + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@backstage/backend-app-api": "workspace:^", diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 3a6c1650fb..82cd02f546 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -73,7 +73,7 @@ "winston": "^3.2.1", "winston-transport": "^4.7.0", "zod": "^3.22.4", - "zod-to-json-schema": "^3.20.4" + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 3eca0fe12f..bedf8cf35a 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -93,7 +93,7 @@ "use-immer": "^0.11.0", "zen-observable": "^0.10.0", "zod": "^3.22.4", - "zod-to-json-schema": "^3.20.4" + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index b6dad69fb9..72da8875d1 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -102,7 +102,7 @@ "react-window": "^1.8.10", "yaml": "^2.0.0", "zod": "^3.22.4", - "zod-to-json-schema": "^3.20.4" + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/yarn.lock b/yarn.lock index e318c2bdd2..103cc9930f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3053,7 +3053,7 @@ __metadata: yauzl: "npm:^3.0.0" yn: "npm:^4.0.0" zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod-to-json-schema: "npm:^3.25.1" peerDependencies: "@google-cloud/cloud-sql-connector": ^1.4.0 better-sqlite3: ^12.0.0 @@ -3205,7 +3205,7 @@ __metadata: uuid: "npm:^11.0.0" yn: "npm:^4.0.0" zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod-to-json-schema: "npm:^3.25.1" languageName: unknown linkType: soft @@ -3931,7 +3931,7 @@ __metadata: react-dom: "npm:^18.0.2" react-router-dom: "npm:^6.3.0" zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.21.4" + zod-to-json-schema: "npm:^3.25.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 @@ -4696,7 +4696,7 @@ __metadata: supertest: "npm:^7.0.0" uuid: "npm:^11.0.0" zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.21.4" + zod-to-json-schema: "npm:^3.25.1" zod-validation-error: "npm:^3.4.0" languageName: unknown linkType: soft @@ -6433,7 +6433,7 @@ __metadata: msw: "npm:^1.0.0" uuid: "npm:^11.0.0" zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod-to-json-schema: "npm:^3.25.1" languageName: unknown linkType: soft @@ -6456,7 +6456,7 @@ __metadata: msw: "npm:^1.0.0" supertest: "npm:^7.0.0" zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod-to-json-schema: "npm:^3.25.1" languageName: unknown linkType: soft @@ -6875,7 +6875,7 @@ __metadata: yaml: "npm:^2.0.0" zen-observable: "npm:^0.10.0" zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod-to-json-schema: "npm:^3.25.1" languageName: unknown linkType: soft @@ -6953,7 +6953,7 @@ __metadata: winston: "npm:^3.2.1" winston-transport: "npm:^4.7.0" zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod-to-json-schema: "npm:^3.25.1" languageName: unknown linkType: soft @@ -7012,7 +7012,7 @@ __metadata: use-immer: "npm:^0.11.0" zen-observable: "npm:^0.10.0" zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod-to-json-schema: "npm:^3.25.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 @@ -7092,7 +7092,7 @@ __metadata: swr: "npm:^2.0.0" yaml: "npm:^2.0.0" zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.20.4" + zod-to-json-schema: "npm:^3.25.1" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 @@ -51118,12 +51118,12 @@ __metadata: languageName: node linkType: hard -"zod-to-json-schema@npm:^3.20.4, zod-to-json-schema@npm:^3.21.4, zod-to-json-schema@npm:^3.25.0": - version: 3.25.0 - resolution: "zod-to-json-schema@npm:3.25.0" +"zod-to-json-schema@npm:^3.25.0, zod-to-json-schema@npm:^3.25.1": + version: 3.25.1 + resolution: "zod-to-json-schema@npm:3.25.1" peerDependencies: zod: ^3.25 || ^4 - checksum: 10/cb932e20b5b5e64c75b2c34a7e6dae74b727292eab9e014b93c2607378b8cb1b227f80b429053ceb77c8e0dddc338837f9e534b2a658540ff60c9e4ffdc7cc19 + checksum: 10/744dd370f4452c8db120de1475ea4d484a11df884c4636111d630e5e1351b8a7590d99cf14a2b9f21e7906f8b78721d958663a7973a40994e7d28770876674cc languageName: node linkType: hard From 4eeba9ed61cc2aab43b13af825bb0a9c9c173b68 Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Tue, 13 Jan 2026 15:35:54 +0100 Subject: [PATCH 131/202] Upgrade `zod-validation-error` to version 4 Signed-off-by: Jon Koops --- .changeset/sour-mails-smoke.md | 6 ++++++ packages/cli/package.json | 2 +- .../new/lib/preparation/loadPortableTemplate.ts | 2 +- .../lib/preparation/loadPortableTemplateConfig.ts | 2 +- plugins/auth-node/package.json | 2 +- .../src/sign-in/createSignInResolverFactory.ts | 2 +- yarn.lock | 14 +++++++------- 7 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 .changeset/sour-mails-smoke.md diff --git a/.changeset/sour-mails-smoke.md b/.changeset/sour-mails-smoke.md new file mode 100644 index 0000000000..f4bb53e025 --- /dev/null +++ b/.changeset/sour-mails-smoke.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-auth-node': patch +'@backstage/cli': patch +--- + +Upgrade `zod-validation-error` to version 4 diff --git a/packages/cli/package.json b/packages/cli/package.json index fbefa70eff..f76258423a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -143,7 +143,7 @@ "yml-loader": "^2.1.0", "yn": "^4.0.0", "zod": "^3.22.4", - "zod-validation-error": "^3.4.0" + "zod-validation-error": "^4.0.2" }, "devDependencies": { "@backstage/backend-plugin-api": "workspace:^", diff --git a/packages/cli/src/modules/new/lib/preparation/loadPortableTemplate.ts b/packages/cli/src/modules/new/lib/preparation/loadPortableTemplate.ts index f59546a308..c303bffbea 100644 --- a/packages/cli/src/modules/new/lib/preparation/loadPortableTemplate.ts +++ b/packages/cli/src/modules/new/lib/preparation/loadPortableTemplate.ts @@ -28,7 +28,7 @@ import { } from '../types'; import { PortableTemplate } from '../types'; import { ForwardedError } from '@backstage/errors'; -import { fromZodError } from 'zod-validation-error'; +import { fromZodError } from 'zod-validation-error/v3'; const templateDefinitionSchema = z .object({ diff --git a/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.ts b/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.ts index 2e736d9c29..dd937482a2 100644 --- a/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.ts +++ b/packages/cli/src/modules/new/lib/preparation/loadPortableTemplateConfig.ts @@ -25,7 +25,7 @@ import { } from '../types'; import { parse as parseYaml } from 'yaml'; import { z } from 'zod'; -import { fromZodError } from 'zod-validation-error'; +import { fromZodError } from 'zod-validation-error/v3'; import { ForwardedError } from '@backstage/errors'; const defaults = { diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index c07aefe30e..80b4405be7 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -52,7 +52,7 @@ "passport": "^0.7.0", "zod": "^3.22.4", "zod-to-json-schema": "^3.25.1", - "zod-validation-error": "^3.4.0" + "zod-validation-error": "^4.0.2" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/auth-node/src/sign-in/createSignInResolverFactory.ts b/plugins/auth-node/src/sign-in/createSignInResolverFactory.ts index e857a0fe3f..2bcdbefbb2 100644 --- a/plugins/auth-node/src/sign-in/createSignInResolverFactory.ts +++ b/plugins/auth-node/src/sign-in/createSignInResolverFactory.ts @@ -18,7 +18,7 @@ import { ZodSchema, ZodTypeDef } from 'zod'; import { SignInResolver } from '../types'; import zodToJsonSchema from 'zod-to-json-schema'; import { JsonObject } from '@backstage/types'; -import { fromError } from 'zod-validation-error'; +import { fromError } from 'zod-validation-error/v3'; import { InputError } from '@backstage/errors'; /** @public */ diff --git a/yarn.lock b/yarn.lock index 103cc9930f..92dfaa0796 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3415,7 +3415,7 @@ __metadata: yml-loader: "npm:^2.1.0" yn: "npm:^4.0.0" zod: "npm:^3.22.4" - zod-validation-error: "npm:^3.4.0" + zod-validation-error: "npm:^4.0.2" peerDependencies: "@jest/environment-jsdom-abstract": ^30.0.0 "@module-federation/enhanced": ^0.9.0 @@ -4697,7 +4697,7 @@ __metadata: uuid: "npm:^11.0.0" zod: "npm:^3.22.4" zod-to-json-schema: "npm:^3.25.1" - zod-validation-error: "npm:^3.4.0" + zod-validation-error: "npm:^4.0.2" languageName: unknown linkType: soft @@ -51127,12 +51127,12 @@ __metadata: languageName: node linkType: hard -"zod-validation-error@npm:^3.4.0": - version: 3.5.4 - resolution: "zod-validation-error@npm:3.5.4" +"zod-validation-error@npm:^4.0.2": + version: 4.0.2 + resolution: "zod-validation-error@npm:4.0.2" peerDependencies: - zod: ^3.24.4 - checksum: 10/eb85392e6fd7af255fb233713b1f038134e66cbaff20d1a52d46bd4210fe7b776d48d7dd2170095fbd2b375f6c41d629109bd5eac245c576083c9cf6e131a20b + zod: ^3.25.0 || ^4.0.0 + checksum: 10/5e35ca8ebb4602dcb526e122d7e9fca695c4a479bd97535f3400a732d49160f24f7213a9ed64986fc9dc3a2e8a6c4e1241ec0c4d8a4e3e69ea91a0328ded2192 languageName: node linkType: hard From 2d7df58cbb3030d434553fd25198ba6e1749f946 Mon Sep 17 00:00:00 2001 From: Jonathon Mills-Kelly <6938575+InsidersByte@users.noreply.github.com> Date: Wed, 14 Jan 2026 15:44:30 +0000 Subject: [PATCH 132/202] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Jonathon Mills-Kelly <6938575+InsidersByte@users.noreply.github.com> --- plugins/scaffolder-backend-module-sentry/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-sentry/README.md b/plugins/scaffolder-backend-module-sentry/README.md index 6fcc598880..29f020da02 100644 --- a/plugins/scaffolder-backend-module-sentry/README.md +++ b/plugins/scaffolder-backend-module-sentry/README.md @@ -36,7 +36,7 @@ scaffolder: token: ${SENTRY_TOKEN} ``` -You can optional override the default Sentry API Base URL (https://sentry.io/api/0) in your `app-config.yaml`: +You can optionally override the default Sentry API Base URL (`https://sentry.io/api/0`) in your `app-config.yaml`: ```yaml scaffolder: From 8850a35f9ea69ad065fc11f567bcdfd0e7ccbb93 Mon Sep 17 00:00:00 2001 From: Sanchit2662 Date: Wed, 14 Jan 2026 23:31:09 +0530 Subject: [PATCH 133/202] fix(backend): clean up process event listeners on backend stop Signed-off-by: Sanchit2662 --- .../src/wiring/BackendInitializer.ts | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index c86fb0b29c..2d32306385 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -157,6 +157,8 @@ export class BackendInitializer { #serviceRegistry: ServiceRegistry; #registeredFeatures = new Array>(); #registeredFeatureLoaders = new Array(); + #unhandledRejectionHandler?: (reason: Error) => void; + #uncaughtExceptionHandler?: (error: Error) => void; constructor(defaultApiFactories: ServiceFactory[]) { this.#serviceRegistry = ServiceRegistry.create([...defaultApiFactories]); @@ -265,16 +267,18 @@ export class BackendInitializer { coreServices.rootLogger, 'root', ); - process.on('unhandledRejection', (reason: Error) => { + this.#unhandledRejectionHandler = (reason: Error) => { rootLogger ?.child({ type: 'unhandledRejection' }) ?.error('Unhandled rejection', reason); - }); - process.on('uncaughtException', error => { + }; + this.#uncaughtExceptionHandler = (error: Error) => { rootLogger ?.child({ type: 'uncaughtException' }) ?.error('Uncaught exception', error); - }); + }; + process.on('unhandledRejection', this.#unhandledRejectionHandler); + process.on('uncaughtException', this.#uncaughtExceptionHandler); } // Initialize all root scoped services @@ -490,6 +494,16 @@ export class BackendInitializer { // Once all plugin shutdown hooks are done, run root shutdown hooks. await rootLifecycleService.shutdown(); + + // Clean up process event listeners to prevent memory leaks and duplicate logging + if (this.#unhandledRejectionHandler) { + process.off('unhandledRejection', this.#unhandledRejectionHandler); + this.#unhandledRejectionHandler = undefined; + } + if (this.#uncaughtExceptionHandler) { + process.off('uncaughtException', this.#uncaughtExceptionHandler); + this.#uncaughtExceptionHandler = undefined; + } } // Bit of a hacky way to grab the lifecycle services, potentially find a nicer way to do this From 2260de4632a646b46fd2fdfc6442c1c109d86081 Mon Sep 17 00:00:00 2001 From: InsidersByte <6938575+InsidersByte@users.noreply.github.com> Date: Wed, 14 Jan 2026 19:29:56 +0000 Subject: [PATCH 134/202] add config.d.ts Signed-off-by: InsidersByte <6938575+InsidersByte@users.noreply.github.com> --- .../config.d.ts | 24 +++++++++++++++++++ .../package.json | 6 +++-- 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 plugins/scaffolder-backend-module-sentry/config.d.ts diff --git a/plugins/scaffolder-backend-module-sentry/config.d.ts b/plugins/scaffolder-backend-module-sentry/config.d.ts new file mode 100644 index 0000000000..2bd36cf6fb --- /dev/null +++ b/plugins/scaffolder-backend-module-sentry/config.d.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + scaffolder?: { + sentry?: { + apiBaseUrl?: string; + token?: string; + }; + }; +} diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index 007b31ac46..982cd3ee67 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -30,7 +30,8 @@ } }, "files": [ - "dist" + "dist", + "config.d.ts" ], "scripts": { "build": "backstage-cli package build", @@ -54,5 +55,6 @@ "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", "@backstage/types": "workspace:^", "msw": "^2.0.0" - } + }, + "configSchema": "config.d.ts" } From 7f96e7712be0ce6150823297e0b89567b7b61c29 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 14 Jan 2026 20:51:18 +0100 Subject: [PATCH 135/202] feat(scaffolder-backend): simplify abort handling in debug:wait action Signed-off-by: mtt-artis --- .../src/scaffolder/actions/builtin/debug/wait.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts index e048720f29..321a5f46a8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts @@ -85,14 +85,11 @@ export function createWaitAction(options?: { } await new Promise(resolve => { - const controller = new AbortController(); const timeoutHandle = setTimeout(abort, delayTime.toMillis()); - ctx.signal?.addEventListener('abort', abort); + ctx.signal?.addEventListener('abort', abort, { once: true }); function abort() { - ctx.signal?.removeEventListener('abort', abort); - clearTimeout(timeoutHandle!); - controller.abort(); + clearTimeout(timeoutHandle); resolve('finished'); } }); From 50128528375fe8b6d5a47e5ca4d32e29d0074885 Mon Sep 17 00:00:00 2001 From: mtt-artis Date: Wed, 14 Jan 2026 21:08:05 +0100 Subject: [PATCH 136/202] feat(scaffolder-backend): add changeset Signed-off-by: mtt-artis --- .changeset/cuddly-colts-dress.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cuddly-colts-dress.md diff --git a/.changeset/cuddly-colts-dress.md b/.changeset/cuddly-colts-dress.md new file mode 100644 index 0000000000..f7e24d1c54 --- /dev/null +++ b/.changeset/cuddly-colts-dress.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Simplify abort handling in debug:wait action From ff3b228a6e64363810322bf0dd69b9317cfb3d92 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 14 Jan 2026 22:48:14 +0100 Subject: [PATCH 137/202] Add docs for Popover Signed-off-by: Charles de Dreuille --- docs-ui/src/content/PasswordField.tsx | 8 + docs-ui/src/content/password-field.mdx | 46 +----- docs-ui/src/content/popover.mdx | 102 +++++++++++++ docs-ui/src/content/popover.props.ts | 174 ++++++++++++++++++++++ docs-ui/src/snippets/stories-snippets.tsx | 11 +- docs-ui/src/utils/data.ts | 5 + 6 files changed, 296 insertions(+), 50 deletions(-) create mode 100644 docs-ui/src/content/PasswordField.tsx create mode 100644 docs-ui/src/content/popover.mdx create mode 100644 docs-ui/src/content/popover.props.ts diff --git a/docs-ui/src/content/PasswordField.tsx b/docs-ui/src/content/PasswordField.tsx new file mode 100644 index 0000000000..953ec3515e --- /dev/null +++ b/docs-ui/src/content/PasswordField.tsx @@ -0,0 +1,8 @@ +'use client'; + +import * as stories from '../../../packages/ui/src/components/PasswordField/PasswordField.stories'; + +export const Default = () => { + const { CustomField } = stories; + return ; +}; diff --git a/docs-ui/src/content/password-field.mdx b/docs-ui/src/content/password-field.mdx index 8dda2b4f2a..2b6d3c1ca6 100644 --- a/docs-ui/src/content/password-field.mdx +++ b/docs-ui/src/content/password-field.mdx @@ -13,53 +13,11 @@ import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; import { CodeBlock } from '@/components/CodeBlock'; import { PasswordFieldDefinition } from '../utils/definitions'; +import { Default } from './PasswordField'; -} - code={passwordFieldDefaultSnippet} -/> - -## Usage - - - -## API reference - - - -## Examples - -### Sizes - -We support two different sizes: `small`, `medium`. - -} - code={passwordFieldSizesSnippet} -/> - -### With description - -Here's a simple PasswordField with a description. - -} - code={passwordFieldDescriptionSnippet} -/> - - - - + diff --git a/docs-ui/src/content/popover.mdx b/docs-ui/src/content/popover.mdx new file mode 100644 index 0000000000..ca3f728b01 --- /dev/null +++ b/docs-ui/src/content/popover.mdx @@ -0,0 +1,102 @@ +import { PropsTable } from '@/components/PropsTable'; +import { PopoverSnippet } from '@/snippets/stories-snippets'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { + popoverPropDefs, + popoverUsageSnippet, + popoverDefaultSnippet, + popoverPlacementsSnippet, + popoverNoArrowSnippet, + popoverRichContentSnippet, + popoverNonModalSnippet, +} from './popover.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { PopoverDefinition } from '../utils/definitions'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; + + + +} + code={popoverDefaultSnippet} +/> + +## Usage + + + +## API reference + +### Popover + +The main popover component that displays floating content with automatic positioning. + + + +## Examples + +### Placements + +Popover supports multiple placement options to position the content relative to the trigger. + +} + code={popoverPlacementsSnippet} +/> + +### Without Arrow + +You can hide the arrow by setting the `hideArrow` prop. + +} + code={popoverNoArrowSnippet} +/> + +### Rich Content + +Popovers can contain complex content including buttons, forms, and other interactive elements. + +} + code={popoverRichContentSnippet} +/> + +### Non-Modal + +Non-modal popovers allow interaction with other elements on the page while the popover is open. + +} + code={popoverNonModalSnippet} +/> + +### Long Content + +Popovers automatically handle scrolling when content exceeds the available viewport space. + +} + code={popoverDefaultSnippet} +/> + + + + diff --git a/docs-ui/src/content/popover.props.ts b/docs-ui/src/content/popover.props.ts new file mode 100644 index 0000000000..74194c6120 --- /dev/null +++ b/docs-ui/src/content/popover.props.ts @@ -0,0 +1,174 @@ +import { + childrenPropDefs, + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const popoverPropDefs: Record = { + hideArrow: { + type: 'boolean', + default: 'false', + description: + 'Whether to hide the arrow pointing to the trigger element. Arrow is also automatically hidden for MenuTrigger and SubmenuTrigger contexts.', + }, + placement: { + type: 'enum', + values: [ + 'top', + 'top start', + 'top end', + 'bottom', + 'bottom start', + 'bottom end', + 'left', + 'left start', + 'left end', + 'right', + 'right start', + 'right end', + ], + default: 'bottom', + description: 'The placement of the popover relative to the trigger.', + }, + containerPadding: { + type: 'number', + default: '12', + description: + 'The padding between the popover and the edge of the viewport.', + }, + offset: { + type: 'number', + default: '0', + description: 'The offset from the trigger element.', + }, + crossOffset: { + type: 'number', + default: '0', + description: 'The cross-axis offset from the trigger element.', + }, + shouldFlip: { + type: 'boolean', + default: 'true', + description: + 'Whether the popover should flip to the opposite side when there is not enough space.', + }, + isOpen: { + type: 'boolean', + description: 'Whether the popover is open (controlled).', + }, + defaultOpen: { + type: 'boolean', + description: 'Whether the popover is open by default (uncontrolled).', + }, + onOpenChange: { + type: 'enum', + values: ['(isOpen: boolean) => void'], + description: + "Handler that is called when the popover's open state changes.", + }, + isNonModal: { + type: 'boolean', + default: 'false', + description: + 'Whether the popover is non-modal. Non-modal popovers do not block interaction with the rest of the page.', + }, + isKeyboardDismissDisabled: { + type: 'boolean', + default: 'false', + description: + 'Whether pressing the escape key to close the popover should be disabled.', + }, + shouldCloseOnBlur: { + type: 'boolean', + default: 'true', + description: + 'Whether to close the popover when the user interacts outside it.', + }, + triggerRef: { + type: 'enum', + values: ['RefObject'], + description: 'A ref for the trigger element.', + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const popoverUsageSnippet = `import { Popover, DialogTrigger, Button } from '@backstage/ui'; + + + + + This is a popover + +`; + +export const popoverDefaultSnippet = ` + + + This is a popover + +`; + +export const popoverPlacementsSnippet = ` + + + Top placement + + + + + + + Right placement + + + + + + + Bottom placement + + + + + + + Left placement + +`; + +export const popoverNoArrowSnippet = ` + + + Popover without arrow + +`; + +export const popoverRichContentSnippet = ` + + + + Popover Title + + This is a popover with rich content. It can contain multiple + elements and formatted text. + + + + + + + +`; + +export const popoverNonModalSnippet = ` + + + + This is a non-modal popover. You can interact with other + elements on the page while it's open. + + +`; diff --git a/docs-ui/src/snippets/stories-snippets.tsx b/docs-ui/src/snippets/stories-snippets.tsx index ba722a5edb..9297844baa 100644 --- a/docs-ui/src/snippets/stories-snippets.tsx +++ b/docs-ui/src/snippets/stories-snippets.tsx @@ -1,6 +1,5 @@ 'use client'; -import { composeStories } from '@storybook/react'; import * as BoxStories from '../../../packages/ui/src/components/Box/Box.stories'; import * as ButtonStories from '../../../packages/ui/src/components/Button/Button.stories'; import * as ButtonIconStories from '../../../packages/ui/src/components/ButtonIcon/ButtonIcon.stories'; @@ -29,6 +28,7 @@ import * as HeaderPageStories from '../../../packages/ui/src/components/HeaderPa import * as TableStories from '../../../packages/ui/src/components/Table/stories/Table.docs.stories'; import * as TagGroupStories from '../../../packages/ui/src/components/TagGroup/TagGroup.stories'; import * as PasswordFieldStories from '../../../packages/ui/src/components/PasswordField/PasswordField.stories'; +import * as PopoverStories from '../../../packages/ui/src/components/Popover/Popover.stories'; import * as VisuallyHiddenStories from '../../../packages/ui/src/components/VisuallyHidden/VisuallyHidden.stories'; import * as ToggleButtonStories from '../../../packages/ui/src/components/ToggleButton/ToggleButton.stories'; import * as ToggleButtonGroupStories from '../../../packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.stories'; @@ -37,11 +37,9 @@ import * as ToggleButtonGroupStories from '../../../packages/ui/src/components/T // eslint-disable-next-line @typescript-eslint/no-explicit-any const createSnippetComponent = (stories: any) => { return function SnippetComponent({ story }: { story: string }) { - const composedStories = composeStories(stories); - const StoryComponent = composedStories[ - story as keyof typeof composedStories - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ] as any; + // In CSF3, stories are already React components that can be rendered directly + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const StoryComponent = stories[story as keyof typeof stories] as any; return StoryComponent ? : null; }; @@ -58,6 +56,7 @@ export const GridSnippet = createSnippetComponent(GridStories); export const TextFieldSnippet = createSnippetComponent(TextFieldStories); export const PasswordFieldSnippet = createSnippetComponent(PasswordFieldStories); +export const PopoverSnippet = createSnippetComponent(PopoverStories); export const TextSnippet = createSnippetComponent(TextStories); export const FlexSnippet = createSnippetComponent(FlexStories); export const SelectSnippet = createSnippetComponent(SelectStories); diff --git a/docs-ui/src/utils/data.ts b/docs-ui/src/utils/data.ts index b20725142c..1b046fd3e5 100644 --- a/docs-ui/src/utils/data.ts +++ b/docs-ui/src/utils/data.ts @@ -76,6 +76,11 @@ export const components: Page[] = [ title: 'PasswordField', slug: 'password-field', }, + { + title: 'Popover', + slug: 'popover', + status: 'new', + }, { title: 'RadioGroup', slug: 'radio-group', From 8a2c0771c0909a8ffc419cc7af3df7ec4cc59142 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 14 Jan 2026 22:52:31 +0100 Subject: [PATCH 138/202] Rename shadow token Signed-off-by: Charles de Dreuille --- .changeset/eager-spies-wish.md | 2 +- docs-ui/public/theme-backstage.css | 2 +- packages/ui/css/styles.css | 4 ++-- packages/ui/src/components/Menu/Menu.module.css | 2 +- packages/ui/src/components/Popover/Popover.module.css | 2 +- packages/ui/src/components/Tooltip/Tooltip.module.css | 2 +- packages/ui/src/css/tokens.css | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.changeset/eager-spies-wish.md b/.changeset/eager-spies-wish.md index c4aa3be26f..a56c64a754 100644 --- a/.changeset/eager-spies-wish.md +++ b/.changeset/eager-spies-wish.md @@ -2,4 +2,4 @@ '@backstage/ui': patch --- -Added new Popover component for Backstage UI with automatic overflow handling, and full placement support. Also introduced `--bui-shadow-overlay` token for consistent elevation styling across overlay components (Popover, Tooltip, Menu). +Added new Popover component for Backstage UI with automatic overflow handling, and full placement support. Also introduced `--bui-shadow` token for consistent elevation styling across overlay components (Popover, Tooltip, Menu). diff --git a/docs-ui/public/theme-backstage.css b/docs-ui/public/theme-backstage.css index 34af02e97b..9dd84816cf 100644 --- a/docs-ui/public/theme-backstage.css +++ b/docs-ui/public/theme-backstage.css @@ -1 +1 @@ -@layer tokens{:root{--bui-font-regular:system-ui;--bui-font-monospace:ui-monospace,"Menlo","Monaco","Consolas","Liberation Mono","Courier New",monospace;--bui-font-weight-regular:400;--bui-font-weight-bold:600;--bui-font-size-1:.625rem;--bui-font-size-2:.75rem;--bui-font-size-3:.875rem;--bui-font-size-4:1rem;--bui-font-size-5:1.25rem;--bui-font-size-6:1.5rem;--bui-font-size-7:2rem;--bui-font-size-8:3rem;--bui-font-size-9:4rem;--bui-font-size-10:5.75rem;--bui-space:.25rem;--bui-space-0_5:calc(var(--bui-space)*.5);--bui-space-1:var(--bui-space);--bui-space-1_5:calc(var(--bui-space)*1.5);--bui-space-2:calc(var(--bui-space)*2);--bui-space-3:calc(var(--bui-space)*3);--bui-space-4:calc(var(--bui-space)*4);--bui-space-5:calc(var(--bui-space)*5);--bui-space-6:calc(var(--bui-space)*6);--bui-space-7:calc(var(--bui-space)*7);--bui-space-8:calc(var(--bui-space)*8);--bui-space-9:calc(var(--bui-space)*9);--bui-space-10:calc(var(--bui-space)*10);--bui-space-11:calc(var(--bui-space)*11);--bui-space-12:calc(var(--bui-space)*12);--bui-space-13:calc(var(--bui-space)*13);--bui-space-14:calc(var(--bui-space)*14);--bui-radius-1:calc(.125rem);--bui-radius-2:calc(.25rem);--bui-radius-3:calc(.5rem);--bui-radius-4:calc(.75rem);--bui-radius-5:calc(1rem);--bui-radius-6:calc(1.25rem);--bui-radius-full:9999px;--bui-black:#000;--bui-white:#fff;--bui-gray-1:#f8f8f8;--bui-gray-2:#ececec;--bui-gray-3:#d9d9d9;--bui-gray-4:#c1c1c1;--bui-gray-5:#9e9e9e;--bui-gray-6:#8c8c8c;--bui-gray-7:#757575;--bui-gray-8:#595959;--bui-bg-surface-0:var(--bui-gray-1);--bui-bg-surface-1:var(--bui-white);--bui-bg-surface-2:var(--bui-gray-1);--bui-bg-surface-3:var(--bui-gray-2);--bui-bg-solid:#1f5493;--bui-bg-solid-hover:#163a66;--bui-bg-solid-pressed:#0f2b4e;--bui-bg-solid-disabled:#163a66;--bui-bg-neutral-on-surface-0:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-0-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-0-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-0-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-1:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-1-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-1-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-1-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-2:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-2-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-2-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-2-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-3:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-3-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-3-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-3-disabled:oklch(0% 0 0/.06);--bui-bg-danger:#feebe7;--bui-bg-warning:#fff2b2;--bui-bg-success:#e6f6eb;--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#1f5493;--bui-fg-link-hover:#1f2d5c;--bui-fg-disabled:#9e9e9e;--bui-fg-solid:var(--bui-white);--bui-fg-solid-disabled:#98a8bc;--bui-fg-tint:#1f5493;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#0000001a;--bui-border-hover:#0003;--bui-border-pressed:#0006;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#a0a0a03b;--bui-scrollbar-thumb:#a0a0a0;--bui-shadow-overlay:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--bui-animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite}[data-theme-mode=dark]{--bui-gray-1:#191919;--bui-gray-2:#242424;--bui-gray-3:#373737;--bui-gray-4:#464646;--bui-gray-5:#575757;--bui-gray-6:#7b7b7b;--bui-gray-7:#9e9e9e;--bui-gray-8:#b4b4b4;--bui-bg-surface-0:#333;--bui-bg-surface-1:var(--bui-gray-1);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-surface-3:var(--bui-gray-3);--bui-bg-solid:#9cc9ff;--bui-bg-solid-hover:#83b9fd;--bui-bg-solid-pressed:#83b9fd;--bui-bg-solid-disabled:#1b3d68;--bui-bg-neutral-on-surface-0:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-0-hover:oklch(100% 0 0/.14);--bui-bg-neutral-on-surface-0-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-0-disabled:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-1:oklch(100% 0 0/.06);--bui-bg-neutral-on-surface-1-hover:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-1-pressed:oklch(100% 0 0/.16);--bui-bg-neutral-on-surface-1-disabled:oklch(100% 0 0/.06);--bui-bg-neutral-on-surface-2:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-2-hover:oklch(100% 0 0/.12);--bui-bg-neutral-on-surface-2-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-2-disabled:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-3:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-3-hover:oklch(100% 0 0/.12);--bui-bg-neutral-on-surface-3-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-3-disabled:oklch(100% 0 0/.08);--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#9cc9ff;--bui-fg-link-hover:#7eb5f7;--bui-fg-disabled:var(--bui-gray-7);--bui-fg-solid:#101821;--bui-fg-solid-disabled:#6191cc;--bui-fg-tint:#9cc9ff;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#ffffff1f;--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#3636363a;--bui-scrollbar-thumb:#575757;--bui-shadow-overlay:none}}@layer base{*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:100%;tab-size:4;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;line-height:1.15}body{margin:0}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{border-color:currentColor}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}:where(a){color:inherit;text-decoration:none}@keyframes pulse{50%{opacity:.5}}body{background-color:var(--bui-bg-surface-0);color:var(--bui-fg-primary);font-family:var(--bui-font-regular);font-weight:var(--bui-font-weight-regular);font-size:var(--bui-font-size-3);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}[data-theme-mode=dark]{color-scheme:dark}[data-theme-mode=light]{color-scheme:light}}@layer components;@layer utilities{.bui-p{padding:var(--p)}.bui-p-0\.5{padding:var(--bui-space-0_5)}.bui-p-1{padding:var(--bui-space-1)}.bui-p-1\.5{padding:var(--bui-space-1_5)}.bui-p-2{padding:var(--bui-space-2)}.bui-p-3{padding:var(--bui-space-3)}.bui-p-4{padding:var(--bui-space-4)}.bui-p-5{padding:var(--bui-space-5)}.bui-p-6{padding:var(--bui-space-6)}.bui-p-7{padding:var(--bui-space-7)}.bui-p-8{padding:var(--bui-space-8)}.bui-p-9{padding:var(--bui-space-9)}.bui-p-10{padding:var(--bui-space-10)}.bui-p-11{padding:var(--bui-space-11)}.bui-p-12{padding:var(--bui-space-12)}.bui-p-13{padding:var(--bui-space-13)}.bui-p-14{padding:var(--bui-space-14)}@media (width>=640px){.xs\:bui-p{padding:var(--p-xs)}.xs\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xs\:bui-p-1{padding:var(--bui-space-1)}.xs\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xs\:bui-p-2{padding:var(--bui-space-2)}.xs\:bui-p-3{padding:var(--bui-space-3)}.xs\:bui-p-4{padding:var(--bui-space-4)}.xs\:bui-p-5{padding:var(--bui-space-5)}.xs\:bui-p-6{padding:var(--bui-space-6)}.xs\:bui-p-7{padding:var(--bui-space-7)}.xs\:bui-p-8{padding:var(--bui-space-8)}.xs\:bui-p-9{padding:var(--bui-space-9)}.xs\:bui-p-10{padding:var(--bui-space-10)}.xs\:bui-p-11{padding:var(--bui-space-11)}.xs\:bui-p-12{padding:var(--bui-space-12)}.xs\:bui-p-13{padding:var(--bui-space-13)}.xs\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-p{padding:var(--p-sm)}.sm\:bui-p-0\.5{padding:var(--bui-space-0_5)}.sm\:bui-p-1{padding:var(--bui-space-1)}.sm\:bui-p-1\.5{padding:var(--bui-space-1_5)}.sm\:bui-p-2{padding:var(--bui-space-2)}.sm\:bui-p-3{padding:var(--bui-space-3)}.sm\:bui-p-4{padding:var(--bui-space-4)}.sm\:bui-p-5{padding:var(--bui-space-5)}.sm\:bui-p-6{padding:var(--bui-space-6)}.sm\:bui-p-7{padding:var(--bui-space-7)}.sm\:bui-p-8{padding:var(--bui-space-8)}.sm\:bui-p-9{padding:var(--bui-space-9)}.sm\:bui-p-10{padding:var(--bui-space-10)}.sm\:bui-p-11{padding:var(--bui-space-11)}.sm\:bui-p-12{padding:var(--bui-space-12)}.sm\:bui-p-13{padding:var(--bui-space-13)}.sm\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-p{padding:var(--p-md)}.md\:bui-p-0\.5{padding:var(--bui-space-0_5)}.md\:bui-p-1{padding:var(--bui-space-1)}.md\:bui-p-1\.5{padding:var(--bui-space-1_5)}.md\:bui-p-2{padding:var(--bui-space-2)}.md\:bui-p-3{padding:var(--bui-space-3)}.md\:bui-p-4{padding:var(--bui-space-4)}.md\:bui-p-5{padding:var(--bui-space-5)}.md\:bui-p-6{padding:var(--bui-space-6)}.md\:bui-p-7{padding:var(--bui-space-7)}.md\:bui-p-8{padding:var(--bui-space-8)}.md\:bui-p-9{padding:var(--bui-space-9)}.md\:bui-p-10{padding:var(--bui-space-10)}.md\:bui-p-11{padding:var(--bui-space-11)}.md\:bui-p-12{padding:var(--bui-space-12)}.md\:bui-p-13{padding:var(--bui-space-13)}.md\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-p{padding:var(--p-lg)}.lg\:bui-p-0\.5{padding:var(--bui-space-0_5)}.lg\:bui-p-1{padding:var(--bui-space-1)}.lg\:bui-p-1\.5{padding:var(--bui-space-1_5)}.lg\:bui-p-2{padding:var(--bui-space-2)}.lg\:bui-p-3{padding:var(--bui-space-3)}.lg\:bui-p-4{padding:var(--bui-space-4)}.lg\:bui-p-5{padding:var(--bui-space-5)}.lg\:bui-p-6{padding:var(--bui-space-6)}.lg\:bui-p-7{padding:var(--bui-space-7)}.lg\:bui-p-8{padding:var(--bui-space-8)}.lg\:bui-p-9{padding:var(--bui-space-9)}.lg\:bui-p-10{padding:var(--bui-space-10)}.lg\:bui-p-11{padding:var(--bui-space-11)}.lg\:bui-p-12{padding:var(--bui-space-12)}.lg\:bui-p-13{padding:var(--bui-space-13)}.lg\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-p{padding:var(--p-xl)}.xl\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xl\:bui-p-1{padding:var(--bui-space-1)}.xl\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xl\:bui-p-2{padding:var(--bui-space-2)}.xl\:bui-p-3{padding:var(--bui-space-3)}.xl\:bui-p-4{padding:var(--bui-space-4)}.xl\:bui-p-5{padding:var(--bui-space-5)}.xl\:bui-p-6{padding:var(--bui-space-6)}.xl\:bui-p-7{padding:var(--bui-space-7)}.xl\:bui-p-8{padding:var(--bui-space-8)}.xl\:bui-p-9{padding:var(--bui-space-9)}.xl\:bui-p-10{padding:var(--bui-space-10)}.xl\:bui-p-11{padding:var(--bui-space-11)}.xl\:bui-p-12{padding:var(--bui-space-12)}.xl\:bui-p-13{padding:var(--bui-space-13)}.xl\:bui-p-14{padding:var(--bui-space-14)}}.bui-pl{padding-left:var(--pl)}.bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.bui-pl-1{padding-left:var(--bui-space-1)}.bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.bui-pl-2{padding-left:var(--bui-space-2)}.bui-pl-3{padding-left:var(--bui-space-3)}.bui-pl-4{padding-left:var(--bui-space-4)}.bui-pl-5{padding-left:var(--bui-space-5)}.bui-pl-6{padding-left:var(--bui-space-6)}.bui-pl-7{padding-left:var(--bui-space-7)}.bui-pl-8{padding-left:var(--bui-space-8)}.bui-pl-9{padding-left:var(--bui-space-9)}.bui-pl-10{padding-left:var(--bui-space-10)}.bui-pl-11{padding-left:var(--bui-space-11)}.bui-pl-12{padding-left:var(--bui-space-12)}.bui-pl-13{padding-left:var(--bui-space-13)}.bui-pl-14{padding-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pl{padding-left:var(--pl-xs)}.xs\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xs\:bui-pl-1{padding-left:var(--bui-space-1)}.xs\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xs\:bui-pl-2{padding-left:var(--bui-space-2)}.xs\:bui-pl-3{padding-left:var(--bui-space-3)}.xs\:bui-pl-4{padding-left:var(--bui-space-4)}.xs\:bui-pl-5{padding-left:var(--bui-space-5)}.xs\:bui-pl-6{padding-left:var(--bui-space-6)}.xs\:bui-pl-7{padding-left:var(--bui-space-7)}.xs\:bui-pl-8{padding-left:var(--bui-space-8)}.xs\:bui-pl-9{padding-left:var(--bui-space-9)}.xs\:bui-pl-10{padding-left:var(--bui-space-10)}.xs\:bui-pl-11{padding-left:var(--bui-space-11)}.xs\:bui-pl-12{padding-left:var(--bui-space-12)}.xs\:bui-pl-13{padding-left:var(--bui-space-13)}.xs\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pl{padding-left:var(--pl-sm)}.sm\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.sm\:bui-pl-1{padding-left:var(--bui-space-1)}.sm\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.sm\:bui-pl-2{padding-left:var(--bui-space-2)}.sm\:bui-pl-3{padding-left:var(--bui-space-3)}.sm\:bui-pl-4{padding-left:var(--bui-space-4)}.sm\:bui-pl-5{padding-left:var(--bui-space-5)}.sm\:bui-pl-6{padding-left:var(--bui-space-6)}.sm\:bui-pl-7{padding-left:var(--bui-space-7)}.sm\:bui-pl-8{padding-left:var(--bui-space-8)}.sm\:bui-pl-9{padding-left:var(--bui-space-9)}.sm\:bui-pl-10{padding-left:var(--bui-space-10)}.sm\:bui-pl-11{padding-left:var(--bui-space-11)}.sm\:bui-pl-12{padding-left:var(--bui-space-12)}.sm\:bui-pl-13{padding-left:var(--bui-space-13)}.sm\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pl{padding-left:var(--pl-md)}.md\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.md\:bui-pl-1{padding-left:var(--bui-space-1)}.md\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.md\:bui-pl-2{padding-left:var(--bui-space-2)}.md\:bui-pl-3{padding-left:var(--bui-space-3)}.md\:bui-pl-4{padding-left:var(--bui-space-4)}.md\:bui-pl-5{padding-left:var(--bui-space-5)}.md\:bui-pl-6{padding-left:var(--bui-space-6)}.md\:bui-pl-7{padding-left:var(--bui-space-7)}.md\:bui-pl-8{padding-left:var(--bui-space-8)}.md\:bui-pl-9{padding-left:var(--bui-space-9)}.md\:bui-pl-10{padding-left:var(--bui-space-10)}.md\:bui-pl-11{padding-left:var(--bui-space-11)}.md\:bui-pl-12{padding-left:var(--bui-space-12)}.md\:bui-pl-13{padding-left:var(--bui-space-13)}.md\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pl{padding-left:var(--pl-lg)}.lg\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.lg\:bui-pl-1{padding-left:var(--bui-space-1)}.lg\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.lg\:bui-pl-2{padding-left:var(--bui-space-2)}.lg\:bui-pl-3{padding-left:var(--bui-space-3)}.lg\:bui-pl-4{padding-left:var(--bui-space-4)}.lg\:bui-pl-5{padding-left:var(--bui-space-5)}.lg\:bui-pl-6{padding-left:var(--bui-space-6)}.lg\:bui-pl-7{padding-left:var(--bui-space-7)}.lg\:bui-pl-8{padding-left:var(--bui-space-8)}.lg\:bui-pl-9{padding-left:var(--bui-space-9)}.lg\:bui-pl-10{padding-left:var(--bui-space-10)}.lg\:bui-pl-11{padding-left:var(--bui-space-11)}.lg\:bui-pl-12{padding-left:var(--bui-space-12)}.lg\:bui-pl-13{padding-left:var(--bui-space-13)}.lg\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pl{padding-left:var(--pl-xl)}.xl\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xl\:bui-pl-1{padding-left:var(--bui-space-1)}.xl\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xl\:bui-pl-2{padding-left:var(--bui-space-2)}.xl\:bui-pl-3{padding-left:var(--bui-space-3)}.xl\:bui-pl-4{padding-left:var(--bui-space-4)}.xl\:bui-pl-5{padding-left:var(--bui-space-5)}.xl\:bui-pl-6{padding-left:var(--bui-space-6)}.xl\:bui-pl-7{padding-left:var(--bui-space-7)}.xl\:bui-pl-8{padding-left:var(--bui-space-8)}.xl\:bui-pl-9{padding-left:var(--bui-space-9)}.xl\:bui-pl-10{padding-left:var(--bui-space-10)}.xl\:bui-pl-11{padding-left:var(--bui-space-11)}.xl\:bui-pl-12{padding-left:var(--bui-space-12)}.xl\:bui-pl-13{padding-left:var(--bui-space-13)}.xl\:bui-pl-14{padding-left:var(--bui-space-14)}}.bui-pr{padding-right:var(--pr)}.bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.bui-pr-1{padding-right:var(--bui-space-1)}.bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.bui-pr-2{padding-right:var(--bui-space-2)}.bui-pr-3{padding-right:var(--bui-space-3)}.bui-pr-4{padding-right:var(--bui-space-4)}.bui-pr-5{padding-right:var(--bui-space-5)}.bui-pr-6{padding-right:var(--bui-space-6)}.bui-pr-7{padding-right:var(--bui-space-7)}.bui-pr-8{padding-right:var(--bui-space-8)}.bui-pr-9{padding-right:var(--bui-space-9)}.bui-pr-10{padding-right:var(--bui-space-10)}.bui-pr-11{padding-right:var(--bui-space-11)}.bui-pr-12{padding-right:var(--bui-space-12)}.bui-pr-13{padding-right:var(--bui-space-13)}.bui-pr-14{padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pr{padding-right:var(--pr-xs)}.xs\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xs\:bui-pr-1{padding-right:var(--bui-space-1)}.xs\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xs\:bui-pr-2{padding-right:var(--bui-space-2)}.xs\:bui-pr-3{padding-right:var(--bui-space-3)}.xs\:bui-pr-4{padding-right:var(--bui-space-4)}.xs\:bui-pr-5{padding-right:var(--bui-space-5)}.xs\:bui-pr-6{padding-right:var(--bui-space-6)}.xs\:bui-pr-7{padding-right:var(--bui-space-7)}.xs\:bui-pr-8{padding-right:var(--bui-space-8)}.xs\:bui-pr-9{padding-right:var(--bui-space-9)}.xs\:bui-pr-10{padding-right:var(--bui-space-10)}.xs\:bui-pr-11{padding-right:var(--bui-space-11)}.xs\:bui-pr-12{padding-right:var(--bui-space-12)}.xs\:bui-pr-13{padding-right:var(--bui-space-13)}.xs\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pr{padding-right:var(--pr-sm)}.sm\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.sm\:bui-pr-1{padding-right:var(--bui-space-1)}.sm\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.sm\:bui-pr-2{padding-right:var(--bui-space-2)}.sm\:bui-pr-3{padding-right:var(--bui-space-3)}.sm\:bui-pr-4{padding-right:var(--bui-space-4)}.sm\:bui-pr-5{padding-right:var(--bui-space-5)}.sm\:bui-pr-6{padding-right:var(--bui-space-6)}.sm\:bui-pr-7{padding-right:var(--bui-space-7)}.sm\:bui-pr-8{padding-right:var(--bui-space-8)}.sm\:bui-pr-9{padding-right:var(--bui-space-9)}.sm\:bui-pr-10{padding-right:var(--bui-space-10)}.sm\:bui-pr-11{padding-right:var(--bui-space-11)}.sm\:bui-pr-12{padding-right:var(--bui-space-12)}.sm\:bui-pr-13{padding-right:var(--bui-space-13)}.sm\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pr{padding-right:var(--pr-md)}.md\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.md\:bui-pr-1{padding-right:var(--bui-space-1)}.md\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.md\:bui-pr-2{padding-right:var(--bui-space-2)}.md\:bui-pr-3{padding-right:var(--bui-space-3)}.md\:bui-pr-4{padding-right:var(--bui-space-4)}.md\:bui-pr-5{padding-right:var(--bui-space-5)}.md\:bui-pr-6{padding-right:var(--bui-space-6)}.md\:bui-pr-7{padding-right:var(--bui-space-7)}.md\:bui-pr-8{padding-right:var(--bui-space-8)}.md\:bui-pr-9{padding-right:var(--bui-space-9)}.md\:bui-pr-10{padding-right:var(--bui-space-10)}.md\:bui-pr-11{padding-right:var(--bui-space-11)}.md\:bui-pr-12{padding-right:var(--bui-space-12)}.md\:bui-pr-13{padding-right:var(--bui-space-13)}.md\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pr{padding-right:var(--pr-lg)}.lg\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.lg\:bui-pr-1{padding-right:var(--bui-space-1)}.lg\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.lg\:bui-pr-2{padding-right:var(--bui-space-2)}.lg\:bui-pr-3{padding-right:var(--bui-space-3)}.lg\:bui-pr-4{padding-right:var(--bui-space-4)}.lg\:bui-pr-5{padding-right:var(--bui-space-5)}.lg\:bui-pr-6{padding-right:var(--bui-space-6)}.lg\:bui-pr-7{padding-right:var(--bui-space-7)}.lg\:bui-pr-8{padding-right:var(--bui-space-8)}.lg\:bui-pr-9{padding-right:var(--bui-space-9)}.lg\:bui-pr-10{padding-right:var(--bui-space-10)}.lg\:bui-pr-11{padding-right:var(--bui-space-11)}.lg\:bui-pr-12{padding-right:var(--bui-space-12)}.lg\:bui-pr-13{padding-right:var(--bui-space-13)}.lg\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pr{padding-right:var(--pr-xl)}.xl\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xl\:bui-pr-1{padding-right:var(--bui-space-1)}.xl\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xl\:bui-pr-2{padding-right:var(--bui-space-2)}.xl\:bui-pr-3{padding-right:var(--bui-space-3)}.xl\:bui-pr-4{padding-right:var(--bui-space-4)}.xl\:bui-pr-5{padding-right:var(--bui-space-5)}.xl\:bui-pr-6{padding-right:var(--bui-space-6)}.xl\:bui-pr-7{padding-right:var(--bui-space-7)}.xl\:bui-pr-8{padding-right:var(--bui-space-8)}.xl\:bui-pr-9{padding-right:var(--bui-space-9)}.xl\:bui-pr-10{padding-right:var(--bui-space-10)}.xl\:bui-pr-11{padding-right:var(--bui-space-11)}.xl\:bui-pr-12{padding-right:var(--bui-space-12)}.xl\:bui-pr-13{padding-right:var(--bui-space-13)}.xl\:bui-pr-14{padding-right:var(--bui-space-14)}}.bui-pt{padding-top:var(--pt)}.bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.bui-pt-1{padding-top:var(--bui-space-1)}.bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.bui-pt-2{padding-top:var(--bui-space-2)}.bui-pt-3{padding-top:var(--bui-space-3)}.bui-pt-4{padding-top:var(--bui-space-4)}.bui-pt-5{padding-top:var(--bui-space-5)}.bui-pt-6{padding-top:var(--bui-space-6)}.bui-pt-7{padding-top:var(--bui-space-7)}.bui-pt-8{padding-top:var(--bui-space-8)}.bui-pt-9{padding-top:var(--bui-space-9)}.bui-pt-10{padding-top:var(--bui-space-10)}.bui-pt-11{padding-top:var(--bui-space-11)}.bui-pt-12{padding-top:var(--bui-space-12)}.bui-pt-13{padding-top:var(--bui-space-13)}.bui-pt-14{padding-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pt{padding-top:var(--pt-xs)}.xs\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xs\:bui-pt-1{padding-top:var(--bui-space-1)}.xs\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xs\:bui-pt-2{padding-top:var(--bui-space-2)}.xs\:bui-pt-3{padding-top:var(--bui-space-3)}.xs\:bui-pt-4{padding-top:var(--bui-space-4)}.xs\:bui-pt-5{padding-top:var(--bui-space-5)}.xs\:bui-pt-6{padding-top:var(--bui-space-6)}.xs\:bui-pt-7{padding-top:var(--bui-space-7)}.xs\:bui-pt-8{padding-top:var(--bui-space-8)}.xs\:bui-pt-9{padding-top:var(--bui-space-9)}.xs\:bui-pt-10{padding-top:var(--bui-space-10)}.xs\:bui-pt-11{padding-top:var(--bui-space-11)}.xs\:bui-pt-12{padding-top:var(--bui-space-12)}.xs\:bui-pt-13{padding-top:var(--bui-space-13)}.xs\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pt{padding-top:var(--pt-sm)}.sm\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.sm\:bui-pt-1{padding-top:var(--bui-space-1)}.sm\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.sm\:bui-pt-2{padding-top:var(--bui-space-2)}.sm\:bui-pt-3{padding-top:var(--bui-space-3)}.sm\:bui-pt-4{padding-top:var(--bui-space-4)}.sm\:bui-pt-5{padding-top:var(--bui-space-5)}.sm\:bui-pt-6{padding-top:var(--bui-space-6)}.sm\:bui-pt-7{padding-top:var(--bui-space-7)}.sm\:bui-pt-8{padding-top:var(--bui-space-8)}.sm\:bui-pt-9{padding-top:var(--bui-space-9)}.sm\:bui-pt-10{padding-top:var(--bui-space-10)}.sm\:bui-pt-11{padding-top:var(--bui-space-11)}.sm\:bui-pt-12{padding-top:var(--bui-space-12)}.sm\:bui-pt-13{padding-top:var(--bui-space-13)}.sm\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pt{padding-top:var(--pt-md)}.md\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.md\:bui-pt-1{padding-top:var(--bui-space-1)}.md\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.md\:bui-pt-2{padding-top:var(--bui-space-2)}.md\:bui-pt-3{padding-top:var(--bui-space-3)}.md\:bui-pt-4{padding-top:var(--bui-space-4)}.md\:bui-pt-5{padding-top:var(--bui-space-5)}.md\:bui-pt-6{padding-top:var(--bui-space-6)}.md\:bui-pt-7{padding-top:var(--bui-space-7)}.md\:bui-pt-8{padding-top:var(--bui-space-8)}.md\:bui-pt-9{padding-top:var(--bui-space-9)}.md\:bui-pt-10{padding-top:var(--bui-space-10)}.md\:bui-pt-11{padding-top:var(--bui-space-11)}.md\:bui-pt-12{padding-top:var(--bui-space-12)}.md\:bui-pt-13{padding-top:var(--bui-space-13)}.md\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pt{padding-top:var(--pt-lg)}.lg\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.lg\:bui-pt-1{padding-top:var(--bui-space-1)}.lg\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.lg\:bui-pt-2{padding-top:var(--bui-space-2)}.lg\:bui-pt-3{padding-top:var(--bui-space-3)}.lg\:bui-pt-4{padding-top:var(--bui-space-4)}.lg\:bui-pt-5{padding-top:var(--bui-space-5)}.lg\:bui-pt-6{padding-top:var(--bui-space-6)}.lg\:bui-pt-7{padding-top:var(--bui-space-7)}.lg\:bui-pt-8{padding-top:var(--bui-space-8)}.lg\:bui-pt-9{padding-top:var(--bui-space-9)}.lg\:bui-pt-10{padding-top:var(--bui-space-10)}.lg\:bui-pt-11{padding-top:var(--bui-space-11)}.lg\:bui-pt-12{padding-top:var(--bui-space-12)}.lg\:bui-pt-13{padding-top:var(--bui-space-13)}.lg\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pt{padding-top:var(--pt-xl)}.xl\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xl\:bui-pt-1{padding-top:var(--bui-space-1)}.xl\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xl\:bui-pt-2{padding-top:var(--bui-space-2)}.xl\:bui-pt-3{padding-top:var(--bui-space-3)}.xl\:bui-pt-4{padding-top:var(--bui-space-4)}.xl\:bui-pt-5{padding-top:var(--bui-space-5)}.xl\:bui-pt-6{padding-top:var(--bui-space-6)}.xl\:bui-pt-7{padding-top:var(--bui-space-7)}.xl\:bui-pt-8{padding-top:var(--bui-space-8)}.xl\:bui-pt-9{padding-top:var(--bui-space-9)}.xl\:bui-pt-10{padding-top:var(--bui-space-10)}.xl\:bui-pt-11{padding-top:var(--bui-space-11)}.xl\:bui-pt-12{padding-top:var(--bui-space-12)}.xl\:bui-pt-13{padding-top:var(--bui-space-13)}.xl\:bui-pt-14{padding-top:var(--bui-space-14)}}.bui-pb{padding-bottom:var(--pb)}.bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.bui-pb-1{padding-bottom:var(--bui-space-1)}.bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.bui-pb-2{padding-bottom:var(--bui-space-2)}.bui-pb-3{padding-bottom:var(--bui-space-3)}.bui-pb-4{padding-bottom:var(--bui-space-4)}.bui-pb-5{padding-bottom:var(--bui-space-5)}.bui-pb-6{padding-bottom:var(--bui-space-6)}.bui-pb-7{padding-bottom:var(--bui-space-7)}.bui-pb-8{padding-bottom:var(--bui-space-8)}.bui-pb-9{padding-bottom:var(--bui-space-9)}.bui-pb-10{padding-bottom:var(--bui-space-10)}.bui-pb-11{padding-bottom:var(--bui-space-11)}.bui-pb-12{padding-bottom:var(--bui-space-12)}.bui-pb-13{padding-bottom:var(--bui-space-13)}.bui-pb-14{padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pb{padding-bottom:var(--pb-xs)}.xs\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xs\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xs\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xs\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xs\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xs\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xs\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xs\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xs\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xs\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xs\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xs\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xs\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xs\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xs\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xs\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pb{padding-bottom:var(--pb-sm)}.sm\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.sm\:bui-pb-1{padding-bottom:var(--bui-space-1)}.sm\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.sm\:bui-pb-2{padding-bottom:var(--bui-space-2)}.sm\:bui-pb-3{padding-bottom:var(--bui-space-3)}.sm\:bui-pb-4{padding-bottom:var(--bui-space-4)}.sm\:bui-pb-5{padding-bottom:var(--bui-space-5)}.sm\:bui-pb-6{padding-bottom:var(--bui-space-6)}.sm\:bui-pb-7{padding-bottom:var(--bui-space-7)}.sm\:bui-pb-8{padding-bottom:var(--bui-space-8)}.sm\:bui-pb-9{padding-bottom:var(--bui-space-9)}.sm\:bui-pb-10{padding-bottom:var(--bui-space-10)}.sm\:bui-pb-11{padding-bottom:var(--bui-space-11)}.sm\:bui-pb-12{padding-bottom:var(--bui-space-12)}.sm\:bui-pb-13{padding-bottom:var(--bui-space-13)}.sm\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pb{padding-bottom:var(--pb-md)}.md\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.md\:bui-pb-1{padding-bottom:var(--bui-space-1)}.md\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.md\:bui-pb-2{padding-bottom:var(--bui-space-2)}.md\:bui-pb-3{padding-bottom:var(--bui-space-3)}.md\:bui-pb-4{padding-bottom:var(--bui-space-4)}.md\:bui-pb-5{padding-bottom:var(--bui-space-5)}.md\:bui-pb-6{padding-bottom:var(--bui-space-6)}.md\:bui-pb-7{padding-bottom:var(--bui-space-7)}.md\:bui-pb-8{padding-bottom:var(--bui-space-8)}.md\:bui-pb-9{padding-bottom:var(--bui-space-9)}.md\:bui-pb-10{padding-bottom:var(--bui-space-10)}.md\:bui-pb-11{padding-bottom:var(--bui-space-11)}.md\:bui-pb-12{padding-bottom:var(--bui-space-12)}.md\:bui-pb-13{padding-bottom:var(--bui-space-13)}.md\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pb{padding-bottom:var(--pb-lg)}.lg\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.lg\:bui-pb-1{padding-bottom:var(--bui-space-1)}.lg\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.lg\:bui-pb-2{padding-bottom:var(--bui-space-2)}.lg\:bui-pb-3{padding-bottom:var(--bui-space-3)}.lg\:bui-pb-4{padding-bottom:var(--bui-space-4)}.lg\:bui-pb-5{padding-bottom:var(--bui-space-5)}.lg\:bui-pb-6{padding-bottom:var(--bui-space-6)}.lg\:bui-pb-7{padding-bottom:var(--bui-space-7)}.lg\:bui-pb-8{padding-bottom:var(--bui-space-8)}.lg\:bui-pb-9{padding-bottom:var(--bui-space-9)}.lg\:bui-pb-10{padding-bottom:var(--bui-space-10)}.lg\:bui-pb-11{padding-bottom:var(--bui-space-11)}.lg\:bui-pb-12{padding-bottom:var(--bui-space-12)}.lg\:bui-pb-13{padding-bottom:var(--bui-space-13)}.lg\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pb{padding-bottom:var(--pb-xl)}.xl\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xl\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xl\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xl\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xl\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xl\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xl\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xl\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xl\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xl\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xl\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xl\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xl\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xl\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xl\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xl\:bui-pb-14{padding-bottom:var(--bui-space-14)}}.bui-py{padding-top:var(--py);padding-bottom:var(--py)}.bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-py{padding-top:var(--py-xs);padding-bottom:var(--py-xs)}.xs\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xs\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xs\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xs\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xs\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xs\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xs\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xs\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xs\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xs\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xs\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xs\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xs\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xs\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xs\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xs\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-py{padding-top:var(--py-sm);padding-bottom:var(--py-sm)}.sm\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.sm\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.sm\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.sm\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.sm\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.sm\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.sm\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.sm\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.sm\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.sm\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.sm\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.sm\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.sm\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.sm\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.sm\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.sm\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-py{padding-top:var(--py-md);padding-bottom:var(--py-md)}.md\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.md\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.md\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.md\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.md\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.md\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.md\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.md\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.md\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.md\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.md\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.md\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.md\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.md\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.md\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.md\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-py{padding-top:var(--py-lg);padding-bottom:var(--py-lg)}.lg\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.lg\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.lg\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.lg\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.lg\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.lg\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.lg\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.lg\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.lg\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.lg\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.lg\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.lg\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.lg\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.lg\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.lg\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.lg\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-py{padding-top:var(--py-xl);padding-bottom:var(--py-xl)}.xl\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xl\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xl\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xl\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xl\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xl\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xl\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xl\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xl\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xl\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xl\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xl\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xl\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xl\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xl\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xl\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}.bui-px{padding-left:var(--px);padding-right:var(--px)}.bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-px{padding-left:var(--px-xs);padding-right:var(--px-xs)}.xs\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xs\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xs\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xs\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xs\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xs\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xs\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xs\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xs\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xs\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xs\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xs\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xs\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xs\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xs\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xs\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-px{padding-left:var(--px-sm);padding-right:var(--px-sm)}.sm\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.sm\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.sm\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.sm\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.sm\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.sm\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.sm\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.sm\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.sm\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.sm\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.sm\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.sm\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.sm\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.sm\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.sm\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.sm\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-px{padding-left:var(--px-md);padding-right:var(--px-md)}.md\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.md\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.md\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.md\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.md\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.md\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.md\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.md\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.md\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.md\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.md\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.md\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.md\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.md\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.md\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.md\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-px{padding-left:var(--px-lg);padding-right:var(--px-lg)}.lg\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.lg\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.lg\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.lg\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.lg\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.lg\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.lg\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.lg\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.lg\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.lg\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.lg\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.lg\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.lg\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.lg\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.lg\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.lg\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-px{padding-left:var(--px-xl);padding-right:var(--px-xl)}.xl\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xl\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xl\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xl\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xl\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xl\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xl\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xl\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xl\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xl\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xl\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xl\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xl\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xl\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xl\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xl\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}.bui-m{margin:var(--m)}.bui-m-0\.5{margin:var(--bui-space-0_5)}.bui-m-1{margin:var(--bui-space-1)}.bui-m-1\.5{margin:var(--bui-space-1_5)}.bui-m-2{margin:var(--bui-space-2)}.bui-m-3{margin:var(--bui-space-3)}.bui-m-4{margin:var(--bui-space-4)}.bui-m-5{margin:var(--bui-space-5)}.bui-m-6{margin:var(--bui-space-6)}.bui-m-7{margin:var(--bui-space-7)}.bui-m-8{margin:var(--bui-space-8)}.bui-m-9{margin:var(--bui-space-9)}.bui-m-10{margin:var(--bui-space-10)}.bui-m-11{margin:var(--bui-space-11)}.bui-m-12{margin:var(--bui-space-12)}.bui-m-13{margin:var(--bui-space-13)}.bui-m-14{margin:var(--bui-space-14)}@media (width>=640px){.xs\:bui-m{margin:var(--p-xs)}.xs\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xs\:bui-m-1{margin:var(--bui-space-1)}.xs\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xs\:bui-m-2{margin:var(--bui-space-2)}.xs\:bui-m-3{margin:var(--bui-space-3)}.xs\:bui-m-4{margin:var(--bui-space-4)}.xs\:bui-m-5{margin:var(--bui-space-5)}.xs\:bui-m-6{margin:var(--bui-space-6)}.xs\:bui-m-7{margin:var(--bui-space-7)}.xs\:bui-m-8{margin:var(--bui-space-8)}.xs\:bui-m-9{margin:var(--bui-space-9)}.xs\:bui-m-10{margin:var(--bui-space-10)}.xs\:bui-m-11{margin:var(--bui-space-11)}.xs\:bui-m-12{margin:var(--bui-space-12)}.xs\:bui-m-13{margin:var(--bui-space-13)}.xs\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-m{margin:var(--p-sm)}.sm\:bui-m-0\.5{margin:var(--bui-space-0_5)}.sm\:bui-m-1{margin:var(--bui-space-1)}.sm\:bui-m-1\.5{margin:var(--bui-space-1_5)}.sm\:bui-m-2{margin:var(--bui-space-2)}.sm\:bui-m-3{margin:var(--bui-space-3)}.sm\:bui-m-4{margin:var(--bui-space-4)}.sm\:bui-m-5{margin:var(--bui-space-5)}.sm\:bui-m-6{margin:var(--bui-space-6)}.sm\:bui-m-7{margin:var(--bui-space-7)}.sm\:bui-m-8{margin:var(--bui-space-8)}.sm\:bui-m-9{margin:var(--bui-space-9)}.sm\:bui-m-10{margin:var(--bui-space-10)}.sm\:bui-m-11{margin:var(--bui-space-11)}.sm\:bui-m-12{margin:var(--bui-space-12)}.sm\:bui-m-13{margin:var(--bui-space-13)}.sm\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-m{margin:var(--p-md)}.md\:bui-m-0\.5{margin:var(--bui-space-0_5)}.md\:bui-m-1{margin:var(--bui-space-1)}.md\:bui-m-1\.5{margin:var(--bui-space-1_5)}.md\:bui-m-2{margin:var(--bui-space-2)}.md\:bui-m-3{margin:var(--bui-space-3)}.md\:bui-m-4{margin:var(--bui-space-4)}.md\:bui-m-5{margin:var(--bui-space-5)}.md\:bui-m-6{margin:var(--bui-space-6)}.md\:bui-m-7{margin:var(--bui-space-7)}.md\:bui-m-8{margin:var(--bui-space-8)}.md\:bui-m-9{margin:var(--bui-space-9)}.md\:bui-m-10{margin:var(--bui-space-10)}.md\:bui-m-11{margin:var(--bui-space-11)}.md\:bui-m-12{margin:var(--bui-space-12)}.md\:bui-m-13{margin:var(--bui-space-13)}.md\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-m{margin:var(--p-lg)}.lg\:bui-m-0\.5{margin:var(--bui-space-0_5)}.lg\:bui-m-1{margin:var(--bui-space-1)}.lg\:bui-m-1\.5{margin:var(--bui-space-1_5)}.lg\:bui-m-2{margin:var(--bui-space-2)}.lg\:bui-m-3{margin:var(--bui-space-3)}.lg\:bui-m-4{margin:var(--bui-space-4)}.lg\:bui-m-5{margin:var(--bui-space-5)}.lg\:bui-m-6{margin:var(--bui-space-6)}.lg\:bui-m-7{margin:var(--bui-space-7)}.lg\:bui-m-8{margin:var(--bui-space-8)}.lg\:bui-m-9{margin:var(--bui-space-9)}.lg\:bui-m-10{margin:var(--bui-space-10)}.lg\:bui-m-11{margin:var(--bui-space-11)}.lg\:bui-m-12{margin:var(--bui-space-12)}.lg\:bui-m-13{margin:var(--bui-space-13)}.lg\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-m{margin:var(--p-xl)}.xl\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xl\:bui-m-1{margin:var(--bui-space-1)}.xl\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xl\:bui-m-2{margin:var(--bui-space-2)}.xl\:bui-m-3{margin:var(--bui-space-3)}.xl\:bui-m-4{margin:var(--bui-space-4)}.xl\:bui-m-5{margin:var(--bui-space-5)}.xl\:bui-m-6{margin:var(--bui-space-6)}.xl\:bui-m-7{margin:var(--bui-space-7)}.xl\:bui-m-8{margin:var(--bui-space-8)}.xl\:bui-m-9{margin:var(--bui-space-9)}.xl\:bui-m-10{margin:var(--bui-space-10)}.xl\:bui-m-11{margin:var(--bui-space-11)}.xl\:bui-m-12{margin:var(--bui-space-12)}.xl\:bui-m-13{margin:var(--bui-space-13)}.xl\:bui-m-14{margin:var(--bui-space-14)}}.bui-ml{margin-left:var(--ml)}.bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.bui-ml-1{margin-left:var(--bui-space-1)}.bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.bui-ml-2{margin-left:var(--bui-space-2)}.bui-ml-3{margin-left:var(--bui-space-3)}.bui-ml-4{margin-left:var(--bui-space-4)}.bui-ml-5{margin-left:var(--bui-space-5)}.bui-ml-6{margin-left:var(--bui-space-6)}.bui-ml-7{margin-left:var(--bui-space-7)}.bui-ml-8{margin-left:var(--bui-space-8)}.bui-ml-9{margin-left:var(--bui-space-9)}.bui-ml-10{margin-left:var(--bui-space-10)}.bui-ml-11{margin-left:var(--bui-space-11)}.bui-ml-12{margin-left:var(--bui-space-12)}.bui-ml-13{margin-left:var(--bui-space-13)}.bui-ml-14{margin-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-ml{margin-left:var(--ml-xs)}.xs\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xs\:bui-ml-1{margin-left:var(--bui-space-1)}.xs\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xs\:bui-ml-2{margin-left:var(--bui-space-2)}.xs\:bui-ml-3{margin-left:var(--bui-space-3)}.xs\:bui-ml-4{margin-left:var(--bui-space-4)}.xs\:bui-ml-5{margin-left:var(--bui-space-5)}.xs\:bui-ml-6{margin-left:var(--bui-space-6)}.xs\:bui-ml-7{margin-left:var(--bui-space-7)}.xs\:bui-ml-8{margin-left:var(--bui-space-8)}.xs\:bui-ml-9{margin-left:var(--bui-space-9)}.xs\:bui-ml-10{margin-left:var(--bui-space-10)}.xs\:bui-ml-11{margin-left:var(--bui-space-11)}.xs\:bui-ml-12{margin-left:var(--bui-space-12)}.xs\:bui-ml-13{margin-left:var(--bui-space-13)}.xs\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-ml{margin-left:var(--ml-sm)}.sm\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.sm\:bui-ml-1{margin-left:var(--bui-space-1)}.sm\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.sm\:bui-ml-2{margin-left:var(--bui-space-2)}.sm\:bui-ml-3{margin-left:var(--bui-space-3)}.sm\:bui-ml-4{margin-left:var(--bui-space-4)}.sm\:bui-ml-5{margin-left:var(--bui-space-5)}.sm\:bui-ml-6{margin-left:var(--bui-space-6)}.sm\:bui-ml-7{margin-left:var(--bui-space-7)}.sm\:bui-ml-8{margin-left:var(--bui-space-8)}.sm\:bui-ml-9{margin-left:var(--bui-space-9)}.sm\:bui-ml-10{margin-left:var(--bui-space-10)}.sm\:bui-ml-11{margin-left:var(--bui-space-11)}.sm\:bui-ml-12{margin-left:var(--bui-space-12)}.sm\:bui-ml-13{margin-left:var(--bui-space-13)}.sm\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-ml{margin-left:var(--ml-md)}.md\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.md\:bui-ml-1{margin-left:var(--bui-space-1)}.md\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.md\:bui-ml-2{margin-left:var(--bui-space-2)}.md\:bui-ml-3{margin-left:var(--bui-space-3)}.md\:bui-ml-4{margin-left:var(--bui-space-4)}.md\:bui-ml-5{margin-left:var(--bui-space-5)}.md\:bui-ml-6{margin-left:var(--bui-space-6)}.md\:bui-ml-7{margin-left:var(--bui-space-7)}.md\:bui-ml-8{margin-left:var(--bui-space-8)}.md\:bui-ml-9{margin-left:var(--bui-space-9)}.md\:bui-ml-10{margin-left:var(--bui-space-10)}.md\:bui-ml-11{margin-left:var(--bui-space-11)}.md\:bui-ml-12{margin-left:var(--bui-space-12)}.md\:bui-ml-13{margin-left:var(--bui-space-13)}.md\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-ml{margin-left:var(--ml-lg)}.lg\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.lg\:bui-ml-1{margin-left:var(--bui-space-1)}.lg\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.lg\:bui-ml-2{margin-left:var(--bui-space-2)}.lg\:bui-ml-3{margin-left:var(--bui-space-3)}.lg\:bui-ml-4{margin-left:var(--bui-space-4)}.lg\:bui-ml-5{margin-left:var(--bui-space-5)}.lg\:bui-ml-6{margin-left:var(--bui-space-6)}.lg\:bui-ml-7{margin-left:var(--bui-space-7)}.lg\:bui-ml-8{margin-left:var(--bui-space-8)}.lg\:bui-ml-9{margin-left:var(--bui-space-9)}.lg\:bui-ml-10{margin-left:var(--bui-space-10)}.lg\:bui-ml-11{margin-left:var(--bui-space-11)}.lg\:bui-ml-12{margin-left:var(--bui-space-12)}.lg\:bui-ml-13{margin-left:var(--bui-space-13)}.lg\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-ml{margin-left:var(--ml-xl)}.xl\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xl\:bui-ml-1{margin-left:var(--bui-space-1)}.xl\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xl\:bui-ml-2{margin-left:var(--bui-space-2)}.xl\:bui-ml-3{margin-left:var(--bui-space-3)}.xl\:bui-ml-4{margin-left:var(--bui-space-4)}.xl\:bui-ml-5{margin-left:var(--bui-space-5)}.xl\:bui-ml-6{margin-left:var(--bui-space-6)}.xl\:bui-ml-7{margin-left:var(--bui-space-7)}.xl\:bui-ml-8{margin-left:var(--bui-space-8)}.xl\:bui-ml-9{margin-left:var(--bui-space-9)}.xl\:bui-ml-10{margin-left:var(--bui-space-10)}.xl\:bui-ml-11{margin-left:var(--bui-space-11)}.xl\:bui-ml-12{margin-left:var(--bui-space-12)}.xl\:bui-ml-13{margin-left:var(--bui-space-13)}.xl\:bui-ml-14{margin-left:var(--bui-space-14)}}.bui-mr{margin-right:var(--mr)}.bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.bui-mr-1{margin-right:var(--bui-space-1)}.bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.bui-mr-2{margin-right:var(--bui-space-2)}.bui-mr-3{margin-right:var(--bui-space-3)}.bui-mr-4{margin-right:var(--bui-space-4)}.bui-mr-5{margin-right:var(--bui-space-5)}.bui-mr-6{margin-right:var(--bui-space-6)}.bui-mr-7{margin-right:var(--bui-space-7)}.bui-mr-8{margin-right:var(--bui-space-8)}.bui-mr-9{margin-right:var(--bui-space-9)}.bui-mr-10{margin-right:var(--bui-space-10)}.bui-mr-11{margin-right:var(--bui-space-11)}.bui-mr-12{margin-right:var(--bui-space-12)}.bui-mr-13{margin-right:var(--bui-space-13)}.bui-mr-14{margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mr{margin-right:var(--mr-xs)}.xs\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xs\:bui-mr-1{margin-right:var(--bui-space-1)}.xs\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xs\:bui-mr-2{margin-right:var(--bui-space-2)}.xs\:bui-mr-3{margin-right:var(--bui-space-3)}.xs\:bui-mr-4{margin-right:var(--bui-space-4)}.xs\:bui-mr-5{margin-right:var(--bui-space-5)}.xs\:bui-mr-6{margin-right:var(--bui-space-6)}.xs\:bui-mr-7{margin-right:var(--bui-space-7)}.xs\:bui-mr-8{margin-right:var(--bui-space-8)}.xs\:bui-mr-9{margin-right:var(--bui-space-9)}.xs\:bui-mr-10{margin-right:var(--bui-space-10)}.xs\:bui-mr-11{margin-right:var(--bui-space-11)}.xs\:bui-mr-12{margin-right:var(--bui-space-12)}.xs\:bui-mr-13{margin-right:var(--bui-space-13)}.xs\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mr{margin-right:var(--mr-sm)}.sm\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.sm\:bui-mr-1{margin-right:var(--bui-space-1)}.sm\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.sm\:bui-mr-2{margin-right:var(--bui-space-2)}.sm\:bui-mr-3{margin-right:var(--bui-space-3)}.sm\:bui-mr-4{margin-right:var(--bui-space-4)}.sm\:bui-mr-5{margin-right:var(--bui-space-5)}.sm\:bui-mr-6{margin-right:var(--bui-space-6)}.sm\:bui-mr-7{margin-right:var(--bui-space-7)}.sm\:bui-mr-8{margin-right:var(--bui-space-8)}.sm\:bui-mr-9{margin-right:var(--bui-space-9)}.sm\:bui-mr-10{margin-right:var(--bui-space-10)}.sm\:bui-mr-11{margin-right:var(--bui-space-11)}.sm\:bui-mr-12{margin-right:var(--bui-space-12)}.sm\:bui-mr-13{margin-right:var(--bui-space-13)}.sm\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mr{margin-right:var(--mr-md)}.md\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.md\:bui-mr-1{margin-right:var(--bui-space-1)}.md\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.md\:bui-mr-2{margin-right:var(--bui-space-2)}.md\:bui-mr-3{margin-right:var(--bui-space-3)}.md\:bui-mr-4{margin-right:var(--bui-space-4)}.md\:bui-mr-5{margin-right:var(--bui-space-5)}.md\:bui-mr-6{margin-right:var(--bui-space-6)}.md\:bui-mr-7{margin-right:var(--bui-space-7)}.md\:bui-mr-8{margin-right:var(--bui-space-8)}.md\:bui-mr-9{margin-right:var(--bui-space-9)}.md\:bui-mr-10{margin-right:var(--bui-space-10)}.md\:bui-mr-11{margin-right:var(--bui-space-11)}.md\:bui-mr-12{margin-right:var(--bui-space-12)}.md\:bui-mr-13{margin-right:var(--bui-space-13)}.md\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mr{margin-right:var(--mr-lg)}.lg\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.lg\:bui-mr-1{margin-right:var(--bui-space-1)}.lg\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.lg\:bui-mr-2{margin-right:var(--bui-space-2)}.lg\:bui-mr-3{margin-right:var(--bui-space-3)}.lg\:bui-mr-4{margin-right:var(--bui-space-4)}.lg\:bui-mr-5{margin-right:var(--bui-space-5)}.lg\:bui-mr-6{margin-right:var(--bui-space-6)}.lg\:bui-mr-7{margin-right:var(--bui-space-7)}.lg\:bui-mr-8{margin-right:var(--bui-space-8)}.lg\:bui-mr-9{margin-right:var(--bui-space-9)}.lg\:bui-mr-10{margin-right:var(--bui-space-10)}.lg\:bui-mr-11{margin-right:var(--bui-space-11)}.lg\:bui-mr-12{margin-right:var(--bui-space-12)}.lg\:bui-mr-13{margin-right:var(--bui-space-13)}.lg\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mr{margin-right:var(--mr-xl)}.xl\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xl\:bui-mr-1{margin-right:var(--bui-space-1)}.xl\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xl\:bui-mr-2{margin-right:var(--bui-space-2)}.xl\:bui-mr-3{margin-right:var(--bui-space-3)}.xl\:bui-mr-4{margin-right:var(--bui-space-4)}.xl\:bui-mr-5{margin-right:var(--bui-space-5)}.xl\:bui-mr-6{margin-right:var(--bui-space-6)}.xl\:bui-mr-7{margin-right:var(--bui-space-7)}.xl\:bui-mr-8{margin-right:var(--bui-space-8)}.xl\:bui-mr-9{margin-right:var(--bui-space-9)}.xl\:bui-mr-10{margin-right:var(--bui-space-10)}.xl\:bui-mr-11{margin-right:var(--bui-space-11)}.xl\:bui-mr-12{margin-right:var(--bui-space-12)}.xl\:bui-mr-13{margin-right:var(--bui-space-13)}.xl\:bui-mr-14{margin-right:var(--bui-space-14)}}.bui-mt{margin-top:var(--mt)}.bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.bui-mt-1{margin-top:var(--bui-space-1)}.bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.bui-mt-2{margin-top:var(--bui-space-2)}.bui-mt-3{margin-top:var(--bui-space-3)}.bui-mt-4{margin-top:var(--bui-space-4)}.bui-mt-5{margin-top:var(--bui-space-5)}.bui-mt-6{margin-top:var(--bui-space-6)}.bui-mt-7{margin-top:var(--bui-space-7)}.bui-mt-8{margin-top:var(--bui-space-8)}.bui-mt-9{margin-top:var(--bui-space-9)}.bui-mt-10{margin-top:var(--bui-space-10)}.bui-mt-11{margin-top:var(--bui-space-11)}.bui-mt-12{margin-top:var(--bui-space-12)}.bui-mt-13{margin-top:var(--bui-space-13)}.bui-mt-14{margin-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mt{margin-top:var(--mt-xs)}.xs\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xs\:bui-mt-1{margin-top:var(--bui-space-1)}.xs\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xs\:bui-mt-2{margin-top:var(--bui-space-2)}.xs\:bui-mt-3{margin-top:var(--bui-space-3)}.xs\:bui-mt-4{margin-top:var(--bui-space-4)}.xs\:bui-mt-5{margin-top:var(--bui-space-5)}.xs\:bui-mt-6{margin-top:var(--bui-space-6)}.xs\:bui-mt-7{margin-top:var(--bui-space-7)}.xs\:bui-mt-8{margin-top:var(--bui-space-8)}.xs\:bui-mt-9{margin-top:var(--bui-space-9)}.xs\:bui-mt-10{margin-top:var(--bui-space-10)}.xs\:bui-mt-11{margin-top:var(--bui-space-11)}.xs\:bui-mt-12{margin-top:var(--bui-space-12)}.xs\:bui-mt-13{margin-top:var(--bui-space-13)}.xs\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mt{margin-top:var(--mt-sm)}.sm\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.sm\:bui-mt-1{margin-top:var(--bui-space-1)}.sm\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.sm\:bui-mt-2{margin-top:var(--bui-space-2)}.sm\:bui-mt-3{margin-top:var(--bui-space-3)}.sm\:bui-mt-4{margin-top:var(--bui-space-4)}.sm\:bui-mt-5{margin-top:var(--bui-space-5)}.sm\:bui-mt-6{margin-top:var(--bui-space-6)}.sm\:bui-mt-7{margin-top:var(--bui-space-7)}.sm\:bui-mt-8{margin-top:var(--bui-space-8)}.sm\:bui-mt-9{margin-top:var(--bui-space-9)}.sm\:bui-mt-10{margin-top:var(--bui-space-10)}.sm\:bui-mt-11{margin-top:var(--bui-space-11)}.sm\:bui-mt-12{margin-top:var(--bui-space-12)}.sm\:bui-mt-13{margin-top:var(--bui-space-13)}.sm\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mt{margin-top:var(--mt-md)}.md\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.md\:bui-mt-1{margin-top:var(--bui-space-1)}.md\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.md\:bui-mt-2{margin-top:var(--bui-space-2)}.md\:bui-mt-3{margin-top:var(--bui-space-3)}.md\:bui-mt-4{margin-top:var(--bui-space-4)}.md\:bui-mt-5{margin-top:var(--bui-space-5)}.md\:bui-mt-6{margin-top:var(--bui-space-6)}.md\:bui-mt-7{margin-top:var(--bui-space-7)}.md\:bui-mt-8{margin-top:var(--bui-space-8)}.md\:bui-mt-9{margin-top:var(--bui-space-9)}.md\:bui-mt-10{margin-top:var(--bui-space-10)}.md\:bui-mt-11{margin-top:var(--bui-space-11)}.md\:bui-mt-12{margin-top:var(--bui-space-12)}.md\:bui-mt-13{margin-top:var(--bui-space-13)}.md\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mt{margin-top:var(--mt-lg)}.lg\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.lg\:bui-mt-1{margin-top:var(--bui-space-1)}.lg\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.lg\:bui-mt-2{margin-top:var(--bui-space-2)}.lg\:bui-mt-3{margin-top:var(--bui-space-3)}.lg\:bui-mt-4{margin-top:var(--bui-space-4)}.lg\:bui-mt-5{margin-top:var(--bui-space-5)}.lg\:bui-mt-6{margin-top:var(--bui-space-6)}.lg\:bui-mt-7{margin-top:var(--bui-space-7)}.lg\:bui-mt-8{margin-top:var(--bui-space-8)}.lg\:bui-mt-9{margin-top:var(--bui-space-9)}.lg\:bui-mt-10{margin-top:var(--bui-space-10)}.lg\:bui-mt-11{margin-top:var(--bui-space-11)}.lg\:bui-mt-12{margin-top:var(--bui-space-12)}.lg\:bui-mt-13{margin-top:var(--bui-space-13)}.lg\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mt{margin-top:var(--mt-xl)}.xl\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xl\:bui-mt-1{margin-top:var(--bui-space-1)}.xl\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xl\:bui-mt-2{margin-top:var(--bui-space-2)}.xl\:bui-mt-3{margin-top:var(--bui-space-3)}.xl\:bui-mt-4{margin-top:var(--bui-space-4)}.xl\:bui-mt-5{margin-top:var(--bui-space-5)}.xl\:bui-mt-6{margin-top:var(--bui-space-6)}.xl\:bui-mt-7{margin-top:var(--bui-space-7)}.xl\:bui-mt-8{margin-top:var(--bui-space-8)}.xl\:bui-mt-9{margin-top:var(--bui-space-9)}.xl\:bui-mt-10{margin-top:var(--bui-space-10)}.xl\:bui-mt-11{margin-top:var(--bui-space-11)}.xl\:bui-mt-12{margin-top:var(--bui-space-12)}.xl\:bui-mt-13{margin-top:var(--bui-space-13)}.xl\:bui-mt-14{margin-top:var(--bui-space-14)}}.bui-mb{margin-bottom:var(--mb)}.bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.bui-mb-1{margin-bottom:var(--bui-space-1)}.bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.bui-mb-2{margin-bottom:var(--bui-space-2)}.bui-mb-3{margin-bottom:var(--bui-space-3)}.bui-mb-4{margin-bottom:var(--bui-space-4)}.bui-mb-5{margin-bottom:var(--bui-space-5)}.bui-mb-6{margin-bottom:var(--bui-space-6)}.bui-mb-7{margin-bottom:var(--bui-space-7)}.bui-mb-8{margin-bottom:var(--bui-space-8)}.bui-mb-9{margin-bottom:var(--bui-space-9)}.bui-mb-10{margin-bottom:var(--bui-space-10)}.bui-mb-11{margin-bottom:var(--bui-space-11)}.bui-mb-12{margin-bottom:var(--bui-space-12)}.bui-mb-13{margin-bottom:var(--bui-space-13)}.bui-mb-14{margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mb{margin-bottom:var(--mb-xs)}.xs\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xs\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xs\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xs\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xs\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xs\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xs\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xs\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xs\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xs\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xs\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xs\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xs\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xs\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xs\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xs\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mb{margin-bottom:var(--mb-sm)}.sm\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.sm\:bui-mb-1{margin-bottom:var(--bui-space-1)}.sm\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.sm\:bui-mb-2{margin-bottom:var(--bui-space-2)}.sm\:bui-mb-3{margin-bottom:var(--bui-space-3)}.sm\:bui-mb-4{margin-bottom:var(--bui-space-4)}.sm\:bui-mb-5{margin-bottom:var(--bui-space-5)}.sm\:bui-mb-6{margin-bottom:var(--bui-space-6)}.sm\:bui-mb-7{margin-bottom:var(--bui-space-7)}.sm\:bui-mb-8{margin-bottom:var(--bui-space-8)}.sm\:bui-mb-9{margin-bottom:var(--bui-space-9)}.sm\:bui-mb-10{margin-bottom:var(--bui-space-10)}.sm\:bui-mb-11{margin-bottom:var(--bui-space-11)}.sm\:bui-mb-12{margin-bottom:var(--bui-space-12)}.sm\:bui-mb-13{margin-bottom:var(--bui-space-13)}.sm\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mb{margin-bottom:var(--mb-md)}.md\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.md\:bui-mb-1{margin-bottom:var(--bui-space-1)}.md\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.md\:bui-mb-2{margin-bottom:var(--bui-space-2)}.md\:bui-mb-3{margin-bottom:var(--bui-space-3)}.md\:bui-mb-4{margin-bottom:var(--bui-space-4)}.md\:bui-mb-5{margin-bottom:var(--bui-space-5)}.md\:bui-mb-6{margin-bottom:var(--bui-space-6)}.md\:bui-mb-7{margin-bottom:var(--bui-space-7)}.md\:bui-mb-8{margin-bottom:var(--bui-space-8)}.md\:bui-mb-9{margin-bottom:var(--bui-space-9)}.md\:bui-mb-10{margin-bottom:var(--bui-space-10)}.md\:bui-mb-11{margin-bottom:var(--bui-space-11)}.md\:bui-mb-12{margin-bottom:var(--bui-space-12)}.md\:bui-mb-13{margin-bottom:var(--bui-space-13)}.md\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mb{margin-bottom:var(--mb-lg)}.lg\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.lg\:bui-mb-1{margin-bottom:var(--bui-space-1)}.lg\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.lg\:bui-mb-2{margin-bottom:var(--bui-space-2)}.lg\:bui-mb-3{margin-bottom:var(--bui-space-3)}.lg\:bui-mb-4{margin-bottom:var(--bui-space-4)}.lg\:bui-mb-5{margin-bottom:var(--bui-space-5)}.lg\:bui-mb-6{margin-bottom:var(--bui-space-6)}.lg\:bui-mb-7{margin-bottom:var(--bui-space-7)}.lg\:bui-mb-8{margin-bottom:var(--bui-space-8)}.lg\:bui-mb-9{margin-bottom:var(--bui-space-9)}.lg\:bui-mb-10{margin-bottom:var(--bui-space-10)}.lg\:bui-mb-11{margin-bottom:var(--bui-space-11)}.lg\:bui-mb-12{margin-bottom:var(--bui-space-12)}.lg\:bui-mb-13{margin-bottom:var(--bui-space-13)}.lg\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mb{margin-bottom:var(--mb-xl)}.xl\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xl\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xl\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xl\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xl\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xl\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xl\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xl\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xl\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xl\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xl\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xl\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xl\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xl\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xl\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xl\:bui-mb-14{margin-bottom:var(--bui-space-14)}}.bui-my{margin-top:var(--my);margin-bottom:var(--my)}.bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-my{margin-top:var(--my-xs);margin-bottom:var(--my-xs)}.xs\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xs\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xs\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xs\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xs\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xs\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xs\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xs\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xs\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xs\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xs\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xs\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xs\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xs\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xs\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xs\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-my{margin-top:var(--my-sm);margin-bottom:var(--my-sm)}.sm\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.sm\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.sm\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.sm\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.sm\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.sm\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.sm\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.sm\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.sm\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.sm\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.sm\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.sm\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.sm\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.sm\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.sm\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.sm\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-my{margin-top:var(--my-md);margin-bottom:var(--my-md)}.md\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.md\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.md\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.md\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.md\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.md\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.md\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.md\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.md\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.md\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.md\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.md\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.md\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.md\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.md\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.md\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-my{margin-top:var(--my-lg);margin-bottom:var(--my-lg)}.lg\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.lg\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.lg\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.lg\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.lg\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.lg\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.lg\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.lg\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.lg\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.lg\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.lg\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.lg\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.lg\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.lg\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.lg\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.lg\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-my{margin-top:var(--my-xl);margin-bottom:var(--my-xl)}.xl\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xl\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xl\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xl\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xl\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xl\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xl\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xl\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xl\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xl\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xl\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xl\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xl\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xl\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xl\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xl\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}.bui-mx{margin-left:var(--mx);margin-right:var(--mx)}.bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mx{margin-left:var(--mx-xs);margin-right:var(--mx-xs)}.xs\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xs\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xs\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xs\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xs\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xs\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xs\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xs\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xs\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xs\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xs\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xs\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xs\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xs\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xs\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xs\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mx{margin-left:var(--mx-sm);margin-right:var(--mx-sm)}.sm\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.sm\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.sm\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.sm\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.sm\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.sm\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.sm\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.sm\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.sm\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.sm\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.sm\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.sm\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.sm\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.sm\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.sm\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.sm\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mx{margin-left:var(--mx-md);margin-right:var(--mx-md)}.md\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.md\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.md\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.md\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.md\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.md\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.md\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.md\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.md\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.md\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.md\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.md\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.md\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.md\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.md\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.md\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mx{margin-left:var(--mx-lg);margin-right:var(--mx-lg)}.lg\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.lg\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.lg\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.lg\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.lg\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.lg\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.lg\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.lg\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.lg\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.lg\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.lg\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.lg\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.lg\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.lg\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.lg\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.lg\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mx{margin-left:var(--mx-xl);margin-right:var(--mx-xl)}.xl\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xl\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xl\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xl\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xl\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xl\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xl\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xl\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xl\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xl\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xl\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xl\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xl\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xl\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xl\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xl\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}.bui-display-none{display:none}.bui-display-inline{display:inline}.bui-display-inline-block{display:inline-block}.bui-display-block{display:block}@media (width>=640px){.xs\:bui-display-none{display:none}.xs\:bui-display-inline{display:inline}.xs\:bui-display-inline-block{display:inline-block}.xs\:bui-display-block{display:block}}@media (width>=768px){.sm\:bui-display-none{display:none}.sm\:bui-display-inline{display:inline}.sm\:bui-display-inline-block{display:inline-block}.sm\:bui-display-block{display:block}}@media (width>=1024px){.md\:bui-display-none{display:none}.md\:bui-display-inline{display:inline}.md\:bui-display-inline-block{display:inline-block}.md\:bui-display-block{display:block}}@media (width>=1280px){.lg\:bui-display-none{display:none}.lg\:bui-display-inline{display:inline}.lg\:bui-display-inline-block{display:inline-block}.lg\:bui-display-block{display:block}}@media (width>=1536px){.xl\:bui-display-none{display:none}.xl\:bui-display-inline{display:inline}.xl\:bui-display-inline-block{display:inline-block}.xl\:bui-display-block{display:block}}.bui-w{width:var(--width)}.bui-min-w{min-width:var(--min-width)}.bui-max-w{max-width:var(--max-width)}@media (width>=640px){.xs\:bui-w{width:var(--width)}.xs\:bui-min-w{min-width:var(--min-width)}.xs\:bui-max-w{max-width:var(--max-width)}}@media (width>=768px){.sm\:bui-w{width:var(--width)}.sm\:bui-min-w{min-width:var(--min-width)}.sm\:bui-max-w{max-width:var(--max-width)}}@media (width>=1024px){.md\:bui-w{width:var(--width)}.md\:bui-min-w{min-width:var(--min-width)}.md\:bui-max-w{max-width:var(--max-width)}}@media (width>=1280px){.lg\:bui-w{width:var(--width)}.lg\:bui-min-w{min-width:var(--min-width)}.lg\:bui-max-w{max-width:var(--max-width)}}@media (width>=1536px){.xl\:bui-w{width:var(--width)}.xl\:bui-min-w{min-width:var(--min-width)}.xl\:bui-max-w{max-width:var(--max-width)}}.bui-h{height:var(--height)}.bui-min-h{min-height:var(--min-height)}.bui-max-h{max-height:var(--max-height)}@media (width>=640px){.xs\:bui-h{height:var(--height)}.xs\:bui-min-h{min-height:var(--min-height)}.xs\:bui-max-h{max-height:var(--max-height)}}@media (width>=768px){.sm\:bui-h{height:var(--height)}.sm\:bui-min-h{min-height:var(--min-height)}.sm\:bui-max-h{max-height:var(--max-height)}}@media (width>=1024px){.md\:bui-h{height:var(--height)}.md\:bui-min-h{min-height:var(--min-height)}.md\:bui-max-h{max-height:var(--max-height)}}@media (width>=1280px){.lg\:bui-h{height:var(--height)}.lg\:bui-min-h{min-height:var(--min-height)}.lg\:bui-max-h{max-height:var(--max-height)}}@media (width>=1536px){.xl\:bui-h{height:var(--height)}.xl\:bui-min-h{min-height:var(--min-height)}.xl\:bui-max-h{max-height:var(--max-height)}}.bui-position-absolute{position:absolute}.bui-position-fixed{position:fixed}.bui-position-sticky{position:sticky}.bui-position-relative{position:relative}.bui-position-static{position:static}@media (width>=640px){.xs\:bui-position-absolute{position:absolute}.xs\:bui-position-fixed{position:fixed}.xs\:bui-position-sticky{position:sticky}.xs\:bui-position-relative{position:relative}.xs\:bui-position-static{position:static}}@media (width>=768px){.sm\:bui-position-absolute{position:absolute}.sm\:bui-position-fixed{position:fixed}.sm\:bui-position-sticky{position:sticky}.sm\:bui-position-relative{position:relative}.sm\:bui-position-static{position:static}}@media (width>=1024px){.md\:bui-position-absolute{position:absolute}.md\:bui-position-fixed{position:fixed}.md\:bui-position-sticky{position:sticky}.md\:bui-position-relative{position:relative}.md\:bui-position-static{position:static}}@media (width>=1280px){.lg\:bui-position-absolute{position:absolute}.lg\:bui-position-fixed{position:fixed}.lg\:bui-position-sticky{position:sticky}.lg\:bui-position-relative{position:relative}.lg\:bui-position-static{position:static}}@media (width>=1536px){.xl\:bui-position-absolute{position:absolute}.xl\:bui-position-fixed{position:fixed}.xl\:bui-position-sticky{position:sticky}.xl\:bui-position-relative{position:relative}.xl\:bui-position-static{position:static}}.bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.bui-col-span-1{grid-column:span 1/span 1}.bui-col-span-2{grid-column:span 2/span 2}.bui-col-span-3{grid-column:span 3/span 3}.bui-col-span-4{grid-column:span 4/span 4}.bui-col-span-5{grid-column:span 5/span 5}.bui-col-span-6{grid-column:span 6/span 6}.bui-col-span-7{grid-column:span 7/span 7}.bui-col-span-8{grid-column:span 8/span 8}.bui-col-span-9{grid-column:span 9/span 9}.bui-col-span-10{grid-column:span 10/span 10}.bui-col-span-11{grid-column:span 11/span 11}.bui-col-span-12{grid-column:span 12/span 12}.bui-col-span-auto{grid-column:span auto/span auto}.bui-col-start-1{grid-column-start:1}.bui-col-start-2{grid-column-start:2}.bui-col-start-3{grid-column-start:3}.bui-col-start-4{grid-column-start:4}.bui-col-start-5{grid-column-start:5}.bui-col-start-6{grid-column-start:6}.bui-col-start-7{grid-column-start:7}.bui-col-start-8{grid-column-start:8}.bui-col-start-9{grid-column-start:9}.bui-col-start-10{grid-column-start:10}.bui-col-start-11{grid-column-start:11}.bui-col-start-12{grid-column-start:12}.bui-col-start-13{grid-column-start:13}.bui-col-start-auto{grid-column-start:auto}.bui-col-end-1{grid-column-end:1}.bui-col-end-2{grid-column-end:2}.bui-col-end-3{grid-column-end:3}.bui-col-end-4{grid-column-end:4}.bui-col-end-5{grid-column-end:5}.bui-col-end-6{grid-column-end:6}.bui-col-end-7{grid-column-end:7}.bui-col-end-8{grid-column-end:8}.bui-col-end-9{grid-column-end:9}.bui-col-end-10{grid-column-end:10}.bui-col-end-11{grid-column-end:11}.bui-col-end-12{grid-column-end:12}.bui-col-end-13{grid-column-end:13}.bui-col-end-auto{grid-column-end:auto}.bui-row-span-1{grid-row:span 1/span 1}.bui-row-span-2{grid-row:span 2/span 2}.bui-row-span-3{grid-row:span 3/span 3}.bui-row-span-4{grid-row:span 4/span 4}.bui-row-span-5{grid-row:span 5/span 5}.bui-row-span-6{grid-row:span 6/span 6}.bui-row-span-7{grid-row:span 7/span 7}.bui-row-span-8{grid-row:span 8/span 8}.bui-row-span-9{grid-row:span 9/span 9}.bui-row-span-10{grid-row:span 10/span 10}.bui-row-span-11{grid-row:span 11/span 11}.bui-row-span-12{grid-row:span 12/span 12}.bui-row-span-auto{grid-row:span auto/span auto}@media (width>=640px){.xs\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xs\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xs\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xs\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xs\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xs\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xs\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xs\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xs\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xs\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xs\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xs\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xs\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xs\:bui-col-span-1{grid-column:span 1/span 1}.xs\:bui-col-span-2{grid-column:span 2/span 2}.xs\:bui-col-span-3{grid-column:span 3/span 3}.xs\:bui-col-span-4{grid-column:span 4/span 4}.xs\:bui-col-span-5{grid-column:span 5/span 5}.xs\:bui-col-span-6{grid-column:span 6/span 6}.xs\:bui-col-span-7{grid-column:span 7/span 7}.xs\:bui-col-span-8{grid-column:span 8/span 8}.xs\:bui-col-span-9{grid-column:span 9/span 9}.xs\:bui-col-span-10{grid-column:span 10/span 10}.xs\:bui-col-span-11{grid-column:span 11/span 11}.xs\:bui-col-span-12{grid-column:span 12/span 12}.xs\:bui-col-span-auto{grid-column:span auto/span auto}.xs\:bui-col-start-1{grid-column-start:1}.xs\:bui-col-start-2{grid-column-start:2}.xs\:bui-col-start-3{grid-column-start:3}.xs\:bui-col-start-4{grid-column-start:4}.xs\:bui-col-start-5{grid-column-start:5}.xs\:bui-col-start-6{grid-column-start:6}.xs\:bui-col-start-7{grid-column-start:7}.xs\:bui-col-start-8{grid-column-start:8}.xs\:bui-col-start-9{grid-column-start:9}.xs\:bui-col-start-10{grid-column-start:10}.xs\:bui-col-start-11{grid-column-start:11}.xs\:bui-col-start-12{grid-column-start:12}.xs\:bui-col-start-13{grid-column-start:13}.xs\:bui-col-start-auto{grid-column-start:auto}.xs\:bui-col-end-1{grid-column-end:1}.xs\:bui-col-end-2{grid-column-end:2}.xs\:bui-col-end-3{grid-column-end:3}.xs\:bui-col-end-4{grid-column-end:4}.xs\:bui-col-end-5{grid-column-end:5}.xs\:bui-col-end-6{grid-column-end:6}.xs\:bui-col-end-7{grid-column-end:7}.xs\:bui-col-end-8{grid-column-end:8}.xs\:bui-col-end-9{grid-column-end:9}.xs\:bui-col-end-10{grid-column-end:10}.xs\:bui-col-end-11{grid-column-end:11}.xs\:bui-col-end-12{grid-column-end:12}.xs\:bui-col-end-13{grid-column-end:13}.xs\:bui-col-end-auto{grid-column-end:auto}.xs\:bui-row-span-1{grid-row:span 1/span 1}.xs\:bui-row-span-2{grid-row:span 2/span 2}.xs\:bui-row-span-3{grid-row:span 3/span 3}.xs\:bui-row-span-4{grid-row:span 4/span 4}.xs\:bui-row-span-5{grid-row:span 5/span 5}.xs\:bui-row-span-6{grid-row:span 6/span 6}.xs\:bui-row-span-7{grid-row:span 7/span 7}.xs\:bui-row-span-8{grid-row:span 8/span 8}.xs\:bui-row-span-9{grid-row:span 9/span 9}.xs\:bui-row-span-10{grid-row:span 10/span 10}.xs\:bui-row-span-11{grid-row:span 11/span 11}.xs\:bui-row-span-12{grid-row:span 12/span 12}.xs\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=768px){.sm\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.sm\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.sm\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.sm\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.sm\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:bui-col-span-1{grid-column:span 1/span 1}.sm\:bui-col-span-2{grid-column:span 2/span 2}.sm\:bui-col-span-3{grid-column:span 3/span 3}.sm\:bui-col-span-4{grid-column:span 4/span 4}.sm\:bui-col-span-5{grid-column:span 5/span 5}.sm\:bui-col-span-6{grid-column:span 6/span 6}.sm\:bui-col-span-7{grid-column:span 7/span 7}.sm\:bui-col-span-8{grid-column:span 8/span 8}.sm\:bui-col-span-9{grid-column:span 9/span 9}.sm\:bui-col-span-10{grid-column:span 10/span 10}.sm\:bui-col-span-11{grid-column:span 11/span 11}.sm\:bui-col-span-12{grid-column:span 12/span 12}.sm\:bui-col-span-auto{grid-column:span auto/span auto}.sm\:bui-col-start-1{grid-column-start:1}.sm\:bui-col-start-2{grid-column-start:2}.sm\:bui-col-start-3{grid-column-start:3}.sm\:bui-col-start-4{grid-column-start:4}.sm\:bui-col-start-5{grid-column-start:5}.sm\:bui-col-start-6{grid-column-start:6}.sm\:bui-col-start-7{grid-column-start:7}.sm\:bui-col-start-8{grid-column-start:8}.sm\:bui-col-start-9{grid-column-start:9}.sm\:bui-col-start-10{grid-column-start:10}.sm\:bui-col-start-11{grid-column-start:11}.sm\:bui-col-start-12{grid-column-start:12}.sm\:bui-col-start-13{grid-column-start:13}.sm\:bui-col-start-auto{grid-column-start:auto}.sm\:bui-col-end-1{grid-column-end:1}.sm\:bui-col-end-2{grid-column-end:2}.sm\:bui-col-end-3{grid-column-end:3}.sm\:bui-col-end-4{grid-column-end:4}.sm\:bui-col-end-5{grid-column-end:5}.sm\:bui-col-end-6{grid-column-end:6}.sm\:bui-col-end-7{grid-column-end:7}.sm\:bui-col-end-8{grid-column-end:8}.sm\:bui-col-end-9{grid-column-end:9}.sm\:bui-col-end-10{grid-column-end:10}.sm\:bui-col-end-11{grid-column-end:11}.sm\:bui-col-end-12{grid-column-end:12}.sm\:bui-col-end-13{grid-column-end:13}.sm\:bui-col-end-auto{grid-column-end:auto}.sm\:bui-row-span-1{grid-row:span 1/span 1}.sm\:bui-row-span-2{grid-row:span 2/span 2}.sm\:bui-row-span-3{grid-row:span 3/span 3}.sm\:bui-row-span-4{grid-row:span 4/span 4}.sm\:bui-row-span-5{grid-row:span 5/span 5}.sm\:bui-row-span-6{grid-row:span 6/span 6}.sm\:bui-row-span-7{grid-row:span 7/span 7}.sm\:bui-row-span-8{grid-row:span 8/span 8}.sm\:bui-row-span-9{grid-row:span 9/span 9}.sm\:bui-row-span-10{grid-row:span 10/span 10}.sm\:bui-row-span-11{grid-row:span 11/span 11}.sm\:bui-row-span-12{grid-row:span 12/span 12}.sm\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1024px){.md\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.md\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.md\:bui-col-span-1{grid-column:span 1/span 1}.md\:bui-col-span-2{grid-column:span 2/span 2}.md\:bui-col-span-3{grid-column:span 3/span 3}.md\:bui-col-span-4{grid-column:span 4/span 4}.md\:bui-col-span-5{grid-column:span 5/span 5}.md\:bui-col-span-6{grid-column:span 6/span 6}.md\:bui-col-span-7{grid-column:span 7/span 7}.md\:bui-col-span-8{grid-column:span 8/span 8}.md\:bui-col-span-9{grid-column:span 9/span 9}.md\:bui-col-span-10{grid-column:span 10/span 10}.md\:bui-col-span-11{grid-column:span 11/span 11}.md\:bui-col-span-12{grid-column:span 12/span 12}.md\:bui-col-span-auto{grid-column:span auto/span auto}.md\:bui-col-start-1{grid-column-start:1}.md\:bui-col-start-2{grid-column-start:2}.md\:bui-col-start-3{grid-column-start:3}.md\:bui-col-start-4{grid-column-start:4}.md\:bui-col-start-5{grid-column-start:5}.md\:bui-col-start-6{grid-column-start:6}.md\:bui-col-start-7{grid-column-start:7}.md\:bui-col-start-8{grid-column-start:8}.md\:bui-col-start-9{grid-column-start:9}.md\:bui-col-start-10{grid-column-start:10}.md\:bui-col-start-11{grid-column-start:11}.md\:bui-col-start-12{grid-column-start:12}.md\:bui-col-start-13{grid-column-start:13}.md\:bui-col-start-auto{grid-column-start:auto}.md\:bui-col-end-1{grid-column-end:1}.md\:bui-col-end-2{grid-column-end:2}.md\:bui-col-end-3{grid-column-end:3}.md\:bui-col-end-4{grid-column-end:4}.md\:bui-col-end-5{grid-column-end:5}.md\:bui-col-end-6{grid-column-end:6}.md\:bui-col-end-7{grid-column-end:7}.md\:bui-col-end-8{grid-column-end:8}.md\:bui-col-end-9{grid-column-end:9}.md\:bui-col-end-10{grid-column-end:10}.md\:bui-col-end-11{grid-column-end:11}.md\:bui-col-end-12{grid-column-end:12}.md\:bui-col-end-13{grid-column-end:13}.md\:bui-col-end-auto{grid-column-end:auto}.md\:bui-row-span-1{grid-row:span 1/span 1}.md\:bui-row-span-2{grid-row:span 2/span 2}.md\:bui-row-span-3{grid-row:span 3/span 3}.md\:bui-row-span-4{grid-row:span 4/span 4}.md\:bui-row-span-5{grid-row:span 5/span 5}.md\:bui-row-span-6{grid-row:span 6/span 6}.md\:bui-row-span-7{grid-row:span 7/span 7}.md\:bui-row-span-8{grid-row:span 8/span 8}.md\:bui-row-span-9{grid-row:span 9/span 9}.md\:bui-row-span-10{grid-row:span 10/span 10}.md\:bui-row-span-11{grid-row:span 11/span 11}.md\:bui-row-span-12{grid-row:span 12/span 12}.md\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1280px){.lg\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.lg\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.lg\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.lg\:bui-col-span-1{grid-column:span 1/span 1}.lg\:bui-col-span-2{grid-column:span 2/span 2}.lg\:bui-col-span-3{grid-column:span 3/span 3}.lg\:bui-col-span-4{grid-column:span 4/span 4}.lg\:bui-col-span-5{grid-column:span 5/span 5}.lg\:bui-col-span-6{grid-column:span 6/span 6}.lg\:bui-col-span-7{grid-column:span 7/span 7}.lg\:bui-col-span-8{grid-column:span 8/span 8}.lg\:bui-col-span-9{grid-column:span 9/span 9}.lg\:bui-col-span-10{grid-column:span 10/span 10}.lg\:bui-col-span-11{grid-column:span 11/span 11}.lg\:bui-col-span-12{grid-column:span 12/span 12}.lg\:bui-col-span-auto{grid-column:span auto/span auto}.lg\:bui-col-start-1{grid-column-start:1}.lg\:bui-col-start-2{grid-column-start:2}.lg\:bui-col-start-3{grid-column-start:3}.lg\:bui-col-start-4{grid-column-start:4}.lg\:bui-col-start-5{grid-column-start:5}.lg\:bui-col-start-6{grid-column-start:6}.lg\:bui-col-start-7{grid-column-start:7}.lg\:bui-col-start-8{grid-column-start:8}.lg\:bui-col-start-9{grid-column-start:9}.lg\:bui-col-start-10{grid-column-start:10}.lg\:bui-col-start-11{grid-column-start:11}.lg\:bui-col-start-12{grid-column-start:12}.lg\:bui-col-start-13{grid-column-start:13}.lg\:bui-col-start-auto{grid-column-start:auto}.lg\:bui-col-end-1{grid-column-end:1}.lg\:bui-col-end-2{grid-column-end:2}.lg\:bui-col-end-3{grid-column-end:3}.lg\:bui-col-end-4{grid-column-end:4}.lg\:bui-col-end-5{grid-column-end:5}.lg\:bui-col-end-6{grid-column-end:6}.lg\:bui-col-end-7{grid-column-end:7}.lg\:bui-col-end-8{grid-column-end:8}.lg\:bui-col-end-9{grid-column-end:9}.lg\:bui-col-end-10{grid-column-end:10}.lg\:bui-col-end-11{grid-column-end:11}.lg\:bui-col-end-12{grid-column-end:12}.lg\:bui-col-end-13{grid-column-end:13}.lg\:bui-col-end-auto{grid-column-end:auto}.lg\:bui-row-span-1{grid-row:span 1/span 1}.lg\:bui-row-span-2{grid-row:span 2/span 2}.lg\:bui-row-span-3{grid-row:span 3/span 3}.lg\:bui-row-span-4{grid-row:span 4/span 4}.lg\:bui-row-span-5{grid-row:span 5/span 5}.lg\:bui-row-span-6{grid-row:span 6/span 6}.lg\:bui-row-span-7{grid-row:span 7/span 7}.lg\:bui-row-span-8{grid-row:span 8/span 8}.lg\:bui-row-span-9{grid-row:span 9/span 9}.lg\:bui-row-span-10{grid-row:span 10/span 10}.lg\:bui-row-span-11{grid-row:span 11/span 11}.lg\:bui-row-span-12{grid-row:span 12/span 12}.lg\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1536px){.xl\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xl\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xl\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xl\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xl\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xl\:bui-col-span-1{grid-column:span 1/span 1}.xl\:bui-col-span-2{grid-column:span 2/span 2}.xl\:bui-col-span-3{grid-column:span 3/span 3}.xl\:bui-col-span-4{grid-column:span 4/span 4}.xl\:bui-col-span-5{grid-column:span 5/span 5}.xl\:bui-col-span-6{grid-column:span 6/span 6}.xl\:bui-col-span-7{grid-column:span 7/span 7}.xl\:bui-col-span-8{grid-column:span 8/span 8}.xl\:bui-col-span-9{grid-column:span 9/span 9}.xl\:bui-col-span-10{grid-column:span 10/span 10}.xl\:bui-col-span-11{grid-column:span 11/span 11}.xl\:bui-col-span-12{grid-column:span 12/span 12}.xl\:bui-col-span-auto{grid-column:span auto/span auto}.xl\:bui-col-start-1{grid-column-start:1}.xl\:bui-col-start-2{grid-column-start:2}.xl\:bui-col-start-3{grid-column-start:3}.xl\:bui-col-start-4{grid-column-start:4}.xl\:bui-col-start-5{grid-column-start:5}.xl\:bui-col-start-6{grid-column-start:6}.xl\:bui-col-start-7{grid-column-start:7}.xl\:bui-col-start-8{grid-column-start:8}.xl\:bui-col-start-9{grid-column-start:9}.xl\:bui-col-start-10{grid-column-start:10}.xl\:bui-col-start-11{grid-column-start:11}.xl\:bui-col-start-12{grid-column-start:12}.xl\:bui-col-start-13{grid-column-start:13}.xl\:bui-col-start-auto{grid-column-start:auto}.xl\:bui-col-end-1{grid-column-end:1}.xl\:bui-col-end-2{grid-column-end:2}.xl\:bui-col-end-3{grid-column-end:3}.xl\:bui-col-end-4{grid-column-end:4}.xl\:bui-col-end-5{grid-column-end:5}.xl\:bui-col-end-6{grid-column-end:6}.xl\:bui-col-end-7{grid-column-end:7}.xl\:bui-col-end-8{grid-column-end:8}.xl\:bui-col-end-9{grid-column-end:9}.xl\:bui-col-end-10{grid-column-end:10}.xl\:bui-col-end-11{grid-column-end:11}.xl\:bui-col-end-12{grid-column-end:12}.xl\:bui-col-end-13{grid-column-end:13}.xl\:bui-col-end-auto{grid-column-end:auto}.xl\:bui-row-span-1{grid-row:span 1/span 1}.xl\:bui-row-span-2{grid-row:span 2/span 2}.xl\:bui-row-span-3{grid-row:span 3/span 3}.xl\:bui-row-span-4{grid-row:span 4/span 4}.xl\:bui-row-span-5{grid-row:span 5/span 5}.xl\:bui-row-span-6{grid-row:span 6/span 6}.xl\:bui-row-span-7{grid-row:span 7/span 7}.xl\:bui-row-span-8{grid-row:span 8/span 8}.xl\:bui-row-span-9{grid-row:span 9/span 9}.xl\:bui-row-span-10{grid-row:span 10/span 10}.xl\:bui-row-span-11{grid-row:span 11/span 11}.xl\:bui-row-span-12{grid-row:span 12/span 12}.xl\:bui-row-span-auto{grid-row:span auto/span auto}}.bui-gap{gap:var(--gap)}.bui-gap-0\.5{gap:var(--bui-space-0_5)}.bui-gap-1{gap:var(--bui-space-1)}.bui-gap-1\.5{gap:var(--bui-space-1_5)}.bui-gap-2{gap:var(--bui-space-2)}.bui-gap-3{gap:var(--bui-space-3)}.bui-gap-4{gap:var(--bui-space-4)}.bui-gap-5{gap:var(--bui-space-5)}.bui-gap-6{gap:var(--bui-space-6)}.bui-gap-7{gap:var(--bui-space-7)}.bui-gap-8{gap:var(--bui-space-8)}.bui-gap-9{gap:var(--bui-space-9)}.bui-gap-10{gap:var(--bui-space-10)}.bui-gap-11{gap:var(--bui-space-11)}.bui-gap-12{gap:var(--bui-space-12)}.bui-gap-13{gap:var(--bui-space-13)}.bui-gap-14{gap:var(--bui-space-14)}@media (width>=640px){.xs\:bui-gap{gap:var(--gap-xs)}.xs\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xs\:bui-gap-1{gap:var(--bui-space-1)}.xs\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xs\:bui-gap-2{gap:var(--bui-space-2)}.xs\:bui-gap-3{gap:var(--bui-space-3)}.xs\:bui-gap-4{gap:var(--bui-space-4)}.xs\:bui-gap-5{gap:var(--bui-space-5)}.xs\:bui-gap-6{gap:var(--bui-space-6)}.xs\:bui-gap-7{gap:var(--bui-space-7)}.xs\:bui-gap-8{gap:var(--bui-space-8)}.xs\:bui-gap-9{gap:var(--bui-space-9)}.xs\:bui-gap-10{gap:var(--bui-space-10)}.xs\:bui-gap-11{gap:var(--bui-space-11)}.xs\:bui-gap-12{gap:var(--bui-space-12)}.xs\:bui-gap-13{gap:var(--bui-space-13)}.xs\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-gap{gap:var(--gap-sm)}.sm\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.sm\:bui-gap-1{gap:var(--bui-space-1)}.sm\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.sm\:bui-gap-2{gap:var(--bui-space-2)}.sm\:bui-gap-3{gap:var(--bui-space-3)}.sm\:bui-gap-4{gap:var(--bui-space-4)}.sm\:bui-gap-5{gap:var(--bui-space-5)}.sm\:bui-gap-6{gap:var(--bui-space-6)}.sm\:bui-gap-7{gap:var(--bui-space-7)}.sm\:bui-gap-8{gap:var(--bui-space-8)}.sm\:bui-gap-9{gap:var(--bui-space-9)}.sm\:bui-gap-10{gap:var(--bui-space-10)}.sm\:bui-gap-11{gap:var(--bui-space-11)}.sm\:bui-gap-12{gap:var(--bui-space-12)}.sm\:bui-gap-13{gap:var(--bui-space-13)}.sm\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-gap{gap:var(--gap-md)}.md\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.md\:bui-gap-1{gap:var(--bui-space-1)}.md\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.md\:bui-gap-2{gap:var(--bui-space-2)}.md\:bui-gap-3{gap:var(--bui-space-3)}.md\:bui-gap-4{gap:var(--bui-space-4)}.md\:bui-gap-5{gap:var(--bui-space-5)}.md\:bui-gap-6{gap:var(--bui-space-6)}.md\:bui-gap-7{gap:var(--bui-space-7)}.md\:bui-gap-8{gap:var(--bui-space-8)}.md\:bui-gap-9{gap:var(--bui-space-9)}.md\:bui-gap-10{gap:var(--bui-space-10)}.md\:bui-gap-11{gap:var(--bui-space-11)}.md\:bui-gap-12{gap:var(--bui-space-12)}.md\:bui-gap-13{gap:var(--bui-space-13)}.md\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-gap{gap:var(--gap-lg)}.lg\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.lg\:bui-gap-1{gap:var(--bui-space-1)}.lg\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.lg\:bui-gap-2{gap:var(--bui-space-2)}.lg\:bui-gap-3{gap:var(--bui-space-3)}.lg\:bui-gap-4{gap:var(--bui-space-4)}.lg\:bui-gap-5{gap:var(--bui-space-5)}.lg\:bui-gap-6{gap:var(--bui-space-6)}.lg\:bui-gap-7{gap:var(--bui-space-7)}.lg\:bui-gap-8{gap:var(--bui-space-8)}.lg\:bui-gap-9{gap:var(--bui-space-9)}.lg\:bui-gap-10{gap:var(--bui-space-10)}.lg\:bui-gap-11{gap:var(--bui-space-11)}.lg\:bui-gap-12{gap:var(--bui-space-12)}.lg\:bui-gap-13{gap:var(--bui-space-13)}.lg\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-gap{gap:var(--gap-xl)}.xl\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xl\:bui-gap-1{gap:var(--bui-space-1)}.xl\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xl\:bui-gap-2{gap:var(--bui-space-2)}.xl\:bui-gap-3{gap:var(--bui-space-3)}.xl\:bui-gap-4{gap:var(--bui-space-4)}.xl\:bui-gap-5{gap:var(--bui-space-5)}.xl\:bui-gap-6{gap:var(--bui-space-6)}.xl\:bui-gap-7{gap:var(--bui-space-7)}.xl\:bui-gap-8{gap:var(--bui-space-8)}.xl\:bui-gap-9{gap:var(--bui-space-9)}.xl\:bui-gap-10{gap:var(--bui-space-10)}.xl\:bui-gap-11{gap:var(--bui-space-11)}.xl\:bui-gap-12{gap:var(--bui-space-12)}.xl\:bui-gap-13{gap:var(--bui-space-13)}.xl\:bui-gap-14{gap:var(--bui-space-14)}}.bui-align-start{align-items:start}.bui-align-center{align-items:center}.bui-align-end{align-items:end}.bui-align-baseline{align-items:baseline}.bui-align-stretch{align-items:stretch}.bui-jc-start{justify-content:start}.bui-jc-center{justify-content:center}.bui-jc-end{justify-content:end}.bui-jc-between{justify-content:space-between}.bui-fd-row{flex-direction:row}.bui-fd-column{flex-direction:column}.bui-fd-row-reverse{flex-direction:row-reverse}.bui-fd-column-reverse{flex-direction:column-reverse}@media (width>=640px){.xs\:bui-align-start{align-items:start}.xs\:bui-align-center{align-items:center}.xs\:bui-align-end{align-items:end}.xs\:bui-align-stretch{align-items:stretch}.xs\:bui-jc-start{justify-content:start}.xs\:bui-jc-center{justify-content:center}.xs\:bui-jc-end{justify-content:end}.xs\:bui-jc-between{justify-content:space-between}.xs\:bui-fd-row{flex-direction:row}.xs\:bui-fd-column{flex-direction:column}.xs\:bui-fd-row-reverse{flex-direction:row-reverse}.xs\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=768px){.sm\:bui-align-start{align-items:start}.sm\:bui-align-center{align-items:center}.sm\:bui-align-end{align-items:end}.sm\:bui-align-stretch{align-items:stretch}.sm\:bui-jc-start{justify-content:start}.sm\:bui-jc-center{justify-content:center}.sm\:bui-jc-end{justify-content:end}.sm\:bui-jc-between{justify-content:space-between}.sm\:bui-fd-row{flex-direction:row}.sm\:bui-fd-column{flex-direction:column}.sm\:bui-fd-row-reverse{flex-direction:row-reverse}.sm\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1024px){.md\:bui-align-start{align-items:start}.md\:bui-align-center{align-items:center}.md\:bui-align-end{align-items:end}.md\:bui-align-stretch{align-items:stretch}.md\:bui-jc-start{justify-content:start}.md\:bui-jc-center{justify-content:center}.md\:bui-jc-end{justify-content:end}.md\:bui-jc-between{justify-content:space-between}.md\:bui-fd-row{flex-direction:row}.md\:bui-fd-column{flex-direction:column}.md\:bui-fd-row-reverse{flex-direction:row-reverse}.md\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1280px){.lg\:bui-align-start{align-items:start}.lg\:bui-align-center{align-items:center}.lg\:bui-align-end{align-items:end}.lg\:bui-align-stretch{align-items:stretch}.lg\:bui-jc-start{justify-content:start}.lg\:bui-jc-center{justify-content:center}.lg\:bui-jc-end{justify-content:end}.lg\:bui-jc-between{justify-content:space-between}.lg\:bui-fd-row{flex-direction:row}.lg\:bui-fd-column{flex-direction:column}.lg\:bui-fd-row-reverse{flex-direction:row-reverse}.lg\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1536px){.xl\:bui-align-start{align-items:start}.xl\:bui-align-center{align-items:center}.xl\:bui-align-end{align-items:end}.xl\:bui-align-stretch{align-items:stretch}.xl\:bui-jc-start{justify-content:start}.xl\:bui-jc-center{justify-content:center}.xl\:bui-jc-end{justify-content:end}.xl\:bui-jc-between{justify-content:space-between}.xl\:bui-fd-row{flex-direction:row}.xl\:bui-fd-column{flex-direction:column}.xl\:bui-fd-row-reverse{flex-direction:row-reverse}.xl\:bui-fd-column-reverse{flex-direction:column-reverse}}} \ No newline at end of file +@layer tokens{:root{--bui-font-regular:system-ui;--bui-font-monospace:ui-monospace,"Menlo","Monaco","Consolas","Liberation Mono","Courier New",monospace;--bui-font-weight-regular:400;--bui-font-weight-bold:600;--bui-font-size-1:.625rem;--bui-font-size-2:.75rem;--bui-font-size-3:.875rem;--bui-font-size-4:1rem;--bui-font-size-5:1.25rem;--bui-font-size-6:1.5rem;--bui-font-size-7:2rem;--bui-font-size-8:3rem;--bui-font-size-9:4rem;--bui-font-size-10:5.75rem;--bui-space:.25rem;--bui-space-0_5:calc(var(--bui-space)*.5);--bui-space-1:var(--bui-space);--bui-space-1_5:calc(var(--bui-space)*1.5);--bui-space-2:calc(var(--bui-space)*2);--bui-space-3:calc(var(--bui-space)*3);--bui-space-4:calc(var(--bui-space)*4);--bui-space-5:calc(var(--bui-space)*5);--bui-space-6:calc(var(--bui-space)*6);--bui-space-7:calc(var(--bui-space)*7);--bui-space-8:calc(var(--bui-space)*8);--bui-space-9:calc(var(--bui-space)*9);--bui-space-10:calc(var(--bui-space)*10);--bui-space-11:calc(var(--bui-space)*11);--bui-space-12:calc(var(--bui-space)*12);--bui-space-13:calc(var(--bui-space)*13);--bui-space-14:calc(var(--bui-space)*14);--bui-radius-1:calc(.125rem);--bui-radius-2:calc(.25rem);--bui-radius-3:calc(.5rem);--bui-radius-4:calc(.75rem);--bui-radius-5:calc(1rem);--bui-radius-6:calc(1.25rem);--bui-radius-full:9999px;--bui-black:#000;--bui-white:#fff;--bui-gray-1:#f8f8f8;--bui-gray-2:#ececec;--bui-gray-3:#d9d9d9;--bui-gray-4:#c1c1c1;--bui-gray-5:#9e9e9e;--bui-gray-6:#8c8c8c;--bui-gray-7:#757575;--bui-gray-8:#595959;--bui-bg-surface-0:var(--bui-gray-1);--bui-bg-surface-1:var(--bui-white);--bui-bg-surface-2:var(--bui-gray-1);--bui-bg-surface-3:var(--bui-gray-2);--bui-bg-solid:#1f5493;--bui-bg-solid-hover:#163a66;--bui-bg-solid-pressed:#0f2b4e;--bui-bg-solid-disabled:#163a66;--bui-bg-neutral-on-surface-0:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-0-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-0-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-0-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-1:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-1-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-1-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-1-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-2:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-2-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-2-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-2-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-3:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-3-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-3-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-3-disabled:oklch(0% 0 0/.06);--bui-bg-danger:#feebe7;--bui-bg-warning:#fff2b2;--bui-bg-success:#e6f6eb;--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#1f5493;--bui-fg-link-hover:#1f2d5c;--bui-fg-disabled:#9e9e9e;--bui-fg-solid:var(--bui-white);--bui-fg-solid-disabled:#98a8bc;--bui-fg-tint:#1f5493;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#0000001a;--bui-border-hover:#0003;--bui-border-pressed:#0006;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#a0a0a03b;--bui-scrollbar-thumb:#a0a0a0;--bui-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--bui-animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite}[data-theme-mode=dark]{--bui-gray-1:#191919;--bui-gray-2:#242424;--bui-gray-3:#373737;--bui-gray-4:#464646;--bui-gray-5:#575757;--bui-gray-6:#7b7b7b;--bui-gray-7:#9e9e9e;--bui-gray-8:#b4b4b4;--bui-bg-surface-0:#333;--bui-bg-surface-1:var(--bui-gray-1);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-surface-3:var(--bui-gray-3);--bui-bg-solid:#9cc9ff;--bui-bg-solid-hover:#83b9fd;--bui-bg-solid-pressed:#83b9fd;--bui-bg-solid-disabled:#1b3d68;--bui-bg-neutral-on-surface-0:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-0-hover:oklch(100% 0 0/.14);--bui-bg-neutral-on-surface-0-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-0-disabled:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-1:oklch(100% 0 0/.06);--bui-bg-neutral-on-surface-1-hover:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-1-pressed:oklch(100% 0 0/.16);--bui-bg-neutral-on-surface-1-disabled:oklch(100% 0 0/.06);--bui-bg-neutral-on-surface-2:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-2-hover:oklch(100% 0 0/.12);--bui-bg-neutral-on-surface-2-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-2-disabled:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-3:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-3-hover:oklch(100% 0 0/.12);--bui-bg-neutral-on-surface-3-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-3-disabled:oklch(100% 0 0/.08);--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#9cc9ff;--bui-fg-link-hover:#7eb5f7;--bui-fg-disabled:var(--bui-gray-7);--bui-fg-solid:#101821;--bui-fg-solid-disabled:#6191cc;--bui-fg-tint:#9cc9ff;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#ffffff1f;--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#3636363a;--bui-scrollbar-thumb:#575757;--bui-shadow:none}}@layer base{*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:100%;tab-size:4;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;line-height:1.15}body{margin:0}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{border-color:currentColor}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}:where(a){color:inherit;text-decoration:none}@keyframes pulse{50%{opacity:.5}}body{background-color:var(--bui-bg-surface-0);color:var(--bui-fg-primary);font-family:var(--bui-font-regular);font-weight:var(--bui-font-weight-regular);font-size:var(--bui-font-size-3);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}[data-theme-mode=dark]{color-scheme:dark}[data-theme-mode=light]{color-scheme:light}}@layer components;@layer utilities{.bui-p{padding:var(--p)}.bui-p-0\.5{padding:var(--bui-space-0_5)}.bui-p-1{padding:var(--bui-space-1)}.bui-p-1\.5{padding:var(--bui-space-1_5)}.bui-p-2{padding:var(--bui-space-2)}.bui-p-3{padding:var(--bui-space-3)}.bui-p-4{padding:var(--bui-space-4)}.bui-p-5{padding:var(--bui-space-5)}.bui-p-6{padding:var(--bui-space-6)}.bui-p-7{padding:var(--bui-space-7)}.bui-p-8{padding:var(--bui-space-8)}.bui-p-9{padding:var(--bui-space-9)}.bui-p-10{padding:var(--bui-space-10)}.bui-p-11{padding:var(--bui-space-11)}.bui-p-12{padding:var(--bui-space-12)}.bui-p-13{padding:var(--bui-space-13)}.bui-p-14{padding:var(--bui-space-14)}@media (width>=640px){.xs\:bui-p{padding:var(--p-xs)}.xs\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xs\:bui-p-1{padding:var(--bui-space-1)}.xs\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xs\:bui-p-2{padding:var(--bui-space-2)}.xs\:bui-p-3{padding:var(--bui-space-3)}.xs\:bui-p-4{padding:var(--bui-space-4)}.xs\:bui-p-5{padding:var(--bui-space-5)}.xs\:bui-p-6{padding:var(--bui-space-6)}.xs\:bui-p-7{padding:var(--bui-space-7)}.xs\:bui-p-8{padding:var(--bui-space-8)}.xs\:bui-p-9{padding:var(--bui-space-9)}.xs\:bui-p-10{padding:var(--bui-space-10)}.xs\:bui-p-11{padding:var(--bui-space-11)}.xs\:bui-p-12{padding:var(--bui-space-12)}.xs\:bui-p-13{padding:var(--bui-space-13)}.xs\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-p{padding:var(--p-sm)}.sm\:bui-p-0\.5{padding:var(--bui-space-0_5)}.sm\:bui-p-1{padding:var(--bui-space-1)}.sm\:bui-p-1\.5{padding:var(--bui-space-1_5)}.sm\:bui-p-2{padding:var(--bui-space-2)}.sm\:bui-p-3{padding:var(--bui-space-3)}.sm\:bui-p-4{padding:var(--bui-space-4)}.sm\:bui-p-5{padding:var(--bui-space-5)}.sm\:bui-p-6{padding:var(--bui-space-6)}.sm\:bui-p-7{padding:var(--bui-space-7)}.sm\:bui-p-8{padding:var(--bui-space-8)}.sm\:bui-p-9{padding:var(--bui-space-9)}.sm\:bui-p-10{padding:var(--bui-space-10)}.sm\:bui-p-11{padding:var(--bui-space-11)}.sm\:bui-p-12{padding:var(--bui-space-12)}.sm\:bui-p-13{padding:var(--bui-space-13)}.sm\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-p{padding:var(--p-md)}.md\:bui-p-0\.5{padding:var(--bui-space-0_5)}.md\:bui-p-1{padding:var(--bui-space-1)}.md\:bui-p-1\.5{padding:var(--bui-space-1_5)}.md\:bui-p-2{padding:var(--bui-space-2)}.md\:bui-p-3{padding:var(--bui-space-3)}.md\:bui-p-4{padding:var(--bui-space-4)}.md\:bui-p-5{padding:var(--bui-space-5)}.md\:bui-p-6{padding:var(--bui-space-6)}.md\:bui-p-7{padding:var(--bui-space-7)}.md\:bui-p-8{padding:var(--bui-space-8)}.md\:bui-p-9{padding:var(--bui-space-9)}.md\:bui-p-10{padding:var(--bui-space-10)}.md\:bui-p-11{padding:var(--bui-space-11)}.md\:bui-p-12{padding:var(--bui-space-12)}.md\:bui-p-13{padding:var(--bui-space-13)}.md\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-p{padding:var(--p-lg)}.lg\:bui-p-0\.5{padding:var(--bui-space-0_5)}.lg\:bui-p-1{padding:var(--bui-space-1)}.lg\:bui-p-1\.5{padding:var(--bui-space-1_5)}.lg\:bui-p-2{padding:var(--bui-space-2)}.lg\:bui-p-3{padding:var(--bui-space-3)}.lg\:bui-p-4{padding:var(--bui-space-4)}.lg\:bui-p-5{padding:var(--bui-space-5)}.lg\:bui-p-6{padding:var(--bui-space-6)}.lg\:bui-p-7{padding:var(--bui-space-7)}.lg\:bui-p-8{padding:var(--bui-space-8)}.lg\:bui-p-9{padding:var(--bui-space-9)}.lg\:bui-p-10{padding:var(--bui-space-10)}.lg\:bui-p-11{padding:var(--bui-space-11)}.lg\:bui-p-12{padding:var(--bui-space-12)}.lg\:bui-p-13{padding:var(--bui-space-13)}.lg\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-p{padding:var(--p-xl)}.xl\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xl\:bui-p-1{padding:var(--bui-space-1)}.xl\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xl\:bui-p-2{padding:var(--bui-space-2)}.xl\:bui-p-3{padding:var(--bui-space-3)}.xl\:bui-p-4{padding:var(--bui-space-4)}.xl\:bui-p-5{padding:var(--bui-space-5)}.xl\:bui-p-6{padding:var(--bui-space-6)}.xl\:bui-p-7{padding:var(--bui-space-7)}.xl\:bui-p-8{padding:var(--bui-space-8)}.xl\:bui-p-9{padding:var(--bui-space-9)}.xl\:bui-p-10{padding:var(--bui-space-10)}.xl\:bui-p-11{padding:var(--bui-space-11)}.xl\:bui-p-12{padding:var(--bui-space-12)}.xl\:bui-p-13{padding:var(--bui-space-13)}.xl\:bui-p-14{padding:var(--bui-space-14)}}.bui-pl{padding-left:var(--pl)}.bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.bui-pl-1{padding-left:var(--bui-space-1)}.bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.bui-pl-2{padding-left:var(--bui-space-2)}.bui-pl-3{padding-left:var(--bui-space-3)}.bui-pl-4{padding-left:var(--bui-space-4)}.bui-pl-5{padding-left:var(--bui-space-5)}.bui-pl-6{padding-left:var(--bui-space-6)}.bui-pl-7{padding-left:var(--bui-space-7)}.bui-pl-8{padding-left:var(--bui-space-8)}.bui-pl-9{padding-left:var(--bui-space-9)}.bui-pl-10{padding-left:var(--bui-space-10)}.bui-pl-11{padding-left:var(--bui-space-11)}.bui-pl-12{padding-left:var(--bui-space-12)}.bui-pl-13{padding-left:var(--bui-space-13)}.bui-pl-14{padding-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pl{padding-left:var(--pl-xs)}.xs\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xs\:bui-pl-1{padding-left:var(--bui-space-1)}.xs\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xs\:bui-pl-2{padding-left:var(--bui-space-2)}.xs\:bui-pl-3{padding-left:var(--bui-space-3)}.xs\:bui-pl-4{padding-left:var(--bui-space-4)}.xs\:bui-pl-5{padding-left:var(--bui-space-5)}.xs\:bui-pl-6{padding-left:var(--bui-space-6)}.xs\:bui-pl-7{padding-left:var(--bui-space-7)}.xs\:bui-pl-8{padding-left:var(--bui-space-8)}.xs\:bui-pl-9{padding-left:var(--bui-space-9)}.xs\:bui-pl-10{padding-left:var(--bui-space-10)}.xs\:bui-pl-11{padding-left:var(--bui-space-11)}.xs\:bui-pl-12{padding-left:var(--bui-space-12)}.xs\:bui-pl-13{padding-left:var(--bui-space-13)}.xs\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pl{padding-left:var(--pl-sm)}.sm\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.sm\:bui-pl-1{padding-left:var(--bui-space-1)}.sm\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.sm\:bui-pl-2{padding-left:var(--bui-space-2)}.sm\:bui-pl-3{padding-left:var(--bui-space-3)}.sm\:bui-pl-4{padding-left:var(--bui-space-4)}.sm\:bui-pl-5{padding-left:var(--bui-space-5)}.sm\:bui-pl-6{padding-left:var(--bui-space-6)}.sm\:bui-pl-7{padding-left:var(--bui-space-7)}.sm\:bui-pl-8{padding-left:var(--bui-space-8)}.sm\:bui-pl-9{padding-left:var(--bui-space-9)}.sm\:bui-pl-10{padding-left:var(--bui-space-10)}.sm\:bui-pl-11{padding-left:var(--bui-space-11)}.sm\:bui-pl-12{padding-left:var(--bui-space-12)}.sm\:bui-pl-13{padding-left:var(--bui-space-13)}.sm\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pl{padding-left:var(--pl-md)}.md\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.md\:bui-pl-1{padding-left:var(--bui-space-1)}.md\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.md\:bui-pl-2{padding-left:var(--bui-space-2)}.md\:bui-pl-3{padding-left:var(--bui-space-3)}.md\:bui-pl-4{padding-left:var(--bui-space-4)}.md\:bui-pl-5{padding-left:var(--bui-space-5)}.md\:bui-pl-6{padding-left:var(--bui-space-6)}.md\:bui-pl-7{padding-left:var(--bui-space-7)}.md\:bui-pl-8{padding-left:var(--bui-space-8)}.md\:bui-pl-9{padding-left:var(--bui-space-9)}.md\:bui-pl-10{padding-left:var(--bui-space-10)}.md\:bui-pl-11{padding-left:var(--bui-space-11)}.md\:bui-pl-12{padding-left:var(--bui-space-12)}.md\:bui-pl-13{padding-left:var(--bui-space-13)}.md\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pl{padding-left:var(--pl-lg)}.lg\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.lg\:bui-pl-1{padding-left:var(--bui-space-1)}.lg\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.lg\:bui-pl-2{padding-left:var(--bui-space-2)}.lg\:bui-pl-3{padding-left:var(--bui-space-3)}.lg\:bui-pl-4{padding-left:var(--bui-space-4)}.lg\:bui-pl-5{padding-left:var(--bui-space-5)}.lg\:bui-pl-6{padding-left:var(--bui-space-6)}.lg\:bui-pl-7{padding-left:var(--bui-space-7)}.lg\:bui-pl-8{padding-left:var(--bui-space-8)}.lg\:bui-pl-9{padding-left:var(--bui-space-9)}.lg\:bui-pl-10{padding-left:var(--bui-space-10)}.lg\:bui-pl-11{padding-left:var(--bui-space-11)}.lg\:bui-pl-12{padding-left:var(--bui-space-12)}.lg\:bui-pl-13{padding-left:var(--bui-space-13)}.lg\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pl{padding-left:var(--pl-xl)}.xl\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xl\:bui-pl-1{padding-left:var(--bui-space-1)}.xl\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xl\:bui-pl-2{padding-left:var(--bui-space-2)}.xl\:bui-pl-3{padding-left:var(--bui-space-3)}.xl\:bui-pl-4{padding-left:var(--bui-space-4)}.xl\:bui-pl-5{padding-left:var(--bui-space-5)}.xl\:bui-pl-6{padding-left:var(--bui-space-6)}.xl\:bui-pl-7{padding-left:var(--bui-space-7)}.xl\:bui-pl-8{padding-left:var(--bui-space-8)}.xl\:bui-pl-9{padding-left:var(--bui-space-9)}.xl\:bui-pl-10{padding-left:var(--bui-space-10)}.xl\:bui-pl-11{padding-left:var(--bui-space-11)}.xl\:bui-pl-12{padding-left:var(--bui-space-12)}.xl\:bui-pl-13{padding-left:var(--bui-space-13)}.xl\:bui-pl-14{padding-left:var(--bui-space-14)}}.bui-pr{padding-right:var(--pr)}.bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.bui-pr-1{padding-right:var(--bui-space-1)}.bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.bui-pr-2{padding-right:var(--bui-space-2)}.bui-pr-3{padding-right:var(--bui-space-3)}.bui-pr-4{padding-right:var(--bui-space-4)}.bui-pr-5{padding-right:var(--bui-space-5)}.bui-pr-6{padding-right:var(--bui-space-6)}.bui-pr-7{padding-right:var(--bui-space-7)}.bui-pr-8{padding-right:var(--bui-space-8)}.bui-pr-9{padding-right:var(--bui-space-9)}.bui-pr-10{padding-right:var(--bui-space-10)}.bui-pr-11{padding-right:var(--bui-space-11)}.bui-pr-12{padding-right:var(--bui-space-12)}.bui-pr-13{padding-right:var(--bui-space-13)}.bui-pr-14{padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pr{padding-right:var(--pr-xs)}.xs\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xs\:bui-pr-1{padding-right:var(--bui-space-1)}.xs\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xs\:bui-pr-2{padding-right:var(--bui-space-2)}.xs\:bui-pr-3{padding-right:var(--bui-space-3)}.xs\:bui-pr-4{padding-right:var(--bui-space-4)}.xs\:bui-pr-5{padding-right:var(--bui-space-5)}.xs\:bui-pr-6{padding-right:var(--bui-space-6)}.xs\:bui-pr-7{padding-right:var(--bui-space-7)}.xs\:bui-pr-8{padding-right:var(--bui-space-8)}.xs\:bui-pr-9{padding-right:var(--bui-space-9)}.xs\:bui-pr-10{padding-right:var(--bui-space-10)}.xs\:bui-pr-11{padding-right:var(--bui-space-11)}.xs\:bui-pr-12{padding-right:var(--bui-space-12)}.xs\:bui-pr-13{padding-right:var(--bui-space-13)}.xs\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pr{padding-right:var(--pr-sm)}.sm\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.sm\:bui-pr-1{padding-right:var(--bui-space-1)}.sm\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.sm\:bui-pr-2{padding-right:var(--bui-space-2)}.sm\:bui-pr-3{padding-right:var(--bui-space-3)}.sm\:bui-pr-4{padding-right:var(--bui-space-4)}.sm\:bui-pr-5{padding-right:var(--bui-space-5)}.sm\:bui-pr-6{padding-right:var(--bui-space-6)}.sm\:bui-pr-7{padding-right:var(--bui-space-7)}.sm\:bui-pr-8{padding-right:var(--bui-space-8)}.sm\:bui-pr-9{padding-right:var(--bui-space-9)}.sm\:bui-pr-10{padding-right:var(--bui-space-10)}.sm\:bui-pr-11{padding-right:var(--bui-space-11)}.sm\:bui-pr-12{padding-right:var(--bui-space-12)}.sm\:bui-pr-13{padding-right:var(--bui-space-13)}.sm\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pr{padding-right:var(--pr-md)}.md\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.md\:bui-pr-1{padding-right:var(--bui-space-1)}.md\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.md\:bui-pr-2{padding-right:var(--bui-space-2)}.md\:bui-pr-3{padding-right:var(--bui-space-3)}.md\:bui-pr-4{padding-right:var(--bui-space-4)}.md\:bui-pr-5{padding-right:var(--bui-space-5)}.md\:bui-pr-6{padding-right:var(--bui-space-6)}.md\:bui-pr-7{padding-right:var(--bui-space-7)}.md\:bui-pr-8{padding-right:var(--bui-space-8)}.md\:bui-pr-9{padding-right:var(--bui-space-9)}.md\:bui-pr-10{padding-right:var(--bui-space-10)}.md\:bui-pr-11{padding-right:var(--bui-space-11)}.md\:bui-pr-12{padding-right:var(--bui-space-12)}.md\:bui-pr-13{padding-right:var(--bui-space-13)}.md\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pr{padding-right:var(--pr-lg)}.lg\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.lg\:bui-pr-1{padding-right:var(--bui-space-1)}.lg\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.lg\:bui-pr-2{padding-right:var(--bui-space-2)}.lg\:bui-pr-3{padding-right:var(--bui-space-3)}.lg\:bui-pr-4{padding-right:var(--bui-space-4)}.lg\:bui-pr-5{padding-right:var(--bui-space-5)}.lg\:bui-pr-6{padding-right:var(--bui-space-6)}.lg\:bui-pr-7{padding-right:var(--bui-space-7)}.lg\:bui-pr-8{padding-right:var(--bui-space-8)}.lg\:bui-pr-9{padding-right:var(--bui-space-9)}.lg\:bui-pr-10{padding-right:var(--bui-space-10)}.lg\:bui-pr-11{padding-right:var(--bui-space-11)}.lg\:bui-pr-12{padding-right:var(--bui-space-12)}.lg\:bui-pr-13{padding-right:var(--bui-space-13)}.lg\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pr{padding-right:var(--pr-xl)}.xl\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xl\:bui-pr-1{padding-right:var(--bui-space-1)}.xl\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xl\:bui-pr-2{padding-right:var(--bui-space-2)}.xl\:bui-pr-3{padding-right:var(--bui-space-3)}.xl\:bui-pr-4{padding-right:var(--bui-space-4)}.xl\:bui-pr-5{padding-right:var(--bui-space-5)}.xl\:bui-pr-6{padding-right:var(--bui-space-6)}.xl\:bui-pr-7{padding-right:var(--bui-space-7)}.xl\:bui-pr-8{padding-right:var(--bui-space-8)}.xl\:bui-pr-9{padding-right:var(--bui-space-9)}.xl\:bui-pr-10{padding-right:var(--bui-space-10)}.xl\:bui-pr-11{padding-right:var(--bui-space-11)}.xl\:bui-pr-12{padding-right:var(--bui-space-12)}.xl\:bui-pr-13{padding-right:var(--bui-space-13)}.xl\:bui-pr-14{padding-right:var(--bui-space-14)}}.bui-pt{padding-top:var(--pt)}.bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.bui-pt-1{padding-top:var(--bui-space-1)}.bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.bui-pt-2{padding-top:var(--bui-space-2)}.bui-pt-3{padding-top:var(--bui-space-3)}.bui-pt-4{padding-top:var(--bui-space-4)}.bui-pt-5{padding-top:var(--bui-space-5)}.bui-pt-6{padding-top:var(--bui-space-6)}.bui-pt-7{padding-top:var(--bui-space-7)}.bui-pt-8{padding-top:var(--bui-space-8)}.bui-pt-9{padding-top:var(--bui-space-9)}.bui-pt-10{padding-top:var(--bui-space-10)}.bui-pt-11{padding-top:var(--bui-space-11)}.bui-pt-12{padding-top:var(--bui-space-12)}.bui-pt-13{padding-top:var(--bui-space-13)}.bui-pt-14{padding-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pt{padding-top:var(--pt-xs)}.xs\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xs\:bui-pt-1{padding-top:var(--bui-space-1)}.xs\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xs\:bui-pt-2{padding-top:var(--bui-space-2)}.xs\:bui-pt-3{padding-top:var(--bui-space-3)}.xs\:bui-pt-4{padding-top:var(--bui-space-4)}.xs\:bui-pt-5{padding-top:var(--bui-space-5)}.xs\:bui-pt-6{padding-top:var(--bui-space-6)}.xs\:bui-pt-7{padding-top:var(--bui-space-7)}.xs\:bui-pt-8{padding-top:var(--bui-space-8)}.xs\:bui-pt-9{padding-top:var(--bui-space-9)}.xs\:bui-pt-10{padding-top:var(--bui-space-10)}.xs\:bui-pt-11{padding-top:var(--bui-space-11)}.xs\:bui-pt-12{padding-top:var(--bui-space-12)}.xs\:bui-pt-13{padding-top:var(--bui-space-13)}.xs\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pt{padding-top:var(--pt-sm)}.sm\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.sm\:bui-pt-1{padding-top:var(--bui-space-1)}.sm\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.sm\:bui-pt-2{padding-top:var(--bui-space-2)}.sm\:bui-pt-3{padding-top:var(--bui-space-3)}.sm\:bui-pt-4{padding-top:var(--bui-space-4)}.sm\:bui-pt-5{padding-top:var(--bui-space-5)}.sm\:bui-pt-6{padding-top:var(--bui-space-6)}.sm\:bui-pt-7{padding-top:var(--bui-space-7)}.sm\:bui-pt-8{padding-top:var(--bui-space-8)}.sm\:bui-pt-9{padding-top:var(--bui-space-9)}.sm\:bui-pt-10{padding-top:var(--bui-space-10)}.sm\:bui-pt-11{padding-top:var(--bui-space-11)}.sm\:bui-pt-12{padding-top:var(--bui-space-12)}.sm\:bui-pt-13{padding-top:var(--bui-space-13)}.sm\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pt{padding-top:var(--pt-md)}.md\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.md\:bui-pt-1{padding-top:var(--bui-space-1)}.md\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.md\:bui-pt-2{padding-top:var(--bui-space-2)}.md\:bui-pt-3{padding-top:var(--bui-space-3)}.md\:bui-pt-4{padding-top:var(--bui-space-4)}.md\:bui-pt-5{padding-top:var(--bui-space-5)}.md\:bui-pt-6{padding-top:var(--bui-space-6)}.md\:bui-pt-7{padding-top:var(--bui-space-7)}.md\:bui-pt-8{padding-top:var(--bui-space-8)}.md\:bui-pt-9{padding-top:var(--bui-space-9)}.md\:bui-pt-10{padding-top:var(--bui-space-10)}.md\:bui-pt-11{padding-top:var(--bui-space-11)}.md\:bui-pt-12{padding-top:var(--bui-space-12)}.md\:bui-pt-13{padding-top:var(--bui-space-13)}.md\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pt{padding-top:var(--pt-lg)}.lg\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.lg\:bui-pt-1{padding-top:var(--bui-space-1)}.lg\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.lg\:bui-pt-2{padding-top:var(--bui-space-2)}.lg\:bui-pt-3{padding-top:var(--bui-space-3)}.lg\:bui-pt-4{padding-top:var(--bui-space-4)}.lg\:bui-pt-5{padding-top:var(--bui-space-5)}.lg\:bui-pt-6{padding-top:var(--bui-space-6)}.lg\:bui-pt-7{padding-top:var(--bui-space-7)}.lg\:bui-pt-8{padding-top:var(--bui-space-8)}.lg\:bui-pt-9{padding-top:var(--bui-space-9)}.lg\:bui-pt-10{padding-top:var(--bui-space-10)}.lg\:bui-pt-11{padding-top:var(--bui-space-11)}.lg\:bui-pt-12{padding-top:var(--bui-space-12)}.lg\:bui-pt-13{padding-top:var(--bui-space-13)}.lg\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pt{padding-top:var(--pt-xl)}.xl\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xl\:bui-pt-1{padding-top:var(--bui-space-1)}.xl\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xl\:bui-pt-2{padding-top:var(--bui-space-2)}.xl\:bui-pt-3{padding-top:var(--bui-space-3)}.xl\:bui-pt-4{padding-top:var(--bui-space-4)}.xl\:bui-pt-5{padding-top:var(--bui-space-5)}.xl\:bui-pt-6{padding-top:var(--bui-space-6)}.xl\:bui-pt-7{padding-top:var(--bui-space-7)}.xl\:bui-pt-8{padding-top:var(--bui-space-8)}.xl\:bui-pt-9{padding-top:var(--bui-space-9)}.xl\:bui-pt-10{padding-top:var(--bui-space-10)}.xl\:bui-pt-11{padding-top:var(--bui-space-11)}.xl\:bui-pt-12{padding-top:var(--bui-space-12)}.xl\:bui-pt-13{padding-top:var(--bui-space-13)}.xl\:bui-pt-14{padding-top:var(--bui-space-14)}}.bui-pb{padding-bottom:var(--pb)}.bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.bui-pb-1{padding-bottom:var(--bui-space-1)}.bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.bui-pb-2{padding-bottom:var(--bui-space-2)}.bui-pb-3{padding-bottom:var(--bui-space-3)}.bui-pb-4{padding-bottom:var(--bui-space-4)}.bui-pb-5{padding-bottom:var(--bui-space-5)}.bui-pb-6{padding-bottom:var(--bui-space-6)}.bui-pb-7{padding-bottom:var(--bui-space-7)}.bui-pb-8{padding-bottom:var(--bui-space-8)}.bui-pb-9{padding-bottom:var(--bui-space-9)}.bui-pb-10{padding-bottom:var(--bui-space-10)}.bui-pb-11{padding-bottom:var(--bui-space-11)}.bui-pb-12{padding-bottom:var(--bui-space-12)}.bui-pb-13{padding-bottom:var(--bui-space-13)}.bui-pb-14{padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pb{padding-bottom:var(--pb-xs)}.xs\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xs\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xs\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xs\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xs\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xs\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xs\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xs\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xs\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xs\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xs\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xs\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xs\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xs\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xs\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xs\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pb{padding-bottom:var(--pb-sm)}.sm\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.sm\:bui-pb-1{padding-bottom:var(--bui-space-1)}.sm\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.sm\:bui-pb-2{padding-bottom:var(--bui-space-2)}.sm\:bui-pb-3{padding-bottom:var(--bui-space-3)}.sm\:bui-pb-4{padding-bottom:var(--bui-space-4)}.sm\:bui-pb-5{padding-bottom:var(--bui-space-5)}.sm\:bui-pb-6{padding-bottom:var(--bui-space-6)}.sm\:bui-pb-7{padding-bottom:var(--bui-space-7)}.sm\:bui-pb-8{padding-bottom:var(--bui-space-8)}.sm\:bui-pb-9{padding-bottom:var(--bui-space-9)}.sm\:bui-pb-10{padding-bottom:var(--bui-space-10)}.sm\:bui-pb-11{padding-bottom:var(--bui-space-11)}.sm\:bui-pb-12{padding-bottom:var(--bui-space-12)}.sm\:bui-pb-13{padding-bottom:var(--bui-space-13)}.sm\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pb{padding-bottom:var(--pb-md)}.md\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.md\:bui-pb-1{padding-bottom:var(--bui-space-1)}.md\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.md\:bui-pb-2{padding-bottom:var(--bui-space-2)}.md\:bui-pb-3{padding-bottom:var(--bui-space-3)}.md\:bui-pb-4{padding-bottom:var(--bui-space-4)}.md\:bui-pb-5{padding-bottom:var(--bui-space-5)}.md\:bui-pb-6{padding-bottom:var(--bui-space-6)}.md\:bui-pb-7{padding-bottom:var(--bui-space-7)}.md\:bui-pb-8{padding-bottom:var(--bui-space-8)}.md\:bui-pb-9{padding-bottom:var(--bui-space-9)}.md\:bui-pb-10{padding-bottom:var(--bui-space-10)}.md\:bui-pb-11{padding-bottom:var(--bui-space-11)}.md\:bui-pb-12{padding-bottom:var(--bui-space-12)}.md\:bui-pb-13{padding-bottom:var(--bui-space-13)}.md\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pb{padding-bottom:var(--pb-lg)}.lg\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.lg\:bui-pb-1{padding-bottom:var(--bui-space-1)}.lg\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.lg\:bui-pb-2{padding-bottom:var(--bui-space-2)}.lg\:bui-pb-3{padding-bottom:var(--bui-space-3)}.lg\:bui-pb-4{padding-bottom:var(--bui-space-4)}.lg\:bui-pb-5{padding-bottom:var(--bui-space-5)}.lg\:bui-pb-6{padding-bottom:var(--bui-space-6)}.lg\:bui-pb-7{padding-bottom:var(--bui-space-7)}.lg\:bui-pb-8{padding-bottom:var(--bui-space-8)}.lg\:bui-pb-9{padding-bottom:var(--bui-space-9)}.lg\:bui-pb-10{padding-bottom:var(--bui-space-10)}.lg\:bui-pb-11{padding-bottom:var(--bui-space-11)}.lg\:bui-pb-12{padding-bottom:var(--bui-space-12)}.lg\:bui-pb-13{padding-bottom:var(--bui-space-13)}.lg\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pb{padding-bottom:var(--pb-xl)}.xl\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xl\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xl\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xl\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xl\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xl\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xl\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xl\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xl\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xl\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xl\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xl\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xl\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xl\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xl\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xl\:bui-pb-14{padding-bottom:var(--bui-space-14)}}.bui-py{padding-top:var(--py);padding-bottom:var(--py)}.bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-py{padding-top:var(--py-xs);padding-bottom:var(--py-xs)}.xs\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xs\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xs\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xs\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xs\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xs\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xs\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xs\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xs\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xs\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xs\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xs\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xs\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xs\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xs\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xs\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-py{padding-top:var(--py-sm);padding-bottom:var(--py-sm)}.sm\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.sm\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.sm\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.sm\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.sm\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.sm\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.sm\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.sm\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.sm\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.sm\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.sm\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.sm\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.sm\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.sm\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.sm\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.sm\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-py{padding-top:var(--py-md);padding-bottom:var(--py-md)}.md\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.md\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.md\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.md\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.md\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.md\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.md\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.md\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.md\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.md\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.md\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.md\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.md\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.md\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.md\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.md\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-py{padding-top:var(--py-lg);padding-bottom:var(--py-lg)}.lg\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.lg\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.lg\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.lg\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.lg\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.lg\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.lg\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.lg\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.lg\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.lg\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.lg\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.lg\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.lg\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.lg\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.lg\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.lg\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-py{padding-top:var(--py-xl);padding-bottom:var(--py-xl)}.xl\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xl\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xl\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xl\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xl\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xl\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xl\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xl\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xl\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xl\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xl\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xl\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xl\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xl\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xl\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xl\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}.bui-px{padding-left:var(--px);padding-right:var(--px)}.bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-px{padding-left:var(--px-xs);padding-right:var(--px-xs)}.xs\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xs\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xs\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xs\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xs\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xs\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xs\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xs\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xs\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xs\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xs\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xs\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xs\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xs\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xs\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xs\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-px{padding-left:var(--px-sm);padding-right:var(--px-sm)}.sm\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.sm\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.sm\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.sm\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.sm\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.sm\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.sm\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.sm\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.sm\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.sm\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.sm\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.sm\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.sm\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.sm\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.sm\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.sm\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-px{padding-left:var(--px-md);padding-right:var(--px-md)}.md\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.md\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.md\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.md\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.md\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.md\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.md\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.md\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.md\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.md\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.md\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.md\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.md\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.md\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.md\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.md\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-px{padding-left:var(--px-lg);padding-right:var(--px-lg)}.lg\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.lg\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.lg\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.lg\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.lg\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.lg\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.lg\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.lg\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.lg\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.lg\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.lg\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.lg\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.lg\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.lg\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.lg\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.lg\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-px{padding-left:var(--px-xl);padding-right:var(--px-xl)}.xl\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xl\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xl\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xl\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xl\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xl\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xl\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xl\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xl\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xl\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xl\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xl\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xl\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xl\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xl\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xl\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}.bui-m{margin:var(--m)}.bui-m-0\.5{margin:var(--bui-space-0_5)}.bui-m-1{margin:var(--bui-space-1)}.bui-m-1\.5{margin:var(--bui-space-1_5)}.bui-m-2{margin:var(--bui-space-2)}.bui-m-3{margin:var(--bui-space-3)}.bui-m-4{margin:var(--bui-space-4)}.bui-m-5{margin:var(--bui-space-5)}.bui-m-6{margin:var(--bui-space-6)}.bui-m-7{margin:var(--bui-space-7)}.bui-m-8{margin:var(--bui-space-8)}.bui-m-9{margin:var(--bui-space-9)}.bui-m-10{margin:var(--bui-space-10)}.bui-m-11{margin:var(--bui-space-11)}.bui-m-12{margin:var(--bui-space-12)}.bui-m-13{margin:var(--bui-space-13)}.bui-m-14{margin:var(--bui-space-14)}@media (width>=640px){.xs\:bui-m{margin:var(--p-xs)}.xs\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xs\:bui-m-1{margin:var(--bui-space-1)}.xs\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xs\:bui-m-2{margin:var(--bui-space-2)}.xs\:bui-m-3{margin:var(--bui-space-3)}.xs\:bui-m-4{margin:var(--bui-space-4)}.xs\:bui-m-5{margin:var(--bui-space-5)}.xs\:bui-m-6{margin:var(--bui-space-6)}.xs\:bui-m-7{margin:var(--bui-space-7)}.xs\:bui-m-8{margin:var(--bui-space-8)}.xs\:bui-m-9{margin:var(--bui-space-9)}.xs\:bui-m-10{margin:var(--bui-space-10)}.xs\:bui-m-11{margin:var(--bui-space-11)}.xs\:bui-m-12{margin:var(--bui-space-12)}.xs\:bui-m-13{margin:var(--bui-space-13)}.xs\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-m{margin:var(--p-sm)}.sm\:bui-m-0\.5{margin:var(--bui-space-0_5)}.sm\:bui-m-1{margin:var(--bui-space-1)}.sm\:bui-m-1\.5{margin:var(--bui-space-1_5)}.sm\:bui-m-2{margin:var(--bui-space-2)}.sm\:bui-m-3{margin:var(--bui-space-3)}.sm\:bui-m-4{margin:var(--bui-space-4)}.sm\:bui-m-5{margin:var(--bui-space-5)}.sm\:bui-m-6{margin:var(--bui-space-6)}.sm\:bui-m-7{margin:var(--bui-space-7)}.sm\:bui-m-8{margin:var(--bui-space-8)}.sm\:bui-m-9{margin:var(--bui-space-9)}.sm\:bui-m-10{margin:var(--bui-space-10)}.sm\:bui-m-11{margin:var(--bui-space-11)}.sm\:bui-m-12{margin:var(--bui-space-12)}.sm\:bui-m-13{margin:var(--bui-space-13)}.sm\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-m{margin:var(--p-md)}.md\:bui-m-0\.5{margin:var(--bui-space-0_5)}.md\:bui-m-1{margin:var(--bui-space-1)}.md\:bui-m-1\.5{margin:var(--bui-space-1_5)}.md\:bui-m-2{margin:var(--bui-space-2)}.md\:bui-m-3{margin:var(--bui-space-3)}.md\:bui-m-4{margin:var(--bui-space-4)}.md\:bui-m-5{margin:var(--bui-space-5)}.md\:bui-m-6{margin:var(--bui-space-6)}.md\:bui-m-7{margin:var(--bui-space-7)}.md\:bui-m-8{margin:var(--bui-space-8)}.md\:bui-m-9{margin:var(--bui-space-9)}.md\:bui-m-10{margin:var(--bui-space-10)}.md\:bui-m-11{margin:var(--bui-space-11)}.md\:bui-m-12{margin:var(--bui-space-12)}.md\:bui-m-13{margin:var(--bui-space-13)}.md\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-m{margin:var(--p-lg)}.lg\:bui-m-0\.5{margin:var(--bui-space-0_5)}.lg\:bui-m-1{margin:var(--bui-space-1)}.lg\:bui-m-1\.5{margin:var(--bui-space-1_5)}.lg\:bui-m-2{margin:var(--bui-space-2)}.lg\:bui-m-3{margin:var(--bui-space-3)}.lg\:bui-m-4{margin:var(--bui-space-4)}.lg\:bui-m-5{margin:var(--bui-space-5)}.lg\:bui-m-6{margin:var(--bui-space-6)}.lg\:bui-m-7{margin:var(--bui-space-7)}.lg\:bui-m-8{margin:var(--bui-space-8)}.lg\:bui-m-9{margin:var(--bui-space-9)}.lg\:bui-m-10{margin:var(--bui-space-10)}.lg\:bui-m-11{margin:var(--bui-space-11)}.lg\:bui-m-12{margin:var(--bui-space-12)}.lg\:bui-m-13{margin:var(--bui-space-13)}.lg\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-m{margin:var(--p-xl)}.xl\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xl\:bui-m-1{margin:var(--bui-space-1)}.xl\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xl\:bui-m-2{margin:var(--bui-space-2)}.xl\:bui-m-3{margin:var(--bui-space-3)}.xl\:bui-m-4{margin:var(--bui-space-4)}.xl\:bui-m-5{margin:var(--bui-space-5)}.xl\:bui-m-6{margin:var(--bui-space-6)}.xl\:bui-m-7{margin:var(--bui-space-7)}.xl\:bui-m-8{margin:var(--bui-space-8)}.xl\:bui-m-9{margin:var(--bui-space-9)}.xl\:bui-m-10{margin:var(--bui-space-10)}.xl\:bui-m-11{margin:var(--bui-space-11)}.xl\:bui-m-12{margin:var(--bui-space-12)}.xl\:bui-m-13{margin:var(--bui-space-13)}.xl\:bui-m-14{margin:var(--bui-space-14)}}.bui-ml{margin-left:var(--ml)}.bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.bui-ml-1{margin-left:var(--bui-space-1)}.bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.bui-ml-2{margin-left:var(--bui-space-2)}.bui-ml-3{margin-left:var(--bui-space-3)}.bui-ml-4{margin-left:var(--bui-space-4)}.bui-ml-5{margin-left:var(--bui-space-5)}.bui-ml-6{margin-left:var(--bui-space-6)}.bui-ml-7{margin-left:var(--bui-space-7)}.bui-ml-8{margin-left:var(--bui-space-8)}.bui-ml-9{margin-left:var(--bui-space-9)}.bui-ml-10{margin-left:var(--bui-space-10)}.bui-ml-11{margin-left:var(--bui-space-11)}.bui-ml-12{margin-left:var(--bui-space-12)}.bui-ml-13{margin-left:var(--bui-space-13)}.bui-ml-14{margin-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-ml{margin-left:var(--ml-xs)}.xs\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xs\:bui-ml-1{margin-left:var(--bui-space-1)}.xs\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xs\:bui-ml-2{margin-left:var(--bui-space-2)}.xs\:bui-ml-3{margin-left:var(--bui-space-3)}.xs\:bui-ml-4{margin-left:var(--bui-space-4)}.xs\:bui-ml-5{margin-left:var(--bui-space-5)}.xs\:bui-ml-6{margin-left:var(--bui-space-6)}.xs\:bui-ml-7{margin-left:var(--bui-space-7)}.xs\:bui-ml-8{margin-left:var(--bui-space-8)}.xs\:bui-ml-9{margin-left:var(--bui-space-9)}.xs\:bui-ml-10{margin-left:var(--bui-space-10)}.xs\:bui-ml-11{margin-left:var(--bui-space-11)}.xs\:bui-ml-12{margin-left:var(--bui-space-12)}.xs\:bui-ml-13{margin-left:var(--bui-space-13)}.xs\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-ml{margin-left:var(--ml-sm)}.sm\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.sm\:bui-ml-1{margin-left:var(--bui-space-1)}.sm\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.sm\:bui-ml-2{margin-left:var(--bui-space-2)}.sm\:bui-ml-3{margin-left:var(--bui-space-3)}.sm\:bui-ml-4{margin-left:var(--bui-space-4)}.sm\:bui-ml-5{margin-left:var(--bui-space-5)}.sm\:bui-ml-6{margin-left:var(--bui-space-6)}.sm\:bui-ml-7{margin-left:var(--bui-space-7)}.sm\:bui-ml-8{margin-left:var(--bui-space-8)}.sm\:bui-ml-9{margin-left:var(--bui-space-9)}.sm\:bui-ml-10{margin-left:var(--bui-space-10)}.sm\:bui-ml-11{margin-left:var(--bui-space-11)}.sm\:bui-ml-12{margin-left:var(--bui-space-12)}.sm\:bui-ml-13{margin-left:var(--bui-space-13)}.sm\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-ml{margin-left:var(--ml-md)}.md\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.md\:bui-ml-1{margin-left:var(--bui-space-1)}.md\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.md\:bui-ml-2{margin-left:var(--bui-space-2)}.md\:bui-ml-3{margin-left:var(--bui-space-3)}.md\:bui-ml-4{margin-left:var(--bui-space-4)}.md\:bui-ml-5{margin-left:var(--bui-space-5)}.md\:bui-ml-6{margin-left:var(--bui-space-6)}.md\:bui-ml-7{margin-left:var(--bui-space-7)}.md\:bui-ml-8{margin-left:var(--bui-space-8)}.md\:bui-ml-9{margin-left:var(--bui-space-9)}.md\:bui-ml-10{margin-left:var(--bui-space-10)}.md\:bui-ml-11{margin-left:var(--bui-space-11)}.md\:bui-ml-12{margin-left:var(--bui-space-12)}.md\:bui-ml-13{margin-left:var(--bui-space-13)}.md\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-ml{margin-left:var(--ml-lg)}.lg\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.lg\:bui-ml-1{margin-left:var(--bui-space-1)}.lg\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.lg\:bui-ml-2{margin-left:var(--bui-space-2)}.lg\:bui-ml-3{margin-left:var(--bui-space-3)}.lg\:bui-ml-4{margin-left:var(--bui-space-4)}.lg\:bui-ml-5{margin-left:var(--bui-space-5)}.lg\:bui-ml-6{margin-left:var(--bui-space-6)}.lg\:bui-ml-7{margin-left:var(--bui-space-7)}.lg\:bui-ml-8{margin-left:var(--bui-space-8)}.lg\:bui-ml-9{margin-left:var(--bui-space-9)}.lg\:bui-ml-10{margin-left:var(--bui-space-10)}.lg\:bui-ml-11{margin-left:var(--bui-space-11)}.lg\:bui-ml-12{margin-left:var(--bui-space-12)}.lg\:bui-ml-13{margin-left:var(--bui-space-13)}.lg\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-ml{margin-left:var(--ml-xl)}.xl\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xl\:bui-ml-1{margin-left:var(--bui-space-1)}.xl\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xl\:bui-ml-2{margin-left:var(--bui-space-2)}.xl\:bui-ml-3{margin-left:var(--bui-space-3)}.xl\:bui-ml-4{margin-left:var(--bui-space-4)}.xl\:bui-ml-5{margin-left:var(--bui-space-5)}.xl\:bui-ml-6{margin-left:var(--bui-space-6)}.xl\:bui-ml-7{margin-left:var(--bui-space-7)}.xl\:bui-ml-8{margin-left:var(--bui-space-8)}.xl\:bui-ml-9{margin-left:var(--bui-space-9)}.xl\:bui-ml-10{margin-left:var(--bui-space-10)}.xl\:bui-ml-11{margin-left:var(--bui-space-11)}.xl\:bui-ml-12{margin-left:var(--bui-space-12)}.xl\:bui-ml-13{margin-left:var(--bui-space-13)}.xl\:bui-ml-14{margin-left:var(--bui-space-14)}}.bui-mr{margin-right:var(--mr)}.bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.bui-mr-1{margin-right:var(--bui-space-1)}.bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.bui-mr-2{margin-right:var(--bui-space-2)}.bui-mr-3{margin-right:var(--bui-space-3)}.bui-mr-4{margin-right:var(--bui-space-4)}.bui-mr-5{margin-right:var(--bui-space-5)}.bui-mr-6{margin-right:var(--bui-space-6)}.bui-mr-7{margin-right:var(--bui-space-7)}.bui-mr-8{margin-right:var(--bui-space-8)}.bui-mr-9{margin-right:var(--bui-space-9)}.bui-mr-10{margin-right:var(--bui-space-10)}.bui-mr-11{margin-right:var(--bui-space-11)}.bui-mr-12{margin-right:var(--bui-space-12)}.bui-mr-13{margin-right:var(--bui-space-13)}.bui-mr-14{margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mr{margin-right:var(--mr-xs)}.xs\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xs\:bui-mr-1{margin-right:var(--bui-space-1)}.xs\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xs\:bui-mr-2{margin-right:var(--bui-space-2)}.xs\:bui-mr-3{margin-right:var(--bui-space-3)}.xs\:bui-mr-4{margin-right:var(--bui-space-4)}.xs\:bui-mr-5{margin-right:var(--bui-space-5)}.xs\:bui-mr-6{margin-right:var(--bui-space-6)}.xs\:bui-mr-7{margin-right:var(--bui-space-7)}.xs\:bui-mr-8{margin-right:var(--bui-space-8)}.xs\:bui-mr-9{margin-right:var(--bui-space-9)}.xs\:bui-mr-10{margin-right:var(--bui-space-10)}.xs\:bui-mr-11{margin-right:var(--bui-space-11)}.xs\:bui-mr-12{margin-right:var(--bui-space-12)}.xs\:bui-mr-13{margin-right:var(--bui-space-13)}.xs\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mr{margin-right:var(--mr-sm)}.sm\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.sm\:bui-mr-1{margin-right:var(--bui-space-1)}.sm\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.sm\:bui-mr-2{margin-right:var(--bui-space-2)}.sm\:bui-mr-3{margin-right:var(--bui-space-3)}.sm\:bui-mr-4{margin-right:var(--bui-space-4)}.sm\:bui-mr-5{margin-right:var(--bui-space-5)}.sm\:bui-mr-6{margin-right:var(--bui-space-6)}.sm\:bui-mr-7{margin-right:var(--bui-space-7)}.sm\:bui-mr-8{margin-right:var(--bui-space-8)}.sm\:bui-mr-9{margin-right:var(--bui-space-9)}.sm\:bui-mr-10{margin-right:var(--bui-space-10)}.sm\:bui-mr-11{margin-right:var(--bui-space-11)}.sm\:bui-mr-12{margin-right:var(--bui-space-12)}.sm\:bui-mr-13{margin-right:var(--bui-space-13)}.sm\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mr{margin-right:var(--mr-md)}.md\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.md\:bui-mr-1{margin-right:var(--bui-space-1)}.md\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.md\:bui-mr-2{margin-right:var(--bui-space-2)}.md\:bui-mr-3{margin-right:var(--bui-space-3)}.md\:bui-mr-4{margin-right:var(--bui-space-4)}.md\:bui-mr-5{margin-right:var(--bui-space-5)}.md\:bui-mr-6{margin-right:var(--bui-space-6)}.md\:bui-mr-7{margin-right:var(--bui-space-7)}.md\:bui-mr-8{margin-right:var(--bui-space-8)}.md\:bui-mr-9{margin-right:var(--bui-space-9)}.md\:bui-mr-10{margin-right:var(--bui-space-10)}.md\:bui-mr-11{margin-right:var(--bui-space-11)}.md\:bui-mr-12{margin-right:var(--bui-space-12)}.md\:bui-mr-13{margin-right:var(--bui-space-13)}.md\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mr{margin-right:var(--mr-lg)}.lg\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.lg\:bui-mr-1{margin-right:var(--bui-space-1)}.lg\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.lg\:bui-mr-2{margin-right:var(--bui-space-2)}.lg\:bui-mr-3{margin-right:var(--bui-space-3)}.lg\:bui-mr-4{margin-right:var(--bui-space-4)}.lg\:bui-mr-5{margin-right:var(--bui-space-5)}.lg\:bui-mr-6{margin-right:var(--bui-space-6)}.lg\:bui-mr-7{margin-right:var(--bui-space-7)}.lg\:bui-mr-8{margin-right:var(--bui-space-8)}.lg\:bui-mr-9{margin-right:var(--bui-space-9)}.lg\:bui-mr-10{margin-right:var(--bui-space-10)}.lg\:bui-mr-11{margin-right:var(--bui-space-11)}.lg\:bui-mr-12{margin-right:var(--bui-space-12)}.lg\:bui-mr-13{margin-right:var(--bui-space-13)}.lg\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mr{margin-right:var(--mr-xl)}.xl\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xl\:bui-mr-1{margin-right:var(--bui-space-1)}.xl\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xl\:bui-mr-2{margin-right:var(--bui-space-2)}.xl\:bui-mr-3{margin-right:var(--bui-space-3)}.xl\:bui-mr-4{margin-right:var(--bui-space-4)}.xl\:bui-mr-5{margin-right:var(--bui-space-5)}.xl\:bui-mr-6{margin-right:var(--bui-space-6)}.xl\:bui-mr-7{margin-right:var(--bui-space-7)}.xl\:bui-mr-8{margin-right:var(--bui-space-8)}.xl\:bui-mr-9{margin-right:var(--bui-space-9)}.xl\:bui-mr-10{margin-right:var(--bui-space-10)}.xl\:bui-mr-11{margin-right:var(--bui-space-11)}.xl\:bui-mr-12{margin-right:var(--bui-space-12)}.xl\:bui-mr-13{margin-right:var(--bui-space-13)}.xl\:bui-mr-14{margin-right:var(--bui-space-14)}}.bui-mt{margin-top:var(--mt)}.bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.bui-mt-1{margin-top:var(--bui-space-1)}.bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.bui-mt-2{margin-top:var(--bui-space-2)}.bui-mt-3{margin-top:var(--bui-space-3)}.bui-mt-4{margin-top:var(--bui-space-4)}.bui-mt-5{margin-top:var(--bui-space-5)}.bui-mt-6{margin-top:var(--bui-space-6)}.bui-mt-7{margin-top:var(--bui-space-7)}.bui-mt-8{margin-top:var(--bui-space-8)}.bui-mt-9{margin-top:var(--bui-space-9)}.bui-mt-10{margin-top:var(--bui-space-10)}.bui-mt-11{margin-top:var(--bui-space-11)}.bui-mt-12{margin-top:var(--bui-space-12)}.bui-mt-13{margin-top:var(--bui-space-13)}.bui-mt-14{margin-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mt{margin-top:var(--mt-xs)}.xs\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xs\:bui-mt-1{margin-top:var(--bui-space-1)}.xs\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xs\:bui-mt-2{margin-top:var(--bui-space-2)}.xs\:bui-mt-3{margin-top:var(--bui-space-3)}.xs\:bui-mt-4{margin-top:var(--bui-space-4)}.xs\:bui-mt-5{margin-top:var(--bui-space-5)}.xs\:bui-mt-6{margin-top:var(--bui-space-6)}.xs\:bui-mt-7{margin-top:var(--bui-space-7)}.xs\:bui-mt-8{margin-top:var(--bui-space-8)}.xs\:bui-mt-9{margin-top:var(--bui-space-9)}.xs\:bui-mt-10{margin-top:var(--bui-space-10)}.xs\:bui-mt-11{margin-top:var(--bui-space-11)}.xs\:bui-mt-12{margin-top:var(--bui-space-12)}.xs\:bui-mt-13{margin-top:var(--bui-space-13)}.xs\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mt{margin-top:var(--mt-sm)}.sm\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.sm\:bui-mt-1{margin-top:var(--bui-space-1)}.sm\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.sm\:bui-mt-2{margin-top:var(--bui-space-2)}.sm\:bui-mt-3{margin-top:var(--bui-space-3)}.sm\:bui-mt-4{margin-top:var(--bui-space-4)}.sm\:bui-mt-5{margin-top:var(--bui-space-5)}.sm\:bui-mt-6{margin-top:var(--bui-space-6)}.sm\:bui-mt-7{margin-top:var(--bui-space-7)}.sm\:bui-mt-8{margin-top:var(--bui-space-8)}.sm\:bui-mt-9{margin-top:var(--bui-space-9)}.sm\:bui-mt-10{margin-top:var(--bui-space-10)}.sm\:bui-mt-11{margin-top:var(--bui-space-11)}.sm\:bui-mt-12{margin-top:var(--bui-space-12)}.sm\:bui-mt-13{margin-top:var(--bui-space-13)}.sm\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mt{margin-top:var(--mt-md)}.md\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.md\:bui-mt-1{margin-top:var(--bui-space-1)}.md\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.md\:bui-mt-2{margin-top:var(--bui-space-2)}.md\:bui-mt-3{margin-top:var(--bui-space-3)}.md\:bui-mt-4{margin-top:var(--bui-space-4)}.md\:bui-mt-5{margin-top:var(--bui-space-5)}.md\:bui-mt-6{margin-top:var(--bui-space-6)}.md\:bui-mt-7{margin-top:var(--bui-space-7)}.md\:bui-mt-8{margin-top:var(--bui-space-8)}.md\:bui-mt-9{margin-top:var(--bui-space-9)}.md\:bui-mt-10{margin-top:var(--bui-space-10)}.md\:bui-mt-11{margin-top:var(--bui-space-11)}.md\:bui-mt-12{margin-top:var(--bui-space-12)}.md\:bui-mt-13{margin-top:var(--bui-space-13)}.md\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mt{margin-top:var(--mt-lg)}.lg\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.lg\:bui-mt-1{margin-top:var(--bui-space-1)}.lg\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.lg\:bui-mt-2{margin-top:var(--bui-space-2)}.lg\:bui-mt-3{margin-top:var(--bui-space-3)}.lg\:bui-mt-4{margin-top:var(--bui-space-4)}.lg\:bui-mt-5{margin-top:var(--bui-space-5)}.lg\:bui-mt-6{margin-top:var(--bui-space-6)}.lg\:bui-mt-7{margin-top:var(--bui-space-7)}.lg\:bui-mt-8{margin-top:var(--bui-space-8)}.lg\:bui-mt-9{margin-top:var(--bui-space-9)}.lg\:bui-mt-10{margin-top:var(--bui-space-10)}.lg\:bui-mt-11{margin-top:var(--bui-space-11)}.lg\:bui-mt-12{margin-top:var(--bui-space-12)}.lg\:bui-mt-13{margin-top:var(--bui-space-13)}.lg\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mt{margin-top:var(--mt-xl)}.xl\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xl\:bui-mt-1{margin-top:var(--bui-space-1)}.xl\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xl\:bui-mt-2{margin-top:var(--bui-space-2)}.xl\:bui-mt-3{margin-top:var(--bui-space-3)}.xl\:bui-mt-4{margin-top:var(--bui-space-4)}.xl\:bui-mt-5{margin-top:var(--bui-space-5)}.xl\:bui-mt-6{margin-top:var(--bui-space-6)}.xl\:bui-mt-7{margin-top:var(--bui-space-7)}.xl\:bui-mt-8{margin-top:var(--bui-space-8)}.xl\:bui-mt-9{margin-top:var(--bui-space-9)}.xl\:bui-mt-10{margin-top:var(--bui-space-10)}.xl\:bui-mt-11{margin-top:var(--bui-space-11)}.xl\:bui-mt-12{margin-top:var(--bui-space-12)}.xl\:bui-mt-13{margin-top:var(--bui-space-13)}.xl\:bui-mt-14{margin-top:var(--bui-space-14)}}.bui-mb{margin-bottom:var(--mb)}.bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.bui-mb-1{margin-bottom:var(--bui-space-1)}.bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.bui-mb-2{margin-bottom:var(--bui-space-2)}.bui-mb-3{margin-bottom:var(--bui-space-3)}.bui-mb-4{margin-bottom:var(--bui-space-4)}.bui-mb-5{margin-bottom:var(--bui-space-5)}.bui-mb-6{margin-bottom:var(--bui-space-6)}.bui-mb-7{margin-bottom:var(--bui-space-7)}.bui-mb-8{margin-bottom:var(--bui-space-8)}.bui-mb-9{margin-bottom:var(--bui-space-9)}.bui-mb-10{margin-bottom:var(--bui-space-10)}.bui-mb-11{margin-bottom:var(--bui-space-11)}.bui-mb-12{margin-bottom:var(--bui-space-12)}.bui-mb-13{margin-bottom:var(--bui-space-13)}.bui-mb-14{margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mb{margin-bottom:var(--mb-xs)}.xs\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xs\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xs\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xs\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xs\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xs\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xs\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xs\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xs\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xs\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xs\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xs\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xs\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xs\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xs\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xs\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mb{margin-bottom:var(--mb-sm)}.sm\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.sm\:bui-mb-1{margin-bottom:var(--bui-space-1)}.sm\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.sm\:bui-mb-2{margin-bottom:var(--bui-space-2)}.sm\:bui-mb-3{margin-bottom:var(--bui-space-3)}.sm\:bui-mb-4{margin-bottom:var(--bui-space-4)}.sm\:bui-mb-5{margin-bottom:var(--bui-space-5)}.sm\:bui-mb-6{margin-bottom:var(--bui-space-6)}.sm\:bui-mb-7{margin-bottom:var(--bui-space-7)}.sm\:bui-mb-8{margin-bottom:var(--bui-space-8)}.sm\:bui-mb-9{margin-bottom:var(--bui-space-9)}.sm\:bui-mb-10{margin-bottom:var(--bui-space-10)}.sm\:bui-mb-11{margin-bottom:var(--bui-space-11)}.sm\:bui-mb-12{margin-bottom:var(--bui-space-12)}.sm\:bui-mb-13{margin-bottom:var(--bui-space-13)}.sm\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mb{margin-bottom:var(--mb-md)}.md\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.md\:bui-mb-1{margin-bottom:var(--bui-space-1)}.md\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.md\:bui-mb-2{margin-bottom:var(--bui-space-2)}.md\:bui-mb-3{margin-bottom:var(--bui-space-3)}.md\:bui-mb-4{margin-bottom:var(--bui-space-4)}.md\:bui-mb-5{margin-bottom:var(--bui-space-5)}.md\:bui-mb-6{margin-bottom:var(--bui-space-6)}.md\:bui-mb-7{margin-bottom:var(--bui-space-7)}.md\:bui-mb-8{margin-bottom:var(--bui-space-8)}.md\:bui-mb-9{margin-bottom:var(--bui-space-9)}.md\:bui-mb-10{margin-bottom:var(--bui-space-10)}.md\:bui-mb-11{margin-bottom:var(--bui-space-11)}.md\:bui-mb-12{margin-bottom:var(--bui-space-12)}.md\:bui-mb-13{margin-bottom:var(--bui-space-13)}.md\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mb{margin-bottom:var(--mb-lg)}.lg\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.lg\:bui-mb-1{margin-bottom:var(--bui-space-1)}.lg\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.lg\:bui-mb-2{margin-bottom:var(--bui-space-2)}.lg\:bui-mb-3{margin-bottom:var(--bui-space-3)}.lg\:bui-mb-4{margin-bottom:var(--bui-space-4)}.lg\:bui-mb-5{margin-bottom:var(--bui-space-5)}.lg\:bui-mb-6{margin-bottom:var(--bui-space-6)}.lg\:bui-mb-7{margin-bottom:var(--bui-space-7)}.lg\:bui-mb-8{margin-bottom:var(--bui-space-8)}.lg\:bui-mb-9{margin-bottom:var(--bui-space-9)}.lg\:bui-mb-10{margin-bottom:var(--bui-space-10)}.lg\:bui-mb-11{margin-bottom:var(--bui-space-11)}.lg\:bui-mb-12{margin-bottom:var(--bui-space-12)}.lg\:bui-mb-13{margin-bottom:var(--bui-space-13)}.lg\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mb{margin-bottom:var(--mb-xl)}.xl\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xl\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xl\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xl\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xl\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xl\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xl\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xl\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xl\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xl\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xl\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xl\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xl\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xl\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xl\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xl\:bui-mb-14{margin-bottom:var(--bui-space-14)}}.bui-my{margin-top:var(--my);margin-bottom:var(--my)}.bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-my{margin-top:var(--my-xs);margin-bottom:var(--my-xs)}.xs\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xs\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xs\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xs\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xs\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xs\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xs\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xs\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xs\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xs\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xs\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xs\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xs\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xs\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xs\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xs\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-my{margin-top:var(--my-sm);margin-bottom:var(--my-sm)}.sm\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.sm\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.sm\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.sm\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.sm\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.sm\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.sm\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.sm\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.sm\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.sm\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.sm\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.sm\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.sm\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.sm\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.sm\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.sm\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-my{margin-top:var(--my-md);margin-bottom:var(--my-md)}.md\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.md\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.md\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.md\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.md\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.md\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.md\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.md\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.md\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.md\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.md\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.md\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.md\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.md\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.md\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.md\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-my{margin-top:var(--my-lg);margin-bottom:var(--my-lg)}.lg\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.lg\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.lg\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.lg\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.lg\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.lg\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.lg\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.lg\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.lg\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.lg\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.lg\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.lg\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.lg\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.lg\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.lg\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.lg\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-my{margin-top:var(--my-xl);margin-bottom:var(--my-xl)}.xl\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xl\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xl\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xl\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xl\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xl\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xl\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xl\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xl\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xl\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xl\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xl\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xl\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xl\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xl\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xl\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}.bui-mx{margin-left:var(--mx);margin-right:var(--mx)}.bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mx{margin-left:var(--mx-xs);margin-right:var(--mx-xs)}.xs\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xs\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xs\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xs\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xs\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xs\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xs\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xs\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xs\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xs\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xs\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xs\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xs\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xs\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xs\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xs\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mx{margin-left:var(--mx-sm);margin-right:var(--mx-sm)}.sm\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.sm\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.sm\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.sm\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.sm\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.sm\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.sm\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.sm\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.sm\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.sm\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.sm\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.sm\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.sm\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.sm\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.sm\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.sm\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mx{margin-left:var(--mx-md);margin-right:var(--mx-md)}.md\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.md\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.md\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.md\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.md\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.md\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.md\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.md\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.md\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.md\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.md\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.md\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.md\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.md\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.md\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.md\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mx{margin-left:var(--mx-lg);margin-right:var(--mx-lg)}.lg\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.lg\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.lg\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.lg\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.lg\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.lg\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.lg\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.lg\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.lg\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.lg\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.lg\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.lg\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.lg\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.lg\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.lg\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.lg\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mx{margin-left:var(--mx-xl);margin-right:var(--mx-xl)}.xl\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xl\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xl\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xl\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xl\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xl\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xl\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xl\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xl\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xl\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xl\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xl\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xl\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xl\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xl\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xl\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}.bui-display-none{display:none}.bui-display-inline{display:inline}.bui-display-inline-block{display:inline-block}.bui-display-block{display:block}@media (width>=640px){.xs\:bui-display-none{display:none}.xs\:bui-display-inline{display:inline}.xs\:bui-display-inline-block{display:inline-block}.xs\:bui-display-block{display:block}}@media (width>=768px){.sm\:bui-display-none{display:none}.sm\:bui-display-inline{display:inline}.sm\:bui-display-inline-block{display:inline-block}.sm\:bui-display-block{display:block}}@media (width>=1024px){.md\:bui-display-none{display:none}.md\:bui-display-inline{display:inline}.md\:bui-display-inline-block{display:inline-block}.md\:bui-display-block{display:block}}@media (width>=1280px){.lg\:bui-display-none{display:none}.lg\:bui-display-inline{display:inline}.lg\:bui-display-inline-block{display:inline-block}.lg\:bui-display-block{display:block}}@media (width>=1536px){.xl\:bui-display-none{display:none}.xl\:bui-display-inline{display:inline}.xl\:bui-display-inline-block{display:inline-block}.xl\:bui-display-block{display:block}}.bui-w{width:var(--width)}.bui-min-w{min-width:var(--min-width)}.bui-max-w{max-width:var(--max-width)}@media (width>=640px){.xs\:bui-w{width:var(--width)}.xs\:bui-min-w{min-width:var(--min-width)}.xs\:bui-max-w{max-width:var(--max-width)}}@media (width>=768px){.sm\:bui-w{width:var(--width)}.sm\:bui-min-w{min-width:var(--min-width)}.sm\:bui-max-w{max-width:var(--max-width)}}@media (width>=1024px){.md\:bui-w{width:var(--width)}.md\:bui-min-w{min-width:var(--min-width)}.md\:bui-max-w{max-width:var(--max-width)}}@media (width>=1280px){.lg\:bui-w{width:var(--width)}.lg\:bui-min-w{min-width:var(--min-width)}.lg\:bui-max-w{max-width:var(--max-width)}}@media (width>=1536px){.xl\:bui-w{width:var(--width)}.xl\:bui-min-w{min-width:var(--min-width)}.xl\:bui-max-w{max-width:var(--max-width)}}.bui-h{height:var(--height)}.bui-min-h{min-height:var(--min-height)}.bui-max-h{max-height:var(--max-height)}@media (width>=640px){.xs\:bui-h{height:var(--height)}.xs\:bui-min-h{min-height:var(--min-height)}.xs\:bui-max-h{max-height:var(--max-height)}}@media (width>=768px){.sm\:bui-h{height:var(--height)}.sm\:bui-min-h{min-height:var(--min-height)}.sm\:bui-max-h{max-height:var(--max-height)}}@media (width>=1024px){.md\:bui-h{height:var(--height)}.md\:bui-min-h{min-height:var(--min-height)}.md\:bui-max-h{max-height:var(--max-height)}}@media (width>=1280px){.lg\:bui-h{height:var(--height)}.lg\:bui-min-h{min-height:var(--min-height)}.lg\:bui-max-h{max-height:var(--max-height)}}@media (width>=1536px){.xl\:bui-h{height:var(--height)}.xl\:bui-min-h{min-height:var(--min-height)}.xl\:bui-max-h{max-height:var(--max-height)}}.bui-position-absolute{position:absolute}.bui-position-fixed{position:fixed}.bui-position-sticky{position:sticky}.bui-position-relative{position:relative}.bui-position-static{position:static}@media (width>=640px){.xs\:bui-position-absolute{position:absolute}.xs\:bui-position-fixed{position:fixed}.xs\:bui-position-sticky{position:sticky}.xs\:bui-position-relative{position:relative}.xs\:bui-position-static{position:static}}@media (width>=768px){.sm\:bui-position-absolute{position:absolute}.sm\:bui-position-fixed{position:fixed}.sm\:bui-position-sticky{position:sticky}.sm\:bui-position-relative{position:relative}.sm\:bui-position-static{position:static}}@media (width>=1024px){.md\:bui-position-absolute{position:absolute}.md\:bui-position-fixed{position:fixed}.md\:bui-position-sticky{position:sticky}.md\:bui-position-relative{position:relative}.md\:bui-position-static{position:static}}@media (width>=1280px){.lg\:bui-position-absolute{position:absolute}.lg\:bui-position-fixed{position:fixed}.lg\:bui-position-sticky{position:sticky}.lg\:bui-position-relative{position:relative}.lg\:bui-position-static{position:static}}@media (width>=1536px){.xl\:bui-position-absolute{position:absolute}.xl\:bui-position-fixed{position:fixed}.xl\:bui-position-sticky{position:sticky}.xl\:bui-position-relative{position:relative}.xl\:bui-position-static{position:static}}.bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.bui-col-span-1{grid-column:span 1/span 1}.bui-col-span-2{grid-column:span 2/span 2}.bui-col-span-3{grid-column:span 3/span 3}.bui-col-span-4{grid-column:span 4/span 4}.bui-col-span-5{grid-column:span 5/span 5}.bui-col-span-6{grid-column:span 6/span 6}.bui-col-span-7{grid-column:span 7/span 7}.bui-col-span-8{grid-column:span 8/span 8}.bui-col-span-9{grid-column:span 9/span 9}.bui-col-span-10{grid-column:span 10/span 10}.bui-col-span-11{grid-column:span 11/span 11}.bui-col-span-12{grid-column:span 12/span 12}.bui-col-span-auto{grid-column:span auto/span auto}.bui-col-start-1{grid-column-start:1}.bui-col-start-2{grid-column-start:2}.bui-col-start-3{grid-column-start:3}.bui-col-start-4{grid-column-start:4}.bui-col-start-5{grid-column-start:5}.bui-col-start-6{grid-column-start:6}.bui-col-start-7{grid-column-start:7}.bui-col-start-8{grid-column-start:8}.bui-col-start-9{grid-column-start:9}.bui-col-start-10{grid-column-start:10}.bui-col-start-11{grid-column-start:11}.bui-col-start-12{grid-column-start:12}.bui-col-start-13{grid-column-start:13}.bui-col-start-auto{grid-column-start:auto}.bui-col-end-1{grid-column-end:1}.bui-col-end-2{grid-column-end:2}.bui-col-end-3{grid-column-end:3}.bui-col-end-4{grid-column-end:4}.bui-col-end-5{grid-column-end:5}.bui-col-end-6{grid-column-end:6}.bui-col-end-7{grid-column-end:7}.bui-col-end-8{grid-column-end:8}.bui-col-end-9{grid-column-end:9}.bui-col-end-10{grid-column-end:10}.bui-col-end-11{grid-column-end:11}.bui-col-end-12{grid-column-end:12}.bui-col-end-13{grid-column-end:13}.bui-col-end-auto{grid-column-end:auto}.bui-row-span-1{grid-row:span 1/span 1}.bui-row-span-2{grid-row:span 2/span 2}.bui-row-span-3{grid-row:span 3/span 3}.bui-row-span-4{grid-row:span 4/span 4}.bui-row-span-5{grid-row:span 5/span 5}.bui-row-span-6{grid-row:span 6/span 6}.bui-row-span-7{grid-row:span 7/span 7}.bui-row-span-8{grid-row:span 8/span 8}.bui-row-span-9{grid-row:span 9/span 9}.bui-row-span-10{grid-row:span 10/span 10}.bui-row-span-11{grid-row:span 11/span 11}.bui-row-span-12{grid-row:span 12/span 12}.bui-row-span-auto{grid-row:span auto/span auto}@media (width>=640px){.xs\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xs\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xs\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xs\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xs\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xs\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xs\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xs\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xs\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xs\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xs\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xs\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xs\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xs\:bui-col-span-1{grid-column:span 1/span 1}.xs\:bui-col-span-2{grid-column:span 2/span 2}.xs\:bui-col-span-3{grid-column:span 3/span 3}.xs\:bui-col-span-4{grid-column:span 4/span 4}.xs\:bui-col-span-5{grid-column:span 5/span 5}.xs\:bui-col-span-6{grid-column:span 6/span 6}.xs\:bui-col-span-7{grid-column:span 7/span 7}.xs\:bui-col-span-8{grid-column:span 8/span 8}.xs\:bui-col-span-9{grid-column:span 9/span 9}.xs\:bui-col-span-10{grid-column:span 10/span 10}.xs\:bui-col-span-11{grid-column:span 11/span 11}.xs\:bui-col-span-12{grid-column:span 12/span 12}.xs\:bui-col-span-auto{grid-column:span auto/span auto}.xs\:bui-col-start-1{grid-column-start:1}.xs\:bui-col-start-2{grid-column-start:2}.xs\:bui-col-start-3{grid-column-start:3}.xs\:bui-col-start-4{grid-column-start:4}.xs\:bui-col-start-5{grid-column-start:5}.xs\:bui-col-start-6{grid-column-start:6}.xs\:bui-col-start-7{grid-column-start:7}.xs\:bui-col-start-8{grid-column-start:8}.xs\:bui-col-start-9{grid-column-start:9}.xs\:bui-col-start-10{grid-column-start:10}.xs\:bui-col-start-11{grid-column-start:11}.xs\:bui-col-start-12{grid-column-start:12}.xs\:bui-col-start-13{grid-column-start:13}.xs\:bui-col-start-auto{grid-column-start:auto}.xs\:bui-col-end-1{grid-column-end:1}.xs\:bui-col-end-2{grid-column-end:2}.xs\:bui-col-end-3{grid-column-end:3}.xs\:bui-col-end-4{grid-column-end:4}.xs\:bui-col-end-5{grid-column-end:5}.xs\:bui-col-end-6{grid-column-end:6}.xs\:bui-col-end-7{grid-column-end:7}.xs\:bui-col-end-8{grid-column-end:8}.xs\:bui-col-end-9{grid-column-end:9}.xs\:bui-col-end-10{grid-column-end:10}.xs\:bui-col-end-11{grid-column-end:11}.xs\:bui-col-end-12{grid-column-end:12}.xs\:bui-col-end-13{grid-column-end:13}.xs\:bui-col-end-auto{grid-column-end:auto}.xs\:bui-row-span-1{grid-row:span 1/span 1}.xs\:bui-row-span-2{grid-row:span 2/span 2}.xs\:bui-row-span-3{grid-row:span 3/span 3}.xs\:bui-row-span-4{grid-row:span 4/span 4}.xs\:bui-row-span-5{grid-row:span 5/span 5}.xs\:bui-row-span-6{grid-row:span 6/span 6}.xs\:bui-row-span-7{grid-row:span 7/span 7}.xs\:bui-row-span-8{grid-row:span 8/span 8}.xs\:bui-row-span-9{grid-row:span 9/span 9}.xs\:bui-row-span-10{grid-row:span 10/span 10}.xs\:bui-row-span-11{grid-row:span 11/span 11}.xs\:bui-row-span-12{grid-row:span 12/span 12}.xs\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=768px){.sm\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.sm\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.sm\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.sm\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.sm\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:bui-col-span-1{grid-column:span 1/span 1}.sm\:bui-col-span-2{grid-column:span 2/span 2}.sm\:bui-col-span-3{grid-column:span 3/span 3}.sm\:bui-col-span-4{grid-column:span 4/span 4}.sm\:bui-col-span-5{grid-column:span 5/span 5}.sm\:bui-col-span-6{grid-column:span 6/span 6}.sm\:bui-col-span-7{grid-column:span 7/span 7}.sm\:bui-col-span-8{grid-column:span 8/span 8}.sm\:bui-col-span-9{grid-column:span 9/span 9}.sm\:bui-col-span-10{grid-column:span 10/span 10}.sm\:bui-col-span-11{grid-column:span 11/span 11}.sm\:bui-col-span-12{grid-column:span 12/span 12}.sm\:bui-col-span-auto{grid-column:span auto/span auto}.sm\:bui-col-start-1{grid-column-start:1}.sm\:bui-col-start-2{grid-column-start:2}.sm\:bui-col-start-3{grid-column-start:3}.sm\:bui-col-start-4{grid-column-start:4}.sm\:bui-col-start-5{grid-column-start:5}.sm\:bui-col-start-6{grid-column-start:6}.sm\:bui-col-start-7{grid-column-start:7}.sm\:bui-col-start-8{grid-column-start:8}.sm\:bui-col-start-9{grid-column-start:9}.sm\:bui-col-start-10{grid-column-start:10}.sm\:bui-col-start-11{grid-column-start:11}.sm\:bui-col-start-12{grid-column-start:12}.sm\:bui-col-start-13{grid-column-start:13}.sm\:bui-col-start-auto{grid-column-start:auto}.sm\:bui-col-end-1{grid-column-end:1}.sm\:bui-col-end-2{grid-column-end:2}.sm\:bui-col-end-3{grid-column-end:3}.sm\:bui-col-end-4{grid-column-end:4}.sm\:bui-col-end-5{grid-column-end:5}.sm\:bui-col-end-6{grid-column-end:6}.sm\:bui-col-end-7{grid-column-end:7}.sm\:bui-col-end-8{grid-column-end:8}.sm\:bui-col-end-9{grid-column-end:9}.sm\:bui-col-end-10{grid-column-end:10}.sm\:bui-col-end-11{grid-column-end:11}.sm\:bui-col-end-12{grid-column-end:12}.sm\:bui-col-end-13{grid-column-end:13}.sm\:bui-col-end-auto{grid-column-end:auto}.sm\:bui-row-span-1{grid-row:span 1/span 1}.sm\:bui-row-span-2{grid-row:span 2/span 2}.sm\:bui-row-span-3{grid-row:span 3/span 3}.sm\:bui-row-span-4{grid-row:span 4/span 4}.sm\:bui-row-span-5{grid-row:span 5/span 5}.sm\:bui-row-span-6{grid-row:span 6/span 6}.sm\:bui-row-span-7{grid-row:span 7/span 7}.sm\:bui-row-span-8{grid-row:span 8/span 8}.sm\:bui-row-span-9{grid-row:span 9/span 9}.sm\:bui-row-span-10{grid-row:span 10/span 10}.sm\:bui-row-span-11{grid-row:span 11/span 11}.sm\:bui-row-span-12{grid-row:span 12/span 12}.sm\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1024px){.md\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.md\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.md\:bui-col-span-1{grid-column:span 1/span 1}.md\:bui-col-span-2{grid-column:span 2/span 2}.md\:bui-col-span-3{grid-column:span 3/span 3}.md\:bui-col-span-4{grid-column:span 4/span 4}.md\:bui-col-span-5{grid-column:span 5/span 5}.md\:bui-col-span-6{grid-column:span 6/span 6}.md\:bui-col-span-7{grid-column:span 7/span 7}.md\:bui-col-span-8{grid-column:span 8/span 8}.md\:bui-col-span-9{grid-column:span 9/span 9}.md\:bui-col-span-10{grid-column:span 10/span 10}.md\:bui-col-span-11{grid-column:span 11/span 11}.md\:bui-col-span-12{grid-column:span 12/span 12}.md\:bui-col-span-auto{grid-column:span auto/span auto}.md\:bui-col-start-1{grid-column-start:1}.md\:bui-col-start-2{grid-column-start:2}.md\:bui-col-start-3{grid-column-start:3}.md\:bui-col-start-4{grid-column-start:4}.md\:bui-col-start-5{grid-column-start:5}.md\:bui-col-start-6{grid-column-start:6}.md\:bui-col-start-7{grid-column-start:7}.md\:bui-col-start-8{grid-column-start:8}.md\:bui-col-start-9{grid-column-start:9}.md\:bui-col-start-10{grid-column-start:10}.md\:bui-col-start-11{grid-column-start:11}.md\:bui-col-start-12{grid-column-start:12}.md\:bui-col-start-13{grid-column-start:13}.md\:bui-col-start-auto{grid-column-start:auto}.md\:bui-col-end-1{grid-column-end:1}.md\:bui-col-end-2{grid-column-end:2}.md\:bui-col-end-3{grid-column-end:3}.md\:bui-col-end-4{grid-column-end:4}.md\:bui-col-end-5{grid-column-end:5}.md\:bui-col-end-6{grid-column-end:6}.md\:bui-col-end-7{grid-column-end:7}.md\:bui-col-end-8{grid-column-end:8}.md\:bui-col-end-9{grid-column-end:9}.md\:bui-col-end-10{grid-column-end:10}.md\:bui-col-end-11{grid-column-end:11}.md\:bui-col-end-12{grid-column-end:12}.md\:bui-col-end-13{grid-column-end:13}.md\:bui-col-end-auto{grid-column-end:auto}.md\:bui-row-span-1{grid-row:span 1/span 1}.md\:bui-row-span-2{grid-row:span 2/span 2}.md\:bui-row-span-3{grid-row:span 3/span 3}.md\:bui-row-span-4{grid-row:span 4/span 4}.md\:bui-row-span-5{grid-row:span 5/span 5}.md\:bui-row-span-6{grid-row:span 6/span 6}.md\:bui-row-span-7{grid-row:span 7/span 7}.md\:bui-row-span-8{grid-row:span 8/span 8}.md\:bui-row-span-9{grid-row:span 9/span 9}.md\:bui-row-span-10{grid-row:span 10/span 10}.md\:bui-row-span-11{grid-row:span 11/span 11}.md\:bui-row-span-12{grid-row:span 12/span 12}.md\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1280px){.lg\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.lg\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.lg\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.lg\:bui-col-span-1{grid-column:span 1/span 1}.lg\:bui-col-span-2{grid-column:span 2/span 2}.lg\:bui-col-span-3{grid-column:span 3/span 3}.lg\:bui-col-span-4{grid-column:span 4/span 4}.lg\:bui-col-span-5{grid-column:span 5/span 5}.lg\:bui-col-span-6{grid-column:span 6/span 6}.lg\:bui-col-span-7{grid-column:span 7/span 7}.lg\:bui-col-span-8{grid-column:span 8/span 8}.lg\:bui-col-span-9{grid-column:span 9/span 9}.lg\:bui-col-span-10{grid-column:span 10/span 10}.lg\:bui-col-span-11{grid-column:span 11/span 11}.lg\:bui-col-span-12{grid-column:span 12/span 12}.lg\:bui-col-span-auto{grid-column:span auto/span auto}.lg\:bui-col-start-1{grid-column-start:1}.lg\:bui-col-start-2{grid-column-start:2}.lg\:bui-col-start-3{grid-column-start:3}.lg\:bui-col-start-4{grid-column-start:4}.lg\:bui-col-start-5{grid-column-start:5}.lg\:bui-col-start-6{grid-column-start:6}.lg\:bui-col-start-7{grid-column-start:7}.lg\:bui-col-start-8{grid-column-start:8}.lg\:bui-col-start-9{grid-column-start:9}.lg\:bui-col-start-10{grid-column-start:10}.lg\:bui-col-start-11{grid-column-start:11}.lg\:bui-col-start-12{grid-column-start:12}.lg\:bui-col-start-13{grid-column-start:13}.lg\:bui-col-start-auto{grid-column-start:auto}.lg\:bui-col-end-1{grid-column-end:1}.lg\:bui-col-end-2{grid-column-end:2}.lg\:bui-col-end-3{grid-column-end:3}.lg\:bui-col-end-4{grid-column-end:4}.lg\:bui-col-end-5{grid-column-end:5}.lg\:bui-col-end-6{grid-column-end:6}.lg\:bui-col-end-7{grid-column-end:7}.lg\:bui-col-end-8{grid-column-end:8}.lg\:bui-col-end-9{grid-column-end:9}.lg\:bui-col-end-10{grid-column-end:10}.lg\:bui-col-end-11{grid-column-end:11}.lg\:bui-col-end-12{grid-column-end:12}.lg\:bui-col-end-13{grid-column-end:13}.lg\:bui-col-end-auto{grid-column-end:auto}.lg\:bui-row-span-1{grid-row:span 1/span 1}.lg\:bui-row-span-2{grid-row:span 2/span 2}.lg\:bui-row-span-3{grid-row:span 3/span 3}.lg\:bui-row-span-4{grid-row:span 4/span 4}.lg\:bui-row-span-5{grid-row:span 5/span 5}.lg\:bui-row-span-6{grid-row:span 6/span 6}.lg\:bui-row-span-7{grid-row:span 7/span 7}.lg\:bui-row-span-8{grid-row:span 8/span 8}.lg\:bui-row-span-9{grid-row:span 9/span 9}.lg\:bui-row-span-10{grid-row:span 10/span 10}.lg\:bui-row-span-11{grid-row:span 11/span 11}.lg\:bui-row-span-12{grid-row:span 12/span 12}.lg\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1536px){.xl\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xl\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xl\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xl\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xl\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xl\:bui-col-span-1{grid-column:span 1/span 1}.xl\:bui-col-span-2{grid-column:span 2/span 2}.xl\:bui-col-span-3{grid-column:span 3/span 3}.xl\:bui-col-span-4{grid-column:span 4/span 4}.xl\:bui-col-span-5{grid-column:span 5/span 5}.xl\:bui-col-span-6{grid-column:span 6/span 6}.xl\:bui-col-span-7{grid-column:span 7/span 7}.xl\:bui-col-span-8{grid-column:span 8/span 8}.xl\:bui-col-span-9{grid-column:span 9/span 9}.xl\:bui-col-span-10{grid-column:span 10/span 10}.xl\:bui-col-span-11{grid-column:span 11/span 11}.xl\:bui-col-span-12{grid-column:span 12/span 12}.xl\:bui-col-span-auto{grid-column:span auto/span auto}.xl\:bui-col-start-1{grid-column-start:1}.xl\:bui-col-start-2{grid-column-start:2}.xl\:bui-col-start-3{grid-column-start:3}.xl\:bui-col-start-4{grid-column-start:4}.xl\:bui-col-start-5{grid-column-start:5}.xl\:bui-col-start-6{grid-column-start:6}.xl\:bui-col-start-7{grid-column-start:7}.xl\:bui-col-start-8{grid-column-start:8}.xl\:bui-col-start-9{grid-column-start:9}.xl\:bui-col-start-10{grid-column-start:10}.xl\:bui-col-start-11{grid-column-start:11}.xl\:bui-col-start-12{grid-column-start:12}.xl\:bui-col-start-13{grid-column-start:13}.xl\:bui-col-start-auto{grid-column-start:auto}.xl\:bui-col-end-1{grid-column-end:1}.xl\:bui-col-end-2{grid-column-end:2}.xl\:bui-col-end-3{grid-column-end:3}.xl\:bui-col-end-4{grid-column-end:4}.xl\:bui-col-end-5{grid-column-end:5}.xl\:bui-col-end-6{grid-column-end:6}.xl\:bui-col-end-7{grid-column-end:7}.xl\:bui-col-end-8{grid-column-end:8}.xl\:bui-col-end-9{grid-column-end:9}.xl\:bui-col-end-10{grid-column-end:10}.xl\:bui-col-end-11{grid-column-end:11}.xl\:bui-col-end-12{grid-column-end:12}.xl\:bui-col-end-13{grid-column-end:13}.xl\:bui-col-end-auto{grid-column-end:auto}.xl\:bui-row-span-1{grid-row:span 1/span 1}.xl\:bui-row-span-2{grid-row:span 2/span 2}.xl\:bui-row-span-3{grid-row:span 3/span 3}.xl\:bui-row-span-4{grid-row:span 4/span 4}.xl\:bui-row-span-5{grid-row:span 5/span 5}.xl\:bui-row-span-6{grid-row:span 6/span 6}.xl\:bui-row-span-7{grid-row:span 7/span 7}.xl\:bui-row-span-8{grid-row:span 8/span 8}.xl\:bui-row-span-9{grid-row:span 9/span 9}.xl\:bui-row-span-10{grid-row:span 10/span 10}.xl\:bui-row-span-11{grid-row:span 11/span 11}.xl\:bui-row-span-12{grid-row:span 12/span 12}.xl\:bui-row-span-auto{grid-row:span auto/span auto}}.bui-gap{gap:var(--gap)}.bui-gap-0\.5{gap:var(--bui-space-0_5)}.bui-gap-1{gap:var(--bui-space-1)}.bui-gap-1\.5{gap:var(--bui-space-1_5)}.bui-gap-2{gap:var(--bui-space-2)}.bui-gap-3{gap:var(--bui-space-3)}.bui-gap-4{gap:var(--bui-space-4)}.bui-gap-5{gap:var(--bui-space-5)}.bui-gap-6{gap:var(--bui-space-6)}.bui-gap-7{gap:var(--bui-space-7)}.bui-gap-8{gap:var(--bui-space-8)}.bui-gap-9{gap:var(--bui-space-9)}.bui-gap-10{gap:var(--bui-space-10)}.bui-gap-11{gap:var(--bui-space-11)}.bui-gap-12{gap:var(--bui-space-12)}.bui-gap-13{gap:var(--bui-space-13)}.bui-gap-14{gap:var(--bui-space-14)}@media (width>=640px){.xs\:bui-gap{gap:var(--gap-xs)}.xs\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xs\:bui-gap-1{gap:var(--bui-space-1)}.xs\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xs\:bui-gap-2{gap:var(--bui-space-2)}.xs\:bui-gap-3{gap:var(--bui-space-3)}.xs\:bui-gap-4{gap:var(--bui-space-4)}.xs\:bui-gap-5{gap:var(--bui-space-5)}.xs\:bui-gap-6{gap:var(--bui-space-6)}.xs\:bui-gap-7{gap:var(--bui-space-7)}.xs\:bui-gap-8{gap:var(--bui-space-8)}.xs\:bui-gap-9{gap:var(--bui-space-9)}.xs\:bui-gap-10{gap:var(--bui-space-10)}.xs\:bui-gap-11{gap:var(--bui-space-11)}.xs\:bui-gap-12{gap:var(--bui-space-12)}.xs\:bui-gap-13{gap:var(--bui-space-13)}.xs\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-gap{gap:var(--gap-sm)}.sm\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.sm\:bui-gap-1{gap:var(--bui-space-1)}.sm\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.sm\:bui-gap-2{gap:var(--bui-space-2)}.sm\:bui-gap-3{gap:var(--bui-space-3)}.sm\:bui-gap-4{gap:var(--bui-space-4)}.sm\:bui-gap-5{gap:var(--bui-space-5)}.sm\:bui-gap-6{gap:var(--bui-space-6)}.sm\:bui-gap-7{gap:var(--bui-space-7)}.sm\:bui-gap-8{gap:var(--bui-space-8)}.sm\:bui-gap-9{gap:var(--bui-space-9)}.sm\:bui-gap-10{gap:var(--bui-space-10)}.sm\:bui-gap-11{gap:var(--bui-space-11)}.sm\:bui-gap-12{gap:var(--bui-space-12)}.sm\:bui-gap-13{gap:var(--bui-space-13)}.sm\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-gap{gap:var(--gap-md)}.md\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.md\:bui-gap-1{gap:var(--bui-space-1)}.md\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.md\:bui-gap-2{gap:var(--bui-space-2)}.md\:bui-gap-3{gap:var(--bui-space-3)}.md\:bui-gap-4{gap:var(--bui-space-4)}.md\:bui-gap-5{gap:var(--bui-space-5)}.md\:bui-gap-6{gap:var(--bui-space-6)}.md\:bui-gap-7{gap:var(--bui-space-7)}.md\:bui-gap-8{gap:var(--bui-space-8)}.md\:bui-gap-9{gap:var(--bui-space-9)}.md\:bui-gap-10{gap:var(--bui-space-10)}.md\:bui-gap-11{gap:var(--bui-space-11)}.md\:bui-gap-12{gap:var(--bui-space-12)}.md\:bui-gap-13{gap:var(--bui-space-13)}.md\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-gap{gap:var(--gap-lg)}.lg\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.lg\:bui-gap-1{gap:var(--bui-space-1)}.lg\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.lg\:bui-gap-2{gap:var(--bui-space-2)}.lg\:bui-gap-3{gap:var(--bui-space-3)}.lg\:bui-gap-4{gap:var(--bui-space-4)}.lg\:bui-gap-5{gap:var(--bui-space-5)}.lg\:bui-gap-6{gap:var(--bui-space-6)}.lg\:bui-gap-7{gap:var(--bui-space-7)}.lg\:bui-gap-8{gap:var(--bui-space-8)}.lg\:bui-gap-9{gap:var(--bui-space-9)}.lg\:bui-gap-10{gap:var(--bui-space-10)}.lg\:bui-gap-11{gap:var(--bui-space-11)}.lg\:bui-gap-12{gap:var(--bui-space-12)}.lg\:bui-gap-13{gap:var(--bui-space-13)}.lg\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-gap{gap:var(--gap-xl)}.xl\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xl\:bui-gap-1{gap:var(--bui-space-1)}.xl\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xl\:bui-gap-2{gap:var(--bui-space-2)}.xl\:bui-gap-3{gap:var(--bui-space-3)}.xl\:bui-gap-4{gap:var(--bui-space-4)}.xl\:bui-gap-5{gap:var(--bui-space-5)}.xl\:bui-gap-6{gap:var(--bui-space-6)}.xl\:bui-gap-7{gap:var(--bui-space-7)}.xl\:bui-gap-8{gap:var(--bui-space-8)}.xl\:bui-gap-9{gap:var(--bui-space-9)}.xl\:bui-gap-10{gap:var(--bui-space-10)}.xl\:bui-gap-11{gap:var(--bui-space-11)}.xl\:bui-gap-12{gap:var(--bui-space-12)}.xl\:bui-gap-13{gap:var(--bui-space-13)}.xl\:bui-gap-14{gap:var(--bui-space-14)}}.bui-align-start{align-items:start}.bui-align-center{align-items:center}.bui-align-end{align-items:end}.bui-align-baseline{align-items:baseline}.bui-align-stretch{align-items:stretch}.bui-jc-start{justify-content:start}.bui-jc-center{justify-content:center}.bui-jc-end{justify-content:end}.bui-jc-between{justify-content:space-between}.bui-fd-row{flex-direction:row}.bui-fd-column{flex-direction:column}.bui-fd-row-reverse{flex-direction:row-reverse}.bui-fd-column-reverse{flex-direction:column-reverse}@media (width>=640px){.xs\:bui-align-start{align-items:start}.xs\:bui-align-center{align-items:center}.xs\:bui-align-end{align-items:end}.xs\:bui-align-stretch{align-items:stretch}.xs\:bui-jc-start{justify-content:start}.xs\:bui-jc-center{justify-content:center}.xs\:bui-jc-end{justify-content:end}.xs\:bui-jc-between{justify-content:space-between}.xs\:bui-fd-row{flex-direction:row}.xs\:bui-fd-column{flex-direction:column}.xs\:bui-fd-row-reverse{flex-direction:row-reverse}.xs\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=768px){.sm\:bui-align-start{align-items:start}.sm\:bui-align-center{align-items:center}.sm\:bui-align-end{align-items:end}.sm\:bui-align-stretch{align-items:stretch}.sm\:bui-jc-start{justify-content:start}.sm\:bui-jc-center{justify-content:center}.sm\:bui-jc-end{justify-content:end}.sm\:bui-jc-between{justify-content:space-between}.sm\:bui-fd-row{flex-direction:row}.sm\:bui-fd-column{flex-direction:column}.sm\:bui-fd-row-reverse{flex-direction:row-reverse}.sm\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1024px){.md\:bui-align-start{align-items:start}.md\:bui-align-center{align-items:center}.md\:bui-align-end{align-items:end}.md\:bui-align-stretch{align-items:stretch}.md\:bui-jc-start{justify-content:start}.md\:bui-jc-center{justify-content:center}.md\:bui-jc-end{justify-content:end}.md\:bui-jc-between{justify-content:space-between}.md\:bui-fd-row{flex-direction:row}.md\:bui-fd-column{flex-direction:column}.md\:bui-fd-row-reverse{flex-direction:row-reverse}.md\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1280px){.lg\:bui-align-start{align-items:start}.lg\:bui-align-center{align-items:center}.lg\:bui-align-end{align-items:end}.lg\:bui-align-stretch{align-items:stretch}.lg\:bui-jc-start{justify-content:start}.lg\:bui-jc-center{justify-content:center}.lg\:bui-jc-end{justify-content:end}.lg\:bui-jc-between{justify-content:space-between}.lg\:bui-fd-row{flex-direction:row}.lg\:bui-fd-column{flex-direction:column}.lg\:bui-fd-row-reverse{flex-direction:row-reverse}.lg\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1536px){.xl\:bui-align-start{align-items:start}.xl\:bui-align-center{align-items:center}.xl\:bui-align-end{align-items:end}.xl\:bui-align-stretch{align-items:stretch}.xl\:bui-jc-start{justify-content:start}.xl\:bui-jc-center{justify-content:center}.xl\:bui-jc-end{justify-content:end}.xl\:bui-jc-between{justify-content:space-between}.xl\:bui-fd-row{flex-direction:row}.xl\:bui-fd-column{flex-direction:column}.xl\:bui-fd-row-reverse{flex-direction:row-reverse}.xl\:bui-fd-column-reverse{flex-direction:column-reverse}}} \ No newline at end of file diff --git a/packages/ui/css/styles.css b/packages/ui/css/styles.css index 2db8988873..4f0f333c25 100644 --- a/packages/ui/css/styles.css +++ b/packages/ui/css/styles.css @@ -100,7 +100,7 @@ --bui-ring: #1f5493; --bui-scrollbar: #a0a0a03b; --bui-scrollbar-thumb: #a0a0a0; - --bui-shadow-overlay: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a; + --bui-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a; --bui-animate-pulse: pulse 2s cubic-bezier(.4, 0, .6, 1) infinite; } @@ -162,7 +162,7 @@ --bui-ring: #1f5493; --bui-scrollbar: #3636363a; --bui-scrollbar-thumb: #575757; - --bui-shadow-overlay: none; + --bui-shadow: none; } } diff --git a/packages/ui/src/components/Menu/Menu.module.css b/packages/ui/src/components/Menu/Menu.module.css index ef86eaba6d..d8ebf6cba8 100644 --- a/packages/ui/src/components/Menu/Menu.module.css +++ b/packages/ui/src/components/Menu/Menu.module.css @@ -20,7 +20,7 @@ .bui-MenuPopover { display: flex; flex-direction: column; - box-shadow: var(--bui-shadow-overlay); + box-shadow: var(--bui-shadow); border: 1px solid var(--bui-border); border-radius: var(--bui-radius-2); background: var(--bui-bg-surface-1); diff --git a/packages/ui/src/components/Popover/Popover.module.css b/packages/ui/src/components/Popover/Popover.module.css index 5c94446771..fe757c8dd4 100644 --- a/packages/ui/src/components/Popover/Popover.module.css +++ b/packages/ui/src/components/Popover/Popover.module.css @@ -18,7 +18,7 @@ @layer components { .bui-Popover { - box-shadow: var(--bui-shadow-overlay); + box-shadow: var(--bui-shadow); border-radius: var(--bui-radius-3); background: var(--bui-bg-surface-1); border: 1px solid var(--bui-gray-3); diff --git a/packages/ui/src/components/Tooltip/Tooltip.module.css b/packages/ui/src/components/Tooltip/Tooltip.module.css index b8f2fe0c1a..5c9c27eb8d 100644 --- a/packages/ui/src/components/Tooltip/Tooltip.module.css +++ b/packages/ui/src/components/Tooltip/Tooltip.module.css @@ -18,7 +18,7 @@ @layer components { .bui-Tooltip { - box-shadow: var(--bui-shadow-overlay); + box-shadow: var(--bui-shadow); border-radius: 4px; background: var(--bui-bg-surface-1); border: 1px solid var(--bui-gray-3); diff --git a/packages/ui/src/css/tokens.css b/packages/ui/src/css/tokens.css index 89292e3954..948dbace4b 100644 --- a/packages/ui/src/css/tokens.css +++ b/packages/ui/src/css/tokens.css @@ -147,7 +147,7 @@ --bui-scrollbar-thumb: #a0a0a0; /* Shadows */ - --bui-shadow-overlay: 0 10px 15px -3px rgba(0 0 0 / 0.1), + --bui-shadow: 0 10px 15px -3px rgba(0 0 0 / 0.1), 0 4px 6px -4px rgba(0 0 0 / 0.1); --bui-animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; @@ -232,6 +232,6 @@ --bui-scrollbar-thumb: #575757; /* Shadows */ - --bui-shadow-overlay: none; + --bui-shadow: none; } } From ca2e767204f74501355b2207a1f098f497b3fd22 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 14 Jan 2026 23:53:30 +0100 Subject: [PATCH 139/202] Migrate all mdx files Signed-off-by: Charles de Dreuille --- docs-ui/src/app/components/[slug]/page.tsx | 23 ----- .../components/accordion}/accordion.props.ts | 0 .../components/accordion/page.mdx} | 23 +++-- .../src/app/components/accordion/stories.tsx | 19 +++++ .../components/avatar}/avatar.props.ts | 0 .../components/avatar/page.mdx} | 12 +-- docs-ui/src/app/components/avatar/stories.tsx | 15 ++++ .../components/box}/box.props.ts | 0 .../box.mdx => app/components/box/page.mdx} | 6 +- docs-ui/src/app/components/box/stories.tsx | 7 ++ .../button-icon}/button-icon.props.ts | 0 .../components/button-icon/page.mdx} | 14 +-- .../app/components/button-icon/stories.tsx | 15 ++++ .../button-link}/button-link.props.ts | 0 .../components/button-link/page.mdx} | 14 +-- .../app/components/button-link/stories.tsx | 15 ++++ .../components/button}/button.props.ts | 0 .../components/button/page.mdx} | 19 +++-- docs-ui/src/app/components/button/stories.tsx | 17 ++++ .../components/card}/card.props.ts | 0 .../card.mdx => app/components/card/page.mdx} | 10 +-- docs-ui/src/app/components/card/stories.tsx | 13 +++ .../components/checkbox}/checkbox.props.ts | 0 .../components/checkbox/page.mdx} | 8 +- .../src/app/components/checkbox/stories.tsx | 8 ++ .../components/container}/container.props.ts | 0 .../components/container/page.mdx} | 6 +- .../src/app/components/container/stories.tsx | 7 ++ .../components/dialog}/dialog.props.ts | 0 .../components/dialog/page.mdx} | 10 +-- docs-ui/src/app/components/dialog/stories.tsx | 15 ++++ .../components/flex}/flex.props.ts | 0 .../flex.mdx => app/components/flex/page.mdx} | 6 +- docs-ui/src/app/components/flex/stories.tsx | 7 ++ .../components/grid}/grid.props.ts | 0 .../grid.mdx => app/components/grid/page.mdx} | 6 +- docs-ui/src/app/components/grid/stories.tsx | 7 ++ .../header-page}/header-page.props.ts | 0 .../components/header-page/page.mdx} | 14 +-- .../app/components/header-page/stories.tsx | 17 ++++ .../components/header}/header.props.ts | 0 .../components/header/page.mdx} | 14 +-- docs-ui/src/app/components/header/stories.tsx | 17 ++++ .../components/link}/link.props.ts | 0 .../link.mdx => app/components/link/page.mdx} | 18 ++-- docs-ui/src/app/components/link/stories.tsx | 17 ++++ .../components/menu}/menu.props.ts | 0 .../menu.mdx => app/components/menu/page.mdx} | 22 ++--- docs-ui/src/app/components/menu/stories.tsx | 31 +++++++ .../app/components/password-field/page.mdx | 65 ++++++++++++++ .../password-field}/password-field.props.ts | 0 .../app/components/password-field/stories.tsx | 13 +++ .../components/popover/page.mdx} | 16 ++-- .../components/popover}/popover.props.ts | 0 .../src/app/components/popover/stories.tsx | 7 ++ .../components/radio-group/page.mdx} | 16 ++-- .../radio-group}/radio-group.props.ts | 0 .../app/components/radio-group/stories.tsx | 19 +++++ .../components/search-field/page.mdx} | 12 +-- .../search-field}/search-field.props.ts | 0 .../app/components/search-field/stories.tsx | 15 ++++ .../components/select/page.mdx} | 22 ++--- .../components/select}/select.props.ts | 0 docs-ui/src/app/components/select/stories.tsx | 27 ++++++ .../components/skeleton/page.mdx} | 10 +-- .../components/skeleton}/skeleton.props.ts | 0 .../src/app/components/skeleton/stories.tsx | 8 ++ .../components/switch/page.mdx} | 8 +- docs-ui/src/app/components/switch/stories.tsx | 8 ++ .../components/switch}/switch.props.ts | 0 .../components/table/page.mdx} | 12 +-- docs-ui/src/app/components/table/stories.tsx | 21 +++++ .../components/table}/table.props.ts | 0 .../tabs.mdx => app/components/tabs/page.mdx} | 12 +-- docs-ui/src/app/components/tabs/stories.tsx | 17 ++++ .../components/tabs}/tabs.props.ts | 0 .../components/tag-group/page.mdx} | 16 ++-- .../src/app/components/tag-group/stories.tsx | 19 +++++ .../components/tag-group}/tag-group.props.ts | 0 .../components/text-field/page.mdx} | 10 +-- .../src/app/components/text-field/stories.tsx | 13 +++ .../text-field}/text-field.props.ts | 0 .../text.mdx => app/components/text/page.mdx} | 14 +-- docs-ui/src/app/components/text/stories.tsx | 17 ++++ .../components/text}/text.props.ts | 0 .../components/toggle-button-group/page.mdx} | 22 ++--- .../toggle-button-group/stories.tsx | 25 ++++++ .../toggle-button-group.props.ts | 0 .../components/toggle-button/page.mdx} | 18 ++-- .../app/components/toggle-button/stories.tsx | 21 +++++ .../toggle-button}/toggle-button.props.ts | 0 .../components/tooltip/page.mdx} | 6 +- .../src/app/components/tooltip/stories.tsx | 7 ++ .../components/tooltip}/tooltip.props.ts | 0 .../components/visually-hidden/page.mdx} | 8 +- .../components/visually-hidden/stories.tsx | 8 ++ .../visually-hidden}/visually-hidden.props.ts | 0 docs-ui/src/content/PasswordField.tsx | 8 -- docs-ui/src/content/password-field.mdx | 23 ----- docs-ui/src/snippets/stories-snippets.tsx | 85 ------------------- 100 files changed, 741 insertions(+), 339 deletions(-) delete mode 100644 docs-ui/src/app/components/[slug]/page.tsx rename docs-ui/src/{content => app/components/accordion}/accordion.props.ts (100%) rename docs-ui/src/{content/accordion.mdx => app/components/accordion/page.mdx} (84%) create mode 100644 docs-ui/src/app/components/accordion/stories.tsx rename docs-ui/src/{content => app/components/avatar}/avatar.props.ts (100%) rename docs-ui/src/{content/avatar.mdx => app/components/avatar/page.mdx} (81%) create mode 100644 docs-ui/src/app/components/avatar/stories.tsx rename docs-ui/src/{content => app/components/box}/box.props.ts (100%) rename docs-ui/src/{content/box.mdx => app/components/box/page.mdx} (90%) create mode 100644 docs-ui/src/app/components/box/stories.tsx rename docs-ui/src/{content => app/components/button-icon}/button-icon.props.ts (100%) rename docs-ui/src/{content/button-icon.mdx => app/components/button-icon/page.mdx} (81%) create mode 100644 docs-ui/src/app/components/button-icon/stories.tsx rename docs-ui/src/{content => app/components/button-link}/button-link.props.ts (100%) rename docs-ui/src/{content/button-link.mdx => app/components/button-link/page.mdx} (81%) create mode 100644 docs-ui/src/app/components/button-link/stories.tsx rename docs-ui/src/{content => app/components/button}/button.props.ts (100%) rename docs-ui/src/{content/button.mdx => app/components/button/page.mdx} (80%) create mode 100644 docs-ui/src/app/components/button/stories.tsx rename docs-ui/src/{content => app/components/card}/card.props.ts (100%) rename docs-ui/src/{content/card.mdx => app/components/card/page.mdx} (86%) create mode 100644 docs-ui/src/app/components/card/stories.tsx rename docs-ui/src/{content => app/components/checkbox}/checkbox.props.ts (100%) rename docs-ui/src/{content/checkbox.mdx => app/components/checkbox/page.mdx} (81%) create mode 100644 docs-ui/src/app/components/checkbox/stories.tsx rename docs-ui/src/{content => app/components/container}/container.props.ts (100%) rename docs-ui/src/{content/container.mdx => app/components/container/page.mdx} (87%) create mode 100644 docs-ui/src/app/components/container/stories.tsx rename docs-ui/src/{content => app/components/dialog}/dialog.props.ts (100%) rename docs-ui/src/{content/dialog.mdx => app/components/dialog/page.mdx} (88%) create mode 100644 docs-ui/src/app/components/dialog/stories.tsx rename docs-ui/src/{content => app/components/flex}/flex.props.ts (100%) rename docs-ui/src/{content/flex.mdx => app/components/flex/page.mdx} (90%) create mode 100644 docs-ui/src/app/components/flex/stories.tsx rename docs-ui/src/{content => app/components/grid}/grid.props.ts (100%) rename docs-ui/src/{content/grid.mdx => app/components/grid/page.mdx} (94%) create mode 100644 docs-ui/src/app/components/grid/stories.tsx rename docs-ui/src/{content => app/components/header-page}/header-page.props.ts (100%) rename docs-ui/src/{content/header-page.mdx => app/components/header-page/page.mdx} (81%) create mode 100644 docs-ui/src/app/components/header-page/stories.tsx rename docs-ui/src/{content => app/components/header}/header.props.ts (100%) rename docs-ui/src/{content/header.mdx => app/components/header/page.mdx} (79%) create mode 100644 docs-ui/src/app/components/header/stories.tsx rename docs-ui/src/{content => app/components/link}/link.props.ts (100%) rename docs-ui/src/{content/link.mdx => app/components/link/page.mdx} (83%) create mode 100644 docs-ui/src/app/components/link/stories.tsx rename docs-ui/src/{content => app/components/menu}/menu.props.ts (100%) rename docs-ui/src/{content/menu.mdx => app/components/menu/page.mdx} (89%) create mode 100644 docs-ui/src/app/components/menu/stories.tsx create mode 100644 docs-ui/src/app/components/password-field/page.mdx rename docs-ui/src/{content => app/components/password-field}/password-field.props.ts (100%) create mode 100644 docs-ui/src/app/components/password-field/stories.tsx rename docs-ui/src/{content/popover.mdx => app/components/popover/page.mdx} (82%) rename docs-ui/src/{content => app/components/popover}/popover.props.ts (100%) create mode 100644 docs-ui/src/app/components/popover/stories.tsx rename docs-ui/src/{content/radio-group.mdx => app/components/radio-group/page.mdx} (81%) rename docs-ui/src/{content => app/components/radio-group}/radio-group.props.ts (100%) create mode 100644 docs-ui/src/app/components/radio-group/stories.tsx rename docs-ui/src/{content/search-field.mdx => app/components/search-field/page.mdx} (80%) rename docs-ui/src/{content => app/components/search-field}/search-field.props.ts (100%) create mode 100644 docs-ui/src/app/components/search-field/stories.tsx rename docs-ui/src/{content/select.mdx => app/components/select/page.mdx} (80%) rename docs-ui/src/{content => app/components/select}/select.props.ts (100%) create mode 100644 docs-ui/src/app/components/select/stories.tsx rename docs-ui/src/{content/skeleton.mdx => app/components/skeleton/page.mdx} (82%) rename docs-ui/src/{content => app/components/skeleton}/skeleton.props.ts (100%) create mode 100644 docs-ui/src/app/components/skeleton/stories.tsx rename docs-ui/src/{content/switch.mdx => app/components/switch/page.mdx} (81%) create mode 100644 docs-ui/src/app/components/switch/stories.tsx rename docs-ui/src/{content => app/components/switch}/switch.props.ts (100%) rename docs-ui/src/{content/table.mdx => app/components/table/page.mdx} (91%) create mode 100644 docs-ui/src/app/components/table/stories.tsx rename docs-ui/src/{content => app/components/table}/table.props.ts (100%) rename docs-ui/src/{content/tabs.mdx => app/components/tabs/page.mdx} (84%) create mode 100644 docs-ui/src/app/components/tabs/stories.tsx rename docs-ui/src/{content => app/components/tabs}/tabs.props.ts (100%) rename docs-ui/src/{content/tag-group.mdx => app/components/tag-group/page.mdx} (80%) create mode 100644 docs-ui/src/app/components/tag-group/stories.tsx rename docs-ui/src/{content => app/components/tag-group}/tag-group.props.ts (100%) rename docs-ui/src/{content/text-field.mdx => app/components/text-field/page.mdx} (80%) create mode 100644 docs-ui/src/app/components/text-field/stories.tsx rename docs-ui/src/{content => app/components/text-field}/text-field.props.ts (100%) rename docs-ui/src/{content/text.mdx => app/components/text/page.mdx} (82%) create mode 100644 docs-ui/src/app/components/text/stories.tsx rename docs-ui/src/{content => app/components/text}/text.props.ts (100%) rename docs-ui/src/{content/toggle-button-group.mdx => app/components/toggle-button-group/page.mdx} (74%) create mode 100644 docs-ui/src/app/components/toggle-button-group/stories.tsx rename docs-ui/src/{content => app/components/toggle-button-group}/toggle-button-group.props.ts (100%) rename docs-ui/src/{content/toggle-button.mdx => app/components/toggle-button/page.mdx} (79%) create mode 100644 docs-ui/src/app/components/toggle-button/stories.tsx rename docs-ui/src/{content => app/components/toggle-button}/toggle-button.props.ts (100%) rename docs-ui/src/{content/tooltip.mdx => app/components/tooltip/page.mdx} (85%) create mode 100644 docs-ui/src/app/components/tooltip/stories.tsx rename docs-ui/src/{content => app/components/tooltip}/tooltip.props.ts (100%) rename docs-ui/src/{content/visually-hidden.mdx => app/components/visually-hidden/page.mdx} (82%) create mode 100644 docs-ui/src/app/components/visually-hidden/stories.tsx rename docs-ui/src/{content => app/components/visually-hidden}/visually-hidden.props.ts (100%) delete mode 100644 docs-ui/src/content/PasswordField.tsx delete mode 100644 docs-ui/src/content/password-field.mdx delete mode 100644 docs-ui/src/snippets/stories-snippets.tsx diff --git a/docs-ui/src/app/components/[slug]/page.tsx b/docs-ui/src/app/components/[slug]/page.tsx deleted file mode 100644 index 1422334471..0000000000 --- a/docs-ui/src/app/components/[slug]/page.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { components, layoutComponents } from '@/utils/data'; - -export default async function Page({ - params, -}: { - params: Promise<{ slug: string }>; -}) { - const { slug } = await params; - - const { default: Component } = await import(`@/content/${slug}.mdx`); - - return ; -} - -export function generateStaticParams() { - const list = [...components, ...layoutComponents]; - - return list.map(component => ({ - slug: component.slug, - })); -} - -export const dynamicParams = false; diff --git a/docs-ui/src/content/accordion.props.ts b/docs-ui/src/app/components/accordion/accordion.props.ts similarity index 100% rename from docs-ui/src/content/accordion.props.ts rename to docs-ui/src/app/components/accordion/accordion.props.ts diff --git a/docs-ui/src/content/accordion.mdx b/docs-ui/src/app/components/accordion/page.mdx similarity index 84% rename from docs-ui/src/content/accordion.mdx rename to docs-ui/src/app/components/accordion/page.mdx index fc4bad1f8e..d2332673ea 100644 --- a/docs-ui/src/content/accordion.mdx +++ b/docs-ui/src/app/components/accordion/page.mdx @@ -1,7 +1,14 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { AccordionSnippet } from '@/snippets/stories-snippets'; +import { + Default, + WithSubtitle, + CustomTrigger, + DefaultExpanded, + GroupSingleOpen, + GroupMultipleOpen, +} from './stories'; import { accordionPropDefs, accordionTriggerPropDefs, @@ -16,7 +23,7 @@ import { } from './accordion.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { AccordionDefinition } from '../utils/definitions'; +import { AccordionDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={accordionUsageSnippet} /> @@ -72,7 +79,7 @@ Here's a view when using both title and subtitle props. align="center" py={4} height={240} - preview={} + preview={} code={accordionWithSubtitleSnippet} /> @@ -84,7 +91,7 @@ Here's a view when providing custom multi-line content as children. align="center" py={4} height={280} - preview={} + preview={} code={accordionCustomTriggerSnippet} /> @@ -96,7 +103,7 @@ Here's a view when the panel is expanded by default. align="center" py={4} height={280} - preview={} + preview={} code={accordionDefaultExpandedSnippet} /> @@ -108,7 +115,7 @@ Here's a view when only one accordion can be open at a time. align="center" py={4} height={280} - preview={} + preview={} code={accordionGroupSingleOpenSnippet} /> @@ -120,7 +127,7 @@ Here's a view when multiple accordions can be open simultaneously. align="center" py={4} height={280} - preview={} + preview={} code={accordionGroupMultipleOpenSnippet} /> diff --git a/docs-ui/src/app/components/accordion/stories.tsx b/docs-ui/src/app/components/accordion/stories.tsx new file mode 100644 index 0000000000..3f871b840b --- /dev/null +++ b/docs-ui/src/app/components/accordion/stories.tsx @@ -0,0 +1,19 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Accordion/Accordion.stories'; + +const { + Default: DefaultStory, + WithSubtitle: WithSubtitleStory, + CustomTrigger: CustomTriggerStory, + DefaultExpanded: DefaultExpandedStory, + GroupSingleOpen: GroupSingleOpenStory, + GroupMultipleOpen: GroupMultipleOpenStory, +} = stories; + +export const Default = () => ; +export const WithSubtitle = () => ; +export const CustomTrigger = () => ; +export const DefaultExpanded = () => ; +export const GroupSingleOpen = () => ; +export const GroupMultipleOpen = () => ; diff --git a/docs-ui/src/content/avatar.props.ts b/docs-ui/src/app/components/avatar/avatar.props.ts similarity index 100% rename from docs-ui/src/content/avatar.props.ts rename to docs-ui/src/app/components/avatar/avatar.props.ts diff --git a/docs-ui/src/content/avatar.mdx b/docs-ui/src/app/components/avatar/page.mdx similarity index 81% rename from docs-ui/src/content/avatar.mdx rename to docs-ui/src/app/components/avatar/page.mdx index 047485c2cc..5ec0d78fdd 100644 --- a/docs-ui/src/content/avatar.mdx +++ b/docs-ui/src/app/components/avatar/page.mdx @@ -1,7 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { AvatarSnippet } from '@/snippets/stories-snippets'; +import { Default, Sizes, Fallback, Purpose } from './stories'; import { avatarPropDefs, snippetUsage, @@ -11,7 +11,7 @@ import { } from './avatar.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { AvatarDefinition } from '../utils/definitions'; +import { AvatarDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={``} /> @@ -44,7 +44,7 @@ Avatar sizes can be set using the `size` prop. align="center" py={4} open - preview={} + preview={} code={snippetSizes} /> @@ -56,7 +56,7 @@ If the image is not available, the avatar will show the initials of the name. align="center" py={4} open - preview={} + preview={} code={snippetFallback} /> @@ -67,7 +67,7 @@ Control how the avatar is announced to screen readers using the `purpose` prop. } + preview={} code={snippetPurpose} /> diff --git a/docs-ui/src/app/components/avatar/stories.tsx b/docs-ui/src/app/components/avatar/stories.tsx new file mode 100644 index 0000000000..e8e9f601b1 --- /dev/null +++ b/docs-ui/src/app/components/avatar/stories.tsx @@ -0,0 +1,15 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Avatar/Avatar.stories'; + +const { + Default: DefaultStory, + Sizes: SizesStory, + Fallback: FallbackStory, + Purpose: PurposeStory, +} = stories; + +export const Default = () => ; +export const Sizes = () => ; +export const Fallback = () => ; +export const Purpose = () => ; diff --git a/docs-ui/src/content/box.props.ts b/docs-ui/src/app/components/box/box.props.ts similarity index 100% rename from docs-ui/src/content/box.props.ts rename to docs-ui/src/app/components/box/box.props.ts diff --git a/docs-ui/src/content/box.mdx b/docs-ui/src/app/components/box/page.mdx similarity index 90% rename from docs-ui/src/content/box.mdx rename to docs-ui/src/app/components/box/page.mdx index 66e99ec19a..f6e50b28d7 100644 --- a/docs-ui/src/content/box.mdx +++ b/docs-ui/src/app/components/box/page.mdx @@ -1,7 +1,7 @@ import { CodeBlock } from '@/components/CodeBlock'; import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; -import { BoxSnippet } from '@/snippets/stories-snippets'; +import { Default } from './stories'; import { boxPropDefs, snippetUsage, @@ -12,7 +12,7 @@ import { import { spacingPropDefs } from '@/utils/propDefs'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { BoxDefinition } from '../utils/definitions'; +import { BoxDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={boxPreviewSnippet} align="center" /> diff --git a/docs-ui/src/app/components/box/stories.tsx b/docs-ui/src/app/components/box/stories.tsx new file mode 100644 index 0000000000..82afa5fdff --- /dev/null +++ b/docs-ui/src/app/components/box/stories.tsx @@ -0,0 +1,7 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Box/Box.stories'; + +const { Default: DefaultStory } = stories; + +export const Default = () => ; diff --git a/docs-ui/src/content/button-icon.props.ts b/docs-ui/src/app/components/button-icon/button-icon.props.ts similarity index 100% rename from docs-ui/src/content/button-icon.props.ts rename to docs-ui/src/app/components/button-icon/button-icon.props.ts diff --git a/docs-ui/src/content/button-icon.mdx b/docs-ui/src/app/components/button-icon/page.mdx similarity index 81% rename from docs-ui/src/content/button-icon.mdx rename to docs-ui/src/app/components/button-icon/page.mdx index b0595cc9bc..fc2d6eec9f 100644 --- a/docs-ui/src/content/button-icon.mdx +++ b/docs-ui/src/app/components/button-icon/page.mdx @@ -1,7 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { ButtonIconSnippet } from '@/snippets/stories-snippets'; +import { Variants, Sizes, Disabled, Loading } from './stories'; import { buttonIconPropDefs, buttonIconUsageSnippet, @@ -15,7 +15,7 @@ import { } from './button-icon.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { ButtonIconDefinition } from '../utils/definitions'; +import { ButtonIconDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={buttonIconDefaultSnippet} /> @@ -48,7 +48,7 @@ Here's a view when buttons have different variants. align="center" py={4} open - preview={} + preview={} code={buttonIconVariantsSnippet} /> @@ -60,7 +60,7 @@ Here's a view when buttons have different sizes. align="center" py={4} open - preview={} + preview={} code={buttonIconSizesSnippet} /> @@ -72,7 +72,7 @@ Here's a view when buttons are disabled. align="center" py={4} open - preview={} + preview={} code={buttonIconDisabledSnippet} /> @@ -84,7 +84,7 @@ Here's a view when buttons are in a loading state. align="center" py={4} open - preview={} + preview={} code={buttonIconLoadingSnippet} /> diff --git a/docs-ui/src/app/components/button-icon/stories.tsx b/docs-ui/src/app/components/button-icon/stories.tsx new file mode 100644 index 0000000000..5d28fb28a6 --- /dev/null +++ b/docs-ui/src/app/components/button-icon/stories.tsx @@ -0,0 +1,15 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/ButtonIcon/ButtonIcon.stories'; + +const { + Variants: VariantsStory, + Sizes: SizesStory, + Disabled: DisabledStory, + Loading: LoadingStory, +} = stories; + +export const Variants = () => ; +export const Sizes = () => ; +export const Disabled = () => ; +export const Loading = () => ; diff --git a/docs-ui/src/content/button-link.props.ts b/docs-ui/src/app/components/button-link/button-link.props.ts similarity index 100% rename from docs-ui/src/content/button-link.props.ts rename to docs-ui/src/app/components/button-link/button-link.props.ts diff --git a/docs-ui/src/content/button-link.mdx b/docs-ui/src/app/components/button-link/page.mdx similarity index 81% rename from docs-ui/src/content/button-link.mdx rename to docs-ui/src/app/components/button-link/page.mdx index 4f3f376aee..074ea204ec 100644 --- a/docs-ui/src/content/button-link.mdx +++ b/docs-ui/src/app/components/button-link/page.mdx @@ -1,7 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { ButtonLinkSnippet } from '@/snippets/stories-snippets'; +import { Variants, Sizes, WithIcons, Disabled } from './stories'; import { buttonLinkPropDefs, buttonLinkSnippetUsage, @@ -13,7 +13,7 @@ import { } from './button-link.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { ButtonLinkDefinition } from '../utils/definitions'; +import { ButtonLinkDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={buttonLinkVariantsSnippet} /> @@ -46,7 +46,7 @@ Here's a view when buttons have different variants. align="center" py={4} open - preview={} + preview={} code={buttonLinkVariantsSnippet} /> @@ -58,7 +58,7 @@ Here's a view when buttons have different sizes. align="center" py={4} open - preview={} + preview={} code={buttonLinkSizesSnippet} /> @@ -70,7 +70,7 @@ Here's a view when buttons have icons. align="center" py={4} open - preview={} + preview={} code={buttonLinkIconsSnippet} /> @@ -82,7 +82,7 @@ Here's a view when buttons are disabled. align="center" py={4} open - preview={} + preview={} code={buttonLinkDisabledSnippet} /> diff --git a/docs-ui/src/app/components/button-link/stories.tsx b/docs-ui/src/app/components/button-link/stories.tsx new file mode 100644 index 0000000000..a865afe87c --- /dev/null +++ b/docs-ui/src/app/components/button-link/stories.tsx @@ -0,0 +1,15 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/ButtonLink/ButtonLink.stories'; + +const { + Variants: VariantsStory, + Sizes: SizesStory, + WithIcons: WithIconsStory, + Disabled: DisabledStory, +} = stories; + +export const Variants = () => ; +export const Sizes = () => ; +export const WithIcons = () => ; +export const Disabled = () => ; diff --git a/docs-ui/src/content/button.props.ts b/docs-ui/src/app/components/button/button.props.ts similarity index 100% rename from docs-ui/src/content/button.props.ts rename to docs-ui/src/app/components/button/button.props.ts diff --git a/docs-ui/src/content/button.mdx b/docs-ui/src/app/components/button/page.mdx similarity index 80% rename from docs-ui/src/content/button.mdx rename to docs-ui/src/app/components/button/page.mdx index d161c97edf..daead3ba92 100644 --- a/docs-ui/src/content/button.mdx +++ b/docs-ui/src/app/components/button/page.mdx @@ -1,7 +1,8 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { ButtonSnippet, ButtonLinkSnippet } from '@/snippets/stories-snippets'; +import { Variants, Sizes, WithIcons, Disabled, Loading } from './stories'; +import { Variants as ButtonLinkVariants } from '../button-link/stories'; import { buttonPropDefs, buttonSnippetUsage, @@ -16,7 +17,7 @@ import { import { ChangelogComponent } from '@/components/ChangelogComponent'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { ButtonDefinition } from '../utils/definitions'; +import { ButtonDefinition } from '../../../utils/definitions'; } + preview={} code={buttonVariantsSnippet} /> @@ -48,7 +49,7 @@ Here's a view when buttons have different variants. align="center" py={4} open - preview={} + preview={} code={buttonVariantsSnippet} /> @@ -60,7 +61,7 @@ Here's a view when buttons have different sizes. align="center" py={4} open - preview={} + preview={} code={buttonSizesSnippet} /> @@ -72,7 +73,7 @@ Here's a view when buttons have icons. align="center" py={4} open - preview={} + preview={} code={buttonIconsSnippet} /> @@ -84,7 +85,7 @@ Here's a view when buttons are disabled. align="center" py={4} open - preview={} + preview={} code={buttonDisabledSnippet} /> @@ -96,7 +97,7 @@ Here's a view when buttons are in a loading state. align="center" py={4} open - preview={} + preview={} code={buttonLoadingSnippet} /> @@ -114,7 +115,7 @@ If you want to use a button as a link, please use the `ButtonLink` component. align="center" py={4} open - preview={} + preview={} code={buttonAsLinkSnippet} /> diff --git a/docs-ui/src/app/components/button/stories.tsx b/docs-ui/src/app/components/button/stories.tsx new file mode 100644 index 0000000000..b958eb8fdf --- /dev/null +++ b/docs-ui/src/app/components/button/stories.tsx @@ -0,0 +1,17 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Button/Button.stories'; + +const { + Variants: VariantsStory, + Sizes: SizesStory, + WithIcons: WithIconsStory, + Disabled: DisabledStory, + Loading: LoadingStory, +} = stories; + +export const Variants = () => ; +export const Sizes = () => ; +export const WithIcons = () => ; +export const Disabled = () => ; +export const Loading = () => ; diff --git a/docs-ui/src/content/card.props.ts b/docs-ui/src/app/components/card/card.props.ts similarity index 100% rename from docs-ui/src/content/card.props.ts rename to docs-ui/src/app/components/card/card.props.ts diff --git a/docs-ui/src/content/card.mdx b/docs-ui/src/app/components/card/page.mdx similarity index 86% rename from docs-ui/src/content/card.mdx rename to docs-ui/src/app/components/card/page.mdx index c38a07e920..fd1ed233d9 100644 --- a/docs-ui/src/content/card.mdx +++ b/docs-ui/src/app/components/card/page.mdx @@ -1,5 +1,5 @@ import { PropsTable } from '@/components/PropsTable'; -import { CardSnippet } from '@/snippets/stories-snippets'; +import { CustomSize, WithLongBody, WithListRow } from './stories'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; import { @@ -14,7 +14,7 @@ import { } from './card.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { CardDefinition } from '../utils/definitions'; +import { CardDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={cardDefaultSnippet} /> @@ -68,7 +68,7 @@ Here's a view when card has a long body. } + preview={} code={cardLongBodySnippet} open /> @@ -80,7 +80,7 @@ Here's a view when card has a list. } + preview={} code={cardListRowSnippet} open /> diff --git a/docs-ui/src/app/components/card/stories.tsx b/docs-ui/src/app/components/card/stories.tsx new file mode 100644 index 0000000000..d9c4b80ec4 --- /dev/null +++ b/docs-ui/src/app/components/card/stories.tsx @@ -0,0 +1,13 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Card/Card.stories'; + +const { + CustomSize: CustomSizeStory, + WithLongBody: WithLongBodyStory, + WithListRow: WithListRowStory, +} = stories; + +export const CustomSize = () => ; +export const WithLongBody = () => ; +export const WithListRow = () => ; diff --git a/docs-ui/src/content/checkbox.props.ts b/docs-ui/src/app/components/checkbox/checkbox.props.ts similarity index 100% rename from docs-ui/src/content/checkbox.props.ts rename to docs-ui/src/app/components/checkbox/checkbox.props.ts diff --git a/docs-ui/src/content/checkbox.mdx b/docs-ui/src/app/components/checkbox/page.mdx similarity index 81% rename from docs-ui/src/content/checkbox.mdx rename to docs-ui/src/app/components/checkbox/page.mdx index c574e8fed4..5f1735bc50 100644 --- a/docs-ui/src/content/checkbox.mdx +++ b/docs-ui/src/app/components/checkbox/page.mdx @@ -1,5 +1,5 @@ import { PropsTable } from '@/components/PropsTable'; -import { CheckboxSnippet } from '@/snippets/stories-snippets'; +import { Default, AllVariants } from './stories'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; import { @@ -10,7 +10,7 @@ import { } from './checkbox.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { CheckboxDefinition } from '../utils/definitions'; +import { CheckboxDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={checkboxDefaultSnippet} /> @@ -43,7 +43,7 @@ Here's a view when checkboxes have different variants. align="center" py={4} open - preview={} + preview={} code={checkboxVariantsSnippet} /> diff --git a/docs-ui/src/app/components/checkbox/stories.tsx b/docs-ui/src/app/components/checkbox/stories.tsx new file mode 100644 index 0000000000..55d75ed3a6 --- /dev/null +++ b/docs-ui/src/app/components/checkbox/stories.tsx @@ -0,0 +1,8 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Checkbox/Checkbox.stories'; + +const { Default: DefaultStory, AllVariants: AllVariantsStory } = stories; + +export const Default = () => ; +export const AllVariants = () => ; diff --git a/docs-ui/src/content/container.props.ts b/docs-ui/src/app/components/container/container.props.ts similarity index 100% rename from docs-ui/src/content/container.props.ts rename to docs-ui/src/app/components/container/container.props.ts diff --git a/docs-ui/src/content/container.mdx b/docs-ui/src/app/components/container/page.mdx similarity index 87% rename from docs-ui/src/content/container.mdx rename to docs-ui/src/app/components/container/page.mdx index 56c2d5d405..18c307b104 100644 --- a/docs-ui/src/content/container.mdx +++ b/docs-ui/src/app/components/container/page.mdx @@ -1,7 +1,7 @@ import { CodeBlock } from '@/components/CodeBlock'; import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; -import { ContainerSnippet } from '@/snippets/stories-snippets'; +import { Preview } from './stories'; import { containerPropDefs, containerUsageSnippet, @@ -11,7 +11,7 @@ import { } from './container.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { ContainerDefinition } from '../utils/definitions'; +import { ContainerDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={containerDefaultSnippet} /> diff --git a/docs-ui/src/app/components/container/stories.tsx b/docs-ui/src/app/components/container/stories.tsx new file mode 100644 index 0000000000..95f4097e12 --- /dev/null +++ b/docs-ui/src/app/components/container/stories.tsx @@ -0,0 +1,7 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Container/Container.stories'; + +const { Preview: PreviewStory } = stories; + +export const Preview = () => ; diff --git a/docs-ui/src/content/dialog.props.ts b/docs-ui/src/app/components/dialog/dialog.props.ts similarity index 100% rename from docs-ui/src/content/dialog.props.ts rename to docs-ui/src/app/components/dialog/dialog.props.ts diff --git a/docs-ui/src/content/dialog.mdx b/docs-ui/src/app/components/dialog/page.mdx similarity index 88% rename from docs-ui/src/content/dialog.mdx rename to docs-ui/src/app/components/dialog/page.mdx index e1e4ec436e..b80a3f4a69 100644 --- a/docs-ui/src/content/dialog.mdx +++ b/docs-ui/src/app/components/dialog/page.mdx @@ -1,7 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { DialogSnippet } from '@/snippets/stories-snippets'; +import { Default, PreviewFixedWidthAndHeight, PreviewWithForm } from './stories'; import { dialogPropDefs, dialogTriggerPropDefs, @@ -18,7 +18,7 @@ import { } from './dialog.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { DialogDefinition } from '../utils/definitions'; +import { DialogDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={dialogDefaultSnippet} /> @@ -82,7 +82,7 @@ Dialog with a fixed height body that scrolls when content overflows. } + preview={} code={dialogFixedWidthAndHeightSnippet} /> @@ -93,7 +93,7 @@ Dialog containing form elements for user input. } + preview={} code={dialogWithFormSnippet} /> diff --git a/docs-ui/src/app/components/dialog/stories.tsx b/docs-ui/src/app/components/dialog/stories.tsx new file mode 100644 index 0000000000..46e41fe742 --- /dev/null +++ b/docs-ui/src/app/components/dialog/stories.tsx @@ -0,0 +1,15 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Dialog/Dialog.stories'; + +const { + Default: DefaultStory, + PreviewFixedWidthAndHeight: PreviewFixedWidthAndHeightStory, + PreviewWithForm: PreviewWithFormStory, +} = stories; + +export const Default = () => ; +export const PreviewFixedWidthAndHeight = () => ( + +); +export const PreviewWithForm = () => ; diff --git a/docs-ui/src/content/flex.props.ts b/docs-ui/src/app/components/flex/flex.props.ts similarity index 100% rename from docs-ui/src/content/flex.props.ts rename to docs-ui/src/app/components/flex/flex.props.ts diff --git a/docs-ui/src/content/flex.mdx b/docs-ui/src/app/components/flex/page.mdx similarity index 90% rename from docs-ui/src/content/flex.mdx rename to docs-ui/src/app/components/flex/page.mdx index 36284f1f0b..1083642f28 100644 --- a/docs-ui/src/content/flex.mdx +++ b/docs-ui/src/app/components/flex/page.mdx @@ -1,7 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { CodeBlock } from '@/components/CodeBlock'; import { Snippet } from '@/components/Snippet'; -import { FlexSnippet } from '@/snippets/stories-snippets'; +import { Default } from './stories'; import { flexPropDefs, flexUsageSnippet, @@ -14,7 +14,7 @@ import { import { spacingPropDefs } from '@/utils/propDefs'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { FlexDefinition } from '../utils/definitions'; +import { FlexDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={flexDefaultSnippet} /> diff --git a/docs-ui/src/app/components/flex/stories.tsx b/docs-ui/src/app/components/flex/stories.tsx new file mode 100644 index 0000000000..1770374930 --- /dev/null +++ b/docs-ui/src/app/components/flex/stories.tsx @@ -0,0 +1,7 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Flex/Flex.stories'; + +const { Default: DefaultStory } = stories; + +export const Default = () => ; diff --git a/docs-ui/src/content/grid.props.ts b/docs-ui/src/app/components/grid/grid.props.ts similarity index 100% rename from docs-ui/src/content/grid.props.ts rename to docs-ui/src/app/components/grid/grid.props.ts diff --git a/docs-ui/src/content/grid.mdx b/docs-ui/src/app/components/grid/page.mdx similarity index 94% rename from docs-ui/src/content/grid.mdx rename to docs-ui/src/app/components/grid/page.mdx index 5f179b9e14..bcf83f985e 100644 --- a/docs-ui/src/content/grid.mdx +++ b/docs-ui/src/app/components/grid/page.mdx @@ -1,7 +1,7 @@ import { CodeBlock } from '@/components/CodeBlock'; import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; -import { GridSnippet } from '@/snippets/stories-snippets'; +import { Default } from './stories'; import { spacingPropDefs } from '@/utils/propDefs'; import { gridPropDefs, @@ -16,7 +16,7 @@ import { } from './grid.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { GridDefinition } from '../utils/definitions'; +import { GridDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={gridDefaultSnippet} /> diff --git a/docs-ui/src/app/components/grid/stories.tsx b/docs-ui/src/app/components/grid/stories.tsx new file mode 100644 index 0000000000..137f474fb6 --- /dev/null +++ b/docs-ui/src/app/components/grid/stories.tsx @@ -0,0 +1,7 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Grid/Grid.stories'; + +const { Default: DefaultStory } = stories; + +export const Default = () => ; diff --git a/docs-ui/src/content/header-page.props.ts b/docs-ui/src/app/components/header-page/header-page.props.ts similarity index 100% rename from docs-ui/src/content/header-page.props.ts rename to docs-ui/src/app/components/header-page/header-page.props.ts diff --git a/docs-ui/src/content/header-page.mdx b/docs-ui/src/app/components/header-page/page.mdx similarity index 81% rename from docs-ui/src/content/header-page.mdx rename to docs-ui/src/app/components/header-page/page.mdx index 12ef2c32ee..459daac1f2 100644 --- a/docs-ui/src/content/header-page.mdx +++ b/docs-ui/src/app/components/header-page/page.mdx @@ -1,7 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { CodeBlock } from '@/components/CodeBlock'; import { Snippet } from '@/components/Snippet'; -import { HeaderPageSnippet } from '@/snippets/stories-snippets'; +import { WithEverything, WithLongBreadcrumbs, WithTabs, WithCustomActions, WithMenuItems } from './stories'; import { propDefs, usage, @@ -14,7 +14,7 @@ import { } from './header-page.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { HeaderPageDefinition } from '../utils/definitions'; +import { HeaderPageDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={defaultSnippet} /> @@ -46,7 +46,7 @@ prop is an array of objects with a `label` and `href` property. By default we tr } + preview={} code={withBreadcrumbs} /> @@ -57,7 +57,7 @@ prop is an array of objects with a `label` and `href` property. } + preview={} code={withTabs} /> @@ -68,7 +68,7 @@ prop is a React node. } + preview={} code={withCustomActions} /> @@ -79,7 +79,7 @@ prop is an array of objects with a `label`, `value` and `onClick` property. } + preview={} code={withMenuItems} /> diff --git a/docs-ui/src/app/components/header-page/stories.tsx b/docs-ui/src/app/components/header-page/stories.tsx new file mode 100644 index 0000000000..fce9e9bf82 --- /dev/null +++ b/docs-ui/src/app/components/header-page/stories.tsx @@ -0,0 +1,17 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/HeaderPage/HeaderPage.stories'; + +const { + WithEverything: WithEverythingStory, + WithLongBreadcrumbs: WithLongBreadcrumbsStory, + WithTabs: WithTabsStory, + WithCustomActions: WithCustomActionsStory, + WithMenuItems: WithMenuItemsStory, +} = stories; + +export const WithEverything = () => ; +export const WithLongBreadcrumbs = () => ; +export const WithTabs = () => ; +export const WithCustomActions = () => ; +export const WithMenuItems = () => ; diff --git a/docs-ui/src/content/header.props.ts b/docs-ui/src/app/components/header/header.props.ts similarity index 100% rename from docs-ui/src/content/header.props.ts rename to docs-ui/src/app/components/header/header.props.ts diff --git a/docs-ui/src/content/header.mdx b/docs-ui/src/app/components/header/page.mdx similarity index 79% rename from docs-ui/src/content/header.mdx rename to docs-ui/src/app/components/header/page.mdx index eb505d7a62..f1ffdf8b33 100644 --- a/docs-ui/src/content/header.mdx +++ b/docs-ui/src/app/components/header/page.mdx @@ -1,7 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { CodeBlock } from '@/components/CodeBlock'; import { Snippet } from '@/components/Snippet'; -import { HeaderSnippet } from '@/snippets/stories-snippets'; +import { WithAllOptionsAndTabs, WithAllOptions, WithBreadcrumbs, WithHeaderPage } from './stories'; import { propDefs, usage, @@ -13,7 +13,7 @@ import { } from './header.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { HeaderDefinition } from '../utils/definitions'; +import { HeaderDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={defaultSnippet} /> @@ -44,7 +44,7 @@ A simple example of how to use the Header component. } + preview={} code={simple} open /> @@ -56,7 +56,7 @@ under the hood and will be active when you are on the corresponding page. } +preview={} code={withTabs} open /> @@ -66,7 +66,7 @@ open Breacrumbs should appear when you scroll down (and not directly visible as it is in the demo below). } + preview={} code={withBreadcrumbs} open /> @@ -76,7 +76,7 @@ Breacrumbs should appear when you scroll down (and not directly visible as it is You can use the `Header` component inside the [HeaderPage](/components/header-page) component to compose your multi-level navigation. } + preview={} code={withHeaderPage} open /> diff --git a/docs-ui/src/app/components/header/stories.tsx b/docs-ui/src/app/components/header/stories.tsx new file mode 100644 index 0000000000..526063e0df --- /dev/null +++ b/docs-ui/src/app/components/header/stories.tsx @@ -0,0 +1,17 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Header/Header.stories'; + +const { + WithAllOptionsAndTabs: WithAllOptionsAndTabsStory, + WithAllOptions: WithAllOptionsStory, + WithBreadcrumbs: WithBreadcrumbsStory, + WithHeaderPage: WithHeaderPageStory, +} = stories; + +export const WithAllOptionsAndTabs = () => ( + +); +export const WithAllOptions = () => ; +export const WithBreadcrumbs = () => ; +export const WithHeaderPage = () => ; diff --git a/docs-ui/src/content/link.props.ts b/docs-ui/src/app/components/link/link.props.ts similarity index 100% rename from docs-ui/src/content/link.props.ts rename to docs-ui/src/app/components/link/link.props.ts diff --git a/docs-ui/src/content/link.mdx b/docs-ui/src/app/components/link/page.mdx similarity index 83% rename from docs-ui/src/content/link.mdx rename to docs-ui/src/app/components/link/page.mdx index 486c06b298..e71c43d73b 100644 --- a/docs-ui/src/content/link.mdx +++ b/docs-ui/src/app/components/link/page.mdx @@ -1,11 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { - MenuSnippet, - ButtonSnippet, - LinkSnippet, -} from '@/snippets/stories-snippets'; +import { Default, AllVariants, AllWeights, AllColors, Truncate } from './stories'; import { linkPropDefs, linkUsageSnippet, @@ -19,7 +15,7 @@ import { import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { LinkDefinition } from '../utils/definitions'; +import { LinkDefinition } from '../../../utils/definitions'; } + preview={} code={linkDefaultSnippet} /> @@ -60,7 +56,7 @@ Here's a view when links have different variants. align="center" py={4} open - preview={} + preview={} code={linkVariantsSnippet} /> @@ -72,7 +68,7 @@ Here's a view when links have different weights. align="center" py={4} open - preview={} + preview={} code={linkWeightsSnippet} /> @@ -84,7 +80,7 @@ Here's a view when links have different colors. align="center" py={4} open - preview={} + preview={} code={linkColorsSnippet} /> @@ -94,7 +90,7 @@ The `Link` component has a `truncate` prop that can be used to truncate the text } + preview={} code={linkTruncateSnippet} /> diff --git a/docs-ui/src/app/components/link/stories.tsx b/docs-ui/src/app/components/link/stories.tsx new file mode 100644 index 0000000000..16d4906a24 --- /dev/null +++ b/docs-ui/src/app/components/link/stories.tsx @@ -0,0 +1,17 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Link/Link.stories'; + +const { + Default: DefaultStory, + AllVariants: AllVariantsStory, + AllWeights: AllWeightsStory, + AllColors: AllColorsStory, + Truncate: TruncateStory, +} = stories; + +export const Default = () => ; +export const AllVariants = () => ; +export const AllWeights = () => ; +export const AllColors = () => ; +export const Truncate = () => ; diff --git a/docs-ui/src/content/menu.props.ts b/docs-ui/src/app/components/menu/menu.props.ts similarity index 100% rename from docs-ui/src/content/menu.props.ts rename to docs-ui/src/app/components/menu/menu.props.ts diff --git a/docs-ui/src/content/menu.mdx b/docs-ui/src/app/components/menu/page.mdx similarity index 89% rename from docs-ui/src/content/menu.mdx rename to docs-ui/src/app/components/menu/page.mdx index 9ede0885e2..f6327d9d11 100644 --- a/docs-ui/src/content/menu.mdx +++ b/docs-ui/src/app/components/menu/page.mdx @@ -1,7 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { MenuSnippet } from '@/snippets/stories-snippets'; +import { Preview, PreviewSubmenu, PreviewIcons, PreviewLinks, PreviewSections, PreviewSeparators, PreviewAutocompleteMenu, PreviewAutocompleteListbox, PreviewAutocompleteListboxMultiple } from './stories'; import { usage, preview, @@ -27,7 +27,7 @@ import { import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { MenuDefinition } from '../utils/definitions'; +import { MenuDefinition } from '../../../utils/definitions'; } + preview={} code={preview} /> @@ -142,7 +142,7 @@ the submenu is displayed in the correct position. The best practice is to use th align="center" py={4} open - preview={} + preview={} code={submenu} /> @@ -154,7 +154,7 @@ You can use the `iconStart` prop to add an icon to the menu item. align="center" py={4} open - preview={} + preview={} code={icons} /> @@ -167,7 +167,7 @@ to work for both internal and external links. align="center" py={4} open - preview={} + preview={} code={links} /> @@ -179,7 +179,7 @@ You can use the `MenuSection` component to add a section to the menu. align="center" py={4} open - preview={} + preview={} code={sections} /> @@ -191,7 +191,7 @@ You can use the `MenuSeparator` component to add a separator to the menu. align="center" py={4} open - preview={} + preview={} code={separators} /> @@ -203,7 +203,7 @@ You can use the `MenuAutocomplete` component to add a autocomplete to the menu. align="center" py={4} open - preview={} + preview={} code={autocomplete} /> @@ -215,7 +215,7 @@ You can use the `MenuListBox` component to add a list box to the menu. align="center" py={4} open - preview={} + preview={} code={autocompleteListbox} /> @@ -228,7 +228,7 @@ allow multiple selection. align="center" py={4} open - preview={} + preview={} code={autocompleteListboxMultiple} /> diff --git a/docs-ui/src/app/components/menu/stories.tsx b/docs-ui/src/app/components/menu/stories.tsx new file mode 100644 index 0000000000..3e4f445ec2 --- /dev/null +++ b/docs-ui/src/app/components/menu/stories.tsx @@ -0,0 +1,31 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Menu/Menu.stories'; + +const { + Preview: PreviewStory, + PreviewSubmenu: PreviewSubmenuStory, + PreviewIcons: PreviewIconsStory, + PreviewLinks: PreviewLinksStory, + PreviewSections: PreviewSectionsStory, + PreviewSeparators: PreviewSeparatorsStory, + PreviewAutocompleteMenu: PreviewAutocompleteMenuStory, + PreviewAutocompleteListbox: PreviewAutocompleteListboxStory, + PreviewAutocompleteListboxMultiple: PreviewAutocompleteListboxMultipleStory, +} = stories; + +export const Preview = () => ; +export const PreviewSubmenu = () => ; +export const PreviewIcons = () => ; +export const PreviewLinks = () => ; +export const PreviewSections = () => ; +export const PreviewSeparators = () => ; +export const PreviewAutocompleteMenu = () => ( + +); +export const PreviewAutocompleteListbox = () => ( + +); +export const PreviewAutocompleteListboxMultiple = () => ( + +); diff --git a/docs-ui/src/app/components/password-field/page.mdx b/docs-ui/src/app/components/password-field/page.mdx new file mode 100644 index 0000000000..80f2774042 --- /dev/null +++ b/docs-ui/src/app/components/password-field/page.mdx @@ -0,0 +1,65 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { + inputPropDefs, + passwordFieldUsageSnippet, + passwordFieldDefaultSnippet, + passwordFieldSizesSnippet, + passwordFieldDescriptionSnippet, +} from './password-field.props'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { CodeBlock } from '@/components/CodeBlock'; +import { PasswordFieldDefinition } from '../../../utils/definitions'; +import { WithLabel, Sizes, WithDescription } from './stories'; + + + +} + code={passwordFieldDefaultSnippet} +/> + +## Usage + + + +## API reference + + + +## Examples + +### Sizes + +We support two different sizes: `small`, `medium`. + +} + code={passwordFieldSizesSnippet} +/> + +### With description + +Here's a simple PasswordField with a description. + +} + code={passwordFieldDescriptionSnippet} +/> + + + + diff --git a/docs-ui/src/content/password-field.props.ts b/docs-ui/src/app/components/password-field/password-field.props.ts similarity index 100% rename from docs-ui/src/content/password-field.props.ts rename to docs-ui/src/app/components/password-field/password-field.props.ts diff --git a/docs-ui/src/app/components/password-field/stories.tsx b/docs-ui/src/app/components/password-field/stories.tsx new file mode 100644 index 0000000000..407c716fb1 --- /dev/null +++ b/docs-ui/src/app/components/password-field/stories.tsx @@ -0,0 +1,13 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/PasswordField/PasswordField.stories'; + +const { + WithLabel: WithLabelStory, + Sizes: SizesStory, + WithDescription: WithDescriptionStory, +} = stories; + +export const WithLabel = () => ; +export const Sizes = () => ; +export const WithDescription = () => ; diff --git a/docs-ui/src/content/popover.mdx b/docs-ui/src/app/components/popover/page.mdx similarity index 82% rename from docs-ui/src/content/popover.mdx rename to docs-ui/src/app/components/popover/page.mdx index ca3f728b01..8e9a948c71 100644 --- a/docs-ui/src/content/popover.mdx +++ b/docs-ui/src/app/components/popover/page.mdx @@ -1,5 +1,5 @@ import { PropsTable } from '@/components/PropsTable'; -import { PopoverSnippet } from '@/snippets/stories-snippets'; +import { Default } from './stories'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; import { @@ -13,7 +13,7 @@ import { } from './popover.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { PopoverDefinition } from '../utils/definitions'; +import { PopoverDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={popoverDefaultSnippet} /> @@ -49,7 +49,7 @@ Popover supports multiple placement options to position the content relative to } + preview={} code={popoverPlacementsSnippet} /> @@ -60,7 +60,7 @@ You can hide the arrow by setting the `hideArrow` prop. } + preview={} code={popoverNoArrowSnippet} /> @@ -71,7 +71,7 @@ Popovers can contain complex content including buttons, forms, and other interac } + preview={} code={popoverRichContentSnippet} /> @@ -82,7 +82,7 @@ Non-modal popovers allow interaction with other elements on the page while the p } + preview={} code={popoverNonModalSnippet} /> @@ -93,7 +93,7 @@ Popovers automatically handle scrolling when content exceeds the available viewp } + preview={} code={popoverDefaultSnippet} /> diff --git a/docs-ui/src/content/popover.props.ts b/docs-ui/src/app/components/popover/popover.props.ts similarity index 100% rename from docs-ui/src/content/popover.props.ts rename to docs-ui/src/app/components/popover/popover.props.ts diff --git a/docs-ui/src/app/components/popover/stories.tsx b/docs-ui/src/app/components/popover/stories.tsx new file mode 100644 index 0000000000..5a01027a67 --- /dev/null +++ b/docs-ui/src/app/components/popover/stories.tsx @@ -0,0 +1,7 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Popover/Popover.stories'; + +const { Default: DefaultStory } = stories; + +export const Default = () => ; diff --git a/docs-ui/src/content/radio-group.mdx b/docs-ui/src/app/components/radio-group/page.mdx similarity index 81% rename from docs-ui/src/content/radio-group.mdx rename to docs-ui/src/app/components/radio-group/page.mdx index ba9352c026..de8379574f 100644 --- a/docs-ui/src/content/radio-group.mdx +++ b/docs-ui/src/app/components/radio-group/page.mdx @@ -1,6 +1,6 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; -import { RadioGroupSnippet } from '@/snippets/stories-snippets'; +import { Default, Horizontal, Disabled, DisabledSingle, Validation, ReadOnly } from './stories'; import { radioGroupPropDefs, radioGroupUsageSnippet, @@ -16,7 +16,7 @@ import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; import { CodeBlock } from '@/components/CodeBlock'; -import { RadioGroupDefinition } from '../utils/definitions'; +import { RadioGroupDefinition } from '../../../utils/definitions'; } + preview={} code={radioGroupDefaultSnippet} /> @@ -48,7 +48,7 @@ Here's a simple TextField with a description. align="center" py={4} open - preview={} + preview={} code={radioGroupHorizontalSnippet} /> @@ -60,7 +60,7 @@ You can disable the entire radio group by adding the `isDisabled` prop to the `R align="center" py={4} open - preview={} + preview={} code={radioGroupDisabledSnippet} /> @@ -72,7 +72,7 @@ You can disable a single radio by adding the `isDisabled` prop to the `Radio` co align="center" py={4} open - preview={} + preview={} code={radioGroupDisabledSingleSnippet} /> @@ -84,7 +84,7 @@ Here's an example of a radio group with errors. align="center" py={4} open - preview={} + preview={} code={radioGroupValidationSnippet} /> @@ -96,7 +96,7 @@ You can make the radio group read only by adding the `isReadOnly` prop to the `R align="center" py={4} open - preview={} + preview={} code={radioGroupReadOnlySnippet} /> diff --git a/docs-ui/src/content/radio-group.props.ts b/docs-ui/src/app/components/radio-group/radio-group.props.ts similarity index 100% rename from docs-ui/src/content/radio-group.props.ts rename to docs-ui/src/app/components/radio-group/radio-group.props.ts diff --git a/docs-ui/src/app/components/radio-group/stories.tsx b/docs-ui/src/app/components/radio-group/stories.tsx new file mode 100644 index 0000000000..23853d3bcd --- /dev/null +++ b/docs-ui/src/app/components/radio-group/stories.tsx @@ -0,0 +1,19 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/RadioGroup/RadioGroup.stories'; + +const { + Default: DefaultStory, + Horizontal: HorizontalStory, + Disabled: DisabledStory, + DisabledSingle: DisabledSingleStory, + Validation: ValidationStory, + ReadOnly: ReadOnlyStory, +} = stories; + +export const Default = () => ; +export const Horizontal = () => ; +export const Disabled = () => ; +export const DisabledSingle = () => ; +export const Validation = () => ; +export const ReadOnly = () => ; diff --git a/docs-ui/src/content/search-field.mdx b/docs-ui/src/app/components/search-field/page.mdx similarity index 80% rename from docs-ui/src/content/search-field.mdx rename to docs-ui/src/app/components/search-field/page.mdx index 6b33feea42..9be0dc36af 100644 --- a/docs-ui/src/content/search-field.mdx +++ b/docs-ui/src/app/components/search-field/page.mdx @@ -1,6 +1,6 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; -import { SearchFieldSnippet } from '@/snippets/stories-snippets'; +import { WithLabel, Sizes, WithDescription, StartCollapsed } from './stories'; import { searchFieldPropDefs, searchFieldUsageSnippet, @@ -13,7 +13,7 @@ import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; import { CodeBlock } from '@/components/CodeBlock'; -import { SearchFieldDefinition } from '../utils/definitions'; +import { SearchFieldDefinition } from '../../../utils/definitions'; } + preview={} code={searchFieldDefaultSnippet} /> @@ -45,7 +45,7 @@ We support two different sizes: `small`, `medium`. align="center" py={4} open - preview={} + preview={} code={searchFieldSizesSnippet} /> @@ -57,7 +57,7 @@ Here's a simple SearchField with a description. align="center" py={4} open - preview={} + preview={} code={searchFieldDescriptionSnippet} /> @@ -69,7 +69,7 @@ You can make the SearchField collapsible by setting the `startCollapsed` prop to align="center" py={4} open - preview={} + preview={} code={searchFieldCollapsibleSnippet} /> diff --git a/docs-ui/src/content/search-field.props.ts b/docs-ui/src/app/components/search-field/search-field.props.ts similarity index 100% rename from docs-ui/src/content/search-field.props.ts rename to docs-ui/src/app/components/search-field/search-field.props.ts diff --git a/docs-ui/src/app/components/search-field/stories.tsx b/docs-ui/src/app/components/search-field/stories.tsx new file mode 100644 index 0000000000..9aeec97ab3 --- /dev/null +++ b/docs-ui/src/app/components/search-field/stories.tsx @@ -0,0 +1,15 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/SearchField/SearchField.stories'; + +const { + WithLabel: WithLabelStory, + Sizes: SizesStory, + WithDescription: WithDescriptionStory, + StartCollapsed: StartCollapsedStory, +} = stories; + +export const WithLabel = () => ; +export const Sizes = () => ; +export const WithDescription = () => ; +export const StartCollapsed = () => ; diff --git a/docs-ui/src/content/select.mdx b/docs-ui/src/app/components/select/page.mdx similarity index 80% rename from docs-ui/src/content/select.mdx rename to docs-ui/src/app/components/select/page.mdx index 757a79effe..66d632b521 100644 --- a/docs-ui/src/content/select.mdx +++ b/docs-ui/src/app/components/select/page.mdx @@ -1,7 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { SelectSnippet } from '@/snippets/stories-snippets'; +import { Preview, WithLabelAndDescription, Sizes, WithIcon, Disabled, DisabledOption, Searchable, MultipleSelection, SearchableMultiple } from './stories'; import { selectPropDefs, selectUsageSnippet, @@ -19,7 +19,7 @@ import { import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { SelectDefinition } from '../utils/definitions'; +import { SelectDefinition } from '../../../utils/definitions'; } + preview={} code={selectDefaultSnippet} /> @@ -51,7 +51,7 @@ Select component with label and description. align="center" py={4} open - preview={} + preview={} code={selectDescriptionSnippet} /> @@ -63,7 +63,7 @@ Here's a view when the selects have different sizes. align="center" py={4} open - preview={} + preview={} code={selectSizesSnippet} /> @@ -76,7 +76,7 @@ Here's a view when the select has an icon. align="center" py={4} open -preview={} +preview={} code={selectIconSnippet} /> @@ -88,7 +88,7 @@ Here's a view when the select is disabled. align="center" py={4} open - preview={} + preview={} code={selectDisabledSnippet} /> @@ -100,7 +100,7 @@ You can disable specific options within the Select component using `disabledKeys align="center" py={4} open - preview={} + preview={} code={selectDisabledOptionsSnippet} /> @@ -112,7 +112,7 @@ Here's a view when the select has searchable filtering. align="center" py={4} open - preview={} + preview={} code={selectSearchableSnippet} /> @@ -124,7 +124,7 @@ Here's a view when the select allows multiple selections. align="center" py={4} open - preview={} + preview={} code={selectMultipleSnippet} /> @@ -136,7 +136,7 @@ Here's a view when the select combines search and multiple selection. align="center" py={4} open - preview={} + preview={} code={selectSearchableMultipleSnippet} /> diff --git a/docs-ui/src/content/select.props.ts b/docs-ui/src/app/components/select/select.props.ts similarity index 100% rename from docs-ui/src/content/select.props.ts rename to docs-ui/src/app/components/select/select.props.ts diff --git a/docs-ui/src/app/components/select/stories.tsx b/docs-ui/src/app/components/select/stories.tsx new file mode 100644 index 0000000000..149a6d2ae1 --- /dev/null +++ b/docs-ui/src/app/components/select/stories.tsx @@ -0,0 +1,27 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Select/Select.stories'; + +const { + Preview: PreviewStory, + WithLabelAndDescription: WithLabelAndDescriptionStory, + Sizes: SizesStory, + WithIcon: WithIconStory, + Disabled: DisabledStory, + DisabledOption: DisabledOptionStory, + Searchable: SearchableStory, + MultipleSelection: MultipleSelectionStory, + SearchableMultiple: SearchableMultipleStory, +} = stories; + +export const Preview = () => ; +export const WithLabelAndDescription = () => ( + +); +export const Sizes = () => ; +export const WithIcon = () => ; +export const Disabled = () => ; +export const DisabledOption = () => ; +export const Searchable = () => ; +export const MultipleSelection = () => ; +export const SearchableMultiple = () => ; diff --git a/docs-ui/src/content/skeleton.mdx b/docs-ui/src/app/components/skeleton/page.mdx similarity index 82% rename from docs-ui/src/content/skeleton.mdx rename to docs-ui/src/app/components/skeleton/page.mdx index 07cd4b3332..a32672e352 100644 --- a/docs-ui/src/content/skeleton.mdx +++ b/docs-ui/src/app/components/skeleton/page.mdx @@ -1,7 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { SkeletonSnippet } from '@/snippets/stories-snippets'; +import { Demo1, Demo2 } from './stories'; import { skeletonPropDefs, skeletonUsageSnippet, @@ -12,7 +12,7 @@ import { import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { SkeletonDefinition } from '../utils/definitions'; +import { SkeletonDefinition } from '../../../utils/definitions'; } + preview={} code={skeletonDefaultSnippet} /> @@ -43,7 +43,7 @@ You can use a mix of different sizes to create a more complex skeleton. } + preview={} code={skeletonDemo1Snippet} open /> @@ -55,7 +55,7 @@ You can use a mix of different sizes to create a more complex skeleton. } + preview={} code={skeletonDemo2Snippet} open /> diff --git a/docs-ui/src/content/skeleton.props.ts b/docs-ui/src/app/components/skeleton/skeleton.props.ts similarity index 100% rename from docs-ui/src/content/skeleton.props.ts rename to docs-ui/src/app/components/skeleton/skeleton.props.ts diff --git a/docs-ui/src/app/components/skeleton/stories.tsx b/docs-ui/src/app/components/skeleton/stories.tsx new file mode 100644 index 0000000000..c699cea620 --- /dev/null +++ b/docs-ui/src/app/components/skeleton/stories.tsx @@ -0,0 +1,8 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Skeleton/Skeleton.stories'; + +const { Demo1: Demo1Story, Demo2: Demo2Story } = stories; + +export const Demo1 = () => ; +export const Demo2 = () => ; diff --git a/docs-ui/src/content/switch.mdx b/docs-ui/src/app/components/switch/page.mdx similarity index 81% rename from docs-ui/src/content/switch.mdx rename to docs-ui/src/app/components/switch/page.mdx index 965cc7832b..e721973a50 100644 --- a/docs-ui/src/content/switch.mdx +++ b/docs-ui/src/app/components/switch/page.mdx @@ -1,12 +1,12 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { SwitchSnippet } from '@/snippets/stories-snippets'; +import { Default, Disabled } from './stories'; import { switchPropDefs, snippetUsage } from './switch.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { SwitchDefinition } from '../utils/definitions'; +import { SwitchDefinition } from '../../../utils/definitions'; } + preview={} code={``} /> @@ -38,7 +38,7 @@ A switch can be disabled using the `isDisabled` prop. align="center" py={4} open - preview={} + preview={} code={``} /> diff --git a/docs-ui/src/app/components/switch/stories.tsx b/docs-ui/src/app/components/switch/stories.tsx new file mode 100644 index 0000000000..1a8a56a051 --- /dev/null +++ b/docs-ui/src/app/components/switch/stories.tsx @@ -0,0 +1,8 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Switch/Switch.stories'; + +const { Default: DefaultStory, Disabled: DisabledStory } = stories; + +export const Default = () => ; +export const Disabled = () => ; diff --git a/docs-ui/src/content/switch.props.ts b/docs-ui/src/app/components/switch/switch.props.ts similarity index 100% rename from docs-ui/src/content/switch.props.ts rename to docs-ui/src/app/components/switch/switch.props.ts diff --git a/docs-ui/src/content/table.mdx b/docs-ui/src/app/components/table/page.mdx similarity index 91% rename from docs-ui/src/content/table.mdx rename to docs-ui/src/app/components/table/page.mdx index 00b7290532..db87494f21 100644 --- a/docs-ui/src/content/table.mdx +++ b/docs-ui/src/app/components/table/page.mdx @@ -1,7 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { TableSnippet } from '@/snippets/stories-snippets'; +import { TableRockBand, SelectionModePlayground, SelectionBehaviorPlayground, SelectionToggleWithActions } from './stories'; import { tablePropDefs, tableHeaderPropDefs, @@ -24,7 +24,7 @@ import { import { ChangelogComponent } from '@/components/ChangelogComponent'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { TableDefinition } from '../utils/definitions'; +import { TableDefinition } from '../../../utils/definitions'; } + preview={} code={tableBasicSnippet} /> @@ -100,7 +100,7 @@ Tables support row selection with two configuration options: selection mode and Use `selectionMode` to control how many rows can be selected. With `single`, only one row can be selected at a time. With `multiple`, any number of rows can be selected, and a header checkbox provides select-all functionality. } + preview={} code={tableSelectionModeSnippet} /> @@ -109,7 +109,7 @@ Use `selectionMode` to control how many rows can be selected. With `single`, onl Use `selectionBehavior` to control how selection is indicated and interacted with. With `toggle`, checkboxes appear for selection. With `replace`, selection is indicated by row background color—click to select, Cmd/Ctrl+click for multiple. } + preview={} code={tableSelectionBehaviorSnippet} /> @@ -120,7 +120,7 @@ With toggle behavior, clicking a row triggers its action when nothing is selecte With replace behavior, clicking selects the row and double-clicking triggers the action. } + preview={} code={tableSelectionActionsSnippet} /> diff --git a/docs-ui/src/app/components/table/stories.tsx b/docs-ui/src/app/components/table/stories.tsx new file mode 100644 index 0000000000..2627694d30 --- /dev/null +++ b/docs-ui/src/app/components/table/stories.tsx @@ -0,0 +1,21 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Table/stories/Table.docs.stories'; + +const { + TableRockBand: TableRockBandStory, + SelectionModePlayground: SelectionModePlaygroundStory, + SelectionBehaviorPlayground: SelectionBehaviorPlaygroundStory, + SelectionToggleWithActions: SelectionToggleWithActionsStory, +} = stories; + +export const TableRockBand = () => ; +export const SelectionModePlayground = () => ( + +); +export const SelectionBehaviorPlayground = () => ( + +); +export const SelectionToggleWithActions = () => ( + +); diff --git a/docs-ui/src/content/table.props.ts b/docs-ui/src/app/components/table/table.props.ts similarity index 100% rename from docs-ui/src/content/table.props.ts rename to docs-ui/src/app/components/table/table.props.ts diff --git a/docs-ui/src/content/tabs.mdx b/docs-ui/src/app/components/tabs/page.mdx similarity index 84% rename from docs-ui/src/content/tabs.mdx rename to docs-ui/src/app/components/tabs/page.mdx index 1fc7448eba..0f3ab6bfd4 100644 --- a/docs-ui/src/content/tabs.mdx +++ b/docs-ui/src/app/components/tabs/page.mdx @@ -1,5 +1,5 @@ import { PropsTable } from '@/components/PropsTable'; -import { TabsSnippet } from '@/snippets/stories-snippets'; +import { Default, WithTabPanels, WithMockedURLTab2, PrefixMatchingDeepNesting } from './stories'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; import { @@ -13,7 +13,7 @@ import { } from './tabs.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { TabsDefinition } from '../utils/definitions'; +import { TabsDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={tabsDefaultSnippet} /> @@ -53,7 +53,7 @@ To connect the tabs with the panels, you need to use the `id` prop on the tab an } + preview={} code={tabsWithTabPanelsSnippet} open /> @@ -64,7 +64,7 @@ You can use the `href` prop on the tab to make it a link. This will use the `rea } + preview={} code={tabsWithLinksSnippet} open /> @@ -75,7 +75,7 @@ You can use the `matchStrategy` prop on the tab to control how the tab is matche } + preview={} code={tabsWithDeeplyNestedRoutesSnippet} open /> diff --git a/docs-ui/src/app/components/tabs/stories.tsx b/docs-ui/src/app/components/tabs/stories.tsx new file mode 100644 index 0000000000..ee1704a45e --- /dev/null +++ b/docs-ui/src/app/components/tabs/stories.tsx @@ -0,0 +1,17 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Tabs/Tabs.stories'; + +const { + Default: DefaultStory, + WithTabPanels: WithTabPanelsStory, + WithMockedURLTab2: WithMockedURLTab2Story, + PrefixMatchingDeepNesting: PrefixMatchingDeepNestingStory, +} = stories; + +export const Default = () => ; +export const WithTabPanels = () => ; +export const WithMockedURLTab2 = () => ; +export const PrefixMatchingDeepNesting = () => ( + +); diff --git a/docs-ui/src/content/tabs.props.ts b/docs-ui/src/app/components/tabs/tabs.props.ts similarity index 100% rename from docs-ui/src/content/tabs.props.ts rename to docs-ui/src/app/components/tabs/tabs.props.ts diff --git a/docs-ui/src/content/tag-group.mdx b/docs-ui/src/app/components/tag-group/page.mdx similarity index 80% rename from docs-ui/src/content/tag-group.mdx rename to docs-ui/src/app/components/tag-group/page.mdx index 88e60b31f5..a10695727d 100644 --- a/docs-ui/src/content/tag-group.mdx +++ b/docs-ui/src/app/components/tag-group/page.mdx @@ -1,7 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { TagGroupSnippet } from '@/snippets/stories-snippets'; +import { Default, WithLink, WithIcon, Sizes, RemovingTags, Disabled } from './stories'; import { tagGroupPropDefs, tagPropDefs, @@ -15,7 +15,7 @@ import { } from './tag-group.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { TagGroupDefinition } from '../utils/definitions'; +import { TagGroupDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={preview} /> @@ -58,7 +58,7 @@ A tag can be a link by passing a `href` prop. align="center" py={4} open - preview={} + preview={} code={withLink} /> @@ -70,7 +70,7 @@ A tag can have an icon by passing a `icon` prop. align="center" py={4} open - preview={} + preview={} code={withIcons} /> @@ -82,7 +82,7 @@ A tag can have a size by passing a `size` prop. It could be `small` or `medium`. align="center" py={4} open - preview={} + preview={} code={sizes} /> @@ -94,7 +94,7 @@ A tag can be removed by passing a `onRemove` prop. align="center" py={4} open - preview={} + preview={} code={removingTags} /> @@ -106,7 +106,7 @@ A switch can be disabled using the `isDisabled` prop. align="center" py={4} open - preview={} + preview={} code={disabled} /> diff --git a/docs-ui/src/app/components/tag-group/stories.tsx b/docs-ui/src/app/components/tag-group/stories.tsx new file mode 100644 index 0000000000..4fb495df08 --- /dev/null +++ b/docs-ui/src/app/components/tag-group/stories.tsx @@ -0,0 +1,19 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/TagGroup/TagGroup.stories'; + +const { + Default: DefaultStory, + WithLink: WithLinkStory, + WithIcon: WithIconStory, + Sizes: SizesStory, + RemovingTags: RemovingTagsStory, + Disabled: DisabledStory, +} = stories; + +export const Default = () => ; +export const WithLink = () => ; +export const WithIcon = () => ; +export const Sizes = () => ; +export const RemovingTags = () => ; +export const Disabled = () => ; diff --git a/docs-ui/src/content/tag-group.props.ts b/docs-ui/src/app/components/tag-group/tag-group.props.ts similarity index 100% rename from docs-ui/src/content/tag-group.props.ts rename to docs-ui/src/app/components/tag-group/tag-group.props.ts diff --git a/docs-ui/src/content/text-field.mdx b/docs-ui/src/app/components/text-field/page.mdx similarity index 80% rename from docs-ui/src/content/text-field.mdx rename to docs-ui/src/app/components/text-field/page.mdx index 9f14a82975..1ab0857787 100644 --- a/docs-ui/src/content/text-field.mdx +++ b/docs-ui/src/app/components/text-field/page.mdx @@ -1,6 +1,6 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; -import { TextFieldSnippet } from '@/snippets/stories-snippets'; +import { WithLabel, Sizes, WithDescription } from './stories'; import { inputPropDefs, textFieldUsageSnippet, @@ -10,7 +10,7 @@ import { } from './text-field.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { TextFieldDefinition } from '../utils/definitions'; +import { TextFieldDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; import { CodeBlock } from '@/components/CodeBlock'; @@ -22,7 +22,7 @@ import { CodeBlock } from '@/components/CodeBlock'; } + preview={} code={textFieldDefaultSnippet} /> @@ -44,7 +44,7 @@ We support two different sizes: `small`, `medium`. align="center" py={4} open - preview={} + preview={} code={textFieldSizesSnippet} /> @@ -56,7 +56,7 @@ Here's a simple TextField with a description. align="center" py={4} open - preview={} + preview={} code={textFieldDescriptionSnippet} /> diff --git a/docs-ui/src/app/components/text-field/stories.tsx b/docs-ui/src/app/components/text-field/stories.tsx new file mode 100644 index 0000000000..9b6afd6c80 --- /dev/null +++ b/docs-ui/src/app/components/text-field/stories.tsx @@ -0,0 +1,13 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/TextField/TextField.stories'; + +const { + WithLabel: WithLabelStory, + Sizes: SizesStory, + WithDescription: WithDescriptionStory, +} = stories; + +export const WithLabel = () => ; +export const Sizes = () => ; +export const WithDescription = () => ; diff --git a/docs-ui/src/content/text-field.props.ts b/docs-ui/src/app/components/text-field/text-field.props.ts similarity index 100% rename from docs-ui/src/content/text-field.props.ts rename to docs-ui/src/app/components/text-field/text-field.props.ts diff --git a/docs-ui/src/content/text.mdx b/docs-ui/src/app/components/text/page.mdx similarity index 82% rename from docs-ui/src/content/text.mdx rename to docs-ui/src/app/components/text/page.mdx index c987b8e5f7..2f72087b05 100644 --- a/docs-ui/src/content/text.mdx +++ b/docs-ui/src/app/components/text/page.mdx @@ -1,5 +1,5 @@ import { PropsTable } from '@/components/PropsTable'; -import { TextSnippet } from '@/snippets/stories-snippets'; +import { Default, AllVariants, AllWeights, AllColors, Truncate } from './stories'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; import { @@ -14,7 +14,7 @@ import { } from './text.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { TextDefinition } from '../utils/definitions'; +import { TextDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={textDefaultSnippet} /> @@ -45,7 +45,7 @@ appearance of the text. } + preview={} code={textVariantsSnippet} /> @@ -56,7 +56,7 @@ appearance of the text. } + preview={} code={textWeightsSnippet} /> @@ -67,7 +67,7 @@ appearance of the text. } + preview={} code={textColorsSnippet} /> @@ -77,7 +77,7 @@ The `Text` component has a `truncate` prop that can be used to truncate the text } + preview={} code={textTruncateSnippet} /> diff --git a/docs-ui/src/app/components/text/stories.tsx b/docs-ui/src/app/components/text/stories.tsx new file mode 100644 index 0000000000..946734151a --- /dev/null +++ b/docs-ui/src/app/components/text/stories.tsx @@ -0,0 +1,17 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Text/Text.stories'; + +const { + Default: DefaultStory, + AllVariants: AllVariantsStory, + AllWeights: AllWeightsStory, + AllColors: AllColorsStory, + Truncate: TruncateStory, +} = stories; + +export const Default = () => ; +export const AllVariants = () => ; +export const AllWeights = () => ; +export const AllColors = () => ; +export const Truncate = () => ; diff --git a/docs-ui/src/content/text.props.ts b/docs-ui/src/app/components/text/text.props.ts similarity index 100% rename from docs-ui/src/content/text.props.ts rename to docs-ui/src/app/components/text/text.props.ts diff --git a/docs-ui/src/content/toggle-button-group.mdx b/docs-ui/src/app/components/toggle-button-group/page.mdx similarity index 74% rename from docs-ui/src/content/toggle-button-group.mdx rename to docs-ui/src/app/components/toggle-button-group/page.mdx index 19034f0ac5..16d5d14cc6 100644 --- a/docs-ui/src/content/toggle-button-group.mdx +++ b/docs-ui/src/app/components/toggle-button-group/page.mdx @@ -13,11 +13,11 @@ import { toggleButtonGroupIconsOnlySnippet, toggleButtonGroupSurfacesSnippet, } from './toggle-button-group.props'; -import { ToggleButtonGroupSnippet } from '@/snippets/stories-snippets'; +import { SingleSelection, Surfaces, MultipleSelection, WithIcons, IconsOnly, DisallowEmptySelection, Orientation, DisabledGroup } from './stories'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { ToggleButtonGroupDefinition } from '../utils/definitions'; +import { ToggleButtonGroupDefinition } from '../../../utils/definitions'; } + preview={} code={toggleButtonGroupSingleSnippet} /> @@ -47,7 +47,7 @@ import { ToggleButtonGroupDefinition } from '../utils/definitions'; align="center" py={4} open - preview={} + preview={} code={toggleButtonGroupSurfacesSnippet} /> @@ -57,7 +57,7 @@ import { ToggleButtonGroupDefinition } from '../utils/definitions'; align="center" py={4} open - preview={} + preview={} code={toggleButtonGroupSingleSnippet} /> @@ -67,7 +67,7 @@ import { ToggleButtonGroupDefinition } from '../utils/definitions'; align="center" py={4} open - preview={} + preview={} code={toggleButtonGroupMultipleSnippet} /> @@ -77,7 +77,7 @@ import { ToggleButtonGroupDefinition } from '../utils/definitions'; align="center" py={4} open - preview={} + preview={} code={toggleButtonGroupIconsSnippet} /> @@ -87,7 +87,7 @@ import { ToggleButtonGroupDefinition } from '../utils/definitions'; align="center" py={4} open - preview={} + preview={} code={toggleButtonGroupIconsOnlySnippet} /> @@ -97,7 +97,7 @@ import { ToggleButtonGroupDefinition } from '../utils/definitions'; align="center" py={4} open - preview={} + preview={} code={toggleButtonGroupDisallowEmptySnippet} /> @@ -107,7 +107,7 @@ import { ToggleButtonGroupDefinition } from '../utils/definitions'; align="center" py={4} open - preview={} + preview={} code={toggleButtonGroupVerticalSnippet} /> @@ -117,7 +117,7 @@ import { ToggleButtonGroupDefinition } from '../utils/definitions'; align="center" py={4} open - preview={} + preview={} code={toggleButtonGroupDisabledSnippet} /> diff --git a/docs-ui/src/app/components/toggle-button-group/stories.tsx b/docs-ui/src/app/components/toggle-button-group/stories.tsx new file mode 100644 index 0000000000..8d75c021b5 --- /dev/null +++ b/docs-ui/src/app/components/toggle-button-group/stories.tsx @@ -0,0 +1,25 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.stories'; + +const { + SingleSelection: SingleSelectionStory, + Surfaces: SurfacesStory, + MultipleSelection: MultipleSelectionStory, + WithIcons: WithIconsStory, + IconsOnly: IconsOnlyStory, + DisallowEmptySelection: DisallowEmptySelectionStory, + Orientation: OrientationStory, + DisabledGroup: DisabledGroupStory, +} = stories; + +export const SingleSelection = () => ; +export const Surfaces = () => ; +export const MultipleSelection = () => ; +export const WithIcons = () => ; +export const IconsOnly = () => ; +export const DisallowEmptySelection = () => ( + +); +export const Orientation = () => ; +export const DisabledGroup = () => ; diff --git a/docs-ui/src/content/toggle-button-group.props.ts b/docs-ui/src/app/components/toggle-button-group/toggle-button-group.props.ts similarity index 100% rename from docs-ui/src/content/toggle-button-group.props.ts rename to docs-ui/src/app/components/toggle-button-group/toggle-button-group.props.ts diff --git a/docs-ui/src/content/toggle-button.mdx b/docs-ui/src/app/components/toggle-button/page.mdx similarity index 79% rename from docs-ui/src/content/toggle-button.mdx rename to docs-ui/src/app/components/toggle-button/page.mdx index 78750ec365..45dea83828 100644 --- a/docs-ui/src/content/toggle-button.mdx +++ b/docs-ui/src/app/components/toggle-button/page.mdx @@ -1,7 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { ToggleButtonSnippet } from '@/snippets/stories-snippets'; +import { Default, Surfaces, Sizes, WithIcons, Disabled, Controlled, DynamicContent } from './stories'; import { toggleButtonPropDefs, toggleButtonUsageSnippet, @@ -15,7 +15,7 @@ import { import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { ToggleButtonDefinition } from '../utils/definitions'; +import { ToggleButtonDefinition } from '../../../utils/definitions'; } + preview={} code={toggleButtonUsageSnippet} /> @@ -45,7 +45,7 @@ import { ToggleButtonDefinition } from '../utils/definitions'; align="center" py={4} open - preview={} + preview={} code={toggleButtonSurfacesSnippet} /> @@ -55,7 +55,7 @@ import { ToggleButtonDefinition } from '../utils/definitions'; align="center" py={4} open - preview={} + preview={} code={toggleButtonSizesSnippet} /> @@ -65,7 +65,7 @@ import { ToggleButtonDefinition } from '../utils/definitions'; align="center" py={4} open - preview={} + preview={} code={toggleButtonIconsSnippet} /> @@ -75,7 +75,7 @@ import { ToggleButtonDefinition } from '../utils/definitions'; align="center" py={4} open - preview={} + preview={} code={toggleButtonDisabledSnippet} /> @@ -85,7 +85,7 @@ import { ToggleButtonDefinition } from '../utils/definitions'; align="center" py={4} open - preview={} + preview={} code={toggleButtonControlledSnippet} /> @@ -97,7 +97,7 @@ The `children` prop can be a function that receives render props, allowing you t align="center" py={4} open - preview={} + preview={} code={toggleButtonFunctionChildrenSnippet} /> diff --git a/docs-ui/src/app/components/toggle-button/stories.tsx b/docs-ui/src/app/components/toggle-button/stories.tsx new file mode 100644 index 0000000000..ce210dc6ab --- /dev/null +++ b/docs-ui/src/app/components/toggle-button/stories.tsx @@ -0,0 +1,21 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/ToggleButton/ToggleButton.stories'; + +const { + Default: DefaultStory, + Surfaces: SurfacesStory, + Sizes: SizesStory, + WithIcons: WithIconsStory, + Disabled: DisabledStory, + Controlled: ControlledStory, + DynamicContent: DynamicContentStory, +} = stories; + +export const Default = () => ; +export const Surfaces = () => ; +export const Sizes = () => ; +export const WithIcons = () => ; +export const Disabled = () => ; +export const Controlled = () => ; +export const DynamicContent = () => ; diff --git a/docs-ui/src/content/toggle-button.props.ts b/docs-ui/src/app/components/toggle-button/toggle-button.props.ts similarity index 100% rename from docs-ui/src/content/toggle-button.props.ts rename to docs-ui/src/app/components/toggle-button/toggle-button.props.ts diff --git a/docs-ui/src/content/tooltip.mdx b/docs-ui/src/app/components/tooltip/page.mdx similarity index 85% rename from docs-ui/src/content/tooltip.mdx rename to docs-ui/src/app/components/tooltip/page.mdx index 8ebf6539d6..c62ece88ec 100644 --- a/docs-ui/src/content/tooltip.mdx +++ b/docs-ui/src/app/components/tooltip/page.mdx @@ -1,5 +1,5 @@ import { PropsTable } from '@/components/PropsTable'; -import { TooltipSnippet } from '@/snippets/stories-snippets'; +import { Default } from './stories'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; import { @@ -10,7 +10,7 @@ import { } from './tooltip.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { TooltipDefinition } from '../utils/definitions'; +import { TooltipDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={tooltipDefaultSnippet} align="center" /> diff --git a/docs-ui/src/app/components/tooltip/stories.tsx b/docs-ui/src/app/components/tooltip/stories.tsx new file mode 100644 index 0000000000..101131db43 --- /dev/null +++ b/docs-ui/src/app/components/tooltip/stories.tsx @@ -0,0 +1,7 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/Tooltip/Tooltip.stories'; + +const { Default: DefaultStory } = stories; + +export const Default = () => ; diff --git a/docs-ui/src/content/tooltip.props.ts b/docs-ui/src/app/components/tooltip/tooltip.props.ts similarity index 100% rename from docs-ui/src/content/tooltip.props.ts rename to docs-ui/src/app/components/tooltip/tooltip.props.ts diff --git a/docs-ui/src/content/visually-hidden.mdx b/docs-ui/src/app/components/visually-hidden/page.mdx similarity index 82% rename from docs-ui/src/content/visually-hidden.mdx rename to docs-ui/src/app/components/visually-hidden/page.mdx index 72ffc7a1d4..98591e502c 100644 --- a/docs-ui/src/content/visually-hidden.mdx +++ b/docs-ui/src/app/components/visually-hidden/page.mdx @@ -1,7 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { VisuallyHiddenSnippet } from '@/snippets/stories-snippets'; +import { Default, ExampleUsage } from './stories'; import { visuallyHiddenPropDefs, visuallyHiddenUsageSnippet, @@ -10,7 +10,7 @@ import { } from './visually-hidden.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; -import { VisuallyHiddenDefinition } from '../utils/definitions'; +import { VisuallyHiddenDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; } + preview={} code={visuallyHiddenDefaultSnippet} /> @@ -42,7 +42,7 @@ Here's an example of providing screen reader context for a list of links in a fo } + preview={} code={visuallyHiddenExampleUsageSnippet} open /> diff --git a/docs-ui/src/app/components/visually-hidden/stories.tsx b/docs-ui/src/app/components/visually-hidden/stories.tsx new file mode 100644 index 0000000000..2edefe0f94 --- /dev/null +++ b/docs-ui/src/app/components/visually-hidden/stories.tsx @@ -0,0 +1,8 @@ +'use client'; + +import * as stories from '@backstage/ui/src/components/VisuallyHidden/VisuallyHidden.stories'; + +const { Default: DefaultStory, ExampleUsage: ExampleUsageStory } = stories; + +export const Default = () => ; +export const ExampleUsage = () => ; diff --git a/docs-ui/src/content/visually-hidden.props.ts b/docs-ui/src/app/components/visually-hidden/visually-hidden.props.ts similarity index 100% rename from docs-ui/src/content/visually-hidden.props.ts rename to docs-ui/src/app/components/visually-hidden/visually-hidden.props.ts diff --git a/docs-ui/src/content/PasswordField.tsx b/docs-ui/src/content/PasswordField.tsx deleted file mode 100644 index 953ec3515e..0000000000 --- a/docs-ui/src/content/PasswordField.tsx +++ /dev/null @@ -1,8 +0,0 @@ -'use client'; - -import * as stories from '../../../packages/ui/src/components/PasswordField/PasswordField.stories'; - -export const Default = () => { - const { CustomField } = stories; - return ; -}; diff --git a/docs-ui/src/content/password-field.mdx b/docs-ui/src/content/password-field.mdx deleted file mode 100644 index 2b6d3c1ca6..0000000000 --- a/docs-ui/src/content/password-field.mdx +++ /dev/null @@ -1,23 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { PasswordFieldSnippet } from '@/snippets/stories-snippets'; -import { - inputPropDefs, - passwordFieldUsageSnippet, - passwordFieldDefaultSnippet, - passwordFieldSizesSnippet, - passwordFieldDescriptionSnippet, -} from './password-field.props'; -import { PageTitle } from '@/components/PageTitle'; -import { Theming } from '@/components/Theming'; -import { ChangelogComponent } from '@/components/ChangelogComponent'; -import { CodeBlock } from '@/components/CodeBlock'; -import { PasswordFieldDefinition } from '../utils/definitions'; -import { Default } from './PasswordField'; - - - - diff --git a/docs-ui/src/snippets/stories-snippets.tsx b/docs-ui/src/snippets/stories-snippets.tsx deleted file mode 100644 index 9297844baa..0000000000 --- a/docs-ui/src/snippets/stories-snippets.tsx +++ /dev/null @@ -1,85 +0,0 @@ -'use client'; - -import * as BoxStories from '../../../packages/ui/src/components/Box/Box.stories'; -import * as ButtonStories from '../../../packages/ui/src/components/Button/Button.stories'; -import * as ButtonIconStories from '../../../packages/ui/src/components/ButtonIcon/ButtonIcon.stories'; -import * as ButtonLinkStories from '../../../packages/ui/src/components/ButtonLink/ButtonLink.stories'; -import * as CheckboxStories from '../../../packages/ui/src/components/Checkbox/Checkbox.stories'; -import * as ContainerStories from '../../../packages/ui/src/components/Container/Container.stories'; -import * as GridStories from '../../../packages/ui/src/components/Grid/Grid.stories'; -import * as TextFieldStories from '../../../packages/ui/src/components/TextField/TextField.stories'; -import * as TextStories from '../../../packages/ui/src/components/Text/Text.stories'; -import * as FlexStories from '../../../packages/ui/src/components/Flex/Flex.stories'; -import * as SelectStories from '../../../packages/ui/src/components/Select/Select.stories'; -import * as MenuStories from '../../../packages/ui/src/components/Menu/Menu.stories'; -import * as LinkStories from '../../../packages/ui/src/components/Link/Link.stories'; -import * as AvatarStories from '../../../packages/ui/src/components/Avatar/Avatar.stories'; -import * as AccordionStories from '../../../packages/ui/src/components/Accordion/Accordion.stories'; -import * as DialogStories from '../../../packages/ui/src/components/Dialog/Dialog.stories'; -import * as RadioGroupStories from '../../../packages/ui/src/components/RadioGroup/RadioGroup.stories'; -import * as TabsStories from '../../../packages/ui/src/components/Tabs/Tabs.stories'; -import * as SwitchStories from '../../../packages/ui/src/components/Switch/Switch.stories'; -import * as SearchFieldStories from '../../../packages/ui/src/components/SearchField/SearchField.stories'; -import * as TooltipStories from '../../../packages/ui/src/components/Tooltip/Tooltip.stories'; -import * as SkeletonStories from '../../../packages/ui/src/components/Skeleton/Skeleton.stories'; -import * as CardStories from '../../../packages/ui/src/components/Card/Card.stories'; -import * as HeaderStories from '../../../packages/ui/src/components/Header/Header.stories'; -import * as HeaderPageStories from '../../../packages/ui/src/components/HeaderPage/HeaderPage.stories'; -import * as TableStories from '../../../packages/ui/src/components/Table/stories/Table.docs.stories'; -import * as TagGroupStories from '../../../packages/ui/src/components/TagGroup/TagGroup.stories'; -import * as PasswordFieldStories from '../../../packages/ui/src/components/PasswordField/PasswordField.stories'; -import * as PopoverStories from '../../../packages/ui/src/components/Popover/Popover.stories'; -import * as VisuallyHiddenStories from '../../../packages/ui/src/components/VisuallyHidden/VisuallyHidden.stories'; -import * as ToggleButtonStories from '../../../packages/ui/src/components/ToggleButton/ToggleButton.stories'; -import * as ToggleButtonGroupStories from '../../../packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.stories'; - -// Helper function to create snippet components -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const createSnippetComponent = (stories: any) => { - return function SnippetComponent({ story }: { story: string }) { - // In CSF3, stories are already React components that can be rendered directly - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const StoryComponent = stories[story as keyof typeof stories] as any; - - return StoryComponent ? : null; - }; -}; - -// Create snippet components using the helper function -export const BoxSnippet = createSnippetComponent(BoxStories); -export const ButtonSnippet = createSnippetComponent(ButtonStories); -export const ButtonIconSnippet = createSnippetComponent(ButtonIconStories); -export const ButtonLinkSnippet = createSnippetComponent(ButtonLinkStories); -export const CheckboxSnippet = createSnippetComponent(CheckboxStories); -export const ContainerSnippet = createSnippetComponent(ContainerStories); -export const GridSnippet = createSnippetComponent(GridStories); -export const TextFieldSnippet = createSnippetComponent(TextFieldStories); -export const PasswordFieldSnippet = - createSnippetComponent(PasswordFieldStories); -export const PopoverSnippet = createSnippetComponent(PopoverStories); -export const TextSnippet = createSnippetComponent(TextStories); -export const FlexSnippet = createSnippetComponent(FlexStories); -export const SelectSnippet = createSnippetComponent(SelectStories); -export const MenuSnippet = createSnippetComponent(MenuStories); -export const LinkSnippet = createSnippetComponent(LinkStories); -export const AvatarSnippet = createSnippetComponent(AvatarStories); -export const AccordionSnippet = createSnippetComponent(AccordionStories); -export const DialogSnippet = createSnippetComponent(DialogStories); -export const RadioGroupSnippet = createSnippetComponent(RadioGroupStories); -export const TabsSnippet = createSnippetComponent(TabsStories); -export const SwitchSnippet = createSnippetComponent(SwitchStories); -export const SearchFieldSnippet = createSnippetComponent(SearchFieldStories); -export const TooltipSnippet = createSnippetComponent(TooltipStories); -export const SkeletonSnippet = createSnippetComponent(SkeletonStories); -export const CardSnippet = createSnippetComponent(CardStories); -export const HeaderSnippet = createSnippetComponent(HeaderStories); -export const HeaderPageSnippet = createSnippetComponent(HeaderPageStories); -export const TableSnippet = createSnippetComponent(TableStories); -export const TagGroupSnippet = createSnippetComponent(TagGroupStories); -export const ToggleButtonSnippet = createSnippetComponent(ToggleButtonStories); -export const ToggleButtonGroupSnippet = createSnippetComponent( - ToggleButtonGroupStories, -); -export const VisuallyHiddenSnippet = createSnippetComponent( - VisuallyHiddenStories, -); From 455b94088f52ccbde6195c1e80fd92ec2d560c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 15 Jan 2026 09:30:20 +0100 Subject: [PATCH 140/202] exit prerelease mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/pre.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index f668ddbe53..a713419cdb 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -1,5 +1,5 @@ { - "mode": "pre", + "mode": "exit", "tag": "next", "initialVersions": { "example-app": "0.2.116", From fe7fe696ea8fb8a241dc723b8e9bfe1304af85e6 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Wed, 14 Jan 2026 17:46:50 +0100 Subject: [PATCH 141/202] feat(ui): expose pagination props in Table and useTable Add support for custom pagination options in the Table component and useTable hook: - Add `pageSizeOptions` prop to customize page size dropdown options - Add `onPageSizeChange`, `onNextPage`, and `onPreviousPage` callbacks - Validate pageSize against options, warn and use first option if invalid - Export `PageSizeOption` type for custom option labels - Add visual story demonstrating custom page size options Signed-off-by: Johan Persson --- .changeset/proud-sides-tan.md | 7 ++ packages/ui/report.api.md | 29 ++++++-- .../src/components/Table/components/Table.tsx | 1 + .../Table/hooks/getEffectivePageSize.ts | 52 +++++++++++++ .../ui/src/components/Table/hooks/types.ts | 17 ++++- .../Table/hooks/useCompletePagination.ts | 5 +- .../Table/hooks/useCursorPagination.ts | 3 +- .../Table/hooks/useOffsetPagination.ts | 5 +- .../ui/src/components/Table/hooks/useTable.ts | 29 +++++++- .../Table/stories/Table.visual.stories.tsx | 48 ++++++++++++ .../TablePagination/TablePagination.tsx | 73 +++++++++++++++---- .../src/components/TablePagination/index.ts | 2 +- .../src/components/TablePagination/types.ts | 7 ++ 13 files changed, 244 insertions(+), 34 deletions(-) create mode 100644 .changeset/proud-sides-tan.md create mode 100644 packages/ui/src/components/Table/hooks/getEffectivePageSize.ts diff --git a/.changeset/proud-sides-tan.md b/.changeset/proud-sides-tan.md new file mode 100644 index 0000000000..0790d0f72e --- /dev/null +++ b/.changeset/proud-sides-tan.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Added support for custom pagination options in `useTable` hook and `Table` component. You can now configure `pageSizeOptions` to customize the page size dropdown, and hook into pagination events via `onPageSizeChange`, `onNextPage`, and `onPreviousPage` callbacks. When `pageSize` doesn't match any option, the first option is used and a warning is logged. + +Affected components: Table, TablePagination diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 8c56f040c2..295d5e8d4f 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -1195,15 +1195,29 @@ export interface PagePagination extends TablePaginationProps { } // @public (undocumented) -export interface PaginationOptions { +export interface PageSizeOption { // (undocumented) - getLabel?: TablePaginationProps['getLabel']; + label: string; + // (undocumented) + value: number; +} + +// @public (undocumented) +export interface PaginationOptions + extends Partial< + Pick< + TablePaginationProps, + | 'pageSize' + | 'pageSizeOptions' + | 'onPageSizeChange' + | 'onNextPage' + | 'onPreviousPage' + | 'showPageSizeOptions' + | 'getLabel' + > + > { // (undocumented) initialOffset?: number; - // (undocumented) - pageSize?: number; - // (undocumented) - showPageSizeOptions?: boolean; } // @public @@ -1559,6 +1573,7 @@ export interface TableItem { // @public export function TablePagination({ pageSize, + pageSizeOptions, offset, totalCount, hasNextPage, @@ -1603,6 +1618,8 @@ export interface TablePaginationProps { // (undocumented) pageSize: number; // (undocumented) + pageSizeOptions?: number[] | PageSizeOption[]; + // (undocumented) showPageSizeOptions?: boolean; // (undocumented) totalCount?: number; diff --git a/packages/ui/src/components/Table/components/Table.tsx b/packages/ui/src/components/Table/components/Table.tsx index 59020dc2b0..84211901a6 100644 --- a/packages/ui/src/components/Table/components/Table.tsx +++ b/packages/ui/src/components/Table/components/Table.tsx @@ -196,6 +196,7 @@ export function Table({ {pagination.type === 'page' && ( getOptionValue(opt) === pageSize); + + if (isValid) { + return pageSize; + } + + console.warn( + `useTable: pageSize ${pageSize} is not in pageSizeOptions, using ${firstValue} instead`, + ); + return firstValue; +} diff --git a/packages/ui/src/components/Table/hooks/types.ts b/packages/ui/src/components/Table/hooks/types.ts index a09fae0946..f22efd4b53 100644 --- a/packages/ui/src/components/Table/hooks/types.ts +++ b/packages/ui/src/components/Table/hooks/types.ts @@ -45,11 +45,20 @@ export interface QueryOptions { } /** @public */ -export interface PaginationOptions { - pageSize?: number; +export interface PaginationOptions + extends Partial< + Pick< + TablePaginationProps, + | 'pageSize' + | 'pageSizeOptions' + | 'onPageSizeChange' + | 'onNextPage' + | 'onPreviousPage' + | 'showPageSizeOptions' + | 'getLabel' + > + > { initialOffset?: number; - showPageSizeOptions?: boolean; - getLabel?: TablePaginationProps['getLabel']; } /** @public */ diff --git a/packages/ui/src/components/Table/hooks/useCompletePagination.ts b/packages/ui/src/components/Table/hooks/useCompletePagination.ts index 3f199335d5..70db68dae1 100644 --- a/packages/ui/src/components/Table/hooks/useCompletePagination.ts +++ b/packages/ui/src/components/Table/hooks/useCompletePagination.ts @@ -22,6 +22,7 @@ import type { UseTableCompleteOptions, } from './types'; import { useStableCallback } from './useStableCallback'; +import { getEffectivePageSize } from './getEffectivePageSize'; /** @internal */ export function useCompletePagination( @@ -35,8 +36,8 @@ export function useCompletePagination( filterFn, searchFn, } = options; - const { pageSize: defaultPageSize = 20, initialOffset = 0 } = - paginationOptions; + const { initialOffset = 0 } = paginationOptions; + const defaultPageSize = getEffectivePageSize(paginationOptions); const getData = useStableCallback(getDataProp); const { sort, filter, search } = query; diff --git a/packages/ui/src/components/Table/hooks/useCursorPagination.ts b/packages/ui/src/components/Table/hooks/useCursorPagination.ts index 007f39023f..5da56b3ce4 100644 --- a/packages/ui/src/components/Table/hooks/useCursorPagination.ts +++ b/packages/ui/src/components/Table/hooks/useCursorPagination.ts @@ -24,13 +24,14 @@ import type { import { usePageCache } from './usePageCache'; import { useStableCallback } from './useStableCallback'; import { useDebouncedReload } from './useDebouncedReload'; +import { getEffectivePageSize } from './getEffectivePageSize'; export function useCursorPagination( options: UseTableCursorOptions, query: QueryState, ): PaginationResult & { reload: () => void } { const { getData: getDataProp, paginationOptions = {} } = options; - const { pageSize: defaultPageSize = 20 } = paginationOptions; + const defaultPageSize = getEffectivePageSize(paginationOptions); const getData = useStableCallback(getDataProp); const { sort, filter, search } = query; diff --git a/packages/ui/src/components/Table/hooks/useOffsetPagination.ts b/packages/ui/src/components/Table/hooks/useOffsetPagination.ts index c5844ec018..b34dbe8a6c 100644 --- a/packages/ui/src/components/Table/hooks/useOffsetPagination.ts +++ b/packages/ui/src/components/Table/hooks/useOffsetPagination.ts @@ -24,14 +24,15 @@ import type { import { usePageCache } from './usePageCache'; import { useStableCallback } from './useStableCallback'; import { useDebouncedReload } from './useDebouncedReload'; +import { getEffectivePageSize } from './getEffectivePageSize'; export function useOffsetPagination( options: UseTableOffsetOptions, query: QueryState, ): PaginationResult & { reload: () => void } { const { getData: getDataProp, paginationOptions = {} } = options; - const { pageSize: defaultPageSize = 20, initialOffset = 0 } = - paginationOptions; + const { initialOffset = 0 } = paginationOptions; + const defaultPageSize = getEffectivePageSize(paginationOptions); const getData = useStableCallback(getDataProp); const { sort, filter, search } = query; diff --git a/packages/ui/src/components/Table/hooks/useTable.ts b/packages/ui/src/components/Table/hooks/useTable.ts index 6b653c41db..b8f5b8bd13 100644 --- a/packages/ui/src/components/Table/hooks/useTable.ts +++ b/packages/ui/src/components/Table/hooks/useTable.ts @@ -34,7 +34,14 @@ function useTableProps( TableProps, 'columnConfig' | 'rowConfig' | 'selection' | 'emptyState' > { - const { showPageSizeOptions = true, getLabel } = paginationOptions; + const { + showPageSizeOptions = true, + pageSizeOptions, + onPageSizeChange: onPageSizeChangeCallback, + onNextPage: onNextPageCallback, + onPreviousPage: onPreviousPageCallback, + getLabel, + } = paginationOptions; const previousDataRef = useRef(paginationResult.data); if (paginationResult.data) { @@ -48,18 +55,29 @@ function useTableProps( () => ({ type: 'page' as const, pageSize: paginationResult.pageSize, + pageSizeOptions, offset: paginationResult.offset, totalCount: paginationResult.totalCount, hasNextPage: paginationResult.hasNextPage, hasPreviousPage: paginationResult.hasPreviousPage, - onNextPage: paginationResult.onNextPage, - onPreviousPage: paginationResult.onPreviousPage, - onPageSizeChange: paginationResult.onPageSizeChange, + onNextPage: () => { + paginationResult.onNextPage(); + onNextPageCallback?.(); + }, + onPreviousPage: () => { + paginationResult.onPreviousPage(); + onPreviousPageCallback?.(); + }, + onPageSizeChange: (size: number) => { + paginationResult.onPageSizeChange(size); + onPageSizeChangeCallback?.(size); + }, showPageSizeOptions, getLabel, }), [ paginationResult.pageSize, + pageSizeOptions, paginationResult.offset, paginationResult.totalCount, paginationResult.hasNextPage, @@ -67,6 +85,9 @@ function useTableProps( paginationResult.onNextPage, paginationResult.onPreviousPage, paginationResult.onPageSizeChange, + onNextPageCallback, + onPreviousPageCallback, + onPageSizeChangeCallback, ], ); diff --git a/packages/ui/src/components/Table/stories/Table.visual.stories.tsx b/packages/ui/src/components/Table/stories/Table.visual.stories.tsx index 34259dde96..3ec6af5789 100644 --- a/packages/ui/src/components/Table/stories/Table.visual.stories.tsx +++ b/packages/ui/src/components/Table/stories/Table.visual.stories.tsx @@ -338,3 +338,51 @@ export const StaleState: Story = { ); }, }; + +export const CustomPageSizeOptions: Story = { + render: () => { + const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'type', + label: 'Type', + cell: item => , + }, + ]; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data1, + paginationOptions: { + pageSize: 3, + pageSizeOptions: [ + { label: '2 per page', value: 2 }, + { label: '3 per page', value: 3 }, + { label: '5 per page', value: 5 }, + { label: '7 per page', value: 7 }, + ], + onPageSizeChange: size => { + console.log('Page size changed to:', size); + }, + onNextPage: () => { + console.log('Navigated to next page'); + }, + onPreviousPage: () => { + console.log('Navigated to previous page'); + }, + }, + }); + + return
; + }, +}; diff --git a/packages/ui/src/components/TablePagination/TablePagination.tsx b/packages/ui/src/components/TablePagination/TablePagination.tsx index de9234afa5..632fb3188a 100644 --- a/packages/ui/src/components/TablePagination/TablePagination.tsx +++ b/packages/ui/src/components/TablePagination/TablePagination.tsx @@ -16,12 +16,43 @@ import clsx from 'clsx'; import { Text, ButtonIcon, Select } from '../..'; -import type { TablePaginationProps } from './types'; +import type { TablePaginationProps, PageSizeOption } from './types'; import { useStyles } from '../../hooks/useStyles'; import { TablePaginationDefinition } from './definition'; import styles from './TablePagination.module.css'; import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react'; -import { useId } from 'react'; +import { useId, useMemo } from 'react'; + +const DEFAULT_PAGE_SIZE_OPTIONS: PageSizeOption[] = [ + { label: 'Show 5 results', value: 5 }, + { label: 'Show 10 results', value: 10 }, + { label: 'Show 20 results', value: 20 }, + { label: 'Show 30 results', value: 30 }, + { label: 'Show 40 results', value: 40 }, + { label: 'Show 50 results', value: 50 }, +]; + +function getOptionValue(option: number | PageSizeOption): number { + return typeof option === 'number' ? option : option.value; +} + +function isNumberArray( + options: number[] | PageSizeOption[], +): options is number[] { + return options.length > 0 && typeof options[0] === 'number'; +} + +function normalizePageSizeOptions( + options: number[] | PageSizeOption[], +): PageSizeOption[] { + if (isNumberArray(options)) { + return options.map(value => ({ + label: `Show ${value} results`, + value, + })); + } + return options; +} /** * Pagination controls for Table components with page navigation and size selection. @@ -30,6 +61,7 @@ import { useId } from 'react'; */ export function TablePagination({ pageSize, + pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS, offset, totalCount, hasNextPage, @@ -42,15 +74,33 @@ export function TablePagination({ }: TablePaginationProps) { const { classNames } = useStyles(TablePaginationDefinition, {}); const labelId = useId(); + const normalizedOptions = useMemo( + () => normalizePageSizeOptions(pageSizeOptions), + [pageSizeOptions], + ); + + const effectivePageSize = useMemo(() => { + const isValid = pageSizeOptions.some( + opt => getOptionValue(opt) === pageSize, + ); + if (isValid) { + return pageSize; + } + const firstValue = getOptionValue(pageSizeOptions[0]); + console.warn( + `TablePagination: pageSize ${pageSize} is not in pageSizeOptions, using ${firstValue} instead`, + ); + return firstValue; + }, [pageSize, pageSizeOptions]); const hasItems = totalCount !== undefined && totalCount !== 0; let label = `${totalCount} items`; if (getLabel) { - label = getLabel({ pageSize, offset, totalCount }); + label = getLabel({ pageSize: effectivePageSize, offset, totalCount }); } else if (offset !== undefined) { const fromCount = offset + 1; - const toCount = Math.min(offset + pageSize, totalCount ?? 0); + const toCount = Math.min(offset + effectivePageSize, totalCount ?? 0); label = `${fromCount} - ${toCount} of ${totalCount}`; } @@ -62,16 +112,11 @@ export function TablePagination({ name="pageSize" size="small" aria-label="Select table page size" - placeholder="Show 10 results" - options={[ - { label: 'Show 5 results', value: '5' }, - { label: 'Show 10 results', value: '10' }, - { label: 'Show 20 results', value: '20' }, - { label: 'Show 30 results', value: '30' }, - { label: 'Show 40 results', value: '40' }, - { label: 'Show 50 results', value: '50' }, - ]} - defaultValue={pageSize.toString()} + options={normalizedOptions.map(opt => ({ + label: opt.label, + value: String(opt.value), + }))} + defaultValue={effectivePageSize.toString()} onChange={value => { const newPageSize = Number(value); onPageSizeChange?.(newPageSize); diff --git a/packages/ui/src/components/TablePagination/index.ts b/packages/ui/src/components/TablePagination/index.ts index 42642fb162..e0cd949abf 100644 --- a/packages/ui/src/components/TablePagination/index.ts +++ b/packages/ui/src/components/TablePagination/index.ts @@ -15,5 +15,5 @@ */ export { TablePagination } from './TablePagination'; -export type { TablePaginationProps } from './types'; +export type { TablePaginationProps, PageSizeOption } from './types'; export { TablePaginationDefinition } from './definition'; diff --git a/packages/ui/src/components/TablePagination/types.ts b/packages/ui/src/components/TablePagination/types.ts index 8bd370500a..04881a0f5f 100644 --- a/packages/ui/src/components/TablePagination/types.ts +++ b/packages/ui/src/components/TablePagination/types.ts @@ -14,9 +14,16 @@ * limitations under the License. */ +/** @public */ +export interface PageSizeOption { + label: string; + value: number; +} + /** @public */ export interface TablePaginationProps { pageSize: number; + pageSizeOptions?: number[] | PageSizeOption[]; offset?: number; totalCount?: number; hasNextPage: boolean; From 9847445d4804e0590f7c9d75cf3804c3c9bdc69d Mon Sep 17 00:00:00 2001 From: meleu Date: Thu, 15 Jan 2026 09:49:03 -0300 Subject: [PATCH 142/202] Backstage is an ~~application~~ framework Claiming that "Backstage is an application" is a misleading information that brings a lot of confusion. I see both tech and non-tech people sometimes thinking that "deploying a Backstage" is a thing. Clearly lacking the understanding that "building a portal with Backstage" is the realistic view. Signed-off-by: meleu --- docs/faq/technical.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq/technical.md b/docs/faq/technical.md index 32511351f0..5e01da00d2 100644 --- a/docs/faq/technical.md +++ b/docs/faq/technical.md @@ -7,7 +7,7 @@ description: General technical questions about Backstage. ### What technology does Backstage use? Backstage is a large scale [TypeScript](https://www.typescriptlang.org/) -application whose frontend parts use [React](https://react.dev/) and +framework whose frontend parts use [React](https://react.dev/) and [Material UI](https://material-ui.com/), while the backend parts use [Node.js](https://nodejs.org/) and the [Express](https://expressjs.com/) framework. From 4fb15d223ea63f38b7bdec1f1637be32a85b2706 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Thu, 15 Jan 2026 15:32:10 +0100 Subject: [PATCH 143/202] fix(ui): add missing aria-label to SearchField components Add aria-label attributes to SearchField components to fix accessibility warnings from React Aria. - Select: add aria-label to SearchField using searchPlaceholder - MenuAutocomplete: add aria-label to RASearchField, remove redundant aria-label from RAInput - MenuAutocompleteListbox: same as MenuAutocomplete Per React Aria docs, aria-label should be on SearchField, not the child Input. Signed-off-by: Johan Persson --- .changeset/bumpy-carrots-stare.md | 7 +++++++ packages/ui/src/components/Menu/Menu.tsx | 4 ++-- packages/ui/src/components/Select/SelectContent.tsx | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 .changeset/bumpy-carrots-stare.md diff --git a/.changeset/bumpy-carrots-stare.md b/.changeset/bumpy-carrots-stare.md new file mode 100644 index 0000000000..9b3c39fcf1 --- /dev/null +++ b/.changeset/bumpy-carrots-stare.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Added missing `aria-label` attributes to `SearchField` components in `Select`, `MenuAutocomplete`, and `MenuAutocompleteListbox` to fix accessibility warnings. + +Affected components: Select, MenuAutocomplete, MenuAutocompleteListbox diff --git a/packages/ui/src/components/Menu/Menu.tsx b/packages/ui/src/components/Menu/Menu.tsx index 12048776be..75cdbe5e21 100644 --- a/packages/ui/src/components/Menu/Menu.tsx +++ b/packages/ui/src/components/Menu/Menu.tsx @@ -255,13 +255,13 @@ export const MenuAutocomplete = (props: MenuAutocompleteProps) => { classNames.searchField, styles[classNames.searchField], )} + aria-label={props.placeholder || 'Search'} > Date: Thu, 15 Jan 2026 15:18:14 +0100 Subject: [PATCH 144/202] feat(ui): add column width configuration to Table component Added support for configuring column widths in the Table component using React Aria's ResizableTableContainer. Columns now accept `width`, `defaultWidth`, `minWidth`, and `maxWidth` props. - `width`/`defaultWidth` accept ColumnSize (number, percentage, or fr units) - `minWidth`/`maxWidth` accept ColumnStaticSize (number or percentage) - Wrapped Table in ResizableTableContainer to enable width system - Updated stories to demonstrate proportional column widths Signed-off-by: Johan Persson --- .changeset/heavy-regions-camp.md | 7 + packages/ui/report.api.md | 10 +- .../src/components/Table/components/Table.tsx | 124 +++++++++--------- .../Table/stories/Table.dev.stories.tsx | 4 + .../Table/stories/Table.docs.stories.tsx | 4 + packages/ui/src/components/Table/types.ts | 6 +- 6 files changed, 94 insertions(+), 61 deletions(-) create mode 100644 .changeset/heavy-regions-camp.md diff --git a/.changeset/heavy-regions-camp.md b/.changeset/heavy-regions-camp.md new file mode 100644 index 0000000000..624758548d --- /dev/null +++ b/.changeset/heavy-regions-camp.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Added support for column width configuration in Table component. Columns now accept `width`, `defaultWidth`, `minWidth`, and `maxWidth` props for responsive layout control. + +Affected components: Table, Column diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 295d5e8d4f..7643469bdc 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -7,6 +7,8 @@ import { ButtonProps as ButtonProps_2 } from 'react-aria-components'; import { CellProps as CellProps_2 } from 'react-aria-components'; import { CheckboxProps as CheckboxProps_2 } from 'react-aria-components'; import { ColumnProps as ColumnProps_2 } from 'react-aria-components'; +import type { ColumnSize } from '@react-types/table'; +import type { ColumnStaticSize } from '@react-types/table'; import { ComponentProps } from 'react'; import type { ComponentPropsWithRef } from 'react'; import { DetailedHTMLProps } from 'react'; @@ -478,6 +480,8 @@ export interface ColumnConfig { // (undocumented) cell: (item: T) => ReactNode; // (undocumented) + defaultWidth?: ColumnSize | null; + // (undocumented) header?: () => ReactNode; // (undocumented) id: string; @@ -490,7 +494,11 @@ export interface ColumnConfig { // (undocumented) label: string; // (undocumented) - width?: number | string; + maxWidth?: ColumnStaticSize | null; + // (undocumented) + minWidth?: ColumnStaticSize | null; + // (undocumented) + width?: ColumnSize | null; } // @public (undocumented) diff --git a/packages/ui/src/components/Table/components/Table.tsx b/packages/ui/src/components/Table/components/Table.tsx index 84211901a6..103a1aece9 100644 --- a/packages/ui/src/components/Table/components/Table.tsx +++ b/packages/ui/src/components/Table/components/Table.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Key } from 'react-aria-components'; +import { type Key, ResizableTableContainer } from 'react-aria-components'; import { TableRoot } from './TableRoot'; import { TableHeader } from './TableHeader'; import { TableBody } from './TableBody'; @@ -132,67 +132,73 @@ export function Table({ {liveRegionLabel} - - - {column => - column.header ? ( - <>{column.header()} - ) : ( - - {column.label} - - ) - } - - {emptyState} : undefined - } + + - {item => { - const itemIndex = data?.indexOf(item) ?? -1; - - if (isRowRenderFn(rowConfig)) { - return rowConfig({ - item, - index: itemIndex, - }); + + {column => + column.header ? ( + <>{column.header()} + ) : ( + + {column.label} + + ) } + + {emptyState} : undefined + } + > + {item => { + const itemIndex = data?.indexOf(item) ?? -1; - return ( - rowConfig?.onClick?.(item) - : undefined - } - > - {column => ( - {column.cell(item)} - )} - - ); - }} - - + if (isRowRenderFn(rowConfig)) { + return rowConfig({ + item, + index: itemIndex, + }); + } + + return ( + rowConfig?.onClick?.(item) + : undefined + } + > + {column => ( + {column.cell(item)} + )} + + ); + }} + + + {pagination.type === 'page' && ( ( ), @@ -61,16 +62,19 @@ export const BasicLocalData: Story = { { id: 'owner', label: 'Owner', + defaultWidth: '1fr', cell: item => , }, { id: 'type', label: 'Type', + defaultWidth: '1fr', cell: item => , }, { id: 'lifecycle', label: 'Lifecycle', + defaultWidth: '1fr', cell: item => , }, ]; diff --git a/packages/ui/src/components/Table/stories/Table.docs.stories.tsx b/packages/ui/src/components/Table/stories/Table.docs.stories.tsx index 49e0b44885..aff656b083 100644 --- a/packages/ui/src/components/Table/stories/Table.docs.stories.tsx +++ b/packages/ui/src/components/Table/stories/Table.docs.stories.tsx @@ -40,6 +40,7 @@ export const TableRockBand: Story = { id: 'name', label: 'Band name', isRowHeader: true, + defaultWidth: '4fr', cell: item => ( ), @@ -47,16 +48,19 @@ export const TableRockBand: Story = { { id: 'genre', label: 'Genre', + defaultWidth: '4fr', cell: item => , }, { id: 'yearFormed', label: 'Year formed', + defaultWidth: '1fr', cell: item => , }, { id: 'albums', label: 'Albums', + defaultWidth: '1fr', cell: item => , }, ]; diff --git a/packages/ui/src/components/Table/types.ts b/packages/ui/src/components/Table/types.ts index 5fec5355e5..380be8817c 100644 --- a/packages/ui/src/components/Table/types.ts +++ b/packages/ui/src/components/Table/types.ts @@ -21,6 +21,7 @@ import { } from 'react-aria-components'; import type { ReactNode } from 'react'; import type { SortDescriptor as ReactStatelySortDescriptor } from 'react-stately'; +import type { ColumnSize, ColumnStaticSize } from '@react-types/table'; import type { TextColors } from '../../types'; import { TablePaginationProps } from '../TablePagination'; @@ -92,7 +93,10 @@ export interface ColumnConfig { header?: () => ReactNode; isSortable?: boolean; isHidden?: boolean; - width?: number | string; + width?: ColumnSize | null; + defaultWidth?: ColumnSize | null; + minWidth?: ColumnStaticSize | null; + maxWidth?: ColumnStaticSize | null; isRowHeader?: boolean; } From c29416593ecdb100c41ed8b3184ea2c4732eb2dd Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 15 Jan 2026 17:01:49 +0100 Subject: [PATCH 145/202] Migrate all components to their new format Signed-off-by: Charles de Dreuille --- .../components/accordion/accordion.props.ts | 119 ------ .../app/components/accordion/components.tsx | 130 ++++++ docs-ui/src/app/components/accordion/page.mdx | 41 +- .../components/accordion/props-definition.ts | 59 +++ .../src/app/components/accordion/snippets.ts | 68 ++++ .../src/app/components/accordion/stories.tsx | 19 - .../src/app/components/avatar/avatar.props.ts | 89 ----- .../src/app/components/avatar/components.tsx | 100 +++++ docs-ui/src/app/components/avatar/page.mdx | 30 +- .../app/components/avatar/props-definition.ts | 28 ++ docs-ui/src/app/components/avatar/snippets.ts | 52 +++ docs-ui/src/app/components/avatar/stories.tsx | 15 - docs-ui/src/app/components/box/components.tsx | 34 ++ docs-ui/src/app/components/box/page.mdx | 15 +- .../box/{box.props.ts => props-definition.ts} | 16 - docs-ui/src/app/components/box/snippets.ts | 21 + docs-ui/src/app/components/box/stories.tsx | 7 - .../button-icon/button-icon.props.ts | 62 --- .../app/components/button-icon/components.tsx | 68 ++++ .../src/app/components/button-icon/page.mdx | 38 +- .../button-icon/props-definition.ts | 31 ++ .../app/components/button-icon/snippets.ts | 22 ++ .../app/components/button-icon/stories.tsx | 15 - .../button-link/button-link.props.ts | 73 ---- .../app/components/button-link/components.tsx | 122 ++++++ .../src/app/components/button-link/page.mdx | 35 +- .../button-link/props-definition.ts | 32 ++ .../app/components/button-link/snippets.ts | 68 ++++ .../app/components/button-link/stories.tsx | 15 - .../src/app/components/button/button.props.ts | 77 ---- .../src/app/components/button/components.tsx | 78 ++++ docs-ui/src/app/components/button/page.mdx | 57 ++- .../app/components/button/props-definition.ts | 30 ++ docs-ui/src/app/components/button/snippets.ts | 54 +++ docs-ui/src/app/components/button/stories.tsx | 17 - docs-ui/src/app/components/card/card.props.ts | 83 ---- .../src/app/components/card/components.tsx | 66 ++++ docs-ui/src/app/components/card/page.mdx | 44 ++- .../app/components/card/props-definition.ts | 30 ++ docs-ui/src/app/components/card/snippets.ts | 41 ++ docs-ui/src/app/components/card/stories.tsx | 13 - .../app/components/checkbox/components.tsx | 21 + docs-ui/src/app/components/checkbox/page.mdx | 19 +- ...{checkbox.props.ts => props-definition.ts} | 13 - .../src/app/components/checkbox/snippets.ts | 12 + .../src/app/components/checkbox/stories.tsx | 8 - .../app/components/container/components.tsx | 24 ++ docs-ui/src/app/components/container/page.mdx | 14 +- .../components/container/props-definition.ts | 12 + .../{container.props.ts => snippets.ts} | 17 +- .../src/app/components/container/stories.tsx | 7 - .../src/app/components/dialog/components.tsx | 104 +++++ docs-ui/src/app/components/dialog/page.mdx | 6 +- .../app/components/dialog/props-definition.ts | 88 +++++ .../dialog/{dialog.props.ts => snippets.ts} | 93 +---- docs-ui/src/app/components/dialog/stories.tsx | 15 - .../src/app/components/flex/components.tsx | 38 ++ docs-ui/src/app/components/flex/flex.props.ts | 55 --- docs-ui/src/app/components/flex/page.mdx | 16 +- .../app/components/flex/props-definition.ts | 27 ++ docs-ui/src/app/components/flex/snippets.ts | 27 ++ docs-ui/src/app/components/flex/stories.tsx | 7 - .../src/app/components/grid/components.tsx | 59 +++ docs-ui/src/app/components/grid/grid.props.ts | 111 ------ docs-ui/src/app/components/grid/page.mdx | 65 +-- .../app/components/grid/props-definition.ts | 22 ++ docs-ui/src/app/components/grid/snippets.ts | 12 + docs-ui/src/app/components/grid/stories.tsx | 7 - .../app/components/header-page/components.tsx | 107 +++++ .../src/app/components/header-page/page.mdx | 7 +- ...ader-page.props.ts => props-definition.ts} | 52 --- .../app/components/header-page/snippets.ts | 51 +++ .../app/components/header-page/stories.tsx | 17 - .../src/app/components/header/components.tsx | 90 +++++ docs-ui/src/app/components/header/page.mdx | 6 +- .../app/components/header/props-definition.ts | 83 ++++ .../header/{header.props.ts => snippets.ts} | 84 ---- docs-ui/src/app/components/header/stories.tsx | 17 - .../src/app/components/link/components.tsx | 111 ++++++ docs-ui/src/app/components/link/link.props.ts | 81 ---- docs-ui/src/app/components/link/page.mdx | 71 ++-- .../app/components/link/props-definition.ts | 45 +++ docs-ui/src/app/components/link/snippets.ts | 54 +++ docs-ui/src/app/components/link/stories.tsx | 17 - .../src/app/components/menu/components.tsx | 143 +++++++ docs-ui/src/app/components/menu/menu.props.ts | 372 ------------------ docs-ui/src/app/components/menu/page.mdx | 26 +- .../app/components/menu/props-definition.ts | 241 ++++++++++++ docs-ui/src/app/components/menu/snippets.ts | 100 +++++ docs-ui/src/app/components/menu/stories.tsx | 31 -- .../components/password-field/components.tsx | 34 ++ .../app/components/password-field/page.mdx | 23 +- .../password-field/password-field.props.ts | 43 -- .../password-field/props-definition.ts | 26 ++ .../app/components/password-field/snippets.ts | 19 + .../app/components/password-field/stories.tsx | 13 - .../src/app/components/popover/components.tsx | 18 + docs-ui/src/app/components/popover/page.mdx | 78 +--- .../app/components/popover/popover.props.ts | 174 -------- .../components/popover/props-definition.ts | 55 +++ .../src/app/components/popover/snippets.ts | 19 + .../src/app/components/popover/stories.tsx | 7 - .../app/components/radio-group/components.tsx | 78 ++++ .../src/app/components/radio-group/page.mdx | 36 +- .../radio-group/props-definition.ts | 30 ++ .../radio-group/radio-group.props.ts | 64 --- .../app/components/radio-group/snippets.ts | 54 +++ .../app/components/radio-group/stories.tsx | 19 - .../components/search-field/components.tsx | 51 +++ .../src/app/components/search-field/page.mdx | 27 +- .../search-field/props-definition.ts | 34 ++ .../search-field/search-field.props.ts | 49 --- .../app/components/search-field/snippets.ts | 35 ++ .../app/components/search-field/stories.tsx | 15 - .../src/app/components/select/components.tsx | 150 +++++++ docs-ui/src/app/components/select/page.mdx | 6 +- .../app/components/select/props-definition.ts | 111 ++++++ .../select/{select.props.ts => snippets.ts} | 112 ------ docs-ui/src/app/components/select/stories.tsx | 27 -- .../app/components/skeleton/components.tsx | 31 ++ docs-ui/src/app/components/skeleton/page.mdx | 22 +- .../components/skeleton/props-definition.ts | 25 ++ .../app/components/skeleton/skeleton.props.ts | 58 --- .../src/app/components/skeleton/snippets.ts | 26 ++ .../src/app/components/skeleton/stories.tsx | 8 - .../src/app/components/switch/components.tsx | 11 + docs-ui/src/app/components/switch/page.mdx | 14 +- .../{switch.props.ts => props-definition.ts} | 4 - docs-ui/src/app/components/switch/snippets.ts | 7 + docs-ui/src/app/components/switch/stories.tsx | 8 - .../src/app/components/table/components.tsx | 163 ++++++++ docs-ui/src/app/components/table/page.mdx | 21 +- .../{table.props.ts => props-definition.ts} | 109 ----- docs-ui/src/app/components/table/snippets.ts | 108 +++++ docs-ui/src/app/components/table/stories.tsx | 21 - .../src/app/components/tabs/components.tsx | 104 +++++ docs-ui/src/app/components/tabs/page.mdx | 42 +- .../app/components/tabs/props-definition.ts | 65 +++ docs-ui/src/app/components/tabs/snippets.ts | 60 +++ docs-ui/src/app/components/tabs/stories.tsx | 17 - docs-ui/src/app/components/tabs/tabs.props.ts | 122 ------ .../app/components/tag-group/components.tsx | 127 ++++++ docs-ui/src/app/components/tag-group/page.mdx | 6 +- .../components/tag-group/props-definition.ts | 62 +++ .../src/app/components/tag-group/snippets.ts | 62 +++ .../src/app/components/tag-group/stories.tsx | 19 - .../components/tag-group/tag-group.props.ts | 161 -------- .../app/components/text-field/components.tsx | 47 +++ .../src/app/components/text-field/page.mdx | 23 +- .../components/text-field/props-definition.ts | 30 ++ .../src/app/components/text-field/snippets.ts | 31 ++ .../src/app/components/text-field/stories.tsx | 13 - .../components/text-field/text-field.props.ts | 43 -- .../src/app/components/text/components.tsx | 66 ++++ docs-ui/src/app/components/text/page.mdx | 40 +- .../app/components/text/props-definition.ts | 55 +++ docs-ui/src/app/components/text/snippets.ts | 43 ++ docs-ui/src/app/components/text/stories.tsx | 17 - docs-ui/src/app/components/text/text.props.ts | 131 ------ .../toggle-button-group/components.tsx | 79 ++++ .../components/toggle-button-group/page.mdx | 87 +--- .../toggle-button-group/props-definition.ts | 43 ++ .../toggle-button-group/snippets.ts | 59 +++ .../toggle-button-group/stories.tsx | 25 -- .../toggle-button-group.props.ts | 142 ------- .../components/toggle-button/components.tsx | 57 +++ .../src/app/components/toggle-button/page.mdx | 58 +-- .../toggle-button/props-definition.ts | 35 ++ .../app/components/toggle-button/snippets.ts | 42 ++ .../app/components/toggle-button/stories.tsx | 21 - .../toggle-button/toggle-button.props.ts | 195 --------- .../src/app/components/tooltip/components.tsx | 16 + docs-ui/src/app/components/tooltip/page.mdx | 17 +- .../{tooltip.props.ts => props-definition.ts} | 12 - .../src/app/components/tooltip/snippets.ts | 11 + .../src/app/components/tooltip/stories.tsx | 7 - .../components/visually-hidden/components.tsx | 39 ++ .../app/components/visually-hidden/page.mdx | 19 +- .../visually-hidden/props-definition.ts | 15 + .../{visually-hidden.props.ts => snippets.ts} | 20 +- .../components/visually-hidden/stories.tsx | 8 - 181 files changed, 5189 insertions(+), 3966 deletions(-) delete mode 100644 docs-ui/src/app/components/accordion/accordion.props.ts create mode 100644 docs-ui/src/app/components/accordion/components.tsx create mode 100644 docs-ui/src/app/components/accordion/props-definition.ts create mode 100644 docs-ui/src/app/components/accordion/snippets.ts delete mode 100644 docs-ui/src/app/components/accordion/stories.tsx delete mode 100644 docs-ui/src/app/components/avatar/avatar.props.ts create mode 100644 docs-ui/src/app/components/avatar/components.tsx create mode 100644 docs-ui/src/app/components/avatar/props-definition.ts create mode 100644 docs-ui/src/app/components/avatar/snippets.ts delete mode 100644 docs-ui/src/app/components/avatar/stories.tsx create mode 100644 docs-ui/src/app/components/box/components.tsx rename docs-ui/src/app/components/box/{box.props.ts => props-definition.ts} (54%) create mode 100644 docs-ui/src/app/components/box/snippets.ts delete mode 100644 docs-ui/src/app/components/box/stories.tsx delete mode 100644 docs-ui/src/app/components/button-icon/button-icon.props.ts create mode 100644 docs-ui/src/app/components/button-icon/components.tsx create mode 100644 docs-ui/src/app/components/button-icon/props-definition.ts create mode 100644 docs-ui/src/app/components/button-icon/snippets.ts delete mode 100644 docs-ui/src/app/components/button-icon/stories.tsx delete mode 100644 docs-ui/src/app/components/button-link/button-link.props.ts create mode 100644 docs-ui/src/app/components/button-link/components.tsx create mode 100644 docs-ui/src/app/components/button-link/props-definition.ts create mode 100644 docs-ui/src/app/components/button-link/snippets.ts delete mode 100644 docs-ui/src/app/components/button-link/stories.tsx delete mode 100644 docs-ui/src/app/components/button/button.props.ts create mode 100644 docs-ui/src/app/components/button/components.tsx create mode 100644 docs-ui/src/app/components/button/props-definition.ts create mode 100644 docs-ui/src/app/components/button/snippets.ts delete mode 100644 docs-ui/src/app/components/button/stories.tsx delete mode 100644 docs-ui/src/app/components/card/card.props.ts create mode 100644 docs-ui/src/app/components/card/components.tsx create mode 100644 docs-ui/src/app/components/card/props-definition.ts create mode 100644 docs-ui/src/app/components/card/snippets.ts delete mode 100644 docs-ui/src/app/components/card/stories.tsx create mode 100644 docs-ui/src/app/components/checkbox/components.tsx rename docs-ui/src/app/components/checkbox/{checkbox.props.ts => props-definition.ts} (64%) create mode 100644 docs-ui/src/app/components/checkbox/snippets.ts delete mode 100644 docs-ui/src/app/components/checkbox/stories.tsx create mode 100644 docs-ui/src/app/components/container/components.tsx create mode 100644 docs-ui/src/app/components/container/props-definition.ts rename docs-ui/src/app/components/container/{container.props.ts => snippets.ts} (60%) delete mode 100644 docs-ui/src/app/components/container/stories.tsx create mode 100644 docs-ui/src/app/components/dialog/components.tsx create mode 100644 docs-ui/src/app/components/dialog/props-definition.ts rename docs-ui/src/app/components/dialog/{dialog.props.ts => snippets.ts} (50%) delete mode 100644 docs-ui/src/app/components/dialog/stories.tsx create mode 100644 docs-ui/src/app/components/flex/components.tsx delete mode 100644 docs-ui/src/app/components/flex/flex.props.ts create mode 100644 docs-ui/src/app/components/flex/props-definition.ts create mode 100644 docs-ui/src/app/components/flex/snippets.ts delete mode 100644 docs-ui/src/app/components/flex/stories.tsx create mode 100644 docs-ui/src/app/components/grid/components.tsx delete mode 100644 docs-ui/src/app/components/grid/grid.props.ts create mode 100644 docs-ui/src/app/components/grid/props-definition.ts create mode 100644 docs-ui/src/app/components/grid/snippets.ts delete mode 100644 docs-ui/src/app/components/grid/stories.tsx create mode 100644 docs-ui/src/app/components/header-page/components.tsx rename docs-ui/src/app/components/header-page/{header-page.props.ts => props-definition.ts} (57%) create mode 100644 docs-ui/src/app/components/header-page/snippets.ts delete mode 100644 docs-ui/src/app/components/header-page/stories.tsx create mode 100644 docs-ui/src/app/components/header/components.tsx create mode 100644 docs-ui/src/app/components/header/props-definition.ts rename docs-ui/src/app/components/header/{header.props.ts => snippets.ts} (63%) delete mode 100644 docs-ui/src/app/components/header/stories.tsx create mode 100644 docs-ui/src/app/components/link/components.tsx delete mode 100644 docs-ui/src/app/components/link/link.props.ts create mode 100644 docs-ui/src/app/components/link/props-definition.ts create mode 100644 docs-ui/src/app/components/link/snippets.ts delete mode 100644 docs-ui/src/app/components/link/stories.tsx create mode 100644 docs-ui/src/app/components/menu/components.tsx delete mode 100644 docs-ui/src/app/components/menu/menu.props.ts create mode 100644 docs-ui/src/app/components/menu/props-definition.ts create mode 100644 docs-ui/src/app/components/menu/snippets.ts delete mode 100644 docs-ui/src/app/components/menu/stories.tsx create mode 100644 docs-ui/src/app/components/password-field/components.tsx delete mode 100644 docs-ui/src/app/components/password-field/password-field.props.ts create mode 100644 docs-ui/src/app/components/password-field/props-definition.ts create mode 100644 docs-ui/src/app/components/password-field/snippets.ts delete mode 100644 docs-ui/src/app/components/password-field/stories.tsx create mode 100644 docs-ui/src/app/components/popover/components.tsx delete mode 100644 docs-ui/src/app/components/popover/popover.props.ts create mode 100644 docs-ui/src/app/components/popover/props-definition.ts create mode 100644 docs-ui/src/app/components/popover/snippets.ts delete mode 100644 docs-ui/src/app/components/popover/stories.tsx create mode 100644 docs-ui/src/app/components/radio-group/components.tsx create mode 100644 docs-ui/src/app/components/radio-group/props-definition.ts delete mode 100644 docs-ui/src/app/components/radio-group/radio-group.props.ts create mode 100644 docs-ui/src/app/components/radio-group/snippets.ts delete mode 100644 docs-ui/src/app/components/radio-group/stories.tsx create mode 100644 docs-ui/src/app/components/search-field/components.tsx create mode 100644 docs-ui/src/app/components/search-field/props-definition.ts delete mode 100644 docs-ui/src/app/components/search-field/search-field.props.ts create mode 100644 docs-ui/src/app/components/search-field/snippets.ts delete mode 100644 docs-ui/src/app/components/search-field/stories.tsx create mode 100644 docs-ui/src/app/components/select/components.tsx create mode 100644 docs-ui/src/app/components/select/props-definition.ts rename docs-ui/src/app/components/select/{select.props.ts => snippets.ts} (54%) delete mode 100644 docs-ui/src/app/components/select/stories.tsx create mode 100644 docs-ui/src/app/components/skeleton/components.tsx create mode 100644 docs-ui/src/app/components/skeleton/props-definition.ts delete mode 100644 docs-ui/src/app/components/skeleton/skeleton.props.ts create mode 100644 docs-ui/src/app/components/skeleton/snippets.ts delete mode 100644 docs-ui/src/app/components/skeleton/stories.tsx create mode 100644 docs-ui/src/app/components/switch/components.tsx rename docs-ui/src/app/components/switch/{switch.props.ts => props-definition.ts} (93%) create mode 100644 docs-ui/src/app/components/switch/snippets.ts delete mode 100644 docs-ui/src/app/components/switch/stories.tsx create mode 100644 docs-ui/src/app/components/table/components.tsx rename docs-ui/src/app/components/table/{table.props.ts => props-definition.ts} (73%) create mode 100644 docs-ui/src/app/components/table/snippets.ts delete mode 100644 docs-ui/src/app/components/table/stories.tsx create mode 100644 docs-ui/src/app/components/tabs/components.tsx create mode 100644 docs-ui/src/app/components/tabs/props-definition.ts create mode 100644 docs-ui/src/app/components/tabs/snippets.ts delete mode 100644 docs-ui/src/app/components/tabs/stories.tsx delete mode 100644 docs-ui/src/app/components/tabs/tabs.props.ts create mode 100644 docs-ui/src/app/components/tag-group/components.tsx create mode 100644 docs-ui/src/app/components/tag-group/props-definition.ts create mode 100644 docs-ui/src/app/components/tag-group/snippets.ts delete mode 100644 docs-ui/src/app/components/tag-group/stories.tsx delete mode 100644 docs-ui/src/app/components/tag-group/tag-group.props.ts create mode 100644 docs-ui/src/app/components/text-field/components.tsx create mode 100644 docs-ui/src/app/components/text-field/props-definition.ts create mode 100644 docs-ui/src/app/components/text-field/snippets.ts delete mode 100644 docs-ui/src/app/components/text-field/stories.tsx delete mode 100644 docs-ui/src/app/components/text-field/text-field.props.ts create mode 100644 docs-ui/src/app/components/text/components.tsx create mode 100644 docs-ui/src/app/components/text/props-definition.ts create mode 100644 docs-ui/src/app/components/text/snippets.ts delete mode 100644 docs-ui/src/app/components/text/stories.tsx delete mode 100644 docs-ui/src/app/components/text/text.props.ts create mode 100644 docs-ui/src/app/components/toggle-button-group/components.tsx create mode 100644 docs-ui/src/app/components/toggle-button-group/props-definition.ts create mode 100644 docs-ui/src/app/components/toggle-button-group/snippets.ts delete mode 100644 docs-ui/src/app/components/toggle-button-group/stories.tsx delete mode 100644 docs-ui/src/app/components/toggle-button-group/toggle-button-group.props.ts create mode 100644 docs-ui/src/app/components/toggle-button/components.tsx create mode 100644 docs-ui/src/app/components/toggle-button/props-definition.ts create mode 100644 docs-ui/src/app/components/toggle-button/snippets.ts delete mode 100644 docs-ui/src/app/components/toggle-button/stories.tsx delete mode 100644 docs-ui/src/app/components/toggle-button/toggle-button.props.ts create mode 100644 docs-ui/src/app/components/tooltip/components.tsx rename docs-ui/src/app/components/tooltip/{tooltip.props.ts => props-definition.ts} (74%) create mode 100644 docs-ui/src/app/components/tooltip/snippets.ts delete mode 100644 docs-ui/src/app/components/tooltip/stories.tsx create mode 100644 docs-ui/src/app/components/visually-hidden/components.tsx create mode 100644 docs-ui/src/app/components/visually-hidden/props-definition.ts rename docs-ui/src/app/components/visually-hidden/{visually-hidden.props.ts => snippets.ts} (63%) delete mode 100644 docs-ui/src/app/components/visually-hidden/stories.tsx diff --git a/docs-ui/src/app/components/accordion/accordion.props.ts b/docs-ui/src/app/components/accordion/accordion.props.ts deleted file mode 100644 index aac983b0c3..0000000000 --- a/docs-ui/src/app/components/accordion/accordion.props.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const accordionPropDefs: Record = { - children: { - type: 'enum', - values: ['ReactNode', '(state: { isExpanded: boolean }) => ReactNode'], - }, - defaultExpanded: { - type: 'boolean', - default: 'false', - }, - isExpanded: { - type: 'boolean', - }, - onExpandedChange: { - type: 'enum', - values: ['(isExpanded: boolean) => void'], - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const accordionTriggerPropDefs: Record = { - level: { - type: 'enum', - values: ['1', '2', '3', '4', '5', '6'], - default: '3', - }, - title: { - type: 'string', - }, - subtitle: { - type: 'string', - }, - children: { - type: 'enum', - values: ['ReactNode'], - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const accordionPanelPropDefs: Record = { - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const accordionGroupPropDefs: Record = { - allowsMultiple: { - type: 'boolean', - default: 'false', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const accordionUsageSnippet = `import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui'; - - - - Your content -`; - -export const accordionWithSubtitleSnippet = ` - - - Your content here - -`; - -export const accordionCustomTriggerSnippet = ` - - - Custom Multi-line Trigger - - Click to expand additional details - - - - - Your content here - -`; - -export const accordionDefaultExpandedSnippet = ` - - - Your content here - -`; - -export const accordionGroupSingleOpenSnippet = ` - - - Content 1 - - - - Content 2 - -`; - -export const accordionGroupMultipleOpenSnippet = ` - - - Content 1 - - - - Content 2 - -`; diff --git a/docs-ui/src/app/components/accordion/components.tsx b/docs-ui/src/app/components/accordion/components.tsx new file mode 100644 index 0000000000..95b312d17c --- /dev/null +++ b/docs-ui/src/app/components/accordion/components.tsx @@ -0,0 +1,130 @@ +'use client'; + +import { + Accordion, + AccordionTrigger, + AccordionPanel, + AccordionGroup, +} from '../../../../../packages/ui/src/components/Accordion/Accordion'; +import { Box } from '../../../../../packages/ui/src/components/Box/Box'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; + +const Content = () => ( + + + It's the edge of the world and all of Western civilization + + + The sun may rise in the East, at least it settled in a final location + + + It's understood that Hollywood sells Californication + + +); + +export const Default = () => { + return ( + + + + + + + ); +}; + +export const WithSubtitle = () => { + return ( + + + + + + + ); +}; + +export const CustomTrigger = () => { + return ( + + + + + Custom Multi-line Trigger + + + Click to expand additional details and configuration options + + + + + + + + ); +}; + +export const DefaultExpanded = () => { + return ( + + + + + + + ); +}; + +export const GroupSingleOpen = () => { + return ( + + + + + + + + + + + + + + + + + + + + + ); +}; + +export const GroupMultipleOpen = () => { + return ( + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/docs-ui/src/app/components/accordion/page.mdx b/docs-ui/src/app/components/accordion/page.mdx index d2332673ea..83d650507b 100644 --- a/docs-ui/src/app/components/accordion/page.mdx +++ b/docs-ui/src/app/components/accordion/page.mdx @@ -1,6 +1,21 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; +import { + accordionPropDefs, + accordionTriggerPropDefs, + accordionPanelPropDefs, + accordionGroupPropDefs, +} from './props-definition'; +import { + accordionUsageSnippet, + defaultSnippet, + withSubtitleSnippet, + customTriggerSnippet, + defaultExpandedSnippet, + groupSingleOpenSnippet, + groupMultipleOpenSnippet, +} from './snippets'; import { Default, WithSubtitle, @@ -8,19 +23,7 @@ import { DefaultExpanded, GroupSingleOpen, GroupMultipleOpen, -} from './stories'; -import { - accordionPropDefs, - accordionTriggerPropDefs, - accordionPanelPropDefs, - accordionGroupPropDefs, - accordionUsageSnippet, - accordionWithSubtitleSnippet, - accordionCustomTriggerSnippet, - accordionDefaultExpandedSnippet, - accordionGroupSingleOpenSnippet, - accordionGroupMultipleOpenSnippet, -} from './accordion.props'; +} from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { AccordionDefinition } from '../../../utils/definitions'; @@ -36,7 +39,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; py={4} height={240} preview={} - code={accordionUsageSnippet} + code={defaultSnippet} /> ## Usage @@ -80,7 +83,7 @@ Here's a view when using both title and subtitle props. py={4} height={240} preview={} - code={accordionWithSubtitleSnippet} + code={withSubtitleSnippet} /> ### Custom Trigger @@ -92,7 +95,7 @@ Here's a view when providing custom multi-line content as children. py={4} height={280} preview={} - code={accordionCustomTriggerSnippet} + code={customTriggerSnippet} /> ### Default Expanded @@ -104,7 +107,7 @@ Here's a view when the panel is expanded by default. py={4} height={280} preview={} - code={accordionDefaultExpandedSnippet} + code={defaultExpandedSnippet} /> ### Group Single Open @@ -116,7 +119,7 @@ Here's a view when only one accordion can be open at a time. py={4} height={280} preview={} - code={accordionGroupSingleOpenSnippet} + code={groupSingleOpenSnippet} /> ### Group Multiple Open @@ -128,7 +131,7 @@ Here's a view when multiple accordions can be open simultaneously. py={4} height={280} preview={} - code={accordionGroupMultipleOpenSnippet} + code={groupMultipleOpenSnippet} /> diff --git a/docs-ui/src/app/components/accordion/props-definition.ts b/docs-ui/src/app/components/accordion/props-definition.ts new file mode 100644 index 0000000000..1df46eb648 --- /dev/null +++ b/docs-ui/src/app/components/accordion/props-definition.ts @@ -0,0 +1,59 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const accordionPropDefs: Record = { + children: { + type: 'enum', + values: ['ReactNode', '(state: { isExpanded: boolean }) => ReactNode'], + }, + defaultExpanded: { + type: 'boolean', + default: 'false', + }, + isExpanded: { + type: 'boolean', + }, + onExpandedChange: { + type: 'enum', + values: ['(isExpanded: boolean) => void'], + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const accordionTriggerPropDefs: Record = { + level: { + type: 'enum', + values: ['1', '2', '3', '4', '5', '6'], + default: '3', + }, + title: { + type: 'string', + }, + subtitle: { + type: 'string', + }, + children: { + type: 'enum', + values: ['ReactNode'], + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const accordionPanelPropDefs: Record = { + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const accordionGroupPropDefs: Record = { + allowsMultiple: { + type: 'boolean', + default: 'false', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/accordion/snippets.ts b/docs-ui/src/app/components/accordion/snippets.ts new file mode 100644 index 0000000000..83e89d1b64 --- /dev/null +++ b/docs-ui/src/app/components/accordion/snippets.ts @@ -0,0 +1,68 @@ +export const accordionUsageSnippet = `import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui'; + + + + Your content +`; + +export const defaultSnippet = ` + + + Your content here + +`; + +export const withSubtitleSnippet = ` + + + Your content here + +`; + +export const customTriggerSnippet = ` + + + + Custom Multi-line Trigger + + + Click to expand additional details and configuration options + + + + + Your content here + +`; + +export const defaultExpandedSnippet = ` + + + Your content here + +`; + +export const groupSingleOpenSnippet = ` + + + Content 1 + + + + Content 2 + +`; + +export const groupMultipleOpenSnippet = ` + + + Content 1 + + + + Content 2 + +`; diff --git a/docs-ui/src/app/components/accordion/stories.tsx b/docs-ui/src/app/components/accordion/stories.tsx deleted file mode 100644 index 3f871b840b..0000000000 --- a/docs-ui/src/app/components/accordion/stories.tsx +++ /dev/null @@ -1,19 +0,0 @@ -'use client'; - -import * as stories from '@backstage/ui/src/components/Accordion/Accordion.stories'; - -const { - Default: DefaultStory, - WithSubtitle: WithSubtitleStory, - CustomTrigger: CustomTriggerStory, - DefaultExpanded: DefaultExpandedStory, - GroupSingleOpen: GroupSingleOpenStory, - GroupMultipleOpen: GroupMultipleOpenStory, -} = stories; - -export const Default = () => ; -export const WithSubtitle = () => ; -export const CustomTrigger = () => ; -export const DefaultExpanded = () => ; -export const GroupSingleOpen = () => ; -export const GroupMultipleOpen = () => ; diff --git a/docs-ui/src/app/components/avatar/avatar.props.ts b/docs-ui/src/app/components/avatar/avatar.props.ts deleted file mode 100644 index 80621cdb89..0000000000 --- a/docs-ui/src/app/components/avatar/avatar.props.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; -import type { PropDef } from '@/utils/propDefs'; - -export const avatarPropDefs: Record = { - src: { - type: 'string', - }, - name: { - type: 'string', - }, - size: { - type: 'enum', - values: ['x-small', 'small', 'medium', 'large', 'x-large'], - default: 'medium', - responsive: true, - }, - purpose: { - type: 'enum', - values: ['informative', 'decoration'], - default: 'informative', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const snippetUsage = `import { Avatar } from '@backstage/ui'; - -`; - -export const snippetSizes = ` - - - - - -`; - -export const snippetFallback = ``; - -export const snippetPurpose = ` - - Informative (default) - - Use when avatar appears alone. Announced as "Charles de Dreuille" to screen readers: - - - - - - - Decoration - - Use when name appears adjacent to avatar. Hidden from screen readers to avoid redundancy: - - - - Charles de Dreuille - - -`; diff --git a/docs-ui/src/app/components/avatar/components.tsx b/docs-ui/src/app/components/avatar/components.tsx new file mode 100644 index 0000000000..da257f13b9 --- /dev/null +++ b/docs-ui/src/app/components/avatar/components.tsx @@ -0,0 +1,100 @@ +'use client'; + +import { Avatar } from '../../../../../packages/ui/src/components/Avatar/Avatar'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; + +export const Default = () => { + return ( + + ); +}; + +export const Fallback = () => { + return ( + + ); +}; + +export const Sizes = () => { + return ( + + + + + + + + + + + + + + + + + ); +}; + +export const Purpose = () => { + return ( + + + Informative (default) + + Use when avatar appears alone. Announced as "Charles de + Dreuille" to screen readers: + + + + + + + Decoration + + Use when avatar appears with adjacent text. Hidden from screen + readers: + + + + Charles de Dreuille + + + + ); +}; diff --git a/docs-ui/src/app/components/avatar/page.mdx b/docs-ui/src/app/components/avatar/page.mdx index 5ec0d78fdd..01e647ef7c 100644 --- a/docs-ui/src/app/components/avatar/page.mdx +++ b/docs-ui/src/app/components/avatar/page.mdx @@ -1,14 +1,15 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { Default, Sizes, Fallback, Purpose } from './stories'; +import { avatarPropDefs } from './props-definition'; import { - avatarPropDefs, - snippetUsage, - snippetSizes, - snippetFallback, - snippetPurpose, -} from './avatar.props'; + avatarUsageSnippet, + defaultSnippet, + sizesSnippet, + fallbackSnippet, + purposeSnippet, +} from './snippets'; +import { Default, Sizes, Fallback, Purpose } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { AvatarDefinition } from '../../../utils/definitions'; @@ -19,16 +20,11 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; description="An avatar component with a fallback for initials." /> -} - code={``} -/> +} code={defaultSnippet} /> ## Usage - + ## API reference @@ -45,7 +41,7 @@ Avatar sizes can be set using the `size` prop. py={4} open preview={} - code={snippetSizes} + code={sizesSnippet} /> ### Fallback @@ -57,7 +53,7 @@ If the image is not available, the avatar will show the initials of the name. py={4} open preview={} - code={snippetFallback} + code={fallbackSnippet} /> ### The `purpose` prop @@ -68,7 +64,7 @@ Control how the avatar is announced to screen readers using the `purpose` prop. align="center" py={4} preview={} - code={snippetPurpose} + code={purposeSnippet} /> diff --git a/docs-ui/src/app/components/avatar/props-definition.ts b/docs-ui/src/app/components/avatar/props-definition.ts new file mode 100644 index 0000000000..1a6de676c1 --- /dev/null +++ b/docs-ui/src/app/components/avatar/props-definition.ts @@ -0,0 +1,28 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const avatarPropDefs: Record = { + src: { + type: 'string', + }, + name: { + type: 'string', + required: true, + }, + size: { + type: 'enum', + values: ['x-small', 'small', 'medium', 'large', 'x-large'], + default: 'medium', + responsive: true, + }, + purpose: { + type: 'enum', + values: ['informative', 'decoration'], + default: 'informative', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/avatar/snippets.ts b/docs-ui/src/app/components/avatar/snippets.ts new file mode 100644 index 0000000000..ba3254dcd9 --- /dev/null +++ b/docs-ui/src/app/components/avatar/snippets.ts @@ -0,0 +1,52 @@ +export const avatarUsageSnippet = `import { Avatar } from '@backstage/ui'; + +`; + +export const defaultSnippet = ``; + +export const fallbackSnippet = ``; + +export const sizesSnippet = ` + + + + + + + + + + + + + + +`; + +export const purposeSnippet = ` + + Informative (default) + + Use when avatar appears alone. Announced as "Charles de Dreuille" to screen readers: + + + + + + + Decoration + + Use when avatar appears with adjacent text. Hidden from screen readers: + + + + Charles de Dreuille + + +`; diff --git a/docs-ui/src/app/components/avatar/stories.tsx b/docs-ui/src/app/components/avatar/stories.tsx deleted file mode 100644 index e8e9f601b1..0000000000 --- a/docs-ui/src/app/components/avatar/stories.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client'; - -import * as stories from '@backstage/ui/src/components/Avatar/Avatar.stories'; - -const { - Default: DefaultStory, - Sizes: SizesStory, - Fallback: FallbackStory, - Purpose: PurposeStory, -} = stories; - -export const Default = () => ; -export const Sizes = () => ; -export const Fallback = () => ; -export const Purpose = () => ; diff --git a/docs-ui/src/app/components/box/components.tsx b/docs-ui/src/app/components/box/components.tsx new file mode 100644 index 0000000000..466ecd3708 --- /dev/null +++ b/docs-ui/src/app/components/box/components.tsx @@ -0,0 +1,34 @@ +'use client'; + +import { Box } from '../../../../../packages/ui/src/components/Box/Box'; + +const diagonalStripePattern = (() => { + const svg = ` + + + + + + `.trim(); + return `data:image/svg+xml,${encodeURIComponent(svg)}`; +})(); + +export const Default = () => { + return ( + + ); +}; diff --git a/docs-ui/src/app/components/box/page.mdx b/docs-ui/src/app/components/box/page.mdx index f6e50b28d7..cc8641580f 100644 --- a/docs-ui/src/app/components/box/page.mdx +++ b/docs-ui/src/app/components/box/page.mdx @@ -1,14 +1,14 @@ import { CodeBlock } from '@/components/CodeBlock'; import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; -import { Default } from './stories'; +import { boxPropDefs } from './props-definition'; import { - boxPropDefs, snippetUsage, - boxPreviewSnippet, + defaultSnippet, boxSimpleSnippet, boxResponsiveSnippet, -} from './box.props'; +} from './snippets'; +import { Default } from './components'; import { spacingPropDefs } from '@/utils/propDefs'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; @@ -21,12 +21,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; description="Box is the lowest-level component in Backstage UI. It provides a consistent API for styling and layout." /> -} - code={boxPreviewSnippet} - align="center" -/> +} code={defaultSnippet} align="center" /> ## Usage diff --git a/docs-ui/src/app/components/box/box.props.ts b/docs-ui/src/app/components/box/props-definition.ts similarity index 54% rename from docs-ui/src/app/components/box/box.props.ts rename to docs-ui/src/app/components/box/props-definition.ts index 0ee45c80f4..a18a0f272e 100644 --- a/docs-ui/src/app/components/box/box.props.ts +++ b/docs-ui/src/app/components/box/props-definition.ts @@ -22,19 +22,3 @@ export const boxPropDefs: Record = { ...classNamePropDefs, ...stylePropDefs, }; - -export const snippetUsage = `import { Box } from '@backstage/ui'; - -`; - -export const boxPreviewSnippet = ` - -`; - -export const boxSimpleSnippet = `Hello World`; - -export const boxResponsiveSnippet = ` - Hello World -`; diff --git a/docs-ui/src/app/components/box/snippets.ts b/docs-ui/src/app/components/box/snippets.ts new file mode 100644 index 0000000000..5c19bbe59b --- /dev/null +++ b/docs-ui/src/app/components/box/snippets.ts @@ -0,0 +1,21 @@ +export const snippetUsage = `import { Box } from '@backstage/ui'; + +`; + +export const defaultSnippet = ``; + +export const boxSimpleSnippet = `Hello World`; + +export const boxResponsiveSnippet = ` + Hello World +`; diff --git a/docs-ui/src/app/components/box/stories.tsx b/docs-ui/src/app/components/box/stories.tsx deleted file mode 100644 index 82afa5fdff..0000000000 --- a/docs-ui/src/app/components/box/stories.tsx +++ /dev/null @@ -1,7 +0,0 @@ -'use client'; - -import * as stories from '@backstage/ui/src/components/Box/Box.stories'; - -const { Default: DefaultStory } = stories; - -export const Default = () => ; diff --git a/docs-ui/src/app/components/button-icon/button-icon.props.ts b/docs-ui/src/app/components/button-icon/button-icon.props.ts deleted file mode 100644 index b063484d27..0000000000 --- a/docs-ui/src/app/components/button-icon/button-icon.props.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const buttonIconPropDefs: Record = { - variant: { - type: 'enum', - values: ['primary', 'secondary'], - default: 'primary', - responsive: true, - }, - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'medium', - responsive: true, - }, - icon: { type: 'enum', values: ['ReactNode'], responsive: false }, - isDisabled: { type: 'boolean', default: 'false', responsive: false }, - loading: { type: 'boolean', default: 'false', responsive: false }, - type: { - type: 'enum', - values: ['button', 'submit', 'reset'], - default: 'button', - responsive: false, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const buttonIconUsageSnippet = `import { ButtonIcon } from '@backstage/ui'; - -`; - -export const buttonIconDefaultSnippet = ` - } variant="primary" /> - } variant="secondary" /> -`; - -export const buttonIconVariantsSnippet = ` - } variant="primary" /> - } variant="secondary" /> -`; - -export const buttonIconSizesSnippet = ` - } size="small" /> - } size="medium" /> -`; - -export const buttonIconDisabledSnippet = `} isDisabled />`; - -export const buttonIconLoadingSnippet = `} variant="primary" loading={isLoading} onPress={handleClick} />`; - -export const buttonIconResponsiveSnippet = `} variant={{ initial: 'primary', lg: 'secondary' }} />`; - -export const buttonIconAsLinkSnippet = `import { ButtonLink } from '@backstage/ui'; - - - Button -`; diff --git a/docs-ui/src/app/components/button-icon/components.tsx b/docs-ui/src/app/components/button-icon/components.tsx new file mode 100644 index 0000000000..3ed421289c --- /dev/null +++ b/docs-ui/src/app/components/button-icon/components.tsx @@ -0,0 +1,68 @@ +'use client'; + +import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { RiCloudLine } from '@remixicon/react'; + +export const Variants = () => { + return ( + + } variant="primary" aria-label="Cloud" /> + } + variant="secondary" + aria-label="Cloud" + /> + } + variant="tertiary" + aria-label="Cloud" + /> + + ); +}; + +export const Sizes = () => { + return ( + + } size="small" aria-label="Cloud" /> + } size="medium" aria-label="Cloud" /> + + ); +}; + +export const Disabled = () => { + return ( + + } + variant="primary" + aria-label="Cloud" + /> + } + variant="secondary" + aria-label="Cloud" + /> + } + variant="tertiary" + aria-label="Cloud" + /> + + ); +}; + +export const Loading = () => { + return ( + } + variant="primary" + loading + aria-label="Cloud" + /> + ); +}; diff --git a/docs-ui/src/app/components/button-icon/page.mdx b/docs-ui/src/app/components/button-icon/page.mdx index fc2d6eec9f..4fa239959b 100644 --- a/docs-ui/src/app/components/button-icon/page.mdx +++ b/docs-ui/src/app/components/button-icon/page.mdx @@ -1,18 +1,15 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { Variants, Sizes, Disabled, Loading } from './stories'; +import { buttonIconPropDefs } from './props-definition'; import { - buttonIconPropDefs, buttonIconUsageSnippet, - buttonIconDefaultSnippet, - buttonIconVariantsSnippet, - buttonIconSizesSnippet, - buttonIconDisabledSnippet, - buttonIconLoadingSnippet, - buttonIconResponsiveSnippet, - buttonIconAsLinkSnippet, -} from './button-icon.props'; + variantsSnippet, + sizesSnippet, + disabledSnippet, + loadingSnippet, +} from './snippets'; +import { Variants, Sizes, Disabled, Loading } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ButtonIconDefinition } from '../../../utils/definitions'; @@ -23,12 +20,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; description="A button component with a single icon that can be used to trigger actions." /> -} - code={buttonIconDefaultSnippet} -/> +} code={variantsSnippet} /> ## Usage @@ -49,7 +41,7 @@ Here's a view when buttons have different variants. py={4} open preview={} - code={buttonIconVariantsSnippet} + code={variantsSnippet} /> ### Sizes @@ -61,7 +53,7 @@ Here's a view when buttons have different sizes. py={4} open preview={} - code={buttonIconSizesSnippet} + code={sizesSnippet} /> ### Disabled @@ -73,7 +65,7 @@ Here's a view when buttons are disabled. py={4} open preview={} - code={buttonIconDisabledSnippet} + code={disabledSnippet} /> ### Loading @@ -85,15 +77,9 @@ Here's a view when buttons are in a loading state. py={4} open preview={} - code={buttonIconLoadingSnippet} + code={loadingSnippet} /> -### Responsive - -Here's a view when buttons are responsive. - - - diff --git a/docs-ui/src/app/components/button-icon/props-definition.ts b/docs-ui/src/app/components/button-icon/props-definition.ts new file mode 100644 index 0000000000..abad34dbf3 --- /dev/null +++ b/docs-ui/src/app/components/button-icon/props-definition.ts @@ -0,0 +1,31 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const buttonIconPropDefs: Record = { + variant: { + type: 'enum', + values: ['primary', 'secondary', 'tertiary'], + default: 'primary', + responsive: true, + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'medium', + responsive: true, + }, + icon: { type: 'enum', values: ['ReactNode'], responsive: false }, + isDisabled: { type: 'boolean', default: 'false', responsive: false }, + loading: { type: 'boolean', default: 'false', responsive: false }, + type: { + type: 'enum', + values: ['button', 'submit', 'reset'], + default: 'button', + responsive: false, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/button-icon/snippets.ts b/docs-ui/src/app/components/button-icon/snippets.ts new file mode 100644 index 0000000000..1ea9acc169 --- /dev/null +++ b/docs-ui/src/app/components/button-icon/snippets.ts @@ -0,0 +1,22 @@ +export const buttonIconUsageSnippet = `import { ButtonIcon } from '@backstage/ui'; + +`; + +export const variantsSnippet = ` + } variant="primary" aria-label="Cloud" /> + } variant="secondary" aria-label="Cloud" /> + } variant="tertiary" aria-label="Cloud" /> +`; + +export const sizesSnippet = ` + } size="small" aria-label="Cloud" /> + } size="medium" aria-label="Cloud" /> +`; + +export const disabledSnippet = ` + } variant="primary" aria-label="Cloud" /> + } variant="secondary" aria-label="Cloud" /> + } variant="tertiary" aria-label="Cloud" /> +`; + +export const loadingSnippet = `} variant="primary" loading aria-label="Cloud" />`; diff --git a/docs-ui/src/app/components/button-icon/stories.tsx b/docs-ui/src/app/components/button-icon/stories.tsx deleted file mode 100644 index 5d28fb28a6..0000000000 --- a/docs-ui/src/app/components/button-icon/stories.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client'; - -import * as stories from '@backstage/ui/src/components/ButtonIcon/ButtonIcon.stories'; - -const { - Variants: VariantsStory, - Sizes: SizesStory, - Disabled: DisabledStory, - Loading: LoadingStory, -} = stories; - -export const Variants = () => ; -export const Sizes = () => ; -export const Disabled = () => ; -export const Loading = () => ; diff --git a/docs-ui/src/app/components/button-link/button-link.props.ts b/docs-ui/src/app/components/button-link/button-link.props.ts deleted file mode 100644 index 2bd4e2196a..0000000000 --- a/docs-ui/src/app/components/button-link/button-link.props.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; -import type { PropDef } from '@/utils/propDefs'; - -export const buttonLinkPropDefs: Record = { - variant: { - type: 'enum', - values: ['primary', 'secondary'], - default: 'primary', - responsive: true, - }, - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'medium', - responsive: true, - }, - iconStart: { type: 'enum', values: ['ReactNode'], responsive: false }, - iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false }, - isDisabled: { type: 'boolean', default: 'false', responsive: false }, - href: { type: 'string', responsive: false }, - hrefLang: { type: 'string', responsive: false }, - target: { - type: 'enum', - values: ['HTMLAttributeAnchorTarget'], - default: '_self', - responsive: false, - }, - rel: { type: 'string', responsive: false }, - children: { type: 'enum', values: ['ReactNode'], responsive: false }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const buttonLinkSnippetUsage = `import { ButtonLink } from '@backstage/ui'; - -`; - -export const buttonLinkVariantsSnippet = ` - } variant="primary"> - Button - - } variant="secondary"> - Button - -`; - -export const buttonLinkSizesSnippet = ` - Small - Medium -`; - -export const buttonLinkIconsSnippet = ` - }>Button - }>Button - } - iconEnd={}> - Button - -`; - -export const buttonLinkDisabledSnippet = ` - - Primary - - - Secondary - -`; - -export const buttonLinkResponsiveSnippet = ` - Responsive Button -`; diff --git a/docs-ui/src/app/components/button-link/components.tsx b/docs-ui/src/app/components/button-link/components.tsx new file mode 100644 index 0000000000..4282ebf7dd --- /dev/null +++ b/docs-ui/src/app/components/button-link/components.tsx @@ -0,0 +1,122 @@ +'use client'; + +import { ButtonLink } from '../../../../../packages/ui/src/components/ButtonLink/ButtonLink'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { MemoryRouter } from 'react-router-dom'; +import { RiArrowRightSLine, RiCloudLine } from '@remixicon/react'; + +export const Variants = () => { + return ( + + + } + variant="primary" + href="https://ui.backstage.io" + target="_blank" + > + Button + + } + variant="secondary" + href="https://ui.backstage.io" + target="_blank" + > + Button + + } + variant="tertiary" + href="https://ui.backstage.io" + target="_blank" + > + Button + + + + ); +}; + +export const Sizes = () => { + return ( + + + + Small + + + Medium + + + + ); +}; + +export const WithIcons = () => { + return ( + + + } + href="https://ui.backstage.io" + target="_blank" + > + Button + + } + href="https://ui.backstage.io" + target="_blank" + > + Button + + } + iconEnd={} + href="https://ui.backstage.io" + target="_blank" + > + Button + + + + ); +}; + +export const Disabled = () => { + return ( + + + + Primary + + + Secondary + + + Tertiary + + + + ); +}; diff --git a/docs-ui/src/app/components/button-link/page.mdx b/docs-ui/src/app/components/button-link/page.mdx index 074ea204ec..cb2b0c19da 100644 --- a/docs-ui/src/app/components/button-link/page.mdx +++ b/docs-ui/src/app/components/button-link/page.mdx @@ -1,16 +1,15 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { Variants, Sizes, WithIcons, Disabled } from './stories'; +import { buttonLinkPropDefs } from './props-definition'; import { - buttonLinkPropDefs, - buttonLinkSnippetUsage, - buttonLinkVariantsSnippet, - buttonLinkSizesSnippet, - buttonLinkIconsSnippet, - buttonLinkDisabledSnippet, - buttonLinkResponsiveSnippet, -} from './button-link.props'; + buttonLinkUsageSnippet, + variantsSnippet, + sizesSnippet, + withIconsSnippet, + disabledSnippet, +} from './snippets'; +import { Variants, Sizes, WithIcons, Disabled } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ButtonLinkDefinition } from '../../../utils/definitions'; @@ -25,12 +24,12 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; align="center" py={4} preview={} - code={buttonLinkVariantsSnippet} + code={variantsSnippet} /> ## Usage - + ## API reference @@ -47,7 +46,7 @@ Here's a view when buttons have different variants. py={4} open preview={} - code={buttonLinkVariantsSnippet} + code={variantsSnippet} /> ### Sizes @@ -59,7 +58,7 @@ Here's a view when buttons have different sizes. py={4} open preview={} - code={buttonLinkSizesSnippet} + code={sizesSnippet} /> ### With Icons @@ -71,7 +70,7 @@ Here's a view when buttons have icons. py={4} open preview={} - code={buttonLinkIconsSnippet} + code={withIconsSnippet} /> ### Disabled @@ -83,15 +82,9 @@ Here's a view when buttons are disabled. py={4} open preview={} - code={buttonLinkDisabledSnippet} + code={disabledSnippet} /> -### Responsive - -Here's a view when buttons are responsive. - - - diff --git a/docs-ui/src/app/components/button-link/props-definition.ts b/docs-ui/src/app/components/button-link/props-definition.ts new file mode 100644 index 0000000000..ac711e0965 --- /dev/null +++ b/docs-ui/src/app/components/button-link/props-definition.ts @@ -0,0 +1,32 @@ +import { + classNamePropDefs, + stylePropDefs, + childrenPropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const buttonLinkPropDefs: Record = { + variant: { + type: 'enum', + values: ['primary', 'secondary', 'tertiary'], + default: 'primary', + responsive: true, + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'medium', + responsive: true, + }, + iconStart: { type: 'enum', values: ['ReactNode'], responsive: false }, + iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false }, + isDisabled: { type: 'boolean', default: 'false', responsive: false }, + href: { type: 'string', required: true }, + target: { + type: 'enum', + values: ['_self', '_blank', '_parent', '_top'], + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/button-link/snippets.ts b/docs-ui/src/app/components/button-link/snippets.ts new file mode 100644 index 0000000000..7f1a69027c --- /dev/null +++ b/docs-ui/src/app/components/button-link/snippets.ts @@ -0,0 +1,68 @@ +export const buttonLinkUsageSnippet = `import { ButtonLink } from '@backstage/ui'; + +Button`; + +export const variantsSnippet = ` + } + variant="primary" + href="https://ui.backstage.io" + target="_blank" + > + Button + + } + variant="secondary" + href="https://ui.backstage.io" + target="_blank" + > + Button + + } + variant="tertiary" + href="https://ui.backstage.io" + target="_blank" + > + Button + +`; + +export const sizesSnippet = ` + + Small + + + Medium + +`; + +export const withIconsSnippet = ` + } href="https://ui.backstage.io" target="_blank"> + Button + + } href="https://ui.backstage.io" target="_blank"> + Button + + } + iconEnd={} + href="https://ui.backstage.io" + target="_blank" + > + Button + +`; + +export const disabledSnippet = ` + + Primary + + + Secondary + + + Tertiary + +`; diff --git a/docs-ui/src/app/components/button-link/stories.tsx b/docs-ui/src/app/components/button-link/stories.tsx deleted file mode 100644 index a865afe87c..0000000000 --- a/docs-ui/src/app/components/button-link/stories.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client'; - -import * as stories from '@backstage/ui/src/components/ButtonLink/ButtonLink.stories'; - -const { - Variants: VariantsStory, - Sizes: SizesStory, - WithIcons: WithIconsStory, - Disabled: DisabledStory, -} = stories; - -export const Variants = () => ; -export const Sizes = () => ; -export const WithIcons = () => ; -export const Disabled = () => ; diff --git a/docs-ui/src/app/components/button/button.props.ts b/docs-ui/src/app/components/button/button.props.ts deleted file mode 100644 index e650a9f2e9..0000000000 --- a/docs-ui/src/app/components/button/button.props.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; -import type { PropDef } from '@/utils/propDefs'; - -export const buttonPropDefs: Record = { - variant: { - type: 'enum', - values: ['primary', 'secondary'], - default: 'primary', - responsive: true, - }, - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'medium', - responsive: true, - }, - iconStart: { type: 'enum', values: ['ReactNode'], responsive: false }, - iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false }, - isDisabled: { type: 'boolean', default: 'false', responsive: false }, - loading: { type: 'boolean', default: 'false', responsive: false }, - children: { type: 'enum', values: ['ReactNode'], responsive: false }, - type: { - type: 'enum', - values: ['button', 'submit', 'reset'], - default: 'button', - responsive: false, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const buttonSnippetUsage = `import { Button } from '@backstage/ui'; - - - -`; - -export const buttonSizesSnippet = ` - - -`; - -export const buttonIconsSnippet = ` - - - -`; - -export const buttonDisabledSnippet = ` - - -`; - -export const buttonResponsiveSnippet = ``; - -export const buttonLoadingSnippet = ``; - -export const buttonAsLinkSnippet = `import { ButtonLink } from '@backstage/ui'; - - - Button -`; diff --git a/docs-ui/src/app/components/button/components.tsx b/docs-ui/src/app/components/button/components.tsx new file mode 100644 index 0000000000..558c852e9c --- /dev/null +++ b/docs-ui/src/app/components/button/components.tsx @@ -0,0 +1,78 @@ +'use client'; + +import { Button } from '../../../../../packages/ui/src/components/Button/Button'; +import { ButtonLink } from '../../../../../packages/ui/src/components/ButtonLink/ButtonLink'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { RiArrowRightSLine, RiCloudLine } from '@remixicon/react'; +import { MemoryRouter } from 'react-router-dom'; + +export const Variants = () => { + return ( + + + + + + ); +}; + +export const Sizes = () => { + return ( + + + + + ); +}; + +export const WithIcons = () => { + return ( + + + + + + ); +}; + +export const Disabled = () => { + return ( + + + + + + ); +}; + +export const Loading = () => { + return ( + + ); +}; + +export const AsLink = () => { + return ( + + + Button + + + ); +}; diff --git a/docs-ui/src/app/components/button/page.mdx b/docs-ui/src/app/components/button/page.mdx index daead3ba92..38ff2e2d57 100644 --- a/docs-ui/src/app/components/button/page.mdx +++ b/docs-ui/src/app/components/button/page.mdx @@ -1,35 +1,36 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { Variants, Sizes, WithIcons, Disabled, Loading } from './stories'; -import { Variants as ButtonLinkVariants } from '../button-link/stories'; +import { buttonPropDefs } from './props-definition'; import { - buttonPropDefs, + variantsSnippet, + sizesSnippet, + withIconsSnippet, + disabledSnippet, + loadingSnippet, + asLinkSnippet, buttonSnippetUsage, - buttonVariantsSnippet, - buttonSizesSnippet, - buttonIconsSnippet, - buttonDisabledSnippet, - buttonLoadingSnippet, buttonResponsiveSnippet, - buttonAsLinkSnippet, -} from './button.props'; +} from './snippets'; import { ChangelogComponent } from '@/components/ChangelogComponent'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ButtonDefinition } from '../../../utils/definitions'; +import { + Variants, + Sizes, + WithIcons, + Disabled, + Loading, + AsLink, +} from './components'; -} - code={buttonVariantsSnippet} -/> +} code={variantsSnippet} /> ## Usage @@ -50,20 +51,14 @@ Here's a view when buttons have different variants. py={4} open preview={} - code={buttonVariantsSnippet} + code={variantsSnippet} /> ### Sizes Here's a view when buttons have different sizes. -} - code={buttonSizesSnippet} -/> +} code={sizesSnippet} /> ### With Icons @@ -74,7 +69,7 @@ Here's a view when buttons have icons. py={4} open preview={} - code={buttonIconsSnippet} + code={withIconsSnippet} /> ### Disabled @@ -86,7 +81,7 @@ Here's a view when buttons are disabled. py={4} open preview={} - code={buttonDisabledSnippet} + code={disabledSnippet} /> ### Loading @@ -98,7 +93,7 @@ Here's a view when buttons are in a loading state. py={4} open preview={} - code={buttonLoadingSnippet} + code={loadingSnippet} /> ### Responsive @@ -111,13 +106,7 @@ Here's a view when buttons are responsive. If you want to use a button as a link, please use the `ButtonLink` component. -} - code={buttonAsLinkSnippet} -/> +} code={asLinkSnippet} /> diff --git a/docs-ui/src/app/components/button/props-definition.ts b/docs-ui/src/app/components/button/props-definition.ts new file mode 100644 index 0000000000..4b225ad460 --- /dev/null +++ b/docs-ui/src/app/components/button/props-definition.ts @@ -0,0 +1,30 @@ +import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; +import type { PropDef } from '@/utils/propDefs'; + +export const buttonPropDefs: Record = { + variant: { + type: 'enum', + values: ['primary', 'secondary'], + default: 'primary', + responsive: true, + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'medium', + responsive: true, + }, + iconStart: { type: 'enum', values: ['ReactNode'], responsive: false }, + iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false }, + isDisabled: { type: 'boolean', default: 'false', responsive: false }, + loading: { type: 'boolean', default: 'false', responsive: false }, + children: { type: 'enum', values: ['ReactNode'], responsive: false }, + type: { + type: 'enum', + values: ['button', 'submit', 'reset'], + default: 'button', + responsive: false, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/button/snippets.ts b/docs-ui/src/app/components/button/snippets.ts new file mode 100644 index 0000000000..a31523b064 --- /dev/null +++ b/docs-ui/src/app/components/button/snippets.ts @@ -0,0 +1,54 @@ +export const buttonSnippetUsage = `import { Button } from '@backstage/ui'; + +`; + +export const variantsSnippet = ` + + + + `; + +export const sizesSnippet = ` + + + `; + +export const withIconsSnippet = ` + + + + `; + +export const disabledSnippet = ` + + + + `; + +export const loadingSnippet = ``; + +export const asLinkSnippet = ` + + Button + + `; diff --git a/docs-ui/src/app/components/button/stories.tsx b/docs-ui/src/app/components/button/stories.tsx deleted file mode 100644 index b958eb8fdf..0000000000 --- a/docs-ui/src/app/components/button/stories.tsx +++ /dev/null @@ -1,17 +0,0 @@ -'use client'; - -import * as stories from '@backstage/ui/src/components/Button/Button.stories'; - -const { - Variants: VariantsStory, - Sizes: SizesStory, - WithIcons: WithIconsStory, - Disabled: DisabledStory, - Loading: LoadingStory, -} = stories; - -export const Variants = () => ; -export const Sizes = () => ; -export const WithIcons = () => ; -export const Disabled = () => ; -export const Loading = () => ; diff --git a/docs-ui/src/app/components/card/card.props.ts b/docs-ui/src/app/components/card/card.props.ts deleted file mode 100644 index 70c71bdf56..0000000000 --- a/docs-ui/src/app/components/card/card.props.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const cardPropDefs: Record = { - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const cardHeaderPropDefs: Record = { - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const cardBodyPropDefs: Record = { - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const cardFooterPropDefs: Record = { - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const cardUsageSnippet = `import { card } from '@backstage/ui'; - - - Header - Body - Footer -`; - -export const cardDefaultSnippet = ` - Header - Body - Footer -`; - -export const cardLongBodySnippet = ` - - Header - - - - This is the first paragraph of a long body text that demonstrates how - the Card component handles extensive content. The card should adjust - accordingly to display all the text properly while maintaining its - structure. - - - Here's a second paragraph that adds more content to our card body. - Having multiple paragraphs helps to visualize how spacing works within - the card component. - - - This third paragraph continues to add more text to ensure we have a - proper demonstration of a card with significant content. This makes it - easier to test scrolling behavior and overall layout when content - exceeds the initial view. - - - - Footer - -`; - -export const cardListRowSnippet = ` - - Header - - - Hello world - Hello world - Hello world - Hello world - ... - - - Footer - -`; diff --git a/docs-ui/src/app/components/card/components.tsx b/docs-ui/src/app/components/card/components.tsx new file mode 100644 index 0000000000..89ab09d47d --- /dev/null +++ b/docs-ui/src/app/components/card/components.tsx @@ -0,0 +1,66 @@ +'use client'; + +import { + Card, + CardHeader, + CardBody, + CardFooter, +} from '../../../../../packages/ui/src/components/Card/Card'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; + +export const Default = () => { + return ( + + Header + Body + Footer + + ); +}; + +export const CustomSize = () => { + return ( + + Header + Body + Footer + + ); +}; + +export const WithLongBody = () => { + return ( + + + Header + + + + This is the first paragraph of a long body text that demonstrates how + the Card component handles extensive content. The card should adjust + accordingly to display all the text properly while maintaining its + structure. + + + Here's a second paragraph that adds more content to our card + body. Having multiple paragraphs helps to visualize how spacing works + within the card component. + + + This third paragraph continues to add more text to ensure we have a + proper demonstration of a card with significant content. This makes it + easier to test scrolling behavior and overall layout when content + exceeds the initial view. + + + + Footer + + + ); +}; diff --git a/docs-ui/src/app/components/card/page.mdx b/docs-ui/src/app/components/card/page.mdx index fd1ed233d9..3f50c7c160 100644 --- a/docs-ui/src/app/components/card/page.mdx +++ b/docs-ui/src/app/components/card/page.mdx @@ -1,17 +1,19 @@ import { PropsTable } from '@/components/PropsTable'; -import { CustomSize, WithLongBody, WithListRow } from './stories'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; import { cardPropDefs, - cardUsageSnippet, - cardDefaultSnippet, cardHeaderPropDefs, cardBodyPropDefs, cardFooterPropDefs, - cardLongBodySnippet, - cardListRowSnippet, -} from './card.props'; +} from './props-definition'; +import { + cardUsageSnippet, + defaultSnippet, + customSizeSnippet, + withLongBodySnippet, +} from './snippets'; +import { Default, CustomSize, WithLongBody } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { CardDefinition } from '../../../utils/definitions'; @@ -25,8 +27,8 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; } - code={cardDefaultSnippet} + preview={} + code={defaultSnippet} /> ## Usage @@ -61,6 +63,18 @@ To display a footer in a card, use the `CardFooter` component. This will be fixe ## Examples +### Custom size + +Here's a view when card has a custom size. + +} + code={customSizeSnippet} + open +/> + ### With long body Here's a view when card has a long body. @@ -69,19 +83,7 @@ Here's a view when card has a long body. align="center" py={4} preview={} - code={cardLongBodySnippet} - open -/> - -### With list - -Here's a view when card has a list. - -} - code={cardListRowSnippet} + code={withLongBodySnippet} open /> diff --git a/docs-ui/src/app/components/card/props-definition.ts b/docs-ui/src/app/components/card/props-definition.ts new file mode 100644 index 0000000000..bb7bdad0dd --- /dev/null +++ b/docs-ui/src/app/components/card/props-definition.ts @@ -0,0 +1,30 @@ +import { + classNamePropDefs, + stylePropDefs, + childrenPropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const cardPropDefs: Record = { + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const cardHeaderPropDefs: Record = { + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const cardBodyPropDefs: Record = { + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const cardFooterPropDefs: Record = { + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/card/snippets.ts b/docs-ui/src/app/components/card/snippets.ts new file mode 100644 index 0000000000..b5de9a7008 --- /dev/null +++ b/docs-ui/src/app/components/card/snippets.ts @@ -0,0 +1,41 @@ +export const cardUsageSnippet = `import { Card, CardHeader, CardBody, CardFooter } from '@backstage/ui'; + + + Header + Body + Footer +`; + +export const defaultSnippet = ` + Header + Body + Footer +`; + +export const customSizeSnippet = ` + Header + Body + Footer +`; + +export const withLongBodySnippet = ` + + Header + + + + This is the first paragraph of a long body text that demonstrates how + the Card component handles extensive content. + + + Here's a second paragraph that adds more content to our card body. + + + This third paragraph continues to add more text to ensure we have a + proper demonstration of a card with significant content. + + + + Footer + +`; diff --git a/docs-ui/src/app/components/card/stories.tsx b/docs-ui/src/app/components/card/stories.tsx deleted file mode 100644 index d9c4b80ec4..0000000000 --- a/docs-ui/src/app/components/card/stories.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client'; - -import * as stories from '@backstage/ui/src/components/Card/Card.stories'; - -const { - CustomSize: CustomSizeStory, - WithLongBody: WithLongBodyStory, - WithListRow: WithListRowStory, -} = stories; - -export const CustomSize = () => ; -export const WithLongBody = () => ; -export const WithListRow = () => ; diff --git a/docs-ui/src/app/components/checkbox/components.tsx b/docs-ui/src/app/components/checkbox/components.tsx new file mode 100644 index 0000000000..a826c9e02f --- /dev/null +++ b/docs-ui/src/app/components/checkbox/components.tsx @@ -0,0 +1,21 @@ +'use client'; + +import { Checkbox } from '../../../../../packages/ui/src/components/Checkbox/Checkbox'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; + +export const Default = () => { + return Accept terms and conditions; +}; + +export const AllVariants = () => { + return ( + + Unchecked + Checked + Disabled + + Checked & Disabled + + + ); +}; diff --git a/docs-ui/src/app/components/checkbox/page.mdx b/docs-ui/src/app/components/checkbox/page.mdx index 5f1735bc50..aaf73d7210 100644 --- a/docs-ui/src/app/components/checkbox/page.mdx +++ b/docs-ui/src/app/components/checkbox/page.mdx @@ -1,13 +1,13 @@ import { PropsTable } from '@/components/PropsTable'; -import { Default, AllVariants } from './stories'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; +import { checkboxPropDefs } from './props-definition'; import { - checkboxPropDefs, checkboxUsageSnippet, - checkboxDefaultSnippet, - checkboxVariantsSnippet, -} from './checkbox.props'; + defaultSnippet, + allVariantsSnippet, +} from './snippets'; +import { Default, AllVariants } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { CheckboxDefinition } from '../../../utils/definitions'; @@ -18,12 +18,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; description="A checkbox component that can be used to trigger actions." /> -} - code={checkboxDefaultSnippet} -/> +} code={defaultSnippet} /> ## Usage @@ -44,7 +39,7 @@ Here's a view when checkboxes have different variants. py={4} open preview={} - code={checkboxVariantsSnippet} + code={allVariantsSnippet} /> diff --git a/docs-ui/src/app/components/checkbox/checkbox.props.ts b/docs-ui/src/app/components/checkbox/props-definition.ts similarity index 64% rename from docs-ui/src/app/components/checkbox/checkbox.props.ts rename to docs-ui/src/app/components/checkbox/props-definition.ts index ce0f1033ff..9e5d8a1e16 100644 --- a/docs-ui/src/app/components/checkbox/checkbox.props.ts +++ b/docs-ui/src/app/components/checkbox/props-definition.ts @@ -46,16 +46,3 @@ export const checkboxPropDefs: Record = { ...classNamePropDefs, ...stylePropDefs, }; - -export const checkboxUsageSnippet = `import { Checkbox } from '@backstage/ui'; - -Accept terms`; - -export const checkboxDefaultSnippet = `Accept terms and conditions`; - -export const checkboxVariantsSnippet = ` - Unchecked - Checked - Disabled - Checked & Disabled -`; diff --git a/docs-ui/src/app/components/checkbox/snippets.ts b/docs-ui/src/app/components/checkbox/snippets.ts new file mode 100644 index 0000000000..bef496195f --- /dev/null +++ b/docs-ui/src/app/components/checkbox/snippets.ts @@ -0,0 +1,12 @@ +export const checkboxUsageSnippet = `import { Checkbox } from '@backstage/ui'; + +Accept terms`; + +export const defaultSnippet = `Accept terms and conditions`; + +export const allVariantsSnippet = ` + Unchecked + Checked + Disabled + Checked & Disabled +`; diff --git a/docs-ui/src/app/components/checkbox/stories.tsx b/docs-ui/src/app/components/checkbox/stories.tsx deleted file mode 100644 index 55d75ed3a6..0000000000 --- a/docs-ui/src/app/components/checkbox/stories.tsx +++ /dev/null @@ -1,8 +0,0 @@ -'use client'; - -import * as stories from '@backstage/ui/src/components/Checkbox/Checkbox.stories'; - -const { Default: DefaultStory, AllVariants: AllVariantsStory } = stories; - -export const Default = () => ; -export const AllVariants = () => ; diff --git a/docs-ui/src/app/components/container/components.tsx b/docs-ui/src/app/components/container/components.tsx new file mode 100644 index 0000000000..7032b3f620 --- /dev/null +++ b/docs-ui/src/app/components/container/components.tsx @@ -0,0 +1,24 @@ +'use client'; + +import { Box } from '../../../../../packages/ui/src/components/Box/Box'; + +const DecorativeBox = () => ( + +); + +export const Preview = () => { + return ( +
+ +
+ ); +}; diff --git a/docs-ui/src/app/components/container/page.mdx b/docs-ui/src/app/components/container/page.mdx index 18c307b104..87bc55829e 100644 --- a/docs-ui/src/app/components/container/page.mdx +++ b/docs-ui/src/app/components/container/page.mdx @@ -1,14 +1,14 @@ import { CodeBlock } from '@/components/CodeBlock'; import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; -import { Preview } from './stories'; +import { containerPropDefs } from './props-definition'; import { - containerPropDefs, containerUsageSnippet, - containerDefaultSnippet, + previewSnippet, containerSimpleSnippet, containerResponsiveSnippet, -} from './container.props'; +} from './snippets'; +import { Preview } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ContainerDefinition } from '../../../utils/definitions'; @@ -20,11 +20,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; description="The container component let you use our default max-width and center the content on the page." /> -} - code={containerDefaultSnippet} -/> +} code={previewSnippet} /> ## Usage diff --git a/docs-ui/src/app/components/container/props-definition.ts b/docs-ui/src/app/components/container/props-definition.ts new file mode 100644 index 0000000000..0980520365 --- /dev/null +++ b/docs-ui/src/app/components/container/props-definition.ts @@ -0,0 +1,12 @@ +import { + classNamePropDefs, + stylePropDefs, + gapPropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const containerPropDefs: Record = { + ...gapPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/container/container.props.ts b/docs-ui/src/app/components/container/snippets.ts similarity index 60% rename from docs-ui/src/app/components/container/container.props.ts rename to docs-ui/src/app/components/container/snippets.ts index a4d23a8495..83f4dabbbc 100644 --- a/docs-ui/src/app/components/container/container.props.ts +++ b/docs-ui/src/app/components/container/snippets.ts @@ -1,23 +1,10 @@ -import { - classNamePropDefs, - stylePropDefs, - gapPropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const containerPropDefs: Record = { - ...gapPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; - export const containerUsageSnippet = `import { Container } from "@backstage/ui"; Hello World!`; -export const containerDefaultSnippet = ` +export const previewSnippet = `
-`; +
`; export const containerSimpleSnippet = ` Hello World diff --git a/docs-ui/src/app/components/container/stories.tsx b/docs-ui/src/app/components/container/stories.tsx deleted file mode 100644 index 95f4097e12..0000000000 --- a/docs-ui/src/app/components/container/stories.tsx +++ /dev/null @@ -1,7 +0,0 @@ -'use client'; - -import * as stories from '@backstage/ui/src/components/Container/Container.stories'; - -const { Preview: PreviewStory } = stories; - -export const Preview = () => ; diff --git a/docs-ui/src/app/components/dialog/components.tsx b/docs-ui/src/app/components/dialog/components.tsx new file mode 100644 index 0000000000..3a1fd8c2c9 --- /dev/null +++ b/docs-ui/src/app/components/dialog/components.tsx @@ -0,0 +1,104 @@ +'use client'; + +import { + Dialog, + DialogTrigger, + DialogHeader, + DialogBody, + DialogFooter, +} from '../../../../../packages/ui/src/components/Dialog/Dialog'; +import { Button } from '../../../../../packages/ui/src/components/Button/Button'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; +import { TextField } from '../../../../../packages/ui/src/components/TextField/TextField'; +import { Select } from '../../../../../packages/ui/src/components/Select/Select'; + +export const Default = () => ( + + + + Example Dialog + + This is a basic dialog example. + + + + + + + +); + +export const PreviewFixedWidthAndHeight = () => ( + + + + Long Content Dialog + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim + ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip ex ea commodo consequat. + + + Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat + cupidatat non proident, sunt in culpa qui officia deserunt mollit + anim id est laborum. + + + Sed ut perspiciatis unde omnis iste natus error sit voluptatem + accusantium doloremque laudantium, totam rem aperiam, eaque ipsa + quae ab illo inventore veritatis et quasi architecto beatae vitae + dicta sunt explicabo. + + + + + + + + + +); + +export const PreviewWithForm = () => ( + + + + Create New User + + + + + +); + +export const WithLabelAndDescription = () => ( + + } + style={{ width: 300 }} + /> +); + +export const Disabled = () => ( + +); + +export const Searchable = () => ( + +); + +export const SearchableMultiple = () => ( + ; -export const WithLabelAndDescription = () => ( - -); -export const Sizes = () => ; -export const WithIcon = () => ; -export const Disabled = () => ; -export const DisabledOption = () => ; -export const Searchable = () => ; -export const MultipleSelection = () => ; -export const SearchableMultiple = () => ; diff --git a/docs-ui/src/app/components/skeleton/components.tsx b/docs-ui/src/app/components/skeleton/components.tsx new file mode 100644 index 0000000000..277aa17ae3 --- /dev/null +++ b/docs-ui/src/app/components/skeleton/components.tsx @@ -0,0 +1,31 @@ +'use client'; + +import { Skeleton } from '../../../../../packages/ui/src/components/Skeleton/Skeleton'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; + +export const Demo1 = () => { + return ( + + + + + + + + + + + + + ); +}; + +export const Demo2 = () => { + return ( + + + + + + ); +}; diff --git a/docs-ui/src/app/components/skeleton/page.mdx b/docs-ui/src/app/components/skeleton/page.mdx index a32672e352..21eb35ac5f 100644 --- a/docs-ui/src/app/components/skeleton/page.mdx +++ b/docs-ui/src/app/components/skeleton/page.mdx @@ -1,14 +1,13 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { Demo1, Demo2 } from './stories'; +import { skeletonPropDefs } from './props-definition'; import { - skeletonPropDefs, skeletonUsageSnippet, - skeletonDefaultSnippet, - skeletonDemo1Snippet, - skeletonDemo2Snippet, -} from './skeleton.props'; + demo1Snippet, + demo2Snippet, +} from './snippets'; +import { Demo1, Demo2 } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; @@ -19,12 +18,7 @@ import { SkeletonDefinition } from '../../../utils/definitions'; description="Use to show a placeholder while content is loading." /> -} - code={skeletonDefaultSnippet} -/> +} code={demo2Snippet} /> ## Usage @@ -44,7 +38,7 @@ You can use a mix of different sizes to create a more complex skeleton. align="center" py={4} preview={} - code={skeletonDemo1Snippet} + code={demo1Snippet} open /> @@ -56,7 +50,7 @@ You can use a mix of different sizes to create a more complex skeleton. align="center" py={4} preview={} - code={skeletonDemo2Snippet} + code={demo2Snippet} open /> diff --git a/docs-ui/src/app/components/skeleton/props-definition.ts b/docs-ui/src/app/components/skeleton/props-definition.ts new file mode 100644 index 0000000000..bb80024bcd --- /dev/null +++ b/docs-ui/src/app/components/skeleton/props-definition.ts @@ -0,0 +1,25 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const skeletonPropDefs: Record = { + width: { + type: 'number', + default: '80', + responsive: false, + }, + height: { + type: 'number', + default: '24', + responsive: false, + }, + rounded: { + type: 'boolean', + default: 'false', + responsive: false, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/skeleton/skeleton.props.ts b/docs-ui/src/app/components/skeleton/skeleton.props.ts deleted file mode 100644 index ab79268d15..0000000000 --- a/docs-ui/src/app/components/skeleton/skeleton.props.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const skeletonPropDefs: Record = { - width: { - type: 'number', - default: '80', - responsive: false, - }, - height: { - type: 'number', - default: '24', - responsive: false, - }, - rounded: { - type: 'boolean', - default: 'false', - responsive: false, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const skeletonUsageSnippet = `import { Flex, Skeleton } from '@backstage/ui'; - - - - - -`; - -export const skeletonDefaultSnippet = ` - - - -`; - -export const skeletonDemo1Snippet = ` - - - - - - - - - - -`; - -export const skeletonDemo2Snippet = ` - - - -`; diff --git a/docs-ui/src/app/components/skeleton/snippets.ts b/docs-ui/src/app/components/skeleton/snippets.ts new file mode 100644 index 0000000000..981ac5ff0c --- /dev/null +++ b/docs-ui/src/app/components/skeleton/snippets.ts @@ -0,0 +1,26 @@ +export const skeletonUsageSnippet = `import { Flex, Skeleton } from '@backstage/ui'; + + + + + +`; + +export const demo1Snippet = ` + + + + + + + + + + +`; + +export const demo2Snippet = ` + + + +`; diff --git a/docs-ui/src/app/components/skeleton/stories.tsx b/docs-ui/src/app/components/skeleton/stories.tsx deleted file mode 100644 index c699cea620..0000000000 --- a/docs-ui/src/app/components/skeleton/stories.tsx +++ /dev/null @@ -1,8 +0,0 @@ -'use client'; - -import * as stories from '@backstage/ui/src/components/Skeleton/Skeleton.stories'; - -const { Demo1: Demo1Story, Demo2: Demo2Story } = stories; - -export const Demo1 = () => ; -export const Demo2 = () => ; diff --git a/docs-ui/src/app/components/switch/components.tsx b/docs-ui/src/app/components/switch/components.tsx new file mode 100644 index 0000000000..604d9d68b1 --- /dev/null +++ b/docs-ui/src/app/components/switch/components.tsx @@ -0,0 +1,11 @@ +'use client'; + +import { Switch } from '../../../../../packages/ui/src/components/Switch/Switch'; + +export const Default = () => { + return ; +}; + +export const Disabled = () => { + return ; +}; diff --git a/docs-ui/src/app/components/switch/page.mdx b/docs-ui/src/app/components/switch/page.mdx index e721973a50..494a046f47 100644 --- a/docs-ui/src/app/components/switch/page.mdx +++ b/docs-ui/src/app/components/switch/page.mdx @@ -1,8 +1,9 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { Default, Disabled } from './stories'; -import { switchPropDefs, snippetUsage } from './switch.props'; +import { switchPropDefs } from './props-definition'; +import { snippetUsage, defaultSnippet, disabledSnippet } from './snippets'; +import { Default, Disabled } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; @@ -13,12 +14,7 @@ import { SwitchDefinition } from '../../../utils/definitions'; description="A control that indicates whether a setting is on or off." /> -} - code={``} -/> +} code={defaultSnippet} /> ## Usage @@ -39,7 +35,7 @@ A switch can be disabled using the `isDisabled` prop. py={4} open preview={} - code={``} + code={disabledSnippet} /> diff --git a/docs-ui/src/app/components/switch/switch.props.ts b/docs-ui/src/app/components/switch/props-definition.ts similarity index 93% rename from docs-ui/src/app/components/switch/switch.props.ts rename to docs-ui/src/app/components/switch/props-definition.ts index 5576436102..769c93aac8 100644 --- a/docs-ui/src/app/components/switch/switch.props.ts +++ b/docs-ui/src/app/components/switch/props-definition.ts @@ -65,7 +65,3 @@ export const switchPropDefs: Record = { type: 'string', }, }; - -export const snippetUsage = `import { Switch } from '@backstage/ui'; - -`; diff --git a/docs-ui/src/app/components/switch/snippets.ts b/docs-ui/src/app/components/switch/snippets.ts new file mode 100644 index 0000000000..694f15106c --- /dev/null +++ b/docs-ui/src/app/components/switch/snippets.ts @@ -0,0 +1,7 @@ +export const snippetUsage = `import { Switch } from '@backstage/ui'; + +`; + +export const defaultSnippet = ``; + +export const disabledSnippet = ``; diff --git a/docs-ui/src/app/components/switch/stories.tsx b/docs-ui/src/app/components/switch/stories.tsx deleted file mode 100644 index 1a8a56a051..0000000000 --- a/docs-ui/src/app/components/switch/stories.tsx +++ /dev/null @@ -1,8 +0,0 @@ -'use client'; - -import * as stories from '@backstage/ui/src/components/Switch/Switch.stories'; - -const { Default: DefaultStory, Disabled: DisabledStory } = stories; - -export const Default = () => ; -export const Disabled = () => ; diff --git a/docs-ui/src/app/components/table/components.tsx b/docs-ui/src/app/components/table/components.tsx new file mode 100644 index 0000000000..8850d3a214 --- /dev/null +++ b/docs-ui/src/app/components/table/components.tsx @@ -0,0 +1,163 @@ +'use client'; + +import dynamic from 'next/dynamic'; + +// Import Table components dynamically with ssr: false to avoid SSR issues +const TableComponents = dynamic( + () => + import('../../../../../packages/ui/src/components/Table').then(mod => ({ + default: () => null, // Placeholder + Table: mod.Table, + TableHeader: mod.TableHeader, + Column: mod.Column, + TableBody: mod.TableBody, + Row: mod.Row, + Cell: mod.Cell, + })), + { ssr: false }, +); + +import { + Table, + TableHeader, + Column, + TableBody, + Row, + Cell, +} from '../../../../../packages/ui/src/components/Table'; +import { useState } from 'react'; +import type { Selection } from 'react-aria-components'; + +const rockBands = [ + { id: 1, name: 'The Beatles', genre: 'Rock', year: 1960 }, + { id: 2, name: 'Led Zeppelin', genre: 'Hard Rock', year: 1968 }, + { id: 3, name: 'Pink Floyd', genre: 'Progressive Rock', year: 1965 }, + { id: 4, name: 'The Rolling Stones', genre: 'Rock', year: 1962 }, + { id: 5, name: 'Queen', genre: 'Rock', year: 1970 }, +]; + +// Simple wrapper to ensure client-side only rendering +const ClientOnly = ({ children }: { children: React.ReactNode }) => { + const [mounted, setMounted] = useState(false); + + useState(() => { + if (typeof window !== 'undefined') { + setMounted(true); + } + }); + + if (!mounted) return
Loading...
; + return <>{children}; +}; + +export const TableRockBand = () => ( + +
+ + Name + Genre + Year + + + {rockBands.map(band => ( + + {band.name} + {band.genre} + {band.year} + + ))} + +
+ +); + +export const SelectionModePlayground = () => { + const [selectedKeys, setSelectedKeys] = useState(new Set()); + + return ( + + + + Name + Genre + Year + + + {rockBands.map(band => ( + + {band.name} + {band.genre} + {band.year} + + ))} + +
+
+ ); +}; + +export const SelectionBehaviorPlayground = () => { + const [selectedKeys, setSelectedKeys] = useState(new Set()); + + return ( + + + + Name + Genre + Year + + + {rockBands.map(band => ( + + {band.name} + {band.genre} + {band.year} + + ))} + +
+
+ ); +}; + +export const SelectionToggleWithActions = () => { + const [selectedKeys, setSelectedKeys] = useState(new Set()); + + return ( + + + + Name + Genre + Year + + + {rockBands.map(band => ( + + {band.name} + {band.genre} + {band.year} + + ))} + +
+
+ ); +}; diff --git a/docs-ui/src/app/components/table/page.mdx b/docs-ui/src/app/components/table/page.mdx index db87494f21..d3461124d2 100644 --- a/docs-ui/src/app/components/table/page.mdx +++ b/docs-ui/src/app/components/table/page.mdx @@ -1,26 +1,23 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { TableRockBand, SelectionModePlayground, SelectionBehaviorPlayground, SelectionToggleWithActions } from './stories'; +import { TableRockBand, SelectionModePlayground, SelectionBehaviorPlayground, SelectionToggleWithActions } from './components'; import { tablePropDefs, tableHeaderPropDefs, tableBodyPropDefs, tablePaginationPropDefs, - tableUsageSnippet, - tableBasicSnippet, - tableRowClickSnippet, - tableHybridSnippet, - tableCellInteractionsSnippet, - tablePaginationSnippet, - tableSelectionActionsSnippet, - tableSelectionModeSnippet, - tableSelectionBehaviorSnippet, - tableSortingSnippet, columnPropDefs, rowPropDefs, cellPropDefs, -} from './table.props'; +} from './props-definition'; +import { + tableUsageSnippet, + tableBasicSnippet, + tableSelectionActionsSnippet, + tableSelectionModeSnippet, + tableSelectionBehaviorSnippet, +} from './snippets'; import { ChangelogComponent } from '@/components/ChangelogComponent'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; diff --git a/docs-ui/src/app/components/table/table.props.ts b/docs-ui/src/app/components/table/props-definition.ts similarity index 73% rename from docs-ui/src/app/components/table/table.props.ts rename to docs-ui/src/app/components/table/props-definition.ts index 21bd5597c6..e4ef0ffe8b 100644 --- a/docs-ui/src/app/components/table/table.props.ts +++ b/docs-ui/src/app/components/table/props-definition.ts @@ -266,112 +266,3 @@ export const tablePaginationPropDefs: Record = { ...classNamePropDefs, ...stylePropDefs, }; - -export const tableUsageSnippet = `import { Cell, CellText, ..., TableHeader, TablePagination } from '@backstage/ui'; - - - - - - - - - - - -
-`; - -export const tableBasicSnippet = `import { Table, TableHeader, Column, TableBody, Row, CellText, CellProfile, TablePagination, useTable } from '@backstage/ui'; - -const data = [ - { - name: 'The Beatles', - image: 'https://upload.wikimedia.org/wikipedia/en/thumb/4/42/Beatles_-...jpg', - genre: 'Rock, Pop, Psychedelic Rock', - yearFormed: 1960, - albums: 13 - }, - // ... more data -]; - -// Uncontrolled pagination (easiest) -const { data: paginatedData, paginationProps } = useTable({ - data, - pagination: { - defaultPageSize: 5, - }, -}); - - - - Band name - Genre - Year formed - Albums - - - {paginatedData?.map(item => ( - - - - - - - ))} - -
-`; - -export const tableSelectionActionsSnippet = `import { Table, TableHeader, TableBody, Column, Row, CellText } from '@backstage/ui'; - -function MyTable() { - const [selectedKeys, setSelectedKeys] = React.useState(new Set([])); - - return ( - console.log('Opening', key)} - > - - Name - Status - - - - - - - - - - - -
- ); -}`; - -export const tableSelectionModeSnippet = ` - {/* ... */} -
`; - -export const tableSelectionBehaviorSnippet = ` - {/* ... */} -
`; diff --git a/docs-ui/src/app/components/table/snippets.ts b/docs-ui/src/app/components/table/snippets.ts new file mode 100644 index 0000000000..0c8e029330 --- /dev/null +++ b/docs-ui/src/app/components/table/snippets.ts @@ -0,0 +1,108 @@ +export const tableUsageSnippet = `import { Cell, CellText, ..., TableHeader, TablePagination } from '@backstage/ui'; + + + + + + + + + + + +
+`; + +export const tableBasicSnippet = `import { Table, TableHeader, Column, TableBody, Row, CellText, CellProfile, TablePagination, useTable } from '@backstage/ui'; + +const data = [ + { + name: 'The Beatles', + image: 'https://upload.wikimedia.org/wikipedia/en/thumb/4/42/Beatles_-...jpg', + genre: 'Rock, Pop, Psychedelic Rock', + yearFormed: 1960, + albums: 13 + }, + // ... more data +]; + +// Uncontrolled pagination (easiest) +const { data: paginatedData, paginationProps } = useTable({ + data, + pagination: { + defaultPageSize: 5, + }, +}); + + + + Band name + Genre + Year formed + Albums + + + {paginatedData?.map(item => ( + + + + + + + ))} + +
+`; + +export const tableSelectionActionsSnippet = `import { Table, TableHeader, TableBody, Column, Row, CellText } from '@backstage/ui'; + +function MyTable() { + const [selectedKeys, setSelectedKeys] = React.useState(new Set([])); + + return ( + console.log('Opening', key)} + > + + Name + Status + + + + + + + + + + + +
+ ); +}`; + +export const tableSelectionModeSnippet = ` + {/* ... */} +
`; + +export const tableSelectionBehaviorSnippet = ` + {/* ... */} +
`; diff --git a/docs-ui/src/app/components/table/stories.tsx b/docs-ui/src/app/components/table/stories.tsx deleted file mode 100644 index 2627694d30..0000000000 --- a/docs-ui/src/app/components/table/stories.tsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client'; - -import * as stories from '@backstage/ui/src/components/Table/stories/Table.docs.stories'; - -const { - TableRockBand: TableRockBandStory, - SelectionModePlayground: SelectionModePlaygroundStory, - SelectionBehaviorPlayground: SelectionBehaviorPlaygroundStory, - SelectionToggleWithActions: SelectionToggleWithActionsStory, -} = stories; - -export const TableRockBand = () => ; -export const SelectionModePlayground = () => ( - -); -export const SelectionBehaviorPlayground = () => ( - -); -export const SelectionToggleWithActions = () => ( - -); diff --git a/docs-ui/src/app/components/tabs/components.tsx b/docs-ui/src/app/components/tabs/components.tsx new file mode 100644 index 0000000000..8fbf3d4924 --- /dev/null +++ b/docs-ui/src/app/components/tabs/components.tsx @@ -0,0 +1,104 @@ +'use client'; + +import { + Tabs, + TabList, + Tab, + TabPanel, +} from '../../../../../packages/ui/src/components/Tabs'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; +import { MemoryRouter } from 'react-router-dom'; + +export const Default = () => { + return ( + + + + Tab 1 + Tab 2 + Tab 3 + + + Content for Tab 1 + + + Content for Tab 2 + + + Content for Tab 3 + + + + ); +}; + +export const DefaultSelectedKey = () => { + return ( + + + + Tab 1 + Tab 2 + Tab 3 + + + Content for Tab 1 + + + Content for Tab 2 + + + Content for Tab 3 + + + + ); +}; + +export const DisabledTabs = () => { + return ( + + + + Tab 1 + + Tab 2 (Disabled) + + Tab 3 + + + Content for Tab 1 + + + Content for Tab 2 + + + Content for Tab 3 + + + + ); +}; + +export const Orientation = () => { + return ( + + + + Tab 1 + Tab 2 + Tab 3 + + + Content for Tab 1 + + + Content for Tab 2 + + + Content for Tab 3 + + + + ); +}; diff --git a/docs-ui/src/app/components/tabs/page.mdx b/docs-ui/src/app/components/tabs/page.mdx index 0f3ab6bfd4..0c51b96471 100644 --- a/docs-ui/src/app/components/tabs/page.mdx +++ b/docs-ui/src/app/components/tabs/page.mdx @@ -1,16 +1,20 @@ import { PropsTable } from '@/components/PropsTable'; -import { Default, WithTabPanels, WithMockedURLTab2, PrefixMatchingDeepNesting } from './stories'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; import { tabsPropDefs, + tabListPropDefs, tabPropDefs, + tabPanelPropDefs, +} from './props-definition'; +import { tabsUsageSnippet, - tabsDefaultSnippet, - tabsWithTabPanelsSnippet, - tabsWithLinksSnippet, - tabsWithDeeplyNestedRoutesSnippet, -} from './tabs.props'; + defaultSnippet, + defaultSelectedKeySnippet, + disabledTabsSnippet, + orientationSnippet, +} from './snippets'; +import { Default, DefaultSelectedKey, DisabledTabs, Orientation } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { TabsDefinition } from '../../../utils/definitions'; @@ -24,7 +28,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; } - code={tabsDefaultSnippet} + code={defaultSnippet} /> ## Usage @@ -47,36 +51,36 @@ An individual interactive tab button that toggles the corresponding panel. Rende ## Examples -### Simple tabs +### Default Selected Key -To connect the tabs with the panels, you need to use the `id` prop on the tab and the tab panel. +Here's how to set which tab is selected by default. } - code={tabsWithTabPanelsSnippet} + preview={} + code={defaultSelectedKeySnippet} open /> -### Tabs as links +### Disabled Tabs -You can use the `href` prop on the tab to make it a link. This will use the `react-router` under the hood to navigate to the tab. We automatically detect if this is an external URL and render a `` element instead of a ` + I am a tooltip + + ); +}; diff --git a/docs-ui/src/app/components/tooltip/page.mdx b/docs-ui/src/app/components/tooltip/page.mdx index c62ece88ec..35d650c99b 100644 --- a/docs-ui/src/app/components/tooltip/page.mdx +++ b/docs-ui/src/app/components/tooltip/page.mdx @@ -1,13 +1,9 @@ import { PropsTable } from '@/components/PropsTable'; -import { Default } from './stories'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { - tooltipDefaultSnippet, - tooltipUsageSnippet, - tooltipTriggerPropDefs, - tooltipPropDefs, -} from './tooltip.props'; +import { tooltipTriggerPropDefs, tooltipPropDefs } from './props-definition'; +import { tooltipUsageSnippet, defaultSnippet } from './snippets'; +import { Default } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { TooltipDefinition } from '../../../utils/definitions'; @@ -18,12 +14,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; description="A tooltip displays a description of an element on hover or focus." /> -} - code={tooltipDefaultSnippet} - align="center" -/> +} code={defaultSnippet} align="center" /> ## Usage diff --git a/docs-ui/src/app/components/tooltip/tooltip.props.ts b/docs-ui/src/app/components/tooltip/props-definition.ts similarity index 74% rename from docs-ui/src/app/components/tooltip/tooltip.props.ts rename to docs-ui/src/app/components/tooltip/props-definition.ts index fe64051288..dc0919f4cc 100644 --- a/docs-ui/src/app/components/tooltip/tooltip.props.ts +++ b/docs-ui/src/app/components/tooltip/props-definition.ts @@ -53,15 +53,3 @@ export const tooltipPropDefs: Record = { ...classNamePropDefs, ...stylePropDefs, }; - -export const tooltipUsageSnippet = `import { TooltipTrigger, Tooltip, Button } from '@backstage/ui'; - - - - I am a tooltip -`; - -export const tooltipDefaultSnippet = ` - - I am a tooltip -`; diff --git a/docs-ui/src/app/components/tooltip/snippets.ts b/docs-ui/src/app/components/tooltip/snippets.ts new file mode 100644 index 0000000000..1cf9fb66da --- /dev/null +++ b/docs-ui/src/app/components/tooltip/snippets.ts @@ -0,0 +1,11 @@ +export const tooltipUsageSnippet = `import { TooltipTrigger, Tooltip, Button } from '@backstage/ui'; + + + + I am a tooltip +`; + +export const defaultSnippet = ` + + I am a tooltip +`; diff --git a/docs-ui/src/app/components/tooltip/stories.tsx b/docs-ui/src/app/components/tooltip/stories.tsx deleted file mode 100644 index 101131db43..0000000000 --- a/docs-ui/src/app/components/tooltip/stories.tsx +++ /dev/null @@ -1,7 +0,0 @@ -'use client'; - -import * as stories from '@backstage/ui/src/components/Tooltip/Tooltip.stories'; - -const { Default: DefaultStory } = stories; - -export const Default = () => ; diff --git a/docs-ui/src/app/components/visually-hidden/components.tsx b/docs-ui/src/app/components/visually-hidden/components.tsx new file mode 100644 index 0000000000..8298465240 --- /dev/null +++ b/docs-ui/src/app/components/visually-hidden/components.tsx @@ -0,0 +1,39 @@ +'use client'; + +import { VisuallyHidden } from '../../../../../packages/ui/src/components/VisuallyHidden/VisuallyHidden'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; + +export const Default = () => { + return ( + + + This text is followed by a paragraph that is visually hidden but + accessible to screen readers. Try using a screen reader to hear it, or + inspect the DOM to see it's there. + + + This content is visually hidden but accessible to screen readers + + + ); +}; + +export const ExampleUsage = () => { + return ( + + + Footer links + + + About us + + + Jobs + + + Terms and Conditions + + + ); +}; diff --git a/docs-ui/src/app/components/visually-hidden/page.mdx b/docs-ui/src/app/components/visually-hidden/page.mdx index 98591e502c..07b4bff28d 100644 --- a/docs-ui/src/app/components/visually-hidden/page.mdx +++ b/docs-ui/src/app/components/visually-hidden/page.mdx @@ -1,13 +1,13 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { Default, ExampleUsage } from './stories'; +import { visuallyHiddenPropDefs } from './props-definition'; import { - visuallyHiddenPropDefs, visuallyHiddenUsageSnippet, - visuallyHiddenDefaultSnippet, - visuallyHiddenExampleUsageSnippet, -} from './visually-hidden.props'; + defaultSnippet, + exampleUsageSnippet, +} from './snippets'; +import { Default, ExampleUsage } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { VisuallyHiddenDefinition } from '../../../utils/definitions'; @@ -18,12 +18,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; description="Visually hides content while keeping it accessible to screen readers." /> -} - code={visuallyHiddenDefaultSnippet} -/> +} code={defaultSnippet} /> ## Usage @@ -43,7 +38,7 @@ Here's an example of providing screen reader context for a list of links in a fo align="start" py={4} preview={} - code={visuallyHiddenExampleUsageSnippet} + code={exampleUsageSnippet} open /> diff --git a/docs-ui/src/app/components/visually-hidden/props-definition.ts b/docs-ui/src/app/components/visually-hidden/props-definition.ts new file mode 100644 index 0000000000..c57270c733 --- /dev/null +++ b/docs-ui/src/app/components/visually-hidden/props-definition.ts @@ -0,0 +1,15 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const visuallyHiddenPropDefs: Record = { + children: { + type: 'enum', + values: ['ReactNode'], + responsive: false, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/visually-hidden/visually-hidden.props.ts b/docs-ui/src/app/components/visually-hidden/snippets.ts similarity index 63% rename from docs-ui/src/app/components/visually-hidden/visually-hidden.props.ts rename to docs-ui/src/app/components/visually-hidden/snippets.ts index 18a1c503d1..21ce6a04de 100644 --- a/docs-ui/src/app/components/visually-hidden/visually-hidden.props.ts +++ b/docs-ui/src/app/components/visually-hidden/snippets.ts @@ -1,26 +1,10 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const visuallyHiddenPropDefs: Record = { - children: { - type: 'enum', - values: ['ReactNode'], - responsive: false, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - export const visuallyHiddenUsageSnippet = `import { VisuallyHidden } from '@backstage/ui'; This content is visually hidden but accessible to screen readers `; -export const visuallyHiddenDefaultSnippet = ` +export const defaultSnippet = ` This text is followed by a paragraph that is visually hidden but accessible to screen readers. Try using a screen reader to hear it, or @@ -31,7 +15,7 @@ export const visuallyHiddenDefaultSnippet = ` `; -export const visuallyHiddenExampleUsageSnippet = ` +export const exampleUsageSnippet = ` Footer links diff --git a/docs-ui/src/app/components/visually-hidden/stories.tsx b/docs-ui/src/app/components/visually-hidden/stories.tsx deleted file mode 100644 index 2edefe0f94..0000000000 --- a/docs-ui/src/app/components/visually-hidden/stories.tsx +++ /dev/null @@ -1,8 +0,0 @@ -'use client'; - -import * as stories from '@backstage/ui/src/components/VisuallyHidden/VisuallyHidden.stories'; - -const { Default: DefaultStory, ExampleUsage: ExampleUsageStory } = stories; - -export const Default = () => ; -export const ExampleUsage = () => ; From 8e4fc4234849d67b1a9a9d1c1656d0116d2315ae Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 15 Jan 2026 17:49:42 +0100 Subject: [PATCH 146/202] Fixes a few docs pages Signed-off-by: Charles de Dreuille --- .../src/app/components/card/components.tsx | 2 +- .../src/app/components/menu/components.tsx | 46 ++- .../components/search-field/components.tsx | 12 +- .../src/app/components/table/components.tsx | 309 ++++++++++-------- .../toggle-button-group/components.tsx | 215 ++++++++---- .../components/toggle-button-group/page.mdx | 94 +++++- .../toggle-button-group/snippets.ts | 152 ++++++--- .../components/toggle-button/components.tsx | 2 +- 8 files changed, 538 insertions(+), 294 deletions(-) diff --git a/docs-ui/src/app/components/card/components.tsx b/docs-ui/src/app/components/card/components.tsx index 89ab09d47d..08fa940be8 100644 --- a/docs-ui/src/app/components/card/components.tsx +++ b/docs-ui/src/app/components/card/components.tsx @@ -10,7 +10,7 @@ import { Text } from '../../../../../packages/ui/src/components/Text/Text'; export const Default = () => { return ( - + Header Body Footer diff --git a/docs-ui/src/app/components/menu/components.tsx b/docs-ui/src/app/components/menu/components.tsx index 1c9aa2598d..d4ddfbf4ba 100644 --- a/docs-ui/src/app/components/menu/components.tsx +++ b/docs-ui/src/app/components/menu/components.tsx @@ -12,17 +12,35 @@ import { } from '../../../../../packages/ui/src/components/Menu/Menu'; import { Button } from '../../../../../packages/ui/src/components/Button/Button'; import { MemoryRouter } from 'react-router-dom'; -import { RiFileLine, RiFolderLine, RiImageLine } from '@remixicon/react'; +import { + RiChat1Line, + RiFileLine, + RiFolderLine, + RiImageLine, + RiSettingsLine, + RiShareBoxLine, +} from '@remixicon/react'; export const Preview = () => ( - + - New File - Open File - Save - Save As... + Edit + Duplicate + Rename + + }>Share + }>Feedback + + + }>Settings + + Edit + Duplicate + Rename + + @@ -53,9 +71,9 @@ export const PreviewIcons = () => ( - }>New File - }>New Folder - }>New Image + }>New File + }>New Folder + }>New Image @@ -110,7 +128,7 @@ export const PreviewSeparators = () => ( export const PreviewAutocompleteMenu = () => ( - + Option 1 Option 2 Option 3 @@ -120,7 +138,7 @@ export const PreviewAutocompleteMenu = () => ( export const PreviewAutocompleteListbox = () => ( - + Option 1 Option 2 Option 3 @@ -130,11 +148,7 @@ export const PreviewAutocompleteListbox = () => ( export const PreviewAutocompleteListboxMultiple = () => ( - + Option 1 Option 2 Option 3 diff --git a/docs-ui/src/app/components/search-field/components.tsx b/docs-ui/src/app/components/search-field/components.tsx index 753f6ecd73..33a6ba3540 100644 --- a/docs-ui/src/app/components/search-field/components.tsx +++ b/docs-ui/src/app/components/search-field/components.tsx @@ -2,10 +2,16 @@ import { SearchField } from '../../../../../packages/ui/src/components/SearchField/SearchField'; import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; -import { RiEBike2Line } from '@remixicon/react'; export const WithLabel = () => { - return ; + return ( + + ); }; export const Sizes = () => { @@ -24,6 +30,7 @@ export const WithDescription = () => { label="Label" description="Description" aria-label="Search" + style={{ width: '100%', maxWidth: '300px' }} /> ); }; @@ -45,7 +52,6 @@ export const StartCollapsed = () => { startCollapsed /> - ); }; diff --git a/docs-ui/src/app/components/table/components.tsx b/docs-ui/src/app/components/table/components.tsx index 8850d3a214..cb158579a1 100644 --- a/docs-ui/src/app/components/table/components.tsx +++ b/docs-ui/src/app/components/table/components.tsx @@ -1,163 +1,188 @@ 'use client'; -import dynamic from 'next/dynamic'; - -// Import Table components dynamically with ssr: false to avoid SSR issues -const TableComponents = dynamic( - () => - import('../../../../../packages/ui/src/components/Table').then(mod => ({ - default: () => null, // Placeholder - Table: mod.Table, - TableHeader: mod.TableHeader, - Column: mod.Column, - TableBody: mod.TableBody, - Row: mod.Row, - Cell: mod.Cell, - })), - { ssr: false }, -); - import { Table, - TableHeader, - Column, - TableBody, - Row, - Cell, + CellProfile, + CellText, + type ColumnConfig, + useTable, } from '../../../../../packages/ui/src/components/Table'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; +import { RadioGroup } from '../../../../../packages/ui/src/components/RadioGroup/RadioGroup'; +import { Radio } from '../../../../../packages/ui/src/components/RadioGroup'; +import { data as data4 } from '../../../../../packages/ui/src/components/Table/stories/mocked-data4'; import { useState } from 'react'; -import type { Selection } from 'react-aria-components'; +import { + selectionData, + selectionColumns, +} from '../../../../../packages/ui/src/components/Table/stories/utils'; +import { MemoryRouter } from 'react-router-dom'; -const rockBands = [ - { id: 1, name: 'The Beatles', genre: 'Rock', year: 1960 }, - { id: 2, name: 'Led Zeppelin', genre: 'Hard Rock', year: 1968 }, - { id: 3, name: 'Pink Floyd', genre: 'Progressive Rock', year: 1965 }, - { id: 4, name: 'The Rolling Stones', genre: 'Rock', year: 1962 }, - { id: 5, name: 'Queen', genre: 'Rock', year: 1970 }, +type Data4Item = (typeof data4)[0]; + +const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Band name', + isRowHeader: true, + cell: item => ( + + ), + }, + { + id: 'genre', + label: 'Genre', + cell: item => , + }, + { + id: 'yearFormed', + label: 'Year formed', + cell: item => , + }, + { + id: 'albums', + label: 'Albums', + cell: item => , + }, ]; -// Simple wrapper to ensure client-side only rendering -const ClientOnly = ({ children }: { children: React.ReactNode }) => { - const [mounted, setMounted] = useState(false); - - useState(() => { - if (typeof window !== 'undefined') { - setMounted(true); - } +export const TableRockBand = () => { + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data4, + paginationOptions: { pageSize: 5 }, }); - if (!mounted) return
Loading...
; - return <>{children}; -}; - -export const TableRockBand = () => ( - - - - Name - Genre - Year - - - {rockBands.map(band => ( - - {band.name} - {band.genre} - {band.year} - - ))} - -
-
-); - -export const SelectionModePlayground = () => { - const [selectedKeys, setSelectedKeys] = useState(new Set()); - return ( - - - - Name - Genre - Year - - - {rockBands.map(band => ( - - {band.name} - {band.genre} - {band.year} - - ))} - -
-
- ); -}; - -export const SelectionBehaviorPlayground = () => { - const [selectedKeys, setSelectedKeys] = useState(new Set()); - - return ( - - - - Name - Genre - Year - - - {rockBands.map(band => ( - - {band.name} - {band.genre} - {band.year} - - ))} - -
-
+ + + ); }; export const SelectionToggleWithActions = () => { - const [selectedKeys, setSelectedKeys] = useState(new Set()); + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); return ( - +
- - Name - Genre - Year - - - {rockBands.map(band => ( - - {band.name} - {band.genre} - {band.year} - - ))} - -
- + {...tableProps} + columnConfig={selectionColumns} + selection={{ + mode: 'multiple', + behavior: 'toggle', + selected, + onSelectionChange: setSelected, + }} + rowConfig={{ onClick: item => alert(`Clicked: ${item.name}`) }} + /> +
+ ); +}; + +export const SelectionModePlayground = () => { + const [selectionMode, setSelectionMode] = useState<'single' | 'multiple'>( + 'multiple', + ); + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( + + + +
+ + Selection mode: + + { + setSelectionMode(value as 'single' | 'multiple'); + setSelected(new Set()); + }} + > + single + multiple + +
+ + + ); +}; + +export const SelectionBehaviorPlayground = () => { + const [selectionBehavior, setSelectionBehavior] = useState< + 'toggle' | 'replace' + >('toggle'); + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => selectionData, + paginationOptions: { pageSize: 10 }, + }); + + return ( + + +
+
+ + Selection behavior: + + { + setSelectionBehavior(value as 'toggle' | 'replace'); + setSelected(new Set()); + }} + > + toggle + replace + +
+ + ); }; diff --git a/docs-ui/src/app/components/toggle-button-group/components.tsx b/docs-ui/src/app/components/toggle-button-group/components.tsx index 0812f5d3a5..a6da51f862 100644 --- a/docs-ui/src/app/components/toggle-button-group/components.tsx +++ b/docs-ui/src/app/components/toggle-button-group/components.tsx @@ -2,78 +2,153 @@ import { ToggleButtonGroup } from '../../../../../packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup'; import { ToggleButton } from '../../../../../packages/ui/src/components/ToggleButton/ToggleButton'; -import { RiBold, RiItalic, RiUnderline } from '@remixicon/react'; -import { useState } from 'react'; +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { Text } from '../../../../../packages/ui/src/components/Text/Text'; +import { + RiCloudLine, + RiStarFill, + RiStarLine, + RiArrowRightSLine, +} from '@remixicon/react'; -export const Default = () => { - const [selected, setSelected] = useState([]); - return ( - setSelected(Array.from(keys as Set))} - > - } /> - } /> - } /> - - ); -}; +export const ToggleButtonGroupSurfaces = () => ( + + + Default + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 0 + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 1 + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 2 + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 3 + + + Option 1 + Option 2 + Option 3 + + + + +); -export const SingleSelection = () => { - const [selected, setSelected] = useState('left'); - return ( - { - const key = Array.from(keys as Set)[0]; - if (key) setSelected(key); - }} - > - Left - Center - Right - - ); -}; +export const ToggleButtonGroupSingle = () => ( + + Dogs + Cats + Birds + +); -export const Sizes = () => { - const [small, setSmall] = useState([]); - const [medium, setMedium] = useState([]); +export const ToggleButtonGroupMultiple = () => ( + + Frontend + Backend + Platform + +); - return ( -
- setSmall(Array.from(keys as Set))} - > - } /> - } /> - } /> - - setMedium(Array.from(keys as Set))} - > - } /> - } /> - } /> - -
- ); -}; +export const ToggleButtonGroupVertical = () => ( + + Morning + Afternoon + Evening + +); -export const Disabled = () => { - return ( - - } /> - } /> - } /> - - ); -}; +export const ToggleButtonGroupDisabled = () => ( + + Cat + Dog + Bird + +); + +export const ToggleButtonGroupDisallowEmpty = () => ( + + One + Two + Three + +); + +export const ToggleButtonGroupIcons = () => ( + + } /> + } + /> + }> + Star + + }> + Next + + +); + +export const ToggleButtonGroupIconsOnly = () => ( + + } /> + } /> + } /> + +); diff --git a/docs-ui/src/app/components/toggle-button-group/page.mdx b/docs-ui/src/app/components/toggle-button-group/page.mdx index 47fb4174a1..4742fff9a5 100644 --- a/docs-ui/src/app/components/toggle-button-group/page.mdx +++ b/docs-ui/src/app/components/toggle-button-group/page.mdx @@ -4,12 +4,25 @@ import { CodeBlock } from '@/components/CodeBlock'; import { toggleButtonGroupPropDefs } from './props-definition'; import { toggleButtonGroupUsageSnippet, - defaultSnippet, - singleSelectionSnippet, - sizesSnippet, - disabledSnippet, + toggleButtonGroupSurfacesSnippet, + toggleButtonGroupSingleSnippet, + toggleButtonGroupMultipleSnippet, + toggleButtonGroupIconsSnippet, + toggleButtonGroupIconsOnlySnippet, + toggleButtonGroupDisallowEmptySnippet, + toggleButtonGroupVerticalSnippet, + toggleButtonGroupDisabledSnippet, } from './snippets'; -import { Default, SingleSelection, Sizes, Disabled } from './components'; +import { + ToggleButtonGroupSingle, + ToggleButtonGroupMultiple, + ToggleButtonGroupVertical, + ToggleButtonGroupDisabled, + ToggleButtonGroupDisallowEmpty, + ToggleButtonGroupIcons, + ToggleButtonGroupIconsOnly, + ToggleButtonGroupSurfaces, +} from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; @@ -20,7 +33,12 @@ import { ToggleButtonGroupDefinition } from '../../../utils/definitions'; description="A grouping container for related [ToggleButtons](./toggle-button) supporting single or multiple selection." /> -} code={defaultSnippet} /> +} + code={toggleButtonGroupUsageSnippet} +/> ## Usage @@ -32,24 +50,74 @@ import { ToggleButtonGroupDefinition } from '../../../utils/definitions'; ## Examples +### Surfaces + +} + code={toggleButtonGroupSurfacesSnippet} +/> + ### Single Selection } - code={singleSelectionSnippet} + preview={} + code={toggleButtonGroupSingleSnippet} /> -### Sizes +### Multiple Selection } - code={sizesSnippet} + preview={} + code={toggleButtonGroupMultipleSnippet} +/> + +### Icons and Text + +} + code={toggleButtonGroupIconsSnippet} +/> + +### Icons Only + +} + code={toggleButtonGroupIconsOnlySnippet} +/> + +### Disallow Empty Selection + +} + code={toggleButtonGroupDisallowEmptySnippet} +/> + +### Vertical Orientation + +} + code={toggleButtonGroupVerticalSnippet} /> ### Disabled @@ -58,8 +126,8 @@ import { ToggleButtonGroupDefinition } from '../../../utils/definitions'; align="center" py={4} open - preview={} - code={disabledSnippet} + preview={} + code={toggleButtonGroupDisabledSnippet} /> diff --git a/docs-ui/src/app/components/toggle-button-group/snippets.ts b/docs-ui/src/app/components/toggle-button-group/snippets.ts index 4cf3eff70e..099891dbae 100644 --- a/docs-ui/src/app/components/toggle-button-group/snippets.ts +++ b/docs-ui/src/app/components/toggle-button-group/snippets.ts @@ -1,59 +1,115 @@ export const toggleButtonGroupUsageSnippet = `import { ToggleButtonGroup, ToggleButton } from '@backstage/ui'; -import { useState } from 'react'; -const [selected, setSelected] = useState([]); - - setSelected(Array.from(keys))} -> - } /> - } /> - } /> + + Dogs + Cats + Birds `; -export const defaultSnippet = `const [selected, setSelected] = useState([]); - - setSelected(Array.from(keys))} -> - } /> - } /> - } /> +export const toggleButtonGroupSingleSnippet = ` + Dogs + Cats + Birds `; -export const singleSelectionSnippet = `const [selected, setSelected] = useState('left'); - - { - const key = Array.from(keys)[0]; - if (key) setSelected(key); - }} -> - Left - Center - Right +export const toggleButtonGroupMultipleSnippet = ` + Frontend + Backend + Platform `; -export const sizesSnippet = ` - } /> - } /> - } /> - - - - } /> - } /> - } /> +export const toggleButtonGroupVerticalSnippet = ` + Morning + Afternoon + Evening `; -export const disabledSnippet = ` - } /> - } /> - } /> +export const toggleButtonGroupDisabledSnippet = ` + Cat + Dog + Bird `; + +export const toggleButtonGroupDisallowEmptySnippet = ` + One + Two + Three +`; + +export const toggleButtonGroupIconsSnippet = `import { RiCloudLine, RiStarFill, RiStarLine, RiArrowRightSLine } from '@remixicon/react'; + + + } /> + } + /> + }> + Star + + }> + Next + +`; + +export const toggleButtonGroupIconsOnlySnippet = `import { RiCloudLine, RiStarLine, RiArrowRightSLine } from '@remixicon/react'; + + + } /> + } /> + } /> +`; + +export const toggleButtonGroupSurfacesSnippet = ` + + Default + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 0 + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 1 + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 2 + + + Option 1 + Option 2 + Option 3 + + + + + On Surface 3 + + + Option 1 + Option 2 + Option 3 + + + +`; diff --git a/docs-ui/src/app/components/toggle-button/components.tsx b/docs-ui/src/app/components/toggle-button/components.tsx index 017af4eee1..6fa5aada98 100644 --- a/docs-ui/src/app/components/toggle-button/components.tsx +++ b/docs-ui/src/app/components/toggle-button/components.tsx @@ -20,7 +20,7 @@ export const WithIcon = () => { } + iconStart={} > With Icon From 04db26b94f53d06e5299c26e38b5ccb22f2a93b0 Mon Sep 17 00:00:00 2001 From: Sanchit2662 Date: Thu, 15 Jan 2026 22:10:25 +0530 Subject: [PATCH 147/202] chore: add changeset for backend listener cleanup Signed-off-by: Sanchit2662 --- .changeset/dull-hounds-return.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dull-hounds-return.md diff --git a/.changeset/dull-hounds-return.md b/.changeset/dull-hounds-return.md new file mode 100644 index 0000000000..9123920984 --- /dev/null +++ b/.changeset/dull-hounds-return.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Clean up process event listeners on backend stop to prevent leaks From fb438cdec3cb3b240ba48c883fefe2f218432c0d Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 15 Jan 2026 17:59:21 +0100 Subject: [PATCH 148/202] Fix prettier Signed-off-by: Charles de Dreuille --- docs-ui/src/app/components/avatar/page.mdx | 15 +----- .../src/app/components/button-icon/page.mdx | 8 +-- .../src/app/components/button-link/page.mdx | 15 +----- docs-ui/src/app/components/card/page.mdx | 7 +-- docs-ui/src/app/components/dialog/page.mdx | 6 ++- docs-ui/src/app/components/grid/page.mdx | 1 - .../src/app/components/header-page/page.mdx | 38 +++++--------- docs-ui/src/app/components/header/page.mdx | 32 ++++-------- docs-ui/src/app/components/link/page.mdx | 8 ++- docs-ui/src/app/components/menu/page.mdx | 35 +++++-------- .../app/components/password-field/page.mdx | 15 +++--- docs-ui/src/app/components/popover/page.mdx | 7 +-- .../src/app/components/radio-group/page.mdx | 9 +++- .../src/app/components/search-field/page.mdx | 22 ++++---- docs-ui/src/app/components/select/page.mdx | 12 ++++- docs-ui/src/app/components/skeleton/page.mdx | 22 ++------ docs-ui/src/app/components/table/page.mdx | 13 ++--- docs-ui/src/app/components/tabs/page.mdx | 27 ++++------ docs-ui/src/app/components/tag-group/page.mdx | 51 +++++-------------- .../src/app/components/text-field/page.mdx | 15 +++--- docs-ui/src/app/components/text/page.mdx | 30 ++--------- .../src/app/components/toggle-button/page.mdx | 8 +-- 22 files changed, 139 insertions(+), 257 deletions(-) diff --git a/docs-ui/src/app/components/avatar/page.mdx b/docs-ui/src/app/components/avatar/page.mdx index 01e647ef7c..0074eda5b8 100644 --- a/docs-ui/src/app/components/avatar/page.mdx +++ b/docs-ui/src/app/components/avatar/page.mdx @@ -36,13 +36,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; Avatar sizes can be set using the `size` prop. -} - code={sizesSnippet} -/> +} code={sizesSnippet} /> ### Fallback @@ -60,12 +54,7 @@ If the image is not available, the avatar will show the initials of the name. Control how the avatar is announced to screen readers using the `purpose` prop. -} - code={purposeSnippet} -/> +} code={purposeSnippet} /> diff --git a/docs-ui/src/app/components/button-icon/page.mdx b/docs-ui/src/app/components/button-icon/page.mdx index 4fa239959b..776b016671 100644 --- a/docs-ui/src/app/components/button-icon/page.mdx +++ b/docs-ui/src/app/components/button-icon/page.mdx @@ -48,13 +48,7 @@ Here's a view when buttons have different variants. Here's a view when buttons have different sizes. -} - code={sizesSnippet} -/> +} code={sizesSnippet} /> ### Disabled diff --git a/docs-ui/src/app/components/button-link/page.mdx b/docs-ui/src/app/components/button-link/page.mdx index cb2b0c19da..2da3177c55 100644 --- a/docs-ui/src/app/components/button-link/page.mdx +++ b/docs-ui/src/app/components/button-link/page.mdx @@ -20,12 +20,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; description="A button component that can be used as a link." /> -} - code={variantsSnippet} -/> +} code={variantsSnippet} /> ## Usage @@ -53,13 +48,7 @@ Here's a view when buttons have different variants. Here's a view when buttons have different sizes. -} - code={sizesSnippet} -/> +} code={sizesSnippet} /> ### With Icons diff --git a/docs-ui/src/app/components/card/page.mdx b/docs-ui/src/app/components/card/page.mdx index 3f50c7c160..e0b6e81fff 100644 --- a/docs-ui/src/app/components/card/page.mdx +++ b/docs-ui/src/app/components/card/page.mdx @@ -24,12 +24,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; description="A card component that can be used to display content in a box." /> -} - code={defaultSnippet} -/> +} code={defaultSnippet} /> ## Usage diff --git a/docs-ui/src/app/components/dialog/page.mdx b/docs-ui/src/app/components/dialog/page.mdx index 0047b216b1..1910cef5a8 100644 --- a/docs-ui/src/app/components/dialog/page.mdx +++ b/docs-ui/src/app/components/dialog/page.mdx @@ -1,7 +1,11 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { Default, PreviewFixedWidthAndHeight, PreviewWithForm } from './components'; +import { + Default, + PreviewFixedWidthAndHeight, + PreviewWithForm, +} from './components'; import { dialogPropDefs, dialogTriggerPropDefs, diff --git a/docs-ui/src/app/components/grid/page.mdx b/docs-ui/src/app/components/grid/page.mdx index 7018310ba3..e75f277226 100644 --- a/docs-ui/src/app/components/grid/page.mdx +++ b/docs-ui/src/app/components/grid/page.mdx @@ -36,7 +36,6 @@ The grid component also accepts all the spacing props from the Box component. - diff --git a/docs-ui/src/app/components/header-page/page.mdx b/docs-ui/src/app/components/header-page/page.mdx index faa01ab1c0..cf17bf46b7 100644 --- a/docs-ui/src/app/components/header-page/page.mdx +++ b/docs-ui/src/app/components/header-page/page.mdx @@ -1,7 +1,13 @@ import { PropsTable } from '@/components/PropsTable'; import { CodeBlock } from '@/components/CodeBlock'; import { Snippet } from '@/components/Snippet'; -import { WithEverything, WithLongBreadcrumbs, WithTabs, WithCustomActions, WithMenuItems } from './components'; +import { + WithEverything, + WithLongBreadcrumbs, + WithTabs, + WithCustomActions, + WithMenuItems, +} from './components'; import { propDefs } from './props-definition'; import { usage, @@ -22,11 +28,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; description="A header page component for the plugin that should sit under the Header component." /> -} - code={defaultSnippet} -/> +} code={defaultSnippet} /> ## Usage @@ -43,44 +45,28 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; You can add breadcrumbs to the header page to help users navigate to the previous page. The `breadcrumbs` prop is an array of objects with a `label` and `href` property. By default we truncate the breadcrumb label to 240px. -} - code={withBreadcrumbs} -/> +} code={withBreadcrumbs} /> ### With Tabs You can add tabs to the header page to help users navigate to the different sections of the page. The `tabs` prop is an array of objects with a `label` and `href` property. -} - code={withTabs} -/> +} code={withTabs} /> ### With Custom Actions You can add custom actions to the header page to help users navigate to the different sections of the page. The `customActions` prop is a React node. -} - code={withCustomActions} -/> +} code={withCustomActions} /> ### With Menu Items You can add menu items to the header page to help users navigate to the different sections of the page. The `menuItems` prop is an array of objects with a `label`, `value` and `onClick` property. -} - code={withMenuItems} -/> +} code={withMenuItems} /> diff --git a/docs-ui/src/app/components/header/page.mdx b/docs-ui/src/app/components/header/page.mdx index ea66cf9703..22343cd6c5 100644 --- a/docs-ui/src/app/components/header/page.mdx +++ b/docs-ui/src/app/components/header/page.mdx @@ -1,7 +1,12 @@ import { PropsTable } from '@/components/PropsTable'; import { CodeBlock } from '@/components/CodeBlock'; import { Snippet } from '@/components/Snippet'; -import { WithAllOptionsAndTabs, WithAllOptions, WithBreadcrumbs, WithHeaderPage } from './components'; +import { + WithAllOptionsAndTabs, + WithAllOptions, + WithBreadcrumbs, + WithHeaderPage, +} from './components'; import { propDefs } from './props-definition'; import { usage, @@ -22,11 +27,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; description="A header component for the plugin." /> -} - code={defaultSnippet} -/> +} code={defaultSnippet} /> ## Usage @@ -42,12 +43,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; A simple example of how to use the Header component. -} - code={simple} - open -/> +} code={simple} open /> ### Header with tabs @@ -65,21 +61,13 @@ open Breacrumbs should appear when you scroll down (and not directly visible as it is in the demo below). -} - code={withBreadcrumbs} - open -/> +} code={withBreadcrumbs} open /> ### Header with HeaderPage You can use the `Header` component inside the [HeaderPage](/components/header-page) component to compose your multi-level navigation. -} - code={withHeaderPage} - open -/> +} code={withHeaderPage} open /> diff --git a/docs-ui/src/app/components/link/page.mdx b/docs-ui/src/app/components/link/page.mdx index b138067a26..fb44c7084f 100644 --- a/docs-ui/src/app/components/link/page.mdx +++ b/docs-ui/src/app/components/link/page.mdx @@ -10,7 +10,13 @@ import { allColorsSnippet, underlineSnippet, } from './snippets'; -import { Default, ExternalLink, AllVariants, AllColors, Underline } from './components'; +import { + Default, + ExternalLink, + AllVariants, + AllColors, + Underline, +} from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; diff --git a/docs-ui/src/app/components/menu/page.mdx b/docs-ui/src/app/components/menu/page.mdx index 7d22397ff2..86f2778e01 100644 --- a/docs-ui/src/app/components/menu/page.mdx +++ b/docs-ui/src/app/components/menu/page.mdx @@ -1,7 +1,17 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { Preview, PreviewSubmenu, PreviewIcons, PreviewLinks, PreviewSections, PreviewSeparators, PreviewAutocompleteMenu, PreviewAutocompleteListbox, PreviewAutocompleteListboxMultiple } from './components'; +import { + Preview, + PreviewSubmenu, + PreviewIcons, + PreviewLinks, + PreviewSections, + PreviewSeparators, + PreviewAutocompleteMenu, + PreviewAutocompleteListbox, + PreviewAutocompleteListboxMultiple, +} from './components'; import { menuTriggerPropDefs, submenuTriggerPropDefs, @@ -36,12 +46,7 @@ import { MenuDefinition } from '../../../utils/definitions'; description="A list of actions in a dropdown, enhanced with keyboard navigation." /> -} - code={preview} -/> +} code={preview} /> ## Usage @@ -152,26 +157,14 @@ the submenu is displayed in the correct position. The best practice is to use th You can use the `iconStart` prop to add an icon to the menu item. -} - code={icons} -/> +} code={icons} /> ### With links You can use the `href` prop to add a link to the menu item. This is using our router provider under the hood to work for both internal and external links. -} - code={links} -/> +} code={links} /> ### With sections diff --git a/docs-ui/src/app/components/password-field/page.mdx b/docs-ui/src/app/components/password-field/page.mdx index 50d52cd939..5cd556e2fb 100644 --- a/docs-ui/src/app/components/password-field/page.mdx +++ b/docs-ui/src/app/components/password-field/page.mdx @@ -19,7 +19,12 @@ import { PasswordFieldDefinition } from '../../../utils/definitions'; description="A password field component for your forms." /> -} code={withLabelSnippet} /> +} + code={withLabelSnippet} +/> ## Usage @@ -35,13 +40,7 @@ import { PasswordFieldDefinition } from '../../../utils/definitions'; We support two different sizes: `small`, `medium`. -} - code={sizesSnippet} -/> +} code={sizesSnippet} /> ### With description diff --git a/docs-ui/src/app/components/popover/page.mdx b/docs-ui/src/app/components/popover/page.mdx index b0d943d673..5a3b0e990c 100644 --- a/docs-ui/src/app/components/popover/page.mdx +++ b/docs-ui/src/app/components/popover/page.mdx @@ -14,12 +14,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; description="A popover displays floating content anchored to a trigger element with automatic positioning and collision detection." /> -} - code={defaultSnippet} -/> +} code={defaultSnippet} /> ## Usage diff --git a/docs-ui/src/app/components/radio-group/page.mdx b/docs-ui/src/app/components/radio-group/page.mdx index a0a1fc36b5..9f76c0be9c 100644 --- a/docs-ui/src/app/components/radio-group/page.mdx +++ b/docs-ui/src/app/components/radio-group/page.mdx @@ -10,7 +10,14 @@ import { validationSnippet, readOnlySnippet, } from './snippets'; -import { Default, Horizontal, Disabled, DisabledSingle, Validation, ReadOnly } from './components'; +import { + Default, + Horizontal, + Disabled, + DisabledSingle, + Validation, + ReadOnly, +} from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; diff --git a/docs-ui/src/app/components/search-field/page.mdx b/docs-ui/src/app/components/search-field/page.mdx index 59b1197e7b..f5b7054361 100644 --- a/docs-ui/src/app/components/search-field/page.mdx +++ b/docs-ui/src/app/components/search-field/page.mdx @@ -8,7 +8,12 @@ import { withDescriptionSnippet, startCollapsedSnippet, } from './snippets'; -import { WithLabel, Sizes, WithDescription, StartCollapsed } from './components'; +import { + WithLabel, + Sizes, + WithDescription, + StartCollapsed, +} from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; @@ -20,7 +25,12 @@ import { SearchFieldDefinition } from '../../../utils/definitions'; description="A SearchField is a text field designed for searches." /> -} code={withLabelSnippet} /> +} + code={withLabelSnippet} +/> ## Usage @@ -36,13 +46,7 @@ import { SearchFieldDefinition } from '../../../utils/definitions'; We support two different sizes: `small`, `medium`. -} - code={sizesSnippet} -/> +} code={sizesSnippet} /> ### With description diff --git a/docs-ui/src/app/components/select/page.mdx b/docs-ui/src/app/components/select/page.mdx index d394fd698e..08e29c4a29 100644 --- a/docs-ui/src/app/components/select/page.mdx +++ b/docs-ui/src/app/components/select/page.mdx @@ -1,7 +1,17 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { Preview, WithLabelAndDescription, Sizes, WithIcon, Disabled, DisabledOption, Searchable, MultipleSelection, SearchableMultiple } from './components'; +import { + Preview, + WithLabelAndDescription, + Sizes, + WithIcon, + Disabled, + DisabledOption, + Searchable, + MultipleSelection, + SearchableMultiple, +} from './components'; import { selectPropDefs } from './props-definition'; import { selectUsageSnippet, diff --git a/docs-ui/src/app/components/skeleton/page.mdx b/docs-ui/src/app/components/skeleton/page.mdx index 21eb35ac5f..42f8975b5a 100644 --- a/docs-ui/src/app/components/skeleton/page.mdx +++ b/docs-ui/src/app/components/skeleton/page.mdx @@ -2,11 +2,7 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; import { skeletonPropDefs } from './props-definition'; -import { - skeletonUsageSnippet, - demo1Snippet, - demo2Snippet, -} from './snippets'; +import { skeletonUsageSnippet, demo1Snippet, demo2Snippet } from './snippets'; import { Demo1, Demo2 } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; @@ -34,25 +30,13 @@ import { SkeletonDefinition } from '../../../utils/definitions'; You can use a mix of different sizes to create a more complex skeleton. -} - code={demo1Snippet} - open -/> +} code={demo1Snippet} open /> ### Demo 2 You can use a mix of different sizes to create a more complex skeleton. -} - code={demo2Snippet} - open -/> +} code={demo2Snippet} open /> diff --git a/docs-ui/src/app/components/table/page.mdx b/docs-ui/src/app/components/table/page.mdx index d3461124d2..f9b27cd8d3 100644 --- a/docs-ui/src/app/components/table/page.mdx +++ b/docs-ui/src/app/components/table/page.mdx @@ -1,7 +1,12 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { TableRockBand, SelectionModePlayground, SelectionBehaviorPlayground, SelectionToggleWithActions } from './components'; +import { + TableRockBand, + SelectionModePlayground, + SelectionBehaviorPlayground, + SelectionToggleWithActions, +} from './components'; import { tablePropDefs, tableHeaderPropDefs, @@ -28,11 +33,7 @@ import { TableDefinition } from '../../../utils/definitions'; description="A flexible table component built on top of TanStack Table with built-in styling, interactions, and pagination support." /> -} - code={tableBasicSnippet} -/> +} code={tableBasicSnippet} /> ## Usage diff --git a/docs-ui/src/app/components/tabs/page.mdx b/docs-ui/src/app/components/tabs/page.mdx index 0c51b96471..0b9478a503 100644 --- a/docs-ui/src/app/components/tabs/page.mdx +++ b/docs-ui/src/app/components/tabs/page.mdx @@ -14,7 +14,12 @@ import { disabledTabsSnippet, orientationSnippet, } from './snippets'; -import { Default, DefaultSelectedKey, DisabledTabs, Orientation } from './components'; +import { + Default, + DefaultSelectedKey, + DisabledTabs, + Orientation, +} from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { TabsDefinition } from '../../../utils/definitions'; @@ -25,11 +30,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; description="A component for toggling between related panels on the same page." /> -} - code={defaultSnippet} -/> +} code={defaultSnippet} /> ## Usage @@ -66,23 +67,13 @@ Here's how to set which tab is selected by default. Here's how to disable specific tabs. -} - code={disabledTabsSnippet} - open -/> +} code={disabledTabsSnippet} open /> ### Orientation Here's how to display tabs vertically. -} - code={orientationSnippet} - open -/> +} code={orientationSnippet} open /> diff --git a/docs-ui/src/app/components/tag-group/page.mdx b/docs-ui/src/app/components/tag-group/page.mdx index 098179f623..2c6226284f 100644 --- a/docs-ui/src/app/components/tag-group/page.mdx +++ b/docs-ui/src/app/components/tag-group/page.mdx @@ -1,11 +1,15 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { Default, WithLink, WithIcon, Sizes, RemovingTags, Disabled } from './components'; import { - tagGroupPropDefs, - tagPropDefs, -} from './props-definition'; + Default, + WithLink, + WithIcon, + Sizes, + RemovingTags, + Disabled, +} from './components'; +import { tagGroupPropDefs, tagPropDefs } from './props-definition'; import { usage, preview, @@ -25,12 +29,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; description="A tag group is a list of labels, categories, keywords, filters, or other items, that can be used to group and filter content." /> -} - code={preview} -/> +} code={preview} /> ## Usage @@ -56,37 +55,19 @@ A tag is a single item in a tag group. A tag can be a link by passing a `href` prop. -} - code={withLink} -/> +} code={withLink} /> ### With Icons A tag can have an icon by passing a `icon` prop. -} - code={withIcons} -/> +} code={withIcons} /> ### Sizes A tag can have a size by passing a `size` prop. It could be `small` or `medium`. -} - code={sizes} -/> +} code={sizes} /> ### Removing tags @@ -104,13 +85,7 @@ A tag can be removed by passing a `onRemove` prop. A switch can be disabled using the `isDisabled` prop. -} - code={disabled} -/> +} code={disabled} /> diff --git a/docs-ui/src/app/components/text-field/page.mdx b/docs-ui/src/app/components/text-field/page.mdx index 7824de6842..0fd19d35f3 100644 --- a/docs-ui/src/app/components/text-field/page.mdx +++ b/docs-ui/src/app/components/text-field/page.mdx @@ -19,7 +19,12 @@ import { CodeBlock } from '@/components/CodeBlock'; description="A text field component for your forms." /> -} code={withLabelSnippet} /> +} + code={withLabelSnippet} +/> ## Usage @@ -35,13 +40,7 @@ import { CodeBlock } from '@/components/CodeBlock'; We support two different sizes: `small`, `medium`. -} - code={sizesSnippet} -/> +} code={sizesSnippet} /> ### With description diff --git a/docs-ui/src/app/components/text/page.mdx b/docs-ui/src/app/components/text/page.mdx index aff78e17ef..0bf07f73e7 100644 --- a/docs-ui/src/app/components/text/page.mdx +++ b/docs-ui/src/app/components/text/page.mdx @@ -21,11 +21,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; description="The `Text` component is used to display content on your page." /> -} - code={defaultSnippet} -/> +} code={defaultSnippet} /> ## Usage @@ -42,43 +38,27 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; The `Text` component has a `variant` prop that can be used to change the appearance of the text. -} - code={variantsSnippet} -/> +} code={variantsSnippet} /> ### All weights The `Text` component has a `weight` prop that can be used to change the appearance of the text. -} - code={weightsSnippet} -/> +} code={weightsSnippet} /> ### All colors The `Text` component has a `color` prop that can be used to change the appearance of the text. -} - code={colorsSnippet} -/> +} code={colorsSnippet} /> ### Truncate The `Text` component has a `truncate` prop that can be used to truncate the text. -} - code={truncateSnippet} -/> +} code={truncateSnippet} /> diff --git a/docs-ui/src/app/components/toggle-button/page.mdx b/docs-ui/src/app/components/toggle-button/page.mdx index d2707fe84e..776144d089 100644 --- a/docs-ui/src/app/components/toggle-button/page.mdx +++ b/docs-ui/src/app/components/toggle-button/page.mdx @@ -49,13 +49,7 @@ import { ToggleButtonDefinition } from '../../../utils/definitions'; ### Sizes -} - code={sizesSnippet} -/> +} code={sizesSnippet} /> ### Disabled From 32f92281ffef2c5c428a2c543e110763996b2215 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Thu, 15 Jan 2026 19:32:22 +0100 Subject: [PATCH 149/202] change to ownedKinds, cleanup test file a bit Signed-off-by: Peter Macdonald --- packages/app-next/app-config.yaml | 2 +- plugins/org/src/alpha.tsx | 9 ++------- .../OwnershipCard/OwnershipCard.test.tsx | 20 ------------------- 3 files changed, 3 insertions(+), 28 deletions(-) diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index d297c9caca..97e8446fa4 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -32,7 +32,7 @@ app: initialRelationAggregation: aggregated - entity-card:org/ownership: config: - entityFilterKind: ['Component', 'API', 'System'] + ownedKinds: ['Component', 'API', 'System'] # - apis.plugin.graphiql.browse.gitlab: true # - graphiql-endpoint:graphiql/gitlab: true diff --git a/plugins/org/src/alpha.tsx b/plugins/org/src/alpha.tsx index 729ebb063f..4e11463781 100644 --- a/plugins/org/src/alpha.tsx +++ b/plugins/org/src/alpha.tsx @@ -65,7 +65,7 @@ const EntityOwnershipCard = EntityCardBlueprint.makeWithOverrides({ initialRelationAggregation: z => z.enum(['direct', 'aggregated']).optional(), showAggregateMembersToggle: z => z.boolean().optional(), - entityFilterKind: z => z.array(z.string()).optional(), + ownedKinds: z => z.array(z.string()).optional(), }, }, factory(originalFactory, { config }) { @@ -81,12 +81,7 @@ const EntityOwnershipCard = EntityCardBlueprint.makeWithOverrides({ : !config.showAggregateMembersToggle } entityFilterKind={ - config.entityFilterKind ?? [ - 'Component', - 'API', - 'System', - 'Resource', - ] + config.ownedKinds ?? ['Component', 'API', 'System', 'Resource'] } /> )), diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index 57ca206cc8..5bb058e1b2 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -36,11 +36,6 @@ const items = [ { type: 'ownedBy', targetRef: 'group:default/my-team', - target: { - name: 'my-team', - namespace: 'default', - kind: 'group', - }, }, ], }, @@ -56,11 +51,6 @@ const items = [ { type: 'ownedBy', targetRef: 'group:default/my-team', - target: { - name: 'my-team', - namespace: 'default', - kind: 'group', - }, }, ], }, @@ -76,11 +66,6 @@ const items = [ { type: 'ownedBy', targetRef: 'group:default/my-team', - target: { - name: 'my-team', - namespace: 'default', - kind: 'group', - }, }, ], }, @@ -97,11 +82,6 @@ const items = [ { type: 'ownedBy', targetRef: 'group:default/my-team', - target: { - name: 'my-team', - namespace: 'default', - kind: 'group', - }, }, ], }, From 89345e4be3bf88e0e6fc6e9e9dd68ed6b3b98e41 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Thu, 15 Jan 2026 19:38:34 +0100 Subject: [PATCH 150/202] regenerate api report Signed-off-by: Peter Macdonald --- plugins/org/report-alpha.api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/org/report-alpha.api.md b/plugins/org/report-alpha.api.md index 6077bb4806..cfd2290502 100644 --- a/plugins/org/report-alpha.api.md +++ b/plugins/org/report-alpha.api.md @@ -110,14 +110,14 @@ const _default: OverridableFrontendPlugin< config: { initialRelationAggregation: 'direct' | 'aggregated' | undefined; showAggregateMembersToggle: boolean | undefined; - entityFilterKind: string[] | undefined; + ownedKinds: string[] | undefined; filter: EntityPredicate | undefined; type: 'content' | 'summary' | 'info' | undefined; }; configInput: { showAggregateMembersToggle?: boolean | undefined; - entityFilterKind?: string[] | undefined; initialRelationAggregation?: 'direct' | 'aggregated' | undefined; + ownedKinds?: string[] | undefined; filter?: EntityPredicate | undefined; type?: 'content' | 'summary' | 'info' | undefined; }; From 5e5f78275947f16e9f2a6d135b8512a19b7c8924 Mon Sep 17 00:00:00 2001 From: mtt-artis Date: Thu, 15 Jan 2026 20:11:46 +0100 Subject: [PATCH 151/202] fix(scaffolder-backend): remove unused abort Signed-off-by: mtt-artis --- .changeset/cuddly-colts-dress.md | 2 +- .../src/scaffolder/actions/builtin/debug/wait.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.changeset/cuddly-colts-dress.md b/.changeset/cuddly-colts-dress.md index f7e24d1c54..5e541ebccb 100644 --- a/.changeset/cuddly-colts-dress.md +++ b/.changeset/cuddly-colts-dress.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend': patch --- -Simplify abort handling in debug:wait action +Remove unused abort controller in debug:wait action diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts index 321a5f46a8..73d81e50bf 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.ts @@ -84,13 +84,14 @@ export function createWaitAction(options?: { ); } - await new Promise(resolve => { + await new Promise(resolve => { const timeoutHandle = setTimeout(abort, delayTime.toMillis()); - ctx.signal?.addEventListener('abort', abort, { once: true }); + ctx.signal?.addEventListener('abort', abort); function abort() { + ctx.signal?.removeEventListener('abort', abort); clearTimeout(timeoutHandle); - resolve('finished'); + resolve(); } }); }, From ee4308082c54848163db7d859445e138ed08cf24 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 15 Jan 2026 21:41:00 +0100 Subject: [PATCH 152/202] Update .changeset/update-azure-scaffolder-docs.md Signed-off-by: Vincenzo Scamporlino --- .changeset/update-azure-scaffolder-docs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/update-azure-scaffolder-docs.md b/.changeset/update-azure-scaffolder-docs.md index 1dedda3611..d3c81f35b3 100644 --- a/.changeset/update-azure-scaffolder-docs.md +++ b/.changeset/update-azure-scaffolder-docs.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend-module-azure': patch --- -Improve Azure scaffolder backend module README with clearer setup and usage guidance. +Improved README with clearer setup and usage guidance. From 532574d802524c556c793286673c133efd7a49c7 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 15 Jan 2026 23:19:21 +0100 Subject: [PATCH 153/202] Remove Storybook Signed-off-by: Charles de Dreuille --- docs-ui/package.json | 4 +- docs-ui/src/components/Roadmap/Roadmap.tsx | 37 -- docs-ui/src/components/Roadmap/index.ts | 1 - docs-ui/src/components/Roadmap/list.ts | 47 -- docs-ui/src/components/Roadmap/styles.css | 100 ---- docs-ui/yarn.lock | 576 +-------------------- 6 files changed, 7 insertions(+), 758 deletions(-) delete mode 100644 docs-ui/src/components/Roadmap/Roadmap.tsx delete mode 100644 docs-ui/src/components/Roadmap/index.ts delete mode 100644 docs-ui/src/components/Roadmap/list.ts delete mode 100644 docs-ui/src/components/Roadmap/styles.css diff --git a/docs-ui/package.json b/docs-ui/package.json index 2f17987107..ce9b8d070f 100644 --- a/docs-ui/package.json +++ b/docs-ui/package.json @@ -26,7 +26,6 @@ "@mdx-js/react": "^3.1.0", "@next/mdx": "15.5.7", "@remixicon/react": "^4.6.0", - "@storybook/react": "^8.6.12", "@uiw/codemirror-themes": "^4.23.7", "@uiw/react-codemirror": "^4.23.7", "clsx": "^2.1.1", @@ -37,8 +36,7 @@ "prop-types": "^15.8.1", "react": "19.1.1", "react-dom": "19.1.1", - "shiki": "^3.13.0", - "storybook": "^8.6.15" + "shiki": "^3.13.0" }, "devDependencies": { "@octokit/rest": "^22.0.1", diff --git a/docs-ui/src/components/Roadmap/Roadmap.tsx b/docs-ui/src/components/Roadmap/Roadmap.tsx deleted file mode 100644 index 52f96e0f76..0000000000 --- a/docs-ui/src/components/Roadmap/Roadmap.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { RoadmapItem } from './list'; - -export const Roadmap = ({ list }: { list: RoadmapItem[] }) => { - const orderList = ['inProgress', 'notStarted', 'completed']; - return ( -
- {list - .sort( - (a, b) => orderList.indexOf(a.status) - orderList.indexOf(b.status), - ) - .map(Item)} -
- ); -}; - -const Item = ({ - title, - status = 'notStarted', -}: { - title: string; - status: 'notStarted' | 'inProgress' | 'inReview' | 'completed'; -}) => { - return ( -
-
-
-
{title}
-
- - {status === 'notStarted' && 'Not Started'} - {status === 'inProgress' && 'In Progress'} - {status === 'inReview' && 'Ready for Review'} - {status === 'completed' && 'Completed'} - -
- ); -}; diff --git a/docs-ui/src/components/Roadmap/index.ts b/docs-ui/src/components/Roadmap/index.ts deleted file mode 100644 index c7006073d6..0000000000 --- a/docs-ui/src/components/Roadmap/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { Roadmap } from './Roadmap'; diff --git a/docs-ui/src/components/Roadmap/list.ts b/docs-ui/src/components/Roadmap/list.ts deleted file mode 100644 index 24b9197cf5..0000000000 --- a/docs-ui/src/components/Roadmap/list.ts +++ /dev/null @@ -1,47 +0,0 @@ -export type RoadmapItem = { - title: string; - status: 'notStarted' | 'inProgress' | 'inReview' | 'completed'; -}; - -export const list: RoadmapItem[] = [ - { - title: 'Remove Vanilla Extract and use pure CSS instead', - status: 'inProgress', - }, - { - title: 'Add collapsing across breakpoints for the Inline component', - status: 'notStarted', - }, - { - title: 'Add reversing the order for the Inline component', - status: 'notStarted', - }, - { - title: 'Set up Storybook', - status: 'completed', - }, - { - title: 'Set up iconography', - status: 'completed', - }, - { - title: 'Set up global tokens', - status: 'inProgress', - }, - { - title: 'Set up theming system', - status: 'inProgress', - }, - { - title: 'Create first pass at box component', - status: 'completed', - }, - { - title: 'Create first pass at stack component', - status: 'completed', - }, - { - title: 'Create first pass at inline component', - status: 'completed', - }, -]; diff --git a/docs-ui/src/components/Roadmap/styles.css b/docs-ui/src/components/Roadmap/styles.css deleted file mode 100644 index 63f32312e7..0000000000 --- a/docs-ui/src/components/Roadmap/styles.css +++ /dev/null @@ -1,100 +0,0 @@ -.roadmap { - display: flex; - flex-direction: column; -} - -.roadmap .roadmap-item { - display: flex; - align-items: center; - justify-content: space-between; - border-bottom: 1px solid #e0e0e0; - padding: 8px 0px; -} - -.roadmap .roadmap-item .left { - display: flex; - align-items: center; - padding-left: 12px; - gap: 12px; -} - -.roadmap .roadmap-item .dot { - width: 8px; - height: 8px; - border-radius: 50%; - background-color: #e0e0e0; -} - -.roadmap .roadmap-item.notStarted { - color: #000; -} - -.roadmap .roadmap-item.inProgress { - color: #000; -} - -.roadmap .roadmap-item.inReview { - color: #000; -} - -.roadmap .roadmap-item.completed .title { - color: #a2a2a2; - text-decoration: line-through; -} - -.roadmap .roadmap-item.notStarted .dot { - background-color: #d1d1d1; -} - -.roadmap .roadmap-item.inProgress .dot { - background-color: #ffd000; -} - -.roadmap .roadmap-item.inReview .dot { - background-color: #4ed14a; -} - -.roadmap .roadmap-item.completed .dot { - background-color: #4ed14a; -} - -.roadmap .roadmap-item .title { - font-size: 16px; -} - -.roadmap .roadmap-item .pill { - display: inline-flex; - align-items: center; - height: 24px; - padding: 0px 8px; - border-radius: 40px; - font-size: 12px; - font-weight: 600; - margin-left: 8px; - border-style: solid; - border-width: 1px; -} - -.roadmap .roadmap-item.notStarted .pill { - background-color: #f2f2f2; - border-color: #cdcdcd; - color: #888888; -} - -.roadmap .roadmap-item.inProgress .pill { - background-color: #fff2b9; - border-color: #ffd000; - color: #d79927; -} - -.roadmap .roadmap-item.inReview .pill { - background-color: #d7f9d7; - border-color: #4ed14a; - color: #3a9837; -} - -.roadmap .roadmap-item.completed .pill { - background-color: #d7f9d7; - border-color: #4ed14a; - color: #3a9837; -} diff --git a/docs-ui/yarn.lock b/docs-ui/yarn.lock index 19ce3640f5..c9b872f91e 100644 --- a/docs-ui/yarn.lock +++ b/docs-ui/yarn.lock @@ -218,188 +218,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/aix-ppc64@npm:0.25.9" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/android-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/android-arm64@npm:0.25.9" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/android-arm@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/android-arm@npm:0.25.9" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@esbuild/android-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/android-x64@npm:0.25.9" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/darwin-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/darwin-arm64@npm:0.25.9" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/darwin-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/darwin-x64@npm:0.25.9" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/freebsd-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/freebsd-arm64@npm:0.25.9" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/freebsd-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/freebsd-x64@npm:0.25.9" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/linux-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-arm64@npm:0.25.9" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/linux-arm@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-arm@npm:0.25.9" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@esbuild/linux-ia32@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-ia32@npm:0.25.9" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/linux-loong64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-loong64@npm:0.25.9" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - -"@esbuild/linux-mips64el@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-mips64el@npm:0.25.9" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"@esbuild/linux-ppc64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-ppc64@npm:0.25.9" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/linux-riscv64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-riscv64@npm:0.25.9" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - -"@esbuild/linux-s390x@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-s390x@npm:0.25.9" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - -"@esbuild/linux-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/linux-x64@npm:0.25.9" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/netbsd-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/netbsd-arm64@npm:0.25.9" - conditions: os=netbsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/netbsd-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/netbsd-x64@npm:0.25.9" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openbsd-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/openbsd-arm64@npm:0.25.9" - conditions: os=openbsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/openbsd-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/openbsd-x64@npm:0.25.9" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openharmony-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/openharmony-arm64@npm:0.25.9" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/sunos-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/sunos-x64@npm:0.25.9" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/win32-arm64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/win32-arm64@npm:0.25.9" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/win32-ia32@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/win32-ia32@npm:0.25.9" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/win32-x64@npm:0.25.9": - version: 0.25.9 - resolution: "@esbuild/win32-x64@npm:0.25.9" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.7.0": version: 4.7.0 resolution: "@eslint-community/eslint-utils@npm:4.7.0" @@ -1231,118 +1049,6 @@ __metadata: languageName: node linkType: hard -"@storybook/components@npm:8.6.14": - version: 8.6.14 - resolution: "@storybook/components@npm:8.6.14" - peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/d3647505510313aa3c32fd1f8f202eda723ad99bd353b20aa929c5ecab4edc3c86ad06d0eac02f3c6d948e88f13f65ca5fa35ef27c079ef1b92abe8692f23699 - languageName: node - linkType: hard - -"@storybook/core@npm:8.6.15": - version: 8.6.15 - resolution: "@storybook/core@npm:8.6.15" - dependencies: - "@storybook/theming": "npm:8.6.15" - better-opn: "npm:^3.0.2" - browser-assert: "npm:^1.2.1" - esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0" - esbuild-register: "npm:^3.5.0" - jsdoc-type-pratt-parser: "npm:^4.0.0" - process: "npm:^0.11.10" - recast: "npm:^0.23.5" - semver: "npm:^7.6.2" - util: "npm:^0.12.5" - ws: "npm:^8.2.3" - peerDependencies: - prettier: ^2 || ^3 - peerDependenciesMeta: - prettier: - optional: true - checksum: 10/d268d6fa00c38b35e5c363ee33779c2e087ab8e4681e0e205baa2fdb2780ea9feda3c9f6db35d60092778878d2782b2093c744bdf1af173c5688c3e1e0e960ac - languageName: node - linkType: hard - -"@storybook/global@npm:^5.0.0": - version: 5.0.0 - resolution: "@storybook/global@npm:5.0.0" - checksum: 10/0e7b495f4fe7f36447e793926f1c0460ec07fd66f0da68e3150da5878f6043c9eeb9b41614a45c5ec0d48d5d383c59ca8f88b6dc7882a2a784ac9b20375d8edb - languageName: node - linkType: hard - -"@storybook/manager-api@npm:8.6.14": - version: 8.6.14 - resolution: "@storybook/manager-api@npm:8.6.14" - peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/4544b317050b81574f1cd6f911dcb99ee2adbd7190555209171a7dd2233cd331165fa11c16a5977ebe58eeaf26c86bbfcb23f701cfd79a10f0d034dae65197bd - languageName: node - linkType: hard - -"@storybook/preview-api@npm:8.6.14": - version: 8.6.14 - resolution: "@storybook/preview-api@npm:8.6.14" - peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/9b77288f2f627a7c70cfd3e88bdc4348c1425fab6b333ed77efb913e45881bcdf2ca67f1174e47fe978a7993c2a653fb8accf518ba51441cfb9450145101e9d8 - languageName: node - linkType: hard - -"@storybook/react-dom-shim@npm:8.6.14": - version: 8.6.14 - resolution: "@storybook/react-dom-shim@npm:8.6.14" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.14 - checksum: 10/d14d970b98dc8266d4df80225538fb537449c15877f0f54465dfe52242f0c3f041c975824cc2894f72a3a8023fdffa0a281d90325b95297858e3684913b3cc15 - languageName: node - linkType: hard - -"@storybook/react@npm:^8.6.12": - version: 8.6.14 - resolution: "@storybook/react@npm:8.6.14" - dependencies: - "@storybook/components": "npm:8.6.14" - "@storybook/global": "npm:^5.0.0" - "@storybook/manager-api": "npm:8.6.14" - "@storybook/preview-api": "npm:8.6.14" - "@storybook/react-dom-shim": "npm:8.6.14" - "@storybook/theming": "npm:8.6.14" - peerDependencies: - "@storybook/test": 8.6.14 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.14 - typescript: ">= 4.2.x" - peerDependenciesMeta: - "@storybook/test": - optional: true - typescript: - optional: true - checksum: 10/a8710dcb80da9df4a78cfb3560a8c0c31c6f6c04902aa95446d4e40f6a9179770ead6f9a0a06fa44d00677cd1cd92eb60fdc57f433035ec722275847139671a4 - languageName: node - linkType: hard - -"@storybook/theming@npm:8.6.14": - version: 8.6.14 - resolution: "@storybook/theming@npm:8.6.14" - peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/6936ea3348968fe598ad47421c11a78c6ee2ce62336ea1ce9cb8257e9faa2553d3ac3e443f8a36d35a41b0d60eb169231516649c710582ec68fdead4f23ffc0e - languageName: node - linkType: hard - -"@storybook/theming@npm:8.6.15": - version: 8.6.15 - resolution: "@storybook/theming@npm:8.6.15" - peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/f02760831a13d7af9dbfeb6feea949f4c13c897861cbc75253a6776d133891567889c8d99c7e91a99124d0772c3bde1f978984c83b19117d1d7c908ed7eb8409 - languageName: node - linkType: hard - "@swc/helpers@npm:0.5.15": version: 0.5.15 resolution: "@swc/helpers@npm:0.5.15" @@ -2017,15 +1723,6 @@ __metadata: languageName: node linkType: hard -"ast-types@npm:^0.16.1": - version: 0.16.1 - resolution: "ast-types@npm:0.16.1" - dependencies: - tslib: "npm:^2.0.1" - checksum: 10/f569b475eb1c8cb93888cb6e7b7e36dc43fa19a77e4eb132cbff6e3eb1598ca60f850db6e60b070e5a0ee8c1559fca921dac0916e576f2f104e198793b0bdd8d - languageName: node - linkType: hard - "astring@npm:^1.8.0": version: 1.9.0 resolution: "astring@npm:1.9.0" @@ -2086,15 +1783,6 @@ __metadata: languageName: node linkType: hard -"better-opn@npm:^3.0.2": - version: 3.0.2 - resolution: "better-opn@npm:3.0.2" - dependencies: - open: "npm:^8.0.4" - checksum: 10/24668e5a837d0d2c0edf17ad5ebcfeb00a8a5578a5eb09f7a409e1a60617cdfea40b8ebfc95e5f12d9568157930d033e6805788fcf0780413ac982c95d3745d1 - languageName: node - linkType: hard - "binary-extensions@npm:^2.0.0": version: 2.3.0 resolution: "binary-extensions@npm:2.3.0" @@ -2130,13 +1818,6 @@ __metadata: languageName: node linkType: hard -"browser-assert@npm:^1.2.1": - version: 1.2.1 - resolution: "browser-assert@npm:1.2.1" - checksum: 10/8b2407cd04c1ed592cf892dec35942b7d72635829221e0788c9a16c4d2afa8b7156bc9705b1c4b32c30d88136c576fda3cbcb8f494d6f865264c706ea8798d92 - languageName: node - linkType: hard - "cacache@npm:^19.0.1": version: 19.0.1 resolution: "cacache@npm:19.0.1" @@ -2473,13 +2154,6 @@ __metadata: languageName: node linkType: hard -"define-lazy-prop@npm:^2.0.0": - version: 2.0.0 - resolution: "define-lazy-prop@npm:2.0.0" - checksum: 10/0115fdb065e0490918ba271d7339c42453d209d4cb619dfe635870d906731eff3e1ade8028bb461ea27ce8264ec5e22c6980612d332895977e89c1bbc80fcee2 - languageName: node - linkType: hard - "define-properties@npm:^1.1.3, define-properties@npm:^1.2.1": version: 1.2.1 resolution: "define-properties@npm:1.2.1" @@ -2528,7 +2202,6 @@ __metadata: "@octokit/rest": "npm:^22.0.1" "@remixicon/react": "npm:^4.6.0" "@shikijs/transformers": "npm:^3.13.0" - "@storybook/react": "npm:^8.6.12" "@types/mdx": "npm:^2.0.13" "@types/node": "npm:^22.13.14" "@types/react": "npm:19.1.9" @@ -2548,7 +2221,6 @@ __metadata: react: "npm:19.1.1" react-dom: "npm:19.1.1" shiki: "npm:^3.13.0" - storybook: "npm:^8.6.15" typescript: "npm:^5" unified: "npm:^11.0.4" languageName: unknown @@ -2844,106 +2516,6 @@ __metadata: languageName: node linkType: hard -"esbuild-register@npm:^3.5.0": - version: 3.6.0 - resolution: "esbuild-register@npm:3.6.0" - dependencies: - debug: "npm:^4.3.4" - peerDependencies: - esbuild: ">=0.12 <1" - checksum: 10/4ae1a016e3dad5b53c3d68cf07e31d8c1cec1a0b584038ece726097ac80bd33ab48fb224c766c9b341c04793837e652461eaca9327a116e7564f553b61ccca71 - languageName: node - linkType: hard - -"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0": - version: 0.25.9 - resolution: "esbuild@npm:0.25.9" - dependencies: - "@esbuild/aix-ppc64": "npm:0.25.9" - "@esbuild/android-arm": "npm:0.25.9" - "@esbuild/android-arm64": "npm:0.25.9" - "@esbuild/android-x64": "npm:0.25.9" - "@esbuild/darwin-arm64": "npm:0.25.9" - "@esbuild/darwin-x64": "npm:0.25.9" - "@esbuild/freebsd-arm64": "npm:0.25.9" - "@esbuild/freebsd-x64": "npm:0.25.9" - "@esbuild/linux-arm": "npm:0.25.9" - "@esbuild/linux-arm64": "npm:0.25.9" - "@esbuild/linux-ia32": "npm:0.25.9" - "@esbuild/linux-loong64": "npm:0.25.9" - "@esbuild/linux-mips64el": "npm:0.25.9" - "@esbuild/linux-ppc64": "npm:0.25.9" - "@esbuild/linux-riscv64": "npm:0.25.9" - "@esbuild/linux-s390x": "npm:0.25.9" - "@esbuild/linux-x64": "npm:0.25.9" - "@esbuild/netbsd-arm64": "npm:0.25.9" - "@esbuild/netbsd-x64": "npm:0.25.9" - "@esbuild/openbsd-arm64": "npm:0.25.9" - "@esbuild/openbsd-x64": "npm:0.25.9" - "@esbuild/openharmony-arm64": "npm:0.25.9" - "@esbuild/sunos-x64": "npm:0.25.9" - "@esbuild/win32-arm64": "npm:0.25.9" - "@esbuild/win32-ia32": "npm:0.25.9" - "@esbuild/win32-x64": "npm:0.25.9" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/openharmony-arm64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10/fc174ae7f646ad413adb641c7e46f16be575e462ed209866b55d5954d382e5da839e3f3f89a8e42e2b71d48895cc636ba43523011249fe5ff9c63d8d39d3a364 - languageName: node - linkType: hard - "escape-string-regexp@npm:^4.0.0": version: 4.0.0 resolution: "escape-string-regexp@npm:4.0.0" @@ -3196,16 +2768,6 @@ __metadata: languageName: node linkType: hard -"esprima@npm:~4.0.0": - version: 4.0.1 - resolution: "esprima@npm:4.0.1" - bin: - esparse: ./bin/esparse.js - esvalidate: ./bin/esvalidate.js - checksum: 10/f1d3c622ad992421362294f7acf866aa9409fbad4eb2e8fa230bd33944ce371d32279667b242d8b8907ec2b6ad7353a717f3c0e60e748873a34a7905174bc0eb - languageName: node - linkType: hard - "esquery@npm:^1.4.2": version: 1.6.0 resolution: "esquery@npm:1.6.0" @@ -3937,7 +3499,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.3": +"inherits@npm:2": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 @@ -3986,16 +3548,6 @@ __metadata: languageName: node linkType: hard -"is-arguments@npm:^1.0.4": - version: 1.2.0 - resolution: "is-arguments@npm:1.2.0" - dependencies: - call-bound: "npm:^1.0.2" - has-tostringtag: "npm:^1.0.2" - checksum: 10/471a8ef631b8ee8829c43a8ab05c081700c0e25180c73d19f3bf819c1a8448c426a9e8e601f278973eca68966384b16ceb78b8c63af795b099cd199ea5afc457 - languageName: node - linkType: hard - "is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5": version: 3.0.5 resolution: "is-array-buffer@npm:3.0.5" @@ -4108,15 +3660,6 @@ __metadata: languageName: node linkType: hard -"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": - version: 2.2.1 - resolution: "is-docker@npm:2.2.1" - bin: - is-docker: cli.js - checksum: 10/3fef7ddbf0be25958e8991ad941901bf5922ab2753c46980b60b05c1bf9c9c2402d35e6dc32e4380b980ef5e1970a5d9d5e5aa2e02d77727c3b6b5e918474c56 - languageName: node - linkType: hard - "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -4140,7 +3683,7 @@ __metadata: languageName: node linkType: hard -"is-generator-function@npm:^1.0.10, is-generator-function@npm:^1.0.7": +"is-generator-function@npm:^1.0.10": version: 1.1.0 resolution: "is-generator-function@npm:1.1.0" dependencies: @@ -4262,7 +3805,7 @@ __metadata: languageName: node linkType: hard -"is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.15, is-typed-array@npm:^1.1.3": +"is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.15": version: 1.1.15 resolution: "is-typed-array@npm:1.1.15" dependencies: @@ -4297,15 +3840,6 @@ __metadata: languageName: node linkType: hard -"is-wsl@npm:^2.2.0": - version: 2.2.0 - resolution: "is-wsl@npm:2.2.0" - dependencies: - is-docker: "npm:^2.0.0" - checksum: 10/20849846ae414997d290b75e16868e5261e86ff5047f104027026fd61d8b5a9b0b3ade16239f35e1a067b3c7cc02f70183cb661010ed16f4b6c7c93dad1b19d8 - languageName: node - linkType: hard - "isarray@npm:^2.0.5": version: 2.0.5 resolution: "isarray@npm:2.0.5" @@ -4372,13 +3906,6 @@ __metadata: languageName: node linkType: hard -"jsdoc-type-pratt-parser@npm:^4.0.0": - version: 4.8.0 - resolution: "jsdoc-type-pratt-parser@npm:4.8.0" - checksum: 10/1844ef7848f5d0c3c60af95629197f97ff8603c12b99d750aaf7511724ee0d41989c5d24ea5f8d22078a5a8034337a272c9c22f49a8f07a857946417f3c84cfb - languageName: node - linkType: hard - "json-buffer@npm:3.0.1": version: 3.0.1 resolution: "json-buffer@npm:3.0.1" @@ -5557,17 +5084,6 @@ __metadata: languageName: node linkType: hard -"open@npm:^8.0.4": - version: 8.4.2 - resolution: "open@npm:8.4.2" - dependencies: - define-lazy-prop: "npm:^2.0.0" - is-docker: "npm:^2.1.1" - is-wsl: "npm:^2.2.0" - checksum: 10/acd81a1d19879c818acb3af2d2e8e9d81d17b5367561e623248133deb7dd3aefaed527531df2677d3e6aaf0199f84df57b6b2262babff8bf46ea0029aac536c9 - languageName: node - linkType: hard - "optionator@npm:^0.9.3": version: 0.9.4 resolution: "optionator@npm:0.9.4" @@ -5740,13 +5256,6 @@ __metadata: languageName: node linkType: hard -"process@npm:^0.11.10": - version: 0.11.10 - resolution: "process@npm:0.11.10" - checksum: 10/dbaa7e8d1d5cf375c36963ff43116772a989ef2bb47c9bdee20f38fd8fc061119cf38140631cf90c781aca4d3f0f0d2c834711952b728953f04fd7d238f59f5b - languageName: node - linkType: hard - "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -5830,19 +5339,6 @@ __metadata: languageName: node linkType: hard -"recast@npm:^0.23.5": - version: 0.23.11 - resolution: "recast@npm:0.23.11" - dependencies: - ast-types: "npm:^0.16.1" - esprima: "npm:~4.0.0" - source-map: "npm:~0.6.1" - tiny-invariant: "npm:^1.3.3" - tslib: "npm:^2.0.1" - checksum: 10/a622b7848efe13a59a40c9a1a3a8178433eae1048780e04d7392406e2d67fc29e3efa84b3aa8cfda28fd58989f4b59fa968bed295b739987a666bd11cc57a5b2 - languageName: node - linkType: hard - "recma-build-jsx@npm:^1.0.0": version: 1.0.0 resolution: "recma-build-jsx@npm:1.0.0" @@ -6171,7 +5667,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.7.1, semver@npm:^7.7.2": +"semver@npm:^7.3.5, semver@npm:^7.6.0, semver@npm:^7.7.1, semver@npm:^7.7.2": version: 7.7.2 resolution: "semver@npm:7.7.2" bin: @@ -6442,13 +5938,6 @@ __metadata: languageName: node linkType: hard -"source-map@npm:~0.6.1": - version: 0.6.1 - resolution: "source-map@npm:0.6.1" - checksum: 10/59ef7462f1c29d502b3057e822cdbdae0b0e565302c4dd1a95e11e793d8d9d62006cdc10e0fd99163ca33ff2071360cf50ee13f90440806e7ed57d81cba2f7ff - languageName: node - linkType: hard - "space-separated-tokens@npm:^2.0.0": version: 2.0.2 resolution: "space-separated-tokens@npm:2.0.2" @@ -6482,24 +5971,6 @@ __metadata: languageName: node linkType: hard -"storybook@npm:^8.6.15": - version: 8.6.15 - resolution: "storybook@npm:8.6.15" - dependencies: - "@storybook/core": "npm:8.6.15" - peerDependencies: - prettier: ^2 || ^3 - peerDependenciesMeta: - prettier: - optional: true - bin: - getstorybook: ./bin/index.cjs - sb: ./bin/index.cjs - storybook: ./bin/index.cjs - checksum: 10/15762c79ec8444a46bc14cddfadbdd54dfd379828acd38555887a246c01e7c9ebb61e4eafafe04efb3ddf6278fb47035216e7f7d9f94fc205da148870173abdf - languageName: node - linkType: hard - "string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0": version: 4.2.3 resolution: "string-width@npm:4.2.3" @@ -6729,13 +6200,6 @@ __metadata: languageName: node linkType: hard -"tiny-invariant@npm:^1.3.3": - version: 1.3.3 - resolution: "tiny-invariant@npm:1.3.3" - checksum: 10/5e185c8cc2266967984ce3b352a4e57cb89dad5a8abb0dea21468a6ecaa67cd5bb47a3b7a85d08041008644af4f667fb8b6575ba38ba5fb00b3b5068306e59fe - languageName: node - linkType: hard - "tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13": version: 0.2.14 resolution: "tinyglobby@npm:0.2.14" @@ -6790,7 +6254,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.1, tslib@npm:^2.4.0, tslib@npm:^2.8.0": +"tslib@npm:^2.4.0, tslib@npm:^2.8.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 @@ -7105,19 +6569,6 @@ __metadata: languageName: node linkType: hard -"util@npm:^0.12.5": - version: 0.12.5 - resolution: "util@npm:0.12.5" - dependencies: - inherits: "npm:^2.0.3" - is-arguments: "npm:^1.0.4" - is-generator-function: "npm:^1.0.7" - is-typed-array: "npm:^1.1.3" - which-typed-array: "npm:^1.1.2" - checksum: 10/61a10de7753353dd4d744c917f74cdd7d21b8b46379c1e48e1c4fd8e83f8190e6bd9978fc4e5102ab6a10ebda6019d1b36572fa4a325e175ec8b789a121f6147 - languageName: node - linkType: hard - "vfile-matter@npm:^5.0.1": version: 5.0.1 resolution: "vfile-matter@npm:5.0.1" @@ -7201,7 +6652,7 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19, which-typed-array@npm:^1.1.2": +"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19": version: 1.1.19 resolution: "which-typed-array@npm:1.1.19" dependencies: @@ -7274,21 +6725,6 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.2.3": - version: 8.18.3 - resolution: "ws@npm:8.18.3" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ">=5.0.2" - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 10/725964438d752f0ab0de582cd48d6eeada58d1511c3f613485b5598a83680bedac6187c765b0fe082e2d8cc4341fc57707c813ae780feee82d0c5efe6a4c61b6 - languageName: node - linkType: hard - "yallist@npm:^4.0.0": version: 4.0.0 resolution: "yallist@npm:4.0.0" From df40cfc33493c3f4e68629dc4824f8c4e334c1d7 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 16 Jan 2026 00:47:06 +0100 Subject: [PATCH 154/202] Fixes Menu trigger Signed-off-by: Charles de Dreuille --- .changeset/fine-lines-warn.md | 5 ++++ packages/ui/src/components/Menu/Menu.tsx | 32 ------------------------ 2 files changed, 5 insertions(+), 32 deletions(-) create mode 100644 .changeset/fine-lines-warn.md diff --git a/.changeset/fine-lines-warn.md b/.changeset/fine-lines-warn.md new file mode 100644 index 0000000000..25bd3e7491 --- /dev/null +++ b/.changeset/fine-lines-warn.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Fixed Menu component trigger button not toggling correctly. Removed custom click-outside handler that was interfering with React Aria's built-in state management, allowing the menu to properly open and close when clicking the trigger button. diff --git a/packages/ui/src/components/Menu/Menu.tsx b/packages/ui/src/components/Menu/Menu.tsx index 75cdbe5e21..fa86f67700 100644 --- a/packages/ui/src/components/Menu/Menu.tsx +++ b/packages/ui/src/components/Menu/Menu.tsx @@ -33,7 +33,6 @@ import { RouterProvider, Virtualizer, ListLayout, - OverlayTriggerStateContext, } from 'react-aria-components'; import { useStyles } from '../../hooks/useStyles'; import { MenuDefinition } from './definition'; @@ -56,7 +55,6 @@ import { } from '@remixicon/react'; import { useNavigate, useHref } from 'react-router-dom'; import { isExternalLink } from '../../utils/isExternalLink'; -import { useRef, useEffect, useContext } from 'react'; import styles from './Menu.module.css'; import clsx from 'clsx'; @@ -98,33 +96,6 @@ export const Menu = (props: MenuProps) => { const navigate = useNavigate(); let newMaxWidth = maxWidth || (virtualized ? '260px' : 'undefined'); - const popoverRef = useRef(null); - const state = useContext(OverlayTriggerStateContext); - - // Custom click-outside handler for non-modal popovers - useEffect(() => { - if (!state?.isOpen) return; - - const handleClickOutside = (event: MouseEvent) => { - const target = event.target as Node; - - // Check if click is outside the popover - if (popoverRef.current && !popoverRef.current.contains(target)) { - // Check if click is on a trigger button or submenu - const isOnTrigger = (target as Element).closest('[data-trigger]'); - const isOnSubmenu = (target as Element).closest('[role="menu"]'); - - if (!isOnTrigger && !isOnSubmenu) { - state.close(); - } - } - }; - - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [state]); const menuContent = ( ) => { return ( {virtualized ? ( From 3bd2a1a5bf90212a43ab72c91ad6d0ae866719be Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Jan 2026 23:10:54 +0100 Subject: [PATCH 155/202] frontend-app-api: restrict the ability for plugins to override APIs Signed-off-by: Patrik Oldsberg --- .changeset/busy-hairs-attend.md | 5 + .changeset/quiet-coins-protect.md | 5 + .../building-plugins/01-index.md | 2 +- .../utility-apis/02-creating.md | 3 + .../utility-apis/04-configuring.md | 2 + packages/frontend-app-api/report.api.md | 8 + .../src/wiring/createErrorCollector.ts | 8 + .../src/wiring/createSpecializedApp.test.tsx | 165 +++++++++++++----- .../src/wiring/createSpecializedApp.tsx | 63 ++++++- .../src/apis/system/ApiRef.ts | 10 +- 10 files changed, 223 insertions(+), 48 deletions(-) create mode 100644 .changeset/busy-hairs-attend.md create mode 100644 .changeset/quiet-coins-protect.md diff --git a/.changeset/busy-hairs-attend.md b/.changeset/busy-hairs-attend.md new file mode 100644 index 0000000000..523396a5d4 --- /dev/null +++ b/.changeset/busy-hairs-attend.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Updated documentation for `createApiRef` to clarify the role of the ID in specifying the owning plugin of an API. diff --git a/.changeset/quiet-coins-protect.md b/.changeset/quiet-coins-protect.md new file mode 100644 index 0000000000..758050232a --- /dev/null +++ b/.changeset/quiet-coins-protect.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': minor +--- + +BREAKING: The ability for plugins to override APIs has been restricted to only allow overrides of APIs within the same plugin. For example, a plugin can no longer override any of the core APIs provided by the `app` plugin, this must be done with an `app` module instead. diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index fd88478b37..d9acf76e15 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -124,7 +124,7 @@ export interface ExampleApi { } export const exampleApiRef = createApiRef({ - id: 'plugin.example', + id: 'plugin.example.api', }); export class DefaultExampleApi implements ExampleApi { diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md index c632d9a909..2f6959e0ce 100644 --- a/docs/frontend-system/utility-apis/02-creating.md +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -36,6 +36,9 @@ export const workApiRef = createApiRef({ Both of these are properly exported publicly from the package, so that consumers can reach them. +The frontend system infers the owning plugin for an API from the `ApiRef` id, so +use the pattern `plugin..*` to make ownership explicit. This ensures that other plugins can't mistakenly override your API. + ## Providing an extension through your plugin The plugin itself now wants to provide this API and its default implementation, in the form of an API extension. Doing so means that when users install the Example plugin, an instance of the Work utility API will also be automatically available in their apps - both to the Example plugin itself, and to others. We do this in the main plugin package, not the `-react` package. diff --git a/docs/frontend-system/utility-apis/04-configuring.md b/docs/frontend-system/utility-apis/04-configuring.md index 1f21d9b1ba..53527301ef 100644 --- a/docs/frontend-system/utility-apis/04-configuring.md +++ b/docs/frontend-system/utility-apis/04-configuring.md @@ -36,6 +36,8 @@ Well written input-enabled extension often have extension creator functions that Like with other extension types, you replace Utility APIs with your own custom implementation using [extension overrides](../architecture/25-extension-overrides.md). +Note that it is only possible to override a Utility API using a module for the plugin that originally provided the API. Attempting to override an API using a different plugin or module for a different plugin will result in a conflict error. + ```tsx title="in your app" /* highlight-add-start */ import { createFrontendModule } from '@backstage/frontend-plugin-api'; diff --git a/packages/frontend-app-api/report.api.md b/packages/frontend-app-api/report.api.md index bd0709813b..06b5e55278 100644 --- a/packages/frontend-app-api/report.api.md +++ b/packages/frontend-app-api/report.api.md @@ -109,6 +109,14 @@ export type AppErrorTypes = { node: AppNode; }; }; + API_FACTORY_CONFLICT: { + context: { + node: AppNode; + apiRefId: string; + pluginId: string; + existingPluginId: string; + }; + }; ROUTE_DUPLICATE: { context: { routeId: string; diff --git a/packages/frontend-app-api/src/wiring/createErrorCollector.ts b/packages/frontend-app-api/src/wiring/createErrorCollector.ts index c20e92d0f0..dab69ba19e 100644 --- a/packages/frontend-app-api/src/wiring/createErrorCollector.ts +++ b/packages/frontend-app-api/src/wiring/createErrorCollector.ts @@ -66,6 +66,14 @@ export type AppErrorTypes = { API_EXTENSION_INVALID: { context: { node: AppNode }; }; + API_FACTORY_CONFLICT: { + context: { + node: AppNode; + apiRefId: string; + pluginId: string; + existingPluginId: string; + }; + }; // routing ROUTE_DUPLICATE: { context: { routeId: string }; diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx index 5622af0448..6c79002ad9 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx @@ -20,7 +20,9 @@ import { coreExtensionData, createExtension, createFrontendPlugin, + createFrontendModule, ApiBlueprint, + createApiRef, createRouteRef, createExternalRouteRef, createExtensionInput, @@ -36,21 +38,22 @@ import { MemoryRouter } from 'react-router-dom'; import { ApiProvider, ConfigReader } from '@backstage/core-app-api'; import { Fragment } from 'react'; +function makeAppPlugin(label: string = 'Test') { + return createFrontendPlugin({ + pluginId: 'app', + extensions: [ + createExtension({ + attachTo: { id: 'root', input: 'app' }, + output: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement(
{label}
)], + }), + ], + }); +} describe('createSpecializedApp', () => { it('should render the root app', () => { const app = createSpecializedApp({ - features: [ - createFrontendPlugin({ - pluginId: 'test', - extensions: [ - createExtension({ - attachTo: { id: 'root', input: 'app' }, - output: [coreExtensionData.reactElement], - factory: () => [coreExtensionData.reactElement(
Test
)], - }), - ], - }), - ], + features: [makeAppPlugin()], }); render(app.tree.root.instance!.getData(coreExtensionData.reactElement)); @@ -60,32 +63,7 @@ describe('createSpecializedApp', () => { it('should deduplicate features keeping the last received one', () => { const app = createSpecializedApp({ - features: [ - createFrontendPlugin({ - pluginId: 'test', - extensions: [ - createExtension({ - attachTo: { id: 'root', input: 'app' }, - output: [coreExtensionData.reactElement], - factory: () => [ - coreExtensionData.reactElement(
Test 1
), - ], - }), - ], - }), - createFrontendPlugin({ - pluginId: 'test', - extensions: [ - createExtension({ - attachTo: { id: 'root', input: 'app' }, - output: [coreExtensionData.reactElement], - factory: () => [ - coreExtensionData.reactElement(
Test 2
), - ], - }), - ], - }), - ], + features: [makeAppPlugin('Test 1'), makeAppPlugin('Test 2')], }); render(app.tree.root.instance!.getData(coreExtensionData.reactElement)); @@ -249,8 +227,9 @@ describe('createSpecializedApp', () => { const app = createSpecializedApp({ features: [ - createFrontendPlugin({ - pluginId: 'first', + makeAppPlugin(), + createFrontendModule({ + pluginId: 'app', extensions: [ ApiBlueprint.make({ params: defineParams => @@ -264,9 +243,8 @@ describe('createSpecializedApp', () => { }), ], }), - createFrontendPlugin({ - pluginId: 'test', - featureFlags: [{ name: 'a' }, { name: 'b' }], + createFrontendModule({ + pluginId: 'app', extensions: [ createExtension({ attachTo: { id: 'root', input: 'app' }, @@ -311,6 +289,107 @@ describe('createSpecializedApp', () => { expect(mockAnalyticsApi).toHaveBeenCalled(); }); + it('should select the API factory from the owning plugin on conflict', () => { + const testApiRef = createApiRef<{ value: string }>({ id: 'test.api' }); + + const app = createSpecializedApp({ + features: [ + makeAppPlugin(), + createFrontendPlugin({ + pluginId: 'other-before', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: testApiRef, + deps: {}, + factory: () => ({ value: 'other' }), + }), + }), + ], + }), + createFrontendPlugin({ + pluginId: 'test', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: testApiRef, + deps: {}, + factory: () => ({ value: 'owner' }), + }), + }), + ], + }), + createFrontendPlugin({ + pluginId: 'other-after', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: testApiRef, + deps: {}, + factory: () => ({ value: 'other' }), + }), + }), + ], + }), + ], + }); + + expect(app.errors).toEqual([ + expect.objectContaining({ + code: 'API_FACTORY_CONFLICT', + message: expect.stringContaining("API 'test.api'"), + }), + expect.objectContaining({ + code: 'API_FACTORY_CONFLICT', + message: expect.stringContaining("API 'test.api'"), + }), + ]); + + expect(app.apis.get(testApiRef)).toEqual({ value: 'owner' }); + }); + + it('should allow API overrides within the same plugin', () => { + const testApiRef = createApiRef<{ value: string }>({ id: 'test.api' }); + + const app = createSpecializedApp({ + features: [ + makeAppPlugin(), + createFrontendPlugin({ + pluginId: 'test', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: testApiRef, + deps: {}, + factory: () => ({ value: 'plugin' }), + }), + }), + ], + }), + createFrontendModule({ + pluginId: 'test', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: testApiRef, + deps: {}, + factory: () => ({ value: 'module' }), + }), + }), + ], + }), + ], + }); + + expect(app.errors).toBeUndefined(); + expect(app.apis.get(testApiRef)).toEqual({ value: 'module' }); + }); + it('should use provided apis', async () => { const app = createSpecializedApp({ advanced: { diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx index 9e75d19454..f902500e9f 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx @@ -388,7 +388,10 @@ function createApiFactories(options: { collector: ErrorCollector; }): AnyApiFactory[] { const emptyApiHolder = ApiRegistry.from([]); - const factories = new Array(); + const factoriesById = new Map< + string, + { pluginId: string; factory: AnyApiFactory } + >(); for (const apiNode of options.tree.root.edges.attachments.get('apis') ?? []) { if (!instantiateAppNodeTree(apiNode, emptyApiHolder, options.collector)) { @@ -396,7 +399,45 @@ function createApiFactories(options: { } const apiFactory = apiNode.instance?.getData(ApiBlueprint.dataRefs.factory); if (apiFactory) { - factories.push(apiFactory); + const apiRefId = apiFactory.api.id; + const ownerId = getApiOwnerId(apiRefId); + const pluginId = apiNode.spec.plugin.id ?? 'app'; + const existingFactory = factoriesById.get(apiRefId); + + // This allows modules to override factories provided by the plugin, but + // it rejects API overrides from other plugins. In the event of a + // conflict, the owning plugin is attempted to be inferred from the API + // reference ID. + if (existingFactory && existingFactory.pluginId !== pluginId) { + const shouldReplace = + ownerId === pluginId && existingFactory.pluginId !== ownerId; + const acceptedPluginId = shouldReplace + ? pluginId + : existingFactory.pluginId; + const rejectedPluginId = shouldReplace + ? existingFactory.pluginId + : pluginId; + + options.collector.report({ + code: 'API_FACTORY_CONFLICT', + message: `API '${apiRefId}' is already provided by plugin '${acceptedPluginId}', cannot also be provided by '${rejectedPluginId}'.`, + context: { + node: apiNode, + apiRefId, + pluginId: rejectedPluginId, + existingPluginId: acceptedPluginId, + }, + }); + if (shouldReplace) { + factoriesById.set(apiRefId, { + pluginId, + factory: apiFactory, + }); + } + continue; + } + + factoriesById.set(apiRefId, { pluginId, factory: apiFactory }); } else { options.collector.report({ code: 'API_EXTENSION_INVALID', @@ -408,7 +449,23 @@ function createApiFactories(options: { } } - return factories; + return Array.from(factoriesById.values(), entry => entry.factory); +} + +// TODO(Rugvip): It would be good if this was more explicit, but I think that +// might need to wait for some future update for API factories. +function getApiOwnerId(apiRefId: string): string { + const [prefix, ...rest] = apiRefId.split('.'); + if (!prefix) { + return apiRefId; + } + if (prefix === 'core') { + return 'app'; + } + if (prefix === 'plugin' && rest[0]) { + return rest[0]; + } + return prefix; } function createApiHolder(options: { diff --git a/packages/frontend-plugin-api/src/apis/system/ApiRef.ts b/packages/frontend-plugin-api/src/apis/system/ApiRef.ts index adedff9f73..0db3d89bd5 100644 --- a/packages/frontend-plugin-api/src/apis/system/ApiRef.ts +++ b/packages/frontend-plugin-api/src/apis/system/ApiRef.ts @@ -53,7 +53,15 @@ class ApiRefImpl implements ApiRef { } /** - * Creates a reference to an API. + * Creates a reference to an API. The provided `id` is a stable identifier for + * the API implementation. + * + * @remarks + * + * The frontend system infers the owning plugin for an API from the `id`. The + * recommended pattern is `plugin..*` (for example, + * `plugin.catalog.entity-presentation`). This ensures that other plugins can't + * mistakenly override your API implementation. * * @param config - The descriptor of the API to reference. * @returns An API reference. From 2532d2a6da63d064ff924d35591c921f6b1a8ea6 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Thu, 15 Jan 2026 17:45:20 +0100 Subject: [PATCH 156/202] feat(ui): add className and style props to Table component Allow consumers to pass className and style props to the Table component for custom styling and layout positioning. The props are applied consistently across all render states (normal, loading, and error). Signed-off-by: Johan Persson --- .changeset/thin-insects-cough.md | 7 +++++++ packages/ui/report.api.md | 6 ++++++ .../ui/src/components/Table/components/Table.tsx | 16 +++++++++++++--- packages/ui/src/components/Table/types.ts | 2 ++ 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 .changeset/thin-insects-cough.md diff --git a/.changeset/thin-insects-cough.md b/.changeset/thin-insects-cough.md new file mode 100644 index 0000000000..db24462503 --- /dev/null +++ b/.changeset/thin-insects-cough.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Added `className` and `style` props to the `Table` component. + +Affected components: Table diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 7643469bdc..ca9d2bcff1 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -1532,6 +1532,8 @@ export function Table({ rowConfig, selection, emptyState, + className, + style, }: TableProps): JSX_2.Element; // @public (undocumented) @@ -1638,6 +1640,8 @@ export type TablePaginationType = NoPagination | PagePagination; // @public (undocumented) export interface TableProps { + // (undocumented) + className?: string; // (undocumented) columnConfig: readonly ColumnConfig[]; // (undocumented) @@ -1658,6 +1662,8 @@ export interface TableProps { selection?: TableSelection; // (undocumented) sort?: SortState; + // (undocumented) + style?: React.CSSProperties; } // @public (undocumented) diff --git a/packages/ui/src/components/Table/components/Table.tsx b/packages/ui/src/components/Table/components/Table.tsx index 103a1aece9..fd6e595eba 100644 --- a/packages/ui/src/components/Table/components/Table.tsx +++ b/packages/ui/src/components/Table/components/Table.tsx @@ -96,6 +96,8 @@ export function Table({ rowConfig, selection, emptyState, + className, + style, }: TableProps) { const liveRegionId = useId(); @@ -113,11 +115,19 @@ export function Table({ } = selection || {}; if (loading && !data) { - return
Loading...
; + return ( +
+ Loading... +
+ ); } if (error) { - return
Error: {error.message}
; + return ( +
+ Error: {error.message} +
+ ); } const liveRegionLabel = useLiveRegionLabel( @@ -127,7 +137,7 @@ export function Table({ ); return ( -
+
{liveRegionLabel} diff --git a/packages/ui/src/components/Table/types.ts b/packages/ui/src/components/Table/types.ts index 380be8817c..6ba44b6697 100644 --- a/packages/ui/src/components/Table/types.ts +++ b/packages/ui/src/components/Table/types.ts @@ -133,4 +133,6 @@ export interface TableProps { rowConfig?: RowConfig | RowRenderFn; selection?: TableSelection; emptyState?: ReactNode; + className?: string; + style?: React.CSSProperties; } From 55a9dc2bed59945f1fbf40b2cf986cb755b79753 Mon Sep 17 00:00:00 2001 From: James Brooks <52410024+jabrks@users.noreply.github.com> Date: Fri, 16 Jan 2026 09:33:32 +0000 Subject: [PATCH 157/202] Fix ItemCardHeader colour token again (#32346) Signed-off-by: James Brooks --- .changeset/good-chicken-check.md | 5 +++++ .../core-components/src/layout/ItemCard/ItemCardHeader.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/good-chicken-check.md diff --git a/.changeset/good-chicken-check.md b/.changeset/good-chicken-check.md new file mode 100644 index 0000000000..27d311bae9 --- /dev/null +++ b/.changeset/good-chicken-check.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Update colour token again in ItemCardHeader to respect theme definition. diff --git a/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx b/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx index 115c9e89e1..cb1010b2c7 100644 --- a/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx +++ b/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx @@ -30,7 +30,7 @@ export type ItemCardHeaderClassKey = 'root'; const styles = (theme: Theme) => createStyles({ root: { - color: theme.palette.text.primary, + color: theme.getPageTheme({ themeId: 'card' }).fontColor, padding: theme.spacing(2, 2, 3), backgroundImage: theme.getPageTheme({ themeId: 'card' }).backgroundImage, backgroundPosition: 0, From 9ccf84e219cb2a36834e597bae1675b1bb224e12 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Jan 2026 10:58:04 +0100 Subject: [PATCH 158/202] frontend-plugin-api: move app blueprints to new app-react package Signed-off-by: Patrik Oldsberg --- .changeset/bright-pumas-cut.md | 6 + .changeset/common-heads-start.md | 13 + .changeset/curvy-zoos-drop.md | 5 + .changeset/slick-beans-relax.md | 13 + .changeset/warm-pets-accept.md | 13 + .../building-apps/08-migrating.md | 14 +- .../03-common-extension-blueprints.md | 10 + .../06-swappable-components.md | 7 +- packages/frontend-plugin-api/report.api.md | 14 +- .../src/blueprints/IconBundleBlueprint.ts | 5 +- .../src/blueprints/NavContentBlueprint.ts | 1 + .../src/blueprints/RouterBlueprint.tsx | 5 +- .../src/blueprints/SignInPageBlueprint.tsx | 1 + .../blueprints/SwappableComponentBlueprint.ts | 1 + .../src/blueprints/ThemeBlueprint.ts | 1 + .../src/blueprints/TranslationBlueprint.ts | 1 + plugins/app-backend/package.json | 3 +- plugins/app-node/package.json | 3 +- plugins/app-react/.eslintrc.js | 1 + plugins/app-react/README.md | 5 + plugins/app-react/catalog-info.yaml | 10 + plugins/app-react/package.json | 68 +++++ plugins/app-react/report.api.md | 234 ++++++++++++++++++ plugins/app-react/src/blueprints/index.ts | 98 ++++++++ plugins/app-react/src/index.ts | 23 ++ plugins/app-react/src/setupTests.ts | 16 ++ plugins/app/package.json | 4 +- plugins/app/src/extensions/AppNav.tsx | 8 + plugins/app/src/extensions/AppRoot.tsx | 16 ++ plugins/app/src/extensions/AppThemeApi.tsx | 20 +- plugins/app/src/extensions/IconsApi.ts | 20 +- .../app/src/extensions/TranslationsApi.tsx | 20 +- yarn.lock | 28 +++ 33 files changed, 653 insertions(+), 34 deletions(-) create mode 100644 .changeset/bright-pumas-cut.md create mode 100644 .changeset/common-heads-start.md create mode 100644 .changeset/curvy-zoos-drop.md create mode 100644 .changeset/slick-beans-relax.md create mode 100644 .changeset/warm-pets-accept.md create mode 100644 plugins/app-react/.eslintrc.js create mode 100644 plugins/app-react/README.md create mode 100644 plugins/app-react/catalog-info.yaml create mode 100644 plugins/app-react/package.json create mode 100644 plugins/app-react/report.api.md create mode 100644 plugins/app-react/src/blueprints/index.ts create mode 100644 plugins/app-react/src/index.ts create mode 100644 plugins/app-react/src/setupTests.ts diff --git a/.changeset/bright-pumas-cut.md b/.changeset/bright-pumas-cut.md new file mode 100644 index 0000000000..3cb4d7d0b5 --- /dev/null +++ b/.changeset/bright-pumas-cut.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-app-backend': patch +'@backstage/plugin-app-node': patch +--- + +Updated plugin metadata. diff --git a/.changeset/common-heads-start.md b/.changeset/common-heads-start.md new file mode 100644 index 0000000000..15ef218b12 --- /dev/null +++ b/.changeset/common-heads-start.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-app-react': patch +--- + +Moved the following blueprints from `@backstage/frontend-plugin-api`: + +- `IconBundleBlueprint` +- `NavContentBlueprint` +- `RouterBlueprint` +- `SignInPageBlueprint` +- `SwappableComponentBlueprint` +- `ThemeBlueprint` +- `TranslationBlueprint` diff --git a/.changeset/curvy-zoos-drop.md b/.changeset/curvy-zoos-drop.md new file mode 100644 index 0000000000..b58eff1065 --- /dev/null +++ b/.changeset/curvy-zoos-drop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-react': minor +--- + +Initial release of this web library for `@backstage/plugin-app`. diff --git a/.changeset/slick-beans-relax.md b/.changeset/slick-beans-relax.md new file mode 100644 index 0000000000..b4fc3c3175 --- /dev/null +++ b/.changeset/slick-beans-relax.md @@ -0,0 +1,13 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +The following blueprints are being restricted to only be used in app plugin overrides and modules. They are being moved to the `@backstage/plugin-app-react` package and have been deprecated: + +- `IconBundleBlueprint` +- `NavContentBlueprint` +- `RouterBlueprint` +- `SignInPageBlueprint` +- `SwappableComponentBlueprint` +- `ThemeBlueprint` +- `TranslationBlueprint` diff --git a/.changeset/warm-pets-accept.md b/.changeset/warm-pets-accept.md new file mode 100644 index 0000000000..5bf62cbb1a --- /dev/null +++ b/.changeset/warm-pets-accept.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-app': patch +--- + +The following blueprints are being restricted to only be used in app plugin overrides and modules. They will now produce a deprecation warning when used outside of the app plugin: + +- `IconBundleBlueprint` +- `NavContentBlueprint` +- `RouterBlueprint` +- `SignInPageBlueprint` +- `SwappableComponentBlueprint` +- `ThemeBlueprint` +- `TranslationBlueprint` diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index 88dff704c4..26dc3d085c 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -444,7 +444,7 @@ const app = createApp({ Can be converted to the following extension: ```tsx -import { SignInPageBlueprint } from '@backstage/frontend-plugin-api'; +import { SignInPageBlueprint } from '@backstage/plugin-app-react'; const signInPage = SignInPageBlueprint.make({ params: { @@ -492,7 +492,7 @@ const app = createApp({ Can be converted to the following extension: ```tsx -import { ThemeBlueprint } from '@backstage/frontend-plugin-api'; +import { ThemeBlueprint } from '@backstage/plugin-app-react'; const customLightThemeExtension = ThemeBlueprint.make({ name: 'custom-light', @@ -535,7 +535,7 @@ const app = createApp({ Icons are now installed as extensions, using the `IconBundleBlueprint` to make new instances which can be added to the app. ```ts -import { IconBundleBlueprint } from '@backstage/frontend-plugin-api'; +import { IconBundleBlueprint } from '@backstage/plugin-app-react'; const exampleIconBundle = IconBundleBlueprint.make({ name: 'example-bundle', @@ -586,10 +586,8 @@ Can be converted to the following extension: ```tsx import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha'; -import { - createTranslationMessages, - TranslationBlueprint, -} from '@backstage/frontend-plugin-api'; +import { createTranslationMessages } from '@backstage/frontend-plugin-api'; +import { TranslationBlueprint } from '@backstage/plugin-app-react'; const catalogTranslations = TranslationBlueprint.make({ name: 'catalog-overrides', @@ -705,7 +703,7 @@ export const navModule = createFrontendModule({ Then in the actual implementation for the `SidebarContent` extension, you can provide something like the following, where you implement the entire `Sidebar` component. ```tsx title="in packages/app/src/modules/nav/Sidebar.tsx" -import { NavContentBlueprint } from '@backstage/frontend-plugin-api'; +import { NavContentBlueprint } from '@backstage/plugin-app-react'; export const SidebarContent = NavContentBlueprint.make({ params: { diff --git a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md index abac1f03f5..df83c696f6 100644 --- a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md +++ b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md @@ -23,6 +23,8 @@ Navigation item extensions are used to provide menu items that link to different Page extensions provide content for a particular route in the app. By default pages are attached to the app routes extensions, which renders the root routes. +## Extension blueprints in `@backstage/plugin-app-react` + ### SignInPage - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.SignInPageBlueprint.html) Sign-in page extension have a single purpose - to implement a custom sign-in page. They are always attached to the app root extension and are rendered before the rest of the app until the user is signed in. @@ -43,6 +45,14 @@ Icon bundle extensions provide the ability to replace or provide new icons to th Translation extension provide custom translation messages for the app. They can be used both to override the default english messages to custom ones, as well as provide translations for additional languages. +### NavContent - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.NavContentBlueprint.html) + +Nav content extensions allow you to replace the entire navbar with your own component. They are always attached to the app nav extension. + +### Router - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.RouterBlueprint.html) + +Router extensions allow you to replace the router component used by the app. They are always attached to the app root extension. + ## Extension blueprints in `@backstage/plugin-catalog-react/alpha` These are the [extension blueprints](../architecture/23-extension-blueprints.md) provided by the Catalog plugin. diff --git a/docs/frontend-system/building-plugins/06-swappable-components.md b/docs/frontend-system/building-plugins/06-swappable-components.md index a26396bbff..786388b3c8 100644 --- a/docs/frontend-system/building-plugins/06-swappable-components.md +++ b/docs/frontend-system/building-plugins/06-swappable-components.md @@ -52,11 +52,8 @@ In order to override a Swappable Component, you need to create a `SwappableCompo There are two different ways to add extensions to the `app` plugin, both are documented below in an example of overriding the `Progress` Swappable Component. ```tsx title="in packages/app/src/App.tsx" -import { - Progress, - SwappableComponentBlueprint, - createFrontendModule, -} from '@backstage/frontend-plugin-api'; +import { Progress, createFrontendModule } from '@backstage/frontend-plugin-api'; +import { SwappableComponentBlueprint } from '@backstage/plugin-app-react'; import { MyCustomProgress } from './CustomProgress'; import { createApp } from '@backstage/frontend-defaults'; import appPlugin from '@backstage/plugin-app'; diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index fbe7110d80..ceba08d7c3 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -1457,7 +1457,7 @@ export const googleAuthApiRef: ApiRef< SessionApi >; -// @public (undocumented) +// @public @deprecated (undocumented) export const IconBundleBlueprint: ExtensionBlueprint_2<{ kind: 'icon-bundle'; params: { @@ -1522,7 +1522,7 @@ export const microsoftAuthApiRef: ApiRef< SessionApi >; -// @public +// @public @deprecated export const NavContentBlueprint: ExtensionBlueprint_2<{ kind: 'nav-content'; params: { @@ -1923,7 +1923,7 @@ export type RouteFunc = ( : readonly [params: TParams] ) => string; -// @public (undocumented) +// @public @deprecated (undocumented) export const RouterBlueprint: ExtensionBlueprint_2<{ kind: 'app-router-component'; params: { @@ -1998,7 +1998,7 @@ export namespace SessionState { export type SignedOut = typeof SessionState.SignedOut; } -// @public +// @public @deprecated export const SignInPageBlueprint: ExtensionBlueprint_2<{ kind: 'sign-in-page'; params: { @@ -2066,7 +2066,7 @@ export interface SubRouteRef< readonly T: TParams; } -// @public +// @public @deprecated export const SwappableComponentBlueprint: ExtensionBlueprint_2<{ kind: 'component'; params: >(params: { @@ -2150,7 +2150,7 @@ export interface SwappableComponentsApi { // @public export const swappableComponentsApiRef: ApiRef_2; -// @public +// @public @deprecated export const ThemeBlueprint: ExtensionBlueprint_2<{ kind: 'theme'; params: { @@ -2186,7 +2186,7 @@ export type TranslationApi = { // @public (undocumented) export const translationApiRef: ApiRef; -// @public +// @public @deprecated export const TranslationBlueprint: ExtensionBlueprint_2<{ kind: 'translation'; params: { diff --git a/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts index 303d1a3fd2..2f52f7590a 100644 --- a/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts @@ -21,7 +21,10 @@ const iconsDataRef = createExtensionDataRef<{ [key in string]: IconComponent; }>().with({ id: 'core.icons' }); -/** @public */ +/** + * @public + * @deprecated Use {@link @backstage/plugin-app-react#IconBundleBlueprint} instead. + */ export const IconBundleBlueprint = createExtensionBlueprint({ kind: 'icon-bundle', attachTo: { id: 'api:app/icons', input: 'icons' }, diff --git a/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.ts index a6524ea49e..f56de2032b 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/NavContentBlueprint.ts @@ -60,6 +60,7 @@ const componentDataRef = createExtensionDataRef().with({ * Creates an extension that replaces the entire nav bar with your own component. * * @public + * @deprecated Use {@link @backstage/plugin-app-react#NavContentBlueprint} instead. */ export const NavContentBlueprint = createExtensionBlueprint({ kind: 'nav-content', diff --git a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx index c4b7c16bd6..88096cf16a 100644 --- a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.tsx @@ -21,7 +21,10 @@ const componentDataRef = createExtensionDataRef< (props: { children: ReactNode }) => JSX.Element | null >().with({ id: 'app.router.wrapper' }); -/** @public */ +/** + * @public + * @deprecated Use {@link @backstage/plugin-app-react#RouterBlueprint} instead. + */ export const RouterBlueprint = createExtensionBlueprint({ kind: 'app-router-component', attachTo: { id: 'app/root', input: 'router' }, diff --git a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx index b28a0108ca..9f51e8f666 100644 --- a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.tsx @@ -44,6 +44,7 @@ const componentDataRef = createExtensionDataRef< * Creates an extension that replaces the sign in page. * * @public + * @deprecated Use {@link @backstage/plugin-app-react#SignInPageBlueprint} instead. */ export const SignInPageBlueprint = createExtensionBlueprint({ kind: 'sign-in-page', diff --git a/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.ts index b72d14d87f..38e333826a 100644 --- a/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.ts @@ -31,6 +31,7 @@ export const componentDataRef = createExtensionDataRef<{ * Blueprint for creating swappable components from a SwappableComponentRef and a loader * * @public + * @deprecated Use {@link @backstage/plugin-app-react#SwappableComponentBlueprint} instead. */ export const SwappableComponentBlueprint = createExtensionBlueprint({ kind: 'component', diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts index aaf4f32608..f93075181e 100644 --- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts @@ -25,6 +25,7 @@ const themeDataRef = createExtensionDataRef().with({ * Creates an extension that adds/replaces an app theme. * * @public + * @deprecated Use {@link @backstage/plugin-app-react#ThemeBlueprint} instead. */ export const ThemeBlueprint = createExtensionBlueprint({ kind: 'theme', diff --git a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts index dd4a9ddc09..491053d763 100644 --- a/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/TranslationBlueprint.ts @@ -25,6 +25,7 @@ const translationDataRef = createExtensionDataRef< * Creates an extension that adds translations to your app. * * @public + * @deprecated Use {@link @backstage/plugin-app-react#TranslationBlueprint} instead. */ export const TranslationBlueprint = createExtensionBlueprint({ kind: 'translation', diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 9ab956713f..73ff162919 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -8,7 +8,8 @@ "pluginPackages": [ "@backstage/plugin-app", "@backstage/plugin-app-backend", - "@backstage/plugin-app-node" + "@backstage/plugin-app-node", + "@backstage/plugin-app-react" ] }, "publishConfig": { diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index c41733eb04..3641fbade9 100644 --- a/plugins/app-node/package.json +++ b/plugins/app-node/package.json @@ -8,7 +8,8 @@ "pluginPackages": [ "@backstage/plugin-app", "@backstage/plugin-app-backend", - "@backstage/plugin-app-node" + "@backstage/plugin-app-node", + "@backstage/plugin-app-react" ] }, "publishConfig": { diff --git a/plugins/app-react/.eslintrc.js b/plugins/app-react/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/app-react/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/app-react/README.md b/plugins/app-react/README.md new file mode 100644 index 0000000000..c7e7fb2823 --- /dev/null +++ b/plugins/app-react/README.md @@ -0,0 +1,5 @@ +# @backstage/plugin-app-react + +Welcome to the web library package for the app plugin! + +_This plugin was created through the Backstage CLI_ diff --git a/plugins/app-react/catalog-info.yaml b/plugins/app-react/catalog-info.yaml new file mode 100644 index 0000000000..1ef48d3b2d --- /dev/null +++ b/plugins/app-react/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-app-react + title: '@backstage/plugin-app-react' + description: Web library for the app plugin +spec: + lifecycle: experimental + type: backstage-web-library + owner: framework-maintainers diff --git a/plugins/app-react/package.json b/plugins/app-react/package.json new file mode 100644 index 0000000000..e27c95d7bf --- /dev/null +++ b/plugins/app-react/package.json @@ -0,0 +1,68 @@ +{ + "name": "@backstage/plugin-app-react", + "version": "0.0.0", + "description": "Web library for the app plugin", + "backstage": { + "role": "web-library", + "pluginId": "app", + "pluginPackages": [ + "@backstage/plugin-app", + "@backstage/plugin-app-backend", + "@backstage/plugin-app-node", + "@backstage/plugin-app-react" + ] + }, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/app-react" + }, + "license": "Apache-2.0", + "sideEffects": false, + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/core-plugin-api": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", + "@material-ui/core": "^4.9.13" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/frontend-test-utils": "workspace:^", + "@backstage/test-utils": "workspace:^", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^16.0.0", + "@types/react": "^18.0.0", + "react": "^18.0.2", + "react-dom": "^18.0.2", + "react-router-dom": "^6.3.0" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0", + "react-router-dom": "^6.3.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } +} diff --git a/plugins/app-react/report.api.md b/plugins/app-react/report.api.md new file mode 100644 index 0000000000..48ec50749d --- /dev/null +++ b/plugins/app-react/report.api.md @@ -0,0 +1,234 @@ +## API Report File for "@backstage/plugin-app-react" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { AppTheme } from '@backstage/frontend-plugin-api'; +import { ComponentType } from 'react'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/frontend-plugin-api'; +import { NavContentComponent } from '@backstage/frontend-plugin-api'; +import { NavContentComponentProps } from '@backstage/frontend-plugin-api'; +import { ReactNode } from 'react'; +import { SignInPageProps } from '@backstage/frontend-plugin-api'; +import { SwappableComponentRef } from '@backstage/frontend-plugin-api'; +import { TranslationMessages } from '@backstage/frontend-plugin-api'; +import { TranslationResource } from '@backstage/frontend-plugin-api'; + +// @public +export const IconBundleBlueprint: ExtensionBlueprint<{ + kind: 'icon-bundle'; + params: { + icons: { [key in string]: IconComponent }; + }; + output: ExtensionDataRef< + { + [x: string]: IconComponent; + }, + 'core.icons', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + icons: ConfigurableExtensionDataRef< + { + [x: string]: IconComponent; + }, + 'core.icons', + {} + >; + }; +}>; + +// @public +export const NavContentBlueprint: ExtensionBlueprint<{ + kind: 'nav-content'; + params: { + component: NavContentComponent; + }; + output: ExtensionDataRef< + NavContentComponent, + 'core.nav-content.component', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + component: ConfigurableExtensionDataRef< + NavContentComponent, + 'core.nav-content.component', + {} + >; + }; +}>; + +export { NavContentComponent }; + +export { NavContentComponentProps }; + +// @public +export const RouterBlueprint: ExtensionBlueprint<{ + kind: 'app-router-component'; + params: { + Component?: [error: 'Use the `component` parameter instead']; + component: (props: { children: ReactNode }) => JSX.Element | null; + }; + output: ExtensionDataRef< + (props: { children: ReactNode }) => JSX.Element | null, + 'app.router.wrapper', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + component: ConfigurableExtensionDataRef< + (props: { children: ReactNode }) => JSX.Element | null, + 'app.router.wrapper', + {} + >; + }; +}>; + +// @public +export const SignInPageBlueprint: ExtensionBlueprint<{ + kind: 'sign-in-page'; + params: { + loader: () => Promise>; + }; + output: ExtensionDataRef< + ComponentType, + 'core.sign-in-page.component', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + component: ConfigurableExtensionDataRef< + ComponentType, + 'core.sign-in-page.component', + {} + >; + }; +}>; + +export { SignInPageProps }; + +// @public +export const SwappableComponentBlueprint: ExtensionBlueprint<{ + kind: 'component'; + params: >(params: { + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { + ref: Ref; + } & ((props: IExternalComponentProps) => JSX.Element | null) + : never; + loader: Ref extends SwappableComponentRef + ? + | (() => (props: IInnerComponentProps) => JSX.Element | null) + | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) + : never; + }) => ExtensionBlueprintParams<{ + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { + ref: Ref; + } & ((props: IExternalComponentProps) => JSX.Element | null) + : never; + loader: Ref extends SwappableComponentRef + ? + | (() => (props: IInnerComponentProps) => JSX.Element | null) + | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) + : never; + }>; + output: ExtensionDataRef< + { + ref: SwappableComponentRef; + loader: + | (() => (props: {}) => JSX.Element | null) + | (() => Promise<(props: {}) => JSX.Element | null>); + }, + 'core.swappableComponent', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + component: ConfigurableExtensionDataRef< + { + ref: SwappableComponentRef; + loader: + | (() => (props: {}) => JSX.Element | null) + | (() => Promise<(props: {}) => JSX.Element | null>); + }, + 'core.swappableComponent', + {} + >; + }; +}>; + +// @public +export const ThemeBlueprint: ExtensionBlueprint<{ + kind: 'theme'; + params: { + theme: AppTheme; + }; + output: ExtensionDataRef; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + theme: ConfigurableExtensionDataRef; + }; +}>; + +// @public +export const TranslationBlueprint: ExtensionBlueprint<{ + kind: 'translation'; + params: { + resource: TranslationResource | TranslationMessages; + }; + output: ExtensionDataRef< + | TranslationResource + | TranslationMessages< + string, + { + [x: string]: string; + }, + boolean + >, + 'core.translation.translation', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + translation: ConfigurableExtensionDataRef< + | TranslationResource + | TranslationMessages< + string, + { + [x: string]: string; + }, + boolean + >, + 'core.translation.translation', + {} + >; + }; +}>; +``` diff --git a/plugins/app-react/src/blueprints/index.ts b/plugins/app-react/src/blueprints/index.ts new file mode 100644 index 0000000000..09d05b71ed --- /dev/null +++ b/plugins/app-react/src/blueprints/index.ts @@ -0,0 +1,98 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + IconBundleBlueprint as _IconBundleBlueprint, + NavContentBlueprint as _NavContentBlueprint, + type NavContentComponent, + type NavContentComponentProps, + RouterBlueprint as _RouterBlueprint, + SignInPageBlueprint as _SignInPageBlueprint, + type SignInPageProps, + SwappableComponentBlueprint as _SwappableComponentBlueprint, + ThemeBlueprint as _ThemeBlueprint, + TranslationBlueprint as _TranslationBlueprint, +} from '@backstage/frontend-plugin-api'; + +/** + * Creates an extension that adds/replaces an app theme. This blueprint is limited to use by the app plugin. + * + * @public + */ +export const ThemeBlueprint = _ThemeBlueprint; + +/** + * Blueprint for creating swappable components from a SwappableComponentRef and a loader. This blueprint is limited to use by the app plugin. + * + * @public + */ +export const SwappableComponentBlueprint = _SwappableComponentBlueprint; + +/** + * Creates an extension that replaces the sign in page. This blueprint is limited to use by the app plugin. + * + * @public + */ +export const SignInPageBlueprint = _SignInPageBlueprint; + +/** + * Creates an extension that replaces the router component. This blueprint is limited to use by the app plugin. + * + * @public + */ +export const RouterBlueprint = _RouterBlueprint; + +/** + * Creates an extension that replaces the entire nav bar with your own component. This blueprint is limited to use by the app plugin. + * + * @public + */ +export const NavContentBlueprint = _NavContentBlueprint; + +/** + * Creates an extension that adds icon bundles to your app. This blueprint is limited to use by the app plugin. + * + * @public + */ +export const IconBundleBlueprint = _IconBundleBlueprint; + +/** + * Props for the `SignInPage` component. + * + * @public + */ +export type { SignInPageProps }; + +/** + * The props for the {@link NavContentComponent}. + * + * @public + */ +export type { NavContentComponentProps }; + +/** + * A component that renders the nav bar content, to be passed to the {@link NavContentBlueprint}. + * + * @public + */ +export type { NavContentComponent }; + +/** + * Creates an extension that adds translations to your app. This blueprint is limited to use by the app plugin. + * + * @public + */ +export const TranslationBlueprint = _TranslationBlueprint; diff --git a/plugins/app-react/src/index.ts b/plugins/app-react/src/index.ts new file mode 100644 index 0000000000..bcdb919775 --- /dev/null +++ b/plugins/app-react/src/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Web library for the app plugin. + * + * @packageDocumentation + */ + +export * from './blueprints'; diff --git a/plugins/app-react/src/setupTests.ts b/plugins/app-react/src/setupTests.ts new file mode 100644 index 0000000000..5f11bab1c2 --- /dev/null +++ b/plugins/app-react/src/setupTests.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import '@testing-library/jest-dom'; diff --git a/plugins/app/package.json b/plugins/app/package.json index 1d277a30e3..db77cee7cc 100644 --- a/plugins/app/package.json +++ b/plugins/app/package.json @@ -7,7 +7,8 @@ "pluginPackages": [ "@backstage/plugin-app", "@backstage/plugin-app-backend", - "@backstage/plugin-app-node" + "@backstage/plugin-app-node", + "@backstage/plugin-app-react" ] }, "publishConfig": { @@ -54,6 +55,7 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/integration-react": "workspace:^", + "@backstage/plugin-app-react": "workspace:^", "@backstage/plugin-permission-react": "workspace:^", "@backstage/theme": "workspace:^", "@backstage/types": "workspace:^", diff --git a/plugins/app/src/extensions/AppNav.tsx b/plugins/app/src/extensions/AppNav.tsx index aa030300fe..8e76a16781 100644 --- a/plugins/app/src/extensions/AppNav.tsx +++ b/plugins/app/src/extensions/AppNav.tsx @@ -94,6 +94,14 @@ export const AppNav = createExtension({ }, output: [coreExtensionData.reactElement], *factory({ inputs }) { + if (inputs.content && inputs.content.node.spec.plugin?.id !== 'app') { + // eslint-disable-next-line no-console + console.warn( + `DEPRECATION WARNING: NavContent should only be installed as an extension in the app plugin. ` + + `You can either use appPlugin.override(), or a module for the app plugin. The following extension will be ignored in the future: ${inputs.content.node.spec.id}`, + ); + } + const Content = inputs.content?.get(NavContentBlueprint.dataRefs.component) ?? DefaultNavContent; diff --git a/plugins/app/src/extensions/AppRoot.tsx b/plugins/app/src/extensions/AppRoot.tsx index 9ce2b055dd..47ea50a641 100644 --- a/plugins/app/src/extensions/AppRoot.tsx +++ b/plugins/app/src/extensions/AppRoot.tsx @@ -74,6 +74,22 @@ export const AppRoot = createExtension({ }, output: [coreExtensionData.reactElement], factory({ inputs, apis }) { + if (inputs.router && inputs.router.node.spec.plugin?.id !== 'app') { + // eslint-disable-next-line no-console + console.warn( + `DEPRECATION WARNING: Router should only be installed as an extension in the app plugin. ` + + `You can either use appPlugin.override(), or a module for the app plugin. The following extension will be ignored in the future: ${inputs.router.node.spec.id}`, + ); + } + + if (inputs.signInPage && inputs.signInPage.node.spec.plugin?.id !== 'app') { + // eslint-disable-next-line no-console + console.warn( + `DEPRECATION WARNING: SignInPage should only be installed as an extension in the app plugin. ` + + `You can either use appPlugin.override(), or a module for the app plugin. The following extension will be ignored in the future: ${inputs.signInPage.node.spec.id}`, + ); + } + if (isProtectedApp()) { const identityApi = apis.get(identityApiRef); if (!identityApi) { diff --git a/plugins/app/src/extensions/AppThemeApi.tsx b/plugins/app/src/extensions/AppThemeApi.tsx index e3c516ca69..2b265ddb53 100644 --- a/plugins/app/src/extensions/AppThemeApi.tsx +++ b/plugins/app/src/extensions/AppThemeApi.tsx @@ -44,10 +44,24 @@ export const AppThemeApi = ApiBlueprint.makeWithOverrides({ defineParams({ api: appThemeApiRef, deps: {}, - factory: () => - AppThemeSelector.createWithStorage( + factory: () => { + const nonAppExtensions = inputs.themes.filter( + i => i.node.spec.plugin?.id !== 'app', + ); + + if (nonAppExtensions.length > 0) { + const list = nonAppExtensions.map(i => i.node.spec.id).join(', '); + // eslint-disable-next-line no-console + console.warn( + `DEPRECATION WARNING: Theme should only be installed as an extension in the app plugin. ` + + `You can either use appPlugin.override(), or a module for the app plugin. The following extension will be ignored in the future: ${list}`, + ); + } + + return AppThemeSelector.createWithStorage( inputs.themes.map(i => i.get(ThemeBlueprint.dataRefs.theme)), - ), + ); + }, }), ); }, diff --git a/plugins/app/src/extensions/IconsApi.ts b/plugins/app/src/extensions/IconsApi.ts index fcce9ea718..f10e927161 100644 --- a/plugins/app/src/extensions/IconsApi.ts +++ b/plugins/app/src/extensions/IconsApi.ts @@ -40,12 +40,26 @@ export const IconsApi = ApiBlueprint.makeWithOverrides({ defineParams({ api: iconsApiRef, deps: {}, - factory: () => - new DefaultIconsApi( + factory: () => { + const nonAppExtensions = inputs.icons.filter( + i => i.node.spec.plugin?.id !== 'app', + ); + + if (nonAppExtensions.length > 0) { + const list = nonAppExtensions.map(i => i.node.spec.id).join(', '); + // eslint-disable-next-line no-console + console.warn( + `DEPRECATION WARNING: IconBundle should only be installed as an extension in the app plugin. ` + + `You can either use appPlugin.override(), or a module for the app plugin. The following extension will be ignored in the future: ${list}`, + ); + } + + return new DefaultIconsApi( inputs.icons .map(i => i.get(IconBundleBlueprint.dataRefs.icons)) .reduce((acc, bundle) => ({ ...acc, ...bundle }), defaultIcons), - ), + ); + }, }), ); }, diff --git a/plugins/app/src/extensions/TranslationsApi.tsx b/plugins/app/src/extensions/TranslationsApi.tsx index 31d89a5acb..2a6017af4b 100644 --- a/plugins/app/src/extensions/TranslationsApi.tsx +++ b/plugins/app/src/extensions/TranslationsApi.tsx @@ -42,13 +42,27 @@ export const TranslationsApi = ApiBlueprint.makeWithOverrides({ defineParams({ api: translationApiRef, deps: { languageApi: appLanguageApiRef }, - factory: ({ languageApi }) => - I18nextTranslationApi.create({ + factory: ({ languageApi }) => { + const nonAppExtensions = inputs.translations.filter( + i => i.node.spec.plugin?.id !== 'app', + ); + + if (nonAppExtensions.length > 0) { + const list = nonAppExtensions.map(i => i.node.spec.id).join(', '); + // eslint-disable-next-line no-console + console.warn( + `DEPRECATION WARNING: Translations should only be installed as an extension in the app plugin. ` + + `You can either use appPlugin.override(), or a module for the app plugin. The following extension will be ignored in the future: ${list}`, + ); + } + + return I18nextTranslationApi.create({ languageApi, resources: inputs.translations.map(i => i.get(TranslationBlueprint.dataRefs.translation), ), - }), + }); + }, }), ); }, diff --git a/yarn.lock b/yarn.lock index 92dfaa0796..0f57d51fa7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4155,6 +4155,33 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-app-react@workspace:^, @backstage/plugin-app-react@workspace:plugins/app-react": + version: 0.0.0-use.local + resolution: "@backstage/plugin-app-react@workspace:plugins/app-react" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/frontend-test-utils": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@material-ui/core": "npm:^4.9.13" + "@testing-library/jest-dom": "npm:^6.0.0" + "@testing-library/react": "npm:^16.0.0" + "@types/react": "npm:^18.0.0" + react: "npm:^18.0.2" + react-dom: "npm:^18.0.2" + react-router-dom: "npm:^6.3.0" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + react-router-dom: ^6.3.0 + peerDependenciesMeta: + "@types/react": + optional: true + languageName: unknown + linkType: soft + "@backstage/plugin-app-visualizer@workspace:^, @backstage/plugin-app-visualizer@workspace:plugins/app-visualizer": version: 0.0.0-use.local resolution: "@backstage/plugin-app-visualizer@workspace:plugins/app-visualizer" @@ -4194,6 +4221,7 @@ __metadata: "@backstage/frontend-plugin-api": "workspace:^" "@backstage/frontend-test-utils": "workspace:^" "@backstage/integration-react": "workspace:^" + "@backstage/plugin-app-react": "workspace:^" "@backstage/plugin-permission-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" From 973c839814e4f5e81db210700f26da36c0a192c5 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Fri, 16 Jan 2026 12:23:26 +0100 Subject: [PATCH 159/202] fix(ui): show active sorting indicator in Table component The Table component wasn't showing a visual indicator when a column was actively sorted. The sort button was only visible on hover. This fix uses React Aria's `data-sort-direction` attribute to keep the indicator visible when sorting is active, and rotates the arrow icon via CSS for descending order. Affected components: Table, Column Signed-off-by: Johan Persson --- .changeset/eight-aliens-warn.md | 7 +++++++ .../ui/src/components/Table/Table.module.css | 21 +++++++------------ .../components/Table/components/Column.tsx | 10 +++------ 3 files changed, 17 insertions(+), 21 deletions(-) create mode 100644 .changeset/eight-aliens-warn.md diff --git a/.changeset/eight-aliens-warn.md b/.changeset/eight-aliens-warn.md new file mode 100644 index 0000000000..f602b288d0 --- /dev/null +++ b/.changeset/eight-aliens-warn.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed Table sorting indicator not being visible when a column is actively sorted. + +Affected components: Table, Column diff --git a/packages/ui/src/components/Table/Table.module.css b/packages/ui/src/components/Table/Table.module.css index ea148ae893..be35898fb0 100644 --- a/packages/ui/src/components/Table/Table.module.css +++ b/packages/ui/src/components/Table/Table.module.css @@ -39,12 +39,6 @@ padding: var(--bui-space-3); font-size: var(--bui-font-size-3); color: var(--bui-fg-primary); - - &:hover { - .bui-TableHeadSortButton { - opacity: 1; - } - } } .bui-TableHeadSelection { @@ -68,17 +62,16 @@ transition: opacity 0.1s ease-in-out; color: var(--bui-fg-secondary); + .bui-TableHead:hover &, + [data-sort-direction='ascending'] &, + [data-sort-direction='descending'] & { + opacity: 1; + } + & svg { transition: transform 0.1s ease-in-out; } - - &[data-sort-order='asc'] svg { - opacity: 1; - transform: rotate(0); - } - - &[data-sort-order='desc'] svg { - opacity: 1; + [data-sort-direction='descending'] & svg { transform: rotate(180deg); } } diff --git a/packages/ui/src/components/Table/components/Column.tsx b/packages/ui/src/components/Table/components/Column.tsx index 9076ec8557..473f95a40e 100644 --- a/packages/ui/src/components/Table/components/Column.tsx +++ b/packages/ui/src/components/Table/components/Column.tsx @@ -20,7 +20,7 @@ import { TableDefinition } from '../definition'; import styles from '../Table.module.css'; import clsx from 'clsx'; import { ColumnProps } from '../types'; -import { RiArrowUpLine, RiArrowDownLine } from '@remixicon/react'; +import { RiArrowUpLine } from '@remixicon/react'; /** @public */ export const Column = (props: ColumnProps) => { @@ -32,7 +32,7 @@ export const Column = (props: ColumnProps) => { className={clsx(classNames.head, styles[classNames.head], className)} {...rest} > - {({ allowsSorting, sortDirection }) => ( + {({ allowsSorting }) => (
{ styles[classNames.headSortButton], )} > - {sortDirection === 'descending' ? ( - - ) : ( - - )} + )}
From f157f43cbdd2b464c22b3012ef25343034db8e59 Mon Sep 17 00:00:00 2001 From: gaelgoth Date: Wed, 15 Oct 2025 15:25:39 +0200 Subject: [PATCH 160/202] Fix installation command Signed-off-by: gaelgoth --- .changeset/honest-waves-rest.md | 5 +++++ plugins/mui-to-bui/README.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/honest-waves-rest.md diff --git a/.changeset/honest-waves-rest.md b/.changeset/honest-waves-rest.md new file mode 100644 index 0000000000..d6be53903a --- /dev/null +++ b/.changeset/honest-waves-rest.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-mui-to-bui': patch +--- + +Fix installation command diff --git a/plugins/mui-to-bui/README.md b/plugins/mui-to-bui/README.md index 4e11db20b6..d964b4827f 100644 --- a/plugins/mui-to-bui/README.md +++ b/plugins/mui-to-bui/README.md @@ -11,7 +11,7 @@ The Backstage UI Themer helps you convert an existing MUI v5 theme into Backstag Run this from your Backstage repo root: ```bash -yarn add --cwd packages/app @backstage/plugin-mui-to-bui +yarn --cwd packages/app add @backstage/plugin-mui-to-bui ``` ### 2) Wire it up depending on your frontend system From b164e20697c52c2f6b255378b8bf228c40fb388c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Jan 2026 11:40:36 +0100 Subject: [PATCH 161/202] frontend-plugin-api: added PluginWrapperBlueprint + implementation Signed-off-by: Patrik Oldsberg --- packages/frontend-plugin-api/report.api.md | 53 +++++++++ .../src/apis/definitions/PluginWrapperApi.ts | 41 +++++++ .../src/apis/definitions/index.ts | 1 + .../src/blueprints/PluginWrapperBlueprint.tsx | 56 +++++++++ .../src/blueprints/index.ts | 1 + .../src/components/ExtensionBoundary.tsx | 25 +++- .../DefaultPluginWrapperApi.test.tsx | 98 ++++++++++++++++ .../DefaultPluginWrapperApi.tsx | 107 ++++++++++++++++++ .../app/src/apis/PluginWrapperApi/index.ts | 17 +++ .../app/src/extensions/PluginWrapperApi.ts | 49 ++++++++ plugins/app/src/extensions/index.ts | 1 + 11 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 packages/frontend-plugin-api/src/apis/definitions/PluginWrapperApi.ts create mode 100644 packages/frontend-plugin-api/src/blueprints/PluginWrapperBlueprint.tsx create mode 100644 plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.test.tsx create mode 100644 plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.tsx create mode 100644 plugins/app/src/apis/PluginWrapperApi/index.ts create mode 100644 plugins/app/src/extensions/PluginWrapperApi.ts diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index ceba08d7c3..9115d0732b 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -1868,6 +1868,59 @@ export interface PluginOptions< routes?: TRoutes; } +// @public +export type PluginWrapperApi = { + getPluginWrapper(pluginId: string): + | ComponentType<{ + children: ReactNode; + }> + | undefined; +}; + +// @public +export const pluginWrapperApiRef: ApiRef; + +// @public +export const PluginWrapperBlueprint: ExtensionBlueprint_2<{ + kind: 'plugin-wrapper'; + params: (params: { + loader: () => Promise<{ + component: ComponentType<{ + children: ReactNode; + }>; + }>; + }) => ExtensionBlueprintParams_2<{ + loader: () => Promise<{ + component: ComponentType<{ + children: ReactNode; + }>; + }>; + }>; + output: ExtensionDataRef_2< + () => Promise<{ + component: ComponentType<{ + children: ReactNode; + }>; + }>, + 'core.plugin-wrapper.loader', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + wrapper: ConfigurableExtensionDataRef_2< + () => Promise<{ + component: ComponentType<{ + children: ReactNode; + }>; + }>, + 'core.plugin-wrapper.loader', + {} + >; + }; +}>; + // @public (undocumented) export type PortableSchema = { parse: (input: TInput) => TOutput; diff --git a/packages/frontend-plugin-api/src/apis/definitions/PluginWrapperApi.ts b/packages/frontend-plugin-api/src/apis/definitions/PluginWrapperApi.ts new file mode 100644 index 0000000000..b6c96dd760 --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/definitions/PluginWrapperApi.ts @@ -0,0 +1,41 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentType, ReactNode } from 'react'; +import { ApiRef, createApiRef } from '../system'; + +/** + * The Plugin Wrapper API allows plugins to wrap their extensions with providers. This API is only intended for internal use by the Backstage frontend system. To provide contexts to plugin components, use `ExtensionBoundary` instead. + * + * @public + */ +export type PluginWrapperApi = { + /** + * Returns a wrapper component for a specific plugin, or undefined if no wrappers exist. Do not use this API directly, instead use `ExtensionBoundary` to wrap your plugin components if needed. + */ + getPluginWrapper( + pluginId: string, + ): ComponentType<{ children: ReactNode }> | undefined; +}; + +/** + * The API reference of {@link PluginWrapperApi}. + * + * @public + */ +export const pluginWrapperApiRef: ApiRef = createApiRef({ + id: 'core.plugin-wrapper', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/index.ts b/packages/frontend-plugin-api/src/apis/definitions/index.ts index 5e7f713fcf..7b90d38092 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/index.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/index.ts @@ -49,3 +49,4 @@ export * from './RouteResolutionApi'; export * from './StorageApi'; export * from './AnalyticsApi'; export * from './TranslationApi'; +export * from './PluginWrapperApi'; diff --git a/packages/frontend-plugin-api/src/blueprints/PluginWrapperBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PluginWrapperBlueprint.tsx new file mode 100644 index 0000000000..a005342b31 --- /dev/null +++ b/packages/frontend-plugin-api/src/blueprints/PluginWrapperBlueprint.tsx @@ -0,0 +1,56 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentType, ReactNode } from 'react'; +import { + createExtensionBlueprint, + createExtensionBlueprintParams, + createExtensionDataRef, +} from '../wiring'; + +const wrapperDataRef = createExtensionDataRef< + () => Promise<{ component: ComponentType<{ children: ReactNode }> }> +>().with({ id: 'core.plugin-wrapper.loader' }); + +/** + * Creates extensions that wrap plugin extensions with providers. + * + * @public + */ +export const PluginWrapperBlueprint = createExtensionBlueprint({ + kind: 'plugin-wrapper', + attachTo: { id: 'api:app/plugin-wrapper', input: 'wrappers' }, + output: [wrapperDataRef], + dataRefs: { + wrapper: wrapperDataRef, + }, + defineParams(params: { + loader: () => Promise<{ + component: ComponentType<{ children: ReactNode }>; + }>; + }) { + return createExtensionBlueprintParams( + params as { + loader: () => Promise<{ + component: ComponentType<{ children: ReactNode }>; + }>; + }, + ); + }, + *factory(params) { + yield wrapperDataRef(params.loader); + }, +}); diff --git a/packages/frontend-plugin-api/src/blueprints/index.ts b/packages/frontend-plugin-api/src/blueprints/index.ts index 379c8d237b..1d7e6eb478 100644 --- a/packages/frontend-plugin-api/src/blueprints/index.ts +++ b/packages/frontend-plugin-api/src/blueprints/index.ts @@ -21,6 +21,7 @@ export { export { ApiBlueprint } from './ApiBlueprint'; export { AppRootElementBlueprint } from './AppRootElementBlueprint'; export { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint'; +export { PluginWrapperBlueprint } from './PluginWrapperBlueprint'; export { IconBundleBlueprint } from './IconBundleBlueprint'; export { NavContentBlueprint, diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx index 6e4487ed9b..fefa2dc521 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx @@ -19,6 +19,7 @@ import { ReactNode, Suspense, useEffect, + useMemo, lazy as reactLazy, } from 'react'; import { AnalyticsContext, useAnalytics } from '../analytics'; @@ -27,6 +28,10 @@ import { ErrorApiBoundary } from './ErrorApiBoundary'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker'; import { AppNode, ErrorApi, errorApiRef, useApi } from '../apis'; +import { + PluginWrapperApi, + pluginWrapperApiRef, +} from '../apis/definitions/PluginWrapperApi'; import { coreExtensionData } from '../wiring'; import { AppNodeProvider } from './AppNodeProvider'; import { Progress } from './DefaultSwappableComponents'; @@ -39,6 +44,13 @@ function useOptionalErrorApi(): ErrorApi | undefined { } } +function useOptionalPluginWrapperApi(): PluginWrapperApi | undefined { + try { + return useApi(pluginWrapperApiRef); + } catch { + return undefined; + } +} type RouteTrackerProps = PropsWithChildren<{ enabled?: boolean; }>; @@ -78,11 +90,18 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) { ); const plugin = node.spec.plugin; + const pluginId = plugin.id ?? 'app'; + + const pluginWrapperApi = useOptionalPluginWrapperApi(); + + const PluginWrapper = useMemo(() => { + return pluginWrapperApi?.getPluginWrapper(pluginId); + }, [pluginWrapperApi, pluginId]); // Skipping "routeRef" attribute in the new system, the extension "id" should provide more insight const attributes = { extensionId: node.spec.id, - pluginId: plugin.id ?? 'app', + pluginId, }; let content = ( @@ -91,6 +110,10 @@ export function ExtensionBoundary(props: ExtensionBoundaryProps) { ); + if (PluginWrapper) { + content = {content}; + } + if (props.errorPresentation === 'error-api') { content = ( diff --git a/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.test.tsx b/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.test.tsx new file mode 100644 index 0000000000..c723222f2f --- /dev/null +++ b/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.test.tsx @@ -0,0 +1,98 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render, screen } from '@testing-library/react'; +import { DefaultPluginWrapperApi } from './DefaultPluginWrapperApi'; + +describe('DefaultPluginWrapperApi', () => { + it('should wrap multiple components with a single wrapper', async () => { + const api = DefaultPluginWrapperApi.fromWrappers([ + { + loader: async () => ({ + component: ({ children }) => <>Wrapper({children}), + }), + pluginId: 'plugin-1', + }, + ]); + + const Wrapper1 = api.getPluginWrapper('plugin-1')!; + const Wrapper2 = api.getPluginWrapper('plugin-1')!; + const Wrapper3 = api.getPluginWrapper('plugin-1')!; + + expect(Wrapper1).toBeDefined(); + expect(Wrapper2).toBeDefined(); + expect(Wrapper3).toBeDefined(); + + render( + <> +
+ 1 +
+
+ 2 +
+
+ 3 +
+ , + ); + + await expect(screen.findByText('Wrapper(1)')).resolves.toBeInTheDocument(); + await expect(screen.findByText('Wrapper(2)')).resolves.toBeInTheDocument(); + await expect(screen.findByText('Wrapper(3)')).resolves.toBeInTheDocument(); + }); + + it('should wrap multiple components with multiple wrappers', async () => { + const api = DefaultPluginWrapperApi.fromWrappers([ + { + loader: async () => ({ + component: ({ children }) => <>WrapperA({children}), + }), + pluginId: 'plugin-1', + }, + { + loader: async () => ({ + component: ({ children }) => <>WrapperB({children}), + }), + pluginId: 'plugin-1', + }, + ]); + + const Wrapper1 = api.getPluginWrapper('plugin-1')!; + const Wrapper2 = api.getPluginWrapper('plugin-1')!; + + expect(Wrapper1).toBeDefined(); + expect(Wrapper2).toBeDefined(); + + render( + <> +
+ 1 +
+
+ 2 +
+ , + ); + + await expect( + screen.findByText('WrapperB(WrapperA(1))'), + ).resolves.toBeInTheDocument(); + await expect( + screen.findByText('WrapperB(WrapperA(2))'), + ).resolves.toBeInTheDocument(); + }); +}); diff --git a/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.tsx b/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.tsx new file mode 100644 index 0000000000..2bf2744f21 --- /dev/null +++ b/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.tsx @@ -0,0 +1,107 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PluginWrapperApi } from '@backstage/frontend-plugin-api'; +import { ComponentType, ReactNode, useEffect, useState } from 'react'; + +type WrapperInput = { + loader: () => Promise<{ component: ComponentType<{ children: ReactNode }> }>; + pluginId: string; +}; + +/** + * Default implementation of PluginWrapperApi. + * + * @internal + */ +export class DefaultPluginWrapperApi implements PluginWrapperApi { + constructor( + private readonly pluginWrappers: Map< + string, + ComponentType<{ children: ReactNode }> + >, + ) {} + + getPluginWrapper( + pluginId: string, + ): ComponentType<{ children: ReactNode }> | undefined { + return this.pluginWrappers.get(pluginId); + } + + static fromWrappers(wrappers: Array): DefaultPluginWrapperApi { + const loadersByPlugin = new Map< + string, + Array< + () => Promise<{ component: ComponentType<{ children: ReactNode }> }> + > + >(); + + for (const wrapper of wrappers) { + let loaders = loadersByPlugin.get(wrapper.pluginId); + if (!loaders) { + loaders = []; + loadersByPlugin.set(wrapper.pluginId, loaders); + } + loaders.push(wrapper.loader); + } + + const composedWrappers = new Map< + string, + ComponentType<{ children: ReactNode }> + >(); + + for (const [pluginId, loaders] of loadersByPlugin) { + if (loaders.length === 0) { + continue; + } + + const ComposedWrapper = (props: { children: ReactNode }) => { + const [loadedWrappers, setLoadedWrappers] = useState< + Array> | undefined + >(undefined); + const [error, setError] = useState(undefined); + + useEffect(() => { + Promise.all(loaders.map(loader => loader())) + .then(results => { + setLoadedWrappers(results.map(r => r.component)); + }) + .catch(setError); + }, []); + + if (error) { + throw error; + } + + if (!loadedWrappers) { + return null; + } + + let content = props.children; + + for (const Wrapper of loadedWrappers) { + content = {content}; + } + + return <>{content}; + }; + + composedWrappers.set(pluginId, ComposedWrapper); + } + + return new DefaultPluginWrapperApi(composedWrappers); + } +} diff --git a/plugins/app/src/apis/PluginWrapperApi/index.ts b/plugins/app/src/apis/PluginWrapperApi/index.ts new file mode 100644 index 0000000000..f11e40fdf0 --- /dev/null +++ b/plugins/app/src/apis/PluginWrapperApi/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { DefaultPluginWrapperApi } from './DefaultPluginWrapperApi'; diff --git a/plugins/app/src/extensions/PluginWrapperApi.ts b/plugins/app/src/extensions/PluginWrapperApi.ts new file mode 100644 index 0000000000..14e41891a0 --- /dev/null +++ b/plugins/app/src/extensions/PluginWrapperApi.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + PluginWrapperBlueprint, + createExtensionInput, + ApiBlueprint, + pluginWrapperApiRef, +} from '@backstage/frontend-plugin-api'; +import { DefaultPluginWrapperApi } from '../apis/PluginWrapperApi'; + +/** + * Contains the plugin wrappers installed into the app. + */ +export const PluginWrapperApi = ApiBlueprint.makeWithOverrides({ + name: 'plugin-wrapper', + inputs: { + wrappers: createExtensionInput([PluginWrapperBlueprint.dataRefs.wrapper]), + }, + factory: (originalFactory, { inputs }) => { + return originalFactory(defineParams => + defineParams({ + api: pluginWrapperApiRef, + deps: {}, + factory: () => { + return DefaultPluginWrapperApi.fromWrappers( + inputs.wrappers.map(wrapperInput => ({ + loader: wrapperInput.get(PluginWrapperBlueprint.dataRefs.wrapper), + pluginId: wrapperInput.node.spec.plugin.id ?? 'app', + })), + ); + }, + }), + ); + }, +}); diff --git a/plugins/app/src/extensions/index.ts b/plugins/app/src/extensions/index.ts index 0d3ad84e6f..17ac19ff4b 100644 --- a/plugins/app/src/extensions/index.ts +++ b/plugins/app/src/extensions/index.ts @@ -32,3 +32,4 @@ export { alertDisplayAppRootElement, } from './elements'; export { Progress, NotFoundErrorPage, ErrorDisplay } from './components'; +export { PluginWrapperApi } from './PluginWrapperApi'; From 0b494408a67f2d2f0c35149ab77c152018a92eff Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Jan 2026 12:17:21 +0100 Subject: [PATCH 162/202] added new app-react library with AppRootWrapperBlueprint replacement Signed-off-by: Patrik Oldsberg --- packages/frontend-plugin-api/report.api.md | 2 +- .../blueprints/AppRootWrapperBlueprint.tsx | 4 + plugins/app-react/report.api.md | 27 ++++ .../AppRootWrapperBlueprint.test.tsx | 138 ++++++++++++++++++ .../blueprints/AppRootWrapperBlueprint.tsx | 51 +++++++ plugins/app-react/src/blueprints/index.ts | 2 + plugins/app/report.api.md | 21 ++- plugins/app/src/extensions/AppRoot.tsx | 29 +++- 8 files changed, 265 insertions(+), 9 deletions(-) create mode 100644 plugins/app-react/src/blueprints/AppRootWrapperBlueprint.test.tsx create mode 100644 plugins/app-react/src/blueprints/AppRootWrapperBlueprint.tsx diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index 9115d0732b..fe19b724ce 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -271,7 +271,7 @@ export const AppRootElementBlueprint: ExtensionBlueprint_2<{ dataRefs: never; }>; -// @public +// @public @deprecated export const AppRootWrapperBlueprint: ExtensionBlueprint_2<{ kind: 'app-root-wrapper'; params: { diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx index 493fba05f6..62268bce77 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx @@ -27,6 +27,10 @@ const componentDataRef = createExtensionDataRef< * and similar. * * @public + * @deprecated Use {@link PluginWrapperBlueprint} instead if you want to wrap + * all plugin components in the same wrapper. If you want to wrap the entire + * app, use the `AppRootWrapperBlueprint` from `@backstage/plugin-app-react` + * instead, although note that only app modules are able to use that blueprint. */ export const AppRootWrapperBlueprint = createExtensionBlueprint({ kind: 'app-root-wrapper', diff --git a/plugins/app-react/report.api.md b/plugins/app-react/report.api.md index 48ec50749d..415ffe51bd 100644 --- a/plugins/app-react/report.api.md +++ b/plugins/app-react/report.api.md @@ -18,6 +18,33 @@ import { SwappableComponentRef } from '@backstage/frontend-plugin-api'; import { TranslationMessages } from '@backstage/frontend-plugin-api'; import { TranslationResource } from '@backstage/frontend-plugin-api'; +// @public +export const AppRootWrapperBlueprint: ExtensionBlueprint<{ + kind: 'app-root-wrapper'; + params: { + component: (props: { children: ReactNode }) => JSX.Element | null; + }; + output: ExtensionDataRef< + ComponentType<{ + children: ReactNode; + }>, + 'app.root-wrapper-component', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + component: ConfigurableExtensionDataRef< + ComponentType<{ + children: ReactNode; + }>, + 'app.root-wrapper-component', + {} + >; + }; +}>; + // @public export const IconBundleBlueprint: ExtensionBlueprint<{ kind: 'icon-bundle'; diff --git a/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.test.tsx b/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.test.tsx new file mode 100644 index 0000000000..32aaeddeba --- /dev/null +++ b/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.test.tsx @@ -0,0 +1,138 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Fragment } from 'react'; +import { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint'; +import { screen, waitFor } from '@testing-library/react'; +import { + coreExtensionData, + createExtension, + createExtensionInput, + createFrontendModule, +} from '@backstage/frontend-plugin-api'; +import { renderTestApp } from '@backstage/frontend-test-utils'; + +describe('AppRootWrapperBlueprint', () => { + it('should return an extension with sensible defaults', () => { + const extension = AppRootWrapperBlueprint.make({ + params: { + component: () =>
Hello
, + }, + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "T": undefined, + "attachTo": { + "id": "app/root", + "input": "wrappers", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "app-root-wrapper", + "name": undefined, + "output": [ + [Function], + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should render a simple component wrapper', async () => { + const extension = AppRootWrapperBlueprint.make({ + name: 'test', + params: { + component: () =>
Hello
, + }, + }); + + renderTestApp({ + features: [ + createFrontendModule({ + pluginId: 'app', + extensions: [extension], + }), + ], + }); + + await waitFor(() => expect(screen.getByText('Hello')).toBeInTheDocument()); + }); + + it('should render a complex component wrapper', async () => { + const extension = AppRootWrapperBlueprint.makeWithOverrides({ + config: { + schema: { + name: z => z.string(), + }, + }, + inputs: { + children: createExtensionInput([coreExtensionData.reactElement]), + }, + *factory(originalFactory, { inputs, config }) { + yield* originalFactory({ + component: ({ children }) => ( +
+ {inputs.children.flatMap((c, index) => ( + + {c.get(coreExtensionData.reactElement)} + + ))} + {children} +
+ ), + }); + }, + }); + + renderTestApp({ + features: [ + createFrontendModule({ + pluginId: 'app', + extensions: [ + extension, + createExtension({ + name: 'test-child', + attachTo: extension.inputs.children, + output: [coreExtensionData.reactElement], + factory: () => [ + coreExtensionData.reactElement(
Its Me
), + ], + }), + ], + }), + ], + config: { + app: { + extensions: [ + { + 'app-root-wrapper:app': { config: { name: 'Robin' } }, + }, + ], + }, + }, + }); + + await waitFor(() => { + expect(screen.getByTestId('Robin-1')).toBeInTheDocument(); + expect(screen.getByText('Its Me')).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.tsx b/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.tsx new file mode 100644 index 0000000000..79a35e2bc0 --- /dev/null +++ b/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.tsx @@ -0,0 +1,51 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentType, ReactNode } from 'react'; +import { + createExtensionBlueprint, + createExtensionDataRef, +} from '@backstage/frontend-plugin-api'; + +const componentDataRef = createExtensionDataRef< + ComponentType<{ children: ReactNode }> +>().with({ id: 'app.root-wrapper-component' }); + +/** + * Creates an extension that renders a React wrapper at the app root, enclosing + * the app layout. + * + * @remarks + * + * This is useful for example for adding global React contexts and similar. This + * extension is only available for use by app modules, plugins should use the + * {@link @backstage/frontend-plugin-api#PluginWrapperBlueprint} instead. + * + * @public + */ +export const AppRootWrapperBlueprint = createExtensionBlueprint({ + kind: 'app-root-wrapper', + attachTo: { id: 'app/root', input: 'wrappers' }, + output: [componentDataRef], + dataRefs: { + component: componentDataRef, + }, + *factory(params: { + component: (props: { children: ReactNode }) => JSX.Element | null; + }) { + yield componentDataRef(params.component); + }, +}); diff --git a/plugins/app-react/src/blueprints/index.ts b/plugins/app-react/src/blueprints/index.ts index 09d05b71ed..cf036aa060 100644 --- a/plugins/app-react/src/blueprints/index.ts +++ b/plugins/app-react/src/blueprints/index.ts @@ -27,6 +27,8 @@ import { TranslationBlueprint as _TranslationBlueprint, } from '@backstage/frontend-plugin-api'; +export { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint'; + /** * Creates an extension that adds/replaces an app theme. This blueprint is limited to use by the app plugin. * diff --git a/plugins/app/report.api.md b/plugins/app/report.api.md index ea34a1731f..57103b8000 100644 --- a/plugins/app/report.api.md +++ b/plugins/app/report.api.md @@ -149,11 +149,22 @@ const appPlugin: OverridableFrontendPlugin< } >; wrappers: ExtensionInput< - ConfigurableExtensionDataRef< - (props: { children: ReactNode }) => JSX.Element | null, - 'app.root.wrapper', - {} - >, + | ConfigurableExtensionDataRef< + (props: { children: ReactNode }) => JSX.Element | null, + 'app.root.wrapper', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + ComponentType<{ + children: ReactNode; + }>, + 'app.root-wrapper-component', + { + optional: true; + } + >, { singleton: false; optional: false; diff --git a/plugins/app/src/extensions/AppRoot.tsx b/plugins/app/src/extensions/AppRoot.tsx index 47ea50a641..e51c63ca28 100644 --- a/plugins/app/src/extensions/AppRoot.tsx +++ b/plugins/app/src/extensions/AppRoot.tsx @@ -22,7 +22,7 @@ import { JSX, } from 'react'; import { - AppRootWrapperBlueprint, + AppRootWrapperBlueprint as OldAppRootWrapperBlueprint, RouterBlueprint, SignInPageBlueprint, coreExtensionData, @@ -44,6 +44,7 @@ import { identityApiRef, useApi, } from '@backstage/core-plugin-api'; +import { AppRootWrapperBlueprint } from '@backstage/plugin-app-react'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { isProtectedApp } from '../../../../packages/core-app-api/src/app/isProtectedApp'; import { BrowserRouter } from 'react-router-dom'; @@ -69,7 +70,8 @@ export const AppRoot = createExtension({ }), elements: createExtensionInput([coreExtensionData.reactElement]), wrappers: createExtensionInput([ - AppRootWrapperBlueprint.dataRefs.component, + OldAppRootWrapperBlueprint.dataRefs.component.optional(), + AppRootWrapperBlueprint.dataRefs.component.optional(), ]), }, output: [coreExtensionData.reactElement], @@ -117,7 +119,28 @@ export const AppRoot = createExtension({ for (const wrapper of inputs.wrappers) { const Component = wrapper.get(AppRootWrapperBlueprint.dataRefs.component); - content = {content}; + const pluginId = wrapper.node.spec.plugin.id; + if (Component) { + if (pluginId === 'app') { + content = {content}; + } else { + // eslint-disable-next-line no-console + console.warn( + `Warning: The app root wrapper extension from the '${pluginId}' plugin was ignored, only the 'app' plugin is allowed to provide app root wrappers.`, + ); + } + } else { + const OldComponent = wrapper.get( + OldAppRootWrapperBlueprint.dataRefs.component, + ); + if (OldComponent) { + // eslint-disable-next-line no-console + console.warn( + `DEPRECATION WARNING: The ${pluginId} plugin provided a deprecated app root wrapper extension using the AppRootWrapperBlueprint from @backstage/frontend-plugin-api. This will break in a future release.`, + ); + content = {content}; + } + } } return [ From 4554a4e3550022ef2b6e3e0b750124ce29baa931 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Jan 2026 12:17:48 +0100 Subject: [PATCH 163/202] added changesets for app root wrapper changes Signed-off-by: Patrik Oldsberg --- .changeset/fine-ears-yell.md | 5 +++++ .changeset/moody-wasps-travel.md | 5 +++++ .changeset/violet-otters-share.md | 5 +++++ 3 files changed, 15 insertions(+) create mode 100644 .changeset/fine-ears-yell.md create mode 100644 .changeset/moody-wasps-travel.md create mode 100644 .changeset/violet-otters-share.md diff --git a/.changeset/fine-ears-yell.md b/.changeset/fine-ears-yell.md new file mode 100644 index 0000000000..a1c3e06d41 --- /dev/null +++ b/.changeset/fine-ears-yell.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-react': minor +--- + +Added `AppRootWrapperBlueprint` that replaces the blueprint with the same name that was deprecated in `@backstage/frontend-plugin-api`. These blueprints are not directly interchangeable the new one exported from this package can only be used in `app` plugin modules and is restricted from use in other plugins. diff --git a/.changeset/moody-wasps-travel.md b/.changeset/moody-wasps-travel.md new file mode 100644 index 0000000000..fdb46dcc2d --- /dev/null +++ b/.changeset/moody-wasps-travel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app': patch +--- + +Implemented support for the new `PluginWrapperBlueprint` from `@backstage/frontend-plugin-api` and `AppRootWrapperBlueprint` from the new `@backstage/plugin-app-react` library. diff --git a/.changeset/violet-otters-share.md b/.changeset/violet-otters-share.md new file mode 100644 index 0000000000..daed39f98c --- /dev/null +++ b/.changeset/violet-otters-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Added `PluginWrapperBlueprint`, which can install components that will wrap all plugin elements. The `AppRootWrapperBlueprint` has also been deprecated and should be replaced either with the new plugin wrapper, or for app overrides, the new blueprint with the same name from `@backstage/plugin-app-react`. From 872cecb902f114fa7c123f02a38662d79b051122 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Jan 2026 12:22:21 +0100 Subject: [PATCH 164/202] docs: updates for app and plugin wrapper blueprint changes Signed-off-by: Patrik Oldsberg --- docs/frontend-system/building-apps/08-migrating.md | 2 +- .../building-plugins/03-common-extension-blueprints.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index 26dc3d085c..3ac5109fc7 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -660,7 +660,7 @@ const convertedRootFeatures = convertLegacyAppRoot( ); ``` -Any app root wrapper needs to be migrated to be an extension, created using `AppRootWrapperBlueprint`. Note that if you have multiple wrappers they must be completely independent of each other, i.e. the order in which they the appear in the React tree should not matter. If that is not the case then you should group them into a single wrapper. +Any app root wrapper needs to be migrated to be an extension, created using `AppRootWrapperBlueprint` from `@backstage/plugin-app-react`. Note that if you have multiple wrappers they must be completely independent of each other, i.e. the order in which they the appear in the React tree should not matter. If that is not the case then you should group them into a single wrapper. Here is an example converting the `CustomAppBarrier` into extension: diff --git a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md index df83c696f6..9989191181 100644 --- a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md +++ b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md @@ -23,6 +23,12 @@ Navigation item extensions are used to provide menu items that link to different Page extensions provide content for a particular route in the app. By default pages are attached to the app routes extensions, which renders the root routes. +## Extension blueprints in `@backstage/frontend-plugin-api/alpha` + +### Plugin Wrapper - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.packages-frontend-plugin-api_src_alpha.PluginWrapperBlueprint.html) + +Plugin wrappers allow you to install components that will wrap all elements rendered as part of a plugin. This can be useful if you for example need to add a global provider for example for a query client. The provided wrapper will be rendered as separate elements for each wrapped plugin element, so be sure to use a central store like a [Utility API](../utility-apis/01-index.md) if you want to share state between wrapper instances. + ## Extension blueprints in `@backstage/plugin-app-react` ### SignInPage - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.SignInPageBlueprint.html) From 9d194418e79afb18d8734c9323b338efab717b16 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Jan 2026 18:43:24 +0100 Subject: [PATCH 165/202] frontend-plugin-api: add test for PluginWrapperApi in ExtensionBoundary Signed-off-by: Patrik Oldsberg --- .../src/components/ExtensionBoundary.test.tsx | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx index ae741f8922..c12cddcf43 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { useEffect } from 'react'; +import { useEffect, ReactNode } from 'react'; import { act, screen, waitFor } from '@testing-library/react'; import { TestApiProvider, withLogCollector } from '@backstage/test-utils'; import { ExtensionBoundary } from './ExtensionBoundary'; @@ -26,6 +26,10 @@ import { createExtensionTester, renderInTestApp, } from '@backstage/frontend-test-utils'; +import { + pluginWrapperApiRef, + PluginWrapperApi, +} from '../apis/definitions/PluginWrapperApi'; const wrapInBoundaryExtension = (element?: JSX.Element) => { const routeRef = createRouteRef(); @@ -121,6 +125,44 @@ describe('ExtensionBoundary', () => { }); }); + it('should wrap children with PluginWrapper when provided', async () => { + const text = 'Wrapped Content'; + const TextComponent = () => { + return

{text}

; + }; + + const WrapperComponent = ({ children }: { children: ReactNode }) => { + return ( +
+ Wrapper + {children} +
+ ); + }; + + const pluginWrapperApi: PluginWrapperApi = { + getPluginWrapper: jest.fn((pluginId: string) => { + if (pluginId === 'app') { + return WrapperComponent; + } + return undefined; + }), + }; + + renderInTestApp( + + {createExtensionTester( + wrapInBoundaryExtension(), + ).reactElement()} + , + ); + + expect(await screen.findByTestId('plugin-wrapper')).toBeInTheDocument(); + expect(screen.getByText('Wrapper')).toBeInTheDocument(); + expect(screen.getByText(text)).toBeInTheDocument(); + expect(pluginWrapperApi.getPluginWrapper).toHaveBeenCalledWith('app'); + }); + // TODO(Rugvip): Need a way to be able to override APIs in the app to be able to test this properly // eslint-disable-next-line jest/no-disabled-tests it.skip('should emit analytics events if routable', async () => { From c4c2419daac9a0288da5cc1b37c9f5dc9fe495cf Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 15 Jan 2026 12:56:26 +0100 Subject: [PATCH 166/202] review fixes Signed-off-by: Patrik Oldsberg --- .changeset/fine-ears-yell.md | 2 +- .../src/apis/definitions/PluginWrapperApi.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.changeset/fine-ears-yell.md b/.changeset/fine-ears-yell.md index a1c3e06d41..06c5df58c4 100644 --- a/.changeset/fine-ears-yell.md +++ b/.changeset/fine-ears-yell.md @@ -2,4 +2,4 @@ '@backstage/plugin-app-react': minor --- -Added `AppRootWrapperBlueprint` that replaces the blueprint with the same name that was deprecated in `@backstage/frontend-plugin-api`. These blueprints are not directly interchangeable the new one exported from this package can only be used in `app` plugin modules and is restricted from use in other plugins. +Added `AppRootWrapperBlueprint` that replaces the blueprint with the same name that was deprecated in `@backstage/frontend-plugin-api`. These blueprints are not directly interchangeable, the new one exported from this package can only be used in `app` plugin modules and is restricted from use in other plugins. diff --git a/packages/frontend-plugin-api/src/apis/definitions/PluginWrapperApi.ts b/packages/frontend-plugin-api/src/apis/definitions/PluginWrapperApi.ts index b6c96dd760..6a1f921282 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/PluginWrapperApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/PluginWrapperApi.ts @@ -18,13 +18,22 @@ import { ComponentType, ReactNode } from 'react'; import { ApiRef, createApiRef } from '../system'; /** - * The Plugin Wrapper API allows plugins to wrap their extensions with providers. This API is only intended for internal use by the Backstage frontend system. To provide contexts to plugin components, use `ExtensionBoundary` instead. + * The Plugin Wrapper API is used to wrap plugin extensions with providers, + * plugins should generally use `ExtensionBoundary` instead. + * + * @remarks + * + * This API is primarily intended for internal use by the Backstage frontend + * system, but can be used for advanced use-cases. If you do override it, be + * sure to include the default implementation as well. * * @public */ export type PluginWrapperApi = { /** - * Returns a wrapper component for a specific plugin, or undefined if no wrappers exist. Do not use this API directly, instead use `ExtensionBoundary` to wrap your plugin components if needed. + * Returns a wrapper component for a specific plugin, or undefined if no + * wrappers exist. Do not use this API directly, instead use + * `ExtensionBoundary` to wrap your plugin components if needed. */ getPluginWrapper( pluginId: string, From 7b7e547d469db3883e4ac3050588f477d8fa6960 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Jan 2026 16:36:30 +0100 Subject: [PATCH 167/202] frontend-plugin-api: add ExtensionBoundary test for throwing PluginWrapper Signed-off-by: Patrik Oldsberg --- .../src/components/ExtensionBoundary.test.tsx | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx index c12cddcf43..c0068e8f7d 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.test.tsx @@ -163,6 +163,47 @@ describe('ExtensionBoundary', () => { expect(pluginWrapperApi.getPluginWrapper).toHaveBeenCalledWith('app'); }); + it('should handle errors thrown by PluginWrapper with ErrorDisplayBoundary', async () => { + const errorMsg = 'PluginWrapper error'; + const TextComponent = () => { + return

Content

; + }; + + const ThrowingWrapper = () => { + throw new Error(errorMsg); + }; + + const pluginWrapperApi: PluginWrapperApi = { + getPluginWrapper: jest.fn((pluginId: string) => { + if (pluginId === 'app') { + return ThrowingWrapper; + } + return undefined; + }), + }; + + const { error } = await withLogCollector(['error'], async () => { + renderInTestApp( + + {createExtensionTester( + wrapInBoundaryExtension(), + ).reactElement()} + , + ); + await waitFor(() => + expect(screen.getByText(errorMsg)).toBeInTheDocument(), + ); + }); + + expect(error).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + message: expect.stringContaining(errorMsg), + }), + ]), + ); + }); + // TODO(Rugvip): Need a way to be able to override APIs in the app to be able to test this properly // eslint-disable-next-line jest/no-disabled-tests it.skip('should emit analytics events if routable', async () => { From d194acb0e3bee584de220306e1419c8b765f2050 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Jan 2026 16:56:47 +0100 Subject: [PATCH 168/202] frontend-plugin-api: move new PluginWrapper to alpha Signed-off-by: Patrik Oldsberg --- .changeset/violet-otters-share.md | 2 +- packages/frontend-plugin-api/package.json | 4 ++ .../frontend-plugin-api/report-alpha.api.md | 68 +++++++++++++++++++ packages/frontend-plugin-api/report.api.md | 53 --------------- packages/frontend-plugin-api/src/alpha.ts | 27 ++++++++ .../src/apis/definitions/PluginWrapperApi.ts | 10 +-- .../src/apis/definitions/index.ts | 1 - .../blueprints/AppRootWrapperBlueprint.tsx | 9 +-- .../src/blueprints/PluginWrapperBlueprint.tsx | 2 +- .../src/blueprints/index.ts | 1 - .../DefaultPluginWrapperApi.tsx | 2 +- .../app/src/extensions/PluginWrapperApi.ts | 4 +- 12 files changed, 115 insertions(+), 68 deletions(-) create mode 100644 packages/frontend-plugin-api/report-alpha.api.md create mode 100644 packages/frontend-plugin-api/src/alpha.ts diff --git a/.changeset/violet-otters-share.md b/.changeset/violet-otters-share.md index daed39f98c..e60ba5041d 100644 --- a/.changeset/violet-otters-share.md +++ b/.changeset/violet-otters-share.md @@ -2,4 +2,4 @@ '@backstage/frontend-plugin-api': patch --- -Added `PluginWrapperBlueprint`, which can install components that will wrap all plugin elements. The `AppRootWrapperBlueprint` has also been deprecated and should be replaced either with the new plugin wrapper, or for app overrides, the new blueprint with the same name from `@backstage/plugin-app-react`. +Added an alpha `PluginWrapperBlueprint` exported from `@backstage/frontend-plugin-api/alpha`, which can install components that will wrap all plugin elements. The `AppRootWrapperBlueprint` has also been deprecated and should be replaced either with the new plugin wrapper, or for app overrides, the new blueprint with the same name from `@backstage/plugin-app-react`. diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 358977bad4..5c82ef444d 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -16,12 +16,16 @@ "sideEffects": false, "exports": { ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", "./package.json": "./package.json" }, "main": "src/index.ts", "types": "src/index.ts", "typesVersions": { "*": { + "alpha": [ + "src/alpha.ts" + ], "package.json": [ "package.json" ] diff --git a/packages/frontend-plugin-api/report-alpha.api.md b/packages/frontend-plugin-api/report-alpha.api.md new file mode 100644 index 0000000000..77a377bb8e --- /dev/null +++ b/packages/frontend-plugin-api/report-alpha.api.md @@ -0,0 +1,68 @@ +## API Report File for "@backstage/frontend-plugin-api" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { ApiRef } from '@backstage/frontend-plugin-api'; +import { ComponentType } from 'react'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ReactNode } from 'react'; + +// @alpha +export type PluginWrapperApi = { + getPluginWrapper(pluginId: string): + | ComponentType<{ + children: ReactNode; + }> + | undefined; +}; + +// @alpha +export const pluginWrapperApiRef: ApiRef; + +// @alpha +export const PluginWrapperBlueprint: ExtensionBlueprint<{ + kind: 'plugin-wrapper'; + params: (params: { + loader: () => Promise<{ + component: ComponentType<{ + children: ReactNode; + }>; + }>; + }) => ExtensionBlueprintParams<{ + loader: () => Promise<{ + component: ComponentType<{ + children: ReactNode; + }>; + }>; + }>; + output: ExtensionDataRef< + () => Promise<{ + component: ComponentType<{ + children: ReactNode; + }>; + }>, + 'core.plugin-wrapper.loader', + {} + >; + inputs: {}; + config: {}; + configInput: {}; + dataRefs: { + wrapper: ConfigurableExtensionDataRef< + () => Promise<{ + component: ComponentType<{ + children: ReactNode; + }>; + }>, + 'core.plugin-wrapper.loader', + {} + >; + }; +}>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index fe19b724ce..1a7f2e3c7a 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -1868,59 +1868,6 @@ export interface PluginOptions< routes?: TRoutes; } -// @public -export type PluginWrapperApi = { - getPluginWrapper(pluginId: string): - | ComponentType<{ - children: ReactNode; - }> - | undefined; -}; - -// @public -export const pluginWrapperApiRef: ApiRef; - -// @public -export const PluginWrapperBlueprint: ExtensionBlueprint_2<{ - kind: 'plugin-wrapper'; - params: (params: { - loader: () => Promise<{ - component: ComponentType<{ - children: ReactNode; - }>; - }>; - }) => ExtensionBlueprintParams_2<{ - loader: () => Promise<{ - component: ComponentType<{ - children: ReactNode; - }>; - }>; - }>; - output: ExtensionDataRef_2< - () => Promise<{ - component: ComponentType<{ - children: ReactNode; - }>; - }>, - 'core.plugin-wrapper.loader', - {} - >; - inputs: {}; - config: {}; - configInput: {}; - dataRefs: { - wrapper: ConfigurableExtensionDataRef_2< - () => Promise<{ - component: ComponentType<{ - children: ReactNode; - }>; - }>, - 'core.plugin-wrapper.loader', - {} - >; - }; -}>; - // @public (undocumented) export type PortableSchema = { parse: (input: TInput) => TOutput; diff --git a/packages/frontend-plugin-api/src/alpha.ts b/packages/frontend-plugin-api/src/alpha.ts new file mode 100644 index 0000000000..9d73b8cc84 --- /dev/null +++ b/packages/frontend-plugin-api/src/alpha.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Alpha exports for plugin wrapper functionality. + * + * @alpha + */ + +export { PluginWrapperBlueprint } from './blueprints/PluginWrapperBlueprint'; +export { + type PluginWrapperApi, + pluginWrapperApiRef, +} from './apis/definitions/PluginWrapperApi'; diff --git a/packages/frontend-plugin-api/src/apis/definitions/PluginWrapperApi.ts b/packages/frontend-plugin-api/src/apis/definitions/PluginWrapperApi.ts index 6a1f921282..a4b66d6128 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/PluginWrapperApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/PluginWrapperApi.ts @@ -15,7 +15,7 @@ */ import { ComponentType, ReactNode } from 'react'; -import { ApiRef, createApiRef } from '../system'; +import { createApiRef } from '@backstage/frontend-plugin-api'; /** * The Plugin Wrapper API is used to wrap plugin extensions with providers, @@ -27,7 +27,7 @@ import { ApiRef, createApiRef } from '../system'; * system, but can be used for advanced use-cases. If you do override it, be * sure to include the default implementation as well. * - * @public + * @alpha */ export type PluginWrapperApi = { /** @@ -43,8 +43,8 @@ export type PluginWrapperApi = { /** * The API reference of {@link PluginWrapperApi}. * - * @public + * @alpha */ -export const pluginWrapperApiRef: ApiRef = createApiRef({ - id: 'core.plugin-wrapper', +export const pluginWrapperApiRef = createApiRef({ + id: 'core.plugin-wrapper.alpha', }); diff --git a/packages/frontend-plugin-api/src/apis/definitions/index.ts b/packages/frontend-plugin-api/src/apis/definitions/index.ts index 7b90d38092..5e7f713fcf 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/index.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/index.ts @@ -49,4 +49,3 @@ export * from './RouteResolutionApi'; export * from './StorageApi'; export * from './AnalyticsApi'; export * from './TranslationApi'; -export * from './PluginWrapperApi'; diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx index 62268bce77..0fbefa7d08 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx @@ -27,10 +27,11 @@ const componentDataRef = createExtensionDataRef< * and similar. * * @public - * @deprecated Use {@link PluginWrapperBlueprint} instead if you want to wrap - * all plugin components in the same wrapper. If you want to wrap the entire - * app, use the `AppRootWrapperBlueprint` from `@backstage/plugin-app-react` - * instead, although note that only app modules are able to use that blueprint. + * @deprecated Use `PluginWrapperBlueprint` from + * `@backstage/frontend-plugin-api/alpha` instead if you want to wrap all plugin + * components in the same wrapper. If you want to wrap the entire app, use the + * `AppRootWrapperBlueprint` from `@backstage/plugin-app-react` instead, + * although note that only app modules are able to use that blueprint. */ export const AppRootWrapperBlueprint = createExtensionBlueprint({ kind: 'app-root-wrapper', diff --git a/packages/frontend-plugin-api/src/blueprints/PluginWrapperBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PluginWrapperBlueprint.tsx index a005342b31..4bec4a4390 100644 --- a/packages/frontend-plugin-api/src/blueprints/PluginWrapperBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PluginWrapperBlueprint.tsx @@ -28,7 +28,7 @@ const wrapperDataRef = createExtensionDataRef< /** * Creates extensions that wrap plugin extensions with providers. * - * @public + * @alpha */ export const PluginWrapperBlueprint = createExtensionBlueprint({ kind: 'plugin-wrapper', diff --git a/packages/frontend-plugin-api/src/blueprints/index.ts b/packages/frontend-plugin-api/src/blueprints/index.ts index 1d7e6eb478..379c8d237b 100644 --- a/packages/frontend-plugin-api/src/blueprints/index.ts +++ b/packages/frontend-plugin-api/src/blueprints/index.ts @@ -21,7 +21,6 @@ export { export { ApiBlueprint } from './ApiBlueprint'; export { AppRootElementBlueprint } from './AppRootElementBlueprint'; export { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint'; -export { PluginWrapperBlueprint } from './PluginWrapperBlueprint'; export { IconBundleBlueprint } from './IconBundleBlueprint'; export { NavContentBlueprint, diff --git a/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.tsx b/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.tsx index 2bf2744f21..65c41ee2e2 100644 --- a/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.tsx +++ b/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { PluginWrapperApi } from '@backstage/frontend-plugin-api'; +import { PluginWrapperApi } from '@backstage/frontend-plugin-api/alpha'; import { ComponentType, ReactNode, useEffect, useState } from 'react'; type WrapperInput = { diff --git a/plugins/app/src/extensions/PluginWrapperApi.ts b/plugins/app/src/extensions/PluginWrapperApi.ts index 14e41891a0..e402f094f1 100644 --- a/plugins/app/src/extensions/PluginWrapperApi.ts +++ b/plugins/app/src/extensions/PluginWrapperApi.ts @@ -16,9 +16,11 @@ import { PluginWrapperBlueprint, + pluginWrapperApiRef, +} from '@backstage/frontend-plugin-api/alpha'; +import { createExtensionInput, ApiBlueprint, - pluginWrapperApiRef, } from '@backstage/frontend-plugin-api'; import { DefaultPluginWrapperApi } from '../apis/PluginWrapperApi'; From 2b16d8a674c15ddf40a4d7e92053cb30596814ba Mon Sep 17 00:00:00 2001 From: Riley Martine Date: Fri, 16 Jan 2026 09:07:53 -0700 Subject: [PATCH 169/202] docs: Update orphan strategy config to reflect new defaults In [v1.41](https://github.com/backstage/backstage/releases/tag/v1.41.0), the catalog's `orphanStrategy` and `orphanProviderStrategy` were changed to default to `delete`. This change updates the docs to reflect this. Signed-off-by: Riley Martine --- .../software-catalog/configuration.md | 18 +++++++++-------- .../software-catalog/life-of-an-entity.md | 20 ++++++------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/docs/features/software-catalog/configuration.md b/docs/features/software-catalog/configuration.md index 5b293cce1c..3ed027a0a4 100644 --- a/docs/features/software-catalog/configuration.md +++ b/docs/features/software-catalog/configuration.md @@ -181,28 +181,30 @@ this remote source, users cannot also register new entities with e.g. the [catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import) plugin. -## Clean up orphaned entities +## Automatic removal of orphaned entities -In short, entities can become orphaned through multiple means, such as when a catalog-info YAML file is moved from one place to another in the version control system without updating the registration in the catalog. For safety reasons, the default behavior is to just tag the orphaned entities, and keep them around. You can read more about orphaned entities [here](life-of-an-entity.md#orphaning). +Entities can become orphaned through multiple means, such as when a catalog-info YAML file is moved from one place to another in the version control system without updating the registration in the catalog. The default behavior is to automatically remove orphaned entities. You can read more about orphaned entities [here](life-of-an-entity.md#orphaning). -However, if you do wish to automatically remove the orphaned entities, you can use the following configuration, and everything with an orphaned entity tag will be eventually deleted. +However, if you wish to keep orphaned entities, you can use the following configuration, and automatic cleanup will be disabled. ```yaml catalog: - orphanStrategy: delete + orphanStrategy: keep ``` -## Clean up entities from orphaned entity providers +## Automatic removal of entities from orphaned entity providers -By default, if an entity provider which has provided entities to the catalog, is no longer configured, then the entities remain in the catalog until they are manually unregistered. +By default, if an entity provider which has provided entities to the catalog is no longer configured, then the entities it provided will be automatically removed. -To remove these entities automatically, you can use the following configuration. +To keep these entities instead, you can use the following configuration. ```yaml catalog: - orphanProviderStrategy: delete + orphanProviderStrategy: keep ``` +If you have had providers installed in the past that ingested entities into the catalog that you want to keep, the recommendation is to add the provider back to the catalog. If you don’t want the provider to run, you can schedule it with a very large interval. + ## Processing Interval The [processing loop](./life-of-an-entity.md#processing) is diff --git a/docs/features/software-catalog/life-of-an-entity.md b/docs/features/software-catalog/life-of-an-entity.md index c16fb8d523..ca4de99734 100644 --- a/docs/features/software-catalog/life-of-an-entity.md +++ b/docs/features/software-catalog/life-of-an-entity.md @@ -254,8 +254,8 @@ either, it becomes _orphaned_. The end result is as follows: the child entity. - The child entity is _not_ removed from the catalog, but stays around until explicitly deleted via the catalog API, implicitly if `orphanStrategy: delete` - configuration is set, or until it is "reclaimed" by the original parent - or another parent starting to reference it. + configuration is set (the default), or until it is "reclaimed" by the original + parent or another parent starting to reference it. - The catalog page in Backstage for the child entity detects the new annotation and informs users about the orphan status. @@ -282,21 +282,13 @@ Orphaning can occur in several different scenarios. > to inform the owner that something is wrong. But processing and other > behaviors continue as usual. -The reason that the orphaning mechanism exists instead of having an eager -deletion triggered, is safety. Scenarios like these can happen purely by -accident, due to the asynchronous nature of the system and the fallible nature -of humans. In particular when external systems start consuming and relying on -the catalog, there could be substantial consequences to suddenly dropping -entities without explicit owner consent. The catalog therefore takes the stance -that entities that often were added by direct user action should also be deleted -only by direct user action. - -However, if you want to delete orphaned entities automatically anyway, you can -enable the automated clean up with the following app-config option. +The default behavior of the catalog is to automatically remove orphaned +entities. However, if you want to keep them instead, you can disable the +automated cleanup with the following app-config option. ``` catalog: - orphanStrategy: delete + orphanStrategy: keep ``` ## Implicit Deletion From 3799135578694b957ca2395f1f471fec68749445 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 17 Jan 2026 01:09:55 +0100 Subject: [PATCH 170/202] Update docs/frontend-system/building-plugins/03-common-extension-blueprints.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Patrik Oldsberg --- .../building-plugins/03-common-extension-blueprints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md index 9989191181..aff66f3cc6 100644 --- a/docs/frontend-system/building-plugins/03-common-extension-blueprints.md +++ b/docs/frontend-system/building-plugins/03-common-extension-blueprints.md @@ -27,7 +27,7 @@ Page extensions provide content for a particular route in the app. By default pa ### Plugin Wrapper - [Reference](https://backstage.io/api/stable/variables/_backstage_frontend-plugin-api.packages-frontend-plugin-api_src_alpha.PluginWrapperBlueprint.html) -Plugin wrappers allow you to install components that will wrap all elements rendered as part of a plugin. This can be useful if you for example need to add a global provider for example for a query client. The provided wrapper will be rendered as separate elements for each wrapped plugin element, so be sure to use a central store like a [Utility API](../utility-apis/01-index.md) if you want to share state between wrapper instances. +Plugin wrappers allow you to install components that will wrap all elements rendered as part of a plugin. This can be useful if you need to add a global provider, for example for a query client. The provided wrapper will be rendered as separate elements for each wrapped plugin element, so be sure to use a central store like a [Utility API](../utility-apis/01-index.md) if you want to share state between wrapper instances. ## Extension blueprints in `@backstage/plugin-app-react` From 6b0dc4528a6d2800338a4b95c45967ca3403a33a Mon Sep 17 00:00:00 2001 From: whiffy Date: Sat, 17 Jan 2026 18:56:14 -0800 Subject: [PATCH 171/202] fix(logger-level): changed logger level of user photo loading to debug Signed-off-by: whiffy --- .../catalog-backend-module-msgraph/src/microsoftGraph/read.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts index 8bc7e6899b..7202f70384 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts @@ -511,7 +511,7 @@ async function transformUsers( ); } } catch (e) { - logger.warn(`Unable to load user photo for`, { + logger.debug(`Unable to load user photo for`, { user: user.id, error: e, }); From 115b37864ba30b8a189cc59638487741248b04af Mon Sep 17 00:00:00 2001 From: whiffy Date: Sat, 17 Jan 2026 19:06:21 -0800 Subject: [PATCH 172/202] added changeset Signed-off-by: whiffy --- .changeset/chilly-geese-carry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/chilly-geese-carry.md diff --git a/.changeset/chilly-geese-carry.md b/.changeset/chilly-geese-carry.md new file mode 100644 index 0000000000..5d19251360 --- /dev/null +++ b/.changeset/chilly-geese-carry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Changed the logger level from 'warning' to 'debug' when we are unable to load the user's photo. From 140cbc2a2035a2abe99b6d39ee09ff6dc4599134 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Jan 2026 10:42:10 +0100 Subject: [PATCH 173/202] cli: add missing backend-test-utils deps to templates Signed-off-by: Patrik Oldsberg --- .changeset/quiet-cherries-count.md | 5 +++++ packages/cli/templates/node-library/package.json.hbs | 1 + packages/cli/templates/plugin-node-library/package.json.hbs | 1 + .../cli/templates/scaffolder-backend-module/package.json.hbs | 1 + 4 files changed, 8 insertions(+) create mode 100644 .changeset/quiet-cherries-count.md diff --git a/.changeset/quiet-cherries-count.md b/.changeset/quiet-cherries-count.md new file mode 100644 index 0000000000..857bae6e24 --- /dev/null +++ b/.changeset/quiet-cherries-count.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added `@backstage/backend-test-utils` to backend package templates. diff --git a/packages/cli/templates/node-library/package.json.hbs b/packages/cli/templates/node-library/package.json.hbs index 941bf309fd..5b292e3e23 100644 --- a/packages/cli/templates/node-library/package.json.hbs +++ b/packages/cli/templates/node-library/package.json.hbs @@ -20,6 +20,7 @@ "postpack": "backstage-cli package postpack" }, "devDependencies": { + "@backstage/backend-test-utils": "{{versionQuery '@backstage/backend-test-utils'}}", "@backstage/cli": "{{versionQuery '@backstage/cli'}}" }, "files": [ diff --git a/packages/cli/templates/plugin-node-library/package.json.hbs b/packages/cli/templates/plugin-node-library/package.json.hbs index 876d6414ac..bcd064edcc 100644 --- a/packages/cli/templates/plugin-node-library/package.json.hbs +++ b/packages/cli/templates/plugin-node-library/package.json.hbs @@ -21,6 +21,7 @@ "postpack": "backstage-cli package postpack" }, "devDependencies": { + "@backstage/backend-test-utils": "{{versionQuery '@backstage/backend-test-utils'}}", "@backstage/cli": "{{versionQuery '@backstage/cli'}}" }, "files": [ diff --git a/packages/cli/templates/scaffolder-backend-module/package.json.hbs b/packages/cli/templates/scaffolder-backend-module/package.json.hbs index 36c99c7687..72cb370443 100644 --- a/packages/cli/templates/scaffolder-backend-module/package.json.hbs +++ b/packages/cli/templates/scaffolder-backend-module/package.json.hbs @@ -26,6 +26,7 @@ "@backstage/plugin-scaffolder-node": "{{versionQuery '@backstage/plugin-scaffolder-node'}}" }, "devDependencies": { + "@backstage/backend-test-utils": "{{versionQuery '@backstage/backend-test-utils'}}", "@backstage/cli": "{{versionQuery '@backstage/cli'}}", "@backstage/plugin-scaffolder-node-test-utils": "{{versionQuery '@backstage/plugin-scaffolder-node-test-utils'}}" }, From 85c8156984e5ab75c8670da33a2aff75c92b196a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 18 Jan 2026 13:58:01 +0100 Subject: [PATCH 174/202] frontend-plugin-api: simplify deprecation of AppRootWrapperBlueprint Signed-off-by: Patrik Oldsberg --- .changeset/common-heads-start.md | 1 + .changeset/fine-ears-yell.md | 5 - .changeset/moody-wasps-travel.md | 2 +- .changeset/slick-beans-relax.md | 1 + .changeset/violet-otters-share.md | 2 +- .changeset/warm-pets-accept.md | 1 + .../blueprints/AppRootWrapperBlueprint.tsx | 8 +- plugins/app-react/report.api.md | 31 ++-- .../AppRootWrapperBlueprint.test.tsx | 138 ------------------ .../blueprints/AppRootWrapperBlueprint.tsx | 51 ------- plugins/app-react/src/blueprints/index.ts | 9 +- plugins/app/report.api.md | 21 +-- plugins/app/src/extensions/AppRoot.tsx | 25 +--- 13 files changed, 48 insertions(+), 247 deletions(-) delete mode 100644 .changeset/fine-ears-yell.md delete mode 100644 plugins/app-react/src/blueprints/AppRootWrapperBlueprint.test.tsx delete mode 100644 plugins/app-react/src/blueprints/AppRootWrapperBlueprint.tsx diff --git a/.changeset/common-heads-start.md b/.changeset/common-heads-start.md index 15ef218b12..2b0d5c473d 100644 --- a/.changeset/common-heads-start.md +++ b/.changeset/common-heads-start.md @@ -4,6 +4,7 @@ Moved the following blueprints from `@backstage/frontend-plugin-api`: +- `AppRootWrapperBlueprint` - `IconBundleBlueprint` - `NavContentBlueprint` - `RouterBlueprint` diff --git a/.changeset/fine-ears-yell.md b/.changeset/fine-ears-yell.md deleted file mode 100644 index 06c5df58c4..0000000000 --- a/.changeset/fine-ears-yell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-app-react': minor ---- - -Added `AppRootWrapperBlueprint` that replaces the blueprint with the same name that was deprecated in `@backstage/frontend-plugin-api`. These blueprints are not directly interchangeable, the new one exported from this package can only be used in `app` plugin modules and is restricted from use in other plugins. diff --git a/.changeset/moody-wasps-travel.md b/.changeset/moody-wasps-travel.md index fdb46dcc2d..30a526345d 100644 --- a/.changeset/moody-wasps-travel.md +++ b/.changeset/moody-wasps-travel.md @@ -2,4 +2,4 @@ '@backstage/plugin-app': patch --- -Implemented support for the new `PluginWrapperBlueprint` from `@backstage/frontend-plugin-api` and `AppRootWrapperBlueprint` from the new `@backstage/plugin-app-react` library. +Implemented support for the new `PluginWrapperBlueprint` from `@backstage/frontend-plugin-api/alpha`. diff --git a/.changeset/slick-beans-relax.md b/.changeset/slick-beans-relax.md index b4fc3c3175..1bc6443d80 100644 --- a/.changeset/slick-beans-relax.md +++ b/.changeset/slick-beans-relax.md @@ -4,6 +4,7 @@ The following blueprints are being restricted to only be used in app plugin overrides and modules. They are being moved to the `@backstage/plugin-app-react` package and have been deprecated: +- `AppRootWrapperBlueprint` - `IconBundleBlueprint` - `NavContentBlueprint` - `RouterBlueprint` diff --git a/.changeset/violet-otters-share.md b/.changeset/violet-otters-share.md index e60ba5041d..85f0aaf028 100644 --- a/.changeset/violet-otters-share.md +++ b/.changeset/violet-otters-share.md @@ -2,4 +2,4 @@ '@backstage/frontend-plugin-api': patch --- -Added an alpha `PluginWrapperBlueprint` exported from `@backstage/frontend-plugin-api/alpha`, which can install components that will wrap all plugin elements. The `AppRootWrapperBlueprint` has also been deprecated and should be replaced either with the new plugin wrapper, or for app overrides, the new blueprint with the same name from `@backstage/plugin-app-react`. +Added an alpha `PluginWrapperBlueprint` exported from `@backstage/frontend-plugin-api/alpha`, which can install components that will wrap all plugin elements. diff --git a/.changeset/warm-pets-accept.md b/.changeset/warm-pets-accept.md index 5bf62cbb1a..5fc7bcd6d3 100644 --- a/.changeset/warm-pets-accept.md +++ b/.changeset/warm-pets-accept.md @@ -4,6 +4,7 @@ The following blueprints are being restricted to only be used in app plugin overrides and modules. They will now produce a deprecation warning when used outside of the app plugin: +- `AppRootWrapperBlueprint` - `IconBundleBlueprint` - `NavContentBlueprint` - `RouterBlueprint` diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx index 0fbefa7d08..184a8c3327 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.tsx @@ -27,11 +27,9 @@ const componentDataRef = createExtensionDataRef< * and similar. * * @public - * @deprecated Use `PluginWrapperBlueprint` from - * `@backstage/frontend-plugin-api/alpha` instead if you want to wrap all plugin - * components in the same wrapper. If you want to wrap the entire app, use the - * `AppRootWrapperBlueprint` from `@backstage/plugin-app-react` instead, - * although note that only app modules are able to use that blueprint. + * @deprecated Use {@link @backstage/plugin-app-react#AppRootWrapperBlueprint} instead. + * If you were using this blueprint to provide a context for your plugin, + * use `PluginWrapperBlueprint` from `@backstage/frontend-plugin-api/alpha` instead. */ export const AppRootWrapperBlueprint = createExtensionBlueprint({ kind: 'app-root-wrapper', diff --git a/plugins/app-react/report.api.md b/plugins/app-react/report.api.md index 415ffe51bd..251152a9e6 100644 --- a/plugins/app-react/report.api.md +++ b/plugins/app-react/report.api.md @@ -22,13 +22,12 @@ import { TranslationResource } from '@backstage/frontend-plugin-api'; export const AppRootWrapperBlueprint: ExtensionBlueprint<{ kind: 'app-root-wrapper'; params: { + Component?: [error: 'Use the `component` parameter instead']; component: (props: { children: ReactNode }) => JSX.Element | null; }; output: ExtensionDataRef< - ComponentType<{ - children: ReactNode; - }>, - 'app.root-wrapper-component', + (props: { children: ReactNode }) => JSX.Element | null, + 'app.root.wrapper', {} >; inputs: {}; @@ -36,10 +35,8 @@ export const AppRootWrapperBlueprint: ExtensionBlueprint<{ configInput: {}; dataRefs: { component: ConfigurableExtensionDataRef< - ComponentType<{ - children: ReactNode; - }>, - 'app.root-wrapper-component', + (props: { children: ReactNode }) => JSX.Element | null, + 'app.root.wrapper', {} >; }; @@ -163,7 +160,14 @@ export const SwappableComponentBlueprint: ExtensionBlueprint<{ loader: Ref extends SwappableComponentRef ? | (() => (props: IInnerComponentProps) => JSX.Element | null) - | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) + | (() => Promise< + (props: IInnerComponentProps) => JSX.Element + /** + * Creates an extension that replaces the router component. This blueprint is limited to use by the app plugin. + * + * @public + */ | null + >) : never; }) => ExtensionBlueprintParams<{ component: Ref extends SwappableComponentRef< @@ -177,7 +181,14 @@ export const SwappableComponentBlueprint: ExtensionBlueprint<{ loader: Ref extends SwappableComponentRef ? | (() => (props: IInnerComponentProps) => JSX.Element | null) - | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) + | (() => Promise< + (props: IInnerComponentProps) => JSX.Element + /** + * Creates an extension that replaces the router component. This blueprint is limited to use by the app plugin. + * + * @public + */ | null + >) : never; }>; output: ExtensionDataRef< diff --git a/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.test.tsx b/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.test.tsx deleted file mode 100644 index 32aaeddeba..0000000000 --- a/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.test.tsx +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { Fragment } from 'react'; -import { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint'; -import { screen, waitFor } from '@testing-library/react'; -import { - coreExtensionData, - createExtension, - createExtensionInput, - createFrontendModule, -} from '@backstage/frontend-plugin-api'; -import { renderTestApp } from '@backstage/frontend-test-utils'; - -describe('AppRootWrapperBlueprint', () => { - it('should return an extension with sensible defaults', () => { - const extension = AppRootWrapperBlueprint.make({ - params: { - component: () =>
Hello
, - }, - }); - - expect(extension).toMatchInlineSnapshot(` - { - "$$type": "@backstage/ExtensionDefinition", - "T": undefined, - "attachTo": { - "id": "app/root", - "input": "wrappers", - }, - "configSchema": undefined, - "disabled": false, - "factory": [Function], - "inputs": {}, - "kind": "app-root-wrapper", - "name": undefined, - "output": [ - [Function], - ], - "override": [Function], - "toString": [Function], - "version": "v2", - } - `); - }); - - it('should render a simple component wrapper', async () => { - const extension = AppRootWrapperBlueprint.make({ - name: 'test', - params: { - component: () =>
Hello
, - }, - }); - - renderTestApp({ - features: [ - createFrontendModule({ - pluginId: 'app', - extensions: [extension], - }), - ], - }); - - await waitFor(() => expect(screen.getByText('Hello')).toBeInTheDocument()); - }); - - it('should render a complex component wrapper', async () => { - const extension = AppRootWrapperBlueprint.makeWithOverrides({ - config: { - schema: { - name: z => z.string(), - }, - }, - inputs: { - children: createExtensionInput([coreExtensionData.reactElement]), - }, - *factory(originalFactory, { inputs, config }) { - yield* originalFactory({ - component: ({ children }) => ( -
- {inputs.children.flatMap((c, index) => ( - - {c.get(coreExtensionData.reactElement)} - - ))} - {children} -
- ), - }); - }, - }); - - renderTestApp({ - features: [ - createFrontendModule({ - pluginId: 'app', - extensions: [ - extension, - createExtension({ - name: 'test-child', - attachTo: extension.inputs.children, - output: [coreExtensionData.reactElement], - factory: () => [ - coreExtensionData.reactElement(
Its Me
), - ], - }), - ], - }), - ], - config: { - app: { - extensions: [ - { - 'app-root-wrapper:app': { config: { name: 'Robin' } }, - }, - ], - }, - }, - }); - - await waitFor(() => { - expect(screen.getByTestId('Robin-1')).toBeInTheDocument(); - expect(screen.getByText('Its Me')).toBeInTheDocument(); - }); - }); -}); diff --git a/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.tsx b/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.tsx deleted file mode 100644 index 79a35e2bc0..0000000000 --- a/plugins/app-react/src/blueprints/AppRootWrapperBlueprint.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ComponentType, ReactNode } from 'react'; -import { - createExtensionBlueprint, - createExtensionDataRef, -} from '@backstage/frontend-plugin-api'; - -const componentDataRef = createExtensionDataRef< - ComponentType<{ children: ReactNode }> ->().with({ id: 'app.root-wrapper-component' }); - -/** - * Creates an extension that renders a React wrapper at the app root, enclosing - * the app layout. - * - * @remarks - * - * This is useful for example for adding global React contexts and similar. This - * extension is only available for use by app modules, plugins should use the - * {@link @backstage/frontend-plugin-api#PluginWrapperBlueprint} instead. - * - * @public - */ -export const AppRootWrapperBlueprint = createExtensionBlueprint({ - kind: 'app-root-wrapper', - attachTo: { id: 'app/root', input: 'wrappers' }, - output: [componentDataRef], - dataRefs: { - component: componentDataRef, - }, - *factory(params: { - component: (props: { children: ReactNode }) => JSX.Element | null; - }) { - yield componentDataRef(params.component); - }, -}); diff --git a/plugins/app-react/src/blueprints/index.ts b/plugins/app-react/src/blueprints/index.ts index cf036aa060..f5e4d7ac4a 100644 --- a/plugins/app-react/src/blueprints/index.ts +++ b/plugins/app-react/src/blueprints/index.ts @@ -15,6 +15,7 @@ */ import { + AppRootWrapperBlueprint as _AppRootWrapperBlueprint, IconBundleBlueprint as _IconBundleBlueprint, NavContentBlueprint as _NavContentBlueprint, type NavContentComponent, @@ -27,7 +28,13 @@ import { TranslationBlueprint as _TranslationBlueprint, } from '@backstage/frontend-plugin-api'; -export { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint'; +/** + * Creates an extension that renders a React wrapper at the app root, enclosing + * the app layout. This blueprint is limited to use by the app plugin. + * + * @public + */ +export const AppRootWrapperBlueprint = _AppRootWrapperBlueprint; /** * Creates an extension that adds/replaces an app theme. This blueprint is limited to use by the app plugin. diff --git a/plugins/app/report.api.md b/plugins/app/report.api.md index 57103b8000..ea34a1731f 100644 --- a/plugins/app/report.api.md +++ b/plugins/app/report.api.md @@ -149,22 +149,11 @@ const appPlugin: OverridableFrontendPlugin< } >; wrappers: ExtensionInput< - | ConfigurableExtensionDataRef< - (props: { children: ReactNode }) => JSX.Element | null, - 'app.root.wrapper', - { - optional: true; - } - > - | ConfigurableExtensionDataRef< - ComponentType<{ - children: ReactNode; - }>, - 'app.root-wrapper-component', - { - optional: true; - } - >, + ConfigurableExtensionDataRef< + (props: { children: ReactNode }) => JSX.Element | null, + 'app.root.wrapper', + {} + >, { singleton: false; optional: false; diff --git a/plugins/app/src/extensions/AppRoot.tsx b/plugins/app/src/extensions/AppRoot.tsx index e51c63ca28..c9f3035e5f 100644 --- a/plugins/app/src/extensions/AppRoot.tsx +++ b/plugins/app/src/extensions/AppRoot.tsx @@ -22,7 +22,7 @@ import { JSX, } from 'react'; import { - AppRootWrapperBlueprint as OldAppRootWrapperBlueprint, + AppRootWrapperBlueprint, RouterBlueprint, SignInPageBlueprint, coreExtensionData, @@ -44,7 +44,6 @@ import { identityApiRef, useApi, } from '@backstage/core-plugin-api'; -import { AppRootWrapperBlueprint } from '@backstage/plugin-app-react'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { isProtectedApp } from '../../../../packages/core-app-api/src/app/isProtectedApp'; import { BrowserRouter } from 'react-router-dom'; @@ -70,8 +69,7 @@ export const AppRoot = createExtension({ }), elements: createExtensionInput([coreExtensionData.reactElement]), wrappers: createExtensionInput([ - OldAppRootWrapperBlueprint.dataRefs.component.optional(), - AppRootWrapperBlueprint.dataRefs.component.optional(), + AppRootWrapperBlueprint.dataRefs.component, ]), }, output: [coreExtensionData.reactElement], @@ -121,25 +119,14 @@ export const AppRoot = createExtension({ const Component = wrapper.get(AppRootWrapperBlueprint.dataRefs.component); const pluginId = wrapper.node.spec.plugin.id; if (Component) { - if (pluginId === 'app') { - content = {content}; - } else { + content = {content}; + if (pluginId !== 'app') { // eslint-disable-next-line no-console console.warn( - `Warning: The app root wrapper extension from the '${pluginId}' plugin was ignored, only the 'app' plugin is allowed to provide app root wrappers.`, + `DEPRECATION WARNING: AppRootWrappers should only be installed as an extension in the app plugin. ` + + `You can either use appPlugin.override(), or a module for the app plugin. The following extension will be ignored in the future: ${wrapper.node.spec.id}`, ); } - } else { - const OldComponent = wrapper.get( - OldAppRootWrapperBlueprint.dataRefs.component, - ); - if (OldComponent) { - // eslint-disable-next-line no-console - console.warn( - `DEPRECATION WARNING: The ${pluginId} plugin provided a deprecated app root wrapper extension using the AppRootWrapperBlueprint from @backstage/frontend-plugin-api. This will break in a future release.`, - ); - content = {content}; - } } } From f9d575635408d1af263b7b9d320147d1b3db30b5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Jan 2026 13:48:54 +0100 Subject: [PATCH 175/202] app: add missing plugin wrapper API Signed-off-by: Patrik Oldsberg --- .../frontend-defaults/src/createApp.test.tsx | 1 + plugins/app/report.api.md | 31 +++++++++++++++++++ plugins/app/src/plugin.ts | 2 ++ 3 files changed, 34 insertions(+) diff --git a/packages/frontend-defaults/src/createApp.test.tsx b/packages/frontend-defaults/src/createApp.test.tsx index 7b5e345e12..c0928e7815 100644 --- a/packages/frontend-defaults/src/createApp.test.tsx +++ b/packages/frontend-defaults/src/createApp.test.tsx @@ -392,6 +392,7 @@ describe('createApp', () => { + ] diff --git a/plugins/app/report.api.md b/plugins/app/report.api.md index ea34a1731f..c5ee77ef01 100644 --- a/plugins/app/report.api.md +++ b/plugins/app/report.api.md @@ -574,6 +574,37 @@ const appPlugin: OverridableFrontendPlugin< params: ApiFactory, ) => ExtensionBlueprintParams; }>; + 'api:app/plugin-wrapper': OverridableExtensionDefinition<{ + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: { + wrappers: ExtensionInput< + ConfigurableExtensionDataRef< + () => Promise<{ + component: ComponentType<{ + children: ReactNode; + }>; + }>, + 'core.plugin-wrapper.loader', + {} + >, + { + singleton: false; + optional: false; + } + >; + }; + kind: 'api'; + name: 'plugin-wrapper'; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; + }>; 'api:app/scm-auth': OverridableExtensionDefinition<{ kind: 'api'; name: 'scm-auth'; diff --git a/plugins/app/src/plugin.ts b/plugins/app/src/plugin.ts index c9deddb4f9..ec0c627f47 100644 --- a/plugins/app/src/plugin.ts +++ b/plugins/app/src/plugin.ts @@ -28,6 +28,7 @@ import { SwappableComponentsApi, IconsApi, FeatureFlagsApi, + PluginWrapperApi, TranslationsApi, oauthRequestDialogAppRootElement, alertDisplayAppRootElement, @@ -58,6 +59,7 @@ export const appPlugin = createFrontendPlugin({ SwappableComponentsApi, IconsApi, FeatureFlagsApi, + PluginWrapperApi, TranslationsApi, DefaultSignInPage, oauthRequestDialogAppRootElement, From 768b588203e47c264eb953a809ecbdb143ae600c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 19 Jan 2026 14:43:15 +0100 Subject: [PATCH 176/202] frontend-plugin-api: remove redundant cast Signed-off-by: Patrik Oldsberg --- .../src/blueprints/PluginWrapperBlueprint.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/frontend-plugin-api/src/blueprints/PluginWrapperBlueprint.tsx b/packages/frontend-plugin-api/src/blueprints/PluginWrapperBlueprint.tsx index 4bec4a4390..f6f673bd93 100644 --- a/packages/frontend-plugin-api/src/blueprints/PluginWrapperBlueprint.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PluginWrapperBlueprint.tsx @@ -42,13 +42,7 @@ export const PluginWrapperBlueprint = createExtensionBlueprint({ component: ComponentType<{ children: ReactNode }>; }>; }) { - return createExtensionBlueprintParams( - params as { - loader: () => Promise<{ - component: ComponentType<{ children: ReactNode }>; - }>; - }, - ); + return createExtensionBlueprintParams(params); }, *factory(params) { yield wrapperDataRef(params.loader); From a6cd70a5b25275d073e5aa5c013a831c7d973cc8 Mon Sep 17 00:00:00 2001 From: Renas Date: Fri, 9 Jan 2026 16:38:37 +0100 Subject: [PATCH 177/202] feat: add --include to backstage-cli info Signed-off-by: Renas --- packages/cli/cli-report.md | 1 + .../cli/src/modules/info/commands/info.ts | 136 +++++++++++++++++- packages/cli/src/modules/info/index.ts | 15 +- 3 files changed, 143 insertions(+), 9 deletions(-) diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index a8df5242ad..fafd8c4f08 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -149,6 +149,7 @@ Usage: Options: --help + --include --version ``` diff --git a/packages/cli/src/modules/info/commands/info.ts b/packages/cli/src/modules/info/commands/info.ts index 97090d8b9a..8843dd6091 100644 --- a/packages/cli/src/modules/info/commands/info.ts +++ b/packages/cli/src/modules/info/commands/info.ts @@ -19,9 +19,43 @@ import os from 'os'; import { runOutput } from '@backstage/cli-common'; import { paths } from '../../../lib/paths'; import { Lockfile } from '../../../lib/versioning'; +import { BackstagePackageJson, PackageGraph } from '@backstage/cli-node'; +import { minimatch } from 'minimatch'; import fs from 'fs-extra'; -export default async () => { +interface InfoOptions { + include: string[]; +} + +/** + * Attempts to read package.json from node_modules for a given package name. + * Returns undefined if the package.json cannot be read. + */ +function tryReadPackageJson( + packageName: string, + targetPath: string, +): BackstagePackageJson | undefined { + try { + return require(require.resolve(`${packageName}/package.json`, { + paths: [targetPath], + })); + } catch { + return undefined; + } +} + +/** + * Checks if a package has a backstage field in its package.json + */ +function hasBackstageField( + packageName: string, + targetPath: string, +): boolean { + const pkg = tryReadPackageJson(packageName, targetPath); + return pkg?.backstage !== undefined; +} + +export default async (options: InfoOptions) => { await new Promise(async () => { const yarnVersion = await runOutput(['yarn', '--version']); const isLocal = fs.existsSync(paths.resolveOwn('./src')); @@ -44,16 +78,104 @@ export default async () => { console.log(`cli: ${cliVersion} (${isLocal ? 'local' : 'installed'})`); console.log(`backstage: ${backstageVersion}`); console.log(); - console.log('Dependencies:'); + const lockfilePath = paths.resolveTargetRoot('yarn.lock'); const lockfile = await Lockfile.load(lockfilePath); + const targetPath = paths.targetRoot; - const deps = [...lockfile.keys()].filter(n => n.startsWith('@backstage/')); - const maxLength = Math.max(...deps.map(d => d.length)); + // Get workspace package names and their versions + const workspacePackages = new Map(); + try { + const packages = await PackageGraph.listTargetPackages(); + for (const pkg of packages) { + workspacePackages.set(pkg.packageJson.name, pkg.packageJson.version); + } + } catch { + // If we can't list workspace packages, continue without them + } - for (const dep of deps) { - const versions = new Set(lockfile.get(dep)!.map(i => i.version)); - console.log(` ${dep.padEnd(maxLength)} ${[...versions].join(', ')}`); + // Collect all package names from lockfile + const allPackages = [...lockfile.keys()]; + const includePatterns = options.include || []; + + // Collect installed (non-local) packages + const installedDeps = new Set(); + // Collect local workspace packages + const localDeps = new Set(); + + // Process @backstage/* packages + for (const pkg of allPackages) { + if (pkg.startsWith('@backstage/')) { + if (workspacePackages.has(pkg)) { + localDeps.add(pkg); + } else { + installedDeps.add(pkg); + } + } + } + + // Process packages matching --include patterns + for (const pattern of includePatterns) { + for (const pkg of allPackages) { + if (minimatch(pkg, pattern)) { + if (workspacePackages.has(pkg)) { + localDeps.add(pkg); + } else { + installedDeps.add(pkg); + } + } + } + } + + // Process packages with backstage field in their package.json + for (const pkg of allPackages) { + // Skip @backstage/* packages (already processed above) + if (pkg.startsWith('@backstage/')) { + continue; + } + if (workspacePackages.has(pkg)) { + // Check if local package has backstage field + if (hasBackstageField(pkg, targetPath)) { + localDeps.add(pkg); + } + } else if (hasBackstageField(pkg, targetPath)) { + installedDeps.add(pkg); + } + } + + // Helper to get version string for a package + const getVersions = (dep: string): string => { + const entries = lockfile.get(dep); + if (!entries) { + return 'unknown'; + } + const versions = [...new Set(entries.map(i => i.version))]; + return versions.join(', '); + }; + + // Print installed dependencies + console.log('Dependencies:'); + const sortedInstalled = [...installedDeps].sort(); + if (sortedInstalled.length > 0) { + const maxLength = Math.max(...sortedInstalled.map(d => d.length)); + for (const dep of sortedInstalled) { + const versions = getVersions(dep); + console.log(` ${dep.padEnd(maxLength)} ${versions}`); + } + } else { + console.log(' (no installed Backstage packages found)'); + } + + // Print local workspace packages + if (localDeps.size > 0) { + console.log(); + console.log('Local:'); + const sortedLocal = [...localDeps].sort(); + const maxLength = Math.max(...sortedLocal.map(d => d.length)); + for (const dep of sortedLocal) { + const version = workspacePackages.get(dep) ?? 'unknown'; + console.log(` ${dep.padEnd(maxLength)} ${version}`); + } } }); }; diff --git a/packages/cli/src/modules/info/index.ts b/packages/cli/src/modules/info/index.ts index e93f008905..9ec4347763 100644 --- a/packages/cli/src/modules/info/index.ts +++ b/packages/cli/src/modules/info/index.ts @@ -24,8 +24,19 @@ export default createCliPlugin({ path: ['info'], description: 'Show helpful information for debugging and reporting bugs', execute: async ({ args }) => { - yargs().parse(args); - await lazy(() => import('./commands/info'), 'default')(args); + const argv = await yargs() + .options({ + include: { + type: 'string', + array: true, + default: [], + description: + 'Glob patterns for additional packages to include (e.g., @spotify/backstage*)', + }, + }) + .help() + .parse(args); + await lazy(() => import('./commands/info'), 'default')(argv); }, }); }, From fd4439a8b865410a79a4d718805b2dd674fb7ccd Mon Sep 17 00:00:00 2001 From: Renas Date: Fri, 9 Jan 2026 16:47:05 +0100 Subject: [PATCH 178/202] feat: add --output-file option to backstage-cli info for JSON export Signed-off-by: Renas --- packages/cli/cli-report.md | 22 ++++++++ .../cli/src/modules/info/commands/info.ts | 54 +++++++++++++++---- packages/cli/src/modules/info/index.ts | 5 ++ 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index fafd8c4f08..6a750f2d7e 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -150,9 +150,31 @@ Usage: Options: --help --include + --output-file --version ``` +#### Examples + +Include additional packages using glob patterns: + +```bash +backstage-cli info --include "@spotify/*" +backstage-cli info --include "@internal/*" --include "@myorg/backstage-*" +``` + +Export the info output to a JSON file: + +```bash +backstage-cli info --output-file backstage-info.json +``` + +Combine both options: + +```bash +backstage-cli info --include "@spotify/*" --output-file backstage-info.json +``` + ### `backstage-cli migrate` ``` diff --git a/packages/cli/src/modules/info/commands/info.ts b/packages/cli/src/modules/info/commands/info.ts index 8843dd6091..998f9f1000 100644 --- a/packages/cli/src/modules/info/commands/info.ts +++ b/packages/cli/src/modules/info/commands/info.ts @@ -25,6 +25,7 @@ import fs from 'fs-extra'; interface InfoOptions { include: string[]; + outputFile?: string; } /** @@ -67,17 +68,23 @@ export default async (options: InfoOptions) => { const backstageJson = await fs.readJSON(backstageFile); backstageVersion = backstageJson.version ?? 'N/A'; } catch (error) { - console.warn('The "backstage.json" file is not in the expected format'); - console.log(); + if (!options.outputFile) { + console.warn( + 'The "backstage.json" file is not in the expected format', + ); + console.log(); + } } } - console.log(`OS: ${os.type} ${os.release} - ${os.platform}/${os.arch}`); - console.log(`node: ${process.version}`); - console.log(`yarn: ${yarnVersion}`); - console.log(`cli: ${cliVersion} (${isLocal ? 'local' : 'installed'})`); - console.log(`backstage: ${backstageVersion}`); - console.log(); + // Build system info + const systemInfo = { + os: `${os.type} ${os.release} - ${os.platform}/${os.arch}`, + node: process.version, + yarn: yarnVersion, + cli: { version: cliVersion, local: isLocal }, + backstage: backstageVersion, + }; const lockfilePath = paths.resolveTargetRoot('yarn.lock'); const lockfile = await Lockfile.load(lockfilePath); @@ -153,9 +160,37 @@ export default async (options: InfoOptions) => { return versions.join(', '); }; + const sortedInstalled = [...installedDeps].sort(); + const sortedLocal = [...localDeps].sort(); + + // If outputFile is specified, write JSON to file + if (options.outputFile) { + const output = { + system: systemInfo, + dependencies: Object.fromEntries( + sortedInstalled.map(dep => [dep, getVersions(dep)]), + ), + local: Object.fromEntries( + sortedLocal.map(dep => [dep, workspacePackages.get(dep) ?? 'unknown']), + ), + }; + + const outputPath = paths.resolveTargetRoot(options.outputFile); + await fs.writeFile(outputPath, JSON.stringify(output, null, 2)); + console.log(`Info exported to ${outputPath}`); + return; + } + + // Print to console + console.log(`OS: ${systemInfo.os}`); + console.log(`node: ${systemInfo.node}`); + console.log(`yarn: ${systemInfo.yarn}`); + console.log(`cli: ${cliVersion} (${isLocal ? 'local' : 'installed'})`); + console.log(`backstage: ${backstageVersion}`); + console.log(); + // Print installed dependencies console.log('Dependencies:'); - const sortedInstalled = [...installedDeps].sort(); if (sortedInstalled.length > 0) { const maxLength = Math.max(...sortedInstalled.map(d => d.length)); for (const dep of sortedInstalled) { @@ -170,7 +205,6 @@ export default async (options: InfoOptions) => { if (localDeps.size > 0) { console.log(); console.log('Local:'); - const sortedLocal = [...localDeps].sort(); const maxLength = Math.max(...sortedLocal.map(d => d.length)); for (const dep of sortedLocal) { const version = workspacePackages.get(dep) ?? 'unknown'; diff --git a/packages/cli/src/modules/info/index.ts b/packages/cli/src/modules/info/index.ts index 9ec4347763..df858cf72d 100644 --- a/packages/cli/src/modules/info/index.ts +++ b/packages/cli/src/modules/info/index.ts @@ -33,6 +33,11 @@ export default createCliPlugin({ description: 'Glob patterns for additional packages to include (e.g., @spotify/backstage*)', }, + 'output-file': { + type: 'string', + description: + 'Write the info output to a JSON file instead of stdout', + }, }) .help() .parse(args); From c0d7bf6e67b2c041ed9b34b10ddc0958b23dedea Mon Sep 17 00:00:00 2001 From: Renas Date: Fri, 9 Jan 2026 16:52:09 +0100 Subject: [PATCH 179/202] chore: add changeset Signed-off-by: Renas --- .changeset/fruity-tires-fail.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fruity-tires-fail.md diff --git a/.changeset/fruity-tires-fail.md b/.changeset/fruity-tires-fail.md new file mode 100644 index 0000000000..65d3ff26e8 --- /dev/null +++ b/.changeset/fruity-tires-fail.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added `--include` and `--output-file` options to `backstage-cli info` command for including additional packages via glob patterns and exporting output to JSON. From 563035ffe81994bec6c9fe05ac0a9894a8922cf9 Mon Sep 17 00:00:00 2001 From: Renas Date: Fri, 9 Jan 2026 17:48:13 +0100 Subject: [PATCH 180/202] chore: run prettier Signed-off-by: Renas --- packages/cli/src/modules/info/commands/info.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/modules/info/commands/info.ts b/packages/cli/src/modules/info/commands/info.ts index 998f9f1000..94d6c00250 100644 --- a/packages/cli/src/modules/info/commands/info.ts +++ b/packages/cli/src/modules/info/commands/info.ts @@ -48,10 +48,7 @@ function tryReadPackageJson( /** * Checks if a package has a backstage field in its package.json */ -function hasBackstageField( - packageName: string, - targetPath: string, -): boolean { +function hasBackstageField(packageName: string, targetPath: string): boolean { const pkg = tryReadPackageJson(packageName, targetPath); return pkg?.backstage !== undefined; } @@ -171,7 +168,10 @@ export default async (options: InfoOptions) => { sortedInstalled.map(dep => [dep, getVersions(dep)]), ), local: Object.fromEntries( - sortedLocal.map(dep => [dep, workspacePackages.get(dep) ?? 'unknown']), + sortedLocal.map(dep => [ + dep, + workspacePackages.get(dep) ?? 'unknown', + ]), ), }; From 9aa8fd9c848cf8897cc0c15a1afaaaf5585102d9 Mon Sep 17 00:00:00 2001 From: Renas Date: Fri, 9 Jan 2026 17:51:39 +0100 Subject: [PATCH 181/202] chore: revert changes to cli-report Signed-off-by: Renas --- packages/cli/cli-report.md | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index 6a750f2d7e..a8df5242ad 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -149,32 +149,9 @@ Usage: Options: --help - --include - --output-file --version ``` -#### Examples - -Include additional packages using glob patterns: - -```bash -backstage-cli info --include "@spotify/*" -backstage-cli info --include "@internal/*" --include "@myorg/backstage-*" -``` - -Export the info output to a JSON file: - -```bash -backstage-cli info --output-file backstage-info.json -``` - -Combine both options: - -```bash -backstage-cli info --include "@spotify/*" --output-file backstage-info.json -``` - ### `backstage-cli migrate` ``` From f59cb84d3482231f501398c86e53947d3658b5d8 Mon Sep 17 00:00:00 2001 From: Renas Date: Fri, 9 Jan 2026 17:56:01 +0100 Subject: [PATCH 182/202] chore: build api reports Signed-off-by: Renas --- packages/cli/cli-report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index a8df5242ad..7dd3de73ca 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -149,6 +149,8 @@ Usage: Options: --help + --include + --output-file --version ``` From cc8e5f52b53e4a495f2fd9dc32432bff87edb359 Mon Sep 17 00:00:00 2001 From: Renas Date: Fri, 9 Jan 2026 18:00:10 +0100 Subject: [PATCH 183/202] docs: update cli info docs Signed-off-by: Renas --- docs/tooling/cli/03-commands.md | 63 +++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/docs/tooling/cli/03-commands.md b/docs/tooling/cli/03-commands.md index ce8f862c98..3ab2c678d2 100644 --- a/docs/tooling/cli/03-commands.md +++ b/docs/tooling/cli/03-commands.md @@ -432,8 +432,67 @@ Usage: backstage-cli create-github-app Outputs debug information which is useful when opening an issue. Outputs system information, node.js and npm versions, CLI version and type (inside backstage -repo or a created app), all `@backstage/*` package dependency versions. +repo or a created app), all `@backstage/*` package dependency versions, and any +packages that contain a `backstage` field in their `package.json`. + +The command distinguishes between installed packages (from npm) and local +workspace packages, making it easier to understand your Backstage setup. ```text -Usage: backstage-cli info +Usage: backstage-cli info [options] + +Options: + --include Glob patterns for additional packages to include + (e.g., @mycompany/backstage-*) + --output-file Write the info output to a JSON file instead of stdout + -h, --help display help for command +``` + +### Examples + +Output debug information to the console: + +```bash +yarn backstage-cli info +``` + +Include additional packages matching a glob pattern: + +```bash +yarn backstage-cli info --include "@mycompany/*" +``` + +Export information to a JSON file for further processing: + +```bash +yarn backstage-cli info --output-file backstage-info.json +``` + +Combine options to include custom packages and export to JSON: + +```bash +yarn backstage-cli info --include "@mycompany/backstage-*" --include "@internal/*" --output-file debug-info.json +``` + +### JSON Output Format + +When using `--output-file`, the output is structured as follows: + +```json +{ + "system": { + "os": "Darwin 23.0.0 - darwin/arm64", + "node": "v18.17.0", + "yarn": "3.6.0", + "cli": { "version": "0.27.0", "local": false }, + "backstage": "1.20.0" + }, + "dependencies": { + "@backstage/core-plugin-api": "1.8.0", + "@backstage/plugin-catalog": "1.15.0" + }, + "local": { + "@mycompany/backstage-plugin-custom": "0.1.0" + } +} ``` From 4399328c37a5830854987eb86e05db57e1424ba4 Mon Sep 17 00:00:00 2001 From: Renas Date: Mon, 12 Jan 2026 14:26:51 +0100 Subject: [PATCH 184/202] feat: replace --output-file with --format Signed-off-by: Renas --- .changeset/fruity-tires-fail.md | 2 +- docs/tooling/cli/03-commands.md | 22 ++++++++++++++----- packages/cli/cli-report.md | 2 +- .../cli/src/modules/info/commands/info.ts | 12 +++++----- packages/cli/src/modules/info/index.ts | 7 +++--- 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/.changeset/fruity-tires-fail.md b/.changeset/fruity-tires-fail.md index 65d3ff26e8..a0afd0ed7b 100644 --- a/.changeset/fruity-tires-fail.md +++ b/.changeset/fruity-tires-fail.md @@ -2,4 +2,4 @@ '@backstage/cli': patch --- -Added `--include` and `--output-file` options to `backstage-cli info` command for including additional packages via glob patterns and exporting output to JSON. +Added `--include` and `--format` options to `backstage-cli info` command for including additional packages via glob patterns and outputting as JSON or Text. diff --git a/docs/tooling/cli/03-commands.md b/docs/tooling/cli/03-commands.md index 3ab2c678d2..07f6dee879 100644 --- a/docs/tooling/cli/03-commands.md +++ b/docs/tooling/cli/03-commands.md @@ -444,7 +444,7 @@ Usage: backstage-cli info [options] Options: --include Glob patterns for additional packages to include (e.g., @mycompany/backstage-*) - --output-file Write the info output to a JSON file instead of stdout + --format Output format (default: text) -h, --help display help for command ``` @@ -462,21 +462,33 @@ Include additional packages matching a glob pattern: yarn backstage-cli info --include "@mycompany/*" ``` -Export information to a JSON file for further processing: +Output as JSON: ```bash -yarn backstage-cli info --output-file backstage-info.json +yarn backstage-cli info --format json +``` + +Export JSON to a file for further processing: + +```bash +yarn backstage-cli info --format json > backstage-info.json ``` Combine options to include custom packages and export to JSON: ```bash -yarn backstage-cli info --include "@mycompany/backstage-*" --include "@internal/*" --output-file debug-info.json +yarn backstage-cli info --include "@mycompany/backstage-*" --include "@internal/*" --format json > debug-info.json +``` + +Export text output to a file: + +```bash +yarn backstage-cli info --format text > backstage-info.txt ``` ### JSON Output Format -When using `--output-file`, the output is structured as follows: +When using `--format json`, the output is structured as follows: ```json { diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index 7dd3de73ca..f3e077b37e 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -148,9 +148,9 @@ Options: Usage: Options: + --format --help --include - --output-file --version ``` diff --git a/packages/cli/src/modules/info/commands/info.ts b/packages/cli/src/modules/info/commands/info.ts index 94d6c00250..4e2bda29a7 100644 --- a/packages/cli/src/modules/info/commands/info.ts +++ b/packages/cli/src/modules/info/commands/info.ts @@ -25,7 +25,7 @@ import fs from 'fs-extra'; interface InfoOptions { include: string[]; - outputFile?: string; + format: 'text' | 'json'; } /** @@ -65,7 +65,7 @@ export default async (options: InfoOptions) => { const backstageJson = await fs.readJSON(backstageFile); backstageVersion = backstageJson.version ?? 'N/A'; } catch (error) { - if (!options.outputFile) { + if (options.format !== 'json') { console.warn( 'The "backstage.json" file is not in the expected format', ); @@ -160,8 +160,8 @@ export default async (options: InfoOptions) => { const sortedInstalled = [...installedDeps].sort(); const sortedLocal = [...localDeps].sort(); - // If outputFile is specified, write JSON to file - if (options.outputFile) { + // If format is json, output JSON to stdout + if (options.format === 'json') { const output = { system: systemInfo, dependencies: Object.fromEntries( @@ -175,9 +175,7 @@ export default async (options: InfoOptions) => { ), }; - const outputPath = paths.resolveTargetRoot(options.outputFile); - await fs.writeFile(outputPath, JSON.stringify(output, null, 2)); - console.log(`Info exported to ${outputPath}`); + process.stdout.write(`${JSON.stringify(output, null, 2)}\n`); return; } diff --git a/packages/cli/src/modules/info/index.ts b/packages/cli/src/modules/info/index.ts index df858cf72d..c14926df85 100644 --- a/packages/cli/src/modules/info/index.ts +++ b/packages/cli/src/modules/info/index.ts @@ -33,10 +33,11 @@ export default createCliPlugin({ description: 'Glob patterns for additional packages to include (e.g., @spotify/backstage*)', }, - 'output-file': { + format: { type: 'string', - description: - 'Write the info output to a JSON file instead of stdout', + choices: ['text', 'json'], + default: 'text', + description: 'Output format (text or json)', }, }) .help() From 863444a504fda4098259f2db0ed0c2c85bfda34f Mon Sep 17 00:00:00 2001 From: Renas Date: Mon, 19 Jan 2026 15:35:25 +0100 Subject: [PATCH 185/202] feat: change JSON format for dependency versions to array of objects Change the info command's JSON output to use an array of objects for dependency versions instead of comma-separated strings, making the format more parseable and extensible. Co-Authored-By: Claude Opus 4.5 Signed-off-by: Renas --- packages/cli/src/modules/info/commands/info.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/modules/info/commands/info.ts b/packages/cli/src/modules/info/commands/info.ts index 4e2bda29a7..6970699f13 100644 --- a/packages/cli/src/modules/info/commands/info.ts +++ b/packages/cli/src/modules/info/commands/info.ts @@ -147,14 +147,14 @@ export default async (options: InfoOptions) => { } } - // Helper to get version string for a package - const getVersions = (dep: string): string => { + // Helper to get versions for a package as an array of objects + const getVersions = (dep: string): Array<{ version: string }> => { const entries = lockfile.get(dep); if (!entries) { - return 'unknown'; + return [{ version: 'unknown' }]; } const versions = [...new Set(entries.map(i => i.version))]; - return versions.join(', '); + return versions.map(v => ({ version: v })); }; const sortedInstalled = [...installedDeps].sort(); @@ -170,7 +170,7 @@ export default async (options: InfoOptions) => { local: Object.fromEntries( sortedLocal.map(dep => [ dep, - workspacePackages.get(dep) ?? 'unknown', + [{ version: workspacePackages.get(dep) ?? 'unknown' }], ]), ), }; @@ -192,7 +192,9 @@ export default async (options: InfoOptions) => { if (sortedInstalled.length > 0) { const maxLength = Math.max(...sortedInstalled.map(d => d.length)); for (const dep of sortedInstalled) { - const versions = getVersions(dep); + const versions = getVersions(dep) + .map(v => v.version) + .join(', '); console.log(` ${dep.padEnd(maxLength)} ${versions}`); } } else { From cfac8a460dde1f6ce66bae8c0cdc0291b8be4dd0 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 19 Jan 2026 15:24:16 +0100 Subject: [PATCH 186/202] fix(ui): add missing cell styles to table selection column The selection cell in multi-select tables was missing the base cell styles, causing border styling to be incomplete. This adds the cell class alongside the cellSelection class to ensure proper styling. Signed-off-by: Johan Persson --- .changeset/stupid-words-fail.md | 7 +++++++ packages/ui/src/components/Table/components/Row.tsx | 1 + 2 files changed, 8 insertions(+) create mode 100644 .changeset/stupid-words-fail.md diff --git a/.changeset/stupid-words-fail.md b/.changeset/stupid-words-fail.md new file mode 100644 index 0000000000..a16b651f1c --- /dev/null +++ b/.changeset/stupid-words-fail.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed missing border styles on table selection cells in multi-select mode. + +Affected components: Table diff --git a/packages/ui/src/components/Table/components/Row.tsx b/packages/ui/src/components/Table/components/Row.tsx index e08649c63b..6f7a4ce108 100644 --- a/packages/ui/src/components/Table/components/Row.tsx +++ b/packages/ui/src/components/Table/components/Row.tsx @@ -47,6 +47,7 @@ export function Row(props: RowProps) { From d2fddedd3e98395ebfe44abbc1f7f7575af3c7db Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 19 Jan 2026 16:39:08 +0100 Subject: [PATCH 187/202] ui: add indeterminate state to Checkbox component Add support for the indeterminate state in the Checkbox component, displaying a horizontal dash icon instead of a checkmark. This is useful for "select all" scenarios in tables where only some rows are selected. The indeterminate state maintains the unchecked visual style (white background with border) while showing the dash icon. Signed-off-by: Johan Persson --- .changeset/brown-grapes-fry.md | 7 ++++++ packages/ui/report.api.md | 1 + .../components/Checkbox/Checkbox.module.css | 9 ++++++- .../components/Checkbox/Checkbox.stories.tsx | 11 ++++++++ .../ui/src/components/Checkbox/Checkbox.tsx | 25 +++++++++++++------ .../ui/src/components/Checkbox/definition.ts | 1 + 6 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 .changeset/brown-grapes-fry.md diff --git a/.changeset/brown-grapes-fry.md b/.changeset/brown-grapes-fry.md new file mode 100644 index 0000000000..c2fd1fbe2a --- /dev/null +++ b/.changeset/brown-grapes-fry.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Added indeterminate state support to the Checkbox component for handling partial selection scenarios like table header checkboxes. + +Affected components: Checkbox diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index ca9d2bcff1..fa50111cde 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -460,6 +460,7 @@ export const CheckboxDefinition: { }; readonly dataAttributes: { readonly selected: readonly [true, false]; + readonly indeterminate: readonly [true, false]; }; }; diff --git a/packages/ui/src/components/Checkbox/Checkbox.module.css b/packages/ui/src/components/Checkbox/Checkbox.module.css index 23a88d2b80..a4647c935a 100644 --- a/packages/ui/src/components/Checkbox/Checkbox.module.css +++ b/packages/ui/src/components/Checkbox/Checkbox.module.css @@ -62,7 +62,14 @@ color: var(--bui-fg-solid); } - .bui-Checkbox[data-hovered]:not([data-selected]) & { + .bui-Checkbox[data-indeterminate] & { + background-color: var(--bui-bg-surface-1); + box-shadow: inset 0 0 0 1px var(--bui-border); + color: var(--bui-fg-primary); + } + + .bui-Checkbox[data-hovered]:not([data-selected]):not([data-indeterminate]) + & { box-shadow: inset 0 0 0 1px var(--bui-border-hover); } diff --git a/packages/ui/src/components/Checkbox/Checkbox.stories.tsx b/packages/ui/src/components/Checkbox/Checkbox.stories.tsx index c5318cc6fb..e8f7859785 100644 --- a/packages/ui/src/components/Checkbox/Checkbox.stories.tsx +++ b/packages/ui/src/components/Checkbox/Checkbox.stories.tsx @@ -28,16 +28,27 @@ export const Default = meta.story({ }, }); +export const Indeterminate = meta.story({ + args: { + children: 'Select all', + isIndeterminate: true, + }, +}); + export const AllVariants = meta.story({ ...Default.input, render: () => ( Unchecked Checked + Indeterminate Disabled Checked & Disabled + + Indeterminate & Disabled + ), }); diff --git a/packages/ui/src/components/Checkbox/Checkbox.tsx b/packages/ui/src/components/Checkbox/Checkbox.tsx index 97a603daf2..6030fbc06b 100644 --- a/packages/ui/src/components/Checkbox/Checkbox.tsx +++ b/packages/ui/src/components/Checkbox/Checkbox.tsx @@ -21,7 +21,7 @@ import { useStyles } from '../../hooks/useStyles'; import { CheckboxDefinition } from './definition'; import clsx from 'clsx'; import styles from './Checkbox.module.css'; -import { RiCheckLine } from '@remixicon/react'; +import { RiCheckLine, RiSubtractLine } from '@remixicon/react'; /** @public */ export const Checkbox = forwardRef( @@ -35,12 +35,23 @@ export const Checkbox = forwardRef( className={clsx(classNames.root, styles[classNames.root], className)} {...rest} > -
- -
- {children} + {({ isIndeterminate }) => ( + <> +
+ {isIndeterminate ? ( + + ) : ( + + )} +
+ {children} + + )} ); }, diff --git a/packages/ui/src/components/Checkbox/definition.ts b/packages/ui/src/components/Checkbox/definition.ts index 022f2a0af1..c7b5b16276 100644 --- a/packages/ui/src/components/Checkbox/definition.ts +++ b/packages/ui/src/components/Checkbox/definition.ts @@ -27,5 +27,6 @@ export const CheckboxDefinition = { }, dataAttributes: { selected: [true, false] as const, + indeterminate: [true, false] as const, }, } as const satisfies ComponentDefinition; From f0951aaff08e8b70ae0df8e3563a2e7eece6df2c Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Mon, 19 Jan 2026 15:09:49 -0600 Subject: [PATCH 188/202] TechDocs - Updated the `defaultDockerImage` version Signed-off-by: Andre Wanlin --- .changeset/sour-kings-allow.md | 5 +++++ plugins/techdocs-node/src/stages/generate/techdocs.ts | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .changeset/sour-kings-allow.md diff --git a/.changeset/sour-kings-allow.md b/.changeset/sour-kings-allow.md new file mode 100644 index 0000000000..23870f42bb --- /dev/null +++ b/.changeset/sour-kings-allow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-node': patch +--- + +Updated the `defaultDockerImage` to reflect the latest TechDocs Container version of v1.2.8 diff --git a/plugins/techdocs-node/src/stages/generate/techdocs.ts b/plugins/techdocs-node/src/stages/generate/techdocs.ts index 1722bda838..5f60979180 100644 --- a/plugins/techdocs-node/src/stages/generate/techdocs.ts +++ b/plugins/techdocs-node/src/stages/generate/techdocs.ts @@ -53,8 +53,10 @@ export class TechdocsGenerator implements GeneratorBase { /** * The default docker image (and version) used to generate content. Public * and static so that techdocs-node consumers can use the same version. + * + * See {@link https://hub.docker.com/r/spotify/techdocs/tags} for list of available versions. */ - public static readonly defaultDockerImage = 'spotify/techdocs:v1.2.6'; + public static readonly defaultDockerImage = 'spotify/techdocs:v1.2.8'; private readonly logger: LoggerService; private readonly containerRunner?: TechDocsContainerRunner; private readonly options: GeneratorConfig; From 4d5061e1374156824b0739b77310148afa80ddbb Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Mon, 19 Jan 2026 16:33:40 -0600 Subject: [PATCH 189/202] Updated API Report Signed-off-by: Andre Wanlin --- plugins/techdocs-node/report.api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/techdocs-node/report.api.md b/plugins/techdocs-node/report.api.md index 0e208edff1..d2cb74fb54 100644 --- a/plugins/techdocs-node/report.api.md +++ b/plugins/techdocs-node/report.api.md @@ -309,7 +309,7 @@ export class TechdocsGenerator implements GeneratorBase { config: Config; scmIntegrations: ScmIntegrationRegistry; }); - static readonly defaultDockerImage = 'spotify/techdocs:v1.2.6'; + static readonly defaultDockerImage = 'spotify/techdocs:v1.2.8'; static fromConfig( config: Config, options: GeneratorOptions, From a1be5e1770253b5c04232a9d56c4fcda08828fcf Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 20 Jan 2026 00:11:46 +0100 Subject: [PATCH 190/202] review fixes for plugin wrapper blueprint Signed-off-by: Patrik Oldsberg --- packages/frontend-plugin-api/src/alpha.ts | 6 ------ .../DefaultPluginWrapperApi.tsx | 20 ++++++++++--------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/frontend-plugin-api/src/alpha.ts b/packages/frontend-plugin-api/src/alpha.ts index 9d73b8cc84..2251bfafb7 100644 --- a/packages/frontend-plugin-api/src/alpha.ts +++ b/packages/frontend-plugin-api/src/alpha.ts @@ -14,12 +14,6 @@ * limitations under the License. */ -/** - * Alpha exports for plugin wrapper functionality. - * - * @alpha - */ - export { PluginWrapperBlueprint } from './blueprints/PluginWrapperBlueprint'; export { type PluginWrapperApi, diff --git a/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.tsx b/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.tsx index 65c41ee2e2..7e602ee23f 100644 --- a/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.tsx +++ b/plugins/app/src/apis/PluginWrapperApi/DefaultPluginWrapperApi.tsx @@ -15,7 +15,7 @@ */ import { PluginWrapperApi } from '@backstage/frontend-plugin-api/alpha'; -import { ComponentType, ReactNode, useEffect, useState } from 'react'; +import { ComponentType, ReactNode, useEffect, useMemo, useState } from 'react'; type WrapperInput = { loader: () => Promise<{ component: ComponentType<{ children: ReactNode }> }>; @@ -86,17 +86,19 @@ export class DefaultPluginWrapperApi implements PluginWrapperApi { throw error; } - if (!loadedWrappers) { - return null; - } + return useMemo(() => { + if (!loadedWrappers) { + return null; + } - let content = props.children; + let current = props.children; - for (const Wrapper of loadedWrappers) { - content = {content}; - } + for (const Wrapper of loadedWrappers) { + current = {current}; + } - return <>{content}; + return current; + }, [loadedWrappers, props.children]); }; composedWrappers.set(pluginId, ComposedWrapper); From ed1321ea8f16b585eb7982d603807fa06f53b1d8 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Tue, 20 Jan 2026 12:30:55 +0100 Subject: [PATCH 191/202] fix(create-app): pin memfs to 4.51.1 to fix e2e test failures memfs@4.56.2 (published 2026-01-19) has broken dependencies: @jsonjoy.com/fs-snapshot requires @jsonjoy.com/util@^17.x but imports paths that only exist in version 1.x. This causes e2e tests to fail when creating new apps, as yarn resolves memfs@^4.51.1 to the broken latest version. Signed-off-by: Johan Persson --- packages/create-app/seed-yarn.lock | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/create-app/seed-yarn.lock b/packages/create-app/seed-yarn.lock index 9d4d5449d1..d9a767e158 100644 --- a/packages/create-app/seed-yarn.lock +++ b/packages/create-app/seed-yarn.lock @@ -22,3 +22,9 @@ version "1.11.31" resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.11.31.tgz#e5de9ed005551ce9a16aa69e79935fc33065475c" integrity sha512-mAby9aUnKRjMEA7v8cVZS9Ah4duoRBnX7X6r5qrhTxErx+68MoY1TPrVwj/66/SWN3Bl+jijqAqoB8Qx0QE34A== + +// memfs@4.56.2 has broken dependencies: @jsonjoy.com/fs-snapshot requires @jsonjoy.com/util@^17.x but imports paths only in 1.x +memfs@^4.51.1: + version "4.51.1" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.51.1.tgz#25945de4a90d1573945105e187daa9385e1bca73" + integrity sha512-Eyt3XrufitN2ZL9c/uIRMyDwXanLI88h/L3MoWqNY747ha3dMR9dWqp8cRT5ntjZ0U1TNuq4U91ZXK0sMBjYOQ== From f6a5d2f07f92e31efdcb33f0cfa7820e6ebbcf24 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Tue, 20 Jan 2026 11:06:23 +0100 Subject: [PATCH 192/202] fix(cli): use content-based hashing for CSS module class names Replace the default path-based CSS module hash with a content-based hash derived from the CSS content. This prevents class name collisions when multiple versions of packages are loaded simultaneously. Uses 10 characters of hex-encoded MD5 hash for safe, readable class name suffixes. Signed-off-by: Johan Persson --- .changeset/orange-hoops-matter.md | 5 +++++ .../cli/src/modules/build/lib/builder/config.ts | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 .changeset/orange-hoops-matter.md diff --git a/.changeset/orange-hoops-matter.md b/.changeset/orange-hoops-matter.md new file mode 100644 index 0000000000..7632915f23 --- /dev/null +++ b/.changeset/orange-hoops-matter.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fixed CSS module class name collisions when running multiple versions of packages simultaneously by using content-based hashing for class name generation. diff --git a/packages/cli/src/modules/build/lib/builder/config.ts b/packages/cli/src/modules/build/lib/builder/config.ts index 3e360b645c..7776571e43 100644 --- a/packages/cli/src/modules/build/lib/builder/config.ts +++ b/packages/cli/src/modules/build/lib/builder/config.ts @@ -16,7 +16,9 @@ import chalk from 'chalk'; import fs from 'fs-extra'; +import { createHash } from 'crypto'; import { + basename, extname, relative as relativePath, resolve as resolvePath, @@ -239,7 +241,18 @@ export async function makeRollupConfigs( include: /node_modules/, exclude: [/\/[^/]+\.(?:stories|test)\.[^/]+$/], }), - postcss(), + postcss({ + modules: { + generateScopedName(name: string, filename: string, css: string) { + const hash = createHash('md5') + .update(css) + .digest('hex') + .slice(0, 10); + const file = basename(filename, '.module.css'); + return `${file}_${name}__${hash}`; + }, + }, + }), forwardFileImports({ exclude: /\.icon\.svg$/, include: [ From 4452d15efa9d192a7aec1edfb95ac8ff989f4bd8 Mon Sep 17 00:00:00 2001 From: Yi C Date: Tue, 20 Jan 2026 08:56:47 -0500 Subject: [PATCH 193/202] feat: add i18n support for notifications plugin (#31558) * feat: add i18n support for notifications plugin Signed-off-by: Yi Cai * added changeset Signed-off-by: Yi Cai * updates Signed-off-by: Yi Cai * fix failed e2e checks Signed-off-by: Yi Cai * resolved review comments Signed-off-by: Yi Cai * fixed some untranslated string issue Signed-off-by: Yi Cai * updated report-alpha.api.md Signed-off-by: Yi Cai * updated report api Signed-off-by: Yi Cai * updated report api Signed-off-by: Yi Cai * simplified code Signed-off-by: Yi Cai * updated import to use new dependency Signed-off-by: Yi Cai * updated report-alpha.api.md Signed-off-by: Yi Cai * code clean up Signed-off-by: Yi Cai * addressed review comment Signed-off-by: Yi Cai --------- Signed-off-by: Yi Cai --- .changeset/quick-bugs-speak.md | 5 + plugins/notifications/.eslintrc.js | 6 +- plugins/notifications/knip-report.md | 2 - plugins/notifications/report-alpha.api.md | 66 ++++++++++ plugins/notifications/report.api.md | 7 +- plugins/notifications/src/alpha.tsx | 2 + .../NotificationsFilters.tsx | 122 ++++++++++-------- .../NotificationsPage/NotificationsPage.tsx | 37 ++++-- .../NotificationsSideBarItem.tsx | 1 - .../NotificationsTable/BulkActions.tsx | 16 ++- .../NotificationsTable/NotificationsTable.tsx | 16 +++ .../UserNotificationSettingsCard.tsx | 13 +- .../UserNotificationSettingsPanel.test.tsx | 7 +- plugins/notifications/src/translation.ts | 120 +++++++++++++++++ 14 files changed, 330 insertions(+), 90 deletions(-) create mode 100644 .changeset/quick-bugs-speak.md create mode 100644 plugins/notifications/src/translation.ts diff --git a/.changeset/quick-bugs-speak.md b/.changeset/quick-bugs-speak.md new file mode 100644 index 0000000000..626dba957d --- /dev/null +++ b/.changeset/quick-bugs-speak.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Added i18n support. diff --git a/plugins/notifications/.eslintrc.js b/plugins/notifications/.eslintrc.js index a2d8179106..e487f765b2 100644 --- a/plugins/notifications/.eslintrc.js +++ b/plugins/notifications/.eslintrc.js @@ -1,5 +1,5 @@ module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { - rules: { - '@backstage/no-top-level-material-ui-4-imports': 'error', - }, + rules: { + '@backstage/no-top-level-material-ui-4-imports': 'error', + }, }); diff --git a/plugins/notifications/knip-report.md b/plugins/notifications/knip-report.md index 97d5b385fd..28921c328d 100644 --- a/plugins/notifications/knip-report.md +++ b/plugins/notifications/knip-report.md @@ -1,3 +1 @@ # Knip report - - diff --git a/plugins/notifications/report-alpha.api.md b/plugins/notifications/report-alpha.api.md index 98d036365e..eb597e1e04 100644 --- a/plugins/notifications/report-alpha.api.md +++ b/plugins/notifications/report-alpha.api.md @@ -13,6 +13,7 @@ import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/core-plugin-api'; import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: OverridableFrontendPlugin< @@ -67,5 +68,70 @@ const _default: OverridableFrontendPlugin< >; export default _default; +// @alpha (undocumented) +export const notificationsTranslationRef: TranslationRef< + 'plugin.notifications', + { + readonly 'table.errors.markAllReadFailed': 'Failed to mark all notifications as read'; + readonly 'table.pagination.firstTooltip': 'First Page'; + readonly 'table.pagination.labelDisplayedRows': '{from}-{to} of {count}'; + readonly 'table.pagination.labelRowsSelect': 'rows'; + readonly 'table.pagination.lastTooltip': 'Last Page'; + readonly 'table.pagination.nextTooltip': 'Next Page'; + readonly 'table.pagination.previousTooltip': 'Previous Page'; + readonly 'table.emptyMessage': 'No records to display'; + readonly 'table.bulkActions.markAllRead': 'Mark all read'; + readonly 'table.bulkActions.markSelectedAsRead': 'Mark selected as read'; + readonly 'table.bulkActions.returnSelectedAmongUnread': 'Return selected among unread'; + readonly 'table.bulkActions.saveSelectedForLater': 'Save selected for later'; + readonly 'table.bulkActions.undoSaveForSelected': 'Undo save for selected'; + readonly 'table.confirmDialog.title': 'Are you sure?'; + readonly 'table.confirmDialog.markAllReadDescription': 'Mark all notifications as read.'; + readonly 'table.confirmDialog.markAllReadConfirmation': 'Mark All'; + readonly 'filters.view.all': 'All'; + readonly 'filters.view.label': 'View'; + readonly 'filters.view.read': 'Read notifications'; + readonly 'filters.view.saved': 'Saved'; + readonly 'filters.view.unread': 'Unread notifications'; + readonly 'filters.title': 'Filters'; + readonly 'filters.severity.normal': 'Normal'; + readonly 'filters.severity.high': 'High'; + readonly 'filters.severity.low': 'Low'; + readonly 'filters.severity.label': 'Min severity'; + readonly 'filters.severity.critical': 'Critical'; + readonly 'filters.topic.label': 'Topic'; + readonly 'filters.topic.anyTopic': 'Any topic'; + readonly 'filters.createdAfter.label': 'Sent out'; + readonly 'filters.createdAfter.placeholder': 'Notifications since'; + readonly 'filters.createdAfter.last24h': 'Last 24h'; + readonly 'filters.createdAfter.lastWeek': 'Last week'; + readonly 'filters.createdAfter.anyTime': 'Any time'; + readonly 'filters.sortBy.origin': 'Origin'; + readonly 'filters.sortBy.label': 'Sort by'; + readonly 'filters.sortBy.placeholder': 'Field to sort by'; + readonly 'filters.sortBy.newest': 'Newest on top'; + readonly 'filters.sortBy.oldest': 'Oldest on top'; + readonly 'filters.sortBy.topic': 'Topic'; + readonly 'settings.table.origin': 'Origin'; + readonly 'settings.table.topic': 'Topic'; + readonly 'settings.title': 'Notification settings'; + readonly 'settings.errors.useNotificationFormat': 'useNotificationFormat must be used within a NotificationFormatProvider'; + readonly 'settings.errorTitle': 'Failed to load settings'; + readonly 'settings.noSettingsAvailable': 'No notification settings available, check back later'; + readonly 'sidebar.title': 'Notifications'; + readonly 'sidebar.errors.markAsReadFailed': 'Failed to mark notification as read'; + readonly 'sidebar.errors.fetchNotificationFailed': 'Failed to fetch notification'; + readonly 'notificationsPage.title': 'Notifications'; + readonly 'notificationsPage.tableTitle.all_one': 'All notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.all_other': 'All notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.saved_one': 'Saved notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.saved_other': 'Saved notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.unread_one': 'Unread notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.unread_other': 'Unread notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.read_one': 'Read notifications ({{count}})'; + readonly 'notificationsPage.tableTitle.read_other': 'Read notifications ({{count}})'; + } +>; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/notifications/report.api.md b/plugins/notifications/report.api.md index f42814f983..0d2912638f 100644 --- a/plugins/notifications/report.api.md +++ b/plugins/notifications/report.api.md @@ -13,7 +13,6 @@ import { Notification as Notification_2 } from '@backstage/plugin-notifications- import { NotificationSettings } from '@backstage/plugin-notifications-common'; import { NotificationSeverity } from '@backstage/plugin-notifications-common'; import { NotificationStatus } from '@backstage/plugin-notifications-common'; -import * as React_2 from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { TableProps } from '@backstage/core-components'; @@ -111,10 +110,10 @@ export type NotificationSnackbarProperties = { }; dense?: boolean; maxSnack?: number; - snackStyle?: React_2.CSSProperties; - iconVariant?: Partial>; + snackStyle?: React.CSSProperties; + iconVariant?: Partial>; Components?: { - [key in NotificationSeverity]: React_2.JSXElementConstructor; + [key in NotificationSeverity]: React.JSXElementConstructor; }; }; diff --git a/plugins/notifications/src/alpha.tsx b/plugins/notifications/src/alpha.tsx index 32b9ae429a..b930373a46 100644 --- a/plugins/notifications/src/alpha.tsx +++ b/plugins/notifications/src/alpha.tsx @@ -55,3 +55,5 @@ export default createFrontendPlugin({ // TODO(Rugvip): Nav item (i.e. NotificationsSidebarItem) currently needs to be installed manually extensions: [page, api], }); + +export { notificationsTranslationRef } from './translation'; diff --git a/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx b/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx index e8fa422fa1..66aacdb72e 100644 --- a/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx +++ b/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx @@ -15,10 +15,14 @@ */ import { ChangeEvent } from 'react'; import FormControl from '@material-ui/core/FormControl'; +import Divider from '@material-ui/core/Divider'; import Grid from '@material-ui/core/Grid'; import InputLabel from '@material-ui/core/InputLabel'; import MenuItem from '@material-ui/core/MenuItem'; import Select from '@material-ui/core/Select'; +import Typography from '@material-ui/core/Typography'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { notificationsTranslationRef } from '../../translation'; import { GetNotificationsOptions } from '../../api'; import { NotificationSeverity } from '@backstage/plugin-notifications-common'; @@ -44,58 +48,53 @@ export type NotificationsFiltersProps = { const ALL = '___all___'; -export const CreatedAfterOptions: { - [key: string]: { label: string; getDate: () => Date }; -} = { +type TranslationKey = keyof (typeof notificationsTranslationRef)['T']; + +export const CreatedAfterOptions = { last24h: { - label: 'Last 24h', + labelKey: 'filters.createdAfter.last24h' satisfies TranslationKey, getDate: () => new Date(Date.now() - 24 * 3600 * 1000), }, lastWeek: { - label: 'Last week', + labelKey: 'filters.createdAfter.lastWeek' satisfies TranslationKey, getDate: () => new Date(Date.now() - 7 * 24 * 3600 * 1000), }, all: { - label: 'Any time', + labelKey: 'filters.createdAfter.anyTime' satisfies TranslationKey, getDate: () => new Date(0), }, -}; +} as const; -export const SortByOptions: { - [key: string]: { - label: string; - sortBy: SortBy; - }; -} = { +export const SortByOptions = { newest: { - label: 'Newest on top', + labelKey: 'filters.sortBy.newest' satisfies TranslationKey, sortBy: { - sort: 'created', - sortOrder: 'desc', + sort: 'created' as const, + sortOrder: 'desc' as const, }, }, oldest: { - label: 'Oldest on top', + labelKey: 'filters.sortBy.oldest' satisfies TranslationKey, sortBy: { - sort: 'created', - sortOrder: 'asc', + sort: 'created' as const, + sortOrder: 'asc' as const, }, }, topic: { - label: 'Topic', + labelKey: 'filters.sortBy.topic' satisfies TranslationKey, sortBy: { - sort: 'topic', - sortOrder: 'asc', + sort: 'topic' as const, + sortOrder: 'asc' as const, }, }, origin: { - label: 'Origin', + labelKey: 'filters.sortBy.origin' satisfies TranslationKey, sortBy: { - sort: 'origin', - sortOrder: 'asc', + sort: 'origin' as const, + sortOrder: 'asc' as const, }, }, -}; +} as const; const getSortByText = (sortBy?: SortBy): string => { if (sortBy?.sort === 'created' && sortBy?.sortOrder === 'asc') { @@ -111,13 +110,6 @@ const getSortByText = (sortBy?: SortBy): string => { return 'newest'; }; -const AllSeverityOptions: { [key in NotificationSeverity]: string } = { - critical: 'Critical', - high: 'High', - normal: 'Normal', - low: 'Low', -}; - export const NotificationsFilters = ({ sorting, onSortingChanged, @@ -133,6 +125,7 @@ export const NotificationsFilters = ({ onTopicChanged, allTopics, }: NotificationsFiltersProps) => { + const { t } = useTranslationRef(notificationsTranslationRef); const sortByText = getSortByText(sorting); const handleOnCreatedAfterChanged = ( @@ -163,7 +156,8 @@ export const NotificationsFilters = ({ const handleOnSortByChanged = ( event: ChangeEvent<{ name?: string; value: unknown }>, ) => { - const idx = (event.target.value as string) || 'newest'; + const idx = ((event.target.value as string) || + 'newest') as keyof typeof SortByOptions; const option = SortByOptions[idx]; onSortingChanged({ ...option.sortBy }); }; @@ -197,37 +191,49 @@ export const NotificationsFilters = ({ return ( <> + + {t('filters.title')} + + + - View + + {t('filters.view.label')} + - Sent out + + {t('filters.createdAfter.label')} + @@ -236,18 +242,20 @@ export const NotificationsFilters = ({ - Sort by + + {t('filters.sortBy.label')} + @@ -257,18 +265,20 @@ export const NotificationsFilters = ({ - Min severity + {t('filters.severity.label')} @@ -277,16 +287,18 @@ export const NotificationsFilters = ({ - Topic + + {t('filters.topic.label')} +