diff --git a/.changeset/README.md b/.changeset/README.md index 4f3b76b096..6c68425e59 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -6,3 +6,58 @@ find the full documentation for it [in our repository](https://github.com/change We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/master/docs/common-questions.md) + +--- + +## Backstage UI Changesets + +For `@backstage/ui` changesets, use this format: + +```markdown +--- +'@backstage/ui': patch +--- + +Brief summary + +Optional description with code examples. + +**Migration:** + +Migration instructions (breaking changes only). + +**Affected components:** button, card +``` + +**Required:** + +- End with `**Affected components:**` + comma-separated component names +- For breaking changes: Add `**Migration:**` section +- No headings (`##`, `###`) inside - use bold markers + +**Examples:** + +```markdown +Fixed button hover state + +**Affected components:** button +``` + +````markdown +**BREAKING**: New Table API + +**Migration:** + +Update imports: + +```diff +- import { Table } from '@backstage/ui'; ++ import { Table, type ColumnConfig } from '@backstage/ui'; +``` +```` + +**Affected components:** table + +``` + +``` diff --git a/.changeset/api-override-conflict-error-defaults.md b/.changeset/api-override-conflict-error-defaults.md new file mode 100644 index 0000000000..703f76fb6e --- /dev/null +++ b/.changeset/api-override-conflict-error-defaults.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': minor +--- + +**BREAKING**: The `API_FACTORY_CONFLICT` warning is now treated as an error and will prevent the app from starting. diff --git a/.changeset/api-override-conflict-error.md b/.changeset/api-override-conflict-error.md new file mode 100644 index 0000000000..ed88452ccf --- /dev/null +++ b/.changeset/api-override-conflict-error.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': minor +--- + +**BREAKING**: Updated the behavior of the new API override logic to reject the override and block app startup instead of just logging a deprecation warning. diff --git a/.changeset/backend-test-utils-extension-points.md b/.changeset/backend-test-utils-extension-points.md new file mode 100644 index 0000000000..fb26a282e3 --- /dev/null +++ b/.changeset/backend-test-utils-extension-points.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Updated `startTestBackend` to support factory-based extension points (v1.1 format) in addition to the existing direct implementation format. diff --git a/.changeset/bright-pans-greet.md b/.changeset/bright-pans-greet.md new file mode 100644 index 0000000000..89ac3a8bc6 --- /dev/null +++ b/.changeset/bright-pans-greet.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-visualizer': patch +--- + +Bump react-aria-components to v1.14.0 diff --git a/.changeset/catalog-modules-update-extension-points.md b/.changeset/catalog-modules-update-extension-points.md new file mode 100644 index 0000000000..dba229482f --- /dev/null +++ b/.changeset/catalog-modules-update-extension-points.md @@ -0,0 +1,23 @@ +--- +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/plugin-catalog-backend-module-azure': patch +'@backstage/plugin-catalog-backend-module-backstage-openapi': patch +'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch +'@backstage/plugin-catalog-backend-module-bitbucket-server': patch +'@backstage/plugin-catalog-backend-module-gcp': patch +'@backstage/plugin-catalog-backend-module-gerrit': patch +'@backstage/plugin-catalog-backend-module-gitea': patch +'@backstage/plugin-catalog-backend-module-github': patch +'@backstage/plugin-catalog-backend-module-github-org': patch +'@backstage/plugin-catalog-backend-module-gitlab': patch +'@backstage/plugin-catalog-backend-module-gitlab-org': patch +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch +'@backstage/plugin-catalog-backend-module-ldap': patch +'@backstage/plugin-catalog-backend-module-msgraph': patch +'@backstage/plugin-catalog-backend-module-openapi': patch +'@backstage/plugin-catalog-backend-module-puppetdb': patch +'@backstage/plugin-catalog-backend-module-scaffolder-entity-model': patch +--- + +Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports. diff --git a/.changeset/catalog-node-extension-points-to-stable.md b/.changeset/catalog-node-extension-points-to-stable.md new file mode 100644 index 0000000000..7e8e22dfc5 --- /dev/null +++ b/.changeset/catalog-node-extension-points-to-stable.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-catalog-node': minor +--- + +Promoted stable catalog extension points from alpha to main export. The following extension points are now exported from `@backstage/plugin-catalog-node` instead of `@backstage/plugin-catalog-node/alpha`: + +- `catalogLocationsExtensionPoint` and `CatalogLocationsExtensionPoint` +- `catalogProcessingExtensionPoint` and `CatalogProcessingExtensionPoint` +- `catalogAnalysisExtensionPoint` and `CatalogAnalysisExtensionPoint` + +The old alpha exports for these extension points are now deprecated with `@deprecated` markers pointing to the new stable exports. Please update your imports from `@backstage/plugin-catalog-node/alpha` to `@backstage/plugin-catalog-node`. + +Note: The `catalogModelExtensionPoint`, `catalogPermissionExtensionPoint`, and related types remain in alpha. diff --git a/.changeset/catalog-provider-module-failures.md b/.changeset/catalog-provider-module-failures.md new file mode 100644 index 0000000000..c7cb5a3571 --- /dev/null +++ b/.changeset/catalog-provider-module-failures.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +Failures to connect catalog providers are now attributed to the module that provided the failing provider. This means that such failures will be reported as module startup failures rather than a failure to start the catalog plugin, and will therefore respect `onPluginModuleBootFailure` configuration instead. diff --git a/.changeset/chatty-tips-stop.md b/.changeset/chatty-tips-stop.md new file mode 100644 index 0000000000..7b4c435ffc --- /dev/null +++ b/.changeset/chatty-tips-stop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-auth0-provider': minor +--- + +feat: Added organization option to authorization params of the strategy diff --git a/.changeset/clean-suits-follow.md b/.changeset/clean-suits-follow.md new file mode 100644 index 0000000000..992b3e2947 --- /dev/null +++ b/.changeset/clean-suits-follow.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Add newline to OpenAPI license template files. diff --git a/.changeset/cli-template-catalog-node-stable.md b/.changeset/cli-template-catalog-node-stable.md new file mode 100644 index 0000000000..77b06dc8f1 --- /dev/null +++ b/.changeset/cli-template-catalog-node-stable.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated catalog provider module template to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of alpha exports. diff --git a/.changeset/cute-parts-smash.md b/.changeset/cute-parts-smash.md new file mode 100644 index 0000000000..166e0fc528 --- /dev/null +++ b/.changeset/cute-parts-smash.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed Box component to forward HTML attributes to the underlying div element. + +**Affected components:** Box diff --git a/.changeset/early-kids-see.md b/.changeset/early-kids-see.md new file mode 100644 index 0000000000..f2732c2f1a --- /dev/null +++ b/.changeset/early-kids-see.md @@ -0,0 +1,80 @@ +--- +'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch +'@backstage/plugin-search-backend-module-stack-overflow-collator': patch +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch +'@backstage/plugin-auth-backend-module-azure-easyauth-provider': patch +'@backstage/plugin-auth-backend-module-oauth2-proxy-provider': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +'@backstage/plugin-auth-backend-module-pinniped-provider': patch +'@backstage/plugin-auth-backend-module-aws-alb-provider': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket': patch +'@backstage/plugin-search-backend-module-elasticsearch': patch +'@backstage/plugin-auth-backend-module-oidc-provider': patch +'@backstage/backend-dynamic-feature-service': patch +'@backstage/plugin-scaffolder-backend-module-gerrit': patch +'@backstage/plugin-scaffolder-backend-module-github': patch +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +'@backstage/plugin-scaffolder-backend-module-sentry': patch +'@backstage/plugin-scaffolder-backend-module-yeoman': patch +'@backstage/plugin-scaffolder-backend-module-gitea': patch +'@backstage/plugin-scaffolder-backend-module-rails': patch +'@backstage/plugin-catalog-backend-module-openapi': patch +'@backstage/plugin-search-backend-module-techdocs': patch +'@backstage/plugin-catalog-backend-module-gerrit': patch +'@backstage/plugin-catalog-backend-module-github': patch +'@backstage/plugin-catalog-backend-module-gitlab': patch +'@backstage/plugin-search-backend-module-catalog': patch +'@backstage/plugin-search-backend-module-explore': patch +'@backstage/plugin-catalog-backend-module-ldap': patch +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/plugin-scaffolder-node-test-utils': patch +'@backstage/plugin-techdocs-addons-test-utils': patch +'@backstage/plugin-search-backend-module-pg': patch +'@backstage/backend-openapi-utils': patch +'@backstage/plugin-bitbucket-cloud-common': patch +'@backstage/integration-aws-node': patch +'@backstage/plugin-notifications-backend': patch +'@backstage/backend-plugin-api': patch +'@backstage/backend-test-utils': patch +'@backstage/plugin-search-backend-node': patch +'@backstage/backend-dev-utils': patch +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-permission-backend': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/backend-defaults': patch +'@backstage/core-components': patch +'@backstage/plugin-devtools-backend': patch +'@backstage/plugin-kubernetes-react': patch +'@backstage/plugin-techdocs-backend': patch +'@backstage/e2e-test-utils': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-kubernetes-node': patch +'@backstage/plugin-scaffolder-node': patch +'@backstage/plugin-signals-backend': patch +'@backstage/config-loader': patch +'@backstage/eslint-plugin': patch +'@backstage/plugin-events-backend': patch +'@backstage/plugin-search-backend': patch +'@techdocs/cli': patch +'@backstage/plugin-proxy-backend': patch +'@backstage/plugin-search-common': patch +'@backstage/plugin-techdocs-node': patch +'@backstage/integration': patch +'@backstage/plugin-auth-backend': patch +'@backstage/plugin-catalog-node': patch +'@backstage/cli-common': patch +'@backstage/create-app': patch +'@backstage/repo-tools': patch +'@backstage/plugin-app-backend': patch +'@backstage/plugin-scaffolder': patch +'@backstage/cli-node': patch +'@backstage/codemods': patch +'@backstage/plugin-auth-node': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-app-node': patch +'@backstage/cli': patch +'@backstage/ui': patch +--- + +Use node prefix on native imports diff --git a/.changeset/fix-catalog-tags-spacing.md b/.changeset/fix-catalog-tags-spacing.md new file mode 100644 index 0000000000..e611273a04 --- /dev/null +++ b/.changeset/fix-catalog-tags-spacing.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Fixed vertical spacing between tags in the catalog table. diff --git a/.changeset/fix-nextjs16-css-modules.md b/.changeset/fix-nextjs16-css-modules.md new file mode 100644 index 0000000000..faa60fc06a --- /dev/null +++ b/.changeset/fix-nextjs16-css-modules.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed CSS Module syntax to comply with Next.js 16 Turbopack validation by flattening nested dark theme selectors. + +**Affected components:** Popover, Tooltip diff --git a/.changeset/floppy-actors-show.md b/.changeset/floppy-actors-show.md new file mode 100644 index 0000000000..95bb711660 --- /dev/null +++ b/.changeset/floppy-actors-show.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Added `destructive` prop to Button for dangerous actions like delete or remove. Works with all variants (primary, secondary, tertiary). + +**Affected components:** Button diff --git a/.changeset/floppy-parks-decide.md b/.changeset/floppy-parks-decide.md new file mode 100644 index 0000000000..30c1cfc9a5 --- /dev/null +++ b/.changeset/floppy-parks-decide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +--- + +Fully enable API token functionality for Bitbucket-Cloud. diff --git a/.changeset/fuzzy-shrimps-refuse.md b/.changeset/fuzzy-shrimps-refuse.md new file mode 100644 index 0000000000..c2814da631 --- /dev/null +++ b/.changeset/fuzzy-shrimps-refuse.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Implemented support for the `internal` extension input option. diff --git a/.changeset/gold-lions-stick.md b/.changeset/gold-lions-stick.md new file mode 100644 index 0000000000..da18dd6cf6 --- /dev/null +++ b/.changeset/gold-lions-stick.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Fixed `useDefinition` hook adding literal "undefined" class name when no className prop was passed. diff --git a/.changeset/good-cameras-repair.md b/.changeset/good-cameras-repair.md new file mode 100644 index 0000000000..e994112e94 --- /dev/null +++ b/.changeset/good-cameras-repair.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-kubernetes-common': patch +'@backstage/plugin-kubernetes-react': patch +'@backstage/plugin-kubernetes-node': patch +--- + +Add PersistentVolume and PersistentVolumeClaims Rendering diff --git a/.changeset/good-eggs-tell.md b/.changeset/good-eggs-tell.md new file mode 100644 index 0000000000..04ce63db7d --- /dev/null +++ b/.changeset/good-eggs-tell.md @@ -0,0 +1,9 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Updated error reporting and app tree resolution logic to attribute errors to the correct extension and allow app startup to proceed more optimistically: + +- If an attachment fails to provide the required input data, the error is now attributed to the attachment rather than the parent extension. +- Singleton extension inputs will now only forward attachment errors if the input is required. +- Array extension inputs will now filter out failed attachments instead of failing the entire app tree resolution. diff --git a/.changeset/green-bags-shave.md b/.changeset/green-bags-shave.md new file mode 100644 index 0000000000..17211b78f0 --- /dev/null +++ b/.changeset/green-bags-shave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +cleaned up repo creation to make the unique portions explicit diff --git a/.changeset/green-flowers-brush.md b/.changeset/green-flowers-brush.md new file mode 100644 index 0000000000..bf57f18dcb --- /dev/null +++ b/.changeset/green-flowers-brush.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Removed unused dependency diff --git a/.changeset/green-llamas-wink.md b/.changeset/green-llamas-wink.md new file mode 100644 index 0000000000..0590b59ef3 --- /dev/null +++ b/.changeset/green-llamas-wink.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-compat-api': patch +'@backstage/plugin-app-visualizer': patch +--- + +Internal updates for blueprint moves to `@backstage/plugin-app-react`. diff --git a/.changeset/lazy-groups-hunt.md b/.changeset/lazy-groups-hunt.md new file mode 100644 index 0000000000..56e0b19035 --- /dev/null +++ b/.changeset/lazy-groups-hunt.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Minor internal restructure of the postgres config loading code diff --git a/.changeset/legal-impalas-shine.md b/.changeset/legal-impalas-shine.md new file mode 100644 index 0000000000..f9d54690dc --- /dev/null +++ b/.changeset/legal-impalas-shine.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-visualizer': patch +--- + +Improved rendering performance of the details page. diff --git a/.changeset/lemon-eyes-grin.md b/.changeset/lemon-eyes-grin.md new file mode 100644 index 0000000000..7cbd4b29f1 --- /dev/null +++ b/.changeset/lemon-eyes-grin.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Switched `next-app` template to use blueprint from `@backstage/plugin-app-react`. diff --git a/.changeset/lucky-masks-doubt.md b/.changeset/lucky-masks-doubt.md new file mode 100644 index 0000000000..b20633308f --- /dev/null +++ b/.changeset/lucky-masks-doubt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-pg': patch +--- + +Return `numberOfResults` count with search query responses diff --git a/.changeset/many-bags-brake.md b/.changeset/many-bags-brake.md new file mode 100644 index 0000000000..91b48d63f4 --- /dev/null +++ b/.changeset/many-bags-brake.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Added a new `internal` option to `createExtensionInput` that marks the input as only allowing attachments from the same plugin. diff --git a/.changeset/module-failure-reporting.md b/.changeset/module-failure-reporting.md new file mode 100644 index 0000000000..30acaf74d3 --- /dev/null +++ b/.changeset/module-failure-reporting.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': minor +--- + +Added support for extension point factories, along with the ability to report module startup failures via the extension point factory context. diff --git a/.changeset/module-handle-api.md b/.changeset/module-handle-api.md new file mode 100644 index 0000000000..28b0134ec1 --- /dev/null +++ b/.changeset/module-handle-api.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +Added support for extension point factories. This makes it possible to call `registerExtensionPoint` with a single options argument and provide a factory for the extension point rather than a direct implementation. The factory is passed a context with a `reportModuleStartupFailure` method that makes it possible for plugins to report and attribute startup errors to the module that consumed the extension point. diff --git a/.changeset/odd-eagles-guess.md b/.changeset/odd-eagles-guess.md new file mode 100644 index 0000000000..d40ab80640 --- /dev/null +++ b/.changeset/odd-eagles-guess.md @@ -0,0 +1,8 @@ +--- +'@backstage/backend-defaults': patch +'@backstage/plugin-scaffolder-node': patch +'@backstage/repo-tools': patch +'@backstage/cli': patch +--- + +Bump to tar v7 diff --git a/.changeset/plenty-dryers-tan.md b/.changeset/plenty-dryers-tan.md new file mode 100644 index 0000000000..719e151159 --- /dev/null +++ b/.changeset/plenty-dryers-tan.md @@ -0,0 +1,7 @@ +--- +'@backstage/frontend-defaults': patch +'@backstage/frontend-app-api': patch +'@backstage/cli': patch +--- + +Completely removed support for the deprecated `app.experimental.packages` configuration. Replace existing usage directly with `app.packages`. diff --git a/.changeset/plenty-monkeys-share.md b/.changeset/plenty-monkeys-share.md new file mode 100644 index 0000000000..214b0e8546 --- /dev/null +++ b/.changeset/plenty-monkeys-share.md @@ -0,0 +1,14 @@ +--- +'@backstage/ui': patch +--- + +Added new `Alert` component with support for status variants (info, success, warning, danger), icons, loading states, and custom actions. + +Updated status color tokens for improved contrast and consistency across light and dark themes: + +- Added new `--bui-bg-info` and `--bui-fg-info` tokens for info status +- Updated `--bui-bg-danger`, `--bui-fg-danger` tokens +- Updated `--bui-bg-warning`, `--bui-fg-warning` tokens +- Updated `--bui-bg-success`, `--bui-fg-success` tokens + +**Affected components**: Alert diff --git a/.changeset/polite-glasses-throw.md b/.changeset/polite-glasses-throw.md new file mode 100644 index 0000000000..67f554591a --- /dev/null +++ b/.changeset/polite-glasses-throw.md @@ -0,0 +1,13 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: The following blueprints have been removed and are now only available from `@backstage/plugin-app-react`: + +- `IconBundleBlueprint` +- `NavContentBlueprint` +- `RouterBlueprint` +- `SignInPageBlueprint` +- `SwappableComponentBlueprint` +- `ThemeBlueprint` +- `TranslationBlueprint` diff --git a/.changeset/polite-symbols-act.md b/.changeset/polite-symbols-act.md new file mode 100644 index 0000000000..9c3489f670 --- /dev/null +++ b/.changeset/polite-symbols-act.md @@ -0,0 +1,15 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: Changed CSS selectors for `ButtonIcon` and `ButtonLink` components. Custom styles targeting `.bui-Button` to style these components must be updated to use `.bui-ButtonIcon` or `.bui-ButtonLink` respectively. + +```diff +-/* This no longer styles ButtonIcon or ButtonLink */ +-.bui-Button[data-variant="primary"] { ... } ++/* Use component-specific selectors */ ++.bui-ButtonIcon[data-variant="primary"] { ... } ++.bui-ButtonLink[data-variant="primary"] { ... } +``` + +Affected components: ButtonIcon, ButtonLink diff --git a/.changeset/pre.json b/.changeset/pre.json index c162749562..33ae5c0fa6 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -208,5 +208,61 @@ "@backstage/plugin-user-settings-backend": "0.3.10", "@backstage/plugin-user-settings-common": "0.0.1" }, - "changesets": [] + "changesets": [ + "api-override-deprecation-warning-defaults", + "api-override-deprecation-warning", + "backend-test-utils-extension-points", + "big-rabbits-think", + "bright-pans-greet", + "catalog-modules-update-extension-points", + "catalog-node-extension-points-to-stable", + "catalog-provider-module-failures", + "chatty-tips-stop", + "clean-suits-follow", + "cli-template-catalog-node-stable", + "cute-parts-smash", + "early-kids-see", + "floppy-parks-decide", + "fuzzy-shrimps-refuse", + "good-eggs-tell", + "green-bags-shave", + "green-flowers-brush", + "green-llamas-wink", + "lazy-groups-hunt", + "legal-impalas-shine", + "lemon-eyes-grin", + "many-bags-brake", + "module-failure-reporting", + "module-handle-api", + "odd-eagles-guess", + "plenty-dryers-tan", + "plenty-monkeys-share", + "polite-glasses-throw", + "polite-symbols-act", + "proud-stars-grow", + "quiet-carpets-arrive", + "quiet-humans-hammer", + "rare-papers-decide", + "remove-multiple-attachment-points-frontend-app-api", + "remove-multiple-attachment-points-frontend-plugin-api", + "rotten-paths-pump", + "scaffolder-form-fields-api-migration-react", + "scaffolder-form-fields-api-migration", + "seven-states-sleep", + "slimy-dots-cross", + "slimy-dots-sing", + "slimy-zebras-lie", + "small-jars-lick", + "smooth-pants-wave", + "tasty-crabs-occur", + "techdocs-addons-api-migration-react", + "techdocs-addons-api-migration", + "tiny-lamps-give", + "tired-sides-share", + "twelve-dolls-tap", + "vast-rockets-dig", + "wet-cups-juggle", + "yellow-ties-dream", + "young-pens-wash" + ] } diff --git a/.changeset/proud-stars-grow.md b/.changeset/proud-stars-grow.md new file mode 100644 index 0000000000..e8f0bc16c4 --- /dev/null +++ b/.changeset/proud-stars-grow.md @@ -0,0 +1,9 @@ +--- +'@backstage/ui': patch +--- + +Fixed client-side navigation for container components by wrapping the container (not individual items) in RouterProvider. Components now conditionally provide routing context only when children have internal links, removing the Router context requirement when not needed. This also removes the need to wrap these components in MemoryRouter during tests when they are not using the `href` prop. + +Additionally, when multiple tabs match the current URL via prefix matching, the tab with the most specific path (highest segment count) is now selected. For example, with URL `/catalog/users/john`, a tab with path `/catalog/users` is now selected over a tab with path `/catalog`. + +Affected components: Tabs, Tab, TagGroup, Tag, Menu, MenuItem, MenuAutocomplete diff --git a/.changeset/public-keys-sip.md b/.changeset/public-keys-sip.md new file mode 100644 index 0000000000..8254ac54d5 --- /dev/null +++ b/.changeset/public-keys-sip.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Support Prettier v3 for api-reports diff --git a/.changeset/quiet-carpets-arrive.md b/.changeset/quiet-carpets-arrive.md new file mode 100644 index 0000000000..e0104cc286 --- /dev/null +++ b/.changeset/quiet-carpets-arrive.md @@ -0,0 +1,22 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: The `cell` and `header` properties in `ColumnConfig` now return `ReactElement` instead of `ReactNode`. + +This fixes an issue where React Aria's Collection component would inject an `id` prop into Fragment wrappers, causing "Invalid prop `id` supplied to `React.Fragment`" errors on render. + +Migration: + +```diff +const columns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', +- cell: (item) => item.name, ++ cell: (item) => , +- header: () => 'Name', ++ header: () => Name, + }, +]; +``` diff --git a/.changeset/quiet-humans-hammer.md b/.changeset/quiet-humans-hammer.md new file mode 100644 index 0000000000..d47fd647f1 --- /dev/null +++ b/.changeset/quiet-humans-hammer.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-app': minor +--- + +**BREAKING**: Extensions created with the following blueprints must now be provided via an override or a module for the `app` plugin. Extensions from other plugins will now trigger a warning in the app and be ignored. + +- `IconBundleBlueprint` +- `NavContentBlueprint` +- `RouterBlueprint` +- `SignInPageBlueprint` +- `SwappableComponentBlueprint` +- `ThemeBlueprint` +- `TranslationBlueprint` diff --git a/.changeset/rare-papers-decide.md b/.changeset/rare-papers-decide.md new file mode 100644 index 0000000000..ff5b0d3c36 --- /dev/null +++ b/.changeset/rare-papers-decide.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Fixed an infinite render loop in Tabs when navigating to a URL that doesn't match any tab `href`. diff --git a/.changeset/remove-multiple-attachment-points-frontend-app-api.md b/.changeset/remove-multiple-attachment-points-frontend-app-api.md new file mode 100644 index 0000000000..4d54ef599f --- /dev/null +++ b/.changeset/remove-multiple-attachment-points-frontend-app-api.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +**DEPRECATED**: Deprecated support for multiple attachment points. diff --git a/.changeset/remove-multiple-attachment-points-frontend-plugin-api.md b/.changeset/remove-multiple-attachment-points-frontend-plugin-api.md new file mode 100644 index 0000000000..b0d5739ea3 --- /dev/null +++ b/.changeset/remove-multiple-attachment-points-frontend-plugin-api.md @@ -0,0 +1,7 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +**DEPRECATED**: Multiple attachment points for extensions have been deprecated. The functionality continues to work for backward compatibility, but will log a deprecation warning and be removed in a future release. + +Extensions using array attachment points should migrate to using Utility APIs instead. See the [Sharing Extensions Across Multiple Locations](https://backstage.io/docs/frontend-system/architecture/27-sharing-extensions) guide for the recommended pattern. diff --git a/.changeset/rotten-paths-pump.md b/.changeset/rotten-paths-pump.md new file mode 100644 index 0000000000..3d0031edfe --- /dev/null +++ b/.changeset/rotten-paths-pump.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Migrate audit events reference docs to http://backstage.io/docs. diff --git a/.changeset/scaffolder-form-fields-api-migration-react.md b/.changeset/scaffolder-form-fields-api-migration-react.md new file mode 100644 index 0000000000..4d640c58fc --- /dev/null +++ b/.changeset/scaffolder-form-fields-api-migration-react.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Scaffolder form fields in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `FormFieldBlueprint` now uses this new approach, and while form fields created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + +As part of this change, the following alpha exports were removed: + +- `formFieldsApi` +- `formFieldsApiRef` +- `ScaffolderFormFieldsApi` diff --git a/.changeset/scaffolder-form-fields-api-migration.md b/.changeset/scaffolder-form-fields-api-migration.md new file mode 100644 index 0000000000..cfbed3b749 --- /dev/null +++ b/.changeset/scaffolder-form-fields-api-migration.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Scaffolder form fields in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `FormFieldBlueprint` now uses this new approach, and while form fields created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + +As part of this change, the following alpha exports were removed: + +- `formFieldsApiRef` +- `ScaffolderFormFieldsApi` diff --git a/.changeset/sharp-doodles-retire.md b/.changeset/sharp-doodles-retire.md new file mode 100644 index 0000000000..2062b7ec34 --- /dev/null +++ b/.changeset/sharp-doodles-retire.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-node': patch +--- + +Some security fixes diff --git a/.changeset/slimy-dots-cross.md b/.changeset/slimy-dots-cross.md new file mode 100644 index 0000000000..fa1cf18aa2 --- /dev/null +++ b/.changeset/slimy-dots-cross.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': patch +--- + +Dependency update for tests. diff --git a/.changeset/small-jars-lick.md b/.changeset/small-jars-lick.md new file mode 100644 index 0000000000..b2160eb7f7 --- /dev/null +++ b/.changeset/small-jars-lick.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-devtools': patch +--- + +Fixes an issue where a user lacking permission to schedule tasks can now easily see the issue through a custom icon + tooltip. diff --git a/.changeset/smart-ties-attend.md b/.changeset/smart-ties-attend.md new file mode 100644 index 0000000000..b0e4e621d8 --- /dev/null +++ b/.changeset/smart-ties-attend.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': minor +--- + +Log group/user count, tenant ID, execution time as separate fields diff --git a/.changeset/smooth-pants-wave.md b/.changeset/smooth-pants-wave.md new file mode 100644 index 0000000000..d736d4fe58 --- /dev/null +++ b/.changeset/smooth-pants-wave.md @@ -0,0 +1,9 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-app-api': patch +'@backstage/frontend-defaults': patch +'@backstage/core-plugin-api': patch +'@backstage/core-compat-api': patch +--- + +Plugins in the new frontend system now have a `pluginId` field rather than `id` to better align with naming conventions used throughout the frontend and backend systems. The old field is still present but marked as deprecated. All internal code has been updated to prefer `pluginId` while maintaining backward compatibility by falling back to `id` when needed. diff --git a/.changeset/solid-dryers-leave.md b/.changeset/solid-dryers-leave.md new file mode 100644 index 0000000000..db1eb86c6c --- /dev/null +++ b/.changeset/solid-dryers-leave.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Support `connection.type: azure` in database client to use Microsoft Entra authentication with Azure database for PostgreSQL diff --git a/.changeset/tasty-crabs-occur.md b/.changeset/tasty-crabs-occur.md new file mode 100644 index 0000000000..22036e3c34 --- /dev/null +++ b/.changeset/tasty-crabs-occur.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed components to not require a Router context when rendering without internal links. + +Affected components: Link, ButtonLink, Row diff --git a/.changeset/techdocs-addons-api-migration-react.md b/.changeset/techdocs-addons-api-migration-react.md new file mode 100644 index 0000000000..9a2c5f1da9 --- /dev/null +++ b/.changeset/techdocs-addons-api-migration-react.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-techdocs-react': patch +--- + +TechDocs addons in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `AddonBlueprint` now uses this new approach, and while addons created with older versions still work, they will produce a deprecation warning and will stop working in a future release. + +As part of this change, the `techDocsAddonDataRef` alpha export was removed. diff --git a/.changeset/techdocs-addons-api-migration.md b/.changeset/techdocs-addons-api-migration.md new file mode 100644 index 0000000000..f2e481de7e --- /dev/null +++ b/.changeset/techdocs-addons-api-migration.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +TechDocs addons in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `AddonBlueprint` now uses this new approach, and while addons created with older versions still work, they will produce a deprecation warning and will stop working in a future release. diff --git a/.changeset/three-rooms-enjoy.md b/.changeset/three-rooms-enjoy.md new file mode 100644 index 0000000000..9856aba0e8 --- /dev/null +++ b/.changeset/three-rooms-enjoy.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search': minor +--- + +Added support for configuring the default search type in the search page via the `search.defaultType` option in `app-config.yaml`. This applies to both the legacy and new frontend systems. If not set, the default is empty, which means searching for "all" types. diff --git a/.changeset/tiny-lamps-give.md b/.changeset/tiny-lamps-give.md new file mode 100644 index 0000000000..047c690f3f --- /dev/null +++ b/.changeset/tiny-lamps-give.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Bump react-aria-components to v1.14.0 diff --git a/.changeset/tired-queens-cross.md b/.changeset/tired-queens-cross.md new file mode 100644 index 0000000000..2f1be86f15 --- /dev/null +++ b/.changeset/tired-queens-cross.md @@ -0,0 +1,14 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: Removed `large` size variant from Button component as it was never implemented. + +**Migration:** + +```diff +- ++ +``` + +**Affected components:** Button diff --git a/.changeset/tired-sides-share.md b/.changeset/tired-sides-share.md new file mode 100644 index 0000000000..661a7aee50 --- /dev/null +++ b/.changeset/tired-sides-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-react': patch +--- + +Internal refactor to move implementation of blueprints from `@backstage/frontend-plugin-api` to this package. diff --git a/.changeset/twenty-clubs-itch.md b/.changeset/twenty-clubs-itch.md new file mode 100644 index 0000000000..c69aabec7d --- /dev/null +++ b/.changeset/twenty-clubs-itch.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed Switch component disabled state styling to show `not-allowed` cursor and disabled text color. + +**Affected components:** Switch diff --git a/.changeset/wet-cups-juggle.md b/.changeset/wet-cups-juggle.md new file mode 100644 index 0000000000..5e1185f7df --- /dev/null +++ b/.changeset/wet-cups-juggle.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Removed link styles from LinkButton to avoid styling inconsistencies related to import order. diff --git a/.changeset/yellow-ties-dream.md b/.changeset/yellow-ties-dream.md new file mode 100644 index 0000000000..8326b28319 --- /dev/null +++ b/.changeset/yellow-ties-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Migrate audit events reference docs to http://backstage.io/docs. diff --git a/.changeset/yellow-years-run.md b/.changeset/yellow-years-run.md new file mode 100644 index 0000000000..fc57664911 --- /dev/null +++ b/.changeset/yellow-years-run.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-dynamic-feature-service': patch +--- + +Updated README for backend-dynamic-feature-service diff --git a/.changeset/young-pens-wash.md b/.changeset/young-pens-wash.md new file mode 100644 index 0000000000..0deeb40a65 --- /dev/null +++ b/.changeset/young-pens-wash.md @@ -0,0 +1,50 @@ +--- +'@backstage/plugin-auth-backend-module-cloudflare-access-provider': patch +'@backstage/plugin-auth-backend-module-bitbucket-server-provider': patch +'@backstage/plugin-auth-backend-module-azure-easyauth-provider': patch +'@backstage/plugin-auth-backend-module-oauth2-proxy-provider': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +'@backstage/plugin-auth-backend-module-atlassian-provider': patch +'@backstage/plugin-auth-backend-module-bitbucket-provider': patch +'@backstage/plugin-auth-backend-module-microsoft-provider': patch +'@backstage/plugin-auth-backend-module-onelogin-provider': patch +'@backstage/plugin-auth-backend-module-aws-alb-provider': patch +'@backstage/plugin-auth-backend-module-gcp-iap-provider': patch +'@backstage/plugin-auth-backend-module-github-provider': patch +'@backstage/plugin-auth-backend-module-gitlab-provider': patch +'@backstage/plugin-auth-backend-module-google-provider': patch +'@backstage/plugin-auth-backend-module-oauth2-provider': patch +'@backstage/plugin-auth-backend-module-oidc-provider': patch +'@backstage/plugin-auth-backend-module-okta-provider': patch +'@backstage/plugin-scaffolder-backend-module-github': patch +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-test-utils': patch +'@backstage/backend-plugin-api': patch +'@backstage/backend-test-utils': patch +'@backstage/plugin-mcp-actions-backend': patch +'@backstage/plugin-permission-backend': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/backend-defaults': patch +'@backstage/frontend-app-api': patch +'@backstage/plugin-permission-common': patch +'@backstage/core-compat-api': patch +'@backstage/core-components': patch +'@backstage/core-plugin-api': patch +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-permission-node': patch +'@backstage/plugin-scaffolder-node': patch +'@backstage/plugin-search-backend': patch +'@backstage/core-app-api': patch +'@backstage/plugin-catalog-react': patch +'@backstage/repo-tools': patch +'@backstage/plugin-scaffolder': patch +'@backstage/cli-node': patch +'@backstage/plugin-auth-node': patch +'@backstage/cli': patch +'@backstage/plugin-home': patch +'@backstage/plugin-app': patch +--- + +Bump to latest zod to ensure it has the latest features diff --git a/.eslintrc.js b/.eslintrc.js index 54f9e69576..4f7c5afa5e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,12 +14,13 @@ * limitations under the License. */ -var path = require('path'); +var path = require('node:path'); module.exports = { root: true, - plugins: ['@spotify', 'notice', 'react', 'testing-library', '@backstage'], + plugins: ['@spotify', 'notice', 'react', 'testing-library', '@backstage', 'node-import'], rules: { + 'node-import/prefer-node-protocol': 1, '@backstage/no-mixed-plugin-imports': [ 'error', { diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 62277b26d6..f7245deeb2 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -345,6 +345,7 @@ parallelization param params parseable +passwordless Patrik pattison Peloton @@ -527,6 +528,7 @@ transpilers trixie truthy tsconfig +Turbopack TSDoc typeahead ui diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 7525ea3a07..defc6f6a21 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -18,7 +18,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history @@ -27,7 +27,7 @@ jobs: run: git fetch --depth 1 origin ${{ github.base_ref }} - name: setup-node - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml index 257db3929a..1403bff13b 100644 --- a/.github/workflows/automate_changeset_feedback.yml +++ b/.github/workflows/automate_changeset_feedback.yml @@ -27,7 +27,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history diff --git a/.github/workflows/automate_merge_message.yml b/.github/workflows/automate_merge_message.yml index 83a1e32fcf..d644feb580 100644 --- a/.github/workflows/automate_merge_message.yml +++ b/.github/workflows/automate_merge_message.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: '${{ github.event.pull_request.merge_commit_sha }}' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55e01009c5..5e5aed996d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,10 +32,10 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth @@ -68,10 +68,10 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth @@ -121,6 +121,8 @@ jobs: - name: verify doc links run: node scripts/verify-links.js + - name: verify plugin directory + run: node scripts/verify-plugin-directory.js - name: build all packages run: yarn backstage-cli repo build --all @@ -210,7 +212,7 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: fetch master branch run: git fetch origin master @@ -220,7 +222,7 @@ jobs: run: git fetch origin ${{ github.event.pull_request.base.ref }} - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/cleanup_patch-files.yml b/.github/workflows/cleanup_patch-files.yml deleted file mode 100644 index 3dc0e27837..0000000000 --- a/.github/workflows/cleanup_patch-files.yml +++ /dev/null @@ -1,160 +0,0 @@ -name: Cleanup Patch Files -on: - push: - branches: - - 'patch/v*' - -concurrency: - group: cleanup-patch-files - cancel-in-progress: false - -jobs: - cleanup: - name: Cleanup Patch Files - runs-on: ubuntu-latest - if: github.repository == 'backstage/backstage' - permissions: - contents: write - steps: - - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 - with: - egress-policy: audit - - - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - fetch-depth: 0 - token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - - - name: Configure Git - run: | - git config --global user.email noreply@backstage.io - git config --global user.name 'Github patch cleanup workflow' - - - name: Extract PR numbers from commit messages - id: extract-pr-numbers - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - script: | - const { execSync } = require('child_process'); - - // Extract PR numbers from commits pushed to the patch branch - // Commits have messages in format: "Patch from PR #123" - const beforeSha = context.payload.before; - const afterSha = context.payload.after; - - let commitRange; - if (beforeSha && beforeSha !== '0000000000000000000000000000000000000000') { - commitRange = `${beforeSha}..${afterSha}`; - } else { - // First push to branch, check recent commits - commitRange = `-n 20 ${afterSha}`; - } - - // Get commit messages from git log - let commitMessages; - try { - commitMessages = execSync( - `git log --format=%B ${commitRange}`, - { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] } - ); - } catch (error) { - console.log('No commits found in range'); - commitMessages = ''; - } - - // Extract PR numbers from commit messages matching "Patch from PR #123" - const prNumbers = new Set(); - const prPattern = /Patch from PR #(\d+)/g; - let match; - - while ((match = prPattern.exec(commitMessages)) !== null) { - prNumbers.add(parseInt(match[1], 10)); - } - - // Convert to sorted array - const prNumbersArray = Array.from(prNumbers).sort((a, b) => a - b); - - if (prNumbersArray.length > 0) { - console.log(`Found PR numbers: ${prNumbersArray.join(', ')}`); - core.setOutput('pr_numbers', JSON.stringify(prNumbersArray)); - core.setOutput('has_pr_numbers', 'true'); - } else { - console.log('No PR numbers found in commit messages'); - core.setOutput('pr_numbers', '[]'); - core.setOutput('has_pr_numbers', 'false'); - } - - - name: Checkout master - if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true' - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - ref: master - token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - - - name: Delete patch files - if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - PR_NUMBERS: ${{ steps.extract-pr-numbers.outputs.pr_numbers }} - REF_NAME: ${{ github.ref_name }} - with: - github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - script: | - const { execSync } = require('child_process'); - const fs = require('fs'); - const path = require('path'); - - const prNumbers = JSON.parse(process.env.PR_NUMBERS); - const patchesDir = path.join(process.cwd(), '.patches'); - - if (!fs.existsSync(patchesDir)) { - console.log('No .patches directory found, nothing to clean up'); - return; - } - - const deletedFiles = []; - - // Delete patch files for each PR number - for (const prNum of prNumbers) { - const patchFile = path.join(patchesDir, `pr-${prNum}.txt`); - if (fs.existsSync(patchFile)) { - fs.unlinkSync(patchFile); - deletedFiles.push(patchFile); - console.log(`Deleted ${patchFile}`); - } else { - console.log(`Patch file ${patchFile} not found, skipping`); - } - } - - if (deletedFiles.length === 0) { - console.log('No patch files to delete'); - return; - } - - - name: Commit and push changes - if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true' - run: | - # Check if there are any changes to commit - if git diff --quiet && git diff --cached --quiet; then - echo "No changes to commit" - exit 0 - fi - - git add .patches/ - git commit -m "chore: remove applied patch files after patch release merge - - Removed patch files for PRs included in patch release to ${{ github.ref_name }}" - - git push origin master - - - name: Summary - if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true' - run: | - echo "## Patch Files Cleanup Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Patch release to ${{ github.ref_name }} has been merged." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Patch files have been removed from the master branch." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index f7bb55c1b9..75e0a759df 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -25,13 +25,13 @@ jobs: egress-policy: audit - name: checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: backstage ref: ${{ github.event.client_payload.version && env.RELEASE_VERSION || github.ref }} - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth @@ -59,7 +59,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Build and push uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index 52684d5529..ea6ab36249 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -54,12 +54,12 @@ jobs: result-encoding: string - name: checkout latest release - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: refs/tags/${{ steps.find-release.outputs.result }} - name: Use Node.js 22.x - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth @@ -69,17 +69,6 @@ jobs: with: cache-prefix: ${{ runner.os }}-v22.x - - name: build API reference - run: yarn build:api-docs - - - name: upload API reference - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: stable-reference - path: docs/reference/ - if-no-files-found: error - retention-days: 1 - # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore package-docs cache uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 @@ -89,13 +78,13 @@ jobs: restore-keys: | ${{ runner.os }}-v${{ matrix.node-version }}-package-docs-stable- - - name: build API reference (beta) + - name: build API reference run: yarn backstage-repo-tools package-docs - - name: upload API reference (beta) + - name: upload API reference uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: stable-reference-beta + name: stable-reference path: type-docs/ if-no-files-found: error retention-days: 1 @@ -140,10 +129,10 @@ jobs: egress-policy: audit - name: checkout master - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Use Node.js 22.x - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth @@ -153,17 +142,6 @@ jobs: with: cache-prefix: ${{ runner.os }}-v22.x - - name: build API reference - run: yarn build:api-docs - - - name: upload API reference - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: next-reference - path: docs/reference/ - if-no-files-found: error - retention-days: 1 - # Use the lower-level cache actions for the success cache, so that we can store the cache even on failed builds - name: restore package-docs cache uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 @@ -173,13 +151,13 @@ jobs: restore-keys: | ${{ runner.os }}-v${{ matrix.node-version }}-package-docs- - - name: build API reference (beta) + - name: build API reference run: yarn backstage-repo-tools package-docs - - name: upload API reference (beta) + - name: upload API reference uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: - name: next-reference-beta + name: next-reference path: type-docs/ if-no-files-found: error retention-days: 1 @@ -245,14 +223,14 @@ jobs: egress-policy: audit - name: Use Node.js 22.x - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x registry-url: https://registry.npmjs.org/ # Needed for auth # Stable docs - name: checkout latest release - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: refs/tags/${{ needs.stable.outputs.release }} @@ -260,23 +238,31 @@ jobs: run: yarn install --immutable working-directory: microsite - - name: download stable reference - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: stable-reference - path: docs/reference - - name: download stable OpenAPI API docs uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: stable-openapi-docs path: docs - - name: grab lastest releases docs + - name: grab latest releases docs run: | git fetch origin master --depth 1 git checkout FETCH_HEAD -- docs/releases + - name: create missing generated file + run: | + mkdir -p docs/reference + cat > docs/reference/index.md < docs/reference/index.md < ` in the root of the repository. +Once a patch has been applied and merged, manually delete the corresponding patch file from this directory. The patch script will automatically skip patches that have already been applied to the target branch, so it's safe to re-run the script even if some patches are already present. + ## GitHub Workflow A GitHub workflow automatically keeps a "Patch Release" PR in sync with the patches listed in this directory. When you add, modify, or remove patch files, the workflow will: diff --git a/.patches/create-pr-patch.js b/.patches/create-pr-patch.js index 528d4fc404..381810a6ff 100755 --- a/.patches/create-pr-patch.js +++ b/.patches/create-pr-patch.js @@ -1,7 +1,7 @@ #!/usr/bin/env node -const fs = require('fs'); -const path = require('path'); +const fs = require('node:fs'); +const path = require('node:path'); const prNumber = process.argv[2]; const description = process.argv.slice(3).join(' '); diff --git a/.patches/pr-32524.txt b/.patches/pr-32524.txt new file mode 100644 index 0000000000..d5aa035f3f --- /dev/null +++ b/.patches/pr-32524.txt @@ -0,0 +1 @@ +Depend on a version of the zod library that has the version 3 and 4 exports \ No newline at end of file diff --git a/.storybook/main.ts b/.storybook/main.ts index 6a13b1b0a7..f891cd9692 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -26,16 +26,14 @@ const allStories = isChromatic ]; const rootPath = '../'; -const storiesSrcMdx = 'src/**/*.mdx'; const storiesSrcGlob = 'src/**/*.stories.@(js|jsx|mjs|ts|tsx)'; const getStoriesPath = (element: string, pattern: string) => posix.join(rootPath, element, pattern); -const stories = allStories.flatMap(element => [ - getStoriesPath(element, storiesSrcMdx), +const stories = allStories.map(element => getStoriesPath(element, storiesSrcGlob), -]); +); // Resolve absolute path of a package. Needed in monorepos. function getAbsolutePath(value: string): any { diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index ccd7adc57f..12122e6200 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -33,7 +33,6 @@ export default definePreview({ { value: 'light', icon: 'circlehollow', title: 'Light' }, { value: 'dark', icon: 'circle', title: 'Dark' }, ], - showName: true, dynamicTitle: true, }, }, @@ -47,7 +46,6 @@ export default definePreview({ { value: 'backstage', title: 'Backstage' }, { value: 'spotify', title: 'Spotify' }, ], - showName: true, dynamicTitle: true, }, }, diff --git a/ADOPTERS.md b/ADOPTERS.md index 2b3cad0b25..b1b36bc155 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -91,7 +91,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [HP Inc](https://www.hp.com) | [Damon Kaswell](https://github.com/dekoding) | DevEx engagement hub (dev portal: docs, standards, Q&A) and extensive assets catalog (APIs, services, code, data, etc.) for the pan-HP internal developer community. | | [VMware](https://www.vmware.com) | [Waldir Montoya](https://github.com/waldirmontoya25), [Kris Applegate](https://github.com/krisapplegate), [Jamie Klassen](https://github.com/jamieklassen) | Part of [Tanzu Application Platform](https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/index.html) offering; internal developer portal | | [Ualá](https://www.uala.com.ar/) | [Santiago Bernal](https://github.com/sabernal) | Initial work being done to centralize documentation for all our microservices and APIs, as well as scaffolding new services and tracking code quality | -| [IKEA IT AB](https://www.ingka.com) | [@bjornramberg](https://github.com/bjornramberg), [@supriyachitale](https://github.com/supriyachitale) | Supporting engineers at scale with self serve access and connecting the dots of our engineering platform and services, enabling product teams to move faster and go further, and unleashing innovation, reuse and co-creation across the organisation. | +| [IKEA Retail](https://www.ingka.com) | [Björn Ramberg](https://github.com/bjornramberg), [Martin Norin](https://github.com/martin-norin) | Centralized developer portal, supporting the internal engineering community with self serve access, documentation, templates, catalog and more. | | [Invitae](https://www.invitae.com/en) | [@ryan-hanchett](https://github.com/ryan-hanchett), [@gmandler42](https://github.com/gmandler42) | Centralized Developer Experience portal, putting all of our tooling behind a single pane of glass and creating a living service catalog. | | [Fortum](https://www.fortum.com/) | [@brunoamaroalmeida](https://github.com/brunoamaroalmeida), [@dhaval-vithalani](https://github.com/dhaval-vithalani), [@sunilkumarmohanty](https://github.com/sunilkumarmohanty) | A central portal containing information about our applications, services, processes and other software assets to be used by Fortum software engineering community. | | [Procore](https://www.procore.com/jobs/engineering) | [@shayon](https://github.com/Shayon), [@kurtaking](https://github.com/kurtaking), [@pc-bob](https://github.com/pc-bob) | Developer portal - centralized software catalog and templates to enable self serve and reduce cognitive load. | diff --git a/OWNERS.md b/OWNERS.md index 764237d9c1..214b76a40e 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -71,6 +71,7 @@ Scope: The Backstage design system, component library, as well as surrounding to | ------------------- | ------------ | ------------- | --------------------------------------------- | ------------- | | Charles de Dreuille | Spotify | | [cdedreuille](https://github.com/cdedreuille) | `cdedreuille` | | Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip` | +| Johan Persson | Spotify | | [Naycon](https://github.com/Naycon) | `naycon` | ### Documentation diff --git a/REVIEWING.md b/REVIEWING.md index 66af880f43..27da917805 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -62,6 +62,37 @@ Some things that changeset should NOT contain are: - Documentation - changesets can describe new features, but it should not be relied on for documenting them. Documentation should either be placed in [TSDoc](https://tsdoc.org) comments, package README, or [./docs/](./docs/). - Diffs of internal code, for example mirroring what the pull request changes _inside_ a plugin rather than public surfaces. This is not of interest to the reader of a package changelog. Sometimes, however, a small and concise diff can be used in a changeset to illustrate changes that the user will have to make in _their own_ Backstage installation as part of an upgrade, specifically when breaking changes are made to a package. +### Backstage UI Changeset Format + +Changesets for `@backstage/ui` must follow a standardized format to enable proper documentation generation. See [`.changeset/README.md`](.changeset/README.md#backstage-ui-changeset-format) for the complete guide. + +**Required structure:** + +```markdown +--- +'@backstage/ui': patch +--- + +Brief one-line summary of the change + +Optional detailed description with any markdown content. + +**Migration:** + +Migration instructions (only for breaking changes) + +**Affected components:** Button, ButtonIcon +``` + +**Key requirements:** + +1. **Affected components marker** - Must end with `**Affected components:**` followed by comma-separated component names +2. **Migration marker** - Use `**Migration:**` (bold, with colon) for breaking changes only +3. **No headings** - Never use `##`, `###`, or `####` inside changeset entries (these break semantic structure when the entry becomes a list item in CHANGELOG.md) +4. **Bold markers** - Use bold text markers, not plain text, for reliable parsing + +These markers enable the documentation system to extract metadata for per-component changelogs and separate migration guides. + ### When is a changeset needed? In general our changeset feedback bot will take care of informing whether a changeset is needed or not, but there are some edge cases. Whether a changeset is needed depends mostly on what files have been changed, but sometimes also on the kind of change that has been made. diff --git a/SECURITY.md b/SECURITY.md index 5c6c8106d5..46f36f0a67 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,7 +8,7 @@ See our [Release & Versioning Policy](https://backstage.io/docs/overview/version Please report sensitive security issues via Spotify's [bug-bounty program](https://hackerone.com/spotify) rather than GitHub. -If you have questions about a potential vulnerability, please reach out on Discord by asking for a maintainer in the `#support` channel, or via direct message to a maintainer. +If you have questions about a potential vulnerability, please reach out on Discord by asking for a maintainer in the `#security` channel, or via direct message to a maintainer. ## Remediation and Notification Process diff --git a/contrib/catalog/ImmediateEntityProvider.ts b/contrib/catalog/ImmediateEntityProvider.ts index 9f9f986567..ba17f19c9d 100644 --- a/contrib/catalog/ImmediateEntityProvider.ts +++ b/contrib/catalog/ImmediateEntityProvider.ts @@ -16,7 +16,7 @@ import { EntityProviderConnection, } from '@backstage/plugin-catalog-node'; import { parseEntityYaml } from '@backstage/plugin-catalog-node'; -import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; import bodyParser from 'body-parser'; import express from 'express'; import Router from 'express-promise-router'; diff --git a/docs-ui/.eslintrc.json b/docs-ui/.eslintrc.json deleted file mode 100644 index be99640f75..0000000000 --- a/docs-ui/.eslintrc.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": ["next/core-web-vitals", "next/typescript"], - "rules": { - "notice/notice": "off", - "react/forbid-elements": "off", - "jsx-a11y/alt-text": "off" - } -} diff --git a/docs-ui/eslint.config.mjs b/docs-ui/eslint.config.mjs new file mode 100644 index 0000000000..2cb66fd509 --- /dev/null +++ b/docs-ui/eslint.config.mjs @@ -0,0 +1,16 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import nextCoreWebVitals from 'eslint-config-next/core-web-vitals'; + +const eslintConfig = defineConfig([ + ...nextCoreWebVitals, + globalIgnores(['.next/**', 'dist/**', 'node_modules/**']), + { + rules: { + 'notice/notice': 'off', + 'react/forbid-elements': 'off', + 'jsx-a11y/alt-text': 'off', + }, + }, +]); + +export default eslintConfig; diff --git a/docs-ui/package.json b/docs-ui/package.json index ce9b8d070f..bb0b1a393d 100644 --- a/docs-ui/package.json +++ b/docs-ui/package.json @@ -5,17 +5,18 @@ "scripts": { "prebuild": "yarn sync:css", "build": "next build", - "lint": "next lint", + "lint": "eslint .", "prestart": "yarn sync:css", "start": "next dev", "sync:changelog": "node scripts/sync-changelog.mjs", "sync:changelog:dry-run": "node scripts/sync-changelog.mjs --dry-run", + "sync:changelog:force": "node scripts/sync-changelog.mjs --force", "sync:css": "node scripts/sync-css.js", "sync:css:watch": "node scripts/sync-css.js --watch" }, "resolutions": { - "@types/react": "19.1.9", - "@types/react-dom": "19.1.7" + "@types/react": "19.2.10", + "@types/react-dom": "19.2.3" }, "dependencies": { "@base-ui-components/react": "^1.0.0-beta.4", @@ -24,18 +25,18 @@ "@lezer/highlight": "^1.2.1", "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", - "@next/mdx": "15.5.7", + "@next/mdx": "16.1.6", "@remixicon/react": "^4.6.0", "@uiw/codemirror-themes": "^4.23.7", "@uiw/react-codemirror": "^4.23.7", "clsx": "^2.1.1", "html-react-parser": "^5.2.5", "motion": "^12.4.1", - "next": "15.5.9", + "next": "16.1.6", "next-mdx-remote-client": "^2.1.2", "prop-types": "^15.8.1", - "react": "19.1.1", - "react-dom": "19.1.1", + "react": "19.2.4", + "react-dom": "19.2.4", "shiki": "^3.13.0" }, "devDependencies": { @@ -43,11 +44,11 @@ "@shikijs/transformers": "^3.13.0", "@types/mdx": "^2.0.13", "@types/node": "^22.13.14", - "@types/react": "19.1.9", - "@types/react-dom": "19.1.7", + "@types/react": "19.2.10", + "@types/react-dom": "19.2.3", "chokidar": "^3.6.0", - "eslint": "^8", - "eslint-config-next": "15.5.7", + "eslint": "^9", + "eslint-config-next": "16.1.6", "lightningcss": "^1.28.2", "typescript": "^5", "unified": "^11.0.4" diff --git a/docs-ui/public/theme-backstage.css b/docs-ui/public/theme-backstage.css deleted file mode 100644 index 9dd84816cf..0000000000 --- a/docs-ui/public/theme-backstage.css +++ /dev/null @@ -1 +0,0 @@ -@layer tokens{:root{--bui-font-regular:system-ui;--bui-font-monospace:ui-monospace,"Menlo","Monaco","Consolas","Liberation Mono","Courier New",monospace;--bui-font-weight-regular:400;--bui-font-weight-bold:600;--bui-font-size-1:.625rem;--bui-font-size-2:.75rem;--bui-font-size-3:.875rem;--bui-font-size-4:1rem;--bui-font-size-5:1.25rem;--bui-font-size-6:1.5rem;--bui-font-size-7:2rem;--bui-font-size-8:3rem;--bui-font-size-9:4rem;--bui-font-size-10:5.75rem;--bui-space:.25rem;--bui-space-0_5:calc(var(--bui-space)*.5);--bui-space-1:var(--bui-space);--bui-space-1_5:calc(var(--bui-space)*1.5);--bui-space-2:calc(var(--bui-space)*2);--bui-space-3:calc(var(--bui-space)*3);--bui-space-4:calc(var(--bui-space)*4);--bui-space-5:calc(var(--bui-space)*5);--bui-space-6:calc(var(--bui-space)*6);--bui-space-7:calc(var(--bui-space)*7);--bui-space-8:calc(var(--bui-space)*8);--bui-space-9:calc(var(--bui-space)*9);--bui-space-10:calc(var(--bui-space)*10);--bui-space-11:calc(var(--bui-space)*11);--bui-space-12:calc(var(--bui-space)*12);--bui-space-13:calc(var(--bui-space)*13);--bui-space-14:calc(var(--bui-space)*14);--bui-radius-1:calc(.125rem);--bui-radius-2:calc(.25rem);--bui-radius-3:calc(.5rem);--bui-radius-4:calc(.75rem);--bui-radius-5:calc(1rem);--bui-radius-6:calc(1.25rem);--bui-radius-full:9999px;--bui-black:#000;--bui-white:#fff;--bui-gray-1:#f8f8f8;--bui-gray-2:#ececec;--bui-gray-3:#d9d9d9;--bui-gray-4:#c1c1c1;--bui-gray-5:#9e9e9e;--bui-gray-6:#8c8c8c;--bui-gray-7:#757575;--bui-gray-8:#595959;--bui-bg-surface-0:var(--bui-gray-1);--bui-bg-surface-1:var(--bui-white);--bui-bg-surface-2:var(--bui-gray-1);--bui-bg-surface-3:var(--bui-gray-2);--bui-bg-solid:#1f5493;--bui-bg-solid-hover:#163a66;--bui-bg-solid-pressed:#0f2b4e;--bui-bg-solid-disabled:#163a66;--bui-bg-neutral-on-surface-0:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-0-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-0-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-0-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-1:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-1-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-1-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-1-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-2:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-2-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-2-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-2-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-3:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-3-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-3-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-3-disabled:oklch(0% 0 0/.06);--bui-bg-danger:#feebe7;--bui-bg-warning:#fff2b2;--bui-bg-success:#e6f6eb;--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#1f5493;--bui-fg-link-hover:#1f2d5c;--bui-fg-disabled:#9e9e9e;--bui-fg-solid:var(--bui-white);--bui-fg-solid-disabled:#98a8bc;--bui-fg-tint:#1f5493;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#0000001a;--bui-border-hover:#0003;--bui-border-pressed:#0006;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#a0a0a03b;--bui-scrollbar-thumb:#a0a0a0;--bui-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--bui-animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite}[data-theme-mode=dark]{--bui-gray-1:#191919;--bui-gray-2:#242424;--bui-gray-3:#373737;--bui-gray-4:#464646;--bui-gray-5:#575757;--bui-gray-6:#7b7b7b;--bui-gray-7:#9e9e9e;--bui-gray-8:#b4b4b4;--bui-bg-surface-0:#333;--bui-bg-surface-1:var(--bui-gray-1);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-surface-3:var(--bui-gray-3);--bui-bg-solid:#9cc9ff;--bui-bg-solid-hover:#83b9fd;--bui-bg-solid-pressed:#83b9fd;--bui-bg-solid-disabled:#1b3d68;--bui-bg-neutral-on-surface-0:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-0-hover:oklch(100% 0 0/.14);--bui-bg-neutral-on-surface-0-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-0-disabled:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-1:oklch(100% 0 0/.06);--bui-bg-neutral-on-surface-1-hover:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-1-pressed:oklch(100% 0 0/.16);--bui-bg-neutral-on-surface-1-disabled:oklch(100% 0 0/.06);--bui-bg-neutral-on-surface-2:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-2-hover:oklch(100% 0 0/.12);--bui-bg-neutral-on-surface-2-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-2-disabled:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-3:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-3-hover:oklch(100% 0 0/.12);--bui-bg-neutral-on-surface-3-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-3-disabled:oklch(100% 0 0/.08);--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#9cc9ff;--bui-fg-link-hover:#7eb5f7;--bui-fg-disabled:var(--bui-gray-7);--bui-fg-solid:#101821;--bui-fg-solid-disabled:#6191cc;--bui-fg-tint:#9cc9ff;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:#ffffff1f;--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#3636363a;--bui-scrollbar-thumb:#575757;--bui-shadow:none}}@layer base{*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:100%;tab-size:4;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;line-height:1.15}body{margin:0}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{border-color:currentColor}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}:where(a){color:inherit;text-decoration:none}@keyframes pulse{50%{opacity:.5}}body{background-color:var(--bui-bg-surface-0);color:var(--bui-fg-primary);font-family:var(--bui-font-regular);font-weight:var(--bui-font-weight-regular);font-size:var(--bui-font-size-3);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}[data-theme-mode=dark]{color-scheme:dark}[data-theme-mode=light]{color-scheme:light}}@layer components;@layer utilities{.bui-p{padding:var(--p)}.bui-p-0\.5{padding:var(--bui-space-0_5)}.bui-p-1{padding:var(--bui-space-1)}.bui-p-1\.5{padding:var(--bui-space-1_5)}.bui-p-2{padding:var(--bui-space-2)}.bui-p-3{padding:var(--bui-space-3)}.bui-p-4{padding:var(--bui-space-4)}.bui-p-5{padding:var(--bui-space-5)}.bui-p-6{padding:var(--bui-space-6)}.bui-p-7{padding:var(--bui-space-7)}.bui-p-8{padding:var(--bui-space-8)}.bui-p-9{padding:var(--bui-space-9)}.bui-p-10{padding:var(--bui-space-10)}.bui-p-11{padding:var(--bui-space-11)}.bui-p-12{padding:var(--bui-space-12)}.bui-p-13{padding:var(--bui-space-13)}.bui-p-14{padding:var(--bui-space-14)}@media (width>=640px){.xs\:bui-p{padding:var(--p-xs)}.xs\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xs\:bui-p-1{padding:var(--bui-space-1)}.xs\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xs\:bui-p-2{padding:var(--bui-space-2)}.xs\:bui-p-3{padding:var(--bui-space-3)}.xs\:bui-p-4{padding:var(--bui-space-4)}.xs\:bui-p-5{padding:var(--bui-space-5)}.xs\:bui-p-6{padding:var(--bui-space-6)}.xs\:bui-p-7{padding:var(--bui-space-7)}.xs\:bui-p-8{padding:var(--bui-space-8)}.xs\:bui-p-9{padding:var(--bui-space-9)}.xs\:bui-p-10{padding:var(--bui-space-10)}.xs\:bui-p-11{padding:var(--bui-space-11)}.xs\:bui-p-12{padding:var(--bui-space-12)}.xs\:bui-p-13{padding:var(--bui-space-13)}.xs\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-p{padding:var(--p-sm)}.sm\:bui-p-0\.5{padding:var(--bui-space-0_5)}.sm\:bui-p-1{padding:var(--bui-space-1)}.sm\:bui-p-1\.5{padding:var(--bui-space-1_5)}.sm\:bui-p-2{padding:var(--bui-space-2)}.sm\:bui-p-3{padding:var(--bui-space-3)}.sm\:bui-p-4{padding:var(--bui-space-4)}.sm\:bui-p-5{padding:var(--bui-space-5)}.sm\:bui-p-6{padding:var(--bui-space-6)}.sm\:bui-p-7{padding:var(--bui-space-7)}.sm\:bui-p-8{padding:var(--bui-space-8)}.sm\:bui-p-9{padding:var(--bui-space-9)}.sm\:bui-p-10{padding:var(--bui-space-10)}.sm\:bui-p-11{padding:var(--bui-space-11)}.sm\:bui-p-12{padding:var(--bui-space-12)}.sm\:bui-p-13{padding:var(--bui-space-13)}.sm\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-p{padding:var(--p-md)}.md\:bui-p-0\.5{padding:var(--bui-space-0_5)}.md\:bui-p-1{padding:var(--bui-space-1)}.md\:bui-p-1\.5{padding:var(--bui-space-1_5)}.md\:bui-p-2{padding:var(--bui-space-2)}.md\:bui-p-3{padding:var(--bui-space-3)}.md\:bui-p-4{padding:var(--bui-space-4)}.md\:bui-p-5{padding:var(--bui-space-5)}.md\:bui-p-6{padding:var(--bui-space-6)}.md\:bui-p-7{padding:var(--bui-space-7)}.md\:bui-p-8{padding:var(--bui-space-8)}.md\:bui-p-9{padding:var(--bui-space-9)}.md\:bui-p-10{padding:var(--bui-space-10)}.md\:bui-p-11{padding:var(--bui-space-11)}.md\:bui-p-12{padding:var(--bui-space-12)}.md\:bui-p-13{padding:var(--bui-space-13)}.md\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-p{padding:var(--p-lg)}.lg\:bui-p-0\.5{padding:var(--bui-space-0_5)}.lg\:bui-p-1{padding:var(--bui-space-1)}.lg\:bui-p-1\.5{padding:var(--bui-space-1_5)}.lg\:bui-p-2{padding:var(--bui-space-2)}.lg\:bui-p-3{padding:var(--bui-space-3)}.lg\:bui-p-4{padding:var(--bui-space-4)}.lg\:bui-p-5{padding:var(--bui-space-5)}.lg\:bui-p-6{padding:var(--bui-space-6)}.lg\:bui-p-7{padding:var(--bui-space-7)}.lg\:bui-p-8{padding:var(--bui-space-8)}.lg\:bui-p-9{padding:var(--bui-space-9)}.lg\:bui-p-10{padding:var(--bui-space-10)}.lg\:bui-p-11{padding:var(--bui-space-11)}.lg\:bui-p-12{padding:var(--bui-space-12)}.lg\:bui-p-13{padding:var(--bui-space-13)}.lg\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-p{padding:var(--p-xl)}.xl\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xl\:bui-p-1{padding:var(--bui-space-1)}.xl\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xl\:bui-p-2{padding:var(--bui-space-2)}.xl\:bui-p-3{padding:var(--bui-space-3)}.xl\:bui-p-4{padding:var(--bui-space-4)}.xl\:bui-p-5{padding:var(--bui-space-5)}.xl\:bui-p-6{padding:var(--bui-space-6)}.xl\:bui-p-7{padding:var(--bui-space-7)}.xl\:bui-p-8{padding:var(--bui-space-8)}.xl\:bui-p-9{padding:var(--bui-space-9)}.xl\:bui-p-10{padding:var(--bui-space-10)}.xl\:bui-p-11{padding:var(--bui-space-11)}.xl\:bui-p-12{padding:var(--bui-space-12)}.xl\:bui-p-13{padding:var(--bui-space-13)}.xl\:bui-p-14{padding:var(--bui-space-14)}}.bui-pl{padding-left:var(--pl)}.bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.bui-pl-1{padding-left:var(--bui-space-1)}.bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.bui-pl-2{padding-left:var(--bui-space-2)}.bui-pl-3{padding-left:var(--bui-space-3)}.bui-pl-4{padding-left:var(--bui-space-4)}.bui-pl-5{padding-left:var(--bui-space-5)}.bui-pl-6{padding-left:var(--bui-space-6)}.bui-pl-7{padding-left:var(--bui-space-7)}.bui-pl-8{padding-left:var(--bui-space-8)}.bui-pl-9{padding-left:var(--bui-space-9)}.bui-pl-10{padding-left:var(--bui-space-10)}.bui-pl-11{padding-left:var(--bui-space-11)}.bui-pl-12{padding-left:var(--bui-space-12)}.bui-pl-13{padding-left:var(--bui-space-13)}.bui-pl-14{padding-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pl{padding-left:var(--pl-xs)}.xs\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xs\:bui-pl-1{padding-left:var(--bui-space-1)}.xs\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xs\:bui-pl-2{padding-left:var(--bui-space-2)}.xs\:bui-pl-3{padding-left:var(--bui-space-3)}.xs\:bui-pl-4{padding-left:var(--bui-space-4)}.xs\:bui-pl-5{padding-left:var(--bui-space-5)}.xs\:bui-pl-6{padding-left:var(--bui-space-6)}.xs\:bui-pl-7{padding-left:var(--bui-space-7)}.xs\:bui-pl-8{padding-left:var(--bui-space-8)}.xs\:bui-pl-9{padding-left:var(--bui-space-9)}.xs\:bui-pl-10{padding-left:var(--bui-space-10)}.xs\:bui-pl-11{padding-left:var(--bui-space-11)}.xs\:bui-pl-12{padding-left:var(--bui-space-12)}.xs\:bui-pl-13{padding-left:var(--bui-space-13)}.xs\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pl{padding-left:var(--pl-sm)}.sm\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.sm\:bui-pl-1{padding-left:var(--bui-space-1)}.sm\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.sm\:bui-pl-2{padding-left:var(--bui-space-2)}.sm\:bui-pl-3{padding-left:var(--bui-space-3)}.sm\:bui-pl-4{padding-left:var(--bui-space-4)}.sm\:bui-pl-5{padding-left:var(--bui-space-5)}.sm\:bui-pl-6{padding-left:var(--bui-space-6)}.sm\:bui-pl-7{padding-left:var(--bui-space-7)}.sm\:bui-pl-8{padding-left:var(--bui-space-8)}.sm\:bui-pl-9{padding-left:var(--bui-space-9)}.sm\:bui-pl-10{padding-left:var(--bui-space-10)}.sm\:bui-pl-11{padding-left:var(--bui-space-11)}.sm\:bui-pl-12{padding-left:var(--bui-space-12)}.sm\:bui-pl-13{padding-left:var(--bui-space-13)}.sm\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pl{padding-left:var(--pl-md)}.md\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.md\:bui-pl-1{padding-left:var(--bui-space-1)}.md\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.md\:bui-pl-2{padding-left:var(--bui-space-2)}.md\:bui-pl-3{padding-left:var(--bui-space-3)}.md\:bui-pl-4{padding-left:var(--bui-space-4)}.md\:bui-pl-5{padding-left:var(--bui-space-5)}.md\:bui-pl-6{padding-left:var(--bui-space-6)}.md\:bui-pl-7{padding-left:var(--bui-space-7)}.md\:bui-pl-8{padding-left:var(--bui-space-8)}.md\:bui-pl-9{padding-left:var(--bui-space-9)}.md\:bui-pl-10{padding-left:var(--bui-space-10)}.md\:bui-pl-11{padding-left:var(--bui-space-11)}.md\:bui-pl-12{padding-left:var(--bui-space-12)}.md\:bui-pl-13{padding-left:var(--bui-space-13)}.md\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pl{padding-left:var(--pl-lg)}.lg\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.lg\:bui-pl-1{padding-left:var(--bui-space-1)}.lg\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.lg\:bui-pl-2{padding-left:var(--bui-space-2)}.lg\:bui-pl-3{padding-left:var(--bui-space-3)}.lg\:bui-pl-4{padding-left:var(--bui-space-4)}.lg\:bui-pl-5{padding-left:var(--bui-space-5)}.lg\:bui-pl-6{padding-left:var(--bui-space-6)}.lg\:bui-pl-7{padding-left:var(--bui-space-7)}.lg\:bui-pl-8{padding-left:var(--bui-space-8)}.lg\:bui-pl-9{padding-left:var(--bui-space-9)}.lg\:bui-pl-10{padding-left:var(--bui-space-10)}.lg\:bui-pl-11{padding-left:var(--bui-space-11)}.lg\:bui-pl-12{padding-left:var(--bui-space-12)}.lg\:bui-pl-13{padding-left:var(--bui-space-13)}.lg\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pl{padding-left:var(--pl-xl)}.xl\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xl\:bui-pl-1{padding-left:var(--bui-space-1)}.xl\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xl\:bui-pl-2{padding-left:var(--bui-space-2)}.xl\:bui-pl-3{padding-left:var(--bui-space-3)}.xl\:bui-pl-4{padding-left:var(--bui-space-4)}.xl\:bui-pl-5{padding-left:var(--bui-space-5)}.xl\:bui-pl-6{padding-left:var(--bui-space-6)}.xl\:bui-pl-7{padding-left:var(--bui-space-7)}.xl\:bui-pl-8{padding-left:var(--bui-space-8)}.xl\:bui-pl-9{padding-left:var(--bui-space-9)}.xl\:bui-pl-10{padding-left:var(--bui-space-10)}.xl\:bui-pl-11{padding-left:var(--bui-space-11)}.xl\:bui-pl-12{padding-left:var(--bui-space-12)}.xl\:bui-pl-13{padding-left:var(--bui-space-13)}.xl\:bui-pl-14{padding-left:var(--bui-space-14)}}.bui-pr{padding-right:var(--pr)}.bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.bui-pr-1{padding-right:var(--bui-space-1)}.bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.bui-pr-2{padding-right:var(--bui-space-2)}.bui-pr-3{padding-right:var(--bui-space-3)}.bui-pr-4{padding-right:var(--bui-space-4)}.bui-pr-5{padding-right:var(--bui-space-5)}.bui-pr-6{padding-right:var(--bui-space-6)}.bui-pr-7{padding-right:var(--bui-space-7)}.bui-pr-8{padding-right:var(--bui-space-8)}.bui-pr-9{padding-right:var(--bui-space-9)}.bui-pr-10{padding-right:var(--bui-space-10)}.bui-pr-11{padding-right:var(--bui-space-11)}.bui-pr-12{padding-right:var(--bui-space-12)}.bui-pr-13{padding-right:var(--bui-space-13)}.bui-pr-14{padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pr{padding-right:var(--pr-xs)}.xs\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xs\:bui-pr-1{padding-right:var(--bui-space-1)}.xs\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xs\:bui-pr-2{padding-right:var(--bui-space-2)}.xs\:bui-pr-3{padding-right:var(--bui-space-3)}.xs\:bui-pr-4{padding-right:var(--bui-space-4)}.xs\:bui-pr-5{padding-right:var(--bui-space-5)}.xs\:bui-pr-6{padding-right:var(--bui-space-6)}.xs\:bui-pr-7{padding-right:var(--bui-space-7)}.xs\:bui-pr-8{padding-right:var(--bui-space-8)}.xs\:bui-pr-9{padding-right:var(--bui-space-9)}.xs\:bui-pr-10{padding-right:var(--bui-space-10)}.xs\:bui-pr-11{padding-right:var(--bui-space-11)}.xs\:bui-pr-12{padding-right:var(--bui-space-12)}.xs\:bui-pr-13{padding-right:var(--bui-space-13)}.xs\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pr{padding-right:var(--pr-sm)}.sm\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.sm\:bui-pr-1{padding-right:var(--bui-space-1)}.sm\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.sm\:bui-pr-2{padding-right:var(--bui-space-2)}.sm\:bui-pr-3{padding-right:var(--bui-space-3)}.sm\:bui-pr-4{padding-right:var(--bui-space-4)}.sm\:bui-pr-5{padding-right:var(--bui-space-5)}.sm\:bui-pr-6{padding-right:var(--bui-space-6)}.sm\:bui-pr-7{padding-right:var(--bui-space-7)}.sm\:bui-pr-8{padding-right:var(--bui-space-8)}.sm\:bui-pr-9{padding-right:var(--bui-space-9)}.sm\:bui-pr-10{padding-right:var(--bui-space-10)}.sm\:bui-pr-11{padding-right:var(--bui-space-11)}.sm\:bui-pr-12{padding-right:var(--bui-space-12)}.sm\:bui-pr-13{padding-right:var(--bui-space-13)}.sm\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pr{padding-right:var(--pr-md)}.md\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.md\:bui-pr-1{padding-right:var(--bui-space-1)}.md\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.md\:bui-pr-2{padding-right:var(--bui-space-2)}.md\:bui-pr-3{padding-right:var(--bui-space-3)}.md\:bui-pr-4{padding-right:var(--bui-space-4)}.md\:bui-pr-5{padding-right:var(--bui-space-5)}.md\:bui-pr-6{padding-right:var(--bui-space-6)}.md\:bui-pr-7{padding-right:var(--bui-space-7)}.md\:bui-pr-8{padding-right:var(--bui-space-8)}.md\:bui-pr-9{padding-right:var(--bui-space-9)}.md\:bui-pr-10{padding-right:var(--bui-space-10)}.md\:bui-pr-11{padding-right:var(--bui-space-11)}.md\:bui-pr-12{padding-right:var(--bui-space-12)}.md\:bui-pr-13{padding-right:var(--bui-space-13)}.md\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pr{padding-right:var(--pr-lg)}.lg\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.lg\:bui-pr-1{padding-right:var(--bui-space-1)}.lg\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.lg\:bui-pr-2{padding-right:var(--bui-space-2)}.lg\:bui-pr-3{padding-right:var(--bui-space-3)}.lg\:bui-pr-4{padding-right:var(--bui-space-4)}.lg\:bui-pr-5{padding-right:var(--bui-space-5)}.lg\:bui-pr-6{padding-right:var(--bui-space-6)}.lg\:bui-pr-7{padding-right:var(--bui-space-7)}.lg\:bui-pr-8{padding-right:var(--bui-space-8)}.lg\:bui-pr-9{padding-right:var(--bui-space-9)}.lg\:bui-pr-10{padding-right:var(--bui-space-10)}.lg\:bui-pr-11{padding-right:var(--bui-space-11)}.lg\:bui-pr-12{padding-right:var(--bui-space-12)}.lg\:bui-pr-13{padding-right:var(--bui-space-13)}.lg\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pr{padding-right:var(--pr-xl)}.xl\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xl\:bui-pr-1{padding-right:var(--bui-space-1)}.xl\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xl\:bui-pr-2{padding-right:var(--bui-space-2)}.xl\:bui-pr-3{padding-right:var(--bui-space-3)}.xl\:bui-pr-4{padding-right:var(--bui-space-4)}.xl\:bui-pr-5{padding-right:var(--bui-space-5)}.xl\:bui-pr-6{padding-right:var(--bui-space-6)}.xl\:bui-pr-7{padding-right:var(--bui-space-7)}.xl\:bui-pr-8{padding-right:var(--bui-space-8)}.xl\:bui-pr-9{padding-right:var(--bui-space-9)}.xl\:bui-pr-10{padding-right:var(--bui-space-10)}.xl\:bui-pr-11{padding-right:var(--bui-space-11)}.xl\:bui-pr-12{padding-right:var(--bui-space-12)}.xl\:bui-pr-13{padding-right:var(--bui-space-13)}.xl\:bui-pr-14{padding-right:var(--bui-space-14)}}.bui-pt{padding-top:var(--pt)}.bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.bui-pt-1{padding-top:var(--bui-space-1)}.bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.bui-pt-2{padding-top:var(--bui-space-2)}.bui-pt-3{padding-top:var(--bui-space-3)}.bui-pt-4{padding-top:var(--bui-space-4)}.bui-pt-5{padding-top:var(--bui-space-5)}.bui-pt-6{padding-top:var(--bui-space-6)}.bui-pt-7{padding-top:var(--bui-space-7)}.bui-pt-8{padding-top:var(--bui-space-8)}.bui-pt-9{padding-top:var(--bui-space-9)}.bui-pt-10{padding-top:var(--bui-space-10)}.bui-pt-11{padding-top:var(--bui-space-11)}.bui-pt-12{padding-top:var(--bui-space-12)}.bui-pt-13{padding-top:var(--bui-space-13)}.bui-pt-14{padding-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pt{padding-top:var(--pt-xs)}.xs\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xs\:bui-pt-1{padding-top:var(--bui-space-1)}.xs\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xs\:bui-pt-2{padding-top:var(--bui-space-2)}.xs\:bui-pt-3{padding-top:var(--bui-space-3)}.xs\:bui-pt-4{padding-top:var(--bui-space-4)}.xs\:bui-pt-5{padding-top:var(--bui-space-5)}.xs\:bui-pt-6{padding-top:var(--bui-space-6)}.xs\:bui-pt-7{padding-top:var(--bui-space-7)}.xs\:bui-pt-8{padding-top:var(--bui-space-8)}.xs\:bui-pt-9{padding-top:var(--bui-space-9)}.xs\:bui-pt-10{padding-top:var(--bui-space-10)}.xs\:bui-pt-11{padding-top:var(--bui-space-11)}.xs\:bui-pt-12{padding-top:var(--bui-space-12)}.xs\:bui-pt-13{padding-top:var(--bui-space-13)}.xs\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pt{padding-top:var(--pt-sm)}.sm\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.sm\:bui-pt-1{padding-top:var(--bui-space-1)}.sm\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.sm\:bui-pt-2{padding-top:var(--bui-space-2)}.sm\:bui-pt-3{padding-top:var(--bui-space-3)}.sm\:bui-pt-4{padding-top:var(--bui-space-4)}.sm\:bui-pt-5{padding-top:var(--bui-space-5)}.sm\:bui-pt-6{padding-top:var(--bui-space-6)}.sm\:bui-pt-7{padding-top:var(--bui-space-7)}.sm\:bui-pt-8{padding-top:var(--bui-space-8)}.sm\:bui-pt-9{padding-top:var(--bui-space-9)}.sm\:bui-pt-10{padding-top:var(--bui-space-10)}.sm\:bui-pt-11{padding-top:var(--bui-space-11)}.sm\:bui-pt-12{padding-top:var(--bui-space-12)}.sm\:bui-pt-13{padding-top:var(--bui-space-13)}.sm\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pt{padding-top:var(--pt-md)}.md\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.md\:bui-pt-1{padding-top:var(--bui-space-1)}.md\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.md\:bui-pt-2{padding-top:var(--bui-space-2)}.md\:bui-pt-3{padding-top:var(--bui-space-3)}.md\:bui-pt-4{padding-top:var(--bui-space-4)}.md\:bui-pt-5{padding-top:var(--bui-space-5)}.md\:bui-pt-6{padding-top:var(--bui-space-6)}.md\:bui-pt-7{padding-top:var(--bui-space-7)}.md\:bui-pt-8{padding-top:var(--bui-space-8)}.md\:bui-pt-9{padding-top:var(--bui-space-9)}.md\:bui-pt-10{padding-top:var(--bui-space-10)}.md\:bui-pt-11{padding-top:var(--bui-space-11)}.md\:bui-pt-12{padding-top:var(--bui-space-12)}.md\:bui-pt-13{padding-top:var(--bui-space-13)}.md\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pt{padding-top:var(--pt-lg)}.lg\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.lg\:bui-pt-1{padding-top:var(--bui-space-1)}.lg\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.lg\:bui-pt-2{padding-top:var(--bui-space-2)}.lg\:bui-pt-3{padding-top:var(--bui-space-3)}.lg\:bui-pt-4{padding-top:var(--bui-space-4)}.lg\:bui-pt-5{padding-top:var(--bui-space-5)}.lg\:bui-pt-6{padding-top:var(--bui-space-6)}.lg\:bui-pt-7{padding-top:var(--bui-space-7)}.lg\:bui-pt-8{padding-top:var(--bui-space-8)}.lg\:bui-pt-9{padding-top:var(--bui-space-9)}.lg\:bui-pt-10{padding-top:var(--bui-space-10)}.lg\:bui-pt-11{padding-top:var(--bui-space-11)}.lg\:bui-pt-12{padding-top:var(--bui-space-12)}.lg\:bui-pt-13{padding-top:var(--bui-space-13)}.lg\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pt{padding-top:var(--pt-xl)}.xl\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xl\:bui-pt-1{padding-top:var(--bui-space-1)}.xl\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xl\:bui-pt-2{padding-top:var(--bui-space-2)}.xl\:bui-pt-3{padding-top:var(--bui-space-3)}.xl\:bui-pt-4{padding-top:var(--bui-space-4)}.xl\:bui-pt-5{padding-top:var(--bui-space-5)}.xl\:bui-pt-6{padding-top:var(--bui-space-6)}.xl\:bui-pt-7{padding-top:var(--bui-space-7)}.xl\:bui-pt-8{padding-top:var(--bui-space-8)}.xl\:bui-pt-9{padding-top:var(--bui-space-9)}.xl\:bui-pt-10{padding-top:var(--bui-space-10)}.xl\:bui-pt-11{padding-top:var(--bui-space-11)}.xl\:bui-pt-12{padding-top:var(--bui-space-12)}.xl\:bui-pt-13{padding-top:var(--bui-space-13)}.xl\:bui-pt-14{padding-top:var(--bui-space-14)}}.bui-pb{padding-bottom:var(--pb)}.bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.bui-pb-1{padding-bottom:var(--bui-space-1)}.bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.bui-pb-2{padding-bottom:var(--bui-space-2)}.bui-pb-3{padding-bottom:var(--bui-space-3)}.bui-pb-4{padding-bottom:var(--bui-space-4)}.bui-pb-5{padding-bottom:var(--bui-space-5)}.bui-pb-6{padding-bottom:var(--bui-space-6)}.bui-pb-7{padding-bottom:var(--bui-space-7)}.bui-pb-8{padding-bottom:var(--bui-space-8)}.bui-pb-9{padding-bottom:var(--bui-space-9)}.bui-pb-10{padding-bottom:var(--bui-space-10)}.bui-pb-11{padding-bottom:var(--bui-space-11)}.bui-pb-12{padding-bottom:var(--bui-space-12)}.bui-pb-13{padding-bottom:var(--bui-space-13)}.bui-pb-14{padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pb{padding-bottom:var(--pb-xs)}.xs\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xs\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xs\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xs\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xs\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xs\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xs\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xs\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xs\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xs\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xs\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xs\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xs\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xs\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xs\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xs\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pb{padding-bottom:var(--pb-sm)}.sm\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.sm\:bui-pb-1{padding-bottom:var(--bui-space-1)}.sm\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.sm\:bui-pb-2{padding-bottom:var(--bui-space-2)}.sm\:bui-pb-3{padding-bottom:var(--bui-space-3)}.sm\:bui-pb-4{padding-bottom:var(--bui-space-4)}.sm\:bui-pb-5{padding-bottom:var(--bui-space-5)}.sm\:bui-pb-6{padding-bottom:var(--bui-space-6)}.sm\:bui-pb-7{padding-bottom:var(--bui-space-7)}.sm\:bui-pb-8{padding-bottom:var(--bui-space-8)}.sm\:bui-pb-9{padding-bottom:var(--bui-space-9)}.sm\:bui-pb-10{padding-bottom:var(--bui-space-10)}.sm\:bui-pb-11{padding-bottom:var(--bui-space-11)}.sm\:bui-pb-12{padding-bottom:var(--bui-space-12)}.sm\:bui-pb-13{padding-bottom:var(--bui-space-13)}.sm\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pb{padding-bottom:var(--pb-md)}.md\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.md\:bui-pb-1{padding-bottom:var(--bui-space-1)}.md\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.md\:bui-pb-2{padding-bottom:var(--bui-space-2)}.md\:bui-pb-3{padding-bottom:var(--bui-space-3)}.md\:bui-pb-4{padding-bottom:var(--bui-space-4)}.md\:bui-pb-5{padding-bottom:var(--bui-space-5)}.md\:bui-pb-6{padding-bottom:var(--bui-space-6)}.md\:bui-pb-7{padding-bottom:var(--bui-space-7)}.md\:bui-pb-8{padding-bottom:var(--bui-space-8)}.md\:bui-pb-9{padding-bottom:var(--bui-space-9)}.md\:bui-pb-10{padding-bottom:var(--bui-space-10)}.md\:bui-pb-11{padding-bottom:var(--bui-space-11)}.md\:bui-pb-12{padding-bottom:var(--bui-space-12)}.md\:bui-pb-13{padding-bottom:var(--bui-space-13)}.md\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pb{padding-bottom:var(--pb-lg)}.lg\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.lg\:bui-pb-1{padding-bottom:var(--bui-space-1)}.lg\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.lg\:bui-pb-2{padding-bottom:var(--bui-space-2)}.lg\:bui-pb-3{padding-bottom:var(--bui-space-3)}.lg\:bui-pb-4{padding-bottom:var(--bui-space-4)}.lg\:bui-pb-5{padding-bottom:var(--bui-space-5)}.lg\:bui-pb-6{padding-bottom:var(--bui-space-6)}.lg\:bui-pb-7{padding-bottom:var(--bui-space-7)}.lg\:bui-pb-8{padding-bottom:var(--bui-space-8)}.lg\:bui-pb-9{padding-bottom:var(--bui-space-9)}.lg\:bui-pb-10{padding-bottom:var(--bui-space-10)}.lg\:bui-pb-11{padding-bottom:var(--bui-space-11)}.lg\:bui-pb-12{padding-bottom:var(--bui-space-12)}.lg\:bui-pb-13{padding-bottom:var(--bui-space-13)}.lg\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pb{padding-bottom:var(--pb-xl)}.xl\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xl\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xl\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xl\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xl\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xl\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xl\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xl\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xl\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xl\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xl\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xl\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xl\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xl\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xl\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xl\:bui-pb-14{padding-bottom:var(--bui-space-14)}}.bui-py{padding-top:var(--py);padding-bottom:var(--py)}.bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-py{padding-top:var(--py-xs);padding-bottom:var(--py-xs)}.xs\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xs\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xs\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xs\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xs\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xs\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xs\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xs\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xs\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xs\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xs\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xs\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xs\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xs\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xs\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xs\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-py{padding-top:var(--py-sm);padding-bottom:var(--py-sm)}.sm\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.sm\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.sm\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.sm\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.sm\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.sm\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.sm\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.sm\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.sm\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.sm\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.sm\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.sm\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.sm\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.sm\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.sm\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.sm\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-py{padding-top:var(--py-md);padding-bottom:var(--py-md)}.md\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.md\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.md\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.md\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.md\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.md\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.md\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.md\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.md\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.md\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.md\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.md\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.md\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.md\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.md\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.md\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-py{padding-top:var(--py-lg);padding-bottom:var(--py-lg)}.lg\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.lg\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.lg\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.lg\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.lg\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.lg\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.lg\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.lg\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.lg\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.lg\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.lg\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.lg\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.lg\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.lg\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.lg\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.lg\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-py{padding-top:var(--py-xl);padding-bottom:var(--py-xl)}.xl\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xl\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xl\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xl\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xl\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xl\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xl\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xl\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xl\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xl\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xl\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xl\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xl\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xl\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xl\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xl\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}.bui-px{padding-left:var(--px);padding-right:var(--px)}.bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-px{padding-left:var(--px-xs);padding-right:var(--px-xs)}.xs\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xs\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xs\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xs\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xs\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xs\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xs\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xs\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xs\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xs\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xs\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xs\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xs\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xs\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xs\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xs\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-px{padding-left:var(--px-sm);padding-right:var(--px-sm)}.sm\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.sm\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.sm\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.sm\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.sm\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.sm\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.sm\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.sm\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.sm\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.sm\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.sm\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.sm\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.sm\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.sm\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.sm\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.sm\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-px{padding-left:var(--px-md);padding-right:var(--px-md)}.md\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.md\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.md\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.md\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.md\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.md\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.md\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.md\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.md\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.md\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.md\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.md\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.md\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.md\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.md\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.md\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-px{padding-left:var(--px-lg);padding-right:var(--px-lg)}.lg\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.lg\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.lg\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.lg\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.lg\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.lg\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.lg\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.lg\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.lg\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.lg\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.lg\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.lg\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.lg\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.lg\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.lg\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.lg\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-px{padding-left:var(--px-xl);padding-right:var(--px-xl)}.xl\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xl\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xl\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xl\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xl\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xl\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xl\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xl\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xl\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xl\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xl\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xl\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xl\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xl\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xl\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xl\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}.bui-m{margin:var(--m)}.bui-m-0\.5{margin:var(--bui-space-0_5)}.bui-m-1{margin:var(--bui-space-1)}.bui-m-1\.5{margin:var(--bui-space-1_5)}.bui-m-2{margin:var(--bui-space-2)}.bui-m-3{margin:var(--bui-space-3)}.bui-m-4{margin:var(--bui-space-4)}.bui-m-5{margin:var(--bui-space-5)}.bui-m-6{margin:var(--bui-space-6)}.bui-m-7{margin:var(--bui-space-7)}.bui-m-8{margin:var(--bui-space-8)}.bui-m-9{margin:var(--bui-space-9)}.bui-m-10{margin:var(--bui-space-10)}.bui-m-11{margin:var(--bui-space-11)}.bui-m-12{margin:var(--bui-space-12)}.bui-m-13{margin:var(--bui-space-13)}.bui-m-14{margin:var(--bui-space-14)}@media (width>=640px){.xs\:bui-m{margin:var(--p-xs)}.xs\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xs\:bui-m-1{margin:var(--bui-space-1)}.xs\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xs\:bui-m-2{margin:var(--bui-space-2)}.xs\:bui-m-3{margin:var(--bui-space-3)}.xs\:bui-m-4{margin:var(--bui-space-4)}.xs\:bui-m-5{margin:var(--bui-space-5)}.xs\:bui-m-6{margin:var(--bui-space-6)}.xs\:bui-m-7{margin:var(--bui-space-7)}.xs\:bui-m-8{margin:var(--bui-space-8)}.xs\:bui-m-9{margin:var(--bui-space-9)}.xs\:bui-m-10{margin:var(--bui-space-10)}.xs\:bui-m-11{margin:var(--bui-space-11)}.xs\:bui-m-12{margin:var(--bui-space-12)}.xs\:bui-m-13{margin:var(--bui-space-13)}.xs\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-m{margin:var(--p-sm)}.sm\:bui-m-0\.5{margin:var(--bui-space-0_5)}.sm\:bui-m-1{margin:var(--bui-space-1)}.sm\:bui-m-1\.5{margin:var(--bui-space-1_5)}.sm\:bui-m-2{margin:var(--bui-space-2)}.sm\:bui-m-3{margin:var(--bui-space-3)}.sm\:bui-m-4{margin:var(--bui-space-4)}.sm\:bui-m-5{margin:var(--bui-space-5)}.sm\:bui-m-6{margin:var(--bui-space-6)}.sm\:bui-m-7{margin:var(--bui-space-7)}.sm\:bui-m-8{margin:var(--bui-space-8)}.sm\:bui-m-9{margin:var(--bui-space-9)}.sm\:bui-m-10{margin:var(--bui-space-10)}.sm\:bui-m-11{margin:var(--bui-space-11)}.sm\:bui-m-12{margin:var(--bui-space-12)}.sm\:bui-m-13{margin:var(--bui-space-13)}.sm\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-m{margin:var(--p-md)}.md\:bui-m-0\.5{margin:var(--bui-space-0_5)}.md\:bui-m-1{margin:var(--bui-space-1)}.md\:bui-m-1\.5{margin:var(--bui-space-1_5)}.md\:bui-m-2{margin:var(--bui-space-2)}.md\:bui-m-3{margin:var(--bui-space-3)}.md\:bui-m-4{margin:var(--bui-space-4)}.md\:bui-m-5{margin:var(--bui-space-5)}.md\:bui-m-6{margin:var(--bui-space-6)}.md\:bui-m-7{margin:var(--bui-space-7)}.md\:bui-m-8{margin:var(--bui-space-8)}.md\:bui-m-9{margin:var(--bui-space-9)}.md\:bui-m-10{margin:var(--bui-space-10)}.md\:bui-m-11{margin:var(--bui-space-11)}.md\:bui-m-12{margin:var(--bui-space-12)}.md\:bui-m-13{margin:var(--bui-space-13)}.md\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-m{margin:var(--p-lg)}.lg\:bui-m-0\.5{margin:var(--bui-space-0_5)}.lg\:bui-m-1{margin:var(--bui-space-1)}.lg\:bui-m-1\.5{margin:var(--bui-space-1_5)}.lg\:bui-m-2{margin:var(--bui-space-2)}.lg\:bui-m-3{margin:var(--bui-space-3)}.lg\:bui-m-4{margin:var(--bui-space-4)}.lg\:bui-m-5{margin:var(--bui-space-5)}.lg\:bui-m-6{margin:var(--bui-space-6)}.lg\:bui-m-7{margin:var(--bui-space-7)}.lg\:bui-m-8{margin:var(--bui-space-8)}.lg\:bui-m-9{margin:var(--bui-space-9)}.lg\:bui-m-10{margin:var(--bui-space-10)}.lg\:bui-m-11{margin:var(--bui-space-11)}.lg\:bui-m-12{margin:var(--bui-space-12)}.lg\:bui-m-13{margin:var(--bui-space-13)}.lg\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-m{margin:var(--p-xl)}.xl\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xl\:bui-m-1{margin:var(--bui-space-1)}.xl\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xl\:bui-m-2{margin:var(--bui-space-2)}.xl\:bui-m-3{margin:var(--bui-space-3)}.xl\:bui-m-4{margin:var(--bui-space-4)}.xl\:bui-m-5{margin:var(--bui-space-5)}.xl\:bui-m-6{margin:var(--bui-space-6)}.xl\:bui-m-7{margin:var(--bui-space-7)}.xl\:bui-m-8{margin:var(--bui-space-8)}.xl\:bui-m-9{margin:var(--bui-space-9)}.xl\:bui-m-10{margin:var(--bui-space-10)}.xl\:bui-m-11{margin:var(--bui-space-11)}.xl\:bui-m-12{margin:var(--bui-space-12)}.xl\:bui-m-13{margin:var(--bui-space-13)}.xl\:bui-m-14{margin:var(--bui-space-14)}}.bui-ml{margin-left:var(--ml)}.bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.bui-ml-1{margin-left:var(--bui-space-1)}.bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.bui-ml-2{margin-left:var(--bui-space-2)}.bui-ml-3{margin-left:var(--bui-space-3)}.bui-ml-4{margin-left:var(--bui-space-4)}.bui-ml-5{margin-left:var(--bui-space-5)}.bui-ml-6{margin-left:var(--bui-space-6)}.bui-ml-7{margin-left:var(--bui-space-7)}.bui-ml-8{margin-left:var(--bui-space-8)}.bui-ml-9{margin-left:var(--bui-space-9)}.bui-ml-10{margin-left:var(--bui-space-10)}.bui-ml-11{margin-left:var(--bui-space-11)}.bui-ml-12{margin-left:var(--bui-space-12)}.bui-ml-13{margin-left:var(--bui-space-13)}.bui-ml-14{margin-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-ml{margin-left:var(--ml-xs)}.xs\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xs\:bui-ml-1{margin-left:var(--bui-space-1)}.xs\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xs\:bui-ml-2{margin-left:var(--bui-space-2)}.xs\:bui-ml-3{margin-left:var(--bui-space-3)}.xs\:bui-ml-4{margin-left:var(--bui-space-4)}.xs\:bui-ml-5{margin-left:var(--bui-space-5)}.xs\:bui-ml-6{margin-left:var(--bui-space-6)}.xs\:bui-ml-7{margin-left:var(--bui-space-7)}.xs\:bui-ml-8{margin-left:var(--bui-space-8)}.xs\:bui-ml-9{margin-left:var(--bui-space-9)}.xs\:bui-ml-10{margin-left:var(--bui-space-10)}.xs\:bui-ml-11{margin-left:var(--bui-space-11)}.xs\:bui-ml-12{margin-left:var(--bui-space-12)}.xs\:bui-ml-13{margin-left:var(--bui-space-13)}.xs\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-ml{margin-left:var(--ml-sm)}.sm\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.sm\:bui-ml-1{margin-left:var(--bui-space-1)}.sm\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.sm\:bui-ml-2{margin-left:var(--bui-space-2)}.sm\:bui-ml-3{margin-left:var(--bui-space-3)}.sm\:bui-ml-4{margin-left:var(--bui-space-4)}.sm\:bui-ml-5{margin-left:var(--bui-space-5)}.sm\:bui-ml-6{margin-left:var(--bui-space-6)}.sm\:bui-ml-7{margin-left:var(--bui-space-7)}.sm\:bui-ml-8{margin-left:var(--bui-space-8)}.sm\:bui-ml-9{margin-left:var(--bui-space-9)}.sm\:bui-ml-10{margin-left:var(--bui-space-10)}.sm\:bui-ml-11{margin-left:var(--bui-space-11)}.sm\:bui-ml-12{margin-left:var(--bui-space-12)}.sm\:bui-ml-13{margin-left:var(--bui-space-13)}.sm\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-ml{margin-left:var(--ml-md)}.md\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.md\:bui-ml-1{margin-left:var(--bui-space-1)}.md\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.md\:bui-ml-2{margin-left:var(--bui-space-2)}.md\:bui-ml-3{margin-left:var(--bui-space-3)}.md\:bui-ml-4{margin-left:var(--bui-space-4)}.md\:bui-ml-5{margin-left:var(--bui-space-5)}.md\:bui-ml-6{margin-left:var(--bui-space-6)}.md\:bui-ml-7{margin-left:var(--bui-space-7)}.md\:bui-ml-8{margin-left:var(--bui-space-8)}.md\:bui-ml-9{margin-left:var(--bui-space-9)}.md\:bui-ml-10{margin-left:var(--bui-space-10)}.md\:bui-ml-11{margin-left:var(--bui-space-11)}.md\:bui-ml-12{margin-left:var(--bui-space-12)}.md\:bui-ml-13{margin-left:var(--bui-space-13)}.md\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-ml{margin-left:var(--ml-lg)}.lg\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.lg\:bui-ml-1{margin-left:var(--bui-space-1)}.lg\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.lg\:bui-ml-2{margin-left:var(--bui-space-2)}.lg\:bui-ml-3{margin-left:var(--bui-space-3)}.lg\:bui-ml-4{margin-left:var(--bui-space-4)}.lg\:bui-ml-5{margin-left:var(--bui-space-5)}.lg\:bui-ml-6{margin-left:var(--bui-space-6)}.lg\:bui-ml-7{margin-left:var(--bui-space-7)}.lg\:bui-ml-8{margin-left:var(--bui-space-8)}.lg\:bui-ml-9{margin-left:var(--bui-space-9)}.lg\:bui-ml-10{margin-left:var(--bui-space-10)}.lg\:bui-ml-11{margin-left:var(--bui-space-11)}.lg\:bui-ml-12{margin-left:var(--bui-space-12)}.lg\:bui-ml-13{margin-left:var(--bui-space-13)}.lg\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-ml{margin-left:var(--ml-xl)}.xl\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xl\:bui-ml-1{margin-left:var(--bui-space-1)}.xl\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xl\:bui-ml-2{margin-left:var(--bui-space-2)}.xl\:bui-ml-3{margin-left:var(--bui-space-3)}.xl\:bui-ml-4{margin-left:var(--bui-space-4)}.xl\:bui-ml-5{margin-left:var(--bui-space-5)}.xl\:bui-ml-6{margin-left:var(--bui-space-6)}.xl\:bui-ml-7{margin-left:var(--bui-space-7)}.xl\:bui-ml-8{margin-left:var(--bui-space-8)}.xl\:bui-ml-9{margin-left:var(--bui-space-9)}.xl\:bui-ml-10{margin-left:var(--bui-space-10)}.xl\:bui-ml-11{margin-left:var(--bui-space-11)}.xl\:bui-ml-12{margin-left:var(--bui-space-12)}.xl\:bui-ml-13{margin-left:var(--bui-space-13)}.xl\:bui-ml-14{margin-left:var(--bui-space-14)}}.bui-mr{margin-right:var(--mr)}.bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.bui-mr-1{margin-right:var(--bui-space-1)}.bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.bui-mr-2{margin-right:var(--bui-space-2)}.bui-mr-3{margin-right:var(--bui-space-3)}.bui-mr-4{margin-right:var(--bui-space-4)}.bui-mr-5{margin-right:var(--bui-space-5)}.bui-mr-6{margin-right:var(--bui-space-6)}.bui-mr-7{margin-right:var(--bui-space-7)}.bui-mr-8{margin-right:var(--bui-space-8)}.bui-mr-9{margin-right:var(--bui-space-9)}.bui-mr-10{margin-right:var(--bui-space-10)}.bui-mr-11{margin-right:var(--bui-space-11)}.bui-mr-12{margin-right:var(--bui-space-12)}.bui-mr-13{margin-right:var(--bui-space-13)}.bui-mr-14{margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mr{margin-right:var(--mr-xs)}.xs\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xs\:bui-mr-1{margin-right:var(--bui-space-1)}.xs\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xs\:bui-mr-2{margin-right:var(--bui-space-2)}.xs\:bui-mr-3{margin-right:var(--bui-space-3)}.xs\:bui-mr-4{margin-right:var(--bui-space-4)}.xs\:bui-mr-5{margin-right:var(--bui-space-5)}.xs\:bui-mr-6{margin-right:var(--bui-space-6)}.xs\:bui-mr-7{margin-right:var(--bui-space-7)}.xs\:bui-mr-8{margin-right:var(--bui-space-8)}.xs\:bui-mr-9{margin-right:var(--bui-space-9)}.xs\:bui-mr-10{margin-right:var(--bui-space-10)}.xs\:bui-mr-11{margin-right:var(--bui-space-11)}.xs\:bui-mr-12{margin-right:var(--bui-space-12)}.xs\:bui-mr-13{margin-right:var(--bui-space-13)}.xs\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mr{margin-right:var(--mr-sm)}.sm\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.sm\:bui-mr-1{margin-right:var(--bui-space-1)}.sm\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.sm\:bui-mr-2{margin-right:var(--bui-space-2)}.sm\:bui-mr-3{margin-right:var(--bui-space-3)}.sm\:bui-mr-4{margin-right:var(--bui-space-4)}.sm\:bui-mr-5{margin-right:var(--bui-space-5)}.sm\:bui-mr-6{margin-right:var(--bui-space-6)}.sm\:bui-mr-7{margin-right:var(--bui-space-7)}.sm\:bui-mr-8{margin-right:var(--bui-space-8)}.sm\:bui-mr-9{margin-right:var(--bui-space-9)}.sm\:bui-mr-10{margin-right:var(--bui-space-10)}.sm\:bui-mr-11{margin-right:var(--bui-space-11)}.sm\:bui-mr-12{margin-right:var(--bui-space-12)}.sm\:bui-mr-13{margin-right:var(--bui-space-13)}.sm\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mr{margin-right:var(--mr-md)}.md\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.md\:bui-mr-1{margin-right:var(--bui-space-1)}.md\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.md\:bui-mr-2{margin-right:var(--bui-space-2)}.md\:bui-mr-3{margin-right:var(--bui-space-3)}.md\:bui-mr-4{margin-right:var(--bui-space-4)}.md\:bui-mr-5{margin-right:var(--bui-space-5)}.md\:bui-mr-6{margin-right:var(--bui-space-6)}.md\:bui-mr-7{margin-right:var(--bui-space-7)}.md\:bui-mr-8{margin-right:var(--bui-space-8)}.md\:bui-mr-9{margin-right:var(--bui-space-9)}.md\:bui-mr-10{margin-right:var(--bui-space-10)}.md\:bui-mr-11{margin-right:var(--bui-space-11)}.md\:bui-mr-12{margin-right:var(--bui-space-12)}.md\:bui-mr-13{margin-right:var(--bui-space-13)}.md\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mr{margin-right:var(--mr-lg)}.lg\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.lg\:bui-mr-1{margin-right:var(--bui-space-1)}.lg\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.lg\:bui-mr-2{margin-right:var(--bui-space-2)}.lg\:bui-mr-3{margin-right:var(--bui-space-3)}.lg\:bui-mr-4{margin-right:var(--bui-space-4)}.lg\:bui-mr-5{margin-right:var(--bui-space-5)}.lg\:bui-mr-6{margin-right:var(--bui-space-6)}.lg\:bui-mr-7{margin-right:var(--bui-space-7)}.lg\:bui-mr-8{margin-right:var(--bui-space-8)}.lg\:bui-mr-9{margin-right:var(--bui-space-9)}.lg\:bui-mr-10{margin-right:var(--bui-space-10)}.lg\:bui-mr-11{margin-right:var(--bui-space-11)}.lg\:bui-mr-12{margin-right:var(--bui-space-12)}.lg\:bui-mr-13{margin-right:var(--bui-space-13)}.lg\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mr{margin-right:var(--mr-xl)}.xl\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xl\:bui-mr-1{margin-right:var(--bui-space-1)}.xl\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xl\:bui-mr-2{margin-right:var(--bui-space-2)}.xl\:bui-mr-3{margin-right:var(--bui-space-3)}.xl\:bui-mr-4{margin-right:var(--bui-space-4)}.xl\:bui-mr-5{margin-right:var(--bui-space-5)}.xl\:bui-mr-6{margin-right:var(--bui-space-6)}.xl\:bui-mr-7{margin-right:var(--bui-space-7)}.xl\:bui-mr-8{margin-right:var(--bui-space-8)}.xl\:bui-mr-9{margin-right:var(--bui-space-9)}.xl\:bui-mr-10{margin-right:var(--bui-space-10)}.xl\:bui-mr-11{margin-right:var(--bui-space-11)}.xl\:bui-mr-12{margin-right:var(--bui-space-12)}.xl\:bui-mr-13{margin-right:var(--bui-space-13)}.xl\:bui-mr-14{margin-right:var(--bui-space-14)}}.bui-mt{margin-top:var(--mt)}.bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.bui-mt-1{margin-top:var(--bui-space-1)}.bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.bui-mt-2{margin-top:var(--bui-space-2)}.bui-mt-3{margin-top:var(--bui-space-3)}.bui-mt-4{margin-top:var(--bui-space-4)}.bui-mt-5{margin-top:var(--bui-space-5)}.bui-mt-6{margin-top:var(--bui-space-6)}.bui-mt-7{margin-top:var(--bui-space-7)}.bui-mt-8{margin-top:var(--bui-space-8)}.bui-mt-9{margin-top:var(--bui-space-9)}.bui-mt-10{margin-top:var(--bui-space-10)}.bui-mt-11{margin-top:var(--bui-space-11)}.bui-mt-12{margin-top:var(--bui-space-12)}.bui-mt-13{margin-top:var(--bui-space-13)}.bui-mt-14{margin-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mt{margin-top:var(--mt-xs)}.xs\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xs\:bui-mt-1{margin-top:var(--bui-space-1)}.xs\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xs\:bui-mt-2{margin-top:var(--bui-space-2)}.xs\:bui-mt-3{margin-top:var(--bui-space-3)}.xs\:bui-mt-4{margin-top:var(--bui-space-4)}.xs\:bui-mt-5{margin-top:var(--bui-space-5)}.xs\:bui-mt-6{margin-top:var(--bui-space-6)}.xs\:bui-mt-7{margin-top:var(--bui-space-7)}.xs\:bui-mt-8{margin-top:var(--bui-space-8)}.xs\:bui-mt-9{margin-top:var(--bui-space-9)}.xs\:bui-mt-10{margin-top:var(--bui-space-10)}.xs\:bui-mt-11{margin-top:var(--bui-space-11)}.xs\:bui-mt-12{margin-top:var(--bui-space-12)}.xs\:bui-mt-13{margin-top:var(--bui-space-13)}.xs\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mt{margin-top:var(--mt-sm)}.sm\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.sm\:bui-mt-1{margin-top:var(--bui-space-1)}.sm\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.sm\:bui-mt-2{margin-top:var(--bui-space-2)}.sm\:bui-mt-3{margin-top:var(--bui-space-3)}.sm\:bui-mt-4{margin-top:var(--bui-space-4)}.sm\:bui-mt-5{margin-top:var(--bui-space-5)}.sm\:bui-mt-6{margin-top:var(--bui-space-6)}.sm\:bui-mt-7{margin-top:var(--bui-space-7)}.sm\:bui-mt-8{margin-top:var(--bui-space-8)}.sm\:bui-mt-9{margin-top:var(--bui-space-9)}.sm\:bui-mt-10{margin-top:var(--bui-space-10)}.sm\:bui-mt-11{margin-top:var(--bui-space-11)}.sm\:bui-mt-12{margin-top:var(--bui-space-12)}.sm\:bui-mt-13{margin-top:var(--bui-space-13)}.sm\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mt{margin-top:var(--mt-md)}.md\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.md\:bui-mt-1{margin-top:var(--bui-space-1)}.md\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.md\:bui-mt-2{margin-top:var(--bui-space-2)}.md\:bui-mt-3{margin-top:var(--bui-space-3)}.md\:bui-mt-4{margin-top:var(--bui-space-4)}.md\:bui-mt-5{margin-top:var(--bui-space-5)}.md\:bui-mt-6{margin-top:var(--bui-space-6)}.md\:bui-mt-7{margin-top:var(--bui-space-7)}.md\:bui-mt-8{margin-top:var(--bui-space-8)}.md\:bui-mt-9{margin-top:var(--bui-space-9)}.md\:bui-mt-10{margin-top:var(--bui-space-10)}.md\:bui-mt-11{margin-top:var(--bui-space-11)}.md\:bui-mt-12{margin-top:var(--bui-space-12)}.md\:bui-mt-13{margin-top:var(--bui-space-13)}.md\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mt{margin-top:var(--mt-lg)}.lg\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.lg\:bui-mt-1{margin-top:var(--bui-space-1)}.lg\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.lg\:bui-mt-2{margin-top:var(--bui-space-2)}.lg\:bui-mt-3{margin-top:var(--bui-space-3)}.lg\:bui-mt-4{margin-top:var(--bui-space-4)}.lg\:bui-mt-5{margin-top:var(--bui-space-5)}.lg\:bui-mt-6{margin-top:var(--bui-space-6)}.lg\:bui-mt-7{margin-top:var(--bui-space-7)}.lg\:bui-mt-8{margin-top:var(--bui-space-8)}.lg\:bui-mt-9{margin-top:var(--bui-space-9)}.lg\:bui-mt-10{margin-top:var(--bui-space-10)}.lg\:bui-mt-11{margin-top:var(--bui-space-11)}.lg\:bui-mt-12{margin-top:var(--bui-space-12)}.lg\:bui-mt-13{margin-top:var(--bui-space-13)}.lg\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mt{margin-top:var(--mt-xl)}.xl\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xl\:bui-mt-1{margin-top:var(--bui-space-1)}.xl\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xl\:bui-mt-2{margin-top:var(--bui-space-2)}.xl\:bui-mt-3{margin-top:var(--bui-space-3)}.xl\:bui-mt-4{margin-top:var(--bui-space-4)}.xl\:bui-mt-5{margin-top:var(--bui-space-5)}.xl\:bui-mt-6{margin-top:var(--bui-space-6)}.xl\:bui-mt-7{margin-top:var(--bui-space-7)}.xl\:bui-mt-8{margin-top:var(--bui-space-8)}.xl\:bui-mt-9{margin-top:var(--bui-space-9)}.xl\:bui-mt-10{margin-top:var(--bui-space-10)}.xl\:bui-mt-11{margin-top:var(--bui-space-11)}.xl\:bui-mt-12{margin-top:var(--bui-space-12)}.xl\:bui-mt-13{margin-top:var(--bui-space-13)}.xl\:bui-mt-14{margin-top:var(--bui-space-14)}}.bui-mb{margin-bottom:var(--mb)}.bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.bui-mb-1{margin-bottom:var(--bui-space-1)}.bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.bui-mb-2{margin-bottom:var(--bui-space-2)}.bui-mb-3{margin-bottom:var(--bui-space-3)}.bui-mb-4{margin-bottom:var(--bui-space-4)}.bui-mb-5{margin-bottom:var(--bui-space-5)}.bui-mb-6{margin-bottom:var(--bui-space-6)}.bui-mb-7{margin-bottom:var(--bui-space-7)}.bui-mb-8{margin-bottom:var(--bui-space-8)}.bui-mb-9{margin-bottom:var(--bui-space-9)}.bui-mb-10{margin-bottom:var(--bui-space-10)}.bui-mb-11{margin-bottom:var(--bui-space-11)}.bui-mb-12{margin-bottom:var(--bui-space-12)}.bui-mb-13{margin-bottom:var(--bui-space-13)}.bui-mb-14{margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mb{margin-bottom:var(--mb-xs)}.xs\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xs\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xs\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xs\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xs\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xs\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xs\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xs\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xs\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xs\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xs\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xs\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xs\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xs\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xs\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xs\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mb{margin-bottom:var(--mb-sm)}.sm\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.sm\:bui-mb-1{margin-bottom:var(--bui-space-1)}.sm\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.sm\:bui-mb-2{margin-bottom:var(--bui-space-2)}.sm\:bui-mb-3{margin-bottom:var(--bui-space-3)}.sm\:bui-mb-4{margin-bottom:var(--bui-space-4)}.sm\:bui-mb-5{margin-bottom:var(--bui-space-5)}.sm\:bui-mb-6{margin-bottom:var(--bui-space-6)}.sm\:bui-mb-7{margin-bottom:var(--bui-space-7)}.sm\:bui-mb-8{margin-bottom:var(--bui-space-8)}.sm\:bui-mb-9{margin-bottom:var(--bui-space-9)}.sm\:bui-mb-10{margin-bottom:var(--bui-space-10)}.sm\:bui-mb-11{margin-bottom:var(--bui-space-11)}.sm\:bui-mb-12{margin-bottom:var(--bui-space-12)}.sm\:bui-mb-13{margin-bottom:var(--bui-space-13)}.sm\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mb{margin-bottom:var(--mb-md)}.md\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.md\:bui-mb-1{margin-bottom:var(--bui-space-1)}.md\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.md\:bui-mb-2{margin-bottom:var(--bui-space-2)}.md\:bui-mb-3{margin-bottom:var(--bui-space-3)}.md\:bui-mb-4{margin-bottom:var(--bui-space-4)}.md\:bui-mb-5{margin-bottom:var(--bui-space-5)}.md\:bui-mb-6{margin-bottom:var(--bui-space-6)}.md\:bui-mb-7{margin-bottom:var(--bui-space-7)}.md\:bui-mb-8{margin-bottom:var(--bui-space-8)}.md\:bui-mb-9{margin-bottom:var(--bui-space-9)}.md\:bui-mb-10{margin-bottom:var(--bui-space-10)}.md\:bui-mb-11{margin-bottom:var(--bui-space-11)}.md\:bui-mb-12{margin-bottom:var(--bui-space-12)}.md\:bui-mb-13{margin-bottom:var(--bui-space-13)}.md\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mb{margin-bottom:var(--mb-lg)}.lg\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.lg\:bui-mb-1{margin-bottom:var(--bui-space-1)}.lg\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.lg\:bui-mb-2{margin-bottom:var(--bui-space-2)}.lg\:bui-mb-3{margin-bottom:var(--bui-space-3)}.lg\:bui-mb-4{margin-bottom:var(--bui-space-4)}.lg\:bui-mb-5{margin-bottom:var(--bui-space-5)}.lg\:bui-mb-6{margin-bottom:var(--bui-space-6)}.lg\:bui-mb-7{margin-bottom:var(--bui-space-7)}.lg\:bui-mb-8{margin-bottom:var(--bui-space-8)}.lg\:bui-mb-9{margin-bottom:var(--bui-space-9)}.lg\:bui-mb-10{margin-bottom:var(--bui-space-10)}.lg\:bui-mb-11{margin-bottom:var(--bui-space-11)}.lg\:bui-mb-12{margin-bottom:var(--bui-space-12)}.lg\:bui-mb-13{margin-bottom:var(--bui-space-13)}.lg\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mb{margin-bottom:var(--mb-xl)}.xl\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xl\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xl\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xl\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xl\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xl\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xl\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xl\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xl\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xl\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xl\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xl\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xl\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xl\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xl\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xl\:bui-mb-14{margin-bottom:var(--bui-space-14)}}.bui-my{margin-top:var(--my);margin-bottom:var(--my)}.bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-my{margin-top:var(--my-xs);margin-bottom:var(--my-xs)}.xs\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xs\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xs\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xs\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xs\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xs\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xs\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xs\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xs\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xs\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xs\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xs\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xs\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xs\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xs\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xs\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-my{margin-top:var(--my-sm);margin-bottom:var(--my-sm)}.sm\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.sm\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.sm\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.sm\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.sm\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.sm\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.sm\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.sm\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.sm\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.sm\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.sm\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.sm\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.sm\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.sm\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.sm\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.sm\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-my{margin-top:var(--my-md);margin-bottom:var(--my-md)}.md\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.md\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.md\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.md\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.md\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.md\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.md\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.md\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.md\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.md\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.md\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.md\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.md\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.md\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.md\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.md\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-my{margin-top:var(--my-lg);margin-bottom:var(--my-lg)}.lg\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.lg\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.lg\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.lg\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.lg\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.lg\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.lg\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.lg\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.lg\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.lg\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.lg\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.lg\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.lg\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.lg\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.lg\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.lg\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-my{margin-top:var(--my-xl);margin-bottom:var(--my-xl)}.xl\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xl\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xl\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xl\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xl\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xl\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xl\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xl\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xl\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xl\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xl\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xl\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xl\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xl\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xl\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xl\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}.bui-mx{margin-left:var(--mx);margin-right:var(--mx)}.bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mx{margin-left:var(--mx-xs);margin-right:var(--mx-xs)}.xs\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xs\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xs\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xs\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xs\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xs\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xs\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xs\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xs\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xs\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xs\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xs\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xs\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xs\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xs\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xs\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mx{margin-left:var(--mx-sm);margin-right:var(--mx-sm)}.sm\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.sm\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.sm\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.sm\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.sm\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.sm\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.sm\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.sm\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.sm\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.sm\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.sm\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.sm\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.sm\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.sm\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.sm\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.sm\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mx{margin-left:var(--mx-md);margin-right:var(--mx-md)}.md\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.md\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.md\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.md\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.md\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.md\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.md\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.md\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.md\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.md\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.md\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.md\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.md\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.md\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.md\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.md\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mx{margin-left:var(--mx-lg);margin-right:var(--mx-lg)}.lg\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.lg\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.lg\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.lg\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.lg\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.lg\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.lg\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.lg\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.lg\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.lg\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.lg\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.lg\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.lg\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.lg\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.lg\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.lg\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mx{margin-left:var(--mx-xl);margin-right:var(--mx-xl)}.xl\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xl\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xl\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xl\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xl\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xl\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xl\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xl\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xl\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xl\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xl\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xl\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xl\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xl\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xl\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xl\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}.bui-display-none{display:none}.bui-display-inline{display:inline}.bui-display-inline-block{display:inline-block}.bui-display-block{display:block}@media (width>=640px){.xs\:bui-display-none{display:none}.xs\:bui-display-inline{display:inline}.xs\:bui-display-inline-block{display:inline-block}.xs\:bui-display-block{display:block}}@media (width>=768px){.sm\:bui-display-none{display:none}.sm\:bui-display-inline{display:inline}.sm\:bui-display-inline-block{display:inline-block}.sm\:bui-display-block{display:block}}@media (width>=1024px){.md\:bui-display-none{display:none}.md\:bui-display-inline{display:inline}.md\:bui-display-inline-block{display:inline-block}.md\:bui-display-block{display:block}}@media (width>=1280px){.lg\:bui-display-none{display:none}.lg\:bui-display-inline{display:inline}.lg\:bui-display-inline-block{display:inline-block}.lg\:bui-display-block{display:block}}@media (width>=1536px){.xl\:bui-display-none{display:none}.xl\:bui-display-inline{display:inline}.xl\:bui-display-inline-block{display:inline-block}.xl\:bui-display-block{display:block}}.bui-w{width:var(--width)}.bui-min-w{min-width:var(--min-width)}.bui-max-w{max-width:var(--max-width)}@media (width>=640px){.xs\:bui-w{width:var(--width)}.xs\:bui-min-w{min-width:var(--min-width)}.xs\:bui-max-w{max-width:var(--max-width)}}@media (width>=768px){.sm\:bui-w{width:var(--width)}.sm\:bui-min-w{min-width:var(--min-width)}.sm\:bui-max-w{max-width:var(--max-width)}}@media (width>=1024px){.md\:bui-w{width:var(--width)}.md\:bui-min-w{min-width:var(--min-width)}.md\:bui-max-w{max-width:var(--max-width)}}@media (width>=1280px){.lg\:bui-w{width:var(--width)}.lg\:bui-min-w{min-width:var(--min-width)}.lg\:bui-max-w{max-width:var(--max-width)}}@media (width>=1536px){.xl\:bui-w{width:var(--width)}.xl\:bui-min-w{min-width:var(--min-width)}.xl\:bui-max-w{max-width:var(--max-width)}}.bui-h{height:var(--height)}.bui-min-h{min-height:var(--min-height)}.bui-max-h{max-height:var(--max-height)}@media (width>=640px){.xs\:bui-h{height:var(--height)}.xs\:bui-min-h{min-height:var(--min-height)}.xs\:bui-max-h{max-height:var(--max-height)}}@media (width>=768px){.sm\:bui-h{height:var(--height)}.sm\:bui-min-h{min-height:var(--min-height)}.sm\:bui-max-h{max-height:var(--max-height)}}@media (width>=1024px){.md\:bui-h{height:var(--height)}.md\:bui-min-h{min-height:var(--min-height)}.md\:bui-max-h{max-height:var(--max-height)}}@media (width>=1280px){.lg\:bui-h{height:var(--height)}.lg\:bui-min-h{min-height:var(--min-height)}.lg\:bui-max-h{max-height:var(--max-height)}}@media (width>=1536px){.xl\:bui-h{height:var(--height)}.xl\:bui-min-h{min-height:var(--min-height)}.xl\:bui-max-h{max-height:var(--max-height)}}.bui-position-absolute{position:absolute}.bui-position-fixed{position:fixed}.bui-position-sticky{position:sticky}.bui-position-relative{position:relative}.bui-position-static{position:static}@media (width>=640px){.xs\:bui-position-absolute{position:absolute}.xs\:bui-position-fixed{position:fixed}.xs\:bui-position-sticky{position:sticky}.xs\:bui-position-relative{position:relative}.xs\:bui-position-static{position:static}}@media (width>=768px){.sm\:bui-position-absolute{position:absolute}.sm\:bui-position-fixed{position:fixed}.sm\:bui-position-sticky{position:sticky}.sm\:bui-position-relative{position:relative}.sm\:bui-position-static{position:static}}@media (width>=1024px){.md\:bui-position-absolute{position:absolute}.md\:bui-position-fixed{position:fixed}.md\:bui-position-sticky{position:sticky}.md\:bui-position-relative{position:relative}.md\:bui-position-static{position:static}}@media (width>=1280px){.lg\:bui-position-absolute{position:absolute}.lg\:bui-position-fixed{position:fixed}.lg\:bui-position-sticky{position:sticky}.lg\:bui-position-relative{position:relative}.lg\:bui-position-static{position:static}}@media (width>=1536px){.xl\:bui-position-absolute{position:absolute}.xl\:bui-position-fixed{position:fixed}.xl\:bui-position-sticky{position:sticky}.xl\:bui-position-relative{position:relative}.xl\:bui-position-static{position:static}}.bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.bui-col-span-1{grid-column:span 1/span 1}.bui-col-span-2{grid-column:span 2/span 2}.bui-col-span-3{grid-column:span 3/span 3}.bui-col-span-4{grid-column:span 4/span 4}.bui-col-span-5{grid-column:span 5/span 5}.bui-col-span-6{grid-column:span 6/span 6}.bui-col-span-7{grid-column:span 7/span 7}.bui-col-span-8{grid-column:span 8/span 8}.bui-col-span-9{grid-column:span 9/span 9}.bui-col-span-10{grid-column:span 10/span 10}.bui-col-span-11{grid-column:span 11/span 11}.bui-col-span-12{grid-column:span 12/span 12}.bui-col-span-auto{grid-column:span auto/span auto}.bui-col-start-1{grid-column-start:1}.bui-col-start-2{grid-column-start:2}.bui-col-start-3{grid-column-start:3}.bui-col-start-4{grid-column-start:4}.bui-col-start-5{grid-column-start:5}.bui-col-start-6{grid-column-start:6}.bui-col-start-7{grid-column-start:7}.bui-col-start-8{grid-column-start:8}.bui-col-start-9{grid-column-start:9}.bui-col-start-10{grid-column-start:10}.bui-col-start-11{grid-column-start:11}.bui-col-start-12{grid-column-start:12}.bui-col-start-13{grid-column-start:13}.bui-col-start-auto{grid-column-start:auto}.bui-col-end-1{grid-column-end:1}.bui-col-end-2{grid-column-end:2}.bui-col-end-3{grid-column-end:3}.bui-col-end-4{grid-column-end:4}.bui-col-end-5{grid-column-end:5}.bui-col-end-6{grid-column-end:6}.bui-col-end-7{grid-column-end:7}.bui-col-end-8{grid-column-end:8}.bui-col-end-9{grid-column-end:9}.bui-col-end-10{grid-column-end:10}.bui-col-end-11{grid-column-end:11}.bui-col-end-12{grid-column-end:12}.bui-col-end-13{grid-column-end:13}.bui-col-end-auto{grid-column-end:auto}.bui-row-span-1{grid-row:span 1/span 1}.bui-row-span-2{grid-row:span 2/span 2}.bui-row-span-3{grid-row:span 3/span 3}.bui-row-span-4{grid-row:span 4/span 4}.bui-row-span-5{grid-row:span 5/span 5}.bui-row-span-6{grid-row:span 6/span 6}.bui-row-span-7{grid-row:span 7/span 7}.bui-row-span-8{grid-row:span 8/span 8}.bui-row-span-9{grid-row:span 9/span 9}.bui-row-span-10{grid-row:span 10/span 10}.bui-row-span-11{grid-row:span 11/span 11}.bui-row-span-12{grid-row:span 12/span 12}.bui-row-span-auto{grid-row:span auto/span auto}@media (width>=640px){.xs\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xs\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xs\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xs\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xs\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xs\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xs\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xs\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xs\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xs\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xs\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xs\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xs\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xs\:bui-col-span-1{grid-column:span 1/span 1}.xs\:bui-col-span-2{grid-column:span 2/span 2}.xs\:bui-col-span-3{grid-column:span 3/span 3}.xs\:bui-col-span-4{grid-column:span 4/span 4}.xs\:bui-col-span-5{grid-column:span 5/span 5}.xs\:bui-col-span-6{grid-column:span 6/span 6}.xs\:bui-col-span-7{grid-column:span 7/span 7}.xs\:bui-col-span-8{grid-column:span 8/span 8}.xs\:bui-col-span-9{grid-column:span 9/span 9}.xs\:bui-col-span-10{grid-column:span 10/span 10}.xs\:bui-col-span-11{grid-column:span 11/span 11}.xs\:bui-col-span-12{grid-column:span 12/span 12}.xs\:bui-col-span-auto{grid-column:span auto/span auto}.xs\:bui-col-start-1{grid-column-start:1}.xs\:bui-col-start-2{grid-column-start:2}.xs\:bui-col-start-3{grid-column-start:3}.xs\:bui-col-start-4{grid-column-start:4}.xs\:bui-col-start-5{grid-column-start:5}.xs\:bui-col-start-6{grid-column-start:6}.xs\:bui-col-start-7{grid-column-start:7}.xs\:bui-col-start-8{grid-column-start:8}.xs\:bui-col-start-9{grid-column-start:9}.xs\:bui-col-start-10{grid-column-start:10}.xs\:bui-col-start-11{grid-column-start:11}.xs\:bui-col-start-12{grid-column-start:12}.xs\:bui-col-start-13{grid-column-start:13}.xs\:bui-col-start-auto{grid-column-start:auto}.xs\:bui-col-end-1{grid-column-end:1}.xs\:bui-col-end-2{grid-column-end:2}.xs\:bui-col-end-3{grid-column-end:3}.xs\:bui-col-end-4{grid-column-end:4}.xs\:bui-col-end-5{grid-column-end:5}.xs\:bui-col-end-6{grid-column-end:6}.xs\:bui-col-end-7{grid-column-end:7}.xs\:bui-col-end-8{grid-column-end:8}.xs\:bui-col-end-9{grid-column-end:9}.xs\:bui-col-end-10{grid-column-end:10}.xs\:bui-col-end-11{grid-column-end:11}.xs\:bui-col-end-12{grid-column-end:12}.xs\:bui-col-end-13{grid-column-end:13}.xs\:bui-col-end-auto{grid-column-end:auto}.xs\:bui-row-span-1{grid-row:span 1/span 1}.xs\:bui-row-span-2{grid-row:span 2/span 2}.xs\:bui-row-span-3{grid-row:span 3/span 3}.xs\:bui-row-span-4{grid-row:span 4/span 4}.xs\:bui-row-span-5{grid-row:span 5/span 5}.xs\:bui-row-span-6{grid-row:span 6/span 6}.xs\:bui-row-span-7{grid-row:span 7/span 7}.xs\:bui-row-span-8{grid-row:span 8/span 8}.xs\:bui-row-span-9{grid-row:span 9/span 9}.xs\:bui-row-span-10{grid-row:span 10/span 10}.xs\:bui-row-span-11{grid-row:span 11/span 11}.xs\:bui-row-span-12{grid-row:span 12/span 12}.xs\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=768px){.sm\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.sm\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.sm\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.sm\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.sm\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:bui-col-span-1{grid-column:span 1/span 1}.sm\:bui-col-span-2{grid-column:span 2/span 2}.sm\:bui-col-span-3{grid-column:span 3/span 3}.sm\:bui-col-span-4{grid-column:span 4/span 4}.sm\:bui-col-span-5{grid-column:span 5/span 5}.sm\:bui-col-span-6{grid-column:span 6/span 6}.sm\:bui-col-span-7{grid-column:span 7/span 7}.sm\:bui-col-span-8{grid-column:span 8/span 8}.sm\:bui-col-span-9{grid-column:span 9/span 9}.sm\:bui-col-span-10{grid-column:span 10/span 10}.sm\:bui-col-span-11{grid-column:span 11/span 11}.sm\:bui-col-span-12{grid-column:span 12/span 12}.sm\:bui-col-span-auto{grid-column:span auto/span auto}.sm\:bui-col-start-1{grid-column-start:1}.sm\:bui-col-start-2{grid-column-start:2}.sm\:bui-col-start-3{grid-column-start:3}.sm\:bui-col-start-4{grid-column-start:4}.sm\:bui-col-start-5{grid-column-start:5}.sm\:bui-col-start-6{grid-column-start:6}.sm\:bui-col-start-7{grid-column-start:7}.sm\:bui-col-start-8{grid-column-start:8}.sm\:bui-col-start-9{grid-column-start:9}.sm\:bui-col-start-10{grid-column-start:10}.sm\:bui-col-start-11{grid-column-start:11}.sm\:bui-col-start-12{grid-column-start:12}.sm\:bui-col-start-13{grid-column-start:13}.sm\:bui-col-start-auto{grid-column-start:auto}.sm\:bui-col-end-1{grid-column-end:1}.sm\:bui-col-end-2{grid-column-end:2}.sm\:bui-col-end-3{grid-column-end:3}.sm\:bui-col-end-4{grid-column-end:4}.sm\:bui-col-end-5{grid-column-end:5}.sm\:bui-col-end-6{grid-column-end:6}.sm\:bui-col-end-7{grid-column-end:7}.sm\:bui-col-end-8{grid-column-end:8}.sm\:bui-col-end-9{grid-column-end:9}.sm\:bui-col-end-10{grid-column-end:10}.sm\:bui-col-end-11{grid-column-end:11}.sm\:bui-col-end-12{grid-column-end:12}.sm\:bui-col-end-13{grid-column-end:13}.sm\:bui-col-end-auto{grid-column-end:auto}.sm\:bui-row-span-1{grid-row:span 1/span 1}.sm\:bui-row-span-2{grid-row:span 2/span 2}.sm\:bui-row-span-3{grid-row:span 3/span 3}.sm\:bui-row-span-4{grid-row:span 4/span 4}.sm\:bui-row-span-5{grid-row:span 5/span 5}.sm\:bui-row-span-6{grid-row:span 6/span 6}.sm\:bui-row-span-7{grid-row:span 7/span 7}.sm\:bui-row-span-8{grid-row:span 8/span 8}.sm\:bui-row-span-9{grid-row:span 9/span 9}.sm\:bui-row-span-10{grid-row:span 10/span 10}.sm\:bui-row-span-11{grid-row:span 11/span 11}.sm\:bui-row-span-12{grid-row:span 12/span 12}.sm\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1024px){.md\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.md\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.md\:bui-col-span-1{grid-column:span 1/span 1}.md\:bui-col-span-2{grid-column:span 2/span 2}.md\:bui-col-span-3{grid-column:span 3/span 3}.md\:bui-col-span-4{grid-column:span 4/span 4}.md\:bui-col-span-5{grid-column:span 5/span 5}.md\:bui-col-span-6{grid-column:span 6/span 6}.md\:bui-col-span-7{grid-column:span 7/span 7}.md\:bui-col-span-8{grid-column:span 8/span 8}.md\:bui-col-span-9{grid-column:span 9/span 9}.md\:bui-col-span-10{grid-column:span 10/span 10}.md\:bui-col-span-11{grid-column:span 11/span 11}.md\:bui-col-span-12{grid-column:span 12/span 12}.md\:bui-col-span-auto{grid-column:span auto/span auto}.md\:bui-col-start-1{grid-column-start:1}.md\:bui-col-start-2{grid-column-start:2}.md\:bui-col-start-3{grid-column-start:3}.md\:bui-col-start-4{grid-column-start:4}.md\:bui-col-start-5{grid-column-start:5}.md\:bui-col-start-6{grid-column-start:6}.md\:bui-col-start-7{grid-column-start:7}.md\:bui-col-start-8{grid-column-start:8}.md\:bui-col-start-9{grid-column-start:9}.md\:bui-col-start-10{grid-column-start:10}.md\:bui-col-start-11{grid-column-start:11}.md\:bui-col-start-12{grid-column-start:12}.md\:bui-col-start-13{grid-column-start:13}.md\:bui-col-start-auto{grid-column-start:auto}.md\:bui-col-end-1{grid-column-end:1}.md\:bui-col-end-2{grid-column-end:2}.md\:bui-col-end-3{grid-column-end:3}.md\:bui-col-end-4{grid-column-end:4}.md\:bui-col-end-5{grid-column-end:5}.md\:bui-col-end-6{grid-column-end:6}.md\:bui-col-end-7{grid-column-end:7}.md\:bui-col-end-8{grid-column-end:8}.md\:bui-col-end-9{grid-column-end:9}.md\:bui-col-end-10{grid-column-end:10}.md\:bui-col-end-11{grid-column-end:11}.md\:bui-col-end-12{grid-column-end:12}.md\:bui-col-end-13{grid-column-end:13}.md\:bui-col-end-auto{grid-column-end:auto}.md\:bui-row-span-1{grid-row:span 1/span 1}.md\:bui-row-span-2{grid-row:span 2/span 2}.md\:bui-row-span-3{grid-row:span 3/span 3}.md\:bui-row-span-4{grid-row:span 4/span 4}.md\:bui-row-span-5{grid-row:span 5/span 5}.md\:bui-row-span-6{grid-row:span 6/span 6}.md\:bui-row-span-7{grid-row:span 7/span 7}.md\:bui-row-span-8{grid-row:span 8/span 8}.md\:bui-row-span-9{grid-row:span 9/span 9}.md\:bui-row-span-10{grid-row:span 10/span 10}.md\:bui-row-span-11{grid-row:span 11/span 11}.md\:bui-row-span-12{grid-row:span 12/span 12}.md\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1280px){.lg\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.lg\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.lg\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.lg\:bui-col-span-1{grid-column:span 1/span 1}.lg\:bui-col-span-2{grid-column:span 2/span 2}.lg\:bui-col-span-3{grid-column:span 3/span 3}.lg\:bui-col-span-4{grid-column:span 4/span 4}.lg\:bui-col-span-5{grid-column:span 5/span 5}.lg\:bui-col-span-6{grid-column:span 6/span 6}.lg\:bui-col-span-7{grid-column:span 7/span 7}.lg\:bui-col-span-8{grid-column:span 8/span 8}.lg\:bui-col-span-9{grid-column:span 9/span 9}.lg\:bui-col-span-10{grid-column:span 10/span 10}.lg\:bui-col-span-11{grid-column:span 11/span 11}.lg\:bui-col-span-12{grid-column:span 12/span 12}.lg\:bui-col-span-auto{grid-column:span auto/span auto}.lg\:bui-col-start-1{grid-column-start:1}.lg\:bui-col-start-2{grid-column-start:2}.lg\:bui-col-start-3{grid-column-start:3}.lg\:bui-col-start-4{grid-column-start:4}.lg\:bui-col-start-5{grid-column-start:5}.lg\:bui-col-start-6{grid-column-start:6}.lg\:bui-col-start-7{grid-column-start:7}.lg\:bui-col-start-8{grid-column-start:8}.lg\:bui-col-start-9{grid-column-start:9}.lg\:bui-col-start-10{grid-column-start:10}.lg\:bui-col-start-11{grid-column-start:11}.lg\:bui-col-start-12{grid-column-start:12}.lg\:bui-col-start-13{grid-column-start:13}.lg\:bui-col-start-auto{grid-column-start:auto}.lg\:bui-col-end-1{grid-column-end:1}.lg\:bui-col-end-2{grid-column-end:2}.lg\:bui-col-end-3{grid-column-end:3}.lg\:bui-col-end-4{grid-column-end:4}.lg\:bui-col-end-5{grid-column-end:5}.lg\:bui-col-end-6{grid-column-end:6}.lg\:bui-col-end-7{grid-column-end:7}.lg\:bui-col-end-8{grid-column-end:8}.lg\:bui-col-end-9{grid-column-end:9}.lg\:bui-col-end-10{grid-column-end:10}.lg\:bui-col-end-11{grid-column-end:11}.lg\:bui-col-end-12{grid-column-end:12}.lg\:bui-col-end-13{grid-column-end:13}.lg\:bui-col-end-auto{grid-column-end:auto}.lg\:bui-row-span-1{grid-row:span 1/span 1}.lg\:bui-row-span-2{grid-row:span 2/span 2}.lg\:bui-row-span-3{grid-row:span 3/span 3}.lg\:bui-row-span-4{grid-row:span 4/span 4}.lg\:bui-row-span-5{grid-row:span 5/span 5}.lg\:bui-row-span-6{grid-row:span 6/span 6}.lg\:bui-row-span-7{grid-row:span 7/span 7}.lg\:bui-row-span-8{grid-row:span 8/span 8}.lg\:bui-row-span-9{grid-row:span 9/span 9}.lg\:bui-row-span-10{grid-row:span 10/span 10}.lg\:bui-row-span-11{grid-row:span 11/span 11}.lg\:bui-row-span-12{grid-row:span 12/span 12}.lg\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1536px){.xl\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xl\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xl\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xl\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xl\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xl\:bui-col-span-1{grid-column:span 1/span 1}.xl\:bui-col-span-2{grid-column:span 2/span 2}.xl\:bui-col-span-3{grid-column:span 3/span 3}.xl\:bui-col-span-4{grid-column:span 4/span 4}.xl\:bui-col-span-5{grid-column:span 5/span 5}.xl\:bui-col-span-6{grid-column:span 6/span 6}.xl\:bui-col-span-7{grid-column:span 7/span 7}.xl\:bui-col-span-8{grid-column:span 8/span 8}.xl\:bui-col-span-9{grid-column:span 9/span 9}.xl\:bui-col-span-10{grid-column:span 10/span 10}.xl\:bui-col-span-11{grid-column:span 11/span 11}.xl\:bui-col-span-12{grid-column:span 12/span 12}.xl\:bui-col-span-auto{grid-column:span auto/span auto}.xl\:bui-col-start-1{grid-column-start:1}.xl\:bui-col-start-2{grid-column-start:2}.xl\:bui-col-start-3{grid-column-start:3}.xl\:bui-col-start-4{grid-column-start:4}.xl\:bui-col-start-5{grid-column-start:5}.xl\:bui-col-start-6{grid-column-start:6}.xl\:bui-col-start-7{grid-column-start:7}.xl\:bui-col-start-8{grid-column-start:8}.xl\:bui-col-start-9{grid-column-start:9}.xl\:bui-col-start-10{grid-column-start:10}.xl\:bui-col-start-11{grid-column-start:11}.xl\:bui-col-start-12{grid-column-start:12}.xl\:bui-col-start-13{grid-column-start:13}.xl\:bui-col-start-auto{grid-column-start:auto}.xl\:bui-col-end-1{grid-column-end:1}.xl\:bui-col-end-2{grid-column-end:2}.xl\:bui-col-end-3{grid-column-end:3}.xl\:bui-col-end-4{grid-column-end:4}.xl\:bui-col-end-5{grid-column-end:5}.xl\:bui-col-end-6{grid-column-end:6}.xl\:bui-col-end-7{grid-column-end:7}.xl\:bui-col-end-8{grid-column-end:8}.xl\:bui-col-end-9{grid-column-end:9}.xl\:bui-col-end-10{grid-column-end:10}.xl\:bui-col-end-11{grid-column-end:11}.xl\:bui-col-end-12{grid-column-end:12}.xl\:bui-col-end-13{grid-column-end:13}.xl\:bui-col-end-auto{grid-column-end:auto}.xl\:bui-row-span-1{grid-row:span 1/span 1}.xl\:bui-row-span-2{grid-row:span 2/span 2}.xl\:bui-row-span-3{grid-row:span 3/span 3}.xl\:bui-row-span-4{grid-row:span 4/span 4}.xl\:bui-row-span-5{grid-row:span 5/span 5}.xl\:bui-row-span-6{grid-row:span 6/span 6}.xl\:bui-row-span-7{grid-row:span 7/span 7}.xl\:bui-row-span-8{grid-row:span 8/span 8}.xl\:bui-row-span-9{grid-row:span 9/span 9}.xl\:bui-row-span-10{grid-row:span 10/span 10}.xl\:bui-row-span-11{grid-row:span 11/span 11}.xl\:bui-row-span-12{grid-row:span 12/span 12}.xl\:bui-row-span-auto{grid-row:span auto/span auto}}.bui-gap{gap:var(--gap)}.bui-gap-0\.5{gap:var(--bui-space-0_5)}.bui-gap-1{gap:var(--bui-space-1)}.bui-gap-1\.5{gap:var(--bui-space-1_5)}.bui-gap-2{gap:var(--bui-space-2)}.bui-gap-3{gap:var(--bui-space-3)}.bui-gap-4{gap:var(--bui-space-4)}.bui-gap-5{gap:var(--bui-space-5)}.bui-gap-6{gap:var(--bui-space-6)}.bui-gap-7{gap:var(--bui-space-7)}.bui-gap-8{gap:var(--bui-space-8)}.bui-gap-9{gap:var(--bui-space-9)}.bui-gap-10{gap:var(--bui-space-10)}.bui-gap-11{gap:var(--bui-space-11)}.bui-gap-12{gap:var(--bui-space-12)}.bui-gap-13{gap:var(--bui-space-13)}.bui-gap-14{gap:var(--bui-space-14)}@media (width>=640px){.xs\:bui-gap{gap:var(--gap-xs)}.xs\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xs\:bui-gap-1{gap:var(--bui-space-1)}.xs\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xs\:bui-gap-2{gap:var(--bui-space-2)}.xs\:bui-gap-3{gap:var(--bui-space-3)}.xs\:bui-gap-4{gap:var(--bui-space-4)}.xs\:bui-gap-5{gap:var(--bui-space-5)}.xs\:bui-gap-6{gap:var(--bui-space-6)}.xs\:bui-gap-7{gap:var(--bui-space-7)}.xs\:bui-gap-8{gap:var(--bui-space-8)}.xs\:bui-gap-9{gap:var(--bui-space-9)}.xs\:bui-gap-10{gap:var(--bui-space-10)}.xs\:bui-gap-11{gap:var(--bui-space-11)}.xs\:bui-gap-12{gap:var(--bui-space-12)}.xs\:bui-gap-13{gap:var(--bui-space-13)}.xs\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-gap{gap:var(--gap-sm)}.sm\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.sm\:bui-gap-1{gap:var(--bui-space-1)}.sm\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.sm\:bui-gap-2{gap:var(--bui-space-2)}.sm\:bui-gap-3{gap:var(--bui-space-3)}.sm\:bui-gap-4{gap:var(--bui-space-4)}.sm\:bui-gap-5{gap:var(--bui-space-5)}.sm\:bui-gap-6{gap:var(--bui-space-6)}.sm\:bui-gap-7{gap:var(--bui-space-7)}.sm\:bui-gap-8{gap:var(--bui-space-8)}.sm\:bui-gap-9{gap:var(--bui-space-9)}.sm\:bui-gap-10{gap:var(--bui-space-10)}.sm\:bui-gap-11{gap:var(--bui-space-11)}.sm\:bui-gap-12{gap:var(--bui-space-12)}.sm\:bui-gap-13{gap:var(--bui-space-13)}.sm\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-gap{gap:var(--gap-md)}.md\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.md\:bui-gap-1{gap:var(--bui-space-1)}.md\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.md\:bui-gap-2{gap:var(--bui-space-2)}.md\:bui-gap-3{gap:var(--bui-space-3)}.md\:bui-gap-4{gap:var(--bui-space-4)}.md\:bui-gap-5{gap:var(--bui-space-5)}.md\:bui-gap-6{gap:var(--bui-space-6)}.md\:bui-gap-7{gap:var(--bui-space-7)}.md\:bui-gap-8{gap:var(--bui-space-8)}.md\:bui-gap-9{gap:var(--bui-space-9)}.md\:bui-gap-10{gap:var(--bui-space-10)}.md\:bui-gap-11{gap:var(--bui-space-11)}.md\:bui-gap-12{gap:var(--bui-space-12)}.md\:bui-gap-13{gap:var(--bui-space-13)}.md\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-gap{gap:var(--gap-lg)}.lg\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.lg\:bui-gap-1{gap:var(--bui-space-1)}.lg\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.lg\:bui-gap-2{gap:var(--bui-space-2)}.lg\:bui-gap-3{gap:var(--bui-space-3)}.lg\:bui-gap-4{gap:var(--bui-space-4)}.lg\:bui-gap-5{gap:var(--bui-space-5)}.lg\:bui-gap-6{gap:var(--bui-space-6)}.lg\:bui-gap-7{gap:var(--bui-space-7)}.lg\:bui-gap-8{gap:var(--bui-space-8)}.lg\:bui-gap-9{gap:var(--bui-space-9)}.lg\:bui-gap-10{gap:var(--bui-space-10)}.lg\:bui-gap-11{gap:var(--bui-space-11)}.lg\:bui-gap-12{gap:var(--bui-space-12)}.lg\:bui-gap-13{gap:var(--bui-space-13)}.lg\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-gap{gap:var(--gap-xl)}.xl\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xl\:bui-gap-1{gap:var(--bui-space-1)}.xl\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xl\:bui-gap-2{gap:var(--bui-space-2)}.xl\:bui-gap-3{gap:var(--bui-space-3)}.xl\:bui-gap-4{gap:var(--bui-space-4)}.xl\:bui-gap-5{gap:var(--bui-space-5)}.xl\:bui-gap-6{gap:var(--bui-space-6)}.xl\:bui-gap-7{gap:var(--bui-space-7)}.xl\:bui-gap-8{gap:var(--bui-space-8)}.xl\:bui-gap-9{gap:var(--bui-space-9)}.xl\:bui-gap-10{gap:var(--bui-space-10)}.xl\:bui-gap-11{gap:var(--bui-space-11)}.xl\:bui-gap-12{gap:var(--bui-space-12)}.xl\:bui-gap-13{gap:var(--bui-space-13)}.xl\:bui-gap-14{gap:var(--bui-space-14)}}.bui-align-start{align-items:start}.bui-align-center{align-items:center}.bui-align-end{align-items:end}.bui-align-baseline{align-items:baseline}.bui-align-stretch{align-items:stretch}.bui-jc-start{justify-content:start}.bui-jc-center{justify-content:center}.bui-jc-end{justify-content:end}.bui-jc-between{justify-content:space-between}.bui-fd-row{flex-direction:row}.bui-fd-column{flex-direction:column}.bui-fd-row-reverse{flex-direction:row-reverse}.bui-fd-column-reverse{flex-direction:column-reverse}@media (width>=640px){.xs\:bui-align-start{align-items:start}.xs\:bui-align-center{align-items:center}.xs\:bui-align-end{align-items:end}.xs\:bui-align-stretch{align-items:stretch}.xs\:bui-jc-start{justify-content:start}.xs\:bui-jc-center{justify-content:center}.xs\:bui-jc-end{justify-content:end}.xs\:bui-jc-between{justify-content:space-between}.xs\:bui-fd-row{flex-direction:row}.xs\:bui-fd-column{flex-direction:column}.xs\:bui-fd-row-reverse{flex-direction:row-reverse}.xs\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=768px){.sm\:bui-align-start{align-items:start}.sm\:bui-align-center{align-items:center}.sm\:bui-align-end{align-items:end}.sm\:bui-align-stretch{align-items:stretch}.sm\:bui-jc-start{justify-content:start}.sm\:bui-jc-center{justify-content:center}.sm\:bui-jc-end{justify-content:end}.sm\:bui-jc-between{justify-content:space-between}.sm\:bui-fd-row{flex-direction:row}.sm\:bui-fd-column{flex-direction:column}.sm\:bui-fd-row-reverse{flex-direction:row-reverse}.sm\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1024px){.md\:bui-align-start{align-items:start}.md\:bui-align-center{align-items:center}.md\:bui-align-end{align-items:end}.md\:bui-align-stretch{align-items:stretch}.md\:bui-jc-start{justify-content:start}.md\:bui-jc-center{justify-content:center}.md\:bui-jc-end{justify-content:end}.md\:bui-jc-between{justify-content:space-between}.md\:bui-fd-row{flex-direction:row}.md\:bui-fd-column{flex-direction:column}.md\:bui-fd-row-reverse{flex-direction:row-reverse}.md\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1280px){.lg\:bui-align-start{align-items:start}.lg\:bui-align-center{align-items:center}.lg\:bui-align-end{align-items:end}.lg\:bui-align-stretch{align-items:stretch}.lg\:bui-jc-start{justify-content:start}.lg\:bui-jc-center{justify-content:center}.lg\:bui-jc-end{justify-content:end}.lg\:bui-jc-between{justify-content:space-between}.lg\:bui-fd-row{flex-direction:row}.lg\:bui-fd-column{flex-direction:column}.lg\:bui-fd-row-reverse{flex-direction:row-reverse}.lg\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1536px){.xl\:bui-align-start{align-items:start}.xl\:bui-align-center{align-items:center}.xl\:bui-align-end{align-items:end}.xl\:bui-align-stretch{align-items:stretch}.xl\:bui-jc-start{justify-content:start}.xl\:bui-jc-center{justify-content:center}.xl\:bui-jc-end{justify-content:end}.xl\:bui-jc-between{justify-content:space-between}.xl\:bui-fd-row{flex-direction:row}.xl\:bui-fd-column{flex-direction:column}.xl\:bui-fd-row-reverse{flex-direction:row-reverse}.xl\:bui-fd-column-reverse{flex-direction:column-reverse}}} \ No newline at end of file diff --git a/docs-ui/public/theme-spotify.css b/docs-ui/public/theme-spotify.css deleted file mode 100644 index 22eda0cabe..0000000000 --- a/docs-ui/public/theme-spotify.css +++ /dev/null @@ -1 +0,0 @@ -@font-face{font-family:CircularSpTitle;font-weight:900;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Black-4588c99025b967475c31695b0743dd1a.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Black-506746f387a26f25aa3d023b3e501d34.woff)format("woff")}@font-face{font-family:CircularSpTitle;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-b2586b06a2e1522e9d879d84c2792a58.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-1624fb2df28c20d7203d7fb86ce8b481.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:400;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Book-a00e99ef9996a3a157fb6b746856d04f.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Book-3f73da7d35bd81c706bce7bbb84964de.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}[data-theme-name=spotify]{--bui-font-text:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-title:CircularSpTitle,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-regular:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;& .bui-Button{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3)}& .bui-ButtonIcon{padding:0}& .bui-MenuPopup{box-sizing:border-box;max-width:21.25rem}& .bui-MenuSubmenuTrigger,& .bui-MenuItem{height:auto;min-height:2rem}& .bui-Text{font-family:var(--bui-font-text)}& .bui-Heading{font-family:var(--bui-font-title)}& .bui-TableRow{border-radius:var(--bui-radius-4);border:none}& .bui-TableRow:hover td:first-child{border-top-left-radius:var(--bui-radius-2);border-bottom-left-radius:var(--bui-radius-2)}& .bui-TableRow:hover td:last-child{border-top-right-radius:var(--bui-radius-2);border-bottom-right-radius:var(--bui-radius-2)}& .bui-TableBody:before,& .bui-TableBody:after{line-height:var(--bui-space-1);content:"‌";display:block}& .bui-TableHeader .bui-TableRow{border-bottom:1px solid var(--bui-border)}& .bui-TableHead{font-size:var(--bui-font-size-2);color:var(--bui-fg-secondary);font-weight:var(--bui-font-weight-regular)}& .bui-HeaderToolbar{padding-top:var(--bui-space-2);padding-inline:var(--bui-space-2)}& .bui-HeaderToolbarWrapper{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3);border:none}& .bui-HeaderToolbarControls{right:calc(var(--bui-space-3) - 1px)}& .bui-HeaderTabsWrapper{margin-top:var(--bui-space-2);margin-inline:var(--bui-space-2);border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-1);border:none}& .bui-Input{border-radius:var(--bui-radius-3)}& .bui-Tag{border-radius:var(--bui-radius-full)}}[data-theme-mode=light][data-theme-name=spotify]{--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:#0f6c30;--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-solid:var(--bui-black);--bui-fg-solid-disabled:#62ab7c;--bui-border:var(--bui-gray-3);--bui-border-hover:#0000004d;--bui-border-pressed:#00000080;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#0003;& .bui-HeaderToolbarWrapper,& .bui-HeaderTabsWrapper{border:1px solid var(--bui-border)}}[data-theme-mode=dark][data-theme-name=spotify]{--bui-bg-surface-0:var(--bui-black);--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:#0f6c30;--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:var(--bui-white);--bui-fg-link-hover:var(--bui-white);--bui-fg-disabled:var(--bui-gray-5);--bui-fg-solid:var(--bui-black);--bui-fg-solid-disabled:#072f15;--bui-fg-tint:var(--bui-white);--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:var(--bui-gray-3);--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#fff3} \ No newline at end of file diff --git a/docs-ui/scripts/sync-changelog.mjs b/docs-ui/scripts/sync-changelog.mjs index 5eab3616dd..fa59db741b 100644 --- a/docs-ui/scripts/sync-changelog.mjs +++ b/docs-ui/scripts/sync-changelog.mjs @@ -1,5 +1,4 @@ #!/usr/bin/env node -/* eslint-disable no-restricted-syntax */ import fs from 'fs'; import path from 'path'; @@ -13,22 +12,18 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); /** - * Compare two semantic versions. + * Compare two semantic versions for sorting. * Returns: 1 if versionA > versionB, -1 if versionA < versionB, 0 if equal - * Handles pre-release versions (e.g., "0.9.0-next.2") + * Example: compareVersions("0.11.0", "0.10.0") => 1 */ function compareVersions(versionA, versionB) { // Strip 'v' prefix if present versionA = versionA.replace(/^v/, ''); versionB = versionB.replace(/^v/, ''); - // Split into [major.minor.patch, prerelease] - const [versionABase, versionAPre] = versionA.split('-'); - const [versionBBase, versionBPre] = versionB.split('-'); - - // Compare base versions - const versionAParts = versionABase.split('.').map(Number); - const versionBParts = versionBBase.split('.').map(Number); + // Compare major.minor.patch versions + const versionAParts = versionA.split('.').map(Number); + const versionBParts = versionB.split('.').map(Number); for ( let i = 0; @@ -42,13 +37,7 @@ function compareVersions(versionA, versionB) { if (versionAPart < versionBPart) return -1; } - // If base versions equal, compare pre-release - if (!versionAPre && !versionBPre) return 0; - if (!versionAPre) return 1; // versionA is release, versionB is pre-release - if (!versionBPre) return -1; // versionA is pre-release, versionB is release - - // Both have pre-release, compare them - return versionAPre.localeCompare(versionBPre); + return 0; } /** @@ -58,15 +47,6 @@ function isNewerVersion(versionA, versionB) { return compareVersions(versionA, versionB) > 0; } -/** - * Normalize version to base version (remove pre-release suffix) - * "0.9.0-next.3" → "0.9.0" - * "0.9.0" → "0.9.0" - */ -function normalizeVersion(version) { - return version.split('-')[0]; -} - /** * Read the existing changelog.ts and find the highest version number * by scanning the changelogs/ directory for version files @@ -169,6 +149,7 @@ function getValidComponents(changelogPath) { /** * Parse CHANGELOG.md and extract entries newer than sinceVersion + * Only processes main release versions (e.g., 0.11.0), skips pre-releases (e.g., 0.11.0-next.1) */ async function parseChangelogMd(changelogPath, sinceVersion, validComponents) { const content = fs.readFileSync(changelogPath, 'utf-8'); @@ -181,14 +162,17 @@ async function parseChangelogMd(changelogPath, sinceVersion, validComponents) { // Walk through the markdown AST async function walk(node, depth = 0) { - // Version headers (## 0.9.0) + // Version headers (## 0.11.0) if (node.type === 'heading' && node.depth === 2) { const versionText = extractText(node).trim(); - // Validate that this is actually a version number (X.Y.Z or X.Y.Z-pre.N) - // Skip headings that are just markdown content within changelog entries - if (/^\d+\.\d+\.\d+/.test(versionText)) { + // Only process main release versions (X.Y.Z format) + if (/^\d+\.\d+\.\d+$/.test(versionText)) { currentVersion = versionText; currentSection = null; + } else { + // Skip pre-release versions (e.g., 0.11.0-next.1) and non-version headings + currentVersion = null; + currentSection = null; } return; } @@ -301,9 +285,11 @@ async function parseListItem( // Remove the commit SHA prefix from markdown let description = fullMarkdown.replace(/^-?\s*[a-f0-9]+:\s*/, '').trim(); - // Auto-detect components from "Affected components:" line + // Extract components using bold marker (standard format) let components = []; - const componentMatch = description.match(/Affected components?:\s*([^\n]+)/i); + const componentMatch = description.match( + /\*\*Affected components:\*\*\s*([^\n]+)/, + ); if (componentMatch) { const componentNames = componentMatch[1] .split(',') @@ -314,52 +300,64 @@ async function parseListItem( .map(name => mapComponentName(name, validComponents)) .filter(Boolean); - // Optionally strip "Affected components:" line from description + // Strip "**Affected components:**" line from description description = description - .replace(/\n*Affected components?:[ \t]*[^\n]+/i, '') + .replace(/\n*\*\*Affected components:\*\*[ \t]*[^\n]+/g, '') + .trim(); + } + + // Extract migration notes using bold marker (standard format) + let migration = null; + const migrationMatch = description.match( + /\*\*Migration:\*\*\s*\n([\s\S]+?)(?=\n\s*$|$)/, + ); + if (migrationMatch) { + // Clean up indentation from list format (remove leading 2 spaces from each line) + migration = migrationMatch[1] + .split('\n') + .map(line => line.replace(/^ /, '')) + .join('\n') + .trim(); + // Strip migration section from description + description = description + .replace(/\n*\*\*Migration:\*\*[\s\S]+$/, '') .trim(); } const prs = []; // Will be populated later by fetchPRNumbers() - // Infer type from section and description - const type = inferChangeType(section, description, version); + // Check if this is a breaking change + const breaking = isBreakingChange(section, description, version); return { - version: normalizeVersion(version), + version, section, commitSha, description, components, prs, - type, + breaking, + migration, }; } /** - * Infer change type from section and description + * Determine if a change is breaking based on semver rules + * Breaking change rules (semver): + * - version >= 1.0.0: Major changes are breaking + * - version < 1.0.0: Major and Minor changes are breaking */ -function inferChangeType(section, description, version) { - const isPre1 = version.startsWith('0.'); +function isBreakingChange(section, description, version) { + // Parse version to determine breaking change rules based on semver + const [major] = version.split('.').map(Number); - // Check description keywords first - if (description.match(/^(New|Add(ed)?)\s/i)) { - return 'new'; - } - if (description.includes('BREAKING')) { - return 'breaking'; + // Version >= 1.0.0: Only Major Changes are breaking + if (major >= 1) { + return section === 'Major Changes'; } - // Infer from section - if (section === 'Minor Changes') { - return isPre1 ? 'breaking' : undefined; - } else if (section === 'Major Changes') { - return 'breaking'; - } else if (section === 'Patch Changes') { - return 'fix'; - } - - return undefined; + // Version < 1.0.0: Both Major and Minor Changes are breaking + return section === 'Major Changes' || section === 'Minor Changes'; } /** @@ -414,8 +412,13 @@ async function fetchPRNumbers(entries, dryRun = false) { /** * Generate per-version changelog files in changelogs/ directory */ -async function generateVersionFiles(entries, changelogsDir, dryRun = false) { - // Group entries by normalized version +async function generateVersionFiles( + entries, + changelogsDir, + dryRun = false, + force = false, +) { + // Group entries by version const byVersion = {}; entries.forEach(entry => { const version = entry.version; @@ -434,8 +437,8 @@ async function generateVersionFiles(entries, changelogsDir, dryRun = false) { const filePath = path.join(changelogsDir, fileName); const varName = `changelog_${version.replace(/\./g, '_')}`; - // Check if file already exists - skip if it does - if (fs.existsSync(filePath)) { + // Check if file already exists - skip if it does (unless force flag) + if (fs.existsSync(filePath) && !force) { skippedVersions.push(version); // Still add to versionFiles array so main changelog.ts can import it versionFiles.push({ @@ -456,7 +459,7 @@ async function generateVersionFiles(entries, changelogsDir, dryRun = false) { .map(c => `'${c}'`) .join(', ')}]`; const prsStr = `[${entry.prs.map(pr => `'${pr}'`).join(', ')}]`; - const typeStr = entry.type ? `type: '${entry.type}',` : ''; + const breakingStr = entry.breaking ? `breaking: true,` : ''; const shaStr = entry.commitSha ? `commitSha: '${entry.commitSha}',` : ''; @@ -467,12 +470,20 @@ async function generateVersionFiles(entries, changelogsDir, dryRun = false) { .replace(/`/g, '\\`') .replace(/\${/g, '\\${'); + // Escape migration notes if present + const migrationStr = entry.migration + ? `migration: \`${entry.migration + .replace(/\\/g, '\\\\') + .replace(/`/g, '\\`') + .replace(/\${/g, '\\${')}\`,\n ` + : ''; + return ` { components: ${componentsStr}, version: '${entry.version}', prs: ${prsStr}, description: \`${descEscaped}\`, - ${typeStr} + ${migrationStr}${breakingStr} ${shaStr} }`; }) @@ -802,6 +813,7 @@ async function findPRNumber(commitSha) { async function main() { const args = process.argv.slice(2); const dryRun = args.includes('--dry-run'); + const force = args.includes('--force'); const changelogTsPath = path.join(__dirname, '../src/utils/changelog.ts'); const changelogMdPath = path.join( @@ -818,10 +830,15 @@ async function main() { } console.log('📋 Syncing UI component changelogs...\n'); + if (force) { + console.log('⚠️ Force mode: Will overwrite existing version files\n'); + } - // Get last synced version - const lastVersion = getLastSyncedVersion(changelogTsPath); - console.log(`Last synced version: ${lastVersion || '(none)'}`); + // Get last synced version (null if force mode to process all) + const lastVersion = force ? null : getLastSyncedVersion(changelogTsPath); + console.log( + `Last synced version: ${lastVersion || '(none - processing all versions)'}`, + ); // Get valid components const validComponents = getValidComponents(changelogTsPath); @@ -834,26 +851,26 @@ async function main() { lastVersion, validComponents, ); - console.log(`Found ${allEntries.length} total entries since ${lastVersion}`); + console.log( + `Found ${allEntries.length} total entries${ + force ? '' : ` since ${lastVersion}` + }`, + ); // Read existing changelog content for duplicate detection const existingContent = fs.readFileSync(changelogTsPath, 'utf-8'); // Filter to only new, non-duplicate entries - const relevantEntries = allEntries.filter(e => { - const hasComponents = e.components.length > 0 || e.components.length === 0; - const notDuplicate = !isDuplicate(e, existingContent); - return hasComponents && notDuplicate; - }); + const relevantEntries = allEntries.filter( + e => !isDuplicate(e, existingContent), + ); const duplicatesCount = allEntries.length - relevantEntries.length; if (duplicatesCount > 0) { console.log(`Skipped ${duplicatesCount} duplicate entries`); } - console.log( - `Relevant entries (with or without components): ${relevantEntries.length}`, - ); + console.log(`Relevant entries: ${relevantEntries.length}`); if (relevantEntries.length === 0) { console.log('\n✅ No new entries to sync'); @@ -920,6 +937,7 @@ async function main() { relevantEntries, changelogsDir, dryRun, + force, ); // Generate main changelog.ts diff --git a/docs-ui/scripts/sync-css.js b/docs-ui/scripts/sync-css.js index 4763fdecc1..a9c228ac27 100644 --- a/docs-ui/scripts/sync-css.js +++ b/docs-ui/scripts/sync-css.js @@ -1,12 +1,12 @@ -const fs = require('fs'); -const path = require('path'); +const fs = require('node:fs'); +const path = require('node:path'); const { bundle } = require('lightningcss'); const chokidar = require('chokidar'); // Configuration const config = { UIPath: '../../packages/ui', - publicPath: '../public', + outputPath: '../src/css', files: [ { source: 'css/styles.css', @@ -24,13 +24,13 @@ const config = { class CSSSync { constructor() { this.UIPath = path.resolve(__dirname, config.UIPath); - this.publicPath = path.resolve(__dirname, config.publicPath); + this.outputPath = path.resolve(__dirname, config.outputPath); this.isWatching = process.argv.includes('--watch'); } async syncFile(fileConfig) { const sourcePath = path.join(this.UIPath, fileConfig.source); - const destPath = path.join(this.publicPath, fileConfig.destination); + const destPath = path.join(this.outputPath, fileConfig.destination); try { // Check if source file exists @@ -76,9 +76,9 @@ class CSSSync { ); if (successCount > 0) { - console.log('\n📁 Available CSS files in public/:'); + console.log('\n📁 Available CSS files in src/css/:'); config.files.forEach(file => { - const destPath = path.join(this.publicPath, file.destination); + const destPath = path.join(this.outputPath, file.destination); if (fs.existsSync(destPath)) { const stats = fs.statSync(destPath); const size = (stats.size / 1024).toFixed(2); @@ -129,7 +129,7 @@ class CSSSync { async run() { console.log('🎨 BUI CSS Sync Tool\n'); console.log(`📂 BUI path: ${this.UIPath}`); - console.log(`📂 Public path: ${this.publicPath}\n`); + console.log(`📂 Output path: ${this.outputPath}\n`); // Initial sync await this.syncAll(); diff --git a/docs-ui/src/app/components/accordion/page.mdx b/docs-ui/src/app/components/accordion/page.mdx index 83d650507b..486c93ec80 100644 --- a/docs-ui/src/app/components/accordion/page.mdx +++ b/docs-ui/src/app/components/accordion/page.mdx @@ -28,10 +28,16 @@ import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { AccordionDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + disclosure: 'https://react-aria.adobe.com/Disclosure', + disclosureGroup: 'https://react-aria.adobe.com/DisclosureGroup', +}; ` element. + + ### AccordionTrigger Trigger component with built-in animated chevron icon. Renders a heading element (defaults to `

`, configurable via `level` prop) wrapping a ` + } + /> + } + aria-label="Close" + /> + } + /> + + ); +}; + +export const WithActionsAndDescriptions = () => { + return ( + + + + + } + /> + ); +}; + +export const LoadingStates = () => { + return ( + + + + + + ); +}; + +export const WithoutIcons = () => { + return ( + + + + + ); +}; + +export const CustomIcon = () => { + return ( + } + title="This alert uses a custom cloud icon instead of the default info icon." + /> + ); +}; diff --git a/docs-ui/src/app/components/alert/page.mdx b/docs-ui/src/app/components/alert/page.mdx new file mode 100644 index 0000000000..cdfdf184e0 --- /dev/null +++ b/docs-ui/src/app/components/alert/page.mdx @@ -0,0 +1,120 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { alertPropDefs } from './props-definition'; +import { + alertUsageSnippet, + defaultSnippet, + statusVariantsSnippet, + withDescriptionSnippet, + withActionsSnippet, + loadingStatesSnippet, + withoutIconsSnippet, + customIconSnippet, +} from './snippets'; +import { + Default, + StatusVariants, + WithDescription, + WithActions, + LoadingStates, + WithoutIcons, + CustomIcon, +} from './components'; +import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { PageTitle } from '@/components/PageTitle'; +import { Theming } from '@/components/Theming'; +import { AlertDefinition } from '../../../utils/definitions'; + + + +} code={defaultSnippet} /> + +## Usage + + + +## API reference + + + +## Examples + +### Status Variants + +The Alert component supports four status variants, each with its own color theme. + +} + code={statusVariantsSnippet} +/> + +### With Description + +Add a description to provide additional context or details. + +} + code={withDescriptionSnippet} +/> + +### With Actions + +Include custom actions like buttons for interactive alerts. + +} + code={withActionsSnippet} +/> + +### Loading States + +The loading spinner replaces the icon to indicate an ongoing process. + +} + code={loadingStatesSnippet} +/> + +### Without Icons + +Disable icons for a simpler appearance. + +} + code={withoutIconsSnippet} +/> + +### Custom Icon + +Provide a custom icon element instead of the default status icon. + +} + code={customIconSnippet} +/> + + + + diff --git a/docs-ui/src/app/components/alert/props-definition.ts b/docs-ui/src/app/components/alert/props-definition.ts new file mode 100644 index 0000000000..6f2650fe42 --- /dev/null +++ b/docs-ui/src/app/components/alert/props-definition.ts @@ -0,0 +1,76 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const alertPropDefs: Record = { + status: { + type: 'enum', + values: ['info', 'success', 'warning', 'danger'], + responsive: true, + default: 'info', + }, + icon: { + type: 'enum', + values: ['boolean', 'React.ReactElement'], + responsive: false, + }, + loading: { + type: 'enum', + values: ['boolean'], + responsive: false, + }, + title: { + type: 'enum', + values: ['React.ReactNode'], + responsive: false, + }, + description: { + type: 'enum', + values: ['React.ReactNode'], + responsive: false, + }, + customActions: { + type: 'enum', + values: ['React.ReactNode'], + responsive: false, + }, + m: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + mx: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + my: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + mt: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + mb: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + ml: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + mr: { + type: 'enum', + values: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + responsive: true, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/alert/snippets.ts b/docs-ui/src/app/components/alert/snippets.ts new file mode 100644 index 0000000000..739dfde9db --- /dev/null +++ b/docs-ui/src/app/components/alert/snippets.ts @@ -0,0 +1,131 @@ +export const alertUsageSnippet = `import { Alert } from '@backstage/ui'; + +`; + +export const defaultSnippet = ``; + +export const statusVariantsSnippet = ` + + + + +`; + +export const withDescriptionSnippet = ` + + + + +`; + +export const withActionsSnippet = ` + + Dismiss + + } + /> + } + aria-label="Close" + /> + } + /> +`; + +export const withActionsAndDescriptionsSnippet = ` + + + + } +/>`; + +export const loadingStatesSnippet = ` + + + +`; + +export const withoutIconsSnippet = ` + + +`; + +export const customIconSnippet = `import { RiCloudLine } from '@remixicon/react'; + +} + title="This alert uses a custom cloud icon instead of the default info icon." +/>`; diff --git a/docs-ui/src/app/components/avatar/page.mdx b/docs-ui/src/app/components/avatar/page.mdx index 0074eda5b8..7020eb90c4 100644 --- a/docs-ui/src/app/components/avatar/page.mdx +++ b/docs-ui/src/app/components/avatar/page.mdx @@ -17,7 +17,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; } code={defaultSnippet} /> @@ -30,13 +30,22 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; +Avatar also accepts all standard HTML div attributes (`onClick`, `onMouseEnter`, etc.) since it extends `React.ComponentPropsWithoutRef<'div'>`. + ## Examples ### Sizes Avatar sizes can be set using the `size` prop. -} code={sizesSnippet} /> +} + code={sizesSnippet} + layout="side-by-side" +/> ### Fallback @@ -48,6 +57,7 @@ If the image is not available, the avatar will show the initials of the name. open preview={} code={fallbackSnippet} + layout="side-by-side" /> ### The `purpose` prop diff --git a/docs-ui/src/app/components/avatar/props-definition.ts b/docs-ui/src/app/components/avatar/props-definition.ts deleted file mode 100644 index 1a6de676c1..0000000000 --- a/docs-ui/src/app/components/avatar/props-definition.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const avatarPropDefs: Record = { - src: { - type: 'string', - }, - name: { - type: 'string', - required: true, - }, - size: { - type: 'enum', - values: ['x-small', 'small', 'medium', 'large', 'x-large'], - default: 'medium', - responsive: true, - }, - purpose: { - type: 'enum', - values: ['informative', 'decoration'], - default: 'informative', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/docs-ui/src/app/components/avatar/props-definition.tsx b/docs-ui/src/app/components/avatar/props-definition.tsx new file mode 100644 index 0000000000..4b56b94be5 --- /dev/null +++ b/docs-ui/src/app/components/avatar/props-definition.tsx @@ -0,0 +1,41 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const avatarPropDefs: Record = { + src: { + type: 'string', + description: + 'URL of the image to display. Pass an empty string to show initials fallback. Falls back to initials if the image fails to load.', + }, + name: { + type: 'string', + required: true, + description: + 'Name of the person. Used for generating initials fallback and accessibility label.', + }, + size: { + type: 'enum', + values: ['x-small', 'small', 'medium', 'large', 'x-large'], + default: 'medium', + responsive: true, + description: + 'Visual size. Smaller sizes show 1 initial, larger sizes show 2.', + }, + purpose: { + type: 'enum', + values: ['informative', 'decoration'], + default: 'informative', + description: ( + <> + Accessibility behavior. Use decoration when name appears in + adjacent text. + + ), + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/avatar/snippets.ts b/docs-ui/src/app/components/avatar/snippets.ts index ba3254dcd9..2deb8037a6 100644 --- a/docs-ui/src/app/components/avatar/snippets.ts +++ b/docs-ui/src/app/components/avatar/snippets.ts @@ -1,6 +1,6 @@ export const avatarUsageSnippet = `import { Avatar } from '@backstage/ui'; -`; +`; export const defaultSnippet = ` - - - - - + + + + + - - - - - + + + + + `; diff --git a/docs-ui/src/app/components/box/components.tsx b/docs-ui/src/app/components/box/components.tsx index 466ecd3708..dac66f1f59 100644 --- a/docs-ui/src/app/components/box/components.tsx +++ b/docs-ui/src/app/components/box/components.tsx @@ -1,34 +1,43 @@ 'use client'; import { Box } from '../../../../../packages/ui/src/components/Box/Box'; - -const diagonalStripePattern = (() => { - const svg = ` - - - - - - `.trim(); - return `data:image/svg+xml,${encodeURIComponent(svg)}`; -})(); +import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { DecorativeBox } from '@/components/DecorativeBox'; export const Default = () => { return ( - + + + + ); +}; + +export const Surface = () => { + return ( + + + Surface 0 + + + Surface 1 + + + Surface 2 + + + Surface 3 + + + ); +}; + +export const Responsive = () => { + return ( + + Resize to see change + ); }; diff --git a/docs-ui/src/app/components/box/page.mdx b/docs-ui/src/app/components/box/page.mdx index cc8641580f..41ce6feba0 100644 --- a/docs-ui/src/app/components/box/page.mdx +++ b/docs-ui/src/app/components/box/page.mdx @@ -5,10 +5,10 @@ import { boxPropDefs } from './props-definition'; import { snippetUsage, defaultSnippet, - boxSimpleSnippet, + boxSurfaceSnippet, boxResponsiveSnippet, } from './snippets'; -import { Default } from './components'; +import { Default, Surface, Responsive } from './components'; import { spacingPropDefs } from '@/utils/propDefs'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; @@ -18,10 +18,10 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; -} code={defaultSnippet} align="center" /> +} code={defaultSnippet} align="center" /> ## Usage @@ -29,11 +29,6 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; ## API reference -### Box - -This is the Box component, our lowest-level component. Here are all the -available properties. - Padding and margin are used to create space around your component using our @@ -44,17 +39,21 @@ avoid collapsing margins but both are available. ## Examples -### Simple example +### Surface -A simple example of how to use the Box component. +Use surface levels to create visual hierarchy. - +} code={boxSurfaceSnippet} layout="side-by-side" /> -### Responsive +### Responsive props -Here's a view when buttons are responsive. +Props can accept breakpoint objects for responsive behavior. - +} + code={boxResponsiveSnippet} + layout="side-by-side" +/> diff --git a/docs-ui/src/app/components/box/props-definition.ts b/docs-ui/src/app/components/box/props-definition.ts deleted file mode 100644 index a18a0f272e..0000000000 --- a/docs-ui/src/app/components/box/props-definition.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - classNamePropDefs, - displayPropDefs, - heightPropDefs, - positionPropDefs, - stylePropDefs, - widthPropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const boxPropDefs: Record = { - as: { - type: 'enum', - values: ['div', 'span'], - default: 'div', - responsive: true, - }, - ...widthPropDefs, - ...heightPropDefs, - ...positionPropDefs, - ...displayPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/docs-ui/src/app/components/box/props-definition.tsx b/docs-ui/src/app/components/box/props-definition.tsx new file mode 100644 index 0000000000..168db2f445 --- /dev/null +++ b/docs-ui/src/app/components/box/props-definition.tsx @@ -0,0 +1,46 @@ +import { + classNamePropDefs, + heightPropDefs, + positionPropDefs, + stylePropDefs, + widthPropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const boxPropDefs: Record = { + as: { + type: 'string', + default: 'div', + description: + 'HTML element to render. Accepts any valid HTML tag (div, span, section, etc.).', + }, + surface: { + type: 'enum', + values: ['0', '1', '2', '3', 'danger', 'warning', 'success', 'auto'], + responsive: true, + description: + 'Background surface level for visual hierarchy. Higher numbers create elevation.', + }, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Content to render inside the box.', + }, + ...widthPropDefs, + ...heightPropDefs, + ...positionPropDefs, + display: { + type: 'enum', + values: ['none', 'flex', 'block', 'inline'], + responsive: true, + description: ( + <> + Controls layout behavior. Use flex for flexbox layouts,{' '} + none to hide. + + ), + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/box/snippets.ts b/docs-ui/src/app/components/box/snippets.ts index 5c19bbe59b..e9af07dec5 100644 --- a/docs-ui/src/app/components/box/snippets.ts +++ b/docs-ui/src/app/components/box/snippets.ts @@ -1,21 +1,23 @@ export const snippetUsage = `import { Box } from '@backstage/ui'; -`; + + Content with padding and background +`; -export const defaultSnippet = ``; +export const defaultSnippet = ` + +`; -export const boxSimpleSnippet = `Hello World`; +export const boxSurfaceSnippet = ` + Surface 0 + Surface 1 + Surface 2 + Surface 3 +`; export const boxResponsiveSnippet = ` - Hello World + p={{ initial: '2', md: '4' }} + display={{ initial: 'block', md: 'flex' }} +> + Content `; diff --git a/docs-ui/src/app/components/button-icon/page.mdx b/docs-ui/src/app/components/button-icon/page.mdx index 776b016671..51ff0ea5a0 100644 --- a/docs-ui/src/app/components/button-icon/page.mdx +++ b/docs-ui/src/app/components/button-icon/page.mdx @@ -14,10 +14,15 @@ import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ButtonIconDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + button: 'https://react-aria.adobe.com/Button', +}; } code={variantsSnippet} /> @@ -30,12 +35,12 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; + + ## Examples ### Variants -Here's a view when buttons have different variants. - } code={sizesSnippet} /> ### Disabled -Here's a view when buttons are disabled. - = { - variant: { - type: 'enum', - values: ['primary', 'secondary', 'tertiary'], - default: 'primary', - responsive: true, - }, - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'medium', - responsive: true, - }, - icon: { type: 'enum', values: ['ReactNode'], responsive: false }, - isDisabled: { type: 'boolean', default: 'false', responsive: false }, - loading: { type: 'boolean', default: 'false', responsive: false }, - type: { - type: 'enum', - values: ['button', 'submit', 'reset'], - default: 'button', - responsive: false, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/docs-ui/src/app/components/button-icon/props-definition.tsx b/docs-ui/src/app/components/button-icon/props-definition.tsx new file mode 100644 index 0000000000..05917cc2b4 --- /dev/null +++ b/docs-ui/src/app/components/button-icon/props-definition.tsx @@ -0,0 +1,64 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const buttonIconPropDefs: Record = { + variant: { + type: 'enum', + values: ['primary', 'secondary', 'tertiary'], + default: 'primary', + responsive: true, + description: ( + <> + Visual style. Use primary for main actions,{' '} + secondary for alternatives, tertiary for + low-emphasis. + + ), + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + description: ( + <> + Button size. Use small for toolbars, medium{' '} + for standalone actions. + + ), + }, + icon: { + type: 'enum', + values: ['ReactElement'], + description: + 'Icon element to display. Required for accessibility via aria-label.', + }, + isDisabled: { + type: 'boolean', + default: 'false', + description: 'Prevents interaction and applies disabled styling.', + }, + loading: { + type: 'boolean', + default: 'false', + description: 'Shows a spinner and disables the button.', + }, + type: { + type: 'enum', + values: ['button', 'submit', 'reset'], + default: 'button', + description: 'HTML button type attribute.', + }, + onSurface: { + type: 'enum', + values: ['0', '1', '2', '3', 'danger', 'warning', 'success', 'auto'], + responsive: true, + description: 'Surface context for correct color contrast.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/button-icon/snippets.ts b/docs-ui/src/app/components/button-icon/snippets.ts index 1ea9acc169..b1ab27b77b 100644 --- a/docs-ui/src/app/components/button-icon/snippets.ts +++ b/docs-ui/src/app/components/button-icon/snippets.ts @@ -1,6 +1,7 @@ export const buttonIconUsageSnippet = `import { ButtonIcon } from '@backstage/ui'; +import { RiCloseLine } from '@remixicon/react'; -`; +} aria-label="Close" />`; export const variantsSnippet = ` } variant="primary" aria-label="Cloud" /> diff --git a/docs-ui/src/app/components/button-link/page.mdx b/docs-ui/src/app/components/button-link/page.mdx index 2da3177c55..35e4de68c5 100644 --- a/docs-ui/src/app/components/button-link/page.mdx +++ b/docs-ui/src/app/components/button-link/page.mdx @@ -14,10 +14,15 @@ import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ButtonLinkDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + link: 'https://react-aria.adobe.com/Link', +}; } code={variantsSnippet} /> @@ -30,30 +35,16 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; + + ## Examples -### Variants - -Here's a view when buttons have different variants. - -} - code={variantsSnippet} -/> - ### Sizes -Here's a view when buttons have different sizes. - } code={sizesSnippet} /> ### With Icons -Here's a view when buttons have icons. - = { - variant: { - type: 'enum', - values: ['primary', 'secondary', 'tertiary'], - default: 'primary', - responsive: true, - }, - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'medium', - responsive: true, - }, - iconStart: { type: 'enum', values: ['ReactNode'], responsive: false }, - iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false }, - isDisabled: { type: 'boolean', default: 'false', responsive: false }, - href: { type: 'string', required: true }, - target: { - type: 'enum', - values: ['_self', '_blank', '_parent', '_top'], - }, - ...childrenPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/docs-ui/src/app/components/button-link/props-definition.tsx b/docs-ui/src/app/components/button-link/props-definition.tsx new file mode 100644 index 0000000000..deae5364df --- /dev/null +++ b/docs-ui/src/app/components/button-link/props-definition.tsx @@ -0,0 +1,74 @@ +import { + classNamePropDefs, + stylePropDefs, + childrenPropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +export const buttonLinkPropDefs: Record = { + variant: { + type: 'enum', + values: ['primary', 'secondary', 'tertiary'], + default: 'primary', + responsive: true, + description: ( + <> + Visual style. Use primary for main actions,{' '} + secondary for alternatives, tertiary for + low-emphasis. + + ), + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + description: ( + <> + Link size. Use small for inline contexts,{' '} + medium for standalone. + + ), + }, + iconStart: { + type: 'enum', + values: ['ReactElement'], + description: 'Icon displayed before the link text.', + }, + iconEnd: { + type: 'enum', + values: ['ReactElement'], + description: 'Icon displayed after the link text.', + }, + isDisabled: { + type: 'boolean', + default: 'false', + description: 'Prevents interaction and applies disabled styling.', + }, + href: { + type: 'string', + required: true, + description: 'URL the link navigates to.', + }, + target: { + type: 'enum', + values: ['_self', '_blank', '_parent', '_top'], + description: ( + <> + Where to open the linked URL. Use _blank for external + links. + + ), + }, + onSurface: { + type: 'enum', + values: ['0', '1', '2', '3', 'danger', 'warning', 'success', 'auto'], + responsive: true, + description: 'Surface context for correct color contrast.', + }, + ...childrenPropDefs, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/button/components.tsx b/docs-ui/src/app/components/button/components.tsx index 558c852e9c..1fe13f6630 100644 --- a/docs-ui/src/app/components/button/components.tsx +++ b/docs-ui/src/app/components/button/components.tsx @@ -59,6 +59,22 @@ export const Disabled = () => { ); }; +export const Destructive = () => { + return ( + + + + + + ); +}; + export const Loading = () => { return ( `; export const buttonResponsiveSnippet = ``; export const variantsSnippet = ` - - - - `; + + + +`; export const sizesSnippet = ` - - - `; + + +`; export const withIconsSnippet = ` - - - - `; + + + +`; export const disabledSnippet = ` - - - - `; + + + +`; + +export const destructiveSnippet = ` + + + +`; export const loadingSnippet = ``; + Load more items +`; export const asLinkSnippet = ` - - Button - - `; + + Button + +`; diff --git a/docs-ui/src/app/components/card/components.tsx b/docs-ui/src/app/components/card/components.tsx index 08fa940be8..d10e4d7bce 100644 --- a/docs-ui/src/app/components/card/components.tsx +++ b/docs-ui/src/app/components/card/components.tsx @@ -18,7 +18,7 @@ export const Default = () => { ); }; -export const CustomSize = () => { +export const HeaderAndBody = () => { return ( { }} > Header - Body - Footer + Body content without a footer ); }; diff --git a/docs-ui/src/app/components/card/page.mdx b/docs-ui/src/app/components/card/page.mdx index e0b6e81fff..6f410fcd97 100644 --- a/docs-ui/src/app/components/card/page.mdx +++ b/docs-ui/src/app/components/card/page.mdx @@ -10,10 +10,10 @@ import { import { cardUsageSnippet, defaultSnippet, - customSizeSnippet, + headerAndBodySnippet, withLongBodySnippet, } from './snippets'; -import { Default, CustomSize, WithLongBody } from './components'; +import { Default, HeaderAndBody, WithLongBody } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { CardDefinition } from '../../../utils/definitions'; @@ -21,7 +21,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; } code={defaultSnippet} /> @@ -32,54 +32,53 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; ## API reference -### Card +All Card components extend `HTMLDivElement` attributes. -A card component that can be used to display content in a box. +### Card ### CardHeader -To display a header in a card, use the `CardHeader` component. This will be fixed at the top of the card. +Fixed at the top of the card. ### CardBody -To display content in a card, use the `CardBody` component. This will automatically fill the card. +Scrollable content area that fills available space. ### CardFooter -To display a footer in a card, use the `CardFooter` component. This will be fixed at the bottom of the card. +Fixed at the bottom of the card. ## Examples -### Custom size +### Header and body only -Here's a view when card has a custom size. +Cards can omit the footer section. } - code={customSizeSnippet} - open + layout="side-by-side" + preview={} + code={headerAndBodySnippet} /> -### With long body +### Scrollable body -Here's a view when card has a long body. +When body content exceeds the available height, CardBody scrolls while header and footer remain fixed. } code={withLongBodySnippet} - open /> diff --git a/docs-ui/src/app/components/card/props-definition.ts b/docs-ui/src/app/components/card/props-definition.ts index bb7bdad0dd..b3e3776648 100644 --- a/docs-ui/src/app/components/card/props-definition.ts +++ b/docs-ui/src/app/components/card/props-definition.ts @@ -1,30 +1,38 @@ import { classNamePropDefs, stylePropDefs, - childrenPropDefs, type PropDef, } from '@/utils/propDefs'; +const optionalChildrenPropDef: Record = { + children: { + type: 'enum', + values: ['ReactNode'], + responsive: false, + description: 'Content to display inside the component.', + }, +}; + export const cardPropDefs: Record = { - ...childrenPropDefs, + ...optionalChildrenPropDef, ...classNamePropDefs, ...stylePropDefs, }; export const cardHeaderPropDefs: Record = { - ...childrenPropDefs, + ...optionalChildrenPropDef, ...classNamePropDefs, ...stylePropDefs, }; export const cardBodyPropDefs: Record = { - ...childrenPropDefs, + ...optionalChildrenPropDef, ...classNamePropDefs, ...stylePropDefs, }; export const cardFooterPropDefs: Record = { - ...childrenPropDefs, + ...optionalChildrenPropDef, ...classNamePropDefs, ...stylePropDefs, }; diff --git a/docs-ui/src/app/components/card/snippets.ts b/docs-ui/src/app/components/card/snippets.ts index b5de9a7008..6f956e5518 100644 --- a/docs-ui/src/app/components/card/snippets.ts +++ b/docs-ui/src/app/components/card/snippets.ts @@ -6,33 +6,40 @@ export const cardUsageSnippet = `import { Card, CardHeader, CardBody, CardFooter Footer `; -export const defaultSnippet = ` +export const defaultSnippet = ` Header Body Footer `; -export const customSizeSnippet = ` +export const headerAndBodySnippet = ` Header - Body - Footer + Body content without a footer `; -export const withLongBodySnippet = ` +export const withLongBodySnippet = `import { Text } from '@backstage/ui'; + + Header This is the first paragraph of a long body text that demonstrates how - the Card component handles extensive content. + the Card component handles extensive content. The card should adjust + accordingly to display all the text properly while maintaining its + structure. Here's a second paragraph that adds more content to our card body. + Having multiple paragraphs helps to visualize how spacing works within + the card component. This third paragraph continues to add more text to ensure we have a - proper demonstration of a card with significant content. + proper demonstration of a card with significant content. This makes it + easier to test scrolling behavior and overall layout when content + exceeds the initial view. diff --git a/docs-ui/src/app/components/checkbox/page.mdx b/docs-ui/src/app/components/checkbox/page.mdx index aaf73d7210..9657b471da 100644 --- a/docs-ui/src/app/components/checkbox/page.mdx +++ b/docs-ui/src/app/components/checkbox/page.mdx @@ -12,10 +12,15 @@ import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { CheckboxDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + checkbox: 'https://react-aria.adobe.com/Checkbox', +}; } code={defaultSnippet} /> @@ -28,12 +33,12 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; + + ## Examples ### All variants -Here's a view when checkboxes have different variants. - = { children: { type: 'enum', - values: ['React.ReactNode'], - responsive: false, + values: ['ReactNode'], + required: true, + description: 'Label displayed next to the checkbox.', }, isSelected: { - type: 'enum', - values: ['boolean'], - responsive: false, + type: 'boolean', + description: 'Controls checked state (controlled mode).', }, defaultSelected: { - type: 'enum', - values: ['boolean'], - responsive: false, + type: 'boolean', + description: 'Initial checked state (uncontrolled mode).', }, onChange: { type: 'enum', values: ['(isSelected: boolean) => void'], - responsive: false, + description: 'Called when the checked state changes.', }, isDisabled: { - type: 'enum', - values: ['boolean'], - responsive: false, + type: 'boolean', + description: 'Prevents interaction and applies disabled styling.', }, isRequired: { - type: 'enum', - values: ['boolean'], - responsive: false, + type: 'boolean', + description: 'Marks the checkbox as required for form validation.', + }, + isIndeterminate: { + type: 'boolean', + description: 'Shows a mixed state, typically for "select all" checkboxes.', }, name: { type: 'string', - responsive: false, + description: 'Name attribute for form submission.', }, value: { type: 'string', - responsive: false, + description: 'Value attribute for form submission.', }, ...classNamePropDefs, ...stylePropDefs, diff --git a/docs-ui/src/app/components/container/components.tsx b/docs-ui/src/app/components/container/components.tsx index 7032b3f620..a11bbd9463 100644 --- a/docs-ui/src/app/components/container/components.tsx +++ b/docs-ui/src/app/components/container/components.tsx @@ -1,24 +1,20 @@ 'use client'; -import { Box } from '../../../../../packages/ui/src/components/Box/Box'; +import { Container } from '../../../../../packages/ui/src/components/Container/Container'; +import { DecorativeBox } from '@/components/DecorativeBox'; -const DecorativeBox = () => ( - -); - -export const Preview = () => { +export const Default = () => { return ( -
- -
+ + Page content goes here + + ); +}; + +export const ResponsiveSpacing = () => { + return ( + + Content with vertical spacing + ); }; diff --git a/docs-ui/src/app/components/container/page.mdx b/docs-ui/src/app/components/container/page.mdx index 87bc55829e..e0f4014a07 100644 --- a/docs-ui/src/app/components/container/page.mdx +++ b/docs-ui/src/app/components/container/page.mdx @@ -4,11 +4,10 @@ import { Snippet } from '@/components/Snippet'; import { containerPropDefs } from './props-definition'; import { containerUsageSnippet, - previewSnippet, - containerSimpleSnippet, + defaultSnippet, containerResponsiveSnippet, } from './snippets'; -import { Preview } from './components'; +import { Default, ResponsiveSpacing } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ContainerDefinition } from '../../../utils/definitions'; @@ -17,33 +16,30 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; -} code={previewSnippet} /> +} code={defaultSnippet} /> ## Usage -## API reference +## Core Concepts + +Container provides the standard page layout for plugin content. It constrains content to a maximum width, centers it horizontally, and adds consistent horizontal gutters. Use it once per page to wrap your main content area. + +## API Reference ## Examples -### Simple +### Responsive spacing -A simple example of how to use the Container component. +Vertical spacing props accept breakpoint objects. - - -### Responsive padding & margin - -The Container component also supports responsive values, making it easy to -create responsive designs. - - +} code={containerResponsiveSnippet} /> diff --git a/docs-ui/src/app/components/container/props-definition.ts b/docs-ui/src/app/components/container/props-definition.ts index 0980520365..797aec9e92 100644 --- a/docs-ui/src/app/components/container/props-definition.ts +++ b/docs-ui/src/app/components/container/props-definition.ts @@ -1,12 +1,52 @@ import { classNamePropDefs, stylePropDefs, - gapPropDefs, + spacingValues, type PropDef, } from '@/utils/propDefs'; export const containerPropDefs: Record = { - ...gapPropDefs, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Content to render inside the container.', + }, + my: { + type: 'enum | string', + values: spacingValues, + responsive: true, + description: 'Vertical margin (top and bottom).', + }, + mt: { + type: 'enum | string', + values: spacingValues, + responsive: true, + description: 'Top margin.', + }, + mb: { + type: 'enum | string', + values: spacingValues, + responsive: true, + description: 'Bottom margin.', + }, + py: { + type: 'enum | string', + values: spacingValues, + responsive: true, + description: 'Vertical padding (top and bottom).', + }, + pt: { + type: 'enum | string', + values: spacingValues, + responsive: true, + description: 'Top padding.', + }, + pb: { + type: 'enum | string', + values: spacingValues, + responsive: true, + description: 'Bottom padding.', + }, ...classNamePropDefs, ...stylePropDefs, }; diff --git a/docs-ui/src/app/components/container/snippets.ts b/docs-ui/src/app/components/container/snippets.ts index 83f4dabbbc..a314b52387 100644 --- a/docs-ui/src/app/components/container/snippets.ts +++ b/docs-ui/src/app/components/container/snippets.ts @@ -1,19 +1,13 @@ export const containerUsageSnippet = `import { Container } from "@backstage/ui"; -Hello World!`; - -export const previewSnippet = `
- -
`; - -export const containerSimpleSnippet = ` - Hello World - Hello World - Hello World + + {/* Your plugin's main content */} `; -export const containerResponsiveSnippet = ` - Hello World - Hello World - Hello World +export const defaultSnippet = ` + Page content goes here +`; + +export const containerResponsiveSnippet = ` + Content with vertical spacing `; diff --git a/docs-ui/src/app/components/dialog/components.tsx b/docs-ui/src/app/components/dialog/components.tsx index 3a1fd8c2c9..18ab1bf9c5 100644 --- a/docs-ui/src/app/components/dialog/components.tsx +++ b/docs-ui/src/app/components/dialog/components.tsx @@ -58,6 +58,20 @@ export const PreviewFixedWidthAndHeight = () => ( quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. + + Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut + fugit, sed quia consequuntur magni dolores eos qui ratione + voluptatem sequi nesciunt. + + + Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, + consectetur, adipisci velit, sed quia non numquam eius modi tempora + incidunt ut labore et dolore magnam aliquam quaerat voluptatem. + + + Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis + suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur. + diff --git a/docs-ui/src/app/components/dialog/page.mdx b/docs-ui/src/app/components/dialog/page.mdx index 1910cef5a8..12ce2d4135 100644 --- a/docs-ui/src/app/components/dialog/page.mdx +++ b/docs-ui/src/app/components/dialog/page.mdx @@ -12,7 +12,6 @@ import { dialogHeaderPropDefs, dialogBodyPropDefs, dialogFooterPropDefs, - dialogClosePropDefs, } from './props-definition'; import { dialogUsageSnippet, @@ -26,10 +25,16 @@ import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { DialogDefinition } from '../../../utils/definitions'; import { ChangelogComponent } from '@/components/ChangelogComponent'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; + +export const reactAriaUrls = { + dialogTrigger: 'https://react-aria.adobe.com/Modal#dialogtrigger', + modal: 'https://react-aria.adobe.com/Modal', +}; + + ### Dialog The main dialog container that renders as a modal overlay. + + ### DialogHeader Displays the dialog title with a built-in close button. + + ### DialogBody The main content area of the dialog with optional scrolling. @@ -94,7 +108,7 @@ Dialog with a fixed height body that scrolls when content overflows. ### Dialog with Form -Dialog containing form elements for user input. +Forms can be embedded in the dialog body. = { - children: { type: 'enum', values: ['ReactNode'], responsive: false }, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Trigger element and dialog content.', + }, isOpen: { type: 'boolean', description: 'Whether the overlay is open by default (controlled).', @@ -23,66 +27,66 @@ export const dialogTriggerPropDefs: Record = { }; export const dialogPropDefs: Record = { - children: { type: 'enum', values: ['ReactNode'], responsive: false }, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Dialog content (DialogHeader, DialogBody, DialogFooter).', + }, isOpen: { type: 'boolean', - description: 'Whether the overlay is open by default (controlled).', + description: 'Whether the overlay is open (controlled mode).', }, defaultOpen: { type: 'boolean', - description: 'Whether the overlay is open by default (uncontrolled).', + description: 'Initial open state (uncontrolled mode).', }, onOpenChange: { type: 'enum', values: ['(isOpen: boolean) => void'], - description: - "Handler that is called when the overlay's open state changes.", + description: 'Called when the open state changes.', }, width: { type: 'enum', values: ['number', 'string'], - responsive: false, + default: '400', + description: 'Fixed width in pixels (number) or CSS units (string).', }, height: { type: 'enum', values: ['number', 'string'], - responsive: false, + default: 'auto', + description: 'Fixed height in pixels (number) or CSS units (string).', }, ...classNamePropDefs, ...stylePropDefs, }; export const dialogHeaderPropDefs: Record = { - children: { type: 'enum', values: ['ReactNode'], responsive: false }, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Dialog title text.', + }, ...classNamePropDefs, ...stylePropDefs, }; export const dialogBodyPropDefs: Record = { - children: { type: 'enum', values: ['ReactNode'], responsive: false }, - height: { + children: { type: 'enum', - values: ['number', 'string'], - responsive: false, + values: ['ReactNode'], + description: 'Main content of the dialog.', }, ...classNamePropDefs, ...stylePropDefs, }; export const dialogFooterPropDefs: Record = { - children: { type: 'enum', values: ['ReactNode'], responsive: false }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const dialogClosePropDefs: Record = { - variant: { + children: { type: 'enum', - values: ['primary', 'secondary', 'tertiary'], - default: 'secondary', - responsive: false, + values: ['ReactNode'], + description: 'Action buttons or footer content.', }, - children: { type: 'enum', values: ['ReactNode'], responsive: false }, ...classNamePropDefs, ...stylePropDefs, }; diff --git a/docs-ui/src/app/components/dialog/snippets.ts b/docs-ui/src/app/components/dialog/snippets.ts index c1ad50578b..440ff417e8 100644 --- a/docs-ui/src/app/components/dialog/snippets.ts +++ b/docs-ui/src/app/components/dialog/snippets.ts @@ -53,11 +53,14 @@ export const dialogWithFormSnippet = ` - + `; diff --git a/docs-ui/src/app/components/skeleton/components.tsx b/docs-ui/src/app/components/skeleton/components.tsx index 277aa17ae3..9f83a013db 100644 --- a/docs-ui/src/app/components/skeleton/components.tsx +++ b/docs-ui/src/app/components/skeleton/components.tsx @@ -2,30 +2,43 @@ import { Skeleton } from '../../../../../packages/ui/src/components/Skeleton/Skeleton'; import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { Box } from '../../../../../packages/ui/src/components/Box/Box'; -export const Demo1 = () => { +export const CardPlaceholder = () => { return ( - - + - - - - - - + + + + + + ); +}; + +export const AvatarWithText = () => { + return ( + + + + + + + - +
); }; -export const Demo2 = () => { +export const Rounded = () => { return ( - - - - - + + + + + + + ); }; diff --git a/docs-ui/src/app/components/skeleton/page.mdx b/docs-ui/src/app/components/skeleton/page.mdx index 42f8975b5a..b7d218af89 100644 --- a/docs-ui/src/app/components/skeleton/page.mdx +++ b/docs-ui/src/app/components/skeleton/page.mdx @@ -2,8 +2,13 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; import { skeletonPropDefs } from './props-definition'; -import { skeletonUsageSnippet, demo1Snippet, demo2Snippet } from './snippets'; -import { Demo1, Demo2 } from './components'; +import { + skeletonUsageSnippet, + cardPlaceholderSnippet, + avatarWithTextSnippet, + roundedSnippet, +} from './snippets'; +import { CardPlaceholder, AvatarWithText, Rounded } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; @@ -14,7 +19,12 @@ import { SkeletonDefinition } from '../../../utils/definitions'; description="Use to show a placeholder while content is loading." /> -} code={demo2Snippet} /> +} + code={cardPlaceholderSnippet} +/> ## Usage @@ -22,21 +32,29 @@ import { SkeletonDefinition } from '../../../utils/definitions'; ## API reference +Skeleton extends standard HTML div attributes. + ## Examples -### Demo 1 +### Rounded -You can use a mix of different sizes to create a more complex skeleton. +} + code={roundedSnippet} +/> -} code={demo1Snippet} open /> +### Avatar with text -### Demo 2 - -You can use a mix of different sizes to create a more complex skeleton. - -} code={demo2Snippet} open /> +} + code={avatarWithTextSnippet} +/> diff --git a/docs-ui/src/app/components/skeleton/props-definition.ts b/docs-ui/src/app/components/skeleton/props-definition.ts index bb80024bcd..8c14c4c0ec 100644 --- a/docs-ui/src/app/components/skeleton/props-definition.ts +++ b/docs-ui/src/app/components/skeleton/props-definition.ts @@ -6,19 +6,22 @@ import { export const skeletonPropDefs: Record = { width: { - type: 'number', + type: 'string', default: '80', - responsive: false, + description: + 'The width of the skeleton. Accepts a number (pixels) or CSS string value.', }, height: { - type: 'number', + type: 'string', default: '24', - responsive: false, + description: + 'The height of the skeleton. Accepts a number (pixels) or CSS string value.', }, rounded: { type: 'boolean', default: 'false', - responsive: false, + description: + 'Whether to apply fully rounded corners (for circular shapes).', }, ...classNamePropDefs, ...stylePropDefs, diff --git a/docs-ui/src/app/components/skeleton/snippets.ts b/docs-ui/src/app/components/skeleton/snippets.ts index 981ac5ff0c..84bbec1616 100644 --- a/docs-ui/src/app/components/skeleton/snippets.ts +++ b/docs-ui/src/app/components/skeleton/snippets.ts @@ -1,26 +1,30 @@ -export const skeletonUsageSnippet = `import { Flex, Skeleton } from '@backstage/ui'; +export const skeletonUsageSnippet = `import { Skeleton } from '@backstage/ui'; - - - - -`; +`; -export const demo1Snippet = ` - +export const cardPlaceholderSnippet = ` - - - - - - + + + + +`; + +export const avatarWithTextSnippet = ` + + + + + + -`; +
`; -export const demo2Snippet = ` - - - -`; +export const roundedSnippet = ` + + + + + +`; diff --git a/docs-ui/src/app/components/switch/page.mdx b/docs-ui/src/app/components/switch/page.mdx index 494a046f47..1f893aea36 100644 --- a/docs-ui/src/app/components/switch/page.mdx +++ b/docs-ui/src/app/components/switch/page.mdx @@ -1,6 +1,7 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; import { switchPropDefs } from './props-definition'; import { snippetUsage, defaultSnippet, disabledSnippet } from './snippets'; import { Default, Disabled } from './components'; @@ -9,9 +10,13 @@ import { Theming } from '@/components/Theming'; import { ChangelogComponent } from '@/components/ChangelogComponent'; import { SwitchDefinition } from '../../../utils/definitions'; +export const reactAriaUrls = { + switch: 'https://react-aria.adobe.com/Switch', +}; + } code={defaultSnippet} /> @@ -24,19 +29,13 @@ import { SwitchDefinition } from '../../../utils/definitions'; + + ## Examples ### Disabled -A switch can be disabled using the `isDisabled` prop. - -} - code={disabledSnippet} -/> +} code={disabledSnippet} /> diff --git a/docs-ui/src/app/components/switch/props-definition.ts b/docs-ui/src/app/components/switch/props-definition.ts index 769c93aac8..eeab3e64b8 100644 --- a/docs-ui/src/app/components/switch/props-definition.ts +++ b/docs-ui/src/app/components/switch/props-definition.ts @@ -2,66 +2,44 @@ import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; import type { PropDef } from '@/utils/propDefs'; export const switchPropDefs: Record = { - autoFocus: { - type: 'boolean', - }, - defaultSelected: { - type: 'boolean', - }, - ...classNamePropDefs, - isDisabled: { - type: 'boolean', - }, - isReadOnly: { - type: 'boolean', + label: { + type: 'string', + description: 'Text label displayed next to the switch.', }, isSelected: { type: 'boolean', + description: + 'Controlled selected state. Use with onChange for controlled behavior.', }, - label: { - type: 'string', - }, - name: { - type: 'string', + defaultSelected: { + type: 'boolean', + description: 'Initial selected state for uncontrolled usage.', }, onChange: { type: 'enum', values: ['(isSelected: boolean) => void'], + description: 'Called when the switch state changes.', }, - onFocus: { - type: 'enum', - values: ['(e: FocusEvent) => void'], + isDisabled: { + type: 'boolean', + description: 'Prevents user interaction when true.', }, - onBlur: { - type: 'enum', - values: ['(e: FocusEvent) => void'], + isReadOnly: { + type: 'boolean', + description: 'Makes the switch non-interactive but still focusable.', }, - onFocusChange: { - type: 'enum', - values: ['(isFocused: boolean) => void'], + name: { + type: 'string', + description: 'Form field name for form submission.', }, - onKeyDown: { - type: 'enum', - values: ['(e: KeyboardEvent) => void'], - }, - onKeyUp: { - type: 'enum', - values: ['(e: KeyboardEvent) => void'], - }, - onHoverStart: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - }, - onHoverEnd: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - }, - onHoverChange: { - type: 'enum', - values: ['(isHovered: boolean) => void'], - }, - ...stylePropDefs, value: { type: 'string', + description: 'Form field value submitted when selected.', }, + autoFocus: { + type: 'boolean', + description: 'Focuses the switch on mount.', + }, + ...classNamePropDefs, + ...stylePropDefs, }; diff --git a/docs-ui/src/app/components/table/components.tsx b/docs-ui/src/app/components/table/components.tsx index cb158579a1..d26deeb5d3 100644 --- a/docs-ui/src/app/components/table/components.tsx +++ b/docs-ui/src/app/components/table/components.tsx @@ -1,31 +1,46 @@ 'use client'; +import { useState } from 'react'; import { Table, + TableRoot, + TableHeader, + TableBody, + Column, + Row, CellProfile, CellText, - type ColumnConfig, useTable, + type ColumnConfig, } from '../../../../../packages/ui/src/components/Table'; import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; import { Text } from '../../../../../packages/ui/src/components/Text/Text'; -import { RadioGroup } from '../../../../../packages/ui/src/components/RadioGroup/RadioGroup'; -import { Radio } from '../../../../../packages/ui/src/components/RadioGroup'; -import { data as data4 } from '../../../../../packages/ui/src/components/Table/stories/mocked-data4'; -import { useState } from 'react'; +import { SearchField } from '../../../../../packages/ui/src/components/SearchField/SearchField'; import { - selectionData, - selectionColumns, -} from '../../../../../packages/ui/src/components/Table/stories/utils'; + RadioGroup, + Radio, +} from '../../../../../packages/ui/src/components/RadioGroup'; +import { data as rockBandData } from '../../../../../packages/ui/src/components/Table/stories/mocked-data4'; +import { data as catalogData } from '../../../../../packages/ui/src/components/Table/stories/mocked-data1'; import { MemoryRouter } from 'react-router-dom'; -type Data4Item = (typeof data4)[0]; +// ============================================================================= +// Types +// ============================================================================= -const columns: ColumnConfig[] = [ +type RockBandItem = (typeof rockBandData)[0]; +type CatalogItem = (typeof catalogData)[0]; + +// ============================================================================= +// Hero Example +// ============================================================================= + +const heroColumns: ColumnConfig[] = [ { id: 'name', label: 'Band name', isRowHeader: true, + defaultWidth: '3fr', cell: item => ( ), @@ -33,112 +48,183 @@ const columns: ColumnConfig[] = [ { id: 'genre', label: 'Genre', + defaultWidth: '3fr', cell: item => , }, { id: 'yearFormed', label: 'Year formed', + defaultWidth: '1fr', cell: item => , }, { id: 'albums', label: 'Albums', + defaultWidth: '1fr', cell: item => , }, ]; -export const TableRockBand = () => { +export function HeroExample() { const { tableProps } = useTable({ mode: 'complete', - getData: () => data4, + getData: () => rockBandData, paginationOptions: { pageSize: 5 }, }); return ( - +
); -}; +} -export const SelectionToggleWithActions = () => { - const [selected, setSelected] = useState | 'all'>( - new Set(), - ); +// ============================================================================= +// Sorting Example +// ============================================================================= +const sortingColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + isSortable: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + isSortable: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + isSortable: true, + cell: item => , + }, + { + id: 'lifecycle', + label: 'Lifecycle', + isSortable: true, + cell: item => , + }, +]; + +export function SortingExample() { const { tableProps } = useTable({ mode: 'complete', - getData: () => selectionData, - paginationOptions: { pageSize: 10 }, + getData: () => catalogData, + paginationOptions: { pageSize: 5 }, + initialSort: { column: 'name', direction: 'ascending' }, + sortFn: (items, { column, direction }) => { + return [...items].sort((a, b) => { + let aVal: string; + let bVal: string; + if (column === 'owner') { + aVal = a.owner.name; + bVal = b.owner.name; + } else { + aVal = String(a[column as keyof CatalogItem]); + bVal = String(b[column as keyof CatalogItem]); + } + const cmp = aVal.localeCompare(bVal); + return direction === 'descending' ? -cmp : cmp; + }); + }, }); return ( -
alert(`Clicked: ${item.name}`) }} - /> +
); -}; +} -export const SelectionModePlayground = () => { - const [selectionMode, setSelectionMode] = useState<'single' | 'multiple'>( - 'multiple', - ); - const [selected, setSelected] = useState | 'all'>( - new Set(), - ); +// ============================================================================= +// Search Example +// ============================================================================= - const { tableProps } = useTable({ +const searchColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { id: 'type', label: 'Type', cell: item => }, +]; + +export function SearchExample() { + const { tableProps, search } = useTable({ mode: 'complete', - getData: () => selectionData, - paginationOptions: { pageSize: 10 }, + getData: () => catalogData, + paginationOptions: { pageSize: 5 }, + searchFn: (items, query) => { + const lowerQuery = query.toLocaleLowerCase('en-US'); + return items.filter( + item => + item.name.toLocaleLowerCase('en-US').includes(lowerQuery) || + item.owner.name.toLocaleLowerCase('en-US').includes(lowerQuery) || + item.type.toLocaleLowerCase('en-US').includes(lowerQuery), + ); + }, }); return ( - -
+ +
No results found for "{search.value}" + ) : ( + No data available + ) + } + {...tableProps} /> -
- - Selection mode: - - { - setSelectionMode(value as 'single' | 'multiple'); - setSelected(new Set()); - }} - > - single - multiple - -
); -}; +} -export const SelectionBehaviorPlayground = () => { +// ============================================================================= +// Selection Example +// ============================================================================= + +const selectionColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { id: 'type', label: 'Type', cell: item => }, +]; + +export function SelectionExample() { + const [selectionMode, setSelectionMode] = useState<'single' | 'multiple'>( + 'multiple', + ); const [selectionBehavior, setSelectionBehavior] = useState< 'toggle' | 'replace' >('toggle'); @@ -148,41 +234,321 @@ export const SelectionBehaviorPlayground = () => { const { tableProps } = useTable({ mode: 'complete', - getData: () => selectionData, - paginationOptions: { pageSize: 10 }, + getData: () => catalogData.slice(0, 5), }); return ( - +
-
- - Selection behavior: - - { - setSelectionBehavior(value as 'toggle' | 'replace'); - setSelected(new Set()); - }} - > - toggle - replace - -
+ +
+ + Selection mode: + + { + setSelectionMode(value as 'single' | 'multiple'); + setSelected(new Set()); + }} + > + single + multiple + +
+
+ + Selection behavior: + + { + setSelectionBehavior(value as 'toggle' | 'replace'); + setSelected(new Set()); + }} + > + toggle + replace + +
+
); -}; +} + +// ============================================================================= +// Row Actions Example +// ============================================================================= + +export function RowActionsExample() { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => catalogData.slice(0, 5), + }); + + return ( + +
alert(`Clicked: ${item.name}`), + }} + selection={{ + mode: 'multiple', + behavior: 'toggle', + selected, + onSelectionChange: setSelected, + }} + {...tableProps} + /> + + ); +} + +// ============================================================================= +// Empty State Example +// ============================================================================= + +const emptyStateColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { id: 'type', label: 'Type', cell: item => }, +]; + +export function EmptyStateExample() { + const emptyData: CatalogItem[] = []; + + const { tableProps } = useTable({ + mode: 'complete', + getData: () => emptyData, + }); + + return ( + +
No items yet. Create one to get started.} + {...tableProps} + /> + + ); +} + +// ============================================================================= +// Combined Example +// ============================================================================= + +const combinedColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + isSortable: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + isSortable: true, + cell: item => , + }, + { + id: 'type', + label: 'Type', + isSortable: true, + cell: item => , + }, +]; + +export function CombinedExample() { + const [selected, setSelected] = useState | 'all'>( + new Set(), + ); + + const { tableProps, search } = useTable({ + mode: 'offset', + getData: async ({ offset, pageSize, search: query, sort }) => { + // Simulate network delay + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Filter data + let filtered = catalogData; + if (query) { + const lowerQuery = query.toLocaleLowerCase('en-US'); + filtered = filtered.filter( + item => + item.name.toLocaleLowerCase('en-US').includes(lowerQuery) || + item.owner.name.toLocaleLowerCase('en-US').includes(lowerQuery) || + item.type.toLocaleLowerCase('en-US').includes(lowerQuery), + ); + } + + // Sort data + if (sort?.column) { + filtered = [...filtered].sort((a, b) => { + let aVal: string; + let bVal: string; + if (sort.column === 'owner') { + aVal = a.owner.name; + bVal = b.owner.name; + } else { + aVal = String(a[sort.column as keyof CatalogItem]); + bVal = String(b[sort.column as keyof CatalogItem]); + } + const cmp = aVal.localeCompare(bVal); + return sort.direction === 'descending' ? -cmp : cmp; + }); + } + + // Paginate + const data = filtered.slice(offset, offset + pageSize); + + return { data, totalCount: filtered.length }; + }, + paginationOptions: { pageSize: 5, pageSizeOptions: [5, 10, 20] }, + initialSort: { column: 'name', direction: 'ascending' }, + }); + + return ( + + + +
alert(`Clicked: ${item.name}`), + }} + selection={{ + mode: 'multiple', + behavior: 'toggle', + selected, + onSelectionChange: setSelected, + }} + emptyState={ + search.value ? ( + No results match your search + ) : ( + No items available + ) + } + {...tableProps} + /> + + + ); +} + +// ============================================================================= +// Custom Row Example +// ============================================================================= + +const customRowColumns: ColumnConfig[] = [ + { + id: 'name', + label: 'Name', + isRowHeader: true, + cell: item => , + }, + { + id: 'owner', + label: 'Owner', + cell: item => , + }, + { + id: 'lifecycle', + label: 'Lifecycle', + cell: item => , + }, +]; + +export function CustomRowExample() { + const { tableProps } = useTable({ + mode: 'complete', + getData: () => catalogData.slice(0, 5), + }); + + return ( + +
( + + {column => column.cell(item)} + + )} + {...tableProps} + /> + + ); +} + +// ============================================================================= +// Primitives Example +// ============================================================================= + +export function PrimitivesExample() { + const items = catalogData.slice(0, 5); + + return ( + + + + Name + Owner + Type + + + {items.map(item => ( + + + + + + ))} + + + + ); +} diff --git a/docs-ui/src/app/components/table/page.mdx b/docs-ui/src/app/components/table/page.mdx index f9b27cd8d3..6eb280cbe2 100644 --- a/docs-ui/src/app/components/table/page.mdx +++ b/docs-ui/src/app/components/table/page.mdx @@ -2,161 +2,293 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; import { - TableRockBand, - SelectionModePlayground, - SelectionBehaviorPlayground, - SelectionToggleWithActions, + HeroExample, + SortingExample, + SearchExample, + SelectionExample, + RowActionsExample, + EmptyStateExample, + CombinedExample, + CustomRowExample, + PrimitivesExample, } from './components'; import { + tableReturnColumns, + useTableOptionsPropDefs, + useTableReturnPropDefs, tablePropDefs, - tableHeaderPropDefs, - tableBodyPropDefs, tablePaginationPropDefs, + columnConfigPropDefs, + cellTextPropDefs, + cellProfilePropDefs, + tableRootPropDefs, columnPropDefs, rowPropDefs, - cellPropDefs, } from './props-definition'; import { tableUsageSnippet, - tableBasicSnippet, - tableSelectionActionsSnippet, - tableSelectionModeSnippet, - tableSelectionBehaviorSnippet, + tableHeroSnippet, + tableConceptsSnippet, + tableSortingSnippet, + tablePaginationSnippet, + tableSearchSnippet, + tableSelectionSnippet, + tableRowActionsHrefSnippet, + tableRowActionsClickSnippet, + tableRowActionsDisabledSnippet, + tableEmptyStateSnippet, + tableOffsetPaginationSnippet, + tableCursorPaginationSnippet, + tableCombinedSnippet, + tableCustomRowSnippet, + tablePrimitivesSnippet, } from './snippets'; import { ChangelogComponent } from '@/components/ChangelogComponent'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; +import { ReactAriaLink } from '@/components/ReactAriaLink'; import { TableDefinition } from '../../../utils/definitions'; +export const reactAriaUrls = { + table: 'https://react-aria.adobe.com/Table#table', + tableHeader: 'https://react-aria.adobe.com/Table#tableheader', + tableBody: 'https://react-aria.adobe.com/Table#tablebody', + column: 'https://react-aria.adobe.com/Table#column', + row: 'https://react-aria.adobe.com/Table#row', + cell: 'https://react-aria.adobe.com/Table#cell', +}; + -} code={tableBasicSnippet} /> +} code={tableHeroSnippet} /> ## Usage -## API reference +## Core Concepts -### Table +The Table component is designed around a hook + component pattern: -The main table component that renders data in a structured grid format. +- **`useTable`** manages data fetching, pagination state, sorting, and filtering. It returns `tableProps` that you spread onto the Table component. +- **`Table`** handles rendering - columns, rows, cells, and interactions. - +The hook supports three modes for different data scenarios: -### TableHeader +- **`complete`** - You have all data available upfront (client-side) +- **`offset`** - Your API uses offset/limit pagination +- **`cursor`** - Your API uses cursor-based pagination -The header section of the table that contains the column definitions. + - +**Controlled vs uncontrolled state** -### Column +By default, `useTable` manages sort, search, and filter state internally. Pass `initialSort`, `initialSearch`, or `initialFilter` to set starting values. -A column definition that describes how a column should be rendered. +For full control over state, use the controlled props instead: - +- `sort` / `onSortChange` +- `search` / `onSearchChange` +- `filter` / `onFilterChange` -### TableBody - -The body section of the table that contains the rows. - - - -### Row - -A row definition that describes how a row should be rendered. - - - -### Cell - -A cell definition that describes how a cell should be rendered. - - - -### TablePagination - -A pagination component designed to work with the Table component. - - - -## Examples - -### Basic Table - -Coming soon. - -### Row selection - -Tables support row selection with two configuration options: selection mode and selection behavior. - -#### Selection mode - -Use `selectionMode` to control how many rows can be selected. With `single`, only one row can be selected at a time. With `multiple`, any number of rows can be selected, and a header checkbox provides select-all functionality. - -} - code={tableSelectionModeSnippet} -/> - -#### Selection behavior - -Use `selectionBehavior` to control how selection is indicated and interacted with. With `toggle`, checkboxes appear for selection. With `replace`, selection is indicated by row background color—click to select, Cmd/Ctrl+click for multiple. - -} - code={tableSelectionBehaviorSnippet} -/> - -#### With row actions - -With toggle behavior, clicking a row triggers its action when nothing is selected. Once any row is selected, clicking toggles selection instead. - -With replace behavior, clicking selects the row and double-clicking triggers the action. - -} - code={tableSelectionActionsSnippet} -/> - -### Row Clicks - -Coming soon. - -### Pagination - -Coming soon. +## Common Patterns ### Sorting -Coming soon. +Columns can be made sortable by adding `isSortable: true` to the column configuration. When sortable, clicking a column header cycles through ascending, descending, and unsorted states. A visual indicator shows the current sort direction. -### Asynchronous loading +With `mode: 'complete'`, sorting happens client-side. Provide a `sortFn` that receives the full dataset and the current sort descriptor, and returns the sorted array. You can also set an `initialSort` to define the default sort when the table first renders. For server-side sorting with `offset` or `cursor` modes, see [Server-Side Data](#server-side-data). -Coming soon. +} code={tableSortingSnippet} /> -### Empty state +### Pagination -Coming soon. +Configure page size and available options through `paginationOptions`. The table displays navigation controls automatically. -### Column resizing + -This feature is not available yet — let us know if you'd like us to explore it! +### Search -### Column reordering +The `useTable` hook returns a `search` object with `value` and `onChange` properties, ready to connect to a search input. With `mode: 'complete'`, provide a `searchFn` that filters the dataset based on the search query. -This feature is not available yet — let us know if you'd like us to explore it! +The search state is debounced internally, so rapid typing doesn't trigger excessive re-filtering. When the search query changes, pagination resets to the first page automatically. -### Column pinning +For server-side search with `offset` or `cursor` modes, the search query is passed to your `getData` function. See [Server-Side Data](#server-side-data). -This feature is not available yet — let us know if you'd like us to explore it! +} code={tableSearchSnippet} /> -### Column visibility +### Row Selection -This feature is not available yet — let us know if you'd like us to explore it! +Tables support row selection with two configuration options: `mode` and `behavior`. + +Selection mode controls how many rows can be selected: + +- `single` - Only one row can be selected at a time +- `multiple` - Any number of rows can be selected, with a header checkbox for select-all + +Selection behavior controls the interaction style: + +- `toggle` - Checkboxes appear for selection. Click a checkbox to select/deselect. +- `replace` - No checkboxes. Click a row to select it (replacing previous selection). Use Cmd/Ctrl+click to select multiple rows. + +Selection state can be managed in two ways: + +- **Controlled** - Pass both `selected` and `onSelectionChange` to fully manage state externally +- **Uncontrolled** - Pass only `onSelectionChange` to let the Table manage state while receiving change notifications + +} code={tableSelectionSnippet} /> + +### Row Actions + +Rows can respond to user interaction in two ways: navigating to a URL or triggering a callback. + +Use `getHref` when rows should link to detail pages. The entire row becomes clickable and navigates on click. Links within cells remain independently clickable. + + + +Use `onClick` for custom actions like opening a panel or triggering a dialog. + + + +When combining row actions with selection, the interaction depends on selection behavior: + +- **`toggle`**: Clicking a row triggers its action when nothing is selected. Once any row is selected, clicking anywhere on the row toggles selection instead. +- **`replace`**: Single click selects the row. Double-click triggers the row action. + +You can also disable specific rows from being clicked using `getIsDisabled`: + + + +} code={tableRowActionsHrefSnippet} /> + +### Empty State + +When the table has no data to display, provide an `emptyState` to show a helpful message instead of an empty table body. Consider providing different messages depending on context - an empty search result is different from a table with no data at all. + +} code={tableEmptyStateSnippet} /> + +## Server-Side Data + +When your data comes from an API with server-side pagination, use `offset` or `cursor` mode instead of `complete`. The key difference: instead of providing all data upfront, you provide a `getData` function that fetches data for the current page. + +### Offset Pagination + +Use `mode: 'offset'` when your API accepts `offset` and `limit` (or `skip` and `take`) parameters. + + + +The `signal` parameter is an `AbortSignal` - pass it to `fetch` so in-flight requests are cancelled when the user navigates away or triggers a new query. + +### Cursor Pagination + +Use `mode: 'cursor'` when your API uses cursor-based pagination (common with GraphQL or APIs that return `nextCursor`/`prevCursor` tokens). + + + +### Loading States + +When fetching data, the table shows a loading state. If the user triggers a new query (by paginating, sorting, or searching) while previous data is displayed, the table enters a "stale" state where it continues showing the previous data until new data arrives. This prevents jarring layout shifts. + +You can access these states via `tableProps.loading` and `tableProps.isStale` if you need to render additional loading indicators. + +## Combining Features + +Real-world tables often combine multiple features. Here's an example with search, sorting, pagination, and row selection working together. + +} code={tableCombinedSnippet} /> + +## Custom Tables + +For most use cases, `useTable` + `Table` provides everything you need. When you need more control, there are several ways you can customize your table. + +### Custom Row and Header Rendering + +Use a render function for `rowConfig` when you need to customize individual rows based on their data - for example, highlighting rows that require attention. Use `header` in column config to customize header cell content. + +} code={tableCustomRowSnippet} /> + +### Using Primitives Directly + +When the `Table` component doesn't support your use case, you can compose with the lower-level primitives: `TableRoot`, `TableHeader`, `TableBody`, `Column`, and `Row`. + +} code={tablePrimitivesSnippet} /> + +## API Reference + +### useTable + +The `useTable` hook manages data fetching, pagination, sorting, and filtering. + +**Options** + + + +**Return Value** + + + +### Table + +The main table component. + + + +### ColumnConfig + + + +### CellText + + + + + +### CellProfile + + + + + +### TablePagination + + + +### Primitives + +Low-level components for building custom table layouts. + +#### TableRoot + + + + + +#### TableHeader + + + +#### TableBody + + + +#### Column + + + + + +#### Row + + + + + +#### Cell + + diff --git a/docs-ui/src/app/components/table/props-definition.ts b/docs-ui/src/app/components/table/props-definition.ts deleted file mode 100644 index e4ef0ffe8b..0000000000 --- a/docs-ui/src/app/components/table/props-definition.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const tablePropDefs: Record = { - selectionBehavior: { - type: 'enum', - values: ['toggle', 'replace'], - default: 'toggle', - description: 'How multiple selection should behave in the collection.', - }, - disabledBehavior: { - type: 'enum', - values: ['selection', 'all'], - default: 'selection', - description: - 'Whether disabledKeys applies to all interactions, or only selection.', - }, - disabledKeys: { - type: 'enum', - values: ['Iterable'], - description: 'A list of row keys to disable.', - }, - selectionMode: { - type: 'enum', - values: ['single', 'multiple'], - description: 'The type of selection that is allowed in the collection.', - }, - selectedKeys: { - type: 'enum', - values: ['all', 'Iterable'], - description: 'The currently selected keys in the collection (controlled).', - }, - defaultSelectedKeys: { - type: 'enum', - values: ['all', 'Iterable'], - description: 'The initial selected keys in the collection (uncontrolled).', - }, - onRowAction: { - type: 'enum', - values: ['(key: Key) => void'], - description: - 'Handler that is called when a user performs an action on the row.', - }, - onSelectionChange: { - type: 'enum', - values: ['(keys: Selection) => void'], - description: 'Handler that is called when the selection changes.', - }, - onSortChange: { - type: 'enum', - values: ['(descriptor: SortDescriptor) => any'], - description: - 'Handler that is called when the sorted column or direction changes.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const tableHeaderPropDefs: Record = { - onHoverStart: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - description: 'Handler that is called when a hover interaction starts.', - }, - onHoverEnd: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - description: 'Handler that is called when a hover interaction ends.', - }, - onHoverChange: { - type: 'enum', - values: ['(isHovering: boolean) => void'], - description: 'Handler that is called when the hover state changes.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const columnPropDefs: Record = { - id: { - type: 'enum', - values: ['Key'], - description: 'The unique id of the column.', - }, - allowsSorting: { - type: 'boolean', - description: 'Whether the column allows sorting.', - }, - isRowHeader: { - type: 'boolean', - description: - 'Whether a column is a row header and should be announced by assistive technology during row navigation.', - }, - textValue: { - type: 'string', - description: - "A string representation of the column's contents, used for accessibility announcements.", - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const tableBodyPropDefs: Record = { - renderEmptyState: { - type: 'enum', - values: ['(props) => ReactNode'], - description: - 'Provides content to display when there are no rows in the table.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const rowPropDefs: Record = { - textValue: { - type: 'string', - description: - "A string representation of the row's contents, used for accessibility announcements.", - }, - isDisabled: { - type: 'boolean', - description: 'Whether the row is disabled.', - }, - id: { - type: 'enum', - values: ['Key'], - description: 'The unique id of the row.', - }, - href: { - type: 'string', - description: 'The URL to navigate to when the row is clicked.', - }, - hrefLang: { - type: 'string', - description: - 'The language of the URL to navigate to when the row is clicked.', - }, - target: { - type: 'string', - description: - 'The target of the URL to navigate to when the row is clicked.', - }, - rel: { - type: 'string', - description: - 'The relationship of the URL to navigate to when the row is clicked.', - }, - onAction: { - type: 'enum', - values: ['() => void'], - description: - "Handler that is called when a user performs an action on the row. The exact user event depends on the collection's selectionBehavior prop and the interaction modality.", - }, - onHoverStart: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - description: 'Handler that is called when a hover interaction starts.', - }, - onHoverEnd: { - type: 'enum', - values: ['(e: HoverEvent) => void'], - description: 'Handler that is called when a hover interaction ends.', - }, - onHoverChange: { - type: 'enum', - values: ['(isHovering: boolean) => void'], - description: 'Handler that is called when the hover state changes.', - }, - onPress: { - type: 'enum', - values: ['(e: PressEvent) => void'], - description: - 'Handler that is called when the press is released over the target.', - }, - onPressStart: { - type: 'enum', - values: ['(e: PressEvent) => void'], - description: 'Handler that is called when a press interaction starts.', - }, - onPressEnd: { - type: 'enum', - values: ['(e: PressEvent) => void'], - description: - 'Handler that is called when a press interaction ends, either over the target or when the pointer leaves the target.', - }, - onPressChange: { - type: 'enum', - values: ['(isPressed: boolean) => void'], - description: 'Handler that is called when the press state changes.', - }, - onPressUp: { - type: 'enum', - values: ['(e: PressEvent) => void'], - description: - 'Handler that is called when a press is released over the target, regardless of whether it started on the target or not.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const cellPropDefs: Record = { - id: { - type: 'enum', - values: ['Key'], - description: 'The unique id of the cell.', - }, - textValue: { - type: 'string', - description: - "A string representation of the cell's contents, used for features like typeahead.", - }, - leadingIcon: { - type: 'enum', - values: ['ReactNode'], - description: 'Optional icon to display before the cell content.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const tablePaginationPropDefs: Record = { - offset: { - type: 'number', - description: 'The current offset (starting index) for pagination.', - }, - pageSize: { - type: 'number', - description: 'The number of items per page.', - }, - setOffset: { - type: 'enum', - values: ['(offset: number) => void'], - description: 'Handler that is called when the offset changes.', - }, - setPageSize: { - type: 'enum', - values: ['(pageSize: number) => void'], - description: 'Handler that is called when the page size changes.', - }, - rowCount: { - type: 'number', - description: 'The total number of rows in the table.', - }, - onNextPage: { - type: 'enum', - values: ['() => void'], - description: 'Handler that is called when the next page is requested.', - }, - onPreviousPage: { - type: 'enum', - values: ['() => void'], - description: 'Handler that is called when the previous page is requested.', - }, - onPageSizeChange: { - type: 'enum', - values: ['(pageSize: number) => void'], - description: 'Handler that is called when the page size changes.', - }, - showPageSizeOptions: { - type: 'boolean', - description: 'Whether to show the page size options.', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/docs-ui/src/app/components/table/props-definition.tsx b/docs-ui/src/app/components/table/props-definition.tsx new file mode 100644 index 0000000000..e653f67c8a --- /dev/null +++ b/docs-ui/src/app/components/table/props-definition.tsx @@ -0,0 +1,455 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; +import { Chip } from '@/components/Chip'; + +// ============================================================================= +// PropsTable Column Configuration (Table docs use description instead of responsive) +// ============================================================================= + +// For return values (no default column) +export const tableReturnColumns = [ + { key: 'prop' as const, width: '15%' }, + { key: 'type' as const, width: '25%' }, + { key: 'description' as const, width: '60%' }, +]; + +// ============================================================================= +// useTable Hook +// ============================================================================= + +export const useTableOptionsPropDefs: Record = { + mode: { + type: 'enum', + values: ['complete', 'offset', 'cursor'], + description: ( + <> + Data fetching strategy (required). Use complete for + client-side data, offset for offset/limit APIs,{' '} + cursor for cursor-based pagination. + + ), + }, + getData: { + type: 'enum', + values: ['function'], + description: + 'Function that returns or fetches data (required). Signature varies by mode.', + }, + paginationOptions: { + type: 'enum', + values: ['object'], + description: ( + <> + Pagination configuration including pageSize,{' '} + pageSizeOptions, and initialOffset. + + ), + }, + // Uncontrolled state + initialSort: { + type: 'enum', + values: ['SortDescriptor'], + description: 'Default sort configuration on first render (uncontrolled).', + }, + initialSearch: { + type: 'string', + description: 'Default search value on first render (uncontrolled).', + }, + initialFilter: { + type: 'enum', + values: ['TFilter'], + description: 'Default filter value on first render (uncontrolled).', + }, + // Controlled state + sort: { + type: 'enum', + values: ['SortDescriptor'], + description: 'Current sort state (controlled).', + }, + onSortChange: { + type: 'enum', + values: ['(sort: SortDescriptor) => void'], + description: 'Sort change handler (controlled).', + }, + search: { + type: 'string', + description: 'Current search value (controlled).', + }, + onSearchChange: { + type: 'enum', + values: ['(search: string) => void'], + description: 'Search change handler (controlled).', + }, + filter: { + type: 'enum', + values: ['TFilter'], + description: 'Current filter value (controlled).', + }, + onFilterChange: { + type: 'enum', + values: ['(filter: TFilter) => void'], + description: 'Filter change handler (controlled).', + }, + // Client-side functions + sortFn: { + type: 'enum', + values: ['(data, sort) => data'], + description: ( + <> + Client-side sort function. Only used with complete mode. + + ), + }, + searchFn: { + type: 'enum', + values: ['(data, query) => data'], + description: ( + <> + Client-side search function. Only used with complete mode. + + ), + }, + filterFn: { + type: 'enum', + values: ['(data, filter) => data'], + description: ( + <> + Client-side filter function. Only used with complete mode. + + ), + }, +}; + +export const useTableReturnPropDefs: Record = { + tableProps: { + type: 'enum', + values: ['object'], + description: ( + <> + Props to spread onto the Table component. Includes data, + loading, error, pagination, and sort state. + + ), + }, + reload: { + type: 'enum', + values: ['() => void'], + description: 'Function to trigger a data refetch.', + }, + search: { + type: 'enum', + values: ['{ value, onChange }'], + description: ( + <> + Search state object for binding to a SearchField component. + + ), + }, + filter: { + type: 'enum', + values: ['{ value, onChange }'], + description: 'Filter state object for binding to filter controls.', + }, +}; + +// ============================================================================= +// Table Component +// ============================================================================= + +export const tablePropDefs: Record = { + columnConfig: { + type: 'enum', + values: ['ColumnConfig[]'], + description: + 'Array of column configurations defining how each column renders.', + }, + data: { + type: 'enum', + values: ['T[]'], + description: 'Array of data items to display in the table.', + }, + loading: { + type: 'boolean', + default: 'false', + description: 'Whether the table is in a loading state.', + }, + isStale: { + type: 'boolean', + default: 'false', + description: + 'Whether the displayed data is stale while new data is loading.', + }, + error: { + type: 'enum', + values: ['Error'], + description: 'Error object if data fetching failed.', + }, + pagination: { + type: 'enum', + values: ['TablePaginationType'], + description: ( + <> + Pagination configuration (required). Use{' '} + {'{ type: "none" }'} to disable or{' '} + {'{ type: "page", ...props }'} for pagination. + + ), + }, + sort: { + type: 'enum', + values: ['SortState'], + description: 'Sort state including current descriptor and change handler.', + }, + rowConfig: { + type: 'enum', + values: ['RowConfig | RowRenderFn'], + description: ( + <> + Row configuration object with getHref, onClick + , getIsDisabled, or a render function for custom rows. + + ), + }, + selection: { + type: 'enum', + values: ['TableSelection'], + description: + 'Selection configuration including mode, behavior, selected keys, and change handler.', + }, + emptyState: { + type: 'enum', + values: ['ReactNode'], + description: 'Content to display when the table has no data.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +// ============================================================================= +// ColumnConfig +// ============================================================================= + +export const columnConfigPropDefs: Record = { + id: { + type: 'string', + description: 'Unique identifier for the column.', + }, + label: { + type: 'string', + description: 'Display label for the column header.', + }, + cell: { + type: 'enum', + values: ['(item) => ReactNode'], + description: 'Render function for cell content.', + }, + header: { + type: 'enum', + values: ['() => ReactNode'], + description: 'Optional custom render function for the header cell.', + }, + isSortable: { + type: 'boolean', + default: 'false', + description: 'Whether the column supports sorting.', + }, + isHidden: { + type: 'boolean', + default: 'false', + description: 'Whether the column is hidden.', + }, + isRowHeader: { + type: 'boolean', + default: 'false', + description: 'Whether this column is the row header for accessibility.', + }, + width: { + type: 'enum', + values: ['ColumnSize'], + description: 'Current width of the column.', + }, + defaultWidth: { + type: 'enum', + values: ['ColumnSize'], + description: ( + <> + Default width of the column (e.g., 1fr, 200px + ). + + ), + }, + minWidth: { + type: 'enum', + values: ['ColumnStaticSize'], + description: 'Minimum width of the column.', + }, + maxWidth: { + type: 'enum', + values: ['ColumnStaticSize'], + description: 'Maximum width of the column.', + }, +}; + +// ============================================================================= +// CellText +// Note: Extends React Aria Cell props +// ============================================================================= + +export const cellTextPropDefs: Record = { + title: { + type: 'string', + description: 'Primary text content of the cell.', + }, + description: { + type: 'string', + description: 'Secondary description text displayed below the title.', + }, + color: { + type: 'enum', + values: ['TextColors'], + description: 'Text color variant.', + }, + leadingIcon: { + type: 'enum', + values: ['ReactNode'], + description: 'Icon displayed before the text content.', + }, + href: { + type: 'string', + description: 'URL to navigate to when the cell is clicked.', + }, +}; + +// ============================================================================= +// CellProfile +// Note: Extends React Aria Cell props +// ============================================================================= + +export const cellProfilePropDefs: Record = { + name: { + type: 'string', + description: 'Name to display.', + }, + src: { + type: 'string', + description: 'URL of the avatar image.', + }, + description: { + type: 'string', + description: 'Secondary text displayed below the name.', + }, + href: { + type: 'string', + description: 'URL to navigate to when clicked.', + }, + color: { + type: 'enum', + values: ['TextColors'], + description: 'Text color variant.', + }, +}; + +// ============================================================================= +// TablePagination +// ============================================================================= + +export const tablePaginationPropDefs: Record = { + pageSize: { + type: 'number', + description: 'Number of items per page.', + }, + pageSizeOptions: { + type: 'enum', + values: ['number[]'], + description: 'Available page size options for the dropdown.', + }, + offset: { + type: 'number', + description: 'Current offset (starting index) in the data.', + }, + totalCount: { + type: 'number', + description: 'Total number of items across all pages.', + }, + hasNextPage: { + type: 'boolean', + description: 'Whether there is a next page available.', + }, + hasPreviousPage: { + type: 'boolean', + description: 'Whether there is a previous page available.', + }, + onNextPage: { + type: 'enum', + values: ['() => void'], + description: 'Handler called when navigating to the next page.', + }, + onPreviousPage: { + type: 'enum', + values: ['() => void'], + description: 'Handler called when navigating to the previous page.', + }, + onPageSizeChange: { + type: 'enum', + values: ['(size: number) => void'], + description: 'Handler called when the page size changes.', + }, + showPageSizeOptions: { + type: 'boolean', + default: 'true', + description: 'Whether to show the page size dropdown.', + }, + getLabel: { + type: 'enum', + values: ['(props) => string'], + description: 'Custom function to generate the pagination label text.', + }, +}; + +// ============================================================================= +// Primitives +// ============================================================================= + +export const tableRootPropDefs: Record = { + stale: { + type: 'boolean', + default: 'false', + description: ( + <> + Whether the table data is stale (e.g., while fetching new data). Adds{' '} + aria-busy attribute. + + ), + }, +}; + +export const columnPropDefs: Record = { + isRowHeader: { + type: 'boolean', + description: 'Whether this column is a row header for accessibility.', + }, + children: { + type: 'enum', + values: ['ReactNode'], + description: 'Column header content.', + }, +}; + +export const rowPropDefs: Record = { + id: { + type: 'enum', + values: ['string | number'], + description: 'Unique identifier for the row.', + }, + children: { + type: 'enum', + values: ['ReactNode | ((column) => ReactNode)'], + description: + 'Row content. Can be a render function receiving column config.', + }, + ...classNamePropDefs, + ...stylePropDefs, +}; diff --git a/docs-ui/src/app/components/table/snippets.ts b/docs-ui/src/app/components/table/snippets.ts index 0c8e029330..cba479b59f 100644 --- a/docs-ui/src/app/components/table/snippets.ts +++ b/docs-ui/src/app/components/table/snippets.ts @@ -1,108 +1,359 @@ -export const tableUsageSnippet = `import { Cell, CellText, ..., TableHeader, TablePagination } from '@backstage/ui'; +// ============================================================================= +// Usage +// ============================================================================= -
- - - - - - - - - -
-`; +export const tableUsageSnippet = `import { Table, useTable, CellText, type ColumnConfig } from '@backstage/ui'; -export const tableBasicSnippet = `import { Table, TableHeader, Column, TableBody, Row, CellText, CellProfile, TablePagination, useTable } from '@backstage/ui'; - -const data = [ - { - name: 'The Beatles', - image: 'https://upload.wikimedia.org/wikipedia/en/thumb/4/42/Beatles_-...jpg', - genre: 'Rock, Pop, Psychedelic Rock', - yearFormed: 1960, - albums: 13 - }, - // ... more data +const columns: ColumnConfig[] = [ + { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, + { id: 'owner', label: 'Owner', cell: item => }, ]; -// Uncontrolled pagination (easiest) -const { data: paginatedData, paginationProps } = useTable({ - data, - pagination: { - defaultPageSize: 5, +function MyTable() { + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data, + }); + + return ; +}`; + +// ============================================================================= +// Hero / Quick Start +// ============================================================================= + +export const tableHeroSnippet = `import { Table, CellText, useTable, type ColumnConfig } from '@backstage/ui'; + +const data = [ + { id: 1, name: 'Service A', owner: 'Team Alpha', type: 'service' }, + { id: 2, name: 'Service B', owner: 'Team Beta', type: 'website' }, + { id: 3, name: 'Library C', owner: 'Team Gamma', type: 'library' }, +]; + +const columns: ColumnConfig[] = [ + { id: 'name', label: 'Name', isRowHeader: true, cell: item => }, + { id: 'owner', label: 'Owner', cell: item => }, + { id: 'type', label: 'Type', cell: item => }, +]; + +function MyTable() { + const { tableProps } = useTable({ + mode: 'complete', + getData: () => data, + }); + + return
; +}`; + +// ============================================================================= +// Core Concepts +// ============================================================================= + +export const tableConceptsSnippet = `// What useTable returns +const { + tableProps, // Spread onto
+ reload, // Trigger data refetch + search, // { value, onChange } for search input + filter, // { value, onChange } for filters +} = useTable({ ... });`; + +// ============================================================================= +// Common Patterns +// ============================================================================= + +export const tableSortingSnippet = `const columns: ColumnConfig[] = [ + { id: 'name', label: 'Name', isSortable: true, cell: item => }, + { id: 'owner', label: 'Owner', isSortable: true, cell: item => }, + { id: 'type', label: 'Type', cell: item => }, +]; + +const { tableProps } = useTable({ + mode: 'complete', + getData: () => data, + initialSort: { column: 'name', direction: 'ascending' }, + sortFn: (items, { column, direction }) => { + return [...items].sort((a, b) => { + const aVal = String(a[column]); + const bVal = String(b[column]); + const cmp = aVal.localeCompare(bVal); + return direction === 'descending' ? -cmp : cmp; + }); }, }); -
- - Band name - Genre - Year formed - Albums - - - {paginatedData?.map(item => ( - - - - - - - ))} - -
-`; +return ;`; -export const tableSelectionActionsSnippet = `import { Table, TableHeader, TableBody, Column, Row, CellText } from '@backstage/ui'; +export const tablePaginationSnippet = `const { tableProps } = useTable({ + mode: 'complete', + getData: () => data, + paginationOptions: { + pageSize: 10, + pageSizeOptions: [10, 25, 50], + }, +});`; -function MyTable() { - const [selectedKeys, setSelectedKeys] = React.useState(new Set([])); +export const tableSearchSnippet = `const { tableProps, search } = useTable({ + mode: 'complete', + getData: () => data, + searchFn: (items, query) => { + const lowerQuery = query.toLowerCase(); + return items.filter(item => + item.name.toLowerCase().includes(lowerQuery) || + item.owner.toLowerCase().includes(lowerQuery) + ); + }, +}); + +return ( + <> + +
+ +);`; + +export const tableSelectionSnippet = `const [selected, setSelected] = useState | 'all'>(new Set()); + +const { tableProps } = useTable({ + mode: 'complete', + getData: () => data, +}); + +return ( +
+);`; + +export const tableRowActionsHrefSnippet = `
\`/catalog/\${item.namespace}/\${item.name}\` + }} + {...tableProps} +/>`; + +export const tableRowActionsClickSnippet = `
openDetailPanel(item) + }} + {...tableProps} +/>`; + +export const tableRowActionsDisabledSnippet = `
openDetailPanel(item), + getIsDisabled: item => item.status === 'archived', + }} + {...tableProps} +/>`; + +export const tableEmptyStateSnippet = `const { tableProps, search } = useTable({ + mode: 'complete', + getData: () => data, + searchFn: (items, query) => { /* ... */ }, +}); + +return ( +
No results match "{search.value}" + : No items yet. Create one to get started. + } + {...tableProps} + /> +);`; + +// ============================================================================= +// Server-Side Data +// ============================================================================= + +export const tableOffsetPaginationSnippet = `const { tableProps } = useTable({ + mode: 'offset', + getData: async ({ offset, pageSize, sort, search, filter, signal }) => { + const response = await fetch( + \`/api/items?offset=\${offset}&limit=\${pageSize}&q=\${search}\`, + { signal } + ); + const { items, totalCount } = await response.json(); + + return { + data: items, + totalCount, + }; + }, + paginationOptions: { + pageSize: 20, + pageSizeOptions: [20, 50, 100], + }, +});`; + +export const tableCursorPaginationSnippet = `const { tableProps } = useTable({ + mode: 'cursor', + getData: async ({ cursor, pageSize, sort, search, signal }) => { + const response = await fetch( + \`/api/items?cursor=\${cursor ?? ''}&limit=\${pageSize}&q=\${search}\`, + { signal } + ); + const { items, nextCursor, prevCursor, totalCount } = await response.json(); + + return { + data: items, + nextCursor, + prevCursor, + totalCount, // optional - enables "X of Y" display + }; + }, + paginationOptions: { + pageSize: 20, + pageSizeOptions: [20, 50, 100], + }, +});`; + +// ============================================================================= +// Combining Features +// ============================================================================= + +export const tableCombinedSnippet = `interface TypeFilter { + type: string | null; +} + +const columns: ColumnConfig[] = [ + { id: 'name', label: 'Name', isRowHeader: true, isSortable: true, + cell: item => }, + { id: 'owner', label: 'Owner', isSortable: true, + cell: item => }, + { id: 'type', label: 'Type', isSortable: true, + cell: item => }, +]; + +function ItemsTable() { + const [selected, setSelected] = useState | 'all'>(new Set()); + + const { tableProps, search, filter } = useTable({ + mode: 'offset', + initialSort: { column: 'name', direction: 'ascending' }, + getData: async ({ offset, pageSize, sort, search, filter, signal }) => { + const params = new URLSearchParams({ + offset: String(offset), + limit: String(pageSize), + q: search, + ...(sort && { sortBy: sort.column, sortDir: sort.direction }), + ...(filter?.type && { type: filter.type }), + }); + + const response = await fetch(\`/api/items?\${params}\`, { signal }); + const { items, totalCount } = await response.json(); + + return { data: items, totalCount }; + }, + }); return ( -
console.log('Opening', key)} - > - - Name - Status - - - - - - - - - - - -
+ + + +