From b25977f749a7a333c5aad1f090127d0ad13928f1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Apr 2025 15:21:09 +0000 Subject: [PATCH 001/301] Update dependency @davidzemon/passport-okta-oauth to ^0.0.6 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .changeset/renovate-a499ca4.md | 5 +++++ plugins/auth-backend-module-okta-provider/package.json | 2 +- yarn.lock | 10 +++++----- 3 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 .changeset/renovate-a499ca4.md diff --git a/.changeset/renovate-a499ca4.md b/.changeset/renovate-a499ca4.md new file mode 100644 index 0000000000..fc54dcfbf2 --- /dev/null +++ b/.changeset/renovate-a499ca4.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-okta-provider': patch +--- + +Updated dependency `@davidzemon/passport-okta-oauth` to `^0.0.6`. diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json index 08546307cd..e30d60621c 100644 --- a/plugins/auth-backend-module-okta-provider/package.json +++ b/plugins/auth-backend-module-okta-provider/package.json @@ -36,7 +36,7 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", - "@davidzemon/passport-okta-oauth": "^0.0.5", + "@davidzemon/passport-okta-oauth": "^0.0.6", "express": "^4.18.2", "passport": "^0.7.0" }, diff --git a/yarn.lock b/yarn.lock index a76671402b..774100ad84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5454,7 +5454,7 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/types": "workspace:^" - "@davidzemon/passport-okta-oauth": "npm:^0.0.5" + "@davidzemon/passport-okta-oauth": "npm:^0.0.6" express: "npm:^4.18.2" passport: "npm:^0.7.0" supertest: "npm:^7.0.0" @@ -9416,15 +9416,15 @@ __metadata: languageName: node linkType: hard -"@davidzemon/passport-okta-oauth@npm:^0.0.5": - version: 0.0.5 - resolution: "@davidzemon/passport-okta-oauth@npm:0.0.5" +"@davidzemon/passport-okta-oauth@npm:^0.0.6": + version: 0.0.6 + resolution: "@davidzemon/passport-okta-oauth@npm:0.0.6" dependencies: "@types/passport-oauth2": "npm:^1.4.11" passport-oauth2: "npm:^1.6.1" pkginfo: "npm:^0.4.1" uid2: "npm:^1.0.0" - checksum: 10/6932aca4213f1e47c41454cbb9ef1309643ffa14fa3da401f18e397e76f3fa1669be76d96db65dd6435854d7f05b30d3a27f645d10e3c6ed955ad30930d47b3e + checksum: 10/d29a26459ff387d7507c031a6814e3581eb47e2dc9dda6848667362973455cd125e2a251b30ad6c093dfd6cd8ee7adf48301f395808b212008a6ee702a507354 languageName: node linkType: hard From 3ea5d1b6ff765d3a52b6207c3c14d9d18430e24c Mon Sep 17 00:00:00 2001 From: Gabriel Dugny Date: Wed, 16 Apr 2025 00:01:23 +0200 Subject: [PATCH 002/301] chore(deps): Update deprecated xterm to new package under @xterm Signed-off-by: Gabriel Dugny --- .changeset/fine-spoons-laugh.md | 6 ++ plugins/kubernetes-react/package.json | 8 +-- .../PodExecTerminal/PodExecTerminal.tsx | 6 +- .../PodExecTerminalAttachAddon.ts | 2 +- plugins/kubernetes/package.json | 8 +-- yarn.lock | 62 +++++++++---------- 6 files changed, 49 insertions(+), 43 deletions(-) create mode 100644 .changeset/fine-spoons-laugh.md diff --git a/.changeset/fine-spoons-laugh.md b/.changeset/fine-spoons-laugh.md new file mode 100644 index 0000000000..a27ef7e7d5 --- /dev/null +++ b/.changeset/fine-spoons-laugh.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-kubernetes-react': patch +'@backstage/plugin-kubernetes': patch +--- + +chore(deps): Update deprecated xterm to new package under @xterm diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index 4b1c75cb51..772070bef7 100644 --- a/plugins/kubernetes-react/package.json +++ b/plugins/kubernetes-react/package.json @@ -66,15 +66,15 @@ "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.11.3", "@material-ui/lab": "^4.0.0-alpha.61", + "@xterm/addon-attach": "^0.11.0", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", "cronstrue": "^2.32.0", "js-yaml": "^4.1.0", "kubernetes-models": "^4.3.1", "lodash": "^4.17.21", "luxon": "^3.0.0", - "react-use": "^17.4.0", - "xterm": "^5.3.0", - "xterm-addon-attach": "^0.9.0", - "xterm-addon-fit": "^0.8.0" + "react-use": "^17.4.0" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.tsx b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.tsx index eeaf535a05..a3ef823356 100644 --- a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.tsx +++ b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.tsx @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import 'xterm/css/xterm.css'; +import '@xterm/xterm/css/xterm.css'; import { discoveryApiRef, useApi } from '@backstage/core-plugin-api'; import { ClusterAttributes } from '@backstage/plugin-kubernetes-common'; import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; import { useRef, useEffect, useMemo, useState } from 'react'; -import { Terminal } from 'xterm'; -import { FitAddon } from 'xterm-addon-fit'; +import { Terminal } from '@xterm/xterm'; +import { FitAddon } from '@xterm/addon-fit'; import { PodExecTerminalAttachAddon } from './PodExecTerminalAttachAddon'; diff --git a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalAttachAddon.ts b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalAttachAddon.ts index dba58a6746..55171e1cb1 100644 --- a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalAttachAddon.ts +++ b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminalAttachAddon.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AttachAddon, IAttachOptions } from 'xterm-addon-attach'; +import { AttachAddon, IAttachOptions } from '@xterm/addon-attach'; export class PodExecTerminalAttachAddon extends AttachAddon { #textEncoder = new TextEncoder(); diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index d589cd7efd..0fa7818891 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -71,14 +71,14 @@ "@kubernetes-models/base": "^5.0.0", "@kubernetes/client-node": "1.0.0-rc7", "@material-ui/core": "^4.12.2", + "@xterm/addon-attach": "^0.11.0", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", "cronstrue": "^2.2.0", "js-yaml": "^4.0.0", "kubernetes-models": "^4.1.0", "lodash": "^4.17.21", - "luxon": "^3.0.0", - "xterm": "^5.2.1", - "xterm-addon-attach": "^0.9.0", - "xterm-addon-fit": "^0.8.0" + "luxon": "^3.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/yarn.lock b/yarn.lock index 01cdf6283d..5657490f2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6690,6 +6690,9 @@ __metadata: "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@types/react": "npm:^18.0.0" + "@xterm/addon-attach": "npm:^0.11.0" + "@xterm/addon-fit": "npm:^0.10.0" + "@xterm/xterm": "npm:^5.5.0" cronstrue: "npm:^2.32.0" jest-websocket-mock: "npm:^2.5.0" js-yaml: "npm:^4.1.0" @@ -6701,9 +6704,6 @@ __metadata: react-dom: "npm:^18.0.2" react-router-dom: "npm:^6.3.0" react-use: "npm:^17.4.0" - xterm: "npm:^5.3.0" - xterm-addon-attach: "npm:^0.9.0" - xterm-addon-fit: "npm:^0.8.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 @@ -6739,6 +6739,9 @@ __metadata: "@testing-library/jest-dom": "npm:^6.0.0" "@testing-library/react": "npm:^16.0.0" "@types/react": "npm:^18.0.0" + "@xterm/addon-attach": "npm:^0.11.0" + "@xterm/addon-fit": "npm:^0.10.0" + "@xterm/xterm": "npm:^5.5.0" cronstrue: "npm:^2.2.0" js-yaml: "npm:^4.0.0" kubernetes-models: "npm:^4.1.0" @@ -6747,9 +6750,6 @@ __metadata: react: "npm:^18.0.2" react-dom: "npm:^18.0.2" react-router-dom: "npm:^6.3.0" - xterm: "npm:^5.2.1" - xterm-addon-attach: "npm:^0.9.0" - xterm-addon-fit: "npm:^0.8.0" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 @@ -21904,6 +21904,31 @@ __metadata: languageName: node linkType: hard +"@xterm/addon-attach@npm:^0.11.0": + version: 0.11.0 + resolution: "@xterm/addon-attach@npm:0.11.0" + peerDependencies: + "@xterm/xterm": ^5.0.0 + checksum: 10/f11e7ba68b8b1064ce545ecbf2beeaf3ebc94622fa3ca77e0ca0b8e8834964f634ee3897c031376abc7242ddfd305a40b10aa7b3e8056bfa59055967251523b5 + languageName: node + linkType: hard + +"@xterm/addon-fit@npm:^0.10.0": + version: 0.10.0 + resolution: "@xterm/addon-fit@npm:0.10.0" + peerDependencies: + "@xterm/xterm": ^5.0.0 + checksum: 10/8edfad561c0d0316c5883cbe2ce56109f105a2b2bf53b71d5f8c788e656a3205c1093a659dddcf4025a459e4b7ff8e07b6c6a19815c8711deeded560de5f1893 + languageName: node + linkType: hard + +"@xterm/xterm@npm:^5.5.0": + version: 5.5.0 + resolution: "@xterm/xterm@npm:5.5.0" + checksum: 10/d4cdc402de81a83a3e0ef93f38072cb8f54abe4d65865f2e29b92cbc2593f86d052f6b993895c9e5dec97f47548f504e90bcea0aad6845917c09b03f2f3a4629 + languageName: node + linkType: hard + "@xtuc/ieee754@npm:^1.2.0": version: 1.2.0 resolution: "@xtuc/ieee754@npm:1.2.0" @@ -48033,31 +48058,6 @@ __metadata: languageName: node linkType: hard -"xterm-addon-attach@npm:^0.9.0": - version: 0.9.0 - resolution: "xterm-addon-attach@npm:0.9.0" - peerDependencies: - xterm: ^5.0.0 - checksum: 10/70e5d3ecf139c04fae13c644b79c33858ef1a6e28dfe78f91dad3e34f5a155579029b87e91d1d016575acaf17f74e6c59402bde4bcff03461595bea0870f1ec1 - languageName: node - linkType: hard - -"xterm-addon-fit@npm:^0.8.0": - version: 0.8.0 - resolution: "xterm-addon-fit@npm:0.8.0" - peerDependencies: - xterm: ^5.0.0 - checksum: 10/5af2041b442f7c804eda2e6f62e3b68b5159b0ae6bd96e2aa8d85b26441df57291cbfed653d1196d4af5d9b94bfc39993df8b409a25c35e0d36bdaf6f5cdfe5f - languageName: node - linkType: hard - -"xterm@npm:^5.2.1, xterm@npm:^5.3.0": - version: 5.3.0 - resolution: "xterm@npm:5.3.0" - checksum: 10/3690b6a6d744f1d2932279975bb8e6c786e70c675531045016ecfe0f9b7957e2fc6811b815335f3e0e84b40ffae654f6ee4afe55a5768534744497e62252dd50 - languageName: node - linkType: hard - "y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8" From 233936366b8d294181f63875f6b59fc2734cdd62 Mon Sep 17 00:00:00 2001 From: Thomas Cardonne Date: Tue, 29 Apr 2025 18:31:00 +0200 Subject: [PATCH 003/301] feat(catalog)!: remove catalog.useUrlReadersSearch and always use search Signed-off-by: Thomas Cardonne --- .changeset/cyan-islands-swim.md | 14 ++++++ app-config.yaml | 1 - .../templates/default-app/app-config.yaml.hbs | 3 -- plugins/catalog-backend/config.d.ts | 14 ------ plugins/catalog-backend/package.json | 1 - plugins/catalog-backend/report.api.md | 6 +-- .../src/processors/UrlReaderProcessor.test.ts | 38 +-------------- .../src/processors/UrlReaderProcessor.ts | 47 +++---------------- .../src/service/CatalogBuilder.ts | 2 +- yarn.lock | 1 - 10 files changed, 24 insertions(+), 103 deletions(-) create mode 100644 .changeset/cyan-islands-swim.md diff --git a/.changeset/cyan-islands-swim.md b/.changeset/cyan-islands-swim.md new file mode 100644 index 0000000000..396cc6fc68 --- /dev/null +++ b/.changeset/cyan-islands-swim.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-catalog-backend': major +'@backstage/create-app': patch +--- + +**BREAKING:** The experimental `catalog.useUrlReadersSearch` configuration flag (introduced in v1.36) has been removed. + +The `UrlReaderProcessor` now always uses the `search` method of `UrlReaders`. Built-in `UrlReaderService` implementations have been updated accordingly. +If you use custom `UrlReaderService` implementations, you need to adapt their `search` method to correctly handle both specific URLs and potential +search patterns (see changes on built-in readers [in the original PR](https://github.com/backstage/backstage/pull/28379/files#diff-68b0452f173ee54bdd40f7b5e047a9cb8bb59200425622c212c217b76dac1d1b)). + +Previous behavior was to call the `search` method only if the parsed Git URL's filename contained a wildcard and use `readUrl` otherwise. Each `UrlReaderService` must implement this logic in the `search` method instead. + +This allows each `UrlReaderService` implementation to check whether it's a search URL (that contains a wildcard pattern) or not using logic that is specific to each provider. diff --git a/app-config.yaml b/app-config.yaml index 89e417fad3..64c2fa5c4c 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -244,7 +244,6 @@ integrations: secretAccessKey: ${AWS_SECRET_ACCESS_KEY} catalog: - useUrlReadersSearch: true import: entityFilename: catalog-info.yaml pullRequestBranchName: backstage-integration diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index fc6918acea..ca52ec530c 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -103,9 +103,6 @@ catalog: # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml # rules: # - allow: [User, Group] - # Experimental: Always use the search method in UrlReaderProcessor. - # New adopters are encouraged to enable it as this behavior will be the default in a future release. - useUrlReadersSearch: true kubernetes: # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index 7e68c4bea9..3ba6047ada 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -218,19 +218,5 @@ export interface Config { * housing catalog-info files. */ processingInterval?: HumanDuration | false; - - /** - * Defines if the UrlReaderProcessor should always call the search method of the - * different UrlReaders. - * - * If set to false, the UrlReaderProcessor will use the legacy behavior that tries to - * parse a Git URL and calls search if there's wildcard patterns and readUrl otherwise. - * - * If set to true, the UrlReaderProcessor always call the search method and lets each UrlReader - * determine if it's a search pattern or not. - * - * This flag is temporary and will be enabled by default in future releases. - */ - useUrlReadersSearch?: boolean; }; } diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 8f2f8afe94..1b68e7d58d 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -99,7 +99,6 @@ "@backstage/cli": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/repo-tools": "workspace:^", - "@backstage/test-utils": "workspace:^", "@types/core-js": "^2.5.4", "@types/express": "^4.17.6", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/catalog-backend/report.api.md b/plugins/catalog-backend/report.api.md index 9bb94b2dd5..6e5ca956bc 100644 --- a/plugins/catalog-backend/report.api.md +++ b/plugins/catalog-backend/report.api.md @@ -135,11 +135,7 @@ export function transformLegacyPolicyToProcessor( // @public (undocumented) export class UrlReaderProcessor implements CatalogProcessor { - constructor(options: { - reader: UrlReaderService; - logger: LoggerService; - config?: Config; - }); + constructor(options: { reader: UrlReaderService; logger: LoggerService }); // (undocumented) getProcessorName(): string; // (undocumented) diff --git a/plugins/catalog-backend/src/processors/UrlReaderProcessor.test.ts b/plugins/catalog-backend/src/processors/UrlReaderProcessor.test.ts index 2d7268eb33..0b17ecb19f 100644 --- a/plugins/catalog-backend/src/processors/UrlReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/processors/UrlReaderProcessor.test.ts @@ -30,8 +30,6 @@ import { import { defaultEntityDataParser } from '../util/parse'; import { UrlReaderProcessor } from './UrlReaderProcessor'; import { UrlReaders } from '@backstage/backend-defaults/urlReader'; -import { UrlReaderService } from '@backstage/backend-plugin-api'; -import { mockApis } from '@backstage/test-utils'; describe('UrlReaderProcessor', () => { const mockApiOrigin = 'http://localhost'; @@ -191,11 +189,11 @@ describe('UrlReaderProcessor', () => { expect(generated.location).toBe(spec); expect(generated.error.name).toBe('NotFoundError'); expect(generated.error.message).toBe( - `Unable to read url, NotFoundError: could not read ${mockApiOrigin}/component-notfound.yaml, 404 Not Found`, + `Unable to read url, no matching files found for ${mockApiOrigin}/component-notfound.yaml`, ); }); - it('uses search when there are globs', async () => { + it("uses reader' search method", async () => { const logger = mockServices.logger.mock(); const reader = mockServices.urlReader.mock({ @@ -206,38 +204,6 @@ describe('UrlReaderProcessor', () => { const emit = jest.fn(); - await processor.readLocation( - { type: 'url', target: 'https://github.com/a/b/blob/x/**/b.yaml' }, - false, - emit, - defaultEntityDataParser, - mockCache, - ); - - expect(reader.search).toHaveBeenCalledTimes(1); - }); - - it('uses search when catalog.useUrlReadersSearch flag is set to true', async () => { - const logger = mockServices.logger.mock(); - - const reader: jest.Mocked = { - readUrl: jest.fn(), - readTree: jest.fn(), - search: jest.fn().mockImplementation(async () => []), - }; - - const config = mockApis.config({ - data: { - catalog: { - useUrlReadersSearch: true, - }, - }, - }); - - const processor = new UrlReaderProcessor({ reader, logger, config }); - - const emit = jest.fn(); - await processor.readLocation( { type: 'url', target: 'https://github.com/a/b/blob/x/b.yaml' }, false, diff --git a/plugins/catalog-backend/src/processors/UrlReaderProcessor.ts b/plugins/catalog-backend/src/processors/UrlReaderProcessor.ts index bc383512be..4440a81bae 100644 --- a/plugins/catalog-backend/src/processors/UrlReaderProcessor.ts +++ b/plugins/catalog-backend/src/processors/UrlReaderProcessor.ts @@ -18,7 +18,6 @@ import { Entity } from '@backstage/catalog-model'; import { assertError } from '@backstage/errors'; import limiterFactory, { Limit } from 'p-limit'; import { LocationSpec } from '@backstage/plugin-catalog-common'; -import parseGitUrl from 'git-url-parse'; import { CatalogProcessor, CatalogProcessorCache, @@ -29,7 +28,6 @@ import { processingResult, } from '@backstage/plugin-catalog-node'; import { LoggerService, UrlReaderService } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; const CACHE_KEY = 'v1'; @@ -48,25 +46,14 @@ export class UrlReaderProcessor implements CatalogProcessor { // This limiter is used for only consuming a limited number of read streams // concurrently. #limiter: Limit; - #useUrlReadersSearch: boolean; constructor( private readonly options: { reader: UrlReaderService; logger: LoggerService; - config?: Config; }, ) { this.#limiter = limiterFactory(5); - - this.#useUrlReadersSearch = - this.options.config?.getOptionalBoolean('catalog.useUrlReadersSearch') || - false; - if (!this.#useUrlReadersSearch) { - this.options.logger.warn( - 'UrlReaderProcessor uses the legacy readUrl/search behavior which will be removed in a future release. Set catalog.useUrlReadersSearch to true to adopt the new behavior.', - ); - } } getProcessorName() { @@ -149,35 +136,13 @@ export class UrlReaderProcessor implements CatalogProcessor { location: string, etag?: string, ): Promise<{ response: { data: Buffer; url: string }[]; etag?: string }> { - // New behavior: always use the search method - if (this.#useUrlReadersSearch) { - const response = await this.options.reader.search(location, { etag }); + const response = await this.options.reader.search(location, { etag }); - const output = response.files.map(async file => ({ - url: file.url, - data: await this.#limiter(file.content), - })); + const output = response.files.map(async file => ({ + url: file.url, + data: await this.#limiter(file.content), + })); - return { response: await Promise.all(output), etag: response.etag }; - } - - // Old behavior: Does it contain globs? I.e. does it contain asterisks or question marks - // (no curly braces for now) - - const { filepath } = parseGitUrl(location); - if (filepath?.match(/[*?]/)) { - const response = await this.options.reader.search(location, { etag }); - const output = response.files.map(async file => ({ - url: file.url, - data: await this.#limiter(file.content), - })); - return { response: await Promise.all(output), etag: response.etag }; - } - - const data = await this.options.reader.readUrl(location, { etag }); - return { - response: [{ url: location, data: await data.buffer() }], - etag: data.etag, - }; + return { response: await Promise.all(output), etag: response.etag }; } } diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index c9d75298c8..e50aa84d80 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -356,7 +356,7 @@ export class CatalogBuilder { return [ new FileReaderProcessor(), - new UrlReaderProcessor({ reader, logger, config }), + new UrlReaderProcessor({ reader, logger }), new AnnotateLocationEntityProcessor({ integrations }), ]; } diff --git a/yarn.lock b/yarn.lock index 6d4aa9e768..4081495be9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6135,7 +6135,6 @@ __metadata: "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-permission-node": "workspace:^" "@backstage/repo-tools": "workspace:^" - "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" "@opentelemetry/api": "npm:^1.9.0" "@types/core-js": "npm:^2.5.4" From a372bf1b8492c7551f50267295d8ee7d81111883 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 13 May 2025 02:44:27 +0000 Subject: [PATCH 004/301] Update dependency @electric-sql/pglite to ^0.3.0 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .changeset/renovate-9d662c8.md | 5 +++++ packages/repo-tools/package.json | 2 +- yarn.lock | 11 +++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 .changeset/renovate-9d662c8.md diff --git a/.changeset/renovate-9d662c8.md b/.changeset/renovate-9d662c8.md new file mode 100644 index 0000000000..9178efeafa --- /dev/null +++ b/.changeset/renovate-9d662c8.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Updated dependency `@electric-sql/pglite` to `^0.3.0`. diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 77783ff1dc..cf478b0648 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -49,7 +49,7 @@ "@backstage/cli-node": "workspace:^", "@backstage/config-loader": "workspace:^", "@backstage/errors": "workspace:^", - "@electric-sql/pglite": "^0.2.15", + "@electric-sql/pglite": "^0.3.0", "@manypkg/get-packages": "^1.1.3", "@microsoft/api-documenter": "^7.25.7", "@microsoft/api-extractor": "^7.47.2", diff --git a/yarn.lock b/yarn.lock index 91c2126f7c..ffb73d1185 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8909,7 +8909,7 @@ __metadata: "@backstage/config-loader": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/types": "workspace:^" - "@electric-sql/pglite": "npm:^0.2.15" + "@electric-sql/pglite": "npm:^0.3.0" "@manypkg/get-packages": "npm:^1.1.3" "@microsoft/api-documenter": "npm:^7.25.7" "@microsoft/api-extractor": "npm:^7.47.2" @@ -9631,13 +9631,20 @@ __metadata: languageName: node linkType: hard -"@electric-sql/pglite@npm:^0.2.14, @electric-sql/pglite@npm:^0.2.15": +"@electric-sql/pglite@npm:^0.2.14": version: 0.2.17 resolution: "@electric-sql/pglite@npm:0.2.17" checksum: 10/cb04d18614c9a3d985fa63f5ca889104eab55b15a9aa569df264e335ea559f2279ee64c1284aa68d5b92ff516dd37ae553299cf55c226a3e541d6c46416c7160 languageName: node linkType: hard +"@electric-sql/pglite@npm:^0.3.0": + version: 0.3.1 + resolution: "@electric-sql/pglite@npm:0.3.1" + checksum: 10/515eac81a03cace6c6fe89642c2a06d8fc10f2b829038d07782f6524aa15fc36632a853d9acb2f5aff12d9602149b5db4e0c220ba6dc16ff41939394aa9de49f + languageName: node + linkType: hard + "@emotion/babel-plugin@npm:^11.13.5": version: 11.13.5 resolution: "@emotion/babel-plugin@npm:11.13.5" From 9fa96476179a803e9f0836bbccc2792eaa0b6e34 Mon Sep 17 00:00:00 2001 From: "Camgoz, Fatih" Date: Tue, 20 May 2025 11:23:50 -0400 Subject: [PATCH 005/301] Add CSP configuration section to root-http-router.md file Signed-off-by: Camgoz, Fatih --- .../core-services/root-http-router.md | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/docs/backend-system/core-services/root-http-router.md b/docs/backend-system/core-services/root-http-router.md index 7a268e27fc..a60d788ae8 100644 --- a/docs/backend-system/core-services/root-http-router.md +++ b/docs/backend-system/core-services/root-http-router.md @@ -161,3 +161,52 @@ backend.add( }), ); ``` + +## Defining Content Security Policy (CSP) Configuration + +Content Security Policy (CSP) is a crucial security feature that helps protect your Backstage instance from various attacks, particularly Cross-Site Scripting (XSS). Backstage provides a flexible way to configure CSP directives through your `app-config.yaml` file. + +### Basic Configuration + +CSP directives can be defined under the `backend.csp` section in your configuration: + +```yaml +backend: + csp: + default-src: ["'self'"] + script-src: ["'self'", "'unsafe-inline'", 'example.com'] + connect-src: ["'self'", 'http:', 'https:'] + img-src: ["'self'", 'data:', 'https://backstage.io'] + style-src: ["'self'", "'unsafe-inline'"] + frame-src: ['https://some-analytics-provider.com'] +``` + +### Available Directives + +You can configure any CSP directive supported by modern browsers. Common directives include: + +- `default-src`: The fallback for other CSP directives +- `script-src`: Controls which scripts can be executed +- `style-src`: Controls which styles can be applied +- `img-src`: Controls which images can be loaded +- `connect-src`: Controls which URLs can be loaded using fetch, WebSocket, etc. +- `frame-src`: Controls which URLs can be embedded in iframes +- `font-src`: Controls which fonts can be loaded +- `object-src`: Controls which URLs can be loaded as plugins +- `media-src`: Controls which media (audio, video) can be loaded +- `upgrade-insecure-requests`: Instructs browsers to upgrade HTTP to HTTPS + +### Special Values + +- Common special values within directive arrays: + - `self`: Allows content from the same origin + - `unsafe-inline`: Allows inline scripts/styles + - `unsafe-eval`: Allows dynamic code evaluation + - `none`: Blocks all content for that directive + - `data:`: Allows data: URIs (commonly used for images) + - `https:`: Allows any content over HTTPS + +For more information on available CSP options, refer to: + +- [MDN Content Security Policy documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) +- [Helmet Content Security Policy](https://helmetjs.github.io/#content-security-policy) From 2ee13d458bbbd435a97cf63b3a14f99b4f524043 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 21:49:33 +0000 Subject: [PATCH 006/301] chore(deps): update docker/build-push-action action to v6.18.0 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/deploy_docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index e1547801ef..21913eb428 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -62,7 +62,7 @@ jobs: uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - name: Build and push - uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: './example-app' file: ./example-app/packages/backend/Dockerfile From fe1a2f46a25206ea0edd02f0f24420131dbfd000 Mon Sep 17 00:00:00 2001 From: mario ma Date: Wed, 28 May 2025 16:53:32 +0800 Subject: [PATCH 007/301] feat: catalog graph support i18n Signed-off-by: mario ma --- .changeset/clear-bananas-end.md | 5 ++ plugins/catalog-graph/report-alpha.api.md | 29 +++++++++ plugins/catalog-graph/src/alpha.tsx | 2 + .../CatalogGraphCard.test.tsx | 13 +++- .../CatalogGraphCard/CatalogGraphCard.tsx | 7 ++- .../CatalogGraphPage/CatalogGraphPage.tsx | 20 +++---- .../CatalogGraphPage/CurveFilter.test.tsx | 13 ++-- .../CatalogGraphPage/CurveFilter.tsx | 14 +++-- .../CatalogGraphPage/DirectionFilter.test.tsx | 9 +-- .../CatalogGraphPage/DirectionFilter.tsx | 18 +++--- .../CatalogGraphPage/MaxDepthFilter.test.tsx | 17 +++--- .../CatalogGraphPage/MaxDepthFilter.tsx | 13 +++- .../SelectedKindsFilter.test.tsx | 11 +++- .../CatalogGraphPage/SelectedKindsFilter.tsx | 9 ++- .../SelectedRelationsFilter.test.tsx | 13 ++-- .../SelectedRelationsFilter.tsx | 9 ++- plugins/catalog-graph/src/translation.ts | 60 +++++++++++++++++++ 17 files changed, 204 insertions(+), 58 deletions(-) create mode 100644 .changeset/clear-bananas-end.md create mode 100644 plugins/catalog-graph/src/translation.ts diff --git a/.changeset/clear-bananas-end.md b/.changeset/clear-bananas-end.md new file mode 100644 index 0000000000..50e722544c --- /dev/null +++ b/.changeset/clear-bananas-end.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-graph': patch +--- + +Catalog graph plugin support i18n diff --git a/plugins/catalog-graph/report-alpha.api.md b/plugins/catalog-graph/report-alpha.api.md index cf5835e35c..230edde3da 100644 --- a/plugins/catalog-graph/report-alpha.api.md +++ b/plugins/catalog-graph/report-alpha.api.md @@ -16,6 +16,35 @@ import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; + +// @alpha (undocumented) +export const catalogGraphTranslationRef: TranslationRef< + 'catalog-graph', + { + readonly 'catalogGraphCard.title': 'Relations'; + readonly 'catalogGraphCard.deepLinkTitle': 'View graph'; + readonly 'catalogGraphPage.title': 'Catalog Graph'; + readonly 'catalogGraphPage.filterToggleButtonTitle': 'Filters'; + readonly 'catalogGraphPage.supportButtonDescription': 'Start tracking your component in by adding it to the software catalog.'; + readonly 'catalogGraphPage.simplifiedSwitchLabel': 'Simplified'; + readonly 'catalogGraphPage.mergeRelationsSwitchLabel': 'Merge relations'; + readonly 'catalogGraphPage.zoomOutDescription': 'Use pinch & zoom to move around the diagram. Click to change active node, shift click to navigate to entity.'; + readonly 'catalogGraphPage.curveFilter.title': 'Curve'; + readonly 'catalogGraphPage.curveFilter.curveStepBefore': 'Step Before'; + readonly 'catalogGraphPage.curveFilter.curveMonotoneX': 'Monotone X'; + readonly 'catalogGraphPage.directionFilter.title': 'Direction'; + readonly 'catalogGraphPage.directionFilter.leftToRight': 'Left to right'; + readonly 'catalogGraphPage.directionFilter.rightToLeft': 'Right to left'; + readonly 'catalogGraphPage.directionFilter.topToBottom': 'Top to bottom'; + readonly 'catalogGraphPage.directionFilter.bottomToTop': 'Bottom to top'; + readonly 'catalogGraphPage.maxDepthFilter.title': 'Max depth'; + readonly 'catalogGraphPage.maxDepthFilter.inputPlaceholder': '∞ Infinite'; + readonly 'catalogGraphPage.maxDepthFilter.clearButtonAriaLabel': 'clear max depth'; + readonly 'catalogGraphPage.selectedKindsFilter.title': 'Kinds'; + readonly 'catalogGraphPage.selectedRelationsFilter.title': 'Relations'; + } +>; // @public (undocumented) const _default: FrontendPlugin< diff --git a/plugins/catalog-graph/src/alpha.tsx b/plugins/catalog-graph/src/alpha.tsx index aa1f6a05cf..6ee16be1c4 100644 --- a/plugins/catalog-graph/src/alpha.tsx +++ b/plugins/catalog-graph/src/alpha.tsx @@ -96,3 +96,5 @@ export default createFrontendPlugin({ }, extensions: [CatalogGraphPage, CatalogGraphEntityCard], }); + +export { catalogGraphTranslationRef } from './translation'; diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx index e09d57bd31..ed8069a8f4 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx @@ -34,6 +34,7 @@ import userEvent from '@testing-library/user-event'; import { catalogGraphRouteRef } from '../../routes'; import { CatalogGraphCard } from './CatalogGraphCard'; import Button from '@material-ui/core/Button'; +import { translationApiRef } from '@backstage/core-plugin-api/alpha'; describe('', () => { let entity: Entity; @@ -52,7 +53,10 @@ describe('', () => { namespace: 'd', }, }; - apis = TestApiRegistry.from([catalogApiRef, catalog]); + apis = TestApiRegistry.from( + [catalogApiRef, catalog], + [translationApiRef, mockApis.translation()], + ); wrapper = ( @@ -213,7 +217,12 @@ describe('', () => { const analyticsApi = mockApis.analytics(); await renderInTestApp( - + {wrapper} , { diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx index a75993a1bc..733f209990 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx @@ -38,6 +38,8 @@ import { EntityRelationsGraph, EntityRelationsGraphProps, } from '../EntityRelationsGraph'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { catalogGraphTranslationRef } from '../../translation'; /** @public */ export type CatalogGraphCardClassKey = 'card' | 'graph'; @@ -66,6 +68,7 @@ export const CatalogGraphCard = ( action?: ReactNode; }, ) => { + const { t } = useTranslationRef(catalogGraphTranslationRef); const { variant = 'gridItem', relationPairs = ALL_RELATION_PAIRS, @@ -81,7 +84,7 @@ export const CatalogGraphCard = ( action, rootEntityNames, onNodeClick, - title = 'Relations', + title = t('catalogGraphCard.title'), zoom = 'enable-on-click', } = props; @@ -133,7 +136,7 @@ export const CatalogGraphCard = ( variant={variant} noPadding deepLink={{ - title: 'View graph', + title: t('catalogGraphCard.deepLinkTitle'), link: catalogGraphUrl, }} > diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx index cf3e628a98..69cb04bf3a 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx @@ -50,6 +50,8 @@ import { SelectedKindsFilter } from './SelectedKindsFilter'; import { SelectedRelationsFilter } from './SelectedRelationsFilter'; import { SwitchFilter } from './SwitchFilter'; import { useCatalogGraphPage } from './useCatalogGraphPage'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { catalogGraphTranslationRef } from '../../translation'; /** @public */ export type CatalogGraphPageClassKey = @@ -136,7 +138,7 @@ export const CatalogGraphPage = ( initialState, entityFilter, } = props; - + const { t } = useTranslationRef(catalogGraphTranslationRef); const navigate = useNavigate(); const classes = useStyles(); const catalogEntityRoute = useRouteRef(entityRouteRef); @@ -192,7 +194,7 @@ export const CatalogGraphPage = ( return (
humanizeEntityRef(e)).join(', ')} /> @@ -203,13 +205,12 @@ export const CatalogGraphPage = ( selected={showFilters} onChange={() => toggleShowFilters()} > - Filters + {t('catalogGraphPage.filterToggleButtonTitle')} } > - Start tracking your component in by adding it to the software - catalog. + {t('catalogGraphPage.supportButtonDescription')} @@ -230,12 +231,12 @@ export const CatalogGraphPage = ( )} @@ -247,9 +248,8 @@ export const CatalogGraphPage = ( display="block" className={classes.legend} > - Use pinch & zoom to move - around the diagram. Click to change active node, shift click to - navigate to entity. + {' '} + {t('catalogGraphPage.zoomOutDescription')} ', () => { - test('should display current curve label', () => { + test('should display current curve label', async () => { const onChange = jest.fn(); - render(); + await renderInTestApp( + , + ); expect(screen.getByText('Monotone X')).toBeInTheDocument(); }); test('should select an alternative curve factory', async () => { const onChange = jest.fn(); - render(); + await renderInTestApp( + , + ); expect(screen.getByText('Step Before')).toBeInTheDocument(); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx index 411c144b05..0e6a941b1a 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx @@ -16,12 +16,10 @@ import { Select, SelectedItems } from '@backstage/core-components'; import Box from '@material-ui/core/Box'; import { useCallback } from 'react'; +import { catalogGraphTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; type Curve = 'curveStepBefore' | 'curveMonotoneX'; -const CURVE_DISPLAY_NAMES: Record = { - curveMonotoneX: 'Monotone X', - curveStepBefore: 'Step Before', -}; export type Props = { value: Curve; @@ -31,6 +29,12 @@ export type Props = { const curves: Array = ['curveMonotoneX', 'curveStepBefore']; export const CurveFilter = ({ value, onChange }: Props) => { + const { t } = useTranslationRef(catalogGraphTranslationRef); + const CURVE_DISPLAY_NAMES: Record = { + curveMonotoneX: t('catalogGraphPage.curveFilter.curveMonotoneX'), + curveStepBefore: t('catalogGraphPage.curveFilter.curveStepBefore'), + }; + const handleChange = useCallback( (v: SelectedItems) => onChange(v as Curve), [onChange], @@ -39,7 +43,7 @@ export const CurveFilter = ({ value, onChange }: Props) => { return ( ({ label: DIRECTION_DISPLAY_NAMES[v], diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.test.tsx index ef3df7eb53..2e4d5d44a3 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.test.tsx @@ -14,20 +14,21 @@ * limitations under the License. */ -import { render, screen } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import user from '@testing-library/user-event'; import { MaxDepthFilter } from './MaxDepthFilter'; +import { renderInTestApp } from '@backstage/test-utils'; describe('', () => { - test('should display current value', () => { - render( {}} />); + test('should display current value', async () => { + await renderInTestApp( {}} />); expect(screen.getByLabelText('maxp')).toBeInTheDocument(); expect(screen.getByLabelText('maxp')).toHaveValue(5); }); - test('should display infinite if non finite', () => { - render( + test('should display infinite if non finite', async () => { + await renderInTestApp( {}} />, ); @@ -37,7 +38,7 @@ describe('', () => { test('should clear max depth', async () => { const onChange = jest.fn(); - render(); + await renderInTestApp(); expect(onChange).not.toHaveBeenCalled(); await user.click(screen.getByLabelText('clear max depth')); @@ -46,7 +47,7 @@ describe('', () => { test('should set max depth to undefined if below one', async () => { const onChange = jest.fn(); - render(); + await renderInTestApp(); await user.clear(screen.getByLabelText('maxp')); await user.type(screen.getByLabelText('maxp'), '0'); @@ -56,7 +57,7 @@ describe('', () => { test('should select direction', async () => { let value = 5; - render( + await renderInTestApp( { diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.tsx index 33742ad72e..cdd27b9adb 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/MaxDepthFilter.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Box from '@material-ui/core/Box'; import FormControl from '@material-ui/core/FormControl'; import IconButton from '@material-ui/core/IconButton'; @@ -22,6 +23,7 @@ import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import ClearIcon from '@material-ui/icons/Clear'; import { ChangeEvent, useCallback, useEffect, useRef, useState } from 'react'; +import { catalogGraphTranslationRef } from '../../translation'; export type Props = { value: number; @@ -45,6 +47,7 @@ export const MaxDepthFilter = ({ value, onChange }: Props) => { const classes = useStyles(); const onChangeRef = useRef(onChange); const [currentValue, setCurrentValue] = useState(value); + const { t } = useTranslationRef(catalogGraphTranslationRef); // Keep a fresh reference to the latest callback useEffect(() => { @@ -75,16 +78,20 @@ export const MaxDepthFilter = ({ value, onChange }: Props) => { return ( - Max Depth + + {t('catalogGraphPage.maxDepthFilter.title')} + diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.test.tsx index 929b22755d..4349d7005a 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.test.tsx @@ -15,13 +15,18 @@ */ import { ApiProvider } from '@backstage/core-app-api'; -import { AlertApi, alertApiRef } from '@backstage/core-plugin-api'; +import { AlertApi, alertApiRef, errorApiRef } from '@backstage/core-plugin-api'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils'; +import { + mockApis, + renderWithEffects, + TestApiRegistry, +} from '@backstage/test-utils'; import { waitFor, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { SelectedKindsFilter } from './SelectedKindsFilter'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; +import { translationApiRef } from '@backstage/core-plugin-api/alpha'; const catalogApi = catalogApiMock.mock({ getEntityFacets: jest.fn().mockResolvedValue({ @@ -38,6 +43,8 @@ const catalogApi = catalogApiMock.mock({ const apis = TestApiRegistry.from( [catalogApiRef, catalogApi], [alertApiRef, {} as AlertApi], + [translationApiRef, mockApis.translation()], + [errorApiRef, { post: jest.fn() }], ); describe('', () => { diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.tsx index 4970c4527a..d9a5ef9833 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.tsx @@ -27,6 +27,8 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import Autocomplete from '@material-ui/lab/Autocomplete'; import { useCallback, useEffect, useMemo } from 'react'; import useAsync from 'react-use/esm/useAsync'; +import { catalogGraphTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; /** @public */ export type SelectedKindsFilterClassKey = 'formControl'; @@ -49,6 +51,7 @@ export const SelectedKindsFilter = ({ value, onChange }: Props) => { const classes = useStyles(); const alertApi = useApi(alertApiRef); const catalogApi = useApi(catalogApiRef); + const { t } = useTranslationRef(catalogGraphTranslationRef); const { error, value: kinds } = useAsync(async () => { return await catalogApi @@ -91,13 +94,15 @@ export const SelectedKindsFilter = ({ value, onChange }: Props) => { return ( - Kinds + + {t('catalogGraphPage.selectedKindsFilter.title')} + kinds[normalizedKinds.indexOf(k)] ?? k} diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedRelationsFilter.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedRelationsFilter.test.tsx index 41df29d072..057842bcdc 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedRelationsFilter.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedRelationsFilter.test.tsx @@ -19,14 +19,15 @@ import { RELATION_HAS_MEMBER, RELATION_OWNED_BY, } from '@backstage/catalog-model'; -import { render, waitFor, screen } from '@testing-library/react'; +import { waitFor, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ALL_RELATION_PAIRS } from '../EntityRelationsGraph'; import { SelectedRelationsFilter } from './SelectedRelationsFilter'; +import { renderInTestApp } from '@backstage/test-utils'; describe('', () => { - test('should render current value', () => { - render( + test('should render current value', async () => { + await renderInTestApp( ', () => { test('should select value', async () => { const onChange = jest.fn(); - render( + await renderInTestApp( ', () => { test('should return undefined if all values are selected', async () => { const onChange = jest.fn(); - render( + await renderInTestApp( p).filter( @@ -92,7 +93,7 @@ describe('', () => { test('should return all values when cleared', async () => { const onChange = jest.fn(); - render( + await renderInTestApp( { const classes = useStyles(); const relations = useMemo(() => relationPairs.flat(), [relationPairs]); + const { t } = useTranslationRef(catalogGraphTranslationRef); const handleChange = useCallback( (_: unknown, v: string[]) => { @@ -65,13 +68,15 @@ export const SelectedRelationsFilter = ({ return ( - Relations + + {t('catalogGraphPage.selectedRelationsFilter.title')} + Date: Thu, 29 May 2025 15:21:37 -0500 Subject: [PATCH 008/301] first cut at addressing bug with adding backstage dependencies to a package maintained by the backstage plugin Signed-off-by: Cory Steers --- .changeset/tiny-crews-shop.md | 5 ++ packages/yarn-plugin/package.json | 3 +- .../afterWorkspaceDependencyAddition.test.ts | 67 ++++++++++++++++ .../afterWorkspaceDependencyAddition.ts | 33 ++++++++ ...fterWorkspaceDependencyReplacement.test.ts | 77 +++++++++++++++++++ .../afterWorkspaceDependencyReplacement.ts | 35 +++++++++ packages/yarn-plugin/src/handlers/index.ts | 2 + packages/yarn-plugin/src/index.ts | 12 ++- yarn.lock | 5 +- 9 files changed, 234 insertions(+), 5 deletions(-) create mode 100644 .changeset/tiny-crews-shop.md create mode 100644 packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.test.ts create mode 100644 packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.ts create mode 100644 packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.test.ts create mode 100644 packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.ts diff --git a/.changeset/tiny-crews-shop.md b/.changeset/tiny-crews-shop.md new file mode 100644 index 0000000000..18f205c5fc --- /dev/null +++ b/.changeset/tiny-crews-shop.md @@ -0,0 +1,5 @@ +--- +'yarn-plugin-backstage': patch +--- + +added functionality so that adding or updating a backstage dependency to a package would maintain the "backstage:^" placeholder for the version. diff --git a/packages/yarn-plugin/package.json b/packages/yarn-plugin/package.json index 7de8d140bb..c764da4fa6 100644 --- a/packages/yarn-plugin/package.json +++ b/packages/yarn-plugin/package.json @@ -32,8 +32,9 @@ "dependencies": { "@backstage/cli-common": "workspace:^", "@backstage/release-manifests": "workspace:^", - "@yarnpkg/core": "^4.4.0", + "@yarnpkg/core": "^4.4.1", "@yarnpkg/fslib": "^3.1.2", + "@yarnpkg/plugin-essentials": "^4.4.0", "@yarnpkg/plugin-npm": "patch:@yarnpkg/plugin-npm@npm%3A3.1.0#~/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch", "@yarnpkg/plugin-pack": "^4.0.1", "semver": "^7.6.0" diff --git a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.test.ts b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.test.ts new file mode 100644 index 0000000000..fa89080889 --- /dev/null +++ b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.test.ts @@ -0,0 +1,67 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Descriptor, + DescriptorHash, + IdentHash, + Workspace, +} from '@yarnpkg/core'; +import { suggestUtils } from '@yarnpkg/plugin-essentials'; +import { afterWorkspaceDependencyAddition } from './afterWorkspaceDependencyAddition'; + +describe('afterWorkspaceDependencyAddition', () => { + const workspace = {} as Workspace; + const target = {} as suggestUtils.Target; + const strategies: Array = []; + + it('should replace the range for a backstage scoped dependency', async () => { + const input: Descriptor = { + scope: 'backstage', + name: 'test-package', + range: '^1.0.0', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + + await afterWorkspaceDependencyAddition( + workspace, + target, + input, + strategies, + ); + + expect(input.range).toBe('backstage:^'); + }); + + it('should not replace the range for a non-backstage scoped dependency', async () => { + const input: Descriptor = { + scope: 'backstage-community', + name: 'test-package', + range: '^1.0.0', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + + await afterWorkspaceDependencyAddition( + workspace, + target, + input, + strategies, + ); + + expect(input.range).toBe('^1.0.0'); + }); +}); diff --git a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.ts b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.ts new file mode 100644 index 0000000000..8e39b1c759 --- /dev/null +++ b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Descriptor, Workspace } from '@yarnpkg/core'; +import { suggestUtils } from '@yarnpkg/plugin-essentials'; + +export const afterWorkspaceDependencyAddition = async ( + _workspace: Workspace, + _target: suggestUtils.Target, + descriptor: Descriptor, + _strategies: Array, +) => { + if (descriptor.scope === 'backstage' && descriptor.range !== 'backstage:^') { + // is there a better way to log than console.log? + console.log( + `afterWorkspaceDependencyAddition hook: Setting descriptor range from ${descriptor.range} to 'backstage:^' for ${descriptor.scope}/${descriptor.name}`, + ); + descriptor.range = 'backstage:^'; + } +}; diff --git a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.test.ts b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.test.ts new file mode 100644 index 0000000000..eef3f25f97 --- /dev/null +++ b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.test.ts @@ -0,0 +1,77 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Descriptor, + DescriptorHash, + IdentHash, + Workspace, +} from '@yarnpkg/core'; +import { suggestUtils } from '@yarnpkg/plugin-essentials'; +import { afterWorkspaceDependencyReplacement } from './afterWorkspaceDependencyReplacement'; + +describe('afterWorkspaceDependencyReplacement.test', () => { + const workspace = {} as Workspace; + const target = {} as suggestUtils.Target; + + it('should warn that the range is being changed for a backstage scoped dependency', async () => { + const fromDescriptor: Descriptor = { + scope: 'backstage', + name: 'test-package', + range: 'backstage:^', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + const toDescriptor: Descriptor = { + scope: 'backstage', + name: 'test-package', + range: '^1.0.0', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + await afterWorkspaceDependencyReplacement( + workspace, + target, + fromDescriptor, + toDescriptor, + ); + + expect(toDescriptor.range).toBe('^1.0.0'); + }); + it('should ignore that the range is being changed for a non-backstage scoped dependency', async () => { + const fromDescriptor: Descriptor = { + scope: 'backstage-community', + name: 'test-package', + range: 'backstage:^', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + const toDescriptor: Descriptor = { + scope: 'backstage-community', + name: 'test-package', + range: '^1.0.0', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + await afterWorkspaceDependencyReplacement( + workspace, + target, + fromDescriptor, + toDescriptor, + ); + + expect(toDescriptor.range).toBe('^1.0.0'); + }); +}); diff --git a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.ts b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.ts new file mode 100644 index 0000000000..b8141c8bb3 --- /dev/null +++ b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Descriptor, Workspace } from '@yarnpkg/core'; +import { suggestUtils } from '@yarnpkg/plugin-essentials'; + +export const afterWorkspaceDependencyReplacement = async ( + _workspace: Workspace, + _target: suggestUtils.Target, + fromDescriptor: Descriptor, + toDescriptor: Descriptor, +) => { + if ( + toDescriptor.scope === 'backstage' && + toDescriptor.range !== 'backstage:^' + ) { + // is there a better way to log than console.log? + console.log( + `afterWorkspaceDependencyReplacement hook: Setting descriptor range from '${fromDescriptor.range}' to '${toDescriptor.range}' for ${fromDescriptor.scope}/${fromDescriptor.name}. Are you sure you want to be doing that?`, + ); + } +}; diff --git a/packages/yarn-plugin/src/handlers/index.ts b/packages/yarn-plugin/src/handlers/index.ts index baa144b36f..fe8f05c935 100644 --- a/packages/yarn-plugin/src/handlers/index.ts +++ b/packages/yarn-plugin/src/handlers/index.ts @@ -16,3 +16,5 @@ export { beforeWorkspacePacking } from './beforeWorkspacePacking'; export { reduceDependency } from './reduceDependency'; +export { afterWorkspaceDependencyAddition } from './afterWorkspaceDependencyAddition'; +export { afterWorkspaceDependencyReplacement } from './afterWorkspaceDependencyReplacement'; diff --git a/packages/yarn-plugin/src/index.ts b/packages/yarn-plugin/src/index.ts index ad18deb0ff..b08d98eae7 100644 --- a/packages/yarn-plugin/src/index.ts +++ b/packages/yarn-plugin/src/index.ts @@ -23,7 +23,13 @@ import { Plugin, Hooks, semverUtils, YarnVersion } from '@yarnpkg/core'; import { Hooks as PackHooks } from '@yarnpkg/plugin-pack'; -import { beforeWorkspacePacking, reduceDependency } from './handlers'; +import { Hooks as EssentialHooks } from '@yarnpkg/plugin-essentials'; +import { + afterWorkspaceDependencyAddition, + afterWorkspaceDependencyReplacement, + beforeWorkspacePacking, + reduceDependency, +} from './handlers'; import { BackstageNpmResolver } from './resolvers'; // All dependencies of the yarn plugin are bundled during the build. Chalk @@ -44,8 +50,10 @@ if (!semverUtils.satisfiesWithPrereleases(YarnVersion, '^4.1.1')) { /** * @public */ -const plugin: Plugin = { +const plugin: Plugin = { hooks: { + afterWorkspaceDependencyAddition, + afterWorkspaceDependencyReplacement, reduceDependency, beforeWorkspacePacking, }, diff --git a/yarn.lock b/yarn.lock index 14e234f527..539b89f70c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22705,7 +22705,7 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/core@npm:^4.2.1, @yarnpkg/core@npm:^4.4.0": +"@yarnpkg/core@npm:^4.2.1, @yarnpkg/core@npm:^4.4.0, @yarnpkg/core@npm:^4.4.1": version: 4.4.1 resolution: "@yarnpkg/core@npm:4.4.1" dependencies: @@ -49221,8 +49221,9 @@ __metadata: "@backstage/cli-common": "workspace:^" "@backstage/release-manifests": "workspace:^" "@yarnpkg/builder": "npm:^4.2.1" - "@yarnpkg/core": "npm:^4.4.0" + "@yarnpkg/core": "npm:^4.4.1" "@yarnpkg/fslib": "npm:^3.1.2" + "@yarnpkg/plugin-essentials": "npm:^4.4.0" "@yarnpkg/plugin-npm": "patch:@yarnpkg/plugin-npm@npm%3A3.1.0#~/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch" "@yarnpkg/plugin-pack": "npm:^4.0.1" fs-extra: "npm:^11.2.0" From 797688a944d2854fcea1be90903f7048b605a0ef Mon Sep 17 00:00:00 2001 From: Cory Steers Date: Fri, 30 May 2025 10:23:28 -0500 Subject: [PATCH 009/301] enhance logic to make sure backstage can manage the version of the dependency before changing the range Signed-off-by: Cory Steers --- .../afterWorkspaceDependencyAddition.test.ts | 54 +++++++++++++++- .../afterWorkspaceDependencyAddition.ts | 30 ++++++--- ...fterWorkspaceDependencyReplacement.test.ts | 63 ++++++++++++++++++- .../afterWorkspaceDependencyReplacement.ts | 25 +++++--- 4 files changed, 155 insertions(+), 17 deletions(-) diff --git a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.test.ts b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.test.ts index fa89080889..ae575a8b02 100644 --- a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.test.ts +++ b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.test.ts @@ -20,12 +20,28 @@ import { Workspace, } from '@yarnpkg/core'; import { suggestUtils } from '@yarnpkg/plugin-essentials'; +import { getPackageVersion } from '../util'; import { afterWorkspaceDependencyAddition } from './afterWorkspaceDependencyAddition'; +jest.mock('../util', () => ({ + getPackageVersion: jest.fn(), +})); + describe('afterWorkspaceDependencyAddition', () => { - const workspace = {} as Workspace; + const workspace = { + project: { + configuration: {}, + }, + } as Workspace; const target = {} as suggestUtils.Target; const strategies: Array = []; + const mockGetPackageVersion = getPackageVersion as jest.MockedFunction< + typeof getPackageVersion + >; + + beforeEach(() => { + mockGetPackageVersion.mockReset(); + }); it('should replace the range for a backstage scoped dependency', async () => { const input: Descriptor = { @@ -36,6 +52,8 @@ describe('afterWorkspaceDependencyAddition', () => { identHash: {} as IdentHash, }; + mockGetPackageVersion.mockImplementation(() => Promise.resolve('success')); + await afterWorkspaceDependencyAddition( workspace, target, @@ -44,6 +62,39 @@ describe('afterWorkspaceDependencyAddition', () => { ); expect(input.range).toBe('backstage:^'); + expect(mockGetPackageVersion).toHaveBeenCalledTimes(1); + expect(mockGetPackageVersion).toHaveBeenCalledWith( + input, + workspace.project.configuration, + ); + }); + + it('should not replace the range for a backstage scoped dependency where it cant find a version from remote', async () => { + const input: Descriptor = { + scope: 'backstage', + name: 'test-package', + range: '^1.0.0', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + + mockGetPackageVersion.mockImplementation(() => + Promise.reject(new Error('test error')), + ); + + await afterWorkspaceDependencyAddition( + workspace, + target, + input, + strategies, + ); + + expect(input.range).toBe('^1.0.0'); + expect(mockGetPackageVersion).toHaveBeenCalledTimes(1); + expect(mockGetPackageVersion).toHaveBeenCalledWith( + input, + workspace.project.configuration, + ); }); it('should not replace the range for a non-backstage scoped dependency', async () => { @@ -63,5 +114,6 @@ describe('afterWorkspaceDependencyAddition', () => { ); expect(input.range).toBe('^1.0.0'); + expect(mockGetPackageVersion).not.toHaveBeenCalled(); }); }); diff --git a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.ts b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.ts index 8e39b1c759..9307790930 100644 --- a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.ts +++ b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.ts @@ -14,20 +14,34 @@ * limitations under the License. */ -import { Descriptor, Workspace } from '@yarnpkg/core'; +import { Descriptor, structUtils, Workspace } from '@yarnpkg/core'; import { suggestUtils } from '@yarnpkg/plugin-essentials'; +import { getPackageVersion } from '../util'; +import { PROTOCOL } from '../constants'; export const afterWorkspaceDependencyAddition = async ( - _workspace: Workspace, + workspace: Workspace, _target: suggestUtils.Target, descriptor: Descriptor, _strategies: Array, ) => { - if (descriptor.scope === 'backstage' && descriptor.range !== 'backstage:^') { - // is there a better way to log than console.log? - console.log( - `afterWorkspaceDependencyAddition hook: Setting descriptor range from ${descriptor.range} to 'backstage:^' for ${descriptor.scope}/${descriptor.name}`, - ); - descriptor.range = 'backstage:^'; + const descriptorRange = structUtils.parseRange(descriptor.range); + + if ( + descriptor.scope === 'backstage' && + descriptorRange.protocol !== PROTOCOL + ) { + try { + await getPackageVersion(descriptor, workspace.project.configuration); + // is there a better way to log than console.log? + console.log( + `afterWorkspaceDependencyAddition hook: Setting descriptor range from ${descriptor.range} to 'backstage:^' for ${descriptor.scope}/${descriptor.name}`, + ); + descriptor.range = `${PROTOCOL}^`; + } catch (_error: any) { + // if there's no found version then this is likely a deprecated package + // or otherwise the plugin won't be able to resolve the real version + // and we should leave the desired range as is + } } }; diff --git a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.test.ts b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.test.ts index eef3f25f97..7bf9c77554 100644 --- a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.test.ts +++ b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.test.ts @@ -20,11 +20,27 @@ import { Workspace, } from '@yarnpkg/core'; import { suggestUtils } from '@yarnpkg/plugin-essentials'; +import { getPackageVersion } from '../util'; import { afterWorkspaceDependencyReplacement } from './afterWorkspaceDependencyReplacement'; +jest.mock('../util', () => ({ + getPackageVersion: jest.fn(), +})); + describe('afterWorkspaceDependencyReplacement.test', () => { - const workspace = {} as Workspace; + const workspace = { + project: { + configuration: {}, + }, + } as Workspace; const target = {} as suggestUtils.Target; + const mockGetPackageVersion = getPackageVersion as jest.MockedFunction< + typeof getPackageVersion + >; + + beforeEach(() => { + mockGetPackageVersion.mockReset(); + }); it('should warn that the range is being changed for a backstage scoped dependency', async () => { const fromDescriptor: Descriptor = { @@ -41,6 +57,9 @@ describe('afterWorkspaceDependencyReplacement.test', () => { descriptorHash: {} as DescriptorHash, identHash: {} as IdentHash, }; + + mockGetPackageVersion.mockImplementation(() => Promise.resolve('success')); + await afterWorkspaceDependencyReplacement( workspace, target, @@ -49,7 +68,48 @@ describe('afterWorkspaceDependencyReplacement.test', () => { ); expect(toDescriptor.range).toBe('^1.0.0'); + expect(mockGetPackageVersion).toHaveBeenCalledTimes(1); + expect(mockGetPackageVersion).toHaveBeenCalledWith( + toDescriptor, + workspace.project.configuration, + ); }); + + it('should not warn that the range is being changed for a backstage scoped dependency where it cant find a version from remote', async () => { + const fromDescriptor: Descriptor = { + scope: 'backstage', + name: 'test-package', + range: 'backstage:^', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + const toDescriptor: Descriptor = { + scope: 'backstage', + name: 'test-package', + range: '^1.0.0', + descriptorHash: {} as DescriptorHash, + identHash: {} as IdentHash, + }; + + mockGetPackageVersion.mockImplementation(() => + Promise.reject(new Error('test error')), + ); + + await afterWorkspaceDependencyReplacement( + workspace, + target, + fromDescriptor, + toDescriptor, + ); + + expect(toDescriptor.range).toBe('^1.0.0'); + expect(mockGetPackageVersion).toHaveBeenCalledTimes(1); + expect(mockGetPackageVersion).toHaveBeenCalledWith( + toDescriptor, + workspace.project.configuration, + ); + }); + it('should ignore that the range is being changed for a non-backstage scoped dependency', async () => { const fromDescriptor: Descriptor = { scope: 'backstage-community', @@ -73,5 +133,6 @@ describe('afterWorkspaceDependencyReplacement.test', () => { ); expect(toDescriptor.range).toBe('^1.0.0'); + expect(mockGetPackageVersion).not.toHaveBeenCalled(); }); }); diff --git a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.ts b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.ts index b8141c8bb3..a955191244 100644 --- a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.ts +++ b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.ts @@ -14,22 +14,33 @@ * limitations under the License. */ -import { Descriptor, Workspace } from '@yarnpkg/core'; +import { Descriptor, structUtils, Workspace } from '@yarnpkg/core'; import { suggestUtils } from '@yarnpkg/plugin-essentials'; +import { getPackageVersion } from '../util'; +import { PROTOCOL } from '../constants'; export const afterWorkspaceDependencyReplacement = async ( - _workspace: Workspace, + workspace: Workspace, _target: suggestUtils.Target, fromDescriptor: Descriptor, toDescriptor: Descriptor, ) => { + const toDescriptorRange = structUtils.parseRange(toDescriptor.range); + if ( toDescriptor.scope === 'backstage' && - toDescriptor.range !== 'backstage:^' + toDescriptorRange.protocol !== PROTOCOL ) { - // is there a better way to log than console.log? - console.log( - `afterWorkspaceDependencyReplacement hook: Setting descriptor range from '${fromDescriptor.range}' to '${toDescriptor.range}' for ${fromDescriptor.scope}/${fromDescriptor.name}. Are you sure you want to be doing that?`, - ); + try { + await getPackageVersion(toDescriptor, workspace.project.configuration); + // is there a better way to log than console.log? + console.log( + `afterWorkspaceDependencyReplacement hook: Setting descriptor range from '${fromDescriptor.range}' to '${toDescriptor.range}' for ${fromDescriptor.scope}/${fromDescriptor.name}. Are you sure you want to be doing that?`, + ); + } catch (_error: any) { + // if there's no found version then this is likely a deprecated package + // or otherwise the plugin won't be able to resolve the real version + // and we should not warn them. + } } }; From 95955c21f28ac07c05bae543f9a810fc0a6bb660 Mon Sep 17 00:00:00 2001 From: Cory Steers Date: Fri, 30 May 2025 10:47:15 -0500 Subject: [PATCH 010/301] update with doc on new hooks added Signed-off-by: Cory Steers --- packages/yarn-plugin/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/yarn-plugin/README.md b/packages/yarn-plugin/README.md index 073669b9a5..49b57fe0ef 100644 --- a/packages/yarn-plugin/README.md +++ b/packages/yarn-plugin/README.md @@ -82,3 +82,12 @@ specific Backstage repository. As such, when publishing packages, all `backstage:^` versions should be removed from the package.json and replaced with the appropriate npm version ranges. This is handled by the `beforeWorkspacePacking` hook. + +### `afterWorkspaceDependencyAddition` hook + +\_Replaces npm version ranges with `backstage:^` ranges for `@backstage/*` dependencies added after +the plugin has converted existing dependencies to `backstage:^` range + +### `afterWorkspaceDependencyReplacement` hook + +\_warns user with console message when running `yarn add` for a `@backstage/*` scoped dependency that is already a dependency in the target package. Doing so will remove the `backstage:^` scope and replace it with the actual npm version range, which may not be desired. From 1115791e35d7fe824e5701c69aeadfabd541837b Mon Sep 17 00:00:00 2001 From: Cory Steers Date: Fri, 30 May 2025 12:37:22 -0500 Subject: [PATCH 011/301] formatting fixes Signed-off-by: Cory Steers --- packages/yarn-plugin/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/yarn-plugin/README.md b/packages/yarn-plugin/README.md index 49b57fe0ef..5c112506e3 100644 --- a/packages/yarn-plugin/README.md +++ b/packages/yarn-plugin/README.md @@ -85,9 +85,9 @@ the appropriate npm version ranges. This is handled by the ### `afterWorkspaceDependencyAddition` hook -\_Replaces npm version ranges with `backstage:^` ranges for `@backstage/*` dependencies added after -the plugin has converted existing dependencies to `backstage:^` range +_Replaces npm version ranges with `backstage:^` ranges for `@backstage/*` dependencies added after +the plugin has converted existing dependencies to `backstage:^` range_ ### `afterWorkspaceDependencyReplacement` hook -\_warns user with console message when running `yarn add` for a `@backstage/*` scoped dependency that is already a dependency in the target package. Doing so will remove the `backstage:^` scope and replace it with the actual npm version range, which may not be desired. +_warns user with console message when running `yarn add` for a `@backstage/*` scoped dependency that is already a dependency in the target package. Doing so will remove the `backstage:^` scope and replace it with the actual npm version range, which may not be desired._ From 52f5f431d018a7022741ce79cfcca0fa9c7bc0ee Mon Sep 17 00:00:00 2001 From: Cory Steers Date: Fri, 30 May 2025 12:41:39 -0500 Subject: [PATCH 012/301] update to include 3rd hooks import Signed-off-by: Cory Steers --- packages/yarn-plugin/report.api.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/yarn-plugin/report.api.md b/packages/yarn-plugin/report.api.md index 620bbbb567..b9425282b4 100644 --- a/packages/yarn-plugin/report.api.md +++ b/packages/yarn-plugin/report.api.md @@ -4,10 +4,11 @@ ```ts import { Hooks } from '@yarnpkg/core'; -import { Hooks as Hooks_2 } from '@yarnpkg/plugin-pack'; +import { Hooks as Hooks_2 } from '@yarnpkg/plugin-essentials'; +import { Hooks as Hooks_3 } from '@yarnpkg/plugin-pack'; import { Plugin as Plugin_2 } from '@yarnpkg/core'; // @public (undocumented) -const plugin: Plugin_2; +const plugin: Plugin_2; export default plugin; ``` From 31da74d18345d11278033f0ba06691eb91b227ce Mon Sep 17 00:00:00 2001 From: Lucas Duarte Moura Silva Date: Tue, 3 Jun 2025 08:37:44 -0300 Subject: [PATCH 013/301] chore(pageTheme): Update uncomfortably bright color in recently visited and top visited home page cards Signed-off-by: Lucas Duarte Moura Silva --- packages/theme/src/base/pageTheme.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/theme/src/base/pageTheme.ts b/packages/theme/src/base/pageTheme.ts index 12a73b6089..3e1e8641fc 100644 --- a/packages/theme/src/base/pageTheme.ts +++ b/packages/theme/src/base/pageTheme.ts @@ -50,7 +50,7 @@ export const colorVariants: Record = { rubyRed: ['#98002B', '#8D1134'], toastyOrange: ['#BE2200', '#A41D00'], purpleSky: ['#8912CA', '#3E00EA'], - eveningSea: ['#00FFF2', '#035355'], + eveningSea: ['#00CFC5', '#035355'], teal: ['#005B4B'], pinkSea: ['#C8077A', '#C2297D'], greens: ['#4BB8A5', '#187656'], From b68d2698e87ff10d0ff1f0f2a017e5245a33914d Mon Sep 17 00:00:00 2001 From: Lucas Duarte Moura Silva Date: Tue, 3 Jun 2025 09:35:02 -0300 Subject: [PATCH 014/301] chore(pageTheme): Update uncomfortably bright color in recently visited and top visited home page cards Signed-off-by: Lucas Duarte Moura Silva --- .changeset/yummy-pugs-battle.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/yummy-pugs-battle.md diff --git a/.changeset/yummy-pugs-battle.md b/.changeset/yummy-pugs-battle.md new file mode 100644 index 0000000000..a17b934deb --- /dev/null +++ b/.changeset/yummy-pugs-battle.md @@ -0,0 +1,5 @@ +--- +'@backstage/theme': patch +--- + +Update uncomfortably bright color in recently visited and top visited home page cards From 9afe2702ab5c5ffd48f59a4ffffc096a4617333b Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Tue, 3 Jun 2025 16:06:54 +0200 Subject: [PATCH 015/301] fix: pass OAuth token explicitly to avoid config token overwrite Signed-off-by: ElaineDeMattosSilvaB --- .../src/entrypoints/urlReader/lib/GitlabUrlReader.ts | 5 +---- packages/integration/src/gitlab/core.ts | 6 ++++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts index 3e5497a307..a0521d6e23 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts @@ -340,10 +340,7 @@ export class GitlabUrlReader implements UrlReaderService { ); } // Default to the old behavior of assuming the url is for a file - return getGitLabFileFetchUrl(target, { - ...this.integration.config, - ...(token && { token }), - }); + return getGitLabFileFetchUrl(target, this.integration.config, token); } // convert urls of the form: diff --git a/packages/integration/src/gitlab/core.ts b/packages/integration/src/gitlab/core.ts index 4a17b2a15e..b25c50a583 100644 --- a/packages/integration/src/gitlab/core.ts +++ b/packages/integration/src/gitlab/core.ts @@ -40,8 +40,9 @@ import { export async function getGitLabFileFetchUrl( url: string, config: GitLabIntegrationConfig, + token?: string, ): Promise { - const projectID = await getProjectId(url, config); + const projectID = await getProjectId(url, config, token); return buildProjectUrl(url, projectID, config).toString(); } @@ -113,6 +114,7 @@ export function buildProjectUrl( export async function getProjectId( target: string, config: GitLabIntegrationConfig, + token?: string, ): Promise { const url = new URL(target); @@ -143,7 +145,7 @@ export async function getProjectId( const response = await fetch( repoIDLookup.toString(), - getGitLabRequestOptions(config), + getGitLabRequestOptions(config, token), ); const data = await response.json(); From d1e4a6d04f1416af4cf2a718c10743e04893aba9 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Wed, 4 Jun 2025 08:16:24 +0200 Subject: [PATCH 016/301] chore: add changeset Signed-off-by: ElaineDeMattosSilvaB --- .changeset/wise-frogs-talk.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/wise-frogs-talk.md diff --git a/.changeset/wise-frogs-talk.md b/.changeset/wise-frogs-talk.md new file mode 100644 index 0000000000..6a25abc246 --- /dev/null +++ b/.changeset/wise-frogs-talk.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-defaults': patch +'@backstage/integration': patch +--- + +Fixed bug where the GitLab user token and GitLab integration token were being merged together From 934df9a4750eae3f6e34a2e690ff12864691d73a Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Wed, 4 Jun 2025 08:20:54 +0200 Subject: [PATCH 017/301] chore: add api report Signed-off-by: ElaineDeMattosSilvaB --- packages/integration/report.api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/integration/report.api.md b/packages/integration/report.api.md index d700c13429..f07249bc13 100644 --- a/packages/integration/report.api.md +++ b/packages/integration/report.api.md @@ -588,6 +588,7 @@ export function getGitilesAuthenticationUrl( export function getGitLabFileFetchUrl( url: string, config: GitLabIntegrationConfig, + token?: string, ): Promise; // @public From 7f8e4e6d652c8b7abc75e2fcf1fc295a790fca5d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Jun 2025 07:32:15 +0000 Subject: [PATCH 018/301] fix(deps): update dependency @keyv/redis to v4.4.0 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 78 +++++++------------------------------------------------ 1 file changed, 10 insertions(+), 68 deletions(-) diff --git a/yarn.lock b/yarn.lock index ab61b20c89..34481a0baf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11445,13 +11445,14 @@ __metadata: linkType: hard "@keyv/redis@npm:^4.0.1": - version: 4.1.0 - resolution: "@keyv/redis@npm:4.1.0" + version: 4.4.0 + resolution: "@keyv/redis@npm:4.4.0" dependencies: + "@redis/client": "npm:^1.6.0" cluster-key-slot: "npm:^1.1.2" - keyv: "npm:^5.2.1" - redis: "npm:^4.7.0" - checksum: 10/5f1d6c6596829bbae5c6369dff6e70de4d7f91c7c8312585b2bef336638582c9969c792a4b328ee07b80b50136e67769fffd844e237d4db32a44ea7de20fb3f5 + peerDependencies: + keyv: ^5.3.3 + checksum: 10/61ad0a026f1ee8bbd0f09bb1b3c731619b37dc4bba0d5f2111cb5249e643e6a776816786090c53d5ac1fa62a43e1b43d406dae06cfb119abcb47b6ac837eebb0 languageName: node linkType: hard @@ -15958,59 +15959,14 @@ __metadata: languageName: node linkType: hard -"@redis/bloom@npm:1.2.0": - version: 1.2.0 - resolution: "@redis/bloom@npm:1.2.0" - peerDependencies: - "@redis/client": ^1.0.0 - checksum: 10/a16408f729ddd032a52c9d998661dfa7beabc0e92760d30619c3166c7a53a98c037956d93d230b787005fd8a599a7456461ca7429c1916893c2d13d59a41e0e6 - languageName: node - linkType: hard - -"@redis/client@npm:1.6.0": - version: 1.6.0 - resolution: "@redis/client@npm:1.6.0" +"@redis/client@npm:^1.6.0": + version: 1.6.1 + resolution: "@redis/client@npm:1.6.1" dependencies: cluster-key-slot: "npm:1.1.2" generic-pool: "npm:3.9.0" yallist: "npm:4.0.0" - checksum: 10/ad375bd685dc34163304872e611ad31dc09a20f4dc8416e5dd588e8a9d2a42a6882f5c8f83d6388ba04ada71513c461b211c3146bcd8ab4da20b3f7edf08fb63 - languageName: node - linkType: hard - -"@redis/graph@npm:1.1.1": - version: 1.1.1 - resolution: "@redis/graph@npm:1.1.1" - peerDependencies: - "@redis/client": ^1.0.0 - checksum: 10/96b8ee9bec124947465848b56a014805f9639e09704e03c75a92072a319599ac9dcd4f9ace22970a7f72131a241166ad31db4dc6931b34808d22a5ca94649ba5 - languageName: node - linkType: hard - -"@redis/json@npm:1.0.7": - version: 1.0.7 - resolution: "@redis/json@npm:1.0.7" - peerDependencies: - "@redis/client": ^1.0.0 - checksum: 10/df0ac5035608d2e82289237d1adbf541af3743a86692df9958a2c89281bce024eeecfc031db51774d8a46639c5ec34ce9f8b71ebec7bce21865920e36ca3db57 - languageName: node - linkType: hard - -"@redis/search@npm:1.2.0": - version: 1.2.0 - resolution: "@redis/search@npm:1.2.0" - peerDependencies: - "@redis/client": ^1.0.0 - checksum: 10/25bba222c0fb1ec1f2db08fe157d30d56a8ffe234214c72d3a7a991daefe77f18c5e6440ab3aa297aef88bafd5448b04ac9fcd84671f2dadc8989712c06b63b4 - languageName: node - linkType: hard - -"@redis/time-series@npm:1.1.0": - version: 1.1.0 - resolution: "@redis/time-series@npm:1.1.0" - peerDependencies: - "@redis/client": ^1.0.0 - checksum: 10/e1d000eef7f37645f7f4ff94f32dd998384360b12fed1e4d614d828c065b72d7627e27444fab781fac4fdfe301c45f893417550fb1011d3b75237566ff0954e0 + checksum: 10/3ef20235b9b0ecba728bbc7208eabbdfc2eebb50c6fb95b20486a0c14e9d6f3ce620ac0d3d14d7f682ea7cb953b13bf89bd94932b7ab3babeb12ba77136b4291 languageName: node linkType: hard @@ -42883,20 +42839,6 @@ __metadata: languageName: node linkType: hard -"redis@npm:^4.7.0": - version: 4.7.0 - resolution: "redis@npm:4.7.0" - dependencies: - "@redis/bloom": "npm:1.2.0" - "@redis/client": "npm:1.6.0" - "@redis/graph": "npm:1.1.1" - "@redis/json": "npm:1.0.7" - "@redis/search": "npm:1.2.0" - "@redis/time-series": "npm:1.1.0" - checksum: 10/d927a0b1516e2845b7eab67b1466b6f2d0d0695be7a3d4a0a1ffa2f2c60dace98fb9ad01ec0db07519fb9d4d078b99d95b6809508c2a72afe6f814fc8f693188 - languageName: node - linkType: hard - "redux-immutable@npm:^4.0.0": version: 4.0.0 resolution: "redux-immutable@npm:4.0.0" From cef60db9c06c57b4ed23405d150d85bb0620290b Mon Sep 17 00:00:00 2001 From: mario ma Date: Wed, 4 Jun 2025 15:55:51 +0800 Subject: [PATCH 019/301] feat: home plugin support i18n Signed-off-by: mario ma --- .changeset/tall-cloths-invite.md | 6 ++ plugins/home-react/package.json | 1 + plugins/home-react/report.api.md | 11 +++ .../src/components/SettingsModal.tsx | 10 ++- plugins/home-react/src/extensions.tsx | 7 +- plugins/home-react/src/index.ts | 1 + plugins/home-react/src/translation.ts | 32 ++++++++ plugins/home/report.api.md | 33 ++++++++ .../CustomHomepage/AddWidgetDialog.tsx | 5 +- .../CustomHomepage/CustomHomepageButtons.tsx | 13 +-- .../CustomHomepage/CustomHomepageGrid.tsx | 5 +- .../CustomHomepage/WidgetSettingsOverlay.tsx | 11 ++- .../StarredEntityListItem.tsx | 5 +- .../components/VisitList/VisitListEmpty.tsx | 26 +++--- .../src/components/VisitList/VisitListFew.tsx | 19 +++-- .../FeaturedDocsCard/Content.tsx | 11 ++- .../homePageComponents/QuickStart/Content.tsx | 9 ++- .../StarredEntities/Content.tsx | 5 +- .../VisitedByType/Actions.tsx | 7 +- plugins/home/src/index.ts | 1 + plugins/home/src/translation.ts | 81 +++++++++++++++++++ yarn.lock | 1 + 22 files changed, 257 insertions(+), 43 deletions(-) create mode 100644 .changeset/tall-cloths-invite.md create mode 100644 plugins/home-react/src/translation.ts create mode 100644 plugins/home/src/translation.ts diff --git a/.changeset/tall-cloths-invite.md b/.changeset/tall-cloths-invite.md new file mode 100644 index 0000000000..4a7b8812b0 --- /dev/null +++ b/.changeset/tall-cloths-invite.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-home-react': patch +'@backstage/plugin-home': patch +--- + +Home plugin support i18n diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 37180110fe..022760da04 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -44,6 +44,7 @@ "dependencies": { "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@rjsf/utils": "5.23.2" diff --git a/plugins/home-react/report.api.md b/plugins/home-react/report.api.md index 26ef6267aa..369841f192 100644 --- a/plugins/home-react/report.api.md +++ b/plugins/home-react/report.api.md @@ -9,6 +9,7 @@ import { JSX as JSX_3 } from 'react'; import { Overrides } from '@material-ui/core/styles/overrides'; import { RJSFSchema } from '@rjsf/utils'; import { StyleRules } from '@material-ui/core/styles/withStyles'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; import { UiSchema } from '@rjsf/utils'; // @public (undocumented) @@ -81,6 +82,16 @@ export function createCardExtension(options: { settings?: CardSettings; }): Extension<(props: CardExtensionProps) => JSX_2.Element>; +// @public (undocumented) +export const homeReactTranslationRef: TranslationRef< + 'home-react', + { + readonly 'settingsModal.title': 'Settings'; + readonly 'settingsModal.closeButtonTitle': 'Close'; + readonly 'cardExtension.settingsButtonTitle': 'Settings'; + } +>; + // @public (undocumented) export type PluginHomeComponentsNameToClassKey = { PluginHomeContentModal: PluginHomeContentModalClassKey; diff --git a/plugins/home-react/src/components/SettingsModal.tsx b/plugins/home-react/src/components/SettingsModal.tsx index 4a6aa46198..37b8d103c2 100644 --- a/plugins/home-react/src/components/SettingsModal.tsx +++ b/plugins/home-react/src/components/SettingsModal.tsx @@ -14,11 +14,13 @@ * limitations under the License. */ +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Button from '@material-ui/core/Button'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import DialogTitle from '@material-ui/core/DialogTitle'; +import { homeReactTranslationRef } from '../translation'; /** @public */ export const SettingsModal = (props: { @@ -28,16 +30,18 @@ export const SettingsModal = (props: { children: JSX.Element; }) => { const { open, close, componentName, children } = props; - + const { t } = useTranslationRef(homeReactTranslationRef); return ( close()}> - {componentName ? `Settings - ${componentName}` : 'Settings'} + {componentName + ? `${t('settingsModal.title')} - ${componentName}` + : t('settingsModal.title')} {children} diff --git a/plugins/home-react/src/extensions.tsx b/plugins/home-react/src/extensions.tsx index 610c76bb0e..7795e20c73 100644 --- a/plugins/home-react/src/extensions.tsx +++ b/plugins/home-react/src/extensions.tsx @@ -21,6 +21,8 @@ import { InfoCard } from '@backstage/core-components'; import { SettingsModal } from './components'; import { createReactExtension, useApp } from '@backstage/core-plugin-api'; import { RJSFSchema, UiSchema } from '@rjsf/utils'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeReactTranslationRef } from './translation'; /** * @public @@ -133,6 +135,7 @@ function CardExtension(props: CardExtensionComponentProps) { const app = useApp(); const { Progress } = app.getComponents(); const [settingsOpen, setSettingsOpen] = useState(false); + const { t } = useTranslationRef(homeReactTranslationRef); if (Renderer) { return ( @@ -158,7 +161,9 @@ function CardExtension(props: CardExtensionComponentProps) { ? { action: ( setSettingsOpen(true)}> - Settings + + {t('cardExtension.settingsButtonTitle')} + ), } diff --git a/plugins/home-react/src/index.ts b/plugins/home-react/src/index.ts index 54602dc814..f24d85b2b8 100644 --- a/plugins/home-react/src/index.ts +++ b/plugins/home-react/src/index.ts @@ -31,3 +31,4 @@ export type { CardConfig, } from './extensions'; export * from './overridableComponents'; +export { homeReactTranslationRef } from './translation'; diff --git a/plugins/home-react/src/translation.ts b/plugins/home-react/src/translation.ts new file mode 100644 index 0000000000..02e51644bc --- /dev/null +++ b/plugins/home-react/src/translation.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createTranslationRef } from '@backstage/frontend-plugin-api'; + +/** + * @public + */ +export const homeReactTranslationRef = createTranslationRef({ + id: 'home-react', + messages: { + settingsModal: { + title: 'Settings', + closeButtonTitle: 'Close', + }, + cardExtension: { + settingsButtonTitle: 'Settings', + }, + }, +}); diff --git a/plugins/home/report.api.md b/plugins/home/report.api.md index dd33b952ee..0e0f0b0576 100644 --- a/plugins/home/report.api.md +++ b/plugins/home/report.api.md @@ -23,6 +23,7 @@ import { ReactNode } from 'react'; import { RendererProps as RendererProps_2 } from '@backstage/plugin-home-react'; import { RouteRef } from '@backstage/core-plugin-api'; import { StorageApi } from '@backstage/core-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; import { Variant } from '@material-ui/core/styles/createTypography'; // @public @@ -170,6 +171,38 @@ export const homePlugin: BackstagePlugin< {} >; +// @public (undocumented) +export const homeTranslationRef: TranslationRef< + 'home', + { + readonly 'starredEntities.noStarredEntitiesMessage': 'Click the star beside an entity name to add it to this list!'; + readonly 'addWidgetDialog.title': 'Add new widget to dashboard'; + readonly 'customHomepageButtons.clearAll': 'Clear all'; + readonly 'customHomepageButtons.edit': 'Edit'; + readonly 'customHomepageButtons.restoreDefaults': 'Restore defaults'; + readonly 'customHomepageButtons.addWidget': 'Add widget'; + readonly 'customHomepageButtons.save': 'Save'; + readonly 'customHomepage.noWidgets': "No widgets added. Start by clicking the 'Add widget' button."; + readonly 'widgetSettingsOverlay.cancelButtonTitle': 'Cancel'; + readonly 'widgetSettingsOverlay.editSettingsTooptip': 'Edit settings'; + readonly 'widgetSettingsOverlay.deleteWidgetTooltip': 'Delete widget'; + readonly 'widgetSettingsOverlay.submitButtonTitle': 'Submit'; + readonly 'starredEntityListItem.removeFavoriteEntityTitle': 'Remove entity from favorites'; + readonly 'visitList.empty.title': 'There are no visits to show yet.'; + readonly 'visitList.empty.description': 'Once you start using Backstage, your visits will appear here as a quick link to carry on where you left off.'; + readonly 'visitList.few.title': 'The more pages you visit, the more pages will appear here.'; + readonly 'quickStart.title': 'Onboarding'; + readonly 'quickStart.description': 'Get started with Backstage'; + readonly 'quickStart.learnMoreLinkTitle': 'Learn more'; + readonly 'visitedByType.action.viewMore': 'View more'; + readonly 'visitedByType.action.viewLess': 'View less'; + readonly 'featuredDocsCard.empty.title': 'No documents to show'; + readonly 'featuredDocsCard.empty.description': 'Create your own document. Check out our Getting Started Information'; + readonly 'featuredDocsCard.empty.learnMoreLinkTitle': 'DOCS'; + readonly 'featuredDocsCard.learnMoreTitle': 'LEARN MORE'; + } +>; + // @public export const isOperator: (s: string) => s is Operators; diff --git a/plugins/home/src/components/CustomHomepage/AddWidgetDialog.tsx b/plugins/home/src/components/CustomHomepage/AddWidgetDialog.tsx index 6519bfe901..4763401fe5 100644 --- a/plugins/home/src/components/CustomHomepage/AddWidgetDialog.tsx +++ b/plugins/home/src/components/CustomHomepage/AddWidgetDialog.tsx @@ -23,6 +23,8 @@ import ListItemAvatar from '@material-ui/core/ListItemAvatar'; import AddIcon from '@material-ui/icons/Add'; import ListItemText from '@material-ui/core/ListItemText'; import Typography from '@material-ui/core/Typography'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; interface AddWidgetDialogProps { widgets: Widget[]; @@ -35,9 +37,10 @@ const getTitle = (widget: Widget) => { export const AddWidgetDialog = (props: AddWidgetDialogProps) => { const { widgets, handleAdd } = props; + const { t } = useTranslationRef(homeTranslationRef); return ( <> - Add new widget to dashboard + {t('addWidgetDialog.title')} {widgets.map(widget => { diff --git a/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx b/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx index d586f94d20..0496e923de 100644 --- a/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx +++ b/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx @@ -20,6 +20,8 @@ import DeleteIcon from '@material-ui/icons/Delete'; import AddIcon from '@material-ui/icons/Add'; import EditIcon from '@material-ui/icons/Edit'; import CancelIcon from '@material-ui/icons/Cancel'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -55,6 +57,7 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => { restoreDefault, } = props; const styles = useStyles(); + const { t } = useTranslationRef(homeTranslationRef); return ( <> @@ -66,7 +69,7 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => { size="small" startIcon={} > - Edit + {t('customHomepageButtons.edit')} ) : ( <> @@ -78,7 +81,7 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => { size="small" startIcon={} > - Restore defaults + {t('customHomepageButtons.restoreDefaults')} )} {numWidgets > 0 && ( @@ -90,7 +93,7 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => { size="small" startIcon={} > - Clear all + {t('customHomepageButtons.clearAll')} )} {numWidgets > 0 && ( )} diff --git a/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx b/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx index 675e17357e..4b3d35ed62 100644 --- a/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx +++ b/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx @@ -50,6 +50,8 @@ import { WidgetSchema, } from './types'; import { CardConfig } from '@backstage/plugin-home-react'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; // eslint-disable-next-line new-cap const ResponsiveGrid = WidthProvider(Responsive); @@ -222,6 +224,7 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { const getWidgetNameFromKey = (key: string) => { return key.split('__')[0]; }; + const { t } = useTranslationRef(homeTranslationRef); const handleAdd = (widget: Widget) => { const widgetId = `${widget.name}__${widgets.length + 1}${Math.random() @@ -340,7 +343,7 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { {!editMode && widgets.length === 0 && ( - No widgets added. Start by clicking the 'Add widget' button. + {t('customHomepage.noWidgets')} )} { const styles = useStyles(); const onClose = () => setSettingsDialogOpen(false); + const { t } = useTranslationRef(homeTranslationRef); return (
@@ -98,10 +101,10 @@ export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => { > @@ -116,7 +119,7 @@ export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => { > {widget.settingsSchema && ( - + setSettingsDialogOpen(true)} @@ -128,7 +131,7 @@ export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => { )} {deletable !== false && ( - + handleRemove(id)}> diff --git a/plugins/home/src/components/StarredEntityListItem/StarredEntityListItem.tsx b/plugins/home/src/components/StarredEntityListItem/StarredEntityListItem.tsx index 28f199978d..60433fbddf 100644 --- a/plugins/home/src/components/StarredEntityListItem/StarredEntityListItem.tsx +++ b/plugins/home/src/components/StarredEntityListItem/StarredEntityListItem.tsx @@ -26,6 +26,8 @@ import { entityRouteRef } from '@backstage/plugin-catalog-react'; import { useRouteRef } from '@backstage/core-plugin-api'; import { FavoriteToggle } from '@backstage/core-components'; import { makeStyles } from '@material-ui/core/styles'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; type EntityListItemProps = { entity: Entity; @@ -50,6 +52,7 @@ export const StarredEntityListItem = ({ }: EntityListItemProps) => { const classes = useStyles(); const catalogEntityRoute = useRouteRef(entityRouteRef); + const { t } = useTranslationRef(homeTranslationRef); let secondaryText = ''; if (showKind) { @@ -80,7 +83,7 @@ export const StarredEntityListItem = ({ > onToggleStarredEntity(entity)} /> diff --git a/plugins/home/src/components/VisitList/VisitListEmpty.tsx b/plugins/home/src/components/VisitList/VisitListEmpty.tsx index e8b6d369d2..144c9a2066 100644 --- a/plugins/home/src/components/VisitList/VisitListEmpty.tsx +++ b/plugins/home/src/components/VisitList/VisitListEmpty.tsx @@ -14,16 +14,20 @@ * limitations under the License. */ +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Typography from '@material-ui/core/Typography'; +import { homeTranslationRef } from '../../translation'; -export const VisitListEmpty = () => ( - <> - - There are no visits to show yet. - - - Once you start using Backstage, your visits will appear here as a quick - link to carry on where you left off. - - -); +export const VisitListEmpty = () => { + const { t } = useTranslationRef(homeTranslationRef); + return ( + <> + + {t('visitList.empty.title')} + + + {t('visitList.empty.description')} + + + ); +}; diff --git a/plugins/home/src/components/VisitList/VisitListFew.tsx b/plugins/home/src/components/VisitList/VisitListFew.tsx index 26fdb92293..7f741f3af5 100644 --- a/plugins/home/src/components/VisitList/VisitListFew.tsx +++ b/plugins/home/src/components/VisitList/VisitListFew.tsx @@ -14,12 +14,17 @@ * limitations under the License. */ +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Typography from '@material-ui/core/Typography'; +import { homeTranslationRef } from '../../translation'; -export const VisitListFew = () => ( - <> - - The more pages you visit, the more pages will appear here. - - -); +export const VisitListFew = () => { + const { t } = useTranslationRef(homeTranslationRef); + return ( + <> + + {t('visitList.few.title')} + + + ); +}; diff --git a/plugins/home/src/homePageComponents/FeaturedDocsCard/Content.tsx b/plugins/home/src/homePageComponents/FeaturedDocsCard/Content.tsx index 475ef4b47e..a854203fdb 100644 --- a/plugins/home/src/homePageComponents/FeaturedDocsCard/Content.tsx +++ b/plugins/home/src/homePageComponents/FeaturedDocsCard/Content.tsx @@ -33,6 +33,8 @@ import { EntityFilterQuery } from '@backstage/catalog-client'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import { stringifyEntityRef } from '@backstage/catalog-model'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; /** * Props customizing the component. @@ -88,7 +90,8 @@ const useStyles = makeStyles( export const Content = (props: FeaturedDocsCardProps): JSX.Element => { const { emptyState, filter, linkDestination, responseLimit, subLinkText } = props; - const linkText = subLinkText || 'LEARN MORE'; + const { t } = useTranslationRef(homeTranslationRef); + const linkText = subLinkText || t('featuredDocsCard.learnMoreTitle'); const styles = useStyles(); const catalogApi = useApi(catalogApiRef); const { @@ -153,14 +156,14 @@ export const Content = (props: FeaturedDocsCardProps): JSX.Element => { emptyState || ( - DOCS + {t('featuredDocsCard.empty.learnMoreLinkTitle')} } /> diff --git a/plugins/home/src/homePageComponents/QuickStart/Content.tsx b/plugins/home/src/homePageComponents/QuickStart/Content.tsx index b923970888..5317312626 100644 --- a/plugins/home/src/homePageComponents/QuickStart/Content.tsx +++ b/plugins/home/src/homePageComponents/QuickStart/Content.tsx @@ -20,6 +20,8 @@ import Typography from '@material-ui/core/Typography'; import Grid from '@material-ui/core/Grid'; import { ContentModal } from '@backstage/plugin-home-react'; import { useStyles } from './styles'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; /** * Props customizing the component. @@ -54,14 +56,15 @@ export type QuickStartCardProps = { */ export const Content = (props: QuickStartCardProps): JSX.Element => { const styles = useStyles(); + const { t } = useTranslationRef(homeTranslationRef); return ( <> - {props.cardDescription || 'Get started with Backstage'} + {props.cardDescription || t('quickStart.description')} { variant="h6" className={styles.link} > - {props.docsLinkTitle || 'Learn more'} + {props.docsLinkTitle || t('quickStart.learnMoreLinkTitle')} diff --git a/plugins/home/src/homePageComponents/StarredEntities/Content.tsx b/plugins/home/src/homePageComponents/StarredEntities/Content.tsx index 80f5ee74b5..ffe1b36b36 100644 --- a/plugins/home/src/homePageComponents/StarredEntities/Content.tsx +++ b/plugins/home/src/homePageComponents/StarredEntities/Content.tsx @@ -29,6 +29,8 @@ import { ReactNode, useState } from 'react'; import useAsync from 'react-use/esm/useAsync'; import { StarredEntityListItem } from '../../components/StarredEntityListItem/StarredEntityListItem'; import { makeStyles } from '@material-ui/core/styles'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ tabs: { @@ -63,6 +65,7 @@ export const Content = ({ const catalogApi = useApi(catalogApiRef); const { starredEntities, toggleStarredEntity } = useStarredEntities(); const [activeTab, setActiveTab] = useState(0); + const { t } = useTranslationRef(homeTranslationRef); // Grab starred entities from catalog to ensure they still exist and also retrieve display titles const entities = useAsync(async () => { @@ -89,7 +92,7 @@ export const Content = ({ return ( {noStarredEntitiesMessage || - 'Click the star beside an entity name to add it to this list!'} + t('starredEntities.noStarredEntitiesMessage')} ); diff --git a/plugins/home/src/homePageComponents/VisitedByType/Actions.tsx b/plugins/home/src/homePageComponents/VisitedByType/Actions.tsx index a8e8bf7722..4c874f93a7 100644 --- a/plugins/home/src/homePageComponents/VisitedByType/Actions.tsx +++ b/plugins/home/src/homePageComponents/VisitedByType/Actions.tsx @@ -17,6 +17,8 @@ import { useCallback } from 'react'; import Button from '@material-ui/core/Button'; import { useContext } from './Context'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { homeTranslationRef } from '../../translation'; export const Actions = () => { const { collapsed, setCollapsed, visits, numVisitsOpen, loading } = @@ -25,7 +27,10 @@ export const Actions = () => { () => setCollapsed(prevCollapsed => !prevCollapsed), [setCollapsed], ); - const label = collapsed ? 'View More' : 'View Less'; + const { t } = useTranslationRef(homeTranslationRef); + const label = collapsed + ? t('visitedByType.action.viewMore') + : t('visitedByType.action.viewLess'); if (!loading && visits.length <= numVisitsOpen) return <>; diff --git a/plugins/home/src/index.ts b/plugins/home/src/index.ts index 3a6f152d03..35bd43460d 100644 --- a/plugins/home/src/index.ts +++ b/plugins/home/src/index.ts @@ -42,3 +42,4 @@ export * from './assets'; export * from './homePageComponents'; export * from './deprecated'; export * from './api'; +export { homeTranslationRef } from './translation'; diff --git a/plugins/home/src/translation.ts b/plugins/home/src/translation.ts new file mode 100644 index 0000000000..bdd1648780 --- /dev/null +++ b/plugins/home/src/translation.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createTranslationRef } from '@backstage/frontend-plugin-api'; + +/** + * @public + */ +export const homeTranslationRef = createTranslationRef({ + id: 'home', + messages: { + addWidgetDialog: { + title: 'Add new widget to dashboard', + }, + customHomepageButtons: { + edit: 'Edit', + restoreDefaults: 'Restore defaults', + clearAll: 'Clear all', + addWidget: 'Add widget', + save: 'Save', + }, + customHomepage: { + noWidgets: "No widgets added. Start by clicking the 'Add widget' button.", + }, + widgetSettingsOverlay: { + editSettingsTooptip: 'Edit settings', + deleteWidgetTooltip: 'Delete widget', + submitButtonTitle: 'Submit', + cancelButtonTitle: 'Cancel', + }, + starredEntityListItem: { + removeFavoriteEntityTitle: 'Remove entity from favorites', + }, + visitList: { + empty: { + title: 'There are no visits to show yet.', + description: + 'Once you start using Backstage, your visits will appear here as a quick link to carry on where you left off.', + }, + few: { + title: 'The more pages you visit, the more pages will appear here.', + }, + }, + quickStart: { + title: 'Onboarding', + description: 'Get started with Backstage', + learnMoreLinkTitle: 'Learn more', + }, + starredEntities: { + noStarredEntitiesMessage: + 'Click the star beside an entity name to add it to this list!', + }, + visitedByType: { + action: { + viewMore: 'View more', + viewLess: 'View less', + }, + }, + featuredDocsCard: { + learnMoreTitle: 'LEARN MORE', + empty: { + title: 'No documents to show', + description: + 'Create your own document. Check out our Getting Started Information', + learnMoreLinkTitle: 'DOCS', + }, + }, + }, +}); diff --git a/yarn.lock b/yarn.lock index 8803ac52e2..8692bbc5b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6727,6 +6727,7 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@rjsf/utils": "npm:5.23.2" From b126ad619a5922380504c146ae043ce480a15735 Mon Sep 17 00:00:00 2001 From: mario ma Date: Thu, 5 Jun 2025 11:55:05 +0800 Subject: [PATCH 020/301] change homeTranslationRef to alpha api Signed-off-by: mario ma --- plugins/home/report-alpha.api.md | 33 ++++++++++++++++++++++++++++++++ plugins/home/report.api.md | 33 -------------------------------- plugins/home/src/alpha.tsx | 2 ++ plugins/home/src/index.ts | 1 - plugins/home/src/translation.ts | 2 +- 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/plugins/home/report-alpha.api.md b/plugins/home/report-alpha.api.md index 5e43163e31..f36589ea51 100644 --- a/plugins/home/report-alpha.api.md +++ b/plugins/home/report-alpha.api.md @@ -10,6 +10,7 @@ import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: FrontendPlugin< @@ -69,6 +70,38 @@ const _default: FrontendPlugin< >; export default _default; +// @alpha (undocumented) +export const homeTranslationRef: TranslationRef< + 'home', + { + readonly 'starredEntities.noStarredEntitiesMessage': 'Click the star beside an entity name to add it to this list!'; + readonly 'addWidgetDialog.title': 'Add new widget to dashboard'; + readonly 'customHomepageButtons.clearAll': 'Clear all'; + readonly 'customHomepageButtons.edit': 'Edit'; + readonly 'customHomepageButtons.restoreDefaults': 'Restore defaults'; + readonly 'customHomepageButtons.addWidget': 'Add widget'; + readonly 'customHomepageButtons.save': 'Save'; + readonly 'customHomepage.noWidgets': "No widgets added. Start by clicking the 'Add widget' button."; + readonly 'widgetSettingsOverlay.cancelButtonTitle': 'Cancel'; + readonly 'widgetSettingsOverlay.editSettingsTooptip': 'Edit settings'; + readonly 'widgetSettingsOverlay.deleteWidgetTooltip': 'Delete widget'; + readonly 'widgetSettingsOverlay.submitButtonTitle': 'Submit'; + readonly 'starredEntityListItem.removeFavoriteEntityTitle': 'Remove entity from favorites'; + readonly 'visitList.empty.title': 'There are no visits to show yet.'; + readonly 'visitList.empty.description': 'Once you start using Backstage, your visits will appear here as a quick link to carry on where you left off.'; + readonly 'visitList.few.title': 'The more pages you visit, the more pages will appear here.'; + readonly 'quickStart.title': 'Onboarding'; + readonly 'quickStart.description': 'Get started with Backstage'; + readonly 'quickStart.learnMoreLinkTitle': 'Learn more'; + readonly 'visitedByType.action.viewMore': 'View more'; + readonly 'visitedByType.action.viewLess': 'View less'; + readonly 'featuredDocsCard.empty.title': 'No documents to show'; + readonly 'featuredDocsCard.empty.description': 'Create your own document. Check out our Getting Started Information'; + readonly 'featuredDocsCard.empty.learnMoreLinkTitle': 'DOCS'; + readonly 'featuredDocsCard.learnMoreTitle': 'LEARN MORE'; + } +>; + // @alpha (undocumented) export const titleExtensionDataRef: ConfigurableExtensionDataRef< string, diff --git a/plugins/home/report.api.md b/plugins/home/report.api.md index 0e0f0b0576..dd33b952ee 100644 --- a/plugins/home/report.api.md +++ b/plugins/home/report.api.md @@ -23,7 +23,6 @@ import { ReactNode } from 'react'; import { RendererProps as RendererProps_2 } from '@backstage/plugin-home-react'; import { RouteRef } from '@backstage/core-plugin-api'; import { StorageApi } from '@backstage/core-plugin-api'; -import { TranslationRef } from '@backstage/frontend-plugin-api'; import { Variant } from '@material-ui/core/styles/createTypography'; // @public @@ -171,38 +170,6 @@ export const homePlugin: BackstagePlugin< {} >; -// @public (undocumented) -export const homeTranslationRef: TranslationRef< - 'home', - { - readonly 'starredEntities.noStarredEntitiesMessage': 'Click the star beside an entity name to add it to this list!'; - readonly 'addWidgetDialog.title': 'Add new widget to dashboard'; - readonly 'customHomepageButtons.clearAll': 'Clear all'; - readonly 'customHomepageButtons.edit': 'Edit'; - readonly 'customHomepageButtons.restoreDefaults': 'Restore defaults'; - readonly 'customHomepageButtons.addWidget': 'Add widget'; - readonly 'customHomepageButtons.save': 'Save'; - readonly 'customHomepage.noWidgets': "No widgets added. Start by clicking the 'Add widget' button."; - readonly 'widgetSettingsOverlay.cancelButtonTitle': 'Cancel'; - readonly 'widgetSettingsOverlay.editSettingsTooptip': 'Edit settings'; - readonly 'widgetSettingsOverlay.deleteWidgetTooltip': 'Delete widget'; - readonly 'widgetSettingsOverlay.submitButtonTitle': 'Submit'; - readonly 'starredEntityListItem.removeFavoriteEntityTitle': 'Remove entity from favorites'; - readonly 'visitList.empty.title': 'There are no visits to show yet.'; - readonly 'visitList.empty.description': 'Once you start using Backstage, your visits will appear here as a quick link to carry on where you left off.'; - readonly 'visitList.few.title': 'The more pages you visit, the more pages will appear here.'; - readonly 'quickStart.title': 'Onboarding'; - readonly 'quickStart.description': 'Get started with Backstage'; - readonly 'quickStart.learnMoreLinkTitle': 'Learn more'; - readonly 'visitedByType.action.viewMore': 'View more'; - readonly 'visitedByType.action.viewLess': 'View less'; - readonly 'featuredDocsCard.empty.title': 'No documents to show'; - readonly 'featuredDocsCard.empty.description': 'Create your own document. Check out our Getting Started Information'; - readonly 'featuredDocsCard.empty.learnMoreLinkTitle': 'DOCS'; - readonly 'featuredDocsCard.learnMoreTitle': 'LEARN MORE'; - } ->; - // @public export const isOperator: (s: string) => s is Operators; diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx index 854a8a7dd3..fc34c6e806 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -74,3 +74,5 @@ export default createFrontendPlugin({ root: rootRouteRef, }, }); + +export { homeTranslationRef } from './translation'; diff --git a/plugins/home/src/index.ts b/plugins/home/src/index.ts index 35bd43460d..3a6f152d03 100644 --- a/plugins/home/src/index.ts +++ b/plugins/home/src/index.ts @@ -42,4 +42,3 @@ export * from './assets'; export * from './homePageComponents'; export * from './deprecated'; export * from './api'; -export { homeTranslationRef } from './translation'; diff --git a/plugins/home/src/translation.ts b/plugins/home/src/translation.ts index bdd1648780..4070dde28c 100644 --- a/plugins/home/src/translation.ts +++ b/plugins/home/src/translation.ts @@ -16,7 +16,7 @@ import { createTranslationRef } from '@backstage/frontend-plugin-api'; /** - * @public + * @alpha */ export const homeTranslationRef = createTranslationRef({ id: 'home', From ff51a0d3f9c60f791c44dc2208011e57ad7ea640 Mon Sep 17 00:00:00 2001 From: Thibaud Schoolmeesters <92119856+S-Thibaud@users.noreply.github.com> Date: Thu, 5 Jun 2025 08:17:23 +0200 Subject: [PATCH 021/301] Merge branch 'backstage:master' into patch-2 Signed-off-by: S-Thibaud <92119856+S-Thibaud@users.noreply.github.com> --- microsite/data/plugins/hetzner-cloud.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 microsite/data/plugins/hetzner-cloud.yaml diff --git a/microsite/data/plugins/hetzner-cloud.yaml b/microsite/data/plugins/hetzner-cloud.yaml new file mode 100644 index 0000000000..6f1fbe3f25 --- /dev/null +++ b/microsite/data/plugins/hetzner-cloud.yaml @@ -0,0 +1,10 @@ +--- +title: Hetzner Cloud +author: Gluo NV +authorUrl: https://github.com/gluobe +category: Infrastructure +description: Integrate Hetzner Cloud resources into Backstage. +documentation: https://github.com/gluobe/hetzner-backstage-plugin/blob/main/README.md +iconUrl: https://avatars.githubusercontent.com/u/18439789?s=200&v=4 +npmPackageName: '@gluo-nv/backstage-plugin-hetzner' +addedDate: '2025-06-03' From e0189b811df5298c98144f70a0ad0d42bf62a5cc Mon Sep 17 00:00:00 2001 From: Cptn Fizzbin Date: Fri, 6 Jun 2025 09:56:34 -0400 Subject: [PATCH 022/301] Fix authorization header for GitLab requests Signed-off-by: Cptn Fizzbin --- .changeset/six-dots-agree.md | 6 +++ .../urlReader/lib/GitlabUrlReader.test.ts | 43 ++++++++----------- .../urlReader/lib/GitlabUrlReader.ts | 6 +++ packages/integration/src/gitlab/core.test.ts | 25 ++--------- packages/integration/src/gitlab/core.ts | 27 ++++++------ 5 files changed, 50 insertions(+), 57 deletions(-) create mode 100644 .changeset/six-dots-agree.md diff --git a/.changeset/six-dots-agree.md b/.changeset/six-dots-agree.md new file mode 100644 index 0000000000..97f9b3017f --- /dev/null +++ b/.changeset/six-dots-agree.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-defaults': patch +'@backstage/integration': patch +--- + +UrlReader: Fix handling of access tokens for GitLab readURL requests diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts index b7ccf165ad..337325f6f8 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts @@ -108,9 +108,6 @@ describe('GitlabUrlReader', () => { config: createConfig(), response: expect.objectContaining({ url: 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch', - headers: expect.objectContaining({ - 'private-token': '', - }), }), }, { @@ -119,7 +116,7 @@ describe('GitlabUrlReader', () => { response: expect.objectContaining({ url: 'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch', headers: expect.objectContaining({ - 'private-token': '0123456789', + authorization: 'Bearer 0123456789', }), }), }, @@ -252,10 +249,10 @@ describe('GitlabUrlReader', () => { it('should return the file when using a user token', async () => { worker.use( rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => { - if (req.headers.get('private-token') !== 'gl-user-token') { + if (req.headers.get('authorization') !== 'Bearer gl-user-token') { return res( - ctx.status(403), - ctx.json({ message: 'Not Authorized' }), + ctx.status(401), + ctx.json({ message: '401 Unauthorized' }), ); } return res(ctx.status(200), ctx.json({ id: 12345 })); @@ -571,10 +568,10 @@ describe('GitlabUrlReader', () => { it('should return the file when using a user token', async () => { worker.use( rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => { - if (req.headers.get('private-token') !== 'gl-user-token') { + if (req.headers.get('authorization') !== 'Bearer gl-user-token') { return res( - ctx.status(403), - ctx.json({ message: 'Not Authorized' }), + ctx.status(401), + ctx.json({ message: '401 Unauthorized' }), ); } return res(ctx.status(200), ctx.json({ id: 12345 })); @@ -737,10 +734,10 @@ describe('GitlabUrlReader', () => { (_, res, ctx) => res(ctx.status(200), ctx.json({ id: 12345 })), ), rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => { - if (req.headers.get('private-token') !== 'gl-user-token') { + if (req.headers.get('authorization') !== 'Bearer gl-user-token') { return res( - ctx.status(403), - ctx.json({ message: 'Not Authorized' }), + ctx.status(401), + ctx.json({ message: '401 Unauthorized' }), ); } return res(ctx.status(200), ctx.json({ id: 12345 })); @@ -798,10 +795,10 @@ describe('GitlabUrlReader', () => { (_, res, ctx) => res(ctx.status(404)), ), rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => { - if (req.headers.get('private-token') !== 'gl-user-token') { + if (req.headers.get('authorization') !== 'Bearer gl-user-token') { return res( - ctx.status(403), - ctx.json({ message: 'Not Authorized' }), + ctx.status(401), + ctx.json({ message: '401 Unauthorized' }), ); } return res(ctx.status(200), ctx.json({ id: 12345 })); @@ -857,21 +854,19 @@ describe('GitlabUrlReader', () => { beforeEach(() => { worker.use( rest.get('*/api/v4/projects/group%2Fproject', (req, res, ctx) => { - // the private-token header must be included on API calls - if (req.headers.get('private-token') !== 'gl-dummy-token') { + if (req.headers.get('authorization') !== 'Bearer gl-dummy-token') { return res( - ctx.status(403), - ctx.json({ message: 'Not Authorized' }), + ctx.status(401), + ctx.json({ message: '401 Unauthorized' }), ); } return res(ctx.status(200), ctx.json({ id: 12345 })); }), rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => { - // the private-token header must be included on API calls - if (req.headers.get('private-token') !== 'gl-user-token') { + if (req.headers.get('authorization') !== 'Bearer gl-user-token') { return res( - ctx.status(403), - ctx.json({ message: 'Not Authorized' }), + ctx.status(401), + ctx.json({ message: '401 Unauthorized' }), ); } return res(ctx.status(200), ctx.json({ id: 12345 })); diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts index 3e5497a307..6985631c3f 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts @@ -398,6 +398,12 @@ export class GitlabUrlReader implements UrlReaderService { ); const data = await result.json(); if (!result.ok) { + if (result.status === 401) { + throw new Error( + 'GitLab Error: 401 - Unauthorized. The access token used is either expired, or does not have permission to read the project', + ); + } + throw new Error(`Gitlab error: ${data.error}, ${data.error_description}`); } return Number(data.id); diff --git a/packages/integration/src/gitlab/core.test.ts b/packages/integration/src/gitlab/core.test.ts index 81adc29701..d514dba755 100644 --- a/packages/integration/src/gitlab/core.test.ts +++ b/packages/integration/src/gitlab/core.test.ts @@ -188,7 +188,7 @@ describe('gitlab core', () => { }); describe('getGitLabRequestOptions', () => { - it('should return Authorization header when oauthToken is provided', () => { + it('should return Authorization bearer header when a token is provided', () => { const token = '1234567890'; const result = getGitLabRequestOptions( configSelfHosteWithRelativePath, @@ -202,29 +202,12 @@ describe('gitlab core', () => { }); }); - it('should return private-token header when gl-token is provided', () => { - const token = 'glpat-1234566'; - const result = getGitLabRequestOptions( - configSelfHosteWithRelativePath, - token, - ); + it('should return Authorization bearer header using the config token when no token is provided', () => { + const result = getGitLabRequestOptions(configSelfHosteWithRelativePath); expect(result).toEqual({ headers: { - 'PRIVATE-TOKEN': token, - }, - }); - }); - - it('should return private-token header when oauthToken is undefined', () => { - const oauthToken = undefined; - const result = getGitLabRequestOptions( - configSelfHosteWithRelativePath, - oauthToken, - ); - expect(result).toEqual({ - headers: { - 'PRIVATE-TOKEN': configSelfHosteWithRelativePath.token, + Authorization: `Bearer ${configSelfHosteWithRelativePath.token}`, }, }); }); diff --git a/packages/integration/src/gitlab/core.ts b/packages/integration/src/gitlab/core.ts index 4a17b2a15e..dbe463604c 100644 --- a/packages/integration/src/gitlab/core.ts +++ b/packages/integration/src/gitlab/core.ts @@ -56,20 +56,17 @@ export function getGitLabRequestOptions( config: GitLabIntegrationConfig, token?: string, ): { headers: Record } { - if (token) { - // If token comes from the user and starts with "gl", it's a private token (see https://docs.gitlab.com/ee/security/token_overview.html#token-prefixes) - return { - headers: token.startsWith('gl') - ? { 'PRIVATE-TOKEN': token } - : { Authorization: `Bearer ${token}` }, // Otherwise, it's a bearer token - }; + const headers: Record = {}; + + const accessToken = token || config.token; + if (accessToken) { + // OAuth, Personal, Project, and Group access tokens can all be passed via + // a bearer authorization header + // https://docs.gitlab.com/api/rest/authentication/#personalprojectgroup-access-tokens + headers.Authorization = `Bearer ${accessToken}`; } - // If token not provided, fetch the integration token - const { token: configToken = '' } = config; - return { - headers: { 'PRIVATE-TOKEN': configToken }, - }; + return { headers }; } // Converts @@ -149,6 +146,12 @@ export async function getProjectId( const data = await response.json(); if (!response.ok) { + if (response.status === 401) { + throw new Error( + 'GitLab Error: 401 - Unauthorized. The access token used is either expired, or does not have permission to read the project', + ); + } + throw new Error( `GitLab Error '${data.error}', ${data.error_description}`, ); From 386f644182b1e813a4f991eabea3144504ca3b6f Mon Sep 17 00:00:00 2001 From: Cptn Fizzbin Date: Fri, 6 Jun 2025 14:00:09 -0400 Subject: [PATCH 023/301] Fix gitlab discovery processor tests Signed-off-by: Cptn Fizzbin --- .../src/GitLabDiscoveryProcessor.test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts index d7ce338d6d..d75d0e224e 100644 --- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts @@ -72,7 +72,7 @@ function setupFakeServer( assertion(req); } - if (req.headers.get('private-token') !== 'test-token') { + if (req.headers.get('authorization') !== 'Bearer test-token') { return res(ctx.status(401), ctx.json({})); } const page = req.url.searchParams.get('page'); @@ -104,8 +104,11 @@ function setupFakeServer( rest.head( `${API_URL}/projects/:project_path/repository/files/:file_path`, (req, res, ctx) => { - if (req.headers.get('private-token') !== 'test-token') { - return res(ctx.status(401), ctx.json({})); + if (req.headers.get('authorization') !== 'Bearer test-token') { + return res( + ctx.status(401), + ctx.json({ message: '401 Unauthorized' }), + ); } const ref = req.url.searchParams.get('ref'); From 69fb975e2161a7b132235103a48d47f80786549b Mon Sep 17 00:00:00 2001 From: abinavsridhar-wk Date: Mon, 19 May 2025 13:09:56 +0200 Subject: [PATCH 024/301] Fix: 29961: Do not propogate SQL query to API response Signed-off-by: abinavsridhar-wk --- .changeset/wicked-socks-share.md | 5 +++++ plugins/search-backend/src/service/router.ts | 11 +++++++++++ 2 files changed, 16 insertions(+) create mode 100644 .changeset/wicked-socks-share.md diff --git a/.changeset/wicked-socks-share.md b/.changeset/wicked-socks-share.md new file mode 100644 index 0000000000..d3e1275732 --- /dev/null +++ b/.changeset/wicked-socks-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend': patch +--- + +Error messages should not contain backend SQL query strings in the API response, this change will ensure that messages are logged and empty response is returned to the user diff --git a/plugins/search-backend/src/service/router.ts b/plugins/search-backend/src/service/router.ts index 0ba5eede81..51d32e9e95 100644 --- a/plugins/search-backend/src/service/router.ts +++ b/plugins/search-backend/src/service/router.ts @@ -201,6 +201,17 @@ export async function createRouter( // re-throw and let the default error handler middleware captures it and serializes it with the right response code on the standard form throw error; } + if (query.term.indexOf('<') !== -1) { + // Search queries that contain '<' are likely to be a SQL injection attack, log the error and return an empty response + const message = (error as any)?.message || ''; + if ( + typeof message === 'string' && + message.indexOf('syntax error in tsquery') !== -1 + ) { + logger.info('Search query skipped due to tsquery syntax error.'); + return; // Do not throw an error and just skip the search + } + } throw new Error( `There was a problem performing the search query: ${error.message}`, From 5ea47166b28d5e0a7ea6667e602fd098364515c0 Mon Sep 17 00:00:00 2001 From: abinavsridhar-wk Date: Wed, 21 May 2025 16:20:21 +0200 Subject: [PATCH 025/301] Address comments Signed-off-by: abinavsridhar-wk --- plugins/search-backend/src/service/router.ts | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/plugins/search-backend/src/service/router.ts b/plugins/search-backend/src/service/router.ts index 51d32e9e95..eedd84105a 100644 --- a/plugins/search-backend/src/service/router.ts +++ b/plugins/search-backend/src/service/router.ts @@ -197,25 +197,17 @@ export async function createRouter( res.json(filterResultSet(toSearchResults(resultSet))); } catch (error) { + // Log the error message here, but don't expose it to the user in the response + logger.error( + `There was a problem performing the search query: ${error.message}`, + ); if (error.name === 'MissingIndexError') { // re-throw and let the default error handler middleware captures it and serializes it with the right response code on the standard form throw error; } - if (query.term.indexOf('<') !== -1) { - // Search queries that contain '<' are likely to be a SQL injection attack, log the error and return an empty response - const message = (error as any)?.message || ''; - if ( - typeof message === 'string' && - message.indexOf('syntax error in tsquery') !== -1 - ) { - logger.info('Search query skipped due to tsquery syntax error.'); - return; // Do not throw an error and just skip the search - } - } - throw new Error( - `There was a problem performing the search query: ${error.message}`, - ); + // If the error is not a MissingIndexError, we want to throw a generic error without the error message as it may leak internal information + throw new Error(`There was a problem performing the search query`); } }); From a365cbacf289fccc0f356bf4a9cdc7f61a9bccc5 Mon Sep 17 00:00:00 2001 From: abinavsridhar-wk Date: Sat, 7 Jun 2025 19:57:22 +0200 Subject: [PATCH 026/301] Fix test cases Signed-off-by: abinavsridhar-wk --- plugins/search-backend/src/service/router.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/search-backend/src/service/router.test.ts b/plugins/search-backend/src/service/router.test.ts index 5aea71b7c1..623b78016a 100644 --- a/plugins/search-backend/src/service/router.test.ts +++ b/plugins/search-backend/src/service/router.test.ts @@ -106,7 +106,7 @@ describe('createRouter', () => { expect.objectContaining({ error: { name: 'Error', - message: `There was a problem performing the search query: ${error.message}`, + message: `There was a problem performing the search query`, }, }), ); From f983e9907c6cacad72c153b68a1fdcce9c0ee516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sun, 8 Jun 2025 15:23:00 +0200 Subject: [PATCH 027/301] Add an `EventConsumingGooglePubSubPublisher`, for pushing Backstage events to pubsub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/moody-doors-hug.md | 5 + .../README.md | 23 +- .../config.d.ts | 62 ++++++ .../dev/index.ts | 31 ++- .../report.api.md | 4 +- ...ventConsumingGooglePubSubPublisher.test.ts | 111 ++++++++++ .../EventConsumingGooglePubSubPublisher.ts | 153 ++++++++++++++ .../config.test.ts | 196 ++++++++++++++++++ .../config.ts | 146 +++++++++++++ .../index.ts | 17 ++ .../module.ts | 52 +++++ .../types.ts | 30 +++ .../config.test.ts | 2 +- .../config.ts | 4 +- .../module.ts | 2 - .../src/index.ts | 11 +- 16 files changed, 830 insertions(+), 19 deletions(-) create mode 100644 .changeset/moody-doors-hug.md create mode 100644 plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.test.ts create mode 100644 plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.ts create mode 100644 plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.test.ts create mode 100644 plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.ts create mode 100644 plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/index.ts create mode 100644 plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/module.ts create mode 100644 plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/types.ts diff --git a/.changeset/moody-doors-hug.md b/.changeset/moody-doors-hug.md new file mode 100644 index 0000000000..033bdeedd9 --- /dev/null +++ b/.changeset/moody-doors-hug.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-events-backend-module-google-pubsub': patch +--- + +Add an `EventConsumingGooglePubSubPublisher`, for pushing Backstage events to pubsub diff --git a/plugins/events-backend-module-google-pubsub/README.md b/plugins/events-backend-module-google-pubsub/README.md index 67ddcfacb4..9752fa30c6 100644 --- a/plugins/events-backend-module-google-pubsub/README.md +++ b/plugins/events-backend-module-google-pubsub/README.md @@ -19,11 +19,24 @@ events: # The fully qualified name of the subscription subscriptionName: 'projects/my-google-project/subscriptions/github-enterprise-events' # The event system topic to transfer to. This can also be just a plain string - targetTopic: - # This example picks the topic name from a message attribute + a prefix - fromMessageAttribute: - attributeName: 'x-github-event' - withPrefix: 'github.' + targetTopic: 'github.{{ event.attributes.x-github-event }}' +``` + +The following configuration enables the transfer of events from a Backstage events topic into a Google +Pub/Sub topic. + +```yaml +events: + modules: + googlePubSub: + eventConsumingGooglePubSubPublisher: + subscriptions: + # A unique key for your subscription, to be used in logging and metrics + mySubscription: + # The source topic (or array of topics) + sourceTopic: 'github' + # The fully qualified name of the target topic + targetTopicName: 'projects/my-google-project/topics/github-enterprise-events' ``` ## Installation diff --git a/plugins/events-backend-module-google-pubsub/config.d.ts b/plugins/events-backend-module-google-pubsub/config.d.ts index 3e70489b9d..1097b71e00 100644 --- a/plugins/events-backend-module-google-pubsub/config.d.ts +++ b/plugins/events-backend-module-google-pubsub/config.d.ts @@ -90,6 +90,68 @@ export interface Config { }; }; }; + + /** + * Configuration for `EventConsumingGooglePubSubPublisher`, which + * consumes messages from the Backstage events system and forwards them + * into Google Pub/Sub topics. + */ + eventConsumingGooglePubSubPublisher?: { + subscriptions: { + [name: string]: { + /** + * The name of the events backend topic(s) that messages are + * consumed from. + */ + sourceTopic: string | string[]; + + /** + * The complete name of the Google Pub/Sub subscription to forward + * events to, on the form + * `projects/PROJECT_ID/topics/TOPIC_ID`. + * + * The value can contain placeholders on the form `{{ + * message.attributes.foo }}`, to mirror attribute `foo` as the + * whole or part of the topic name. + * + * @example + * + * This example expects the events topic to contain GitHub + * webhook events where the HTTP headers were mapped into + * event metadata fields. The outcome should be that messages + * end up on event topics such as `github.push`, + * `github.repository` etc which matches the [`@backstage/plugin-events-backend-module-github`](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github) structure. + * + * ```yaml + * targetTopic: 'projects/my-project/topics/github.{{ event.metadata.x-github-event }}' + * ``` + */ + targetTopicName: string; + + /** + * Event metadata fields are by default copied to the Pub/Sub + * message attribute. This setting allows you to override or amend + * those attributes. + * + * @remarks + * + * The values can contain placeholders on the form `{{ event.metadata.foo }}`, + * to mirror metadata field `foo` as the whole or part of a + * message attribute value. + * + * @example + * + * ```yaml + * messageAttributes: + * x-gitHub-event: '{{ event.metadata.event }}' + * ``` + */ + messageAttributes?: { + [key: string]: string; + }; + }; + }; + }; }; }; }; diff --git a/plugins/events-backend-module-google-pubsub/dev/index.ts b/plugins/events-backend-module-google-pubsub/dev/index.ts index 752217e6db..78a7e775cf 100644 --- a/plugins/events-backend-module-google-pubsub/dev/index.ts +++ b/plugins/events-backend-module-google-pubsub/dev/index.ts @@ -28,20 +28,21 @@ backend.add(import('../src')); backend.add( createBackendPlugin({ - pluginId: 'example-event-consumer', + pluginId: 'example-event-drivers', register(reg) { reg.registerInit({ deps: { events: eventsServiceRef, logger: coreServices.logger, + scheduler: coreServices.scheduler, }, - async init({ events, logger }) { - events.subscribe({ - id: 'example-subscription', - topics: ['example-topic'], + async init({ events, logger, scheduler }) { + await events.subscribe({ + id: 'inbox-subscription', + topics: ['inbox-topic'], async onEvent(event) { logger.info( - `Received event: ${JSON.stringify( + `Received inbox event: ${JSON.stringify( { topic: event.topic, payload: event.eventPayload, @@ -53,6 +54,24 @@ backend.add( ); }, }); + + await scheduler.scheduleTask({ + id: 'outbox-task', + scope: 'local', + frequency: { seconds: 5 }, + timeout: { seconds: 5 }, + fn: async () => { + await events.publish({ + topic: 'outbox-topic', + eventPayload: { + message: 'Hello, world!', + }, + metadata: { + source: 'outbox-task', + }, + }); + }, + }); }, }); }, diff --git a/plugins/events-backend-module-google-pubsub/report.api.md b/plugins/events-backend-module-google-pubsub/report.api.md index bdb057d7fc..b42c1f80ff 100644 --- a/plugins/events-backend-module-google-pubsub/report.api.md +++ b/plugins/events-backend-module-google-pubsub/report.api.md @@ -6,6 +6,6 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; // @public -const eventsModuleGooglePubsubConsumingEventPublisher: BackendFeature; -export default eventsModuleGooglePubsubConsumingEventPublisher; +const _default: BackendFeature; +export default _default; ``` diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.test.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.test.ts new file mode 100644 index 0000000000..e5af68add7 --- /dev/null +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.test.ts @@ -0,0 +1,111 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { mockServices } from '@backstage/backend-test-utils'; +import { EventParams } from '@backstage/plugin-events-node'; +import { PubSub } from '@google-cloud/pubsub'; +import waitFor from 'wait-for-expect'; +import { EventConsumingGooglePubSubPublisher } from './EventConsumingGooglePubSubPublisher'; + +describe('EventConsumingGooglePubSubPublisher', () => { + const logger = mockServices.logger.mock(); + const events = mockServices.events.mock(); + + let onEvent: undefined | ((event: EventParams) => void); + events.subscribe.mockImplementation(async options => { + onEvent = options.onEvent; + }); + + const topic = { + publishMessage: jest.fn(), + }; + const pubSub = { + close: jest.fn(), + topic: jest.fn(() => topic), + }; + const pubSubFactory = jest.fn(() => pubSub as unknown as PubSub); + + beforeEach(() => { + onEvent = undefined; + jest.clearAllMocks(); + }); + + it('should go though the expected registration and flows', async () => { + const publisher = new EventConsumingGooglePubSubPublisher({ + logger, + events, + tasks: [ + { + id: 'my-id', + sourceTopics: ['my-topic'], + targetTopicPattern: 'projects/my-project/topics/my-topic', + mapToTopic: () => ({ project: 'my-project', topic: 'my-topic' }), + mapToAttributes: m => ({ ...m.metadata, more: 'yes' }), + }, + ], + pubSubFactory, + }); + + // Start up + + await publisher.start(); + + expect(events.subscribe).toHaveBeenCalledWith({ + id: 'EventConsumingGooglePubSubPublisher.my-id', + topics: ['my-topic'], + onEvent: expect.any(Function), + }); + + // Publish successfully + + onEvent!({ + topic: 'my-topic', + eventPayload: { foo: 'bar' }, + metadata: { extra: 'data', more: 'yes' }, + }); + + await waitFor(() => { + expect(pubSubFactory).toHaveBeenCalledWith('my-project'); + expect(pubSub.topic).toHaveBeenCalledWith('my-topic'); + expect(topic.publishMessage).toHaveBeenCalledWith({ + json: { foo: 'bar' }, + attributes: { extra: 'data', more: 'yes' }, + }); + }); + + // Simulate a failed publish + + topic.publishMessage.mockRejectedValueOnce(new Error('Failed to publish')); + + await expect( + onEvent!({ + topic: 'my-topic', + eventPayload: { foo: 'bar' }, + metadata: { extra: 'data', more: 'yes' }, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Failed to publish"`); + + expect(logger.error).toHaveBeenCalledWith( + 'Error publishing Google Pub/Sub message', + new Error('Failed to publish'), + ); + + // Shut down + + await publisher.stop(); + expect(pubSub.close).toHaveBeenCalled(); + }); +}); diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.ts new file mode 100644 index 0000000000..4a88bc2e79 --- /dev/null +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/EventConsumingGooglePubSubPublisher.ts @@ -0,0 +1,153 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + LoggerService, + RootConfigService, + RootLifecycleService, +} from '@backstage/backend-plugin-api'; +import { EventsService } from '@backstage/plugin-events-node'; +import { PubSub } from '@google-cloud/pubsub'; +import { Counter, metrics } from '@opentelemetry/api'; +import { readSubscriptionTasksFromConfig } from './config'; +import { SubscriptionTask } from './types'; + +/** + * Reads messages off of the events system and forwards them into Google Pub/Sub + * topics. + */ +export class EventConsumingGooglePubSubPublisher { + readonly #logger: LoggerService; + readonly #events: EventsService; + readonly #tasks: SubscriptionTask[]; + readonly #pubSubFactory: (projectId: string) => PubSub; + readonly #metrics: { messages: Counter }; + #activeClientsByProjectId: Map; + + static create(options: { + config: RootConfigService; + logger: LoggerService; + rootLifecycle: RootLifecycleService; + events: EventsService; + }) { + const publisher = new EventConsumingGooglePubSubPublisher({ + logger: options.logger, + events: options.events, + tasks: readSubscriptionTasksFromConfig(options.config), + pubSubFactory: projectId => new PubSub({ projectId }), + }); + + options.rootLifecycle.addStartupHook(async () => { + await publisher.start(); + }); + + options.rootLifecycle.addBeforeShutdownHook(async () => { + await publisher.stop(); + }); + + return publisher; + } + + constructor(options: { + logger: LoggerService; + events: EventsService; + tasks: SubscriptionTask[]; + pubSubFactory: (projectId: string) => PubSub; + }) { + this.#logger = options.logger; + this.#events = options.events; + this.#tasks = options.tasks; + this.#pubSubFactory = options.pubSubFactory; + + const meter = metrics.getMeter('default'); + this.#metrics = { + messages: meter.createCounter( + 'events.google.pubsub.publisher.messages.total', + { + description: + 'Number of Pub/Sub messages sent by EventConsumingGooglePubSubPublisher', + unit: 'short', + }, + ), + }; + + this.#activeClientsByProjectId = new Map(); + } + + async start() { + for (const task of this.#tasks) { + this.#logger.info( + `Starting publisher: id=${ + task.id + } sourceTopics=${task.sourceTopics.join(',')} targetTopic=${ + task.targetTopicPattern + }`, + ); + + await this.#events.subscribe({ + id: `EventConsumingGooglePubSubPublisher.${task.id}`, + topics: task.sourceTopics, + onEvent: async event => { + let status: 'success' | 'failed' | 'ignored' = 'failed'; + try { + const topic = task.mapToTopic(event); + if (!topic) { + status = 'ignored'; + return; + } + + let pubsub = this.#activeClientsByProjectId.get(topic.project); + if (!pubsub) { + pubsub = this.#pubSubFactory(topic.project); + this.#activeClientsByProjectId.set(topic.project, pubsub); + } + + await pubsub.topic(topic.topic).publishMessage({ + json: event.eventPayload, + attributes: task.mapToAttributes(event), + }); + + status = 'success'; + } catch (error) { + this.#logger.error( + 'Error publishing Google Pub/Sub message', + error, + ); + status = 'failed'; + throw error; + } finally { + this.#metrics.messages.add(1, { + subscription: task.id, + status: status, + }); + } + }, + }); + } + } + + async stop() { + const clients = Array.from(this.#activeClientsByProjectId.values()); + this.#activeClientsByProjectId = new Map(); + + await Promise.allSettled( + clients.map(async client => { + this.#logger.info(`Closing Google Pub/Sub client: ${client.projectId}`); + await client.close(); + }), + ); + } +} diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.test.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.test.ts new file mode 100644 index 0000000000..c3a5c1a5bb --- /dev/null +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.test.ts @@ -0,0 +1,196 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { mockServices } from '@backstage/backend-test-utils'; +import { readSubscriptionTasksFromConfig } from './config'; + +describe('readSubscriptionTasksFromConfig', () => { + it('reads with basic targetTopic', () => { + const data = { + events: { + modules: { + googlePubSub: { + eventConsumingGooglePubSubPublisher: { + subscriptions: { + subKey1: { + sourceTopic: 'my-topic', + targetTopicName: 'projects/pid/topics/tid', + }, + subKey2: { + sourceTopic: ['my-topic-1', 'my-topic-2'], + targetTopicName: 'projects/pid/topics/tid.{{ event.topic }}', + }, + }, + }, + }, + }, + }, + }; + + const result = readSubscriptionTasksFromConfig( + mockServices.rootConfig({ data }), + ); + + expect(result).toEqual([ + { + id: 'subKey1', + sourceTopics: ['my-topic'], + targetTopicPattern: 'projects/pid/topics/tid', + mapToTopic: expect.any(Function), + mapToAttributes: expect.any(Function), + }, + { + id: 'subKey2', + sourceTopics: ['my-topic-1', 'my-topic-2'], + targetTopicPattern: 'projects/pid/topics/tid.{{ event.topic }}', + mapToTopic: expect.any(Function), + mapToAttributes: expect.any(Function), + }, + ]); + + expect( + result[0].mapToTopic({ + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { attr: 'yes' }, + }), + ).toEqual({ project: 'pid', topic: 'tid' }); + expect( + result[0].mapToAttributes({ + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { attr: 'yes' }, + }), + ).toEqual({ attr: 'yes' }); + }); + + it('fills in placeholders', () => { + const data = { + events: { + modules: { + googlePubSub: { + eventConsumingGooglePubSubPublisher: { + subscriptions: { + sub1: { + sourceTopic: 'my-topic', + targetTopicName: 'projects/pid/topics/tid.{{ event.topic }}', + messageAttributes: { + attr1: 'updated.{{ event.metadata.exists }}', + attr2: 'updated.{{ event.metadata.missing }}', + }, + }, + sub2: { + sourceTopic: 'my-topic', + targetTopicName: + 'projects/pid/topics/tid.{{ event.metadata.missing }}', + messageAttributes: { + attr3: 'new', + }, + }, + }, + }, + }, + }, + }, + }; + + const result = readSubscriptionTasksFromConfig( + mockServices.rootConfig({ data }), + ); + + expect(result).toEqual([ + { + id: 'sub1', + sourceTopics: ['my-topic'], + targetTopicPattern: 'projects/pid/topics/tid.{{ event.topic }}', + mapToTopic: expect.any(Function), + mapToAttributes: expect.any(Function), + }, + { + id: 'sub2', + sourceTopics: ['my-topic'], + targetTopicPattern: + 'projects/pid/topics/tid.{{ event.metadata.missing }}', + mapToTopic: expect.any(Function), + mapToAttributes: expect.any(Function), + }, + ]); + + expect( + result[0].mapToTopic({ + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { exists: 'exists', attr1: 'original1', attr2: 'original2' }, + }), + ).toEqual({ project: 'pid', topic: 'tid.a' }); // Message attribute existed, successfully routed + expect( + result[0].mapToAttributes({ + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { exists: 'exists', attr1: 'original1', attr2: 'original2' }, + }), + ).toEqual({ + exists: 'exists', + attr1: 'updated.exists', // message attribute existed, was replaced + attr2: 'original2', // message attribute did not exist, was not replaced + }); + + expect( + result[1].mapToTopic({ + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { exists: 'exists', attr1: 'original1', attr2: 'original2' }, + }), + ).toBeUndefined(); // Message attribute did not exist, could not be routed + expect( + result[1].mapToAttributes({ + topic: 'a', + eventPayload: { foo: 'bar' }, + metadata: { exists: 'exists', attr1: 'original1', attr2: 'original2' }, + }), + ).toEqual({ + exists: 'exists', + attr1: 'original1', + attr2: 'original2', + attr3: 'new', + }); + }); + + it('rejects malformed subscription name', () => { + const data = { + events: { + modules: { + googlePubSub: { + eventConsumingGooglePubSubPublisher: { + subscriptions: { + subKey: { + sourceTopic: 'sid', + targetTopicName: 'foo', + }, + }, + }, + }, + }, + }, + }; + + expect(() => + readSubscriptionTasksFromConfig(mockServices.rootConfig({ data })), + ).toThrowErrorMatchingInlineSnapshot( + `"Expected Google Pub/Sub 'targetTopicName' to be on the form 'projects/PROJECT_ID/topics/TOPIC_ID' but got 'foo'"`, + ); + }); +}); diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.ts new file mode 100644 index 0000000000..7ccd78cfcb --- /dev/null +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/config.ts @@ -0,0 +1,146 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RootConfigService } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { InputError } from '@backstage/errors'; +import { EventParams } from '@backstage/plugin-events-node'; +import { createPatternResolver } from '../util/createPatternResolver'; +import { SubscriptionTask } from './types'; + +export function readSubscriptionTasksFromConfig( + rootConfig: RootConfigService, +): SubscriptionTask[] { + const subscriptionsConfig = rootConfig.getOptionalConfig( + 'events.modules.googlePubSub.eventConsumingGooglePubSubPublisher.subscriptions', + ); + if (!subscriptionsConfig) { + return []; + } + + return subscriptionsConfig.keys().map(subscriptionId => { + if (!subscriptionId.match(/^[-_\w]+$/)) { + throw new InputError( + `Expected Google Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`, + ); + } + + const config = subscriptionsConfig.getConfig(subscriptionId); + const sourceTopics = readSourceTopics(config); + const mapToTopic = readTopicMapper(config); + const mapToAttributes = readAttributeMapper(config); + + return { + id: subscriptionId, + sourceTopics: sourceTopics, + targetTopicPattern: config.getString('targetTopicName'), + mapToTopic, + mapToAttributes, + }; + }); +} + +function readSourceTopics(config: Config): string[] { + if (Array.isArray(config.getOptional('sourceTopic'))) { + return config.getStringArray('sourceTopic'); + } + return [config.getString('sourceTopic')]; +} + +/** + * Handles the `targetTopicName` configuration field. + */ +function readTopicMapper( + config: Config, +): (event: EventParams) => { project: string; topic: string } | undefined { + const regex = /^projects\/([^/]+)\/topics\/(.+)$/; + + const targetTopicPattern = config.getString('targetTopicName'); + let parts = targetTopicPattern.match(regex); + if (!parts) { + throw new InputError( + `Expected Google Pub/Sub 'targetTopicName' to be on the form 'projects/PROJECT_ID/topics/TOPIC_ID' but got '${targetTopicPattern}'`, + ); + } + + const patternResolver = createPatternResolver(targetTopicPattern); + + return event => { + try { + parts = patternResolver({ event }).match(regex); + if (!parts) { + return undefined; + } + return { + project: parts[1], + topic: parts[2], + }; + } catch { + // could not map to a topic + return undefined; + } + }; +} + +/** + * Handles the `messageAttributes` configuration field. + */ +function readAttributeMapper( + config: Config, +): (event: EventParams) => Record { + const setters = new Array< + (options: { + event: EventParams; + attributes: Record; + }) => void + >(); + + const eventMetadata = config.getOptionalConfig('messageAttributes'); + if (eventMetadata) { + for (const key of eventMetadata?.keys() ?? []) { + const valuePattern = eventMetadata.getString(key); + const patternResolver = createPatternResolver(valuePattern); + setters.push(({ event, attributes }) => { + try { + const value = patternResolver({ event }); + if (value) { + attributes[key] = value; + } + } catch { + // ignore silently, keep original + } + }); + } + } + + return event => { + const result: Record = {}; + for (const [key, value] of Object.entries(event.metadata ?? {})) { + if (value) { + if (typeof value === 'string') { + result[key] = value; + } else if (Array.isArray(value) && value.length > 0) { + // Google Pub/Sub does not support array values + result[key] = value.join(','); + } + } + } + for (const setter of setters) { + setter({ event, attributes: result }); + } + return result; + }; +} diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/index.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/index.ts new file mode 100644 index 0000000000..3e5713a28d --- /dev/null +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { eventsModuleEventConsumingGooglePubSubPublisher } from './module'; diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/module.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/module.ts new file mode 100644 index 0000000000..498996fee0 --- /dev/null +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/module.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { eventsServiceRef } from '@backstage/plugin-events-node'; +import { EventConsumingGooglePubSubPublisher } from './EventConsumingGooglePubSubPublisher'; + +/** + * Reads messages off of the events system and forwards them into Google Pub/Sub + * topics. + * + * @public + */ +export const eventsModuleEventConsumingGooglePubSubPublisher = + createBackendModule({ + pluginId: 'events', + moduleId: 'event-consuming-google-pubsub-publisher', + register(reg) { + reg.registerInit({ + deps: { + config: coreServices.rootConfig, + logger: coreServices.logger, + rootLifecycle: coreServices.rootLifecycle, + events: eventsServiceRef, + }, + async init({ config, logger, rootLifecycle, events }) { + EventConsumingGooglePubSubPublisher.create({ + config, + logger, + rootLifecycle, + events, + }); + }, + }); + }, + }); diff --git a/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/types.ts b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/types.ts new file mode 100644 index 0000000000..36b3aeaa78 --- /dev/null +++ b/plugins/events-backend-module-google-pubsub/src/EventConsumingGooglePubSubPublisher/types.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { EventParams } from '@backstage/plugin-events-node'; + +/** + * A configured subscription task. + */ +export interface SubscriptionTask { + id: string; + sourceTopics: string[]; + targetTopicPattern: string; + mapToTopic: ( + event: EventParams, + ) => { project: string; topic: string } | undefined; + mapToAttributes: (event: EventParams) => Record; +} diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.test.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.test.ts index bfe3c20773..b4034123f0 100644 --- a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.test.ts +++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.test.ts @@ -188,7 +188,7 @@ describe('readSubscriptionTasksFromConfig', () => { expect(() => readSubscriptionTasksFromConfig(mockServices.rootConfig({ data })), ).toThrowErrorMatchingInlineSnapshot( - `"Expected Googoe Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got 'sid'"`, + `"Expected Google Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got 'sid'"`, ); }); }); diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.ts index fdcab2e2e5..d8dfb2ada2 100644 --- a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.ts +++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.ts @@ -34,7 +34,7 @@ export function readSubscriptionTasksFromConfig( return subscriptionsConfig.keys().map(subscriptionId => { if (!subscriptionId.match(/^[-_\w]+$/)) { throw new InputError( - `Expected Googoe Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`, + `Expected Google Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`, ); } @@ -63,7 +63,7 @@ function readSubscriptionName(config: Config): { ); if (!parts) { throw new InputError( - `Expected Googoe Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got '${subscriptionName}'`, + `Expected Google Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got '${subscriptionName}'`, ); } return { diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/module.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/module.ts index 14cfdd2bd8..3d5fce14ee 100644 --- a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/module.ts +++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/module.ts @@ -24,8 +24,6 @@ import { GooglePubSubConsumingEventPublisher } from './GooglePubSubConsumingEven /** * Reads messages off of Google Pub/Sub subscriptions and forwards them into the * Backstage events system. - * - * @public */ export const eventsModuleGooglePubsubConsumingEventPublisher = createBackendModule({ diff --git a/plugins/events-backend-module-google-pubsub/src/index.ts b/plugins/events-backend-module-google-pubsub/src/index.ts index 2b79fcb9a1..0dcbe3c5d8 100644 --- a/plugins/events-backend-module-google-pubsub/src/index.ts +++ b/plugins/events-backend-module-google-pubsub/src/index.ts @@ -14,10 +14,19 @@ * limitations under the License. */ +import { createBackendFeatureLoader } from '@backstage/backend-plugin-api'; +import { eventsModuleEventConsumingGooglePubSubPublisher } from './EventConsumingGooglePubSubPublisher'; +import { eventsModuleGooglePubsubConsumingEventPublisher } from './GooglePubSubConsumingEventPublisher'; + /** * The google-pubsub backend module for the events plugin. * * @packageDocumentation */ -export { eventsModuleGooglePubsubConsumingEventPublisher as default } from './GooglePubSubConsumingEventPublisher'; +export default createBackendFeatureLoader({ + *loader() { + yield eventsModuleGooglePubsubConsumingEventPublisher; + yield eventsModuleEventConsumingGooglePubSubPublisher; + }, +}); From 289e4a1630936069f790030680e41f312fb917d9 Mon Sep 17 00:00:00 2001 From: Adam Letizia Date: Mon, 9 Jun 2025 09:21:57 -0500 Subject: [PATCH 028/301] fix(Scaffolder): filter MultiEntityPicker options based on rendered option values Signed-off-by: Adam Letizia --- .changeset/empty-mugs-agree.md | 5 ++ .../MultiEntityPicker.test.tsx | 77 +++++++++++++++++++ .../MultiEntityPicker/MultiEntityPicker.tsx | 6 ++ 3 files changed, 88 insertions(+) create mode 100644 .changeset/empty-mugs-agree.md diff --git a/.changeset/empty-mugs-agree.md b/.changeset/empty-mugs-agree.md new file mode 100644 index 0000000000..80dd35b8cf --- /dev/null +++ b/.changeset/empty-mugs-agree.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Filter MultiEntityPicker options based on rendered option value diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx index acae3418b9..a02d793a34 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx @@ -947,4 +947,81 @@ describe('', () => { expect(getByText(description.fromUiSchema)).toBeInTheDocument(); }); }); + + describe('entity presentation', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + defaultKind: 'Group', + }, + }; + props = { + onChange, + schema, + required, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + }); + + it('renders and filters selection displayName', async () => { + catalogApi.getEntities.mockResolvedValue({ + items: entities.map(item => ({ + ...item, + spec: { + profile: { displayName: item.metadata.name.replace('-', ' ') }, + }, + })), + }); + + const { getByRole, getByText } = await renderInTestApp( + + + , + ); + + const input = getByRole('textbox'); + + fireEvent.change(input, { target: { value: 'team a' } }); + + expect(getByText('team a')).toBeInTheDocument(); + + fireEvent.change(input, { target: { value: 'squad b' } }); + + expect(getByText('squad b')).toBeInTheDocument(); + + fireEvent.blur(input); + }); + + it('renders and filters selection title', async () => { + catalogApi.getEntities.mockResolvedValue({ + items: entities.map(item => ({ + ...item, + metadata: { + ...item.metadata, + title: item.metadata.name.replace('-', ' ').toUpperCase(), + }, + })), + }); + + const { getByRole, getByText } = await renderInTestApp( + + + , + ); + + const input = getByRole('textbox'); + + fireEvent.change(input, { target: { value: 'team a' } }); + + expect(getByText('TEAM A')).toBeInTheDocument(); + + fireEvent.change(input, { target: { value: 'squad b' } }); + + expect(getByText('SQUAD B')).toBeInTheDocument(); + + fireEvent.blur(input); + }); + }); }); diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx index e7dcf0f996..57c4b87d6d 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx @@ -32,6 +32,7 @@ import { import TextField from '@material-ui/core/TextField'; import Autocomplete, { AutocompleteChangeReason, + createFilterOptions, } from '@material-ui/lab/Autocomplete'; import { useCallback, useEffect, useState } from 'react'; import useAsync from 'react-use/esm/useAsync'; @@ -203,6 +204,11 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { }} /> )} + filterOptions={createFilterOptions({ + stringify: option => + entities?.entityRefToPresentation.get(stringifyEntityRef(option)) + ?.primaryTitle!, + })} ListboxComponent={VirtualizedListbox} /> From c88b975a531a3b56714574e37b3f1b2831a12610 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 10 Jun 2025 07:13:15 +0000 Subject: [PATCH 029/301] chore(deps): update creyd/prettier_action action to v4.6 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/sync_code-formatting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index ee97f3fef3..18ec62c7bf 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -30,7 +30,7 @@ jobs: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} - name: Run Prettier on ADOPTERS.md - uses: creyD/prettier_action@31355f8eef017f8aeba2e0bc09d8502b13dbbad1 # v4.3 + uses: creyD/prettier_action@8c18391fdc98ed0d884c6345f03975edac71b8f0 # v4.6 with: # Modifies commit only if prettier autofixed the ADOPTERS.md prettier_options: --config docs/prettier.config.js --write ADOPTERS.md From c83cd8b4cb948ca1e935665c2bc5a072499796f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 10 Jun 2025 11:26:53 +0200 Subject: [PATCH 030/301] import fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/modern-buses-change.md | 13 +++++++++++++ .../src/schema/openapi/generated/router.ts | 2 +- .../components/Collapsible/Collapsible.stories.tsx | 2 +- packages/core-plugin-api/src/analytics/Tracker.ts | 2 +- .../package/schema/openapi/generate/server.ts | 2 +- .../report.api.md | 2 +- .../src/types.ts | 5 ++++- .../src/schema/openapi/generated/router.ts | 2 +- .../StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx | 2 +- .../src/schema/openapi/generated/router.ts | 2 +- plugins/home-react/src/overridableComponents.ts | 2 +- .../src/schema/openapi/generated/router.ts | 2 +- plugins/user-settings/src/index.ts | 2 +- 13 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 .changeset/modern-buses-change.md diff --git a/.changeset/modern-buses-change.md b/.changeset/modern-buses-change.md new file mode 100644 index 0000000000..b89efab093 --- /dev/null +++ b/.changeset/modern-buses-change.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-auth-backend-module-aws-alb-provider': patch +'@backstage/backend-dynamic-feature-service': patch +'@backstage/core-plugin-api': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-events-backend': patch +'@backstage/plugin-search-backend': patch +'@backstage/plugin-user-settings': patch +'@backstage/repo-tools': patch +'@backstage/plugin-home-react': patch +--- + +Fixed some circular or otherwise unclear imports diff --git a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/router.ts b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/router.ts index 9f1ef68a77..cf48bcfc95 100644 --- a/packages/backend-dynamic-feature-service/src/schema/openapi/generated/router.ts +++ b/packages/backend-dynamic-feature-service/src/schema/openapi/generated/router.ts @@ -18,7 +18,7 @@ // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** import { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage/backend-openapi-utils'; -import { EndpointMap } from './'; +import { EndpointMap } from './apis'; export const spec = { openapi: '3.0.3', diff --git a/packages/canon/src/components/Collapsible/Collapsible.stories.tsx b/packages/canon/src/components/Collapsible/Collapsible.stories.tsx index 175dadba85..8e6a394672 100644 --- a/packages/canon/src/components/Collapsible/Collapsible.stories.tsx +++ b/packages/canon/src/components/Collapsible/Collapsible.stories.tsx @@ -15,7 +15,7 @@ */ import type { Meta, StoryObj } from '@storybook/react'; -import { Collapsible } from './'; +import { Collapsible } from './Collapsible'; import { Button } from '../Button'; import { Box } from '../Box'; import { Text } from '../Text'; diff --git a/packages/core-plugin-api/src/analytics/Tracker.ts b/packages/core-plugin-api/src/analytics/Tracker.ts index 130410e2a2..4ae31b43fd 100644 --- a/packages/core-plugin-api/src/analytics/Tracker.ts +++ b/packages/core-plugin-api/src/analytics/Tracker.ts @@ -20,7 +20,7 @@ import { AnalyticsEventAttributes, AnalyticsTracker, } from '../apis'; -import { AnalyticsContextValue } from './'; +import { AnalyticsContextValue } from './types'; type TempGlobalEvents = { /** diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts index 1a8dd36cab..c51b63cf54 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts @@ -60,7 +60,7 @@ async function generateSpecFile() { // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** import {createValidatedOpenApiRouterFromGeneratedEndpointMap} from '@backstage/backend-openapi-utils'; -import {EndpointMap} from './'; +import {EndpointMap} from './apis'; export const spec = ${JSON.stringify(yaml, null, 2)} as const; export const createOpenApiRouter = async ( options?: Parameters['1'], diff --git a/plugins/auth-backend-module-aws-alb-provider/report.api.md b/plugins/auth-backend-module-aws-alb-provider/report.api.md index ebb3e8e24a..e809ae0fe3 100644 --- a/plugins/auth-backend-module-aws-alb-provider/report.api.md +++ b/plugins/auth-backend-module-aws-alb-provider/report.api.md @@ -6,7 +6,7 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; import { JWTHeaderParameters } from 'jose'; import { KeyObject } from 'crypto'; -import type { PassportProfile } from '@backstage/plugin-auth-node/'; +import type { PassportProfile } from '@backstage/plugin-auth-node'; import { ProxyAuthenticator } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; diff --git a/plugins/auth-backend-module-aws-alb-provider/src/types.ts b/plugins/auth-backend-module-aws-alb-provider/src/types.ts index b4c792a5f9..fd3b3dd80b 100644 --- a/plugins/auth-backend-module-aws-alb-provider/src/types.ts +++ b/plugins/auth-backend-module-aws-alb-provider/src/types.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import type { PassportProfile } from '@backstage/plugin-auth-node/'; +import type { PassportProfile } from '@backstage/plugin-auth-node'; + /** * JWT header extraction result, containing the raw value and the parsed JWT * payload. @@ -26,6 +27,7 @@ export type AwsAlbResult = { expiresInSeconds?: number; accessToken: string; }; + /** * @public */ @@ -39,6 +41,7 @@ export type AwsAlbClaims = { exp: number; iss: string; }; + /** * @internal */ diff --git a/plugins/catalog-backend/src/schema/openapi/generated/router.ts b/plugins/catalog-backend/src/schema/openapi/generated/router.ts index 67eb1a699e..d32ef0a4de 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/router.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/router.ts @@ -18,7 +18,7 @@ // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** import { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage/backend-openapi-utils'; -import { EndpointMap } from './'; +import { EndpointMap } from './apis'; export const spec = { openapi: '3.0.3', diff --git a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx index ed6fb5683f..e351acb28a 100644 --- a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx +++ b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx @@ -19,8 +19,8 @@ import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { act, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ReactNode } from 'react'; -import { AnalyzeResult, catalogImportApiRef } from '../../api/'; import { StepInitAnalyzeUrl } from './StepInitAnalyzeUrl'; +import { AnalyzeResult, catalogImportApiRef } from '../../api/CatalogImportApi'; describe('', () => { const catalogImportApi: jest.Mocked = { diff --git a/plugins/events-backend/src/schema/openapi/generated/router.ts b/plugins/events-backend/src/schema/openapi/generated/router.ts index 3c43880170..d2cedd2166 100644 --- a/plugins/events-backend/src/schema/openapi/generated/router.ts +++ b/plugins/events-backend/src/schema/openapi/generated/router.ts @@ -18,7 +18,7 @@ // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** import { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage/backend-openapi-utils'; -import { EndpointMap } from './'; +import { EndpointMap } from './apis'; export const spec = { openapi: '3.0.3', diff --git a/plugins/home-react/src/overridableComponents.ts b/plugins/home-react/src/overridableComponents.ts index 8a88fd5c94..3653257507 100644 --- a/plugins/home-react/src/overridableComponents.ts +++ b/plugins/home-react/src/overridableComponents.ts @@ -16,7 +16,7 @@ import { Overrides } from '@material-ui/core/styles/overrides'; import { StyleRules } from '@material-ui/core/styles/withStyles'; -import { PluginHomeContentModalClassKey } from './'; +import { PluginHomeContentModalClassKey } from './components/ContentModal'; /** @public */ export type PluginHomeComponentsNameToClassKey = { diff --git a/plugins/search-backend/src/schema/openapi/generated/router.ts b/plugins/search-backend/src/schema/openapi/generated/router.ts index cf972d9f6c..a2510346ca 100644 --- a/plugins/search-backend/src/schema/openapi/generated/router.ts +++ b/plugins/search-backend/src/schema/openapi/generated/router.ts @@ -18,7 +18,7 @@ // * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. * // ****************************************************************** import { createValidatedOpenApiRouterFromGeneratedEndpointMap } from '@backstage/backend-openapi-utils'; -import { EndpointMap } from './'; +import { EndpointMap } from './apis'; export const spec = { openapi: '3.0.3', diff --git a/plugins/user-settings/src/index.ts b/plugins/user-settings/src/index.ts index bc878ecbb3..e64d3c926c 100644 --- a/plugins/user-settings/src/index.ts +++ b/plugins/user-settings/src/index.ts @@ -26,4 +26,4 @@ export { userSettingsPlugin as plugin, UserSettingsPage, } from './plugin'; -export * from './components/'; +export * from './components'; From 96901c6be06209f8c202f3d3bf3f1a07aa6aaa76 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jun 2025 14:27:45 +0000 Subject: [PATCH 031/301] chore(deps): bump webpack-dev-server from 5.2.0 to 5.2.2 Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 5.2.0 to 5.2.2. - [Release notes](https://github.com/webpack/webpack-dev-server/releases) - [Changelog](https://github.com/webpack/webpack-dev-server/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack/webpack-dev-server/compare/v5.2.0...v5.2.2) --- updated-dependencies: - dependency-name: webpack-dev-server dependency-version: 5.2.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5180f46992..142e3c8b2e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -32304,7 +32304,7 @@ __metadata: languageName: node linkType: hard -"http-proxy-middleware@npm:^2.0.0, http-proxy-middleware@npm:^2.0.6, http-proxy-middleware@npm:^2.0.7": +"http-proxy-middleware@npm:^2.0.0, http-proxy-middleware@npm:^2.0.6, http-proxy-middleware@npm:^2.0.7, http-proxy-middleware@npm:^2.0.9": version: 2.0.9 resolution: "http-proxy-middleware@npm:2.0.9" dependencies: @@ -48318,8 +48318,8 @@ __metadata: linkType: hard "webpack-dev-server@npm:^5.0.0": - version: 5.2.1 - resolution: "webpack-dev-server@npm:5.2.1" + version: 5.2.2 + resolution: "webpack-dev-server@npm:5.2.2" dependencies: "@types/bonjour": "npm:^3.5.13" "@types/connect-history-api-fallback": "npm:^1.5.4" @@ -48337,7 +48337,7 @@ __metadata: connect-history-api-fallback: "npm:^2.0.0" express: "npm:^4.21.2" graceful-fs: "npm:^4.2.6" - http-proxy-middleware: "npm:^2.0.7" + http-proxy-middleware: "npm:^2.0.9" ipaddr.js: "npm:^2.1.0" launch-editor: "npm:^2.6.1" open: "npm:^10.0.3" @@ -48358,7 +48358,7 @@ __metadata: optional: true bin: webpack-dev-server: bin/webpack-dev-server.js - checksum: 10/424edfe22b7bbe2301a38b8b519dfeb7643e0ca643be01af3fa48ec18512955c1952246741d7577bdb911ee09dcd6c521ade7d65e0059448ee69ab02bfac4624 + checksum: 10/59517409cd38c01a875a03b9658f3d20d492b5b8bead9ded4a0f3d33e6857daf2d352fe89f0181dcaea6d0fbe84b0494cb4750a87120fe81cdbb3c32b499451c languageName: node linkType: hard From f1f21b8cd76937367a0b4df7c0a074458b16c018 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Tue, 10 Jun 2025 11:18:21 -0400 Subject: [PATCH 032/301] fix: loosen regex requirements for config keys Signed-off-by: aramissennyeydd --- packages/config-loader/src/sources/EnvConfigSource.ts | 2 +- packages/config/src/reader.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/config-loader/src/sources/EnvConfigSource.ts b/packages/config-loader/src/sources/EnvConfigSource.ts index 5b4590e107..88d7d8e772 100644 --- a/packages/config-loader/src/sources/EnvConfigSource.ts +++ b/packages/config-loader/src/sources/EnvConfigSource.ts @@ -85,7 +85,7 @@ export class EnvConfigSource implements ConfigSource { const ENV_PREFIX = 'APP_CONFIG_'; // Update the same pattern in config package if this is changed -const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i; +const CONFIG_KEY_PART_PATTERN = /^[\w-_]+$/i; /** * Read runtime configuration from the environment. diff --git a/packages/config/src/reader.ts b/packages/config/src/reader.ts index 52c33f8618..d865ba8172 100644 --- a/packages/config/src/reader.ts +++ b/packages/config/src/reader.ts @@ -18,7 +18,7 @@ import { JsonValue, JsonObject } from '@backstage/types'; import { AppConfig, Config } from './types'; // Update the same pattern in config-loader package if this is changed -const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i; +const CONFIG_KEY_PART_PATTERN = /^[\w-_]+$/i; function isObject(value: JsonValue | undefined): value is JsonObject { return typeof value === 'object' && value !== null && !Array.isArray(value); From ff2361826d9addef4a123ef978b1a48132138060 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Tue, 10 Jun 2025 11:19:12 -0400 Subject: [PATCH 033/301] add changeset Signed-off-by: aramissennyeydd --- .changeset/dry-hornets-stop.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/dry-hornets-stop.md diff --git a/.changeset/dry-hornets-stop.md b/.changeset/dry-hornets-stop.md new file mode 100644 index 0000000000..4979d463a8 --- /dev/null +++ b/.changeset/dry-hornets-stop.md @@ -0,0 +1,6 @@ +--- +'@backstage/config-loader': patch +'@backstage/config': patch +--- + +Loosen the requirements for a key to be considered valid config. From 6115bb4a0c2b1470eb1eb198ce6f34d298502784 Mon Sep 17 00:00:00 2001 From: Vinnie McGuinness Date: Tue, 10 Jun 2025 16:20:42 +0100 Subject: [PATCH 034/301] chore: add integrations gitea discovery to docs sidebar Signed-off-by: Vinnie McGuinness --- docs/integrations/gitea/discovery.md | 2 +- microsite/sidebars.ts | 5 ++++- mkdocs.yml | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/integrations/gitea/discovery.md b/docs/integrations/gitea/discovery.md index 6cae169b58..ce8ad9d360 100644 --- a/docs/integrations/gitea/discovery.md +++ b/docs/integrations/gitea/discovery.md @@ -30,7 +30,7 @@ backend.add(import('@backstage/plugin-catalog-backend-module-gitea')); ## Configuration -To use the discovery processor, you'll need a Gitea integration +To use the discovery provider, you'll need a Gitea integration [set up](locations.md). Then you can add any number of providers. ```yaml diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts index 1f8d3defb7..0ca6ddde70 100644 --- a/microsite/sidebars.ts +++ b/microsite/sidebars.ts @@ -330,7 +330,10 @@ export default { { type: 'category', label: 'Gitea', - items: ['integrations/gitea/locations'], + items: [ + 'integrations/gitea/locations', + 'integrations/gitea/discovery', + ], }, { type: 'category', diff --git a/mkdocs.yml b/mkdocs.yml index 688ca1a078..eea5fc9c40 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -126,6 +126,9 @@ nav: - GitLab: - Locations: 'integrations/gitlab/locations.md' - Discovery: 'integrations/gitlab/discovery.md' + - Gitea: + - Locations: 'integrations/gitea/locations.md' + - Discovery: 'integrations/gitea/discovery.md' - Google GCS: - Locations: 'integrations/google-cloud-storage/locations.md' - LDAP: From b3a705f3cd1749b6b7e80397a79c2155976834a4 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Tue, 10 Jun 2025 11:53:15 -0400 Subject: [PATCH 035/301] allow x-2 in config Signed-off-by: aramissennyeydd --- packages/config-loader/src/sources/EnvConfigSource.ts | 2 +- packages/config/src/reader.test.ts | 1 + packages/config/src/reader.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/config-loader/src/sources/EnvConfigSource.ts b/packages/config-loader/src/sources/EnvConfigSource.ts index 88d7d8e772..d724f00c6d 100644 --- a/packages/config-loader/src/sources/EnvConfigSource.ts +++ b/packages/config-loader/src/sources/EnvConfigSource.ts @@ -85,7 +85,7 @@ export class EnvConfigSource implements ConfigSource { const ENV_PREFIX = 'APP_CONFIG_'; // Update the same pattern in config package if this is changed -const CONFIG_KEY_PART_PATTERN = /^[\w-_]+$/i; +const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/i; /** * Read runtime configuration from the environment. diff --git a/packages/config/src/reader.test.ts b/packages/config/src/reader.test.ts index 97982ac265..3f6b9ac40a 100644 --- a/packages/config/src/reader.test.ts +++ b/packages/config/src/reader.test.ts @@ -171,6 +171,7 @@ describe('ConfigReader', () => { expect(config.getOptionalString('x_x')).toBeUndefined(); expect(config.getOptionalString('x-X')).toBeUndefined(); expect(config.getOptionalString('x0')).toBeUndefined(); + expect(config.getOptionalString('x-2')).toBeUndefined(); expect(config.getOptionalString('X-x2')).toBeUndefined(); expect(config.getOptionalString('x0_x0')).toBeUndefined(); expect(config.getOptionalString('x_x-x_x')).toBeUndefined(); diff --git a/packages/config/src/reader.ts b/packages/config/src/reader.ts index d865ba8172..2e83de9bf2 100644 --- a/packages/config/src/reader.ts +++ b/packages/config/src/reader.ts @@ -18,7 +18,7 @@ import { JsonValue, JsonObject } from '@backstage/types'; import { AppConfig, Config } from './types'; // Update the same pattern in config-loader package if this is changed -const CONFIG_KEY_PART_PATTERN = /^[\w-_]+$/i; +const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/i; function isObject(value: JsonValue | undefined): value is JsonObject { return typeof value === 'object' && value !== null && !Array.isArray(value); From 2666b82c99469dcfdb7e24849c9bc34d02090585 Mon Sep 17 00:00:00 2001 From: EstoesMoises <118999648+EstoesMoises@users.noreply.github.com> Date: Tue, 10 Jun 2025 18:33:36 +0100 Subject: [PATCH 036/301] Create stackoverflow-teams Request to add the Stack Overflow for Teams plugin that I created for Stack Overflow. This is part of the public Stack Exchange repository. Signed-off-by: EstoesMoises <118999648+EstoesMoises@users.noreply.github.com> --- microsite/data/plugins/stackoverflow-teams | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 microsite/data/plugins/stackoverflow-teams diff --git a/microsite/data/plugins/stackoverflow-teams b/microsite/data/plugins/stackoverflow-teams new file mode 100644 index 0000000000..cfe08757d3 --- /dev/null +++ b/microsite/data/plugins/stackoverflow-teams @@ -0,0 +1,10 @@ +--- +title: Stack Overflow for Teams +author: EstoesMoises +authorUrl: https://github.com/estoesmoises/ +category: Discovery +description: Provide seamless access to Stack Overflow Teams most relevant data, allowing you to display the top users, top tags, and top questions directly within Backstage. It also allows to securely create SO Teams questions from Backstage. +documentation: https://github.com/StackExchange/backstage-stackoverflow/blob/main/README.md +iconUrl: /img/stack-overflow-logo.svg +npmPackageName: 'backstage-plugin-stack-overflow-teams' +addedDate: '2025-06-10' From 092372a7a0af08499bc0081a8c7ddb81257ae429 Mon Sep 17 00:00:00 2001 From: Vinnie McGuinness Date: Wed, 11 Jun 2025 06:21:19 +0100 Subject: [PATCH 037/301] Run prettier son sidebars.ts file Signed-off-by: Vinnie McGuinness --- microsite/sidebars.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts index 0ca6ddde70..4bf0da08a8 100644 --- a/microsite/sidebars.ts +++ b/microsite/sidebars.ts @@ -330,10 +330,7 @@ export default { { type: 'category', label: 'Gitea', - items: [ - 'integrations/gitea/locations', - 'integrations/gitea/discovery', - ], + items: ['integrations/gitea/locations', 'integrations/gitea/discovery'], }, { type: 'category', From d0a40af133809d39fa29953c3dd6d8250f28f536 Mon Sep 17 00:00:00 2001 From: mario ma Date: Wed, 11 Jun 2025 14:40:47 +0800 Subject: [PATCH 038/301] fix: export translationRef from alpha Signed-off-by: mario ma --- plugins/home-react/package.json | 5 +++++ plugins/home-react/report-alpha.api.md | 19 +++++++++++++++++++ plugins/home-react/report.api.md | 11 ----------- plugins/home-react/src/alpha.ts | 16 ++++++++++++++++ plugins/home-react/src/index.ts | 1 - plugins/home-react/src/translation.ts | 2 +- 6 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 plugins/home-react/report-alpha.api.md create mode 100644 plugins/home-react/src/alpha.ts diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 022760da04..8e5767b340 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -27,6 +27,11 @@ }, "license": "Apache-2.0", "sideEffects": false, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, "main": "src/index.ts", "types": "src/index.ts", "files": [ diff --git a/plugins/home-react/report-alpha.api.md b/plugins/home-react/report-alpha.api.md new file mode 100644 index 0000000000..fad4c5a2bc --- /dev/null +++ b/plugins/home-react/report-alpha.api.md @@ -0,0 +1,19 @@ +## API Report File for "@backstage/plugin-home-react" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { TranslationRef } from '@backstage/frontend-plugin-api'; + +// @alpha (undocumented) +export const homeReactTranslationRef: TranslationRef< + 'home-react', + { + readonly 'settingsModal.title': 'Settings'; + readonly 'settingsModal.closeButtonTitle': 'Close'; + readonly 'cardExtension.settingsButtonTitle': 'Settings'; + } +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/home-react/report.api.md b/plugins/home-react/report.api.md index 369841f192..26ef6267aa 100644 --- a/plugins/home-react/report.api.md +++ b/plugins/home-react/report.api.md @@ -9,7 +9,6 @@ import { JSX as JSX_3 } from 'react'; import { Overrides } from '@material-ui/core/styles/overrides'; import { RJSFSchema } from '@rjsf/utils'; import { StyleRules } from '@material-ui/core/styles/withStyles'; -import { TranslationRef } from '@backstage/frontend-plugin-api'; import { UiSchema } from '@rjsf/utils'; // @public (undocumented) @@ -82,16 +81,6 @@ export function createCardExtension(options: { settings?: CardSettings; }): Extension<(props: CardExtensionProps) => JSX_2.Element>; -// @public (undocumented) -export const homeReactTranslationRef: TranslationRef< - 'home-react', - { - readonly 'settingsModal.title': 'Settings'; - readonly 'settingsModal.closeButtonTitle': 'Close'; - readonly 'cardExtension.settingsButtonTitle': 'Settings'; - } ->; - // @public (undocumented) export type PluginHomeComponentsNameToClassKey = { PluginHomeContentModal: PluginHomeContentModalClassKey; diff --git a/plugins/home-react/src/alpha.ts b/plugins/home-react/src/alpha.ts new file mode 100644 index 0000000000..3a08699f66 --- /dev/null +++ b/plugins/home-react/src/alpha.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { homeReactTranslationRef } from './translation'; diff --git a/plugins/home-react/src/index.ts b/plugins/home-react/src/index.ts index f24d85b2b8..54602dc814 100644 --- a/plugins/home-react/src/index.ts +++ b/plugins/home-react/src/index.ts @@ -31,4 +31,3 @@ export type { CardConfig, } from './extensions'; export * from './overridableComponents'; -export { homeReactTranslationRef } from './translation'; diff --git a/plugins/home-react/src/translation.ts b/plugins/home-react/src/translation.ts index 02e51644bc..8de5bb1ff1 100644 --- a/plugins/home-react/src/translation.ts +++ b/plugins/home-react/src/translation.ts @@ -16,7 +16,7 @@ import { createTranslationRef } from '@backstage/frontend-plugin-api'; /** - * @public + * @alpha */ export const homeReactTranslationRef = createTranslationRef({ id: 'home-react', From 666c734f03b38fa8a66023d72e546d31d36bd868 Mon Sep 17 00:00:00 2001 From: mario ma Date: Wed, 11 Jun 2025 15:01:27 +0800 Subject: [PATCH 039/301] fix: missing repo Signed-off-by: mario ma --- plugins/home-react/package.json | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 8e5767b340..4e2dae3931 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -11,9 +11,7 @@ ] }, "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" }, "keywords": [ "backstage", @@ -34,6 +32,16 @@ }, "main": "src/index.ts", "types": "src/index.ts", + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "package.json": [ + "package.json" + ] + } + }, "files": [ "dist" ], From ef9ab8267e3ba7c91bfd2e50b3726544dcb9461b Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Wed, 11 Jun 2025 12:21:20 +0300 Subject: [PATCH 040/301] fix: force broadcast notification user field to be null always Signed-off-by: Hellgren Heikki --- .changeset/two-waves-worry.md | 5 +++++ .../src/database/DatabaseNotificationsStore.test.ts | 11 +++++++++-- .../src/database/DatabaseNotificationsStore.ts | 13 ++++++++----- 3 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 .changeset/two-waves-worry.md diff --git a/.changeset/two-waves-worry.md b/.changeset/two-waves-worry.md new file mode 100644 index 0000000000..519a3a426b --- /dev/null +++ b/.changeset/two-waves-worry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend': patch +--- + +Notifications API will now return user as null always for broadcast notifications diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts index c7819552cb..4a69c9fd0b 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts @@ -228,6 +228,13 @@ describe.each(databases.eachSupportedId())( ]); }); + it('should return null value for user for broadcast notifications', async () => { + await storage.saveBroadcast({ ...testNotification1, read: new Date() }); + const notifications = await storage.getNotifications({ user }); + expect(notifications).toHaveLength(1); + expect(notifications[0].user).toBeNull(); + }); + it('should return read notifications for user', async () => { await storage.saveNotification(testNotification1); await storage.saveBroadcast(testNotification2); @@ -298,7 +305,7 @@ describe.each(databases.eachSupportedId())( user: otherUser, }); expect(otherUserNotifications.map(idOnly)).toEqual([id0, id2]); - expect(otherUserNotifications[1].user).toBe(otherUser); + expect(otherUserNotifications[1].user).toBeNull(); }); it('should allow searching for notifications', async () => { @@ -635,7 +642,7 @@ describe.each(databases.eachSupportedId())( expect(notification?.user).toBeNull(); await storage.markRead({ ids: [id1], user }); notification = await storage.getNotification({ id: id1, user }); - expect(notification?.user).toBe(user); + expect(notification?.user).toBeNull(); const otherNotification = await storage.getNotification({ id: id1, diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts index 35ecf1d778..f761081438 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts @@ -149,7 +149,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { private mapToNotifications = (rows: any[]): Notification[] => { return rows.map(row => ({ id: row.id, - user: row.user, + user: row.type === 'broadcast' ? null : row.user, created: new Date(row.created), saved: row.saved, read: row.read, @@ -252,7 +252,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { join.andOnVal('user', '=', user); } }) - .select(NOTIFICATION_COLUMNS); + .select([...NOTIFICATION_COLUMNS, this.db.raw("'broadcast' as type")]); }; private getNotificationsBaseQuery = ( @@ -261,7 +261,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { const { user, orderField } = options; const subQuery = this.db('notification') - .select(NOTIFICATION_COLUMNS) + .select([...NOTIFICATION_COLUMNS, this.db.raw("'entity' as type")]) .unionAll([this.getBroadcastUnion(user)]) .as('notifications'); @@ -331,7 +331,10 @@ export class DatabaseNotificationsStore implements NotificationsStore { async getNotifications(options: NotificationGetOptions) { const notificationQuery = this.getNotificationsBaseQuery(options); - const notifications = await notificationQuery.select(NOTIFICATION_COLUMNS); + const notifications = await notificationQuery.select([ + ...NOTIFICATION_COLUMNS, + 'type', + ]); return this.mapToNotifications(notifications); } @@ -462,7 +465,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { .select('*') .from( this.db('notification') - .select(NOTIFICATION_COLUMNS) + .select([...NOTIFICATION_COLUMNS, this.db.raw("'entity' as type")]) .unionAll([this.getBroadcastUnion(options.user)]) .as('notifications'), ) From 09e165498bc1b132da48979a7928b41f4b7cf932 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 11 Jun 2025 10:58:14 +0100 Subject: [PATCH 041/301] First pass moving docs to Nextjs 15 Signed-off-by: Charles de Dreuille --- canon-docs/package.json | 20 +- canon-docs/public/header.png | Bin 129816 -> 13783 bytes canon-docs/src/app/(docs)/changelog/page.mdx | 13 + .../src/app/(docs)/components/[slug]/page.tsx | 25 + .../src/app/(docs)/components/avatar/page.mdx | 75 - .../src/app/(docs)/components/avatar/props.ts | 19 - .../src/app/(docs)/components/box/props.ts | 24 - .../src/app/(docs)/components/button/page.mdx | 150 - .../src/app/(docs)/components/button/props.ts | 25 - .../app/(docs)/components/checkbox/page.mdx | 66 - .../(docs)/components/collapsible/page.mdx | 113 - .../(docs)/components/collapsible/props.ts | 43 - .../app/(docs)/components/container/props.ts | 12 - .../src/app/(docs)/components/flex/page.mdx | 83 - .../src/app/(docs)/components/flex/props.ts | 27 - .../src/app/(docs)/components/grid/props.ts | 59 - .../app/(docs)/components/heading/page.mdx | 102 - .../app/(docs)/components/heading/props.ts | 21 - .../(docs)/components/icon-button/page.mdx | 97 - .../(docs)/components/icon-button/props.ts | 20 - .../src/app/(docs)/components/icon/page.mdx | 44 - .../src/app/(docs)/components/link/page.mdx | 84 - .../src/app/(docs)/components/link/props.ts | 22 - .../src/app/(docs)/components/menu/page.mdx | 202 -- .../src/app/(docs)/components/select/page.mdx | 128 - .../src/app/(docs)/components/select/props.ts | 54 - .../src/app/(docs)/components/tabs/page.mdx | 92 - .../app/(docs)/components/text-field/page.mdx | 80 - .../app/(docs)/components/text-field/props.ts | 27 - .../src/app/(docs)/components/text/page.mdx | 139 - .../src/app/(docs)/components/text/props.ts | 26 - canon-docs/src/app/(docs)/page.mdx | 4 - canon-docs/src/app/(docs)/releases/page.mdx | 80 - canon-docs/src/components/Banner/Banner.tsx | 15 - canon-docs/src/components/Banner/index.ts | 15 - canon-docs/src/components/Changelog/index.tsx | 43 + canon-docs/src/components/Chip/Chip.tsx | 16 - canon-docs/src/components/Chip/index.ts | 15 - .../src/components/Chip/styles.module.css | 18 +- .../src/components/CodeBlock/client.tsx | 5 +- canon-docs/src/components/Columns/Columns.tsx | 16 - canon-docs/src/components/Columns/index.ts | 15 - canon-docs/src/components/Columns/styles.css | 16 - .../ComponentInfos/ComponentInfos.tsx | 68 + .../src/components/ComponentInfos/index.ts | 1 + .../src/components/ComponentInfos/types.ts | 9 + .../components/CustomTheme/customTheme.tsx | 2 +- .../components/CustomTheme/styles.module.css | 4 +- canon-docs/src/components/Frame/index.tsx | 1 - .../src/components/HeadlessBanners/BaseUI.tsx | 2 +- .../components/IconLibrary/IconLibrary.tsx | 19 +- .../src/components/IconLibrary/index.ts | 15 - .../components/IconLibrary/styles.module.css | 6 +- .../LayoutComponents.module.css | 28 +- .../LayoutComponents/LayoutComponents.tsx | 10 - .../src/components/LayoutComponents/index.ts | 15 - .../components/LayoutComponents/svgs/box.tsx | 17 +- .../LayoutComponents/svgs/container.tsx | 18 +- .../components/LayoutComponents/svgs/flex.tsx | 35 +- .../components/LayoutComponents/svgs/grid.tsx | 30 +- .../LayoutComponents/svgs/inline.tsx | 90 - .../src/components/PropsTable/PropsTable.tsx | 8 +- canon-docs/src/components/PropsTable/index.ts | 15 - canon-docs/src/components/Roadmap/Roadmap.tsx | 15 - canon-docs/src/components/Roadmap/index.ts | 15 - canon-docs/src/components/Roadmap/list.ts | 15 - .../src/components/Sidebar/Sidebar.module.css | 15 +- .../src/components/Sidebar/playground.tsx | 2 +- canon-docs/src/components/Snippet/client.tsx | 51 + canon-docs/src/components/Snippet/index.tsx | 32 +- .../src/components/Snippet/styles.module.css | 2 +- canon-docs/src/components/Table/index.ts | 15 - canon-docs/src/components/Tabs/parts.tsx | 2 +- .../src/components/Toolbar/nav.module.css | 4 +- .../src/components/Toolbar/styles.module.css | 1 + .../components/Toolbar/theme-name.module.css | 10 +- .../src/components/Toolbar/theme.module.css | 12 +- canon-docs/src/content/components/avatar.mdx | 65 + .../src/content/components/avatar.props.ts | 46 + .../page.mdx => content/components/box.mdx} | 37 +- .../src/content/components/box.props.ts | 40 + canon-docs/src/content/components/button.mdx | 104 + .../src/content/components/button.props.ts | 63 + .../src/content/components/checkbox.mdx | 46 + .../components/checkbox.props.ts} | 20 +- .../src/content/components/collapsible.mdx | 71 + .../content/components/collapsible.props.ts | 86 + .../components/container.mdx} | 46 +- .../src/content/components/container.props.ts | 32 + canon-docs/src/content/components/flex.mdx | 62 + .../src/content/components/flex.props.ts | 55 + .../page.mdx => content/components/grid.mdx} | 95 +- .../src/content/components/grid.props.ts | 111 + canon-docs/src/content/components/heading.mdx | 66 + .../src/content/components/heading.props.ts | 48 + .../src/content/components/icon-button.mdx | 79 + .../content/components/icon-button.props.ts | 46 + canon-docs/src/content/components/icon.mdx | 31 + .../components/icon.props.ts} | 13 +- canon-docs/src/content/components/link.mdx | 71 + .../src/content/components/link.props.ts | 43 + canon-docs/src/content/components/menu.mdx | 105 + .../components/menu.props.ts} | 102 +- canon-docs/src/content/components/select.mdx | 79 + .../src/content/components/select.props.ts | 108 + .../page.mdx => content/components/table.mdx} | 0 canon-docs/src/content/components/tabs.mdx | 61 + .../components/tabs.props.ts} | 28 +- .../src/content/components/text-field.mdx | 69 + .../content/components/text-field.props.ts | 43 + canon-docs/src/content/components/text.mdx | 76 + .../src/content/components/text.props.ts | 82 + canon-docs/src/css/globals.css | 10 +- canon-docs/src/mdx-components.tsx | 95 +- canon-docs/src/snippets/code-snippets.ts | 50 - canon-docs/src/utils/changelog.ts | 376 +++ canon-docs/src/utils/data.ts | 4 +- canon-docs/tsconfig.json | 3 +- canon-docs/yarn.lock | 2499 ++++++++++------- packages/canon/src/css/stories.module.css | 16 + 120 files changed, 4303 insertions(+), 3613 deletions(-) create mode 100644 canon-docs/src/app/(docs)/changelog/page.mdx create mode 100644 canon-docs/src/app/(docs)/components/[slug]/page.tsx delete mode 100644 canon-docs/src/app/(docs)/components/avatar/page.mdx delete mode 100644 canon-docs/src/app/(docs)/components/avatar/props.ts delete mode 100644 canon-docs/src/app/(docs)/components/box/props.ts delete mode 100644 canon-docs/src/app/(docs)/components/button/page.mdx delete mode 100644 canon-docs/src/app/(docs)/components/button/props.ts delete mode 100644 canon-docs/src/app/(docs)/components/checkbox/page.mdx delete mode 100644 canon-docs/src/app/(docs)/components/collapsible/page.mdx delete mode 100644 canon-docs/src/app/(docs)/components/collapsible/props.ts delete mode 100644 canon-docs/src/app/(docs)/components/container/props.ts delete mode 100644 canon-docs/src/app/(docs)/components/flex/page.mdx delete mode 100644 canon-docs/src/app/(docs)/components/flex/props.ts delete mode 100644 canon-docs/src/app/(docs)/components/grid/props.ts delete mode 100644 canon-docs/src/app/(docs)/components/heading/page.mdx delete mode 100644 canon-docs/src/app/(docs)/components/heading/props.ts delete mode 100644 canon-docs/src/app/(docs)/components/icon-button/page.mdx delete mode 100644 canon-docs/src/app/(docs)/components/icon-button/props.ts delete mode 100644 canon-docs/src/app/(docs)/components/icon/page.mdx delete mode 100644 canon-docs/src/app/(docs)/components/link/page.mdx delete mode 100644 canon-docs/src/app/(docs)/components/link/props.ts delete mode 100644 canon-docs/src/app/(docs)/components/menu/page.mdx delete mode 100644 canon-docs/src/app/(docs)/components/select/page.mdx delete mode 100644 canon-docs/src/app/(docs)/components/select/props.ts delete mode 100644 canon-docs/src/app/(docs)/components/tabs/page.mdx delete mode 100644 canon-docs/src/app/(docs)/components/text-field/page.mdx delete mode 100644 canon-docs/src/app/(docs)/components/text-field/props.ts delete mode 100644 canon-docs/src/app/(docs)/components/text/page.mdx delete mode 100644 canon-docs/src/app/(docs)/components/text/props.ts delete mode 100644 canon-docs/src/app/(docs)/releases/page.mdx create mode 100644 canon-docs/src/components/Changelog/index.tsx create mode 100644 canon-docs/src/components/ComponentInfos/ComponentInfos.tsx create mode 100644 canon-docs/src/components/ComponentInfos/index.ts create mode 100644 canon-docs/src/components/ComponentInfos/types.ts delete mode 100644 canon-docs/src/components/LayoutComponents/svgs/inline.tsx create mode 100644 canon-docs/src/components/Snippet/client.tsx create mode 100644 canon-docs/src/content/components/avatar.mdx create mode 100644 canon-docs/src/content/components/avatar.props.ts rename canon-docs/src/{app/(docs)/components/box/page.mdx => content/components/box.mdx} (61%) create mode 100644 canon-docs/src/content/components/box.props.ts create mode 100644 canon-docs/src/content/components/button.mdx create mode 100644 canon-docs/src/content/components/button.props.ts create mode 100644 canon-docs/src/content/components/checkbox.mdx rename canon-docs/src/{app/(docs)/components/checkbox/props.ts => content/components/checkbox.props.ts} (62%) create mode 100644 canon-docs/src/content/components/collapsible.mdx create mode 100644 canon-docs/src/content/components/collapsible.props.ts rename canon-docs/src/{app/(docs)/components/container/page.mdx => content/components/container.mdx} (50%) create mode 100644 canon-docs/src/content/components/container.props.ts create mode 100644 canon-docs/src/content/components/flex.mdx create mode 100644 canon-docs/src/content/components/flex.props.ts rename canon-docs/src/{app/(docs)/components/grid/page.mdx => content/components/grid.mdx} (53%) create mode 100644 canon-docs/src/content/components/grid.props.ts create mode 100644 canon-docs/src/content/components/heading.mdx create mode 100644 canon-docs/src/content/components/heading.props.ts create mode 100644 canon-docs/src/content/components/icon-button.mdx create mode 100644 canon-docs/src/content/components/icon-button.props.ts create mode 100644 canon-docs/src/content/components/icon.mdx rename canon-docs/src/{app/(docs)/components/icon/props.ts => content/components/icon.props.ts} (50%) create mode 100644 canon-docs/src/content/components/link.mdx create mode 100644 canon-docs/src/content/components/link.props.ts create mode 100644 canon-docs/src/content/components/menu.mdx rename canon-docs/src/{app/(docs)/components/menu/props.ts => content/components/menu.props.ts} (51%) create mode 100644 canon-docs/src/content/components/select.mdx create mode 100644 canon-docs/src/content/components/select.props.ts rename canon-docs/src/{app/(docs)/components/table/page.mdx => content/components/table.mdx} (100%) create mode 100644 canon-docs/src/content/components/tabs.mdx rename canon-docs/src/{app/(docs)/components/tabs/props.ts => content/components/tabs.props.ts} (58%) create mode 100644 canon-docs/src/content/components/text-field.mdx create mode 100644 canon-docs/src/content/components/text-field.props.ts create mode 100644 canon-docs/src/content/components/text.mdx create mode 100644 canon-docs/src/content/components/text.props.ts create mode 100644 canon-docs/src/utils/changelog.ts create mode 100644 packages/canon/src/css/stories.module.css diff --git a/canon-docs/package.json b/canon-docs/package.json index 5a4affc45b..05d46df217 100644 --- a/canon-docs/package.json +++ b/canon-docs/package.json @@ -8,19 +8,25 @@ "lint": "next lint", "start": "npm run build:css && next dev" }, + "resolutions": { + "@types/react": "19.1.7", + "@types/react-dom": "19.1.6" + }, "dependencies": { "@codemirror/lang-sass": "^6.0.2", "@lezer/highlight": "^1.2.1", "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", - "@next/mdx": "^15.1.4", + "@next/mdx": "15.3.3", "@storybook/react": "^8.6.8", "@uiw/codemirror-themes": "^4.23.7", "@uiw/react-codemirror": "^4.23.7", + "html-react-parser": "^5.2.5", "motion": "^12.4.1", - "next": "14.2.26", - "react": "^18.0.2", - "react-dom": "^18.0.2", + "next": "15.3.3", + "next-mdx-remote-client": "^2.1.2", + "react": "19.1.0", + "react-dom": "19.1.0", "react-frame-component": "^5.2.7", "shiki": "^1.26.1", "storybook": "^8.6.8" @@ -28,10 +34,10 @@ "devDependencies": { "@types/mdx": "^2.0.13", "@types/node": "^20", - "@types/react": "^18.0.0", - "@types/react-dom": "^18.0.0", + "@types/react": "19.1.7", + "@types/react-dom": "19.1.6", "eslint": "^8", - "eslint-config-next": "14.2.23", + "eslint-config-next": "15.3.3", "lightningcss": "^1.28.2", "typescript": "^5" } diff --git a/canon-docs/public/header.png b/canon-docs/public/header.png index 507bd05ca7c825bea07ab1adc27f95cffec1e6ae..8d11151a3ecdd91c681afecfbf41c45ce8179b0c 100644 GIT binary patch literal 13783 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 OKLr_8>1s){kpBZ + +## Version 0.1.0 + +We're excited to share the initial release of Canon 💚 In this first alpha version, +you'll find the foundation of our design system: a set of versatile layout components +and a handful of essential atomic elements to help you get started. While Canon is +still in its early stages, it's ready for exploration and we'd love for you to give +it a try and share your feedback. diff --git a/canon-docs/src/app/(docs)/components/[slug]/page.tsx b/canon-docs/src/app/(docs)/components/[slug]/page.tsx new file mode 100644 index 0000000000..d6f98c8f93 --- /dev/null +++ b/canon-docs/src/app/(docs)/components/[slug]/page.tsx @@ -0,0 +1,25 @@ +import { components, layoutComponents } from '@/utils/data'; + +export default async function Page({ + params, +}: { + params: Promise<{ slug: string }>; +}) { + const { slug } = await params; + + const { default: Component } = await import( + `@/content/components/${slug}.mdx` + ); + + return ; +} + +export function generateStaticParams() { + const list = [...components, ...layoutComponents]; + + return list.map(component => ({ + slug: component.slug, + })); +} + +export const dynamicParams = false; diff --git a/canon-docs/src/app/(docs)/components/avatar/page.mdx b/canon-docs/src/app/(docs)/components/avatar/page.mdx deleted file mode 100644 index b46caf8d62..0000000000 --- a/canon-docs/src/app/(docs)/components/avatar/page.mdx +++ /dev/null @@ -1,75 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { AvatarSnippet } from '@/snippets/stories-snippets'; -import { avatarPropDefs } from './props'; - -# Avatar - -An avatar component with a fallback for initials. - -} - code={``} -/> - - - - Usage - Theming - - - `} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - - `canon-AvatarRoot` - - `canon-AvatarRoot[data-size='small']` - - `canon-AvatarRoot[data-size='medium']` - - `canon-AvatarRoot[data-size='large']` - - `canon-AvatarImage` - - `canon-AvatarFallback` - - - -## API reference - - - -## Examples - -### Sizes - -Avatar sizes can be set using the `size` prop. - -} - code={` - - - - `} -/> - -### Fallback - -If the image is not available, the avatar will show the initials of the name. - -} - code={``} -/> diff --git a/canon-docs/src/app/(docs)/components/avatar/props.ts b/canon-docs/src/app/(docs)/components/avatar/props.ts deleted file mode 100644 index e86c67a41e..0000000000 --- a/canon-docs/src/app/(docs)/components/avatar/props.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; -import type { PropDef } from '@/utils/propDefs'; - -export const avatarPropDefs: Record = { - src: { - type: 'string', - }, - name: { - type: 'string', - }, - size: { - type: 'enum', - values: ['small', 'medium', 'large'], - default: 'medium', - responsive: true, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/box/props.ts b/canon-docs/src/app/(docs)/components/box/props.ts deleted file mode 100644 index f3e38984dd..0000000000 --- a/canon-docs/src/app/(docs)/components/box/props.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - classNamePropDefs, - displayPropDefs, - heightPropDefs, - positionPropDefs, - stylePropDefs, - widthPropDefs, -} from '../../../../utils/propDefs'; -import 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/canon-docs/src/app/(docs)/components/button/page.mdx b/canon-docs/src/app/(docs)/components/button/page.mdx deleted file mode 100644 index 3554ee39f9..0000000000 --- a/canon-docs/src/app/(docs)/components/button/page.mdx +++ /dev/null @@ -1,150 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { ButtonSnippet } from '@/snippets/stories-snippets'; -import { buttonVariants } from '@/snippets/code-snippets'; -import { buttonPropDefs } from './props'; - -# Button - -A button component that can be used to trigger actions. - -} - code={` - - - -`} -/> - - - - Usage - Theming - - - -`} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - `} - /> - - - - -## API reference - - - -## Examples - -### Variants - -Here's a view when buttons have different variants. - -} - code={buttonVariants} -/> - -### Sizes - -Here's a view when buttons have different sizes. - -} - code={` - - -`} -/> - -### With Icons - -Here's a view when buttons have icons. - -} - code={` - - - -`} -/> - -### Full width - -Here's a view when buttons are full width. - -} - code={` - -`} -/> - -### Disabled - -Here's a view when buttons are disabled. - -} - code={``} -/> - -### As Link - -Here's a view when buttons are rendered as a link. - -} - code={``} -/> - -### Responsive - -Here's a view when buttons are responsive. - - - Responsive Button - `} -/> diff --git a/canon-docs/src/app/(docs)/components/button/props.ts b/canon-docs/src/app/(docs)/components/button/props.ts deleted file mode 100644 index 36b0f73066..0000000000 --- a/canon-docs/src/app/(docs)/components/button/props.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { classNamePropDefs, stylePropDefs } from '../../../../utils/propDefs'; -import type { PropDef } from '../../../../utils/propDefs'; - -export const buttonPropDefs: Record = { - variant: { - type: 'enum', - values: ['primary', 'secondary'], - default: 'primary', - responsive: true, - }, - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'medium', - responsive: true, - }, - render: { - type: 'enum', - values: ['ReactNode'], - responsive: false, - default: ' - )} /> - - - It's the edge of the world and all of Western civilization - The sun may rise in the East, at least it settled in a final location - It's understood that Hollywood sells Californication - - -`} -/> - - - - Usage - Theming - - - - ( - - )} /> - Your content -`} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - - `canon-CollapsibleRoot` - - `canon-CollapsibleTrigger` - - `canon-CollapsiblePanel` - - - -## API reference - -### Collapsible.Root - -Groups all parts of the collapsible. Renders a `
` element. - - - -### Collapsible.Trigger - -The trigger by default render a simple unstyled button. Because menus can be rendered in different ways, we recommend -using the `render` prop to render a custom trigger. - - - )} /> - - - It's the edge of the world and all of Western civilization - The sun may rise in the East, at least it settled in a final location - It's understood that Hollywood sells Californication - - -`} -/> diff --git a/canon-docs/src/app/(docs)/components/collapsible/props.ts b/canon-docs/src/app/(docs)/components/collapsible/props.ts deleted file mode 100644 index 02c62dc89e..0000000000 --- a/canon-docs/src/app/(docs)/components/collapsible/props.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - renderPropDefs, -} from '@/utils/propDefs'; -import type { PropDef } from '@/utils/propDefs'; - -export const collapsibleRootPropDefs: Record = { - defaultOpen: { - type: 'boolean', - default: 'false', - }, - open: { - type: 'boolean', - }, - onOpenChange: { - type: 'enum', - values: ['(open) => void'], - }, - ...renderPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const collapsibleTriggerPropDefs: Record = { - ...renderPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const collapsiblePanelPropDefs: Record = { - hiddenUntilFound: { - type: 'boolean', - default: 'false', - }, - keepMounted: { - type: 'boolean', - default: 'false', - }, - ...renderPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/container/props.ts b/canon-docs/src/app/(docs)/components/container/props.ts deleted file mode 100644 index 4a3410ec12..0000000000 --- a/canon-docs/src/app/(docs)/components/container/props.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - gapPropDefs, -} from '../../../../utils/propDefs'; -import type { PropDef } from '../../../../utils/propDefs'; - -export const containerPropDefs: Record = { - ...gapPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/flex/page.mdx b/canon-docs/src/app/(docs)/components/flex/page.mdx deleted file mode 100644 index 8150f3e3c5..0000000000 --- a/canon-docs/src/app/(docs)/components/flex/page.mdx +++ /dev/null @@ -1,83 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { - flexFAQ1, - flexSimple, - flexResponsive, - flexAlign, -} from '@/snippets/code-snippets'; -import { Snippet } from '@/components/Snippet'; -import { FlexSnippet } from '@/snippets/stories-snippets'; -import { flexPropDefs } from './props'; -import { spacingPropDefs } from '../../../../utils/propDefs'; - -# Flex - -This is the flex container component. It will help to define the number of -columns that will be used in the grid. You can also define the gap between the -columns. All values are responsive. - -} - code={` - - - -`} -/> - - - - Usage - - - -`} - /> - - - -## API reference - - - -The grid component also accepts all the spacing props from the Box component. - - - -## Common questions - -### Can I stack horizontally? - -The Flex component only allows for stacking elements vertically. If you want -to create a column layout, please use the Grid component. - - - -## Examples - -### Simple - -A simple example of how to use the Flex component. - - - -### Responsive - -The Flex component also supports responsive values, making it easy to create -responsive designs. - - - -### Align - -The Flex component also supports responsive alignment, making it easy to -create responsive designs. - - diff --git a/canon-docs/src/app/(docs)/components/flex/props.ts b/canon-docs/src/app/(docs)/components/flex/props.ts deleted file mode 100644 index 2268a6909d..0000000000 --- a/canon-docs/src/app/(docs)/components/flex/props.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { - PropDef, - childrenPropDefs, - classNamePropDefs, - stylePropDefs, -} from '../../../../utils/propDefs'; - -export const flexPropDefs: Record = { - align: { - type: 'enum', - values: ['start', 'center', 'end', 'baseline', 'stretch'], - responsive: true, - }, - direction: { - type: 'enum', - values: ['row', 'column', 'row-reverse', 'column-reverse'], - responsive: true, - }, - justify: { - type: 'enum', - values: ['start', 'center', 'end', 'between'], - responsive: true, - }, - ...childrenPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/grid/props.ts b/canon-docs/src/app/(docs)/components/grid/props.ts deleted file mode 100644 index a9e8717fa1..0000000000 --- a/canon-docs/src/app/(docs)/components/grid/props.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - PropDef, - childrenPropDefs, - classNamePropDefs, - stylePropDefs, -} from '../../../../utils/propDefs'; - -const columnsValues = [ - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - '10', - '11', - '12', -]; - -export const gridPropDefs: Record = { - columns: { - type: 'enum | string', - values: [...columnsValues, 'auto'], - responsive: true, - default: 'auto', - }, - ...childrenPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const gridItemPropDefs: Record = { - colSpan: { - type: 'enum | string', - values: [...columnsValues, 'full'], - responsive: true, - }, - rowSpan: { - type: 'enum | string', - values: [...columnsValues, 'full'], - responsive: true, - }, - start: { - type: 'enum | string', - values: [...columnsValues, 'auto'], - responsive: true, - }, - end: { - type: 'enum | string', - values: [...columnsValues, 'auto'], - responsive: true, - }, - ...childrenPropDefs, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/heading/page.mdx b/canon-docs/src/app/(docs)/components/heading/page.mdx deleted file mode 100644 index a5eb488411..0000000000 --- a/canon-docs/src/app/(docs)/components/heading/page.mdx +++ /dev/null @@ -1,102 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { HeadingSnippet } from '@/snippets/stories-snippets'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { headingPropDefs } from './props'; - -# Heading - -Headings are used to structure the content of your page. - -} - code={`Hello World!`} -/> - - - - Usage - Theming - - - -`} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - `} - /> - - - - -## API reference - - - -## Examples - -### All variants - -The `Heading` component has a `variant` prop that can be used to change the -appearance of the heading. - -} - code={` - Display - Title 1 - Title 2 - Title 3 - Title 4 - Title 5 -`} -/> - -### Truncate - -The `Heading` component has a `truncate` prop that can be used to truncate the -heading. - -} - code={` - A man looks at a painting in a museum and says, “Brothers and sisters I - have none, but that man's father is my father's son.” Who is - in the painting? - `} -/> - -### Custom render - -You can also use the `render` prop to render the heading as a different element. - -} - code={`}>Custom render`} -/> - -### Responsive - -You can also use the `variant` prop to change the appearance of the text based -on the screen size. - - - Responsive heading - `} -/> diff --git a/canon-docs/src/app/(docs)/components/heading/props.ts b/canon-docs/src/app/(docs)/components/heading/props.ts deleted file mode 100644 index 6d62d2dee1..0000000000 --- a/canon-docs/src/app/(docs)/components/heading/props.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { classNamePropDefs, stylePropDefs } from '../../../../utils/propDefs'; -import type { PropDef } from '../../../../utils/propDefs'; - -export const headingPropDefs: Record = { - variant: { - type: 'enum', - values: ['display', 'title1', 'title2', 'title3', 'title4', 'title5'], - responsive: true, - }, - children: { - type: 'enum', - values: ['ReactNode'], - responsive: false, - }, - truncate: { - type: 'boolean', - default: 'false', - }, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/icon-button/page.mdx b/canon-docs/src/app/(docs)/components/icon-button/page.mdx deleted file mode 100644 index 60530ec9ea..0000000000 --- a/canon-docs/src/app/(docs)/components/icon-button/page.mdx +++ /dev/null @@ -1,97 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { IconButtonSnippet } from '@/snippets/stories-snippets'; -import { iconButtonVariants } from '@/snippets/code-snippets'; -import { iconButtonPropDefs } from './props'; - -# Icon Button - -A button component with a single icon that can be used to trigger actions. - -} - code={` - - - `} -/> - - - - Usage - Theming - - - -`} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - `} - /> - - - - -## API reference - - - -## Examples - -### Variants - -Here's a view when buttons have different variants. - -} - code={iconButtonVariants} -/> - -### Sizes - -Here's a view when buttons have different sizes. - -} - code={` - - -`} -/> - -### Disabled - -Here's a view when buttons are disabled. - -} - code={``} -/> - -### Responsive - -Here's a view when buttons are responsive. - -`} -/> diff --git a/canon-docs/src/app/(docs)/components/icon-button/props.ts b/canon-docs/src/app/(docs)/components/icon-button/props.ts deleted file mode 100644 index bcb1580247..0000000000 --- a/canon-docs/src/app/(docs)/components/icon-button/props.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { classNamePropDefs, stylePropDefs } from '../../../../utils/propDefs'; -import type { PropDef } from '../../../../utils/propDefs'; - -export const iconButtonPropDefs: Record = { - variant: { - type: 'enum', - values: ['primary', 'secondary'], - default: 'primary', - responsive: true, - }, - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'medium', - responsive: true, - }, - icon: { type: 'enum', values: 'icon', responsive: false }, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/icon/page.mdx b/canon-docs/src/app/(docs)/components/icon/page.mdx deleted file mode 100644 index 66be460d13..0000000000 --- a/canon-docs/src/app/(docs)/components/icon/page.mdx +++ /dev/null @@ -1,44 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { IconSnippet } from '@/snippets/stories-snippets'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { iconPropDefs } from './props'; - -# Icon - -Icons are used to represent an action or a state. - -} - code={``} -/> - - - - Usage - Theming - - - -`} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - `} - /> - - - - -## API reference - - diff --git a/canon-docs/src/app/(docs)/components/link/page.mdx b/canon-docs/src/app/(docs)/components/link/page.mdx deleted file mode 100644 index 64a3261590..0000000000 --- a/canon-docs/src/app/(docs)/components/link/page.mdx +++ /dev/null @@ -1,84 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { - MenuSnippet, - ButtonSnippet, - LinkSnippet, -} from '@/snippets/stories-snippets'; -import { linkPropDefs } from './props'; - -# Link - -A link component that renders a `` element. - -} - code={`Sign up for Backstage`} -/> - - - - Usage - Theming - - - Sign up for Backstage`} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - - `canon-Link` - - `canon-Link[data-variant='body']` - - `canon-Link[data-variant='subtitle']` - - `canon-Link[data-variant='caption']` - - `canon-Link[data-variant='label']` - - `canon-Link[data-weight='regular']` - - `canon-Link[data-weight='bold']` - - - -## API reference - - - -## Examples - -### Variants - -Here's a view when links have different variants. - -} - code={` - - - - - `} -/> - -### Weights - -Here's a view when links have different weights. - -} - code={` - - - `} -/> diff --git a/canon-docs/src/app/(docs)/components/link/props.ts b/canon-docs/src/app/(docs)/components/link/props.ts deleted file mode 100644 index 83507c5594..0000000000 --- a/canon-docs/src/app/(docs)/components/link/props.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs'; -import type { PropDef } from '@/utils/propDefs'; - -export const linkPropDefs: Record = { - to: { - type: 'string', - }, - variant: { - type: 'enum', - values: ['subtitle', 'body', 'caption', 'label'], - default: 'body', - responsive: true, - }, - weight: { - type: 'enum', - values: ['regular', 'bold'], - default: 'regular', - responsive: true, - }, - ...classNamePropDefs, - ...stylePropDefs, -}; diff --git a/canon-docs/src/app/(docs)/components/menu/page.mdx b/canon-docs/src/app/(docs)/components/menu/page.mdx deleted file mode 100644 index 524db26cd1..0000000000 --- a/canon-docs/src/app/(docs)/components/menu/page.mdx +++ /dev/null @@ -1,202 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { MenuSnippet } from '@/snippets/stories-snippets'; -import { BaseUI } from '@/components/HeadlessBanners/BaseUI'; -import { - menuRootPropDefs, - menuTriggerPropDefs, - menuPositionerPropDefs, - menuItemPropDefs, -} from './props'; - -# Menu - -A list of actions in a dropdown, enhanced with keyboard navigation. - -} - code={` - ( - - )} - /> - - - - Settings - Invite new members - Download app - Log out - - - - `} -/> - - - - Usage - Theming - - - - - - - - - - - - `} - /> - - - We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with - your brand. For additional flexibility, you can use the provided class names for each element listed below. - - `canon-MenuTrigger` - - `canon-MenuBackdrop` - - `canon-MenuPositioner` - - `canon-MenuPopup` - - `canon-MenuArrow` - - `canon-MenuItem` - - `canon-MenuGroup` - - `canon-MenuGroupLabel` - - `canon-MenuRadioGroup` - - `canon-MenuRadioItem` - - `canon-MenuRadioItemIndicator` - - `canon-MenuCheckboxItem` - - `canon-MenuCheckboxItemIndicator` - - `canon-MenuSubmenuTrigger` - - `canon-MenuSeparator` - - - -## API reference - - - -### Menu.Root - -Groups all parts of the menu. Doesn't render its own HTML element. - - - -### Menu.Trigger - -The trigger by default render a simple unstyled button. Because menus can be rendered in different ways, we recommend -using the `render` prop to render a custom trigger. - - - )} - /> - - - - Settings - Invite new members - Download app - Log out - - - - `} -/> - -### Nested navigation - -You can nest menus to create a more complex navigation structure. - -} - code={` ( - - )} - /> - - - - Settings - Invite new members - Download app - Log out - - Submenu - - - - Submenu Item 1 - Submenu Item 2 - Submenu Item 3 - - - - - - - `} -/> diff --git a/canon-docs/src/app/(docs)/components/select/page.mdx b/canon-docs/src/app/(docs)/components/select/page.mdx deleted file mode 100644 index 4399f62235..0000000000 --- a/canon-docs/src/app/(docs)/components/select/page.mdx +++ /dev/null @@ -1,128 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { Tabs } from '@/components/Tabs'; -import { CodeBlock } from '@/components/CodeBlock'; -import { SelectSnippet } from '@/snippets/stories-snippets'; -import { selectPropDefs } from './props'; - -# Select - -A common form component for choosing a predefined value in a dropdown menu. - -} - code={``} -/> - -### Sizes - -Here's a view when the selects have different sizes. - -} - code={` - - `} -/> - -### Disabled - -Here's a view when the select is disabled. - -} - code={``; + +export const selectDefaultSnippet = ``; + +export const selectSizesSnippet = ` + +`; + +export const selectDisabledSnippet = ``; diff --git a/canon-docs/src/app/(docs)/components/table/page.mdx b/canon-docs/src/content/components/table.mdx similarity index 100% rename from canon-docs/src/app/(docs)/components/table/page.mdx rename to canon-docs/src/content/components/table.mdx diff --git a/canon-docs/src/content/components/tabs.mdx b/canon-docs/src/content/components/tabs.mdx new file mode 100644 index 0000000000..85cfa34af2 --- /dev/null +++ b/canon-docs/src/content/components/tabs.mdx @@ -0,0 +1,61 @@ +import { PropsTable } from '@/components/PropsTable'; +import { TabsSnippet } from '@/snippets/stories-snippets'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { + tabsRootPropDefs, + tabsListPropDefs, + tabsTabPropDefs, + tabsPanelPropDefs, + tabsUsageSnippet, + tabsDefaultSnippet, +} from './tabs.props'; +import { ComponentInfos } from '@/components/ComponentInfos'; + +# Tabs + +A component for toggling between related panels on the same page. + +} + code={tabsDefaultSnippet} +/> + + + +## API reference + +### Tabs.Root + +Groups the tabs and the corresponding panels. Renders a `
` element. + + + +### Tabs.List + +Groups the individual tab buttons. Renders a `
` element. + + + +### Tabs.Tab + +An individual interactive tab button that toggles the corresponding panel. Renders a ` - - - -`; - -export const iconButtonVariants = ` - - - -`; - -export const flexFAQ1 = ` - Hello World - Hello World - Hello World -`; - -export const flexSimple = ` - Hello World - Hello World - Hello World -`; - -export const flexResponsive = ` - Hello World - Hello World - Hello World -`; - -export const flexAlign = ` - Hello World - Hello World - Hello World -`; diff --git a/canon-docs/src/utils/changelog.ts b/canon-docs/src/utils/changelog.ts new file mode 100644 index 0000000000..3eeac4fa15 --- /dev/null +++ b/canon-docs/src/utils/changelog.ts @@ -0,0 +1,376 @@ +export type Component = + | 'avatar' + | 'box' + | 'button' + | 'heading' + | 'text' + | 'icon-button' + | 'icon' + | 'tabs' + | 'menu' + | 'textfield' + | 'datatable' + | 'select' + | 'collapsible' + | 'checkbox' + | 'container' + | 'link' + | 'tooltip' + | 'scrollarea' + | 'flex'; + +export interface ChangelogProps { + components: Component[]; + description: string; + version: '0.1.0' | '0.2.0' | '0.3.0' | '0.4.0' | '0.5.0' | '0.6.0'; + platform: 'oss' | 'portal'; + prs: string[]; +} + +export const changelog: ChangelogProps[] = [ + { + components: ['tabs'], + version: '0.4.0', + description: '✨ Add new `Tabs` component', + platform: 'portal', + prs: ['29996'], + }, + { + components: ['text', 'heading'], + version: '0.4.0', + description: '✨ Add `truncate` prop to `Text` and `Heading`', + platform: 'portal', + prs: ['29988'], + }, + { + components: ['menu'], + version: '0.4.0', + description: '✨ Add combobox option to `Menu`', + platform: 'portal', + prs: ['29986'], + }, + { + components: ['textfield'], + version: '0.4.0', + description: '✨ Add icon prop on `TextField`', + platform: 'portal', + prs: ['29820'], + }, + { + components: ['button', 'icon-button'], + version: '0.4.0', + description: 'Improve icon props on `Button` and `IconButton`', + platform: 'portal', + prs: ['29667'], + }, + { + components: ['text', 'heading'], + version: '0.4.0', + description: + 'Improve the way we treat custom render on `Text` and `Heading`', + platform: 'portal', + prs: ['29989'], + }, + { + components: ['menu'], + version: '0.4.0', + description: 'Improve `Menu` styles', + platform: 'portal', + prs: ['29986'], + }, + { + components: ['textfield'], + version: '0.4.0', + description: 'Improve `TextField` styles', + platform: 'portal', + prs: ['29974'], + }, + { + components: ['textfield'], + version: '0.4.0', + description: 'Improve clear button on `TextField`', + platform: 'portal', + prs: ['29878'], + }, + { + components: ['flex'], + version: '0.4.0', + description: 'Fix spacing props on all layout components', + platform: 'portal', + prs: ['30013'], + }, + { + components: [], + version: '0.4.0', + description: 'Fix - Pin Base UI version', + platform: 'portal', + prs: ['29782'], + }, + { + components: ['select'], + version: '0.4.0', + description: 'Fix - Clicking `Select` label moves focus to trigger', + platform: 'portal', + prs: ['29755'], + }, + { + components: ['datatable'], + version: '0.4.0', + description: 'Fix `DataTable.Pagination` count issue', + platform: 'portal', + prs: ['29688'], + }, + { + components: ['datatable'], + version: '0.3.0', + description: 'Add `DataTable` component', + platform: 'portal', + prs: ['29484', '29603'], + }, + { + components: ['select'], + version: '0.3.0', + description: 'Add `Select` component', + platform: 'portal', + prs: ['29440'], + }, + { + components: ['avatar'], + version: '0.3.0', + description: 'Add `Avatar` component', + platform: 'portal', + prs: ['29594'], + }, + { + components: ['collapsible'], + version: '0.3.0', + description: 'Add `Collapsible` component', + platform: 'portal', + prs: ['29617'], + }, + { + components: ['textfield'], + version: '0.3.0', + description: 'Add `TextField` component instead of `Field` + `Input`', + platform: 'portal', + prs: ['29364'], + }, + { + components: ['datatable'], + version: '0.3.0', + description: 'Add `TableCellProfile`', + platform: 'portal', + prs: ['29600'], + }, + { + components: [], + version: '0.3.0', + description: 'Add breakpoint hooks - `up()` and `down()`', + platform: 'portal', + prs: ['29564'], + }, + { + components: [], + version: '0.3.0', + description: 'Add gray scale css tokens', + platform: 'portal', + prs: ['29543'], + }, + { + components: [], + version: '0.3.0', + description: + 'Update CSS styling API using `[data-___]` instead of class names for props', + platform: 'portal', + prs: ['29560'], + }, + { + components: ['checkbox'], + version: '0.3.0', + description: 'Update `Checkbox` dark mode', + platform: 'portal', + prs: ['29544'], + }, + { + components: ['container'], + version: '0.3.0', + description: 'Update `Container` styles', + platform: 'portal', + prs: ['29475'], + }, + { + components: ['menu'], + version: '0.3.0', + description: 'Update `Menu` styles', + platform: 'portal', + prs: ['29351'], + }, + { + components: ['select'], + version: '0.3.0', + description: 'Fix `Select` styles on small sizes + with long option names', + platform: 'portal', + prs: ['29545'], + }, + { + components: ['link'], + version: '0.3.0', + description: 'Fix render prop on `Link`', + platform: 'portal', + prs: ['29247'], + }, + { + components: ['textfield', 'select'], + version: '0.3.0', + description: 'Remove `Field` from `TextField` + `Select`', + platform: 'portal', + prs: ['29482'], + }, + { + components: ['text', 'heading'], + version: '0.3.0', + description: 'Update `textDecoration` to `none` on `Text` / `Heading`', + platform: 'portal', + prs: ['29357'], + }, + { + components: ['text', 'heading'], + version: '0.3.0', + description: 'Update `textDecoration` to `none` on `Text` / `Heading`', + platform: 'portal', + prs: ['29357'], + }, + { + components: [], + version: '0.3.0', + description: 'Docs - Use stories from Storybook for all examples in Nextjs', + platform: 'portal', + prs: ['29306'], + }, + { + components: [], + version: '0.3.0', + description: 'Docs - Add release page (this one 🤗)', + platform: 'portal', + prs: ['29461'], + }, + { + components: [], + version: '0.3.0', + description: 'Docs - Add docs for Menu, Link', + platform: 'portal', + prs: ['29576'], + }, + { + components: [], + version: '0.3.0', + description: 'Fix CSS watch mode', + platform: 'portal', + prs: ['29352'], + }, + { + components: ['menu'], + version: '0.2.0', + description: 'New `Menu` component', + platform: 'portal', + prs: ['29151'], + }, + { + components: ['icon-button'], + version: '0.2.0', + description: 'New `IconButton` component', + platform: 'portal', + prs: ['29239'], + }, + { + components: ['scrollarea'], + version: '0.2.0', + description: 'New `ScrollArea` component', + platform: 'portal', + prs: ['29240'], + }, + { + components: ['button', 'checkbox'], + version: '0.2.0', + description: 'Improve `Button` & `Checkbox` styles', + platform: 'portal', + prs: ['29127', '28789'], + }, + { + components: ['text'], + version: '0.2.0', + description: 'Improve `Text` styles', + platform: 'portal', + prs: ['29200'], + }, + { + components: ['icon'], + version: '0.2.0', + description: 'Renamed `CanonProvider` to `IconProvider`', + platform: 'portal', + prs: ['29002'], + }, + { + components: ['icon'], + version: '0.2.0', + description: 'Added about 40+ new icons', + platform: 'portal', + prs: ['29264'], + }, + { + components: [], + version: '0.2.0', + description: 'Simplified styling into a unique styles.css file', + platform: 'portal', + prs: ['29199'], + }, + { + components: [], + version: '0.2.0', + description: 'Added Canon styles to Backstage', + platform: 'portal', + prs: ['29137'], + }, + { + components: [], + version: '0.2.0', + description: 'Update global CSS tokens', + platform: 'portal', + prs: ['28804'], + }, + { + components: ['flex'], + version: '0.2.0', + description: 'Merge Stack + Inline into Flex', + platform: 'portal', + prs: ['28634'], + }, + { + components: ['button'], + version: '0.2.0', + description: 'Improve `Button` types', + platform: 'portal', + prs: ['29205'], + }, + { + components: [], + version: '0.2.0', + description: 'Move font weight and family back to each components', + platform: 'portal', + prs: ['28972'], + }, + { + components: [], + version: '0.2.0', + description: 'Fix custom values in spacing props', + platform: 'portal', + prs: ['28770'], + }, + { + components: [], + version: '0.2.0', + description: 'Multiple updates on the Canon Docs site', + platform: 'portal', + prs: ['28760'], + }, +]; diff --git a/canon-docs/src/utils/data.ts b/canon-docs/src/utils/data.ts index ba4fd5e432..58f44f764d 100644 --- a/canon-docs/src/utils/data.ts +++ b/canon-docs/src/utils/data.ts @@ -14,8 +14,8 @@ export const overview: Page[] = [ slug: 'about', }, { - title: 'Releases', - slug: 'releases', + title: 'Changelog', + slug: 'changelog', }, ]; diff --git a/canon-docs/tsconfig.json b/canon-docs/tsconfig.json index 2baa7cde8f..2f0e4d0742 100644 --- a/canon-docs/tsconfig.json +++ b/canon-docs/tsconfig.json @@ -19,7 +19,8 @@ ], "paths": { "@/*": ["./src/*"] - } + }, + "target": "ES2017" }, "include": [ "next-env.d.ts", diff --git a/canon-docs/yarn.lock b/canon-docs/yarn.lock index decb4e7efe..fde0c19836 100644 --- a/canon-docs/yarn.lock +++ b/canon-docs/yarn.lock @@ -5,36 +5,52 @@ __metadata: version: 8 cacheKey: 10 -"@babel/runtime@npm:^7.18.6": - version: 7.26.10 - resolution: "@babel/runtime@npm:7.26.10" +"@babel/code-frame@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/code-frame@npm:7.27.1" dependencies: - regenerator-runtime: "npm:^0.14.0" - checksum: 10/9d7ff8e96abe3791047c1138789c742411e3ef19c4d7ca18ce916f83cec92c06ec5dc64401759f6dd1e377cf8a01bbd2c62e033eb7550f435cf6579768d0d4a5 + "@babel/helper-validator-identifier": "npm:^7.27.1" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10/721b8a6e360a1fa0f1c9fe7351ae6c874828e119183688b533c477aa378f1010f37cc9afbfc4722c686d1f5cdd00da02eab4ba7278a0c504fa0d7a321dcd4fdf + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-identifier@npm:7.27.1" + checksum: 10/75041904d21bdc0cd3b07a8ac90b11d64cd3c881e89cb936fa80edd734bf23c35e6bd1312611e8574c4eab1f3af0f63e8a5894f4699e9cfdf70c06fcf4252320 + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.18.6": + version: 7.27.6 + resolution: "@babel/runtime@npm:7.27.6" + checksum: 10/cc957a12ba3781241b83d528eb69ddeb86ca5ac43179a825e83aa81263a6b3eb88c57bed8a937cdeacfc3192e07ec24c73acdfea4507d0c0428c8e23d6322bfe languageName: node linkType: hard "@codemirror/autocomplete@npm:^6.0.0": - version: 6.18.4 - resolution: "@codemirror/autocomplete@npm:6.18.4" + version: 6.18.6 + resolution: "@codemirror/autocomplete@npm:6.18.6" dependencies: "@codemirror/language": "npm:^6.0.0" "@codemirror/state": "npm:^6.0.0" "@codemirror/view": "npm:^6.17.0" "@lezer/common": "npm:^1.0.0" - checksum: 10/7cd62db2ee87d6cb3936ced4f211bdcd9ce75515f64922119370c303a8bdbf5640e640a8d802c30d01cb55fb6c9b5299426ff799f04cff0bef8d0c3450fe2528 + checksum: 10/0574d96fd04ccf2d3b7ae3c4efe0a72f423fa81658876ec50865ce3371cea038aeddf026976ec0d0ccbee72ac66bdf7deec9106dee251ad49019ae7e1a871663 languageName: node linkType: hard "@codemirror/commands@npm:^6.0.0, @codemirror/commands@npm:^6.1.0": - version: 6.8.0 - resolution: "@codemirror/commands@npm:6.8.0" + version: 6.8.1 + resolution: "@codemirror/commands@npm:6.8.1" dependencies: "@codemirror/language": "npm:^6.0.0" "@codemirror/state": "npm:^6.4.0" "@codemirror/view": "npm:^6.27.0" "@lezer/common": "npm:^1.1.0" - checksum: 10/0c7991736bc84d0a7f8e49c311c78a78def84fca36ff61c2f9887067e2c06b1b232711cb99282cd410527ddbe0671d2b9aff93e0848fa731a2014c3691c737ea + checksum: 10/72b6343777d4fe4af1bcc91331eb64705b33e871c9f8e2a0cd177269478a6f3282bf25aeaecdbb55fd5e031b67aabfede068e919fe69e473d07e5090dd76b381 languageName: node linkType: hard @@ -65,8 +81,8 @@ __metadata: linkType: hard "@codemirror/language@npm:^6.0.0": - version: 6.10.8 - resolution: "@codemirror/language@npm:6.10.8" + version: 6.11.1 + resolution: "@codemirror/language@npm:6.11.1" dependencies: "@codemirror/state": "npm:^6.0.0" "@codemirror/view": "npm:^6.23.0" @@ -74,38 +90,38 @@ __metadata: "@lezer/highlight": "npm:^1.0.0" "@lezer/lr": "npm:^1.0.0" style-mod: "npm:^4.0.0" - checksum: 10/63b83b41d9f8475f757144cc204df08834bb14411c484aa265ffa3e93b7d8f696a21110df72101159a8675eda29018c6d08f864965bd4651b607a39f10ad32ed + checksum: 10/024969113d61ccb5d497b75a8a9875d43e1bfc8466de2819bb5db23f01b200937366800a812e8d33eb5e34e3a5d2aa22d0ff8205f34a5e8aeb4cd1d221bcaa78 languageName: node linkType: hard "@codemirror/lint@npm:^6.0.0": - version: 6.8.4 - resolution: "@codemirror/lint@npm:6.8.4" + version: 6.8.5 + resolution: "@codemirror/lint@npm:6.8.5" dependencies: "@codemirror/state": "npm:^6.0.0" "@codemirror/view": "npm:^6.35.0" crelt: "npm:^1.0.5" - checksum: 10/401ead0591d88d31d1bf6527d4caba26e0deb7b49382dfbb8c712037d858047b0699fa2c15831a07db928194549eea9b942004fee42f334b34ff5973c7dbec58 + checksum: 10/9eddfea1dd0615431d57687c2a0d4de510d725aac6f6bbc2eba4bc934963371304dfa4c49398b3043372ef34cead3ebc0ec3f652632a87f5d8a458fa911a309a languageName: node linkType: hard "@codemirror/search@npm:^6.0.0": - version: 6.5.8 - resolution: "@codemirror/search@npm:6.5.8" + version: 6.5.11 + resolution: "@codemirror/search@npm:6.5.11" dependencies: "@codemirror/state": "npm:^6.0.0" "@codemirror/view": "npm:^6.0.0" crelt: "npm:^1.0.5" - checksum: 10/1389fa4e05da72b621ae10ff00d4ef0d23b08a3a7ac48f6e0b867429da7b80036bd7a6feef1a54f5b4c4af9fc4277f754a313a858fbfc12ccdfa48e9bf300cef + checksum: 10/d057f37cb369460b25625d7eb72f40636bf78ecd140608da53010cf3660f982a9e613826e38d85d87c9c2ff11e45c9482429987bfd4f29cbbd192f1ee3fd2695 languageName: node linkType: hard "@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.1.1, @codemirror/state@npm:^6.4.0, @codemirror/state@npm:^6.5.0": - version: 6.5.1 - resolution: "@codemirror/state@npm:6.5.1" + version: 6.5.2 + resolution: "@codemirror/state@npm:6.5.2" dependencies: "@marijn/find-cluster-break": "npm:^1.0.0" - checksum: 10/117f21ba2ff5bc9f9b946ca9943279a29bb9461ec77fc2313cfd8908ed6eb7df2950d7c5b0ba0a0a23c3e0633c6bba56f8c105f97875665e0376a9e110d10a48 + checksum: 10/5ccd3acb0c0a5b88e83fb91be39099fceb9f44a5047cc41a75d53f160e736851f65c8de40950b90c6519e6d2828e12f468db0af658dde30e938896f1c39eec91 languageName: node linkType: hard @@ -122,199 +138,228 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.35.0": - version: 6.36.2 - resolution: "@codemirror/view@npm:6.36.2" + version: 6.37.1 + resolution: "@codemirror/view@npm:6.37.1" dependencies: "@codemirror/state": "npm:^6.5.0" + crelt: "npm:^1.0.6" style-mod: "npm:^4.1.0" w3c-keyname: "npm:^2.2.4" - checksum: 10/9ef7fcf4f9d9b6e66645ae65da1bf0c90e08f6ba786de0373b9f3644632066b91b8ea20faf67bb81eb9adf310ae76888cc7fd0901e2bb4821193f5427455c137 + checksum: 10/04594b0bad98036c57ef0fb962b0e09186de404e26f1068fd98191edae51f2c5b476938d42239c8c7f0b70deead837f925a9d671b7cc8f9f336bdbb01630bbf9 languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/aix-ppc64@npm:0.25.0" +"@emnapi/core@npm:^1.4.3": + version: 1.4.3 + resolution: "@emnapi/core@npm:1.4.3" + dependencies: + "@emnapi/wasi-threads": "npm:1.0.2" + tslib: "npm:^2.4.0" + checksum: 10/b511f66b897d2019835391544fdf11f4fa0ce06cc1181abfa17c7d4cf03aaaa4fc8a64fcd30bb3f901de488d0a6f370b53a8de2215a898f5a4ac98015265b3b7 + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.4.3": + version: 1.4.3 + resolution: "@emnapi/runtime@npm:1.4.3" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/4f90852a1a5912982cc4e176b6420556971bcf6a85ee23e379e2455066d616219751367dcf43e6a6eaf41ea7e95ba9dc830665a52b5d979dfe074237d19578f8 + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.0.2": + version: 1.0.2 + resolution: "@emnapi/wasi-threads@npm:1.0.2" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/e82941776665eb958c2084728191d6b15a94383449975c4621b67a1c8217e1c0ec11056a693906c76863cb96f782f8be500510ecec6874e3f5da35a8e7968cfd + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/aix-ppc64@npm:0.25.5" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/android-arm64@npm:0.25.0" +"@esbuild/android-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/android-arm64@npm:0.25.5" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/android-arm@npm:0.25.0" +"@esbuild/android-arm@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/android-arm@npm:0.25.5" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/android-x64@npm:0.25.0" +"@esbuild/android-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/android-x64@npm:0.25.5" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/darwin-arm64@npm:0.25.0" +"@esbuild/darwin-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/darwin-arm64@npm:0.25.5" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/darwin-x64@npm:0.25.0" +"@esbuild/darwin-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/darwin-x64@npm:0.25.5" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/freebsd-arm64@npm:0.25.0" +"@esbuild/freebsd-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/freebsd-arm64@npm:0.25.5" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/freebsd-x64@npm:0.25.0" +"@esbuild/freebsd-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/freebsd-x64@npm:0.25.5" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-arm64@npm:0.25.0" +"@esbuild/linux-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-arm64@npm:0.25.5" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-arm@npm:0.25.0" +"@esbuild/linux-arm@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-arm@npm:0.25.5" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-ia32@npm:0.25.0" +"@esbuild/linux-ia32@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-ia32@npm:0.25.5" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-loong64@npm:0.25.0" +"@esbuild/linux-loong64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-loong64@npm:0.25.5" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-mips64el@npm:0.25.0" +"@esbuild/linux-mips64el@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-mips64el@npm:0.25.5" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-ppc64@npm:0.25.0" +"@esbuild/linux-ppc64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-ppc64@npm:0.25.5" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-riscv64@npm:0.25.0" +"@esbuild/linux-riscv64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-riscv64@npm:0.25.5" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-s390x@npm:0.25.0" +"@esbuild/linux-s390x@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-s390x@npm:0.25.5" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/linux-x64@npm:0.25.0" +"@esbuild/linux-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-x64@npm:0.25.5" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/netbsd-arm64@npm:0.25.0" +"@esbuild/netbsd-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/netbsd-arm64@npm:0.25.5" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/netbsd-x64@npm:0.25.0" +"@esbuild/netbsd-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/netbsd-x64@npm:0.25.5" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/openbsd-arm64@npm:0.25.0" +"@esbuild/openbsd-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/openbsd-arm64@npm:0.25.5" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/openbsd-x64@npm:0.25.0" +"@esbuild/openbsd-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/openbsd-x64@npm:0.25.5" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/sunos-x64@npm:0.25.0" +"@esbuild/sunos-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/sunos-x64@npm:0.25.5" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/win32-arm64@npm:0.25.0" +"@esbuild/win32-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/win32-arm64@npm:0.25.5" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/win32-ia32@npm:0.25.0" +"@esbuild/win32-ia32@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/win32-ia32@npm:0.25.5" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.0": - version: 0.25.0 - resolution: "@esbuild/win32-x64@npm:0.25.0" +"@esbuild/win32-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/win32-x64@npm:0.25.5" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": - version: 4.4.1 - resolution: "@eslint-community/eslint-utils@npm:4.4.1" +"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.7.0": + version: 4.7.0 + resolution: "@eslint-community/eslint-utils@npm:4.7.0" dependencies: eslint-visitor-keys: "npm:^3.4.3" peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10/ae92a11412674329b4bd38422518601ec9ceae28e251104d1cad83715da9d38e321f68c817c39b64e66d0af7d98df6f9a10ad2dc638911254b47fb8932df00ef + checksum: 10/43ed5d391526d9f5bbe452aef336389a473026fca92057cf97c576db11401ce9bcf8ef0bf72625bbaf6207ed8ba6bf0dcf4d7e809c24f08faa68a28533c491a7 languageName: node linkType: hard @@ -374,17 +419,192 @@ __metadata: languageName: node linkType: hard -"@isaacs/cliui@npm:^8.0.2": - version: 8.0.2 - resolution: "@isaacs/cliui@npm:8.0.2" +"@img/sharp-darwin-arm64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-darwin-arm64@npm:0.34.2" dependencies: - string-width: "npm:^5.1.2" - string-width-cjs: "npm:string-width@^4.2.0" - strip-ansi: "npm:^7.0.1" - strip-ansi-cjs: "npm:strip-ansi@^6.0.1" - wrap-ansi: "npm:^8.1.0" - wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" - checksum: 10/e9ed5fd27c3aec1095e3a16e0c0cf148d1fee55a38665c35f7b3f86a9b5d00d042ddaabc98e8a1cb7463b9378c15f22a94eb35e99469c201453eb8375191f243 + "@img/sharp-libvips-darwin-arm64": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-darwin-arm64": + optional: true + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-darwin-x64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-darwin-x64@npm:0.34.2" + dependencies: + "@img/sharp-libvips-darwin-x64": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-darwin-x64": + optional: true + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-arm64@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.1.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-x64@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.1.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm64@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.1.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-linux-arm@npm:1.1.0" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-ppc64@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-linux-ppc64@npm:1.1.0" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-s390x@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.1.0" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-x64@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-linux-x64@npm:1.1.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-arm64@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.1.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-x64@npm:1.1.0": + version: 1.1.0 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.1.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linux-arm64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-linux-arm64@npm:0.34.2" + dependencies: + "@img/sharp-libvips-linux-arm64": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-linux-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-arm@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-linux-arm@npm:0.34.2" + dependencies: + "@img/sharp-libvips-linux-arm": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-linux-arm": + optional: true + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-s390x@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-linux-s390x@npm:0.34.2" + dependencies: + "@img/sharp-libvips-linux-s390x": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-linux-s390x": + optional: true + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-x64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-linux-x64@npm:0.34.2" + dependencies: + "@img/sharp-libvips-linux-x64": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-linux-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-arm64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.2" + dependencies: + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-x64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-linuxmusl-x64@npm:0.34.2" + dependencies: + "@img/sharp-libvips-linuxmusl-x64": "npm:1.1.0" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-wasm32@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-wasm32@npm:0.34.2" + dependencies: + "@emnapi/runtime": "npm:^1.4.3" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@img/sharp-win32-arm64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-win32-arm64@npm:0.34.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-win32-ia32@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-win32-ia32@npm:0.34.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@img/sharp-win32-x64@npm:0.34.2": + version: 0.34.2 + resolution: "@img/sharp-win32-x64@npm:0.34.2" + conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -396,13 +616,13 @@ __metadata: linkType: hard "@lezer/css@npm:^1.1.7": - version: 1.1.9 - resolution: "@lezer/css@npm:1.1.9" + version: 1.2.1 + resolution: "@lezer/css@npm:1.2.1" dependencies: "@lezer/common": "npm:^1.2.0" "@lezer/highlight": "npm:^1.0.0" - "@lezer/lr": "npm:^1.0.0" - checksum: 10/bcc277bb6b806961ebfd03e98faf8e5870dd7da2eb565c43922ac378d926b49f479309b28cc824fd4e17baac2c8952966807d06fbe2a45d53e9a3f1518134729 + "@lezer/lr": "npm:^1.3.0" + checksum: 10/726ea0e0b4d10c0326cc62d562a63c0d74970dde2002fab8172681c63e3210ff39d0f629370ad498b70e503ab2e9f74dd342d7c9e402b98f315b556116c3551a languageName: node linkType: hard @@ -415,7 +635,7 @@ __metadata: languageName: node linkType: hard -"@lezer/lr@npm:^1.0.0": +"@lezer/lr@npm:^1.0.0, @lezer/lr@npm:^1.3.0": version: 1.4.2 resolution: "@lezer/lr@npm:1.4.2" dependencies: @@ -425,13 +645,13 @@ __metadata: linkType: hard "@lezer/sass@npm:^1.0.0": - version: 1.0.7 - resolution: "@lezer/sass@npm:1.0.7" + version: 1.1.0 + resolution: "@lezer/sass@npm:1.1.0" dependencies: "@lezer/common": "npm:^1.2.0" "@lezer/highlight": "npm:^1.0.0" "@lezer/lr": "npm:^1.0.0" - checksum: 10/a7531cbf0766d3875480c24ae8929dd56053d71f9eaef8815e6f468e2252e66e38236a3f89395463e4cee43230dc35351386bedc6138cf1a19a44a64ce58a372 + checksum: 10/2f42dfaf5a328d4626a0a8ff8cd5973b7f52e8112541281cf1cbb1970acdc52c7c9f55c71f635bcff75a6151a55f7653d277306a15494b23b652e87d16df9ce3 languageName: node linkType: hard @@ -457,7 +677,7 @@ __metadata: languageName: node linkType: hard -"@mdx-js/mdx@npm:^3.0.0": +"@mdx-js/mdx@npm:^3.0.0, @mdx-js/mdx@npm:^3.1.0": version: 3.1.0 resolution: "@mdx-js/mdx@npm:3.1.0" dependencies: @@ -501,25 +721,36 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:14.2.26": - version: 14.2.26 - resolution: "@next/env@npm:14.2.26" - checksum: 10/0caa679cdf9367501daf1bff0e762b720613a3ee1689e5585bfb7c792fdd07c251bc1f7488919a296047eb1f2b82ceb13955b673143468cad338a87b7d98c196 - languageName: node - linkType: hard - -"@next/eslint-plugin-next@npm:14.2.23": - version: 14.2.23 - resolution: "@next/eslint-plugin-next@npm:14.2.23" +"@napi-rs/wasm-runtime@npm:^0.2.11": + version: 0.2.11 + resolution: "@napi-rs/wasm-runtime@npm:0.2.11" dependencies: - glob: "npm:10.3.10" - checksum: 10/e46513a7601bf682f1e60cc668188cf2c9f0471be73ffbbac69da240e9f91c6b9c3aad3e83a8da54c0ba8bce37cc6368bbafb04b74b385d5df0c36dde09a3217 + "@emnapi/core": "npm:^1.4.3" + "@emnapi/runtime": "npm:^1.4.3" + "@tybys/wasm-util": "npm:^0.9.0" + checksum: 10/e30fe3060474c5018e160231df0531d62b5e22f4736ecd49c04ca6cadacb2acf59b9205435794cd5b898e41e2e3ddb6523e93b97799bd1f4d0751557de6e38e4 languageName: node linkType: hard -"@next/mdx@npm:^15.1.4": - version: 15.1.6 - resolution: "@next/mdx@npm:15.1.6" +"@next/env@npm:15.3.3": + version: 15.3.3 + resolution: "@next/env@npm:15.3.3" + checksum: 10/f71fd8b397c60b80ee2ba836152d2c8bc772f4f840b976bfd82ca0c1452539b4832a1ce6702a90da1d4edb874383944ea2662add5c0729423cd4d1cb2e58fc3d + languageName: node + linkType: hard + +"@next/eslint-plugin-next@npm:15.3.3": + version: 15.3.3 + resolution: "@next/eslint-plugin-next@npm:15.3.3" + dependencies: + fast-glob: "npm:3.3.1" + checksum: 10/08b19a39fa01ac69c7b117a29ebdb193353a29cb3d51b2e3f72fabd7f2b014d08e37d819aed8e440a8a0a6e1acd9053dbf939b5f76603e6a87de0042fbf0bd4b + languageName: node + linkType: hard + +"@next/mdx@npm:15.3.3": + version: 15.3.3 + resolution: "@next/mdx@npm:15.3.3" dependencies: source-map: "npm:^0.7.0" peerDependencies: @@ -530,69 +761,62 @@ __metadata: optional: true "@mdx-js/react": optional: true - checksum: 10/3b46e99934543df94bcb335dddab794db4fcaff09223343e9e00597d3df1e094f16dd619c433fb2fa7fff38c3da57b3118cbc010cdeb9790d0cf01e6c37016e8 + checksum: 10/5583f01e986322bee6eae1052fce27964b33796b8fbd2ea360340a65ccf60cd348c614524bcbf8323be178cace1da5c74f45f8a111cf9e62682aad3a0a9d12e3 languageName: node linkType: hard -"@next/swc-darwin-arm64@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-darwin-arm64@npm:14.2.26" +"@next/swc-darwin-arm64@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-darwin-arm64@npm:15.3.3" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@next/swc-darwin-x64@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-darwin-x64@npm:14.2.26" +"@next/swc-darwin-x64@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-darwin-x64@npm:15.3.3" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@next/swc-linux-arm64-gnu@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-linux-arm64-gnu@npm:14.2.26" +"@next/swc-linux-arm64-gnu@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-linux-arm64-gnu@npm:15.3.3" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-linux-arm64-musl@npm:14.2.26" +"@next/swc-linux-arm64-musl@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-linux-arm64-musl@npm:15.3.3" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-linux-x64-gnu@npm:14.2.26" +"@next/swc-linux-x64-gnu@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-linux-x64-gnu@npm:15.3.3" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-linux-x64-musl@npm:14.2.26" +"@next/swc-linux-x64-musl@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-linux-x64-musl@npm:15.3.3" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-win32-arm64-msvc@npm:14.2.26" +"@next/swc-win32-arm64-msvc@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-win32-arm64-msvc@npm:15.3.3" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@next/swc-win32-ia32-msvc@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-win32-ia32-msvc@npm:14.2.26" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@next/swc-win32-x64-msvc@npm:14.2.26": - version: 14.2.26 - resolution: "@next/swc-win32-x64-msvc@npm:14.2.26" +"@next/swc-win32-x64-msvc@npm:15.3.3": + version: 15.3.3 + resolution: "@next/swc-win32-x64-msvc@npm:15.3.3" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -631,13 +855,6 @@ __metadata: languageName: node linkType: hard -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 10/115e8ceeec6bc69dff2048b35c0ab4f8bbee12d8bb6c1f4af758604586d802b6e669dcb02dda61d078de42c2b4ddce41b3d9e726d7daa6b4b850f4adbf7333ff - languageName: node - linkType: hard - "@rtsao/scc@npm:^1.1.0": version: 1.1.0 resolution: "@rtsao/scc@npm:1.1.0" @@ -645,97 +862,97 @@ __metadata: languageName: node linkType: hard -"@rushstack/eslint-patch@npm:^1.3.3": - version: 1.10.5 - resolution: "@rushstack/eslint-patch@npm:1.10.5" - checksum: 10/769d130dfb088c21e7ce72b552ea58c0d6d790cfe9fcadc4cd66e1282a0213a2c6b570d679723e0d3792a6b6ebfcbdeb5e785261d196017840853b8a5c57dfea +"@rushstack/eslint-patch@npm:^1.10.3": + version: 1.11.0 + resolution: "@rushstack/eslint-patch@npm:1.11.0" + checksum: 10/9bb3eb4a48a9a55e31d302b8b99f405e0f3e436fc3cda8c869fdd3fefd3ac398f5a353cceaa6c8cc5e5baf03ccd88d7965fbd25eb111f1f334415f95fa0f996d languageName: node linkType: hard -"@shikijs/core@npm:1.26.1": - version: 1.26.1 - resolution: "@shikijs/core@npm:1.26.1" +"@shikijs/core@npm:1.29.2": + version: 1.29.2 + resolution: "@shikijs/core@npm:1.29.2" dependencies: - "@shikijs/engine-javascript": "npm:1.26.1" - "@shikijs/engine-oniguruma": "npm:1.26.1" - "@shikijs/types": "npm:1.26.1" + "@shikijs/engine-javascript": "npm:1.29.2" + "@shikijs/engine-oniguruma": "npm:1.29.2" + "@shikijs/types": "npm:1.29.2" "@shikijs/vscode-textmate": "npm:^10.0.1" "@types/hast": "npm:^3.0.4" hast-util-to-html: "npm:^9.0.4" - checksum: 10/d70dd7e267da624bdc098f9f0295699d30d6bb0873a0c7f239f0664f253532af395208efe10cb0ad6f96b8c63fee439ce7ddbf5bd60b41149e2911fc3c55fc8f + checksum: 10/83dc5e86efc587d513268175ff43e8273567d2c6616d725bff15b08cf243d90f9371d6fa76ba49bb0e9823fc9947c8d8c650c81cfb19c3eb726178326f639a55 languageName: node linkType: hard -"@shikijs/engine-javascript@npm:1.26.1": - version: 1.26.1 - resolution: "@shikijs/engine-javascript@npm:1.26.1" +"@shikijs/engine-javascript@npm:1.29.2": + version: 1.29.2 + resolution: "@shikijs/engine-javascript@npm:1.29.2" dependencies: - "@shikijs/types": "npm:1.26.1" + "@shikijs/types": "npm:1.29.2" "@shikijs/vscode-textmate": "npm:^10.0.1" - oniguruma-to-es: "npm:0.10.0" - checksum: 10/fd7dac25fe4310bcd055af5163044fa598a65ec54376938f2ee70b8ab864a4d2f4bba2e76353d739ffcf071ffbc57b16147f47e5c9f950bf82f826cf88d3dd85 + oniguruma-to-es: "npm:^2.2.0" + checksum: 10/b49461ff7152650ffdbd77332d1c70e24a2ff1abe869e1038694b410194c6403fe5e8fce104fdd305d10c18702a50c1edbdb87172aa09f11340bc1203ed38488 languageName: node linkType: hard -"@shikijs/engine-oniguruma@npm:1.26.1": - version: 1.26.1 - resolution: "@shikijs/engine-oniguruma@npm:1.26.1" +"@shikijs/engine-oniguruma@npm:1.29.2": + version: 1.29.2 + resolution: "@shikijs/engine-oniguruma@npm:1.29.2" dependencies: - "@shikijs/types": "npm:1.26.1" + "@shikijs/types": "npm:1.29.2" "@shikijs/vscode-textmate": "npm:^10.0.1" - checksum: 10/17b3355fc226f58ef67d419c9e64a4dc99a63481c66f7ec55fdfb5035363605359414211e40ac8260db9c10c507e587dfac1061c0f57f111050e921e38cd58ef + checksum: 10/bb3e2c01da84d573251ebc289b1ecf815261024dea5bddb93ad56c3504a71cde3630db070be401ed3bbcd23a8a839ec78984a82317f9c9d0bba58daed935b781 languageName: node linkType: hard -"@shikijs/langs@npm:1.26.1": - version: 1.26.1 - resolution: "@shikijs/langs@npm:1.26.1" +"@shikijs/langs@npm:1.29.2": + version: 1.29.2 + resolution: "@shikijs/langs@npm:1.29.2" dependencies: - "@shikijs/types": "npm:1.26.1" - checksum: 10/b3909ffa30a89aa81e21da3c0af9388dfadab4f5ce9ce6f5a1cc3f7728e46702c02720123bad49dcd3f02e823c0ce9d533534164b0feb57856d2f8ee9504bce2 + "@shikijs/types": "npm:1.29.2" + checksum: 10/01f62d31c653c718a992918357e54d2d312c8da407997565fc19056fbf47f0fadc0f9f4b5fe1e1ba7b7d08e3984fb1f962159503ef0edd81fab5ee8bfdbf9080 languageName: node linkType: hard -"@shikijs/themes@npm:1.26.1": - version: 1.26.1 - resolution: "@shikijs/themes@npm:1.26.1" +"@shikijs/themes@npm:1.29.2": + version: 1.29.2 + resolution: "@shikijs/themes@npm:1.29.2" dependencies: - "@shikijs/types": "npm:1.26.1" - checksum: 10/5e2f5fe828f1b74dcc4027239768b3fe91567a5e07c3d014747627a7bd8a5ecf22460c59c20924172a96d99f4deed3e81e6f377e5f81239d6c6666d09e12fbd6 + "@shikijs/types": "npm:1.29.2" + checksum: 10/b81606dd882136e3fd751d0829133b5e66b10b8e32bd52ce16e7eac8755891c23e43f3ce06e65b97a75d2bc3b17e7fdb9115e9812679bb820ab163915868fd8b languageName: node linkType: hard -"@shikijs/types@npm:1.26.1": - version: 1.26.1 - resolution: "@shikijs/types@npm:1.26.1" +"@shikijs/types@npm:1.29.2": + version: 1.29.2 + resolution: "@shikijs/types@npm:1.29.2" dependencies: "@shikijs/vscode-textmate": "npm:^10.0.1" "@types/hast": "npm:^3.0.4" - checksum: 10/68ad9307424012cb833c02b7e0db4f0792d6ff9b633591318c76edb5a4ffb84da0de56dbe33b9b62ae810a878fdfd4f9ff6655a067997c664d9b0436266bcb16 + checksum: 10/579e64b6e8cb83023232b8060b08f51cff3909b199d0d1a0c58ed500c898dd34b74bf0457336fa2e6bee1005889e198d7d924347ad616eee30c6ae4c89a67ab8 languageName: node linkType: hard "@shikijs/vscode-textmate@npm:^10.0.1": - version: 10.0.1 - resolution: "@shikijs/vscode-textmate@npm:10.0.1" - checksum: 10/8bb005efbf472a9cac19bb5fb0bb1a2b612e128122e5a9d7b2bb2af674c29912db0e59c547ec458e5333f8f7ac7a8f054a24cb653f11dc20951e299933b416e4 + version: 10.0.2 + resolution: "@shikijs/vscode-textmate@npm:10.0.2" + checksum: 10/d924cba8a01cd9ca12f56ba097d628fdb81455abb85884c8d8a5ae85b628a37dd5907e7691019b97107bd6608c866adf91ba04a1c3bba391281c88e386c044ea languageName: node linkType: hard -"@storybook/components@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/components@npm:8.6.12" +"@storybook/components@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/components@npm:8.6.14" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/ccc6af275bdfbc66de8afb272f59b2b4b6b76bb2961903335cb62e7defae4a2368bd8f5a2008f8598dd8417e5368d824bce836ca96c3c6ff659dbf2f081ec0dd + checksum: 10/d3647505510313aa3c32fd1f8f202eda723ad99bd353b20aa929c5ecab4edc3c86ad06d0eac02f3c6d948e88f13f65ca5fa35ef27c079ef1b92abe8692f23699 languageName: node linkType: hard -"@storybook/core@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/core@npm:8.6.12" +"@storybook/core@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/core@npm:8.6.14" dependencies: - "@storybook/theming": "npm:8.6.12" + "@storybook/theming": "npm:8.6.14" better-opn: "npm:^3.0.2" browser-assert: "npm:^1.2.1" esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0" @@ -751,7 +968,7 @@ __metadata: peerDependenciesMeta: prettier: optional: true - checksum: 10/78776f51b9eae00f9387421b33b646b1dc67ef833fd6272de03399daa7f0ffa248c65b5f24d5d2a9af923a029d06d84d5425e3455302ece542bf47c7a9ec0df6 + checksum: 10/8f8c811edd4ea8dcedcc63a79b3168dc83aa0401e3760990cb5995ea6beaac4026c6ccc1046182f41eed7c09333005f3348d8b42f89fc5dd5b2ce011a9d2a48f languageName: node linkType: hard @@ -762,92 +979,91 @@ __metadata: languageName: node linkType: hard -"@storybook/manager-api@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/manager-api@npm:8.6.12" +"@storybook/manager-api@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/manager-api@npm:8.6.14" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/d1c44c6649a024c4007461c12a15337f5d13532dbaccc4c02f71bd99599fb973e2574eb8f1bc2d93e05da24e4ae43fa47ec637a7c4cccf5ffc67045cafbf087c + checksum: 10/4544b317050b81574f1cd6f911dcb99ee2adbd7190555209171a7dd2233cd331165fa11c16a5977ebe58eeaf26c86bbfcb23f701cfd79a10f0d034dae65197bd languageName: node linkType: hard -"@storybook/preview-api@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/preview-api@npm:8.6.12" +"@storybook/preview-api@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/preview-api@npm:8.6.14" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/d24f11e4e54e9e51297b0f87e1d462b3f14a974b4681f31d93b62b0706ce5b5ed4ffaaac521ec049dcb0e08e7aa7590f2e039aee4bbe9f85033d69474d982f23 + checksum: 10/9b77288f2f627a7c70cfd3e88bdc4348c1425fab6b333ed77efb913e45881bcdf2ca67f1174e47fe978a7993c2a653fb8accf518ba51441cfb9450145101e9d8 languageName: node linkType: hard -"@storybook/react-dom-shim@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/react-dom-shim@npm:8.6.12" +"@storybook/react-dom-shim@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/react-dom-shim@npm:8.6.14" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 - checksum: 10/7677b4fae978209af239471f9eb609db4318815bd4249a0f48b9875482d9ae910b93fbe4db5d7f794ecc2a1249edf40da26af9de673c941c48fccc4007819c96 + storybook: ^8.6.14 + checksum: 10/d14d970b98dc8266d4df80225538fb537449c15877f0f54465dfe52242f0c3f041c975824cc2894f72a3a8023fdffa0a281d90325b95297858e3684913b3cc15 languageName: node linkType: hard "@storybook/react@npm:^8.6.8": - version: 8.6.12 - resolution: "@storybook/react@npm:8.6.12" + version: 8.6.14 + resolution: "@storybook/react@npm:8.6.14" dependencies: - "@storybook/components": "npm:8.6.12" + "@storybook/components": "npm:8.6.14" "@storybook/global": "npm:^5.0.0" - "@storybook/manager-api": "npm:8.6.12" - "@storybook/preview-api": "npm:8.6.12" - "@storybook/react-dom-shim": "npm:8.6.12" - "@storybook/theming": "npm:8.6.12" + "@storybook/manager-api": "npm:8.6.14" + "@storybook/preview-api": "npm:8.6.14" + "@storybook/react-dom-shim": "npm:8.6.14" + "@storybook/theming": "npm:8.6.14" peerDependencies: - "@storybook/test": 8.6.12 + "@storybook/test": 8.6.14 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.6.12 + storybook: ^8.6.14 typescript: ">= 4.2.x" peerDependenciesMeta: "@storybook/test": optional: true typescript: optional: true - checksum: 10/d8258c82743906f48a872a781f3e5a63e9ce3fda2ba3b911e959cf62ebda43989b23746257d97905addb7142ac7e53d0089dc178bcbeea48ed4d37d025dd047b + checksum: 10/a8710dcb80da9df4a78cfb3560a8c0c31c6f6c04902aa95446d4e40f6a9179770ead6f9a0a06fa44d00677cd1cd92eb60fdc57f433035ec722275847139671a4 languageName: node linkType: hard -"@storybook/theming@npm:8.6.12": - version: 8.6.12 - resolution: "@storybook/theming@npm:8.6.12" +"@storybook/theming@npm:8.6.14": + version: 8.6.14 + resolution: "@storybook/theming@npm:8.6.14" peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - checksum: 10/c811d9dbb9eaaa680b922111fca126a2985f2238dfb01c1cd23184323eea12899dc9f079063ac42c5e63b0c83de326bd9cc17241e4060ff04e860c57a55fb8b9 + checksum: 10/6936ea3348968fe598ad47421c11a78c6ee2ce62336ea1ce9cb8257e9faa2553d3ac3e443f8a36d35a41b0d60eb169231516649c710582ec68fdead4f23ffc0e languageName: node linkType: hard -"@swc/counter@npm:^0.1.3": +"@swc/counter@npm:0.1.3": version: 0.1.3 resolution: "@swc/counter@npm:0.1.3" checksum: 10/df8f9cfba9904d3d60f511664c70d23bb323b3a0803ec9890f60133954173047ba9bdeabce28cd70ba89ccd3fd6c71c7b0bd58be85f611e1ffbe5d5c18616598 languageName: node linkType: hard -"@swc/helpers@npm:0.5.5": - version: 0.5.5 - resolution: "@swc/helpers@npm:0.5.5" +"@swc/helpers@npm:0.5.15": + version: 0.5.15 + resolution: "@swc/helpers@npm:0.5.15" dependencies: - "@swc/counter": "npm:^0.1.3" - tslib: "npm:^2.4.0" - checksum: 10/1c5ef04f642542212df28c669438f3e0f459dcde7b448a5b1fcafb2e9e4f13e76d8428535a270e91ed123dd2a21189dbed34086b88a8cf68baf84984d6d0e39b + tslib: "npm:^2.8.0" + checksum: 10/e3f32c6deeecfb0fa3f22edff03a7b358e7ce16d27b0f1c8b5cdc3042c5c4ce4da6eac0b781ab7cc4f54696ece657467d56734fb26883439fb00017385364c4c languageName: node linkType: hard -"@types/acorn@npm:^4.0.0": - version: 4.0.6 - resolution: "@types/acorn@npm:4.0.6" +"@tybys/wasm-util@npm:^0.9.0": + version: 0.9.0 + resolution: "@tybys/wasm-util@npm:0.9.0" dependencies: - "@types/estree": "npm:*" - checksum: 10/e00671d5055d06b07feccb8c2841467a4bdd1ab95a29e191d51cacc08c496e1ba1f54edeefab274bb2ba51cb45b0aaaa662a63897650e9d02e9997ad82124ae4 + tslib: "npm:^2.4.0" + checksum: 10/aa58e64753a420ad1eefaf7bacef3dda61d74f9336925943d9244132d5b48d9242f734f1e707fd5ccfa6dd1d8ec8e6debc234b4dedb3a5b0d8486d1f373350b2 languageName: node linkType: hard @@ -870,9 +1086,9 @@ __metadata: linkType: hard "@types/estree@npm:*, @types/estree@npm:^1.0.0": - version: 1.0.6 - resolution: "@types/estree@npm:1.0.6" - checksum: 10/9d35d475095199c23e05b431bcdd1f6fec7380612aed068b14b2a08aa70494de8a9026765a5a91b1073f636fb0368f6d8973f518a31391d519e20c59388ed88d + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10/25a4c16a6752538ffde2826c2cc0c6491d90e69cd6187bef4a006dd2c3c45469f049e643d7e516c515f21484dc3d48fd5c870be158a5beb72f5baf3dc43e4099 languageName: node linkType: hard @@ -892,7 +1108,7 @@ __metadata: languageName: node linkType: hard -"@types/mdast@npm:^4.0.0": +"@types/mdast@npm:^4.0.0, @types/mdast@npm:^4.0.4": version: 4.0.4 resolution: "@types/mdast@npm:4.0.4" dependencies: @@ -909,44 +1125,36 @@ __metadata: linkType: hard "@types/ms@npm:*": - version: 0.7.34 - resolution: "@types/ms@npm:0.7.34" - checksum: 10/f38d36e7b6edecd9badc9cf50474159e9da5fa6965a75186cceaf883278611b9df6669dc3a3cc122b7938d317b68a9e3d573d316fcb35d1be47ec9e468c6bd8a + version: 2.1.0 + resolution: "@types/ms@npm:2.1.0" + checksum: 10/532d2ebb91937ccc4a89389715e5b47d4c66e708d15942fe6cc25add6dc37b2be058230a327dd50f43f89b8b6d5d52b74685a9e8f70516edfc9bdd6be910eff4 languageName: node linkType: hard "@types/node@npm:^20": - version: 20.17.30 - resolution: "@types/node@npm:20.17.30" + version: 20.19.0 + resolution: "@types/node@npm:20.19.0" dependencies: - undici-types: "npm:~6.19.2" - checksum: 10/69fd3b177417be77b459e8f1dd4e78c85c686167086920fbf35a9fda301709bbeee6a87ad2591fb1ddd96c65e725ec6bb527a06496626a1c94367d1361048f8d + undici-types: "npm:~6.21.0" + checksum: 10/76cc44487faa0bf5302c84e5317de4b9727ea4e4b86d59d18d25435f4ccd7e6c584614dea061256f24a097c25a1d82dac1cd134357b42ae28006ba113c97f490 languageName: node linkType: hard -"@types/prop-types@npm:*": - version: 15.7.14 - resolution: "@types/prop-types@npm:15.7.14" - checksum: 10/d0c5407b9ccc3dd5fae0ccf9b1007e7622ba5e6f1c18399b4f24dff33619d469da4b9fa918a374f19dc0d9fe6a013362aab0b844b606cfc10676efba3f5f736d - languageName: node - linkType: hard - -"@types/react-dom@npm:^18.0.0": - version: 18.3.7 - resolution: "@types/react-dom@npm:18.3.7" +"@types/react-dom@npm:19.1.6": + version: 19.1.6 + resolution: "@types/react-dom@npm:19.1.6" peerDependencies: - "@types/react": ^18.0.0 - checksum: 10/317569219366d487a3103ba1e5e47154e95a002915fdcf73a44162c48fe49c3a57fcf7f57fc6979e70d447112681e6b13c6c3c1df289db8b544df4aab2d318f3 + "@types/react": ^19.0.0 + checksum: 10/b5b20b7f0797f34c5a11915b74dcf8b3b7a9da9fea90279975ce6f150ca5d31bb069dbb0838638a5e9e168098aa4bb4a6f61d078efa1bbb55d7f0bdfe47bb142 languageName: node linkType: hard -"@types/react@npm:^18.0.0": - version: 18.3.21 - resolution: "@types/react@npm:18.3.21" +"@types/react@npm:19.1.7": + version: 19.1.7 + resolution: "@types/react@npm:19.1.7" dependencies: - "@types/prop-types": "npm:*" csstype: "npm:^3.0.2" - checksum: 10/89adc2fe391fd63b620db0aaa10b2be89fad338039415fd748e60c962e47bf2b0f1897cb5226f181eda963f37a1c8ac1bf0b3b29a0e35313bacd0355e5d6c736 + checksum: 10/a2bd9aea233e9b8b1eb27192ee5cefd5a236b03ef9fce2e5ea8ec612cf4735d38fa443a0a1d0abea61dd166aad205fb8d799055dacfd2f0759da6f59f046fc55 languageName: node linkType: hard @@ -965,120 +1173,144 @@ __metadata: linkType: hard "@typescript-eslint/eslint-plugin@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": - version: 8.19.1 - resolution: "@typescript-eslint/eslint-plugin@npm:8.19.1" + version: 8.34.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.34.0" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.19.1" - "@typescript-eslint/type-utils": "npm:8.19.1" - "@typescript-eslint/utils": "npm:8.19.1" - "@typescript-eslint/visitor-keys": "npm:8.19.1" + "@typescript-eslint/scope-manager": "npm:8.34.0" + "@typescript-eslint/type-utils": "npm:8.34.0" + "@typescript-eslint/utils": "npm:8.34.0" + "@typescript-eslint/visitor-keys": "npm:8.34.0" graphemer: "npm:^1.4.0" - ignore: "npm:^5.3.1" + ignore: "npm:^7.0.0" natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.0.0" + ts-api-utils: "npm:^2.1.0" peerDependencies: - "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 + "@typescript-eslint/parser": ^8.34.0 eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.8.0" - checksum: 10/c9a6d3181ec01068075b85ad3ac454910b4452281d60c775cc7229827f6d6a076b7336f5f07a7ad89bf08b3224f6a49aa20342b9438702393bee0aa7315d23b2 + typescript: ">=4.8.4 <5.9.0" + checksum: 10/224f9e8a596e3c37fade2f2a1a9efce2fad652a768710693458e2b7c7f88c3a0e7bbbbc46d34d839c9373861fac542de6b9a7e132e36e2819b63840b9529e605 languageName: node linkType: hard "@typescript-eslint/parser@npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": - version: 8.19.1 - resolution: "@typescript-eslint/parser@npm:8.19.1" + version: 8.34.0 + resolution: "@typescript-eslint/parser@npm:8.34.0" dependencies: - "@typescript-eslint/scope-manager": "npm:8.19.1" - "@typescript-eslint/types": "npm:8.19.1" - "@typescript-eslint/typescript-estree": "npm:8.19.1" - "@typescript-eslint/visitor-keys": "npm:8.19.1" + "@typescript-eslint/scope-manager": "npm:8.34.0" + "@typescript-eslint/types": "npm:8.34.0" + "@typescript-eslint/typescript-estree": "npm:8.34.0" + "@typescript-eslint/visitor-keys": "npm:8.34.0" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.8.0" - checksum: 10/da3db63ff655cf0fb91745ba8e52d853386f601cf6106d36f4541efcb9e2c6c3b82c6743b15680eff9eafeccaf31c9b26191a955e66ae19de9172f67335463ab + typescript: ">=4.8.4 <5.9.0" + checksum: 10/b4c03ff2f09fd800a8f28c24289d24e2f4bfb4745c122f5f496bf832b06f0f37b1ab31ce8d7590ff1f83253de3306d145ef7b3c7b853a4ae716cb7ff443d1c27 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/scope-manager@npm:8.19.1" +"@typescript-eslint/project-service@npm:8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/project-service@npm:8.34.0" dependencies: - "@typescript-eslint/types": "npm:8.19.1" - "@typescript-eslint/visitor-keys": "npm:8.19.1" - checksum: 10/6ffc78b15367f211eb6650459ca2bb6bfe4c1fa95a3474adc08ee9a20c250b2e0e02fd99be36bd3dad74967ecd9349e792b5d818d85735cba40f1b5c236074d1 - languageName: node - linkType: hard - -"@typescript-eslint/type-utils@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/type-utils@npm:8.19.1" - dependencies: - "@typescript-eslint/typescript-estree": "npm:8.19.1" - "@typescript-eslint/utils": "npm:8.19.1" + "@typescript-eslint/tsconfig-utils": "npm:^8.34.0" + "@typescript-eslint/types": "npm:^8.34.0" debug: "npm:^4.3.4" - ts-api-utils: "npm:^2.0.0" + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + checksum: 10/04763896215c208c6b29e0b4f66ee0621878cd88fb6d9008c543db57f1d6b5d7fcc88f048c9a66ba2ed797f68e563c350e1b65403349ef75a4bc419072cef3c8 + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/scope-manager@npm:8.34.0" + dependencies: + "@typescript-eslint/types": "npm:8.34.0" + "@typescript-eslint/visitor-keys": "npm:8.34.0" + checksum: 10/fec7bb94fb3848bdf5ab9cfaf634e56aec3ed9bc4d546f65d83bb6511452e5a4b9eed5d09f54efceb9fa3b23a451d409735359237e8c0d51233d6537e5449fa7 + languageName: node + linkType: hard + +"@typescript-eslint/tsconfig-utils@npm:8.34.0, @typescript-eslint/tsconfig-utils@npm:^8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.34.0" + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + checksum: 10/cbbca9526bd9c0309c77f9436f68c2c06712779a593a17757f1f7558ece27d9f40db2b37ebf12bd9e19cf227479083b7973c502436a0954a08406d8a598910ba + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/type-utils@npm:8.34.0" + dependencies: + "@typescript-eslint/typescript-estree": "npm:8.34.0" + "@typescript-eslint/utils": "npm:8.34.0" + debug: "npm:^4.3.4" + ts-api-utils: "npm:^2.1.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.8.0" - checksum: 10/123ecda88b057d6a4b68226701f435661440a420fda88cba60b49d7fb3e4f49483164ff174f259e28c0beabb0ed04500462a20faefd78331ba202bf54b01e3ef + typescript: ">=4.8.4 <5.9.0" + checksum: 10/e7c565868b18d66ce5de016455c5ba2dc625a845e05ad563bfdf08b1753faa11d9aef22b9dc5071c57b6e73932748505715e7b47993757f1bc244d4d6f70d688 languageName: node linkType: hard -"@typescript-eslint/types@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/types@npm:8.19.1" - checksum: 10/5833a5f8fdac4a490dd3906a0243a0713fbf138fabb451870c70b0b089c539a9624b467b0913ddc0a225a8284342e7fd31cd506dec53c1a6d8f3c8c8902b9cae +"@typescript-eslint/types@npm:8.34.0, @typescript-eslint/types@npm:^8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/types@npm:8.34.0" + checksum: 10/da4dcee51e78139bdeb5832df836528c519a22c2e39b7737ae660afe024576030165424079f423a131ad56e2dca8f033943d6b48a54b4f4d296a6f7f83f5b494 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/typescript-estree@npm:8.19.1" +"@typescript-eslint/typescript-estree@npm:8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.34.0" dependencies: - "@typescript-eslint/types": "npm:8.19.1" - "@typescript-eslint/visitor-keys": "npm:8.19.1" + "@typescript-eslint/project-service": "npm:8.34.0" + "@typescript-eslint/tsconfig-utils": "npm:8.34.0" + "@typescript-eslint/types": "npm:8.34.0" + "@typescript-eslint/visitor-keys": "npm:8.34.0" debug: "npm:^4.3.4" fast-glob: "npm:^3.3.2" is-glob: "npm:^4.0.3" minimatch: "npm:^9.0.4" semver: "npm:^7.6.0" - ts-api-utils: "npm:^2.0.0" + ts-api-utils: "npm:^2.1.0" peerDependencies: - typescript: ">=4.8.4 <5.8.0" - checksum: 10/5de467452d5ef1a380d441b06cd0134652a0c98cdb4ce31b93eb589f7dc75ef60364d03fd80ca0a48d0c8b268f7258d4f6528b16fe1b89442d60a4bc960fe5f5 + typescript: ">=4.8.4 <5.9.0" + checksum: 10/26817d4c948253eb6a8f49fcd7a8f74c4ffeae7943aef9e1cb90d1b7adbc8e0f66605b0b318dc6eee3eda212882e278a300776b26fe4e2319712cd9822a3a4e4 languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/utils@npm:8.19.1" +"@typescript-eslint/utils@npm:8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/utils@npm:8.34.0" dependencies: - "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.19.1" - "@typescript-eslint/types": "npm:8.19.1" - "@typescript-eslint/typescript-estree": "npm:8.19.1" + "@eslint-community/eslint-utils": "npm:^4.7.0" + "@typescript-eslint/scope-manager": "npm:8.34.0" + "@typescript-eslint/types": "npm:8.34.0" + "@typescript-eslint/typescript-estree": "npm:8.34.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.8.0" - checksum: 10/bb92116a53fe143ee87e830941afb21d4222a64ca3f2b6dac5c2d9984f981408e60e52b04c32d95208896075ac222fb4ee631c5b0c4826b87d4bd8091c421ab1 + typescript: ">=4.8.4 <5.9.0" + checksum: 10/c51d2015e8076dd2a9d8255746889130aaf298cf9ff8f73114dcf7148f34536d47d883880eec7e3d89ec3f746c2d3f2b749e8fef5e8ad9914132deb5c013efbd languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/visitor-keys@npm:8.19.1" +"@typescript-eslint/visitor-keys@npm:8.34.0": + version: 8.34.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.34.0" dependencies: - "@typescript-eslint/types": "npm:8.19.1" + "@typescript-eslint/types": "npm:8.34.0" eslint-visitor-keys: "npm:^4.2.0" - checksum: 10/510eb196e7b7d59d3981d672a75454615159e931fe78e2a64b09607c3cfa45110709b0eb5ac3dd271d757a0d98cf4868ad2f45bf9193f96e9efec3efa92a19c1 + checksum: 10/8a591cb9f922b6fd92107ebdf255425cf7ecd56281d032d944fb38e6be319e6cca7dc49bab6ad1d46390d2ca023c3413c03775e638ec5fd70172150debf7636a languageName: node linkType: hard -"@uiw/codemirror-extensions-basic-setup@npm:4.23.7": - version: 4.23.7 - resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.23.7" +"@uiw/codemirror-extensions-basic-setup@npm:4.23.13": + version: 4.23.13 + resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.23.13" dependencies: "@codemirror/autocomplete": "npm:^6.0.0" "@codemirror/commands": "npm:^6.0.0" @@ -1095,13 +1327,13 @@ __metadata: "@codemirror/search": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 10/7b8cbb9ccc931bc1a6909d009e653f78797e4b851aa9fb8e11f90ae1cea233e643a1951ad4ad9e10123ef166808acee1acff24732ea9fc15cd77164f6b179b91 + checksum: 10/35fd1894f9f7da8f94fa077a70821f92118597e2e2b12782b254b0844f06c1855508421c4cb47de7099a972ef706218c08203116aa93da4e8e1585c8d234015d languageName: node linkType: hard "@uiw/codemirror-themes@npm:^4.23.7": - version: 4.23.12 - resolution: "@uiw/codemirror-themes@npm:4.23.12" + version: 4.23.13 + resolution: "@uiw/codemirror-themes@npm:4.23.13" dependencies: "@codemirror/language": "npm:^6.0.0" "@codemirror/state": "npm:^6.0.0" @@ -1110,19 +1342,19 @@ __metadata: "@codemirror/language": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 10/472121efadf2b9eb110030f06d657cb9693861e1ca360c81c7b0d7aa4533e6e6791737dfd14bcbe496bcb818d33f4cf6a18caf504ec628807e273441f969ef83 + checksum: 10/8c168ed1aa6185c4cc92b66f873e38f6bb50778f1ccadcc99db04cbe97c928e7b924a2f41cb40c4b26328489038d986fe9360605e1668e6c0af2ba931b79523e languageName: node linkType: hard "@uiw/react-codemirror@npm:^4.23.7": - version: 4.23.7 - resolution: "@uiw/react-codemirror@npm:4.23.7" + version: 4.23.13 + resolution: "@uiw/react-codemirror@npm:4.23.13" dependencies: "@babel/runtime": "npm:^7.18.6" "@codemirror/commands": "npm:^6.1.0" "@codemirror/state": "npm:^6.1.1" "@codemirror/theme-one-dark": "npm:^6.0.0" - "@uiw/codemirror-extensions-basic-setup": "npm:4.23.7" + "@uiw/codemirror-extensions-basic-setup": "npm:4.23.13" codemirror: "npm:^6.0.0" peerDependencies: "@babel/runtime": ">=7.11.0" @@ -1132,14 +1364,135 @@ __metadata: codemirror: ">=6.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 10/dc6f1fd2f1863120e29ea331b2e279054bf508f00656d3fed97779f17818ae00b6cf9d74d3b0996800174d444b59702da74ca88c303db563daad85c0824fc952 + checksum: 10/51777d7eb313be0716d3597f829e6ee9982ab2dc3e36dc5219f25cdf680b82a6c0a0951d9adaca85b9c829da92f7971aa6e65bdd50e7610972d89463fe2587a1 languageName: node linkType: hard "@ungap/structured-clone@npm:^1.0.0, @ungap/structured-clone@npm:^1.2.0": - version: 1.2.1 - resolution: "@ungap/structured-clone@npm:1.2.1" - checksum: 10/6770f71e8183311b2871601ddb02d62a26373be7cf2950cb546a345a2305c75b502e36ce80166120aa2f5f1ea1562141684651ebbfcc711c58acd32035d3e545 + version: 1.3.0 + resolution: "@ungap/structured-clone@npm:1.3.0" + checksum: 10/80d6910946f2b1552a2406650051c91bbd1f24a6bf854354203d84fe2714b3e8ce4618f49cc3410494173a1c1e8e9777372fe68dce74bd45faf0a7a1a6ccf448 + languageName: node + linkType: hard + +"@unrs/resolver-binding-darwin-arm64@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-darwin-arm64@npm:1.7.13" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-darwin-x64@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-darwin-x64@npm:1.7.13" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-freebsd-x64@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-freebsd-x64@npm:1.7.13" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.7.13" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm-musleabihf@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-arm-musleabihf@npm:1.7.13" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm64-gnu@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-arm64-gnu@npm:1.7.13" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-arm64-musl@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-arm64-musl@npm:1.7.13" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-ppc64-gnu@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-ppc64-gnu@npm:1.7.13" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-riscv64-gnu@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-riscv64-gnu@npm:1.7.13" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-riscv64-musl@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-riscv64-musl@npm:1.7.13" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-s390x-gnu@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-s390x-gnu@npm:1.7.13" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-x64-gnu@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-x64-gnu@npm:1.7.13" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-x64-musl@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-linux-x64-musl@npm:1.7.13" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-wasm32-wasi@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-wasm32-wasi@npm:1.7.13" + dependencies: + "@napi-rs/wasm-runtime": "npm:^0.2.11" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@unrs/resolver-binding-win32-arm64-msvc@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-win32-arm64-msvc@npm:1.7.13" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-win32-ia32-msvc@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-win32-ia32-msvc@npm:1.7.13" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@unrs/resolver-binding-win32-x64-msvc@npm:1.7.13": + version: 1.7.13 + resolution: "@unrs/resolver-binding-win32-x64-msvc@npm:1.7.13" + conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -1153,11 +1506,11 @@ __metadata: linkType: hard "acorn@npm:^8.0.0, acorn@npm:^8.9.0": - version: 8.14.0 - resolution: "acorn@npm:8.14.0" + version: 8.15.0 + resolution: "acorn@npm:8.15.0" bin: acorn: bin/acorn - checksum: 10/6df29c35556782ca9e632db461a7f97947772c6c1d5438a81f0c873a3da3a792487e83e404d1c6c25f70513e91aa18745f6eafb1fcc3a43ecd1920b21dd173d2 + checksum: 10/77f2de5051a631cf1729c090e5759148459cdb76b5f5c70f890503d629cf5052357b0ce783c0f976dd8a93c5150f59f6d18df1def3f502396a20f81282482fa4 languageName: node linkType: hard @@ -1180,14 +1533,7 @@ __metadata: languageName: node linkType: hard -"ansi-regex@npm:^6.0.1": - version: 6.1.0 - resolution: "ansi-regex@npm:6.1.0" - checksum: 10/495834a53b0856c02acd40446f7130cb0f8284f4a39afdab20d5dc42b2e198b1196119fe887beed8f9055c4ff2055e3b2f6d4641d0be018cdfb64fedf6fc1aac - languageName: node - linkType: hard - -"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": +"ansi-styles@npm:^4.1.0": version: 4.3.0 resolution: "ansi-styles@npm:4.3.0" dependencies: @@ -1196,13 +1542,6 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^6.1.0": - version: 6.2.1 - resolution: "ansi-styles@npm:6.2.1" - checksum: 10/70fdf883b704d17a5dfc9cde206e698c16bcd74e7f196ab821511651aee4f9f76c9514bdfa6ca3a27b5e49138b89cb222a28caf3afe4567570139577f991df32 - languageName: node - linkType: hard - "argparse@npm:^2.0.1": version: 2.0.1 resolution: "argparse@npm:2.0.1" @@ -1228,16 +1567,18 @@ __metadata: linkType: hard "array-includes@npm:^3.1.6, array-includes@npm:^3.1.8": - version: 3.1.8 - resolution: "array-includes@npm:3.1.8" + version: 3.1.9 + resolution: "array-includes@npm:3.1.9" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-object-atoms: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.4" - is-string: "npm:^1.0.7" - checksum: 10/290b206c9451f181fb2b1f79a3bf1c0b66bb259791290ffbada760c79b284eef6f5ae2aeb4bcff450ebc9690edd25732c4c73a3c2b340fcc0f4563aed83bf488 + es-abstract: "npm:^1.24.0" + es-object-atoms: "npm:^1.1.1" + get-intrinsic: "npm:^1.3.0" + is-string: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + checksum: 10/8bfe9a58df74f326b4a76b04ee05c13d871759e888b4ee8f013145297cf5eb3c02cfa216067ebdaac5d74eb9763ac5cad77cdf2773b8ab475833701e032173aa languageName: node linkType: hard @@ -1256,16 +1597,17 @@ __metadata: linkType: hard "array.prototype.findlastindex@npm:^1.2.5": - version: 1.2.5 - resolution: "array.prototype.findlastindex@npm:1.2.5" + version: 1.2.6 + resolution: "array.prototype.findlastindex@npm:1.2.6" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" + es-abstract: "npm:^1.23.9" es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10/7c5c821f357cd53ab6cc305de8086430dd8d7a2485db87b13f843e868055e9582b1fd338f02338f67fc3a1603ceaf9610dd2a470b0b506f9d18934780f95b246 + es-object-atoms: "npm:^1.1.1" + es-shim-unscopables: "npm:^1.1.0" + checksum: 10/5ddb6420e820bef6ddfdcc08ce780d0fd5e627e97457919c27e32359916de5a11ce12f7c55073555e503856618eaaa70845d6ca11dcba724766f38eb1c22f7a2 languageName: node linkType: hard @@ -1346,6 +1688,13 @@ __metadata: languageName: node linkType: hard +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10/1a09379937d846f0ce7614e75071c12826945d4e417db634156bf0e4673c495989302f52186dfa9767a1d9181794554717badd193ca2bbab046ef1da741d8efd + languageName: node + linkType: hard + "available-typed-arrays@npm:^1.0.7": version: 1.0.7 resolution: "available-typed-arrays@npm:1.0.7" @@ -1356,9 +1705,9 @@ __metadata: linkType: hard "axe-core@npm:^4.10.0": - version: 4.10.2 - resolution: "axe-core@npm:4.10.2" - checksum: 10/a69423b2ff16c15922c4ea7cf9cc5112728a2817bbe0f2cc212248d648885ffd1ba554e3a341dfc289cd9e67fc0d06f333b5c6837c5c38ca6652507381216fc1 + version: 4.10.3 + resolution: "axe-core@npm:4.10.3" + checksum: 10/9ff51ad0fd0fdec5c0247ea74e8ace5990b54c7f01f8fa3e5cd8ba98b0db24d8ebd7bab4a9bd4d75c28c4edcd1eac455b44c8c6c258c6a98f3d2f88bc60af4cc languageName: node linkType: hard @@ -1393,21 +1742,21 @@ __metadata: linkType: hard "brace-expansion@npm:^1.1.7": - version: 1.1.11 - resolution: "brace-expansion@npm:1.1.11" + version: 1.1.12 + resolution: "brace-expansion@npm:1.1.12" dependencies: balanced-match: "npm:^1.0.0" concat-map: "npm:0.0.1" - checksum: 10/faf34a7bb0c3fcf4b59c7808bc5d2a96a40988addf2e7e09dfbb67a2251800e0d14cd2bfc1aa79174f2f5095c54ff27f46fb1289fe2d77dac755b5eb3434cc07 + checksum: 10/12cb6d6310629e3048cadb003e1aca4d8c9bb5c67c3c321bafdd7e7a50155de081f78ea3e0ed92ecc75a9015e784f301efc8132383132f4f7904ad1ac529c562 languageName: node linkType: hard "brace-expansion@npm:^2.0.1": - version: 2.0.1 - resolution: "brace-expansion@npm:2.0.1" + version: 2.0.2 + resolution: "brace-expansion@npm:2.0.2" dependencies: balanced-match: "npm:^1.0.0" - checksum: 10/a61e7cd2e8a8505e9f0036b3b6108ba5e926b4b55089eeb5550cd04a471fe216c96d4fe7e4c7f995c728c554ae20ddfc4244cad10aef255e72b62930afd233d1 + checksum: 10/01dff195e3646bc4b0d27b63d9bab84d2ebc06121ff5013ad6e5356daa5a9d6b60fa26cf73c74797f2dc3fbec112af13578d51f75228c1112b26c790a87b0488 languageName: node linkType: hard @@ -1476,9 +1825,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.30001579": - version: 1.0.30001690 - resolution: "caniuse-lite@npm:1.0.30001690" - checksum: 10/9fb4659eb09a298601b9593739072c481e2f5cc524bd0530e5e0f002e66246da5e866669854dfc0d53195ee36b201dab02f7933a7cdf60ccba7adb2d4a304caf + version: 1.0.30001722 + resolution: "caniuse-lite@npm:1.0.30001722" + checksum: 10/60ad52c891221c894085d450c7308f94768702bed4f0b4b99199e3c3c5926b22d7c7c05f474fcdb484befbd43d1975f8529c8ad8f66770f5c32c5342716ecd0b languageName: node linkType: hard @@ -1490,21 +1839,23 @@ __metadata: "@lezer/highlight": "npm:^1.2.1" "@mdx-js/loader": "npm:^3.1.0" "@mdx-js/react": "npm:^3.1.0" - "@next/mdx": "npm:^15.1.4" + "@next/mdx": "npm:15.3.3" "@storybook/react": "npm:^8.6.8" "@types/mdx": "npm:^2.0.13" "@types/node": "npm:^20" - "@types/react": "npm:^18.0.0" - "@types/react-dom": "npm:^18.0.0" + "@types/react": "npm:19.1.7" + "@types/react-dom": "npm:19.1.6" "@uiw/codemirror-themes": "npm:^4.23.7" "@uiw/react-codemirror": "npm:^4.23.7" eslint: "npm:^8" - eslint-config-next: "npm:14.2.23" + eslint-config-next: "npm:15.3.3" + html-react-parser: "npm:^5.2.5" lightningcss: "npm:^1.28.2" motion: "npm:^12.4.1" - next: "npm:14.2.26" - react: "npm:^18.0.2" - react-dom: "npm:^18.0.2" + next: "npm:15.3.3" + next-mdx-remote-client: "npm:^2.1.2" + react: "npm:19.1.0" + react-dom: "npm:19.1.0" react-frame-component: "npm:^5.2.7" shiki: "npm:^1.26.1" storybook: "npm:^8.6.8" @@ -1595,13 +1946,33 @@ __metadata: languageName: node linkType: hard -"color-name@npm:~1.1.4": +"color-name@npm:^1.0.0, color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 languageName: node linkType: hard +"color-string@npm:^1.9.0": + version: 1.9.1 + resolution: "color-string@npm:1.9.1" + dependencies: + color-name: "npm:^1.0.0" + simple-swizzle: "npm:^0.2.2" + checksum: 10/72aa0b81ee71b3f4fb1ac9cd839cdbd7a011a7d318ef58e6cb13b3708dca75c7e45029697260488709f1b1c7ac4e35489a87e528156c1e365917d1c4ccb9b9cd + languageName: node + linkType: hard + +"color@npm:^4.2.3": + version: 4.2.3 + resolution: "color@npm:4.2.3" + dependencies: + color-convert: "npm:^2.0.1" + color-string: "npm:^1.9.0" + checksum: 10/b23f5e500a79ea22428db43d1a70642d983405c0dd1f95ef59dbdb9ba66afbb4773b334fa0b75bb10b0552fd7534c6b28d4db0a8b528f91975976e70973c0152 + languageName: node + linkType: hard + "comma-separated-tokens@npm:^2.0.0": version: 2.0.3 resolution: "comma-separated-tokens@npm:2.0.3" @@ -1616,14 +1987,14 @@ __metadata: languageName: node linkType: hard -"crelt@npm:^1.0.5": +"crelt@npm:^1.0.5, crelt@npm:^1.0.6": version: 1.0.6 resolution: "crelt@npm:1.0.6" checksum: 10/5ed326ca6bd243b1dba6b943f665b21c2c04be03271824bc48f20dba324b0f8233e221f8c67312526d24af2b1243c023dc05a41bd8bd05d1a479fd2c72fb39c3 languageName: node linkType: hard -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2": +"cross-spawn@npm:^7.0.2": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -1690,24 +2061,24 @@ __metadata: languageName: node linkType: hard -"debug@npm:^4.0.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.7": - version: 4.4.0 - resolution: "debug@npm:4.4.0" +"debug@npm:^4.0.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.0": + version: 4.4.1 + resolution: "debug@npm:4.4.1" dependencies: ms: "npm:^2.1.3" peerDependenciesMeta: supports-color: optional: true - checksum: 10/1847944c2e3c2c732514b93d11886575625686056cd765336212dc15de2d2b29612b6cd80e1afba767bb8e1803b778caf9973e98169ef1a24a7a7009e1820367 + checksum: 10/8e2709b2144f03c7950f8804d01ccb3786373df01e406a0f66928e47001cf2d336cbed9ee137261d4f90d68d8679468c755e3548ed83ddacdc82b194d2468afe languageName: node linkType: hard "decode-named-character-reference@npm:^1.0.0": - version: 1.0.2 - resolution: "decode-named-character-reference@npm:1.0.2" + version: 1.1.0 + resolution: "decode-named-character-reference@npm:1.1.0" dependencies: character-entities: "npm:^2.0.0" - checksum: 10/f4c71d3b93105f20076052f9cb1523a22a9c796b8296cd35eef1ca54239c78d182c136a848b83ff8da2071e3ae2b1d300bf29d00650a6d6e675438cc31b11d78 + checksum: 10/102970fde2d011f307d3789776e68defd75ba4ade1a34951affd1fabb86cd32026fd809f2658c2b600d839a57b6b6a84e2b3a45166d38c8625d66ca11cd702b8 languageName: node linkType: hard @@ -1754,10 +2125,10 @@ __metadata: languageName: node linkType: hard -"detect-libc@npm:^2.0.3": - version: 2.0.3 - resolution: "detect-libc@npm:2.0.3" - checksum: 10/b4ea018d623e077bd395f168a9e81db77370dde36a5b01d067f2ad7989924a81d31cb547ff764acb2aa25d50bb7fdde0b0a93bec02212b0cb430621623246d39 +"detect-libc@npm:^2.0.3, detect-libc@npm:^2.0.4": + version: 2.0.4 + resolution: "detect-libc@npm:2.0.4" + checksum: 10/136e995f8c5ffbc515955b0175d441b967defd3d5f2268e89fa695e9c7170d8bed17993e31a34b04f0fad33d844a3a598e0fd519a8e9be3cad5f67662d96fee0 languageName: node linkType: hard @@ -1788,6 +2159,44 @@ __metadata: languageName: node linkType: hard +"dom-serializer@npm:^2.0.0": + version: 2.0.0 + resolution: "dom-serializer@npm:2.0.0" + dependencies: + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.2" + entities: "npm:^4.2.0" + checksum: 10/e3bf9027a64450bca0a72297ecdc1e3abb7a2912268a9f3f5d33a2e29c1e2c3502c6e9f860fc6625940bfe0cfb57a44953262b9e94df76872fdfb8151097eeb3 + languageName: node + linkType: hard + +"domelementtype@npm:^2.3.0": + version: 2.3.0 + resolution: "domelementtype@npm:2.3.0" + checksum: 10/ee837a318ff702622f383409d1f5b25dd1024b692ef64d3096ff702e26339f8e345820f29a68bcdcea8cfee3531776b3382651232fbeae95612d6f0a75efb4f6 + languageName: node + linkType: hard + +"domhandler@npm:5.0.3, domhandler@npm:^5.0.2, domhandler@npm:^5.0.3": + version: 5.0.3 + resolution: "domhandler@npm:5.0.3" + dependencies: + domelementtype: "npm:^2.3.0" + checksum: 10/809b805a50a9c6884a29f38aec0a4e1b4537f40e1c861950ed47d10b049febe6b79ab72adaeeebb3cc8fc1cd33f34e97048a72a9265103426d93efafa78d3e96 + languageName: node + linkType: hard + +"domutils@npm:^3.2.1": + version: 3.2.2 + resolution: "domutils@npm:3.2.2" + dependencies: + dom-serializer: "npm:^2.0.0" + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + checksum: 10/2e08842151aa406f50fe5e6d494f4ec73c2373199fa00d1f77b56ec604e566b7f226312ae35ab8160bb7f27a27c7285d574c8044779053e499282ca9198be210 + languageName: node + linkType: hard + "dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": version: 1.0.1 resolution: "dunder-proto@npm:1.0.1" @@ -1799,13 +2208,6 @@ __metadata: languageName: node linkType: hard -"eastasianwidth@npm:^0.2.0": - version: 0.2.0 - resolution: "eastasianwidth@npm:0.2.0" - checksum: 10/9b1d3e1baefeaf7d70799db8774149cef33b97183a6addceeba0cf6b85ba23ee2686f302f14482006df32df75d32b17c509c143a3689627929e4a8efaf483952 - languageName: node - linkType: hard - "emoji-regex-xs@npm:^1.0.0": version: 1.0.0 resolution: "emoji-regex-xs@npm:1.0.0" @@ -1813,13 +2215,6 @@ __metadata: languageName: node linkType: hard -"emoji-regex@npm:^8.0.0": - version: 8.0.0 - resolution: "emoji-regex@npm:8.0.0" - checksum: 10/c72d67a6821be15ec11997877c437491c313d924306b8da5d87d2a2bcc2cec9903cb5b04ee1a088460501d8e5b44f10df82fdc93c444101a7610b80c8b6938e1 - languageName: node - linkType: hard - "emoji-regex@npm:^9.2.2": version: 9.2.2 resolution: "emoji-regex@npm:9.2.2" @@ -1827,36 +2222,40 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.15.0": - version: 5.18.0 - resolution: "enhanced-resolve@npm:5.18.0" - dependencies: - graceful-fs: "npm:^4.2.4" - tapable: "npm:^2.2.0" - checksum: 10/e88463ef97b68d40d0da0cd0c572e23f43dba0be622d6d44eae5cafed05f0c5dac43e463a83a86c4f70186d029357f82b56d9e1e47e8fc91dce3d6602f8bd6ce +"entities@npm:^4.2.0": + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 10/ede2a35c9bce1aeccd055a1b445d41c75a14a2bb1cd22e242f20cf04d236cdcd7f9c859eb83f76885327bfae0c25bf03303665ee1ce3d47c5927b98b0e3e3d48 languageName: node linkType: hard -"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9": - version: 1.23.9 - resolution: "es-abstract@npm:1.23.9" +"entities@npm:^6.0.0": + version: 6.0.1 + resolution: "entities@npm:6.0.1" + checksum: 10/62af1307202884349d2867f0aac5c60d8b57102ea0b0e768b16246099512c28e239254ad772d6834e7e14cb1b6f153fc3d0c031934e3183b086c86d3838d874a + languageName: node + linkType: hard + +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0": + version: 1.24.0 + resolution: "es-abstract@npm:1.24.0" dependencies: array-buffer-byte-length: "npm:^1.0.2" arraybuffer.prototype.slice: "npm:^1.0.4" available-typed-arrays: "npm:^1.0.7" call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" + call-bound: "npm:^1.0.4" data-view-buffer: "npm:^1.0.2" data-view-byte-length: "npm:^1.0.2" data-view-byte-offset: "npm:^1.0.1" es-define-property: "npm:^1.0.1" es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" + es-object-atoms: "npm:^1.1.1" es-set-tostringtag: "npm:^2.1.0" es-to-primitive: "npm:^1.3.0" function.prototype.name: "npm:^1.1.8" - get-intrinsic: "npm:^1.2.7" - get-proto: "npm:^1.0.0" + get-intrinsic: "npm:^1.3.0" + get-proto: "npm:^1.0.1" get-symbol-description: "npm:^1.1.0" globalthis: "npm:^1.0.4" gopd: "npm:^1.2.0" @@ -1868,21 +2267,24 @@ __metadata: is-array-buffer: "npm:^3.0.5" is-callable: "npm:^1.2.7" is-data-view: "npm:^1.0.2" + is-negative-zero: "npm:^2.0.3" is-regex: "npm:^1.2.1" + is-set: "npm:^2.0.3" is-shared-array-buffer: "npm:^1.0.4" is-string: "npm:^1.1.1" is-typed-array: "npm:^1.1.15" - is-weakref: "npm:^1.1.0" + is-weakref: "npm:^1.1.1" math-intrinsics: "npm:^1.1.0" - object-inspect: "npm:^1.13.3" + object-inspect: "npm:^1.13.4" object-keys: "npm:^1.1.1" object.assign: "npm:^4.1.7" own-keys: "npm:^1.0.1" - regexp.prototype.flags: "npm:^1.5.3" + regexp.prototype.flags: "npm:^1.5.4" safe-array-concat: "npm:^1.1.3" safe-push-apply: "npm:^1.0.0" safe-regex-test: "npm:^1.1.0" set-proto: "npm:^1.0.0" + stop-iteration-iterator: "npm:^1.1.0" string.prototype.trim: "npm:^1.2.10" string.prototype.trimend: "npm:^1.0.9" string.prototype.trimstart: "npm:^1.0.8" @@ -1891,8 +2293,8 @@ __metadata: typed-array-byte-offset: "npm:^1.0.4" typed-array-length: "npm:^1.0.7" unbox-primitive: "npm:^1.1.0" - which-typed-array: "npm:^1.1.18" - checksum: 10/31a321966d760d88fc2ed984104841b42f4f24fc322b246002b9be0af162e03803ee41fcc3cf8be89e07a27ba3033168f877dd983703cb81422ffe5322a27582 + which-typed-array: "npm:^1.1.19" + checksum: 10/64e07a886f7439cf5ccfc100f9716e6173e10af6071a50a5031afbdde474a3dbc9619d5965da54e55f8908746a9134a46be02af8c732d574b7b81ed3124e2daf languageName: node linkType: hard @@ -1955,12 +2357,12 @@ __metadata: languageName: node linkType: hard -"es-shim-unscopables@npm:^1.0.2": - version: 1.0.2 - resolution: "es-shim-unscopables@npm:1.0.2" +"es-shim-unscopables@npm:^1.0.2, es-shim-unscopables@npm:^1.1.0": + version: 1.1.0 + resolution: "es-shim-unscopables@npm:1.1.0" dependencies: - hasown: "npm:^2.0.0" - checksum: 10/6d3bf91f658a27cc7217cd32b407a0d714393a84d125ad576319b9e83a893bea165cf41270c29e9ceaa56d3cf41608945d7e2a2c31fd51c0009b0c31402b91c7 + hasown: "npm:^2.0.2" + checksum: 10/c351f586c30bbabc62355be49564b2435468b52c3532b8a1663672e3d10dc300197e69c247869dd173e56d86423ab95fc0c10b0939cdae597094e0fdca078cba languageName: node linkType: hard @@ -2011,34 +2413,34 @@ __metadata: linkType: hard "esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0": - version: 0.25.0 - resolution: "esbuild@npm:0.25.0" + version: 0.25.5 + resolution: "esbuild@npm:0.25.5" dependencies: - "@esbuild/aix-ppc64": "npm:0.25.0" - "@esbuild/android-arm": "npm:0.25.0" - "@esbuild/android-arm64": "npm:0.25.0" - "@esbuild/android-x64": "npm:0.25.0" - "@esbuild/darwin-arm64": "npm:0.25.0" - "@esbuild/darwin-x64": "npm:0.25.0" - "@esbuild/freebsd-arm64": "npm:0.25.0" - "@esbuild/freebsd-x64": "npm:0.25.0" - "@esbuild/linux-arm": "npm:0.25.0" - "@esbuild/linux-arm64": "npm:0.25.0" - "@esbuild/linux-ia32": "npm:0.25.0" - "@esbuild/linux-loong64": "npm:0.25.0" - "@esbuild/linux-mips64el": "npm:0.25.0" - "@esbuild/linux-ppc64": "npm:0.25.0" - "@esbuild/linux-riscv64": "npm:0.25.0" - "@esbuild/linux-s390x": "npm:0.25.0" - "@esbuild/linux-x64": "npm:0.25.0" - "@esbuild/netbsd-arm64": "npm:0.25.0" - "@esbuild/netbsd-x64": "npm:0.25.0" - "@esbuild/openbsd-arm64": "npm:0.25.0" - "@esbuild/openbsd-x64": "npm:0.25.0" - "@esbuild/sunos-x64": "npm:0.25.0" - "@esbuild/win32-arm64": "npm:0.25.0" - "@esbuild/win32-ia32": "npm:0.25.0" - "@esbuild/win32-x64": "npm:0.25.0" + "@esbuild/aix-ppc64": "npm:0.25.5" + "@esbuild/android-arm": "npm:0.25.5" + "@esbuild/android-arm64": "npm:0.25.5" + "@esbuild/android-x64": "npm:0.25.5" + "@esbuild/darwin-arm64": "npm:0.25.5" + "@esbuild/darwin-x64": "npm:0.25.5" + "@esbuild/freebsd-arm64": "npm:0.25.5" + "@esbuild/freebsd-x64": "npm:0.25.5" + "@esbuild/linux-arm": "npm:0.25.5" + "@esbuild/linux-arm64": "npm:0.25.5" + "@esbuild/linux-ia32": "npm:0.25.5" + "@esbuild/linux-loong64": "npm:0.25.5" + "@esbuild/linux-mips64el": "npm:0.25.5" + "@esbuild/linux-ppc64": "npm:0.25.5" + "@esbuild/linux-riscv64": "npm:0.25.5" + "@esbuild/linux-s390x": "npm:0.25.5" + "@esbuild/linux-x64": "npm:0.25.5" + "@esbuild/netbsd-arm64": "npm:0.25.5" + "@esbuild/netbsd-x64": "npm:0.25.5" + "@esbuild/openbsd-arm64": "npm:0.25.5" + "@esbuild/openbsd-x64": "npm:0.25.5" + "@esbuild/sunos-x64": "npm:0.25.5" + "@esbuild/win32-arm64": "npm:0.25.5" + "@esbuild/win32-ia32": "npm:0.25.5" + "@esbuild/win32-x64": "npm:0.25.5" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -2092,7 +2494,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10/451daf6a442df29ec5d528587caa4ce783d41ff4acb93252da5a852b8d36c22e9f84d17f6721d4fbef9a1ba9855bc9fe1f167dd732c11665fe53032f2b89f114 + checksum: 10/0fa4c3b42c6ddf1a008e75a4bb3dcab08ce22ac0b31dd59dc01f7fe8e21380bfaec07a2fe3730a7cf430da5a30142d016714b358666325a4733547afa42be405 languageName: node linkType: hard @@ -2103,27 +2505,27 @@ __metadata: languageName: node linkType: hard -"eslint-config-next@npm:14.2.23": - version: 14.2.23 - resolution: "eslint-config-next@npm:14.2.23" +"eslint-config-next@npm:15.3.3": + version: 15.3.3 + resolution: "eslint-config-next@npm:15.3.3" dependencies: - "@next/eslint-plugin-next": "npm:14.2.23" - "@rushstack/eslint-patch": "npm:^1.3.3" + "@next/eslint-plugin-next": "npm:15.3.3" + "@rushstack/eslint-patch": "npm:^1.10.3" "@typescript-eslint/eslint-plugin": "npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0" "@typescript-eslint/parser": "npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0" eslint-import-resolver-node: "npm:^0.3.6" eslint-import-resolver-typescript: "npm:^3.5.2" - eslint-plugin-import: "npm:^2.28.1" - eslint-plugin-jsx-a11y: "npm:^6.7.1" - eslint-plugin-react: "npm:^7.33.2" - eslint-plugin-react-hooks: "npm:^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + eslint-plugin-import: "npm:^2.31.0" + eslint-plugin-jsx-a11y: "npm:^6.10.0" + eslint-plugin-react: "npm:^7.37.0" + eslint-plugin-react-hooks: "npm:^5.0.0" peerDependencies: - eslint: ^7.23.0 || ^8.0.0 + eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 typescript: ">=3.3.1" peerDependenciesMeta: typescript: optional: true - checksum: 10/ee23822e91c155cb0531c786cf11573b8914bc7dfa3b506c45c1d13d4b0c1b515764bd2744daa9c36bddf3bb7cfff434fa6bd169540a2fb933a336df78e7d850 + checksum: 10/63c51a9e4cc3f54073d15aff85b1088459439169721c379af1202df62c3a757c3849a194f46d4ec95a9c202322aa25d1ddab24159addcca290532a09afda3532 languageName: node linkType: hard @@ -2139,17 +2541,16 @@ __metadata: linkType: hard "eslint-import-resolver-typescript@npm:^3.5.2": - version: 3.7.0 - resolution: "eslint-import-resolver-typescript@npm:3.7.0" + version: 3.10.1 + resolution: "eslint-import-resolver-typescript@npm:3.10.1" dependencies: "@nolyfill/is-core-module": "npm:1.0.39" - debug: "npm:^4.3.7" - enhanced-resolve: "npm:^5.15.0" - fast-glob: "npm:^3.3.2" - get-tsconfig: "npm:^4.7.5" - is-bun-module: "npm:^1.0.2" - is-glob: "npm:^4.0.3" - stable-hash: "npm:^0.0.4" + debug: "npm:^4.4.0" + get-tsconfig: "npm:^4.10.0" + is-bun-module: "npm:^2.0.0" + stable-hash: "npm:^0.0.5" + tinyglobby: "npm:^0.2.13" + unrs-resolver: "npm:^1.6.2" peerDependencies: eslint: "*" eslint-plugin-import: "*" @@ -2159,7 +2560,7 @@ __metadata: optional: true eslint-plugin-import-x: optional: true - checksum: 10/8158730c11e562c56ed9bf7236dc75bce35b6992dc32c39ac2f4177ab77fca97b95999850204a6458054243607b54aee88c028a61fed4184f24f425fa1afff01 + checksum: 10/b8d6a9b2045c70f043f722f78c9e65bc0283126f0ad92c8f07473f7647d77f7b1562f765a472f17e06b81897b407091c0ec9f2e4592b158c9fd92d0b0c33de89 languageName: node linkType: hard @@ -2175,7 +2576,7 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-import@npm:^2.28.1": +"eslint-plugin-import@npm:^2.31.0": version: 2.31.0 resolution: "eslint-plugin-import@npm:2.31.0" dependencies: @@ -2204,7 +2605,7 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-jsx-a11y@npm:^6.7.1": +"eslint-plugin-jsx-a11y@npm:^6.10.0": version: 6.10.2 resolution: "eslint-plugin-jsx-a11y@npm:6.10.2" dependencies: @@ -2229,18 +2630,18 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-react-hooks@npm:^4.5.0 || 5.0.0-canary-7118f5dd7-20230705": - version: 5.0.0-canary-7118f5dd7-20230705 - resolution: "eslint-plugin-react-hooks@npm:5.0.0-canary-7118f5dd7-20230705" +"eslint-plugin-react-hooks@npm:^5.0.0": + version: 5.2.0 + resolution: "eslint-plugin-react-hooks@npm:5.2.0" peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - checksum: 10/0e0e80c21552998b7af2b92a74aec5e862b33f6f16c3e1303d42b5821d68b7ba11c2037bd73e3581fc7d97d9a2e64dd19e202d47d6885f53e33aac87c048d641 + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + checksum: 10/ebb79e9cf69ae06e3a7876536653c5e556b5fd8cd9dc49577f10a6e728360e7b6f5ce91f4339b33e93b26e3bb23805418f8b5e75db80baddd617b1dffe73bed1 languageName: node linkType: hard -"eslint-plugin-react@npm:^7.33.2": - version: 7.37.3 - resolution: "eslint-plugin-react@npm:7.37.3" +"eslint-plugin-react@npm:^7.37.0": + version: 7.37.5 + resolution: "eslint-plugin-react@npm:7.37.5" dependencies: array-includes: "npm:^3.1.8" array.prototype.findlast: "npm:^1.2.5" @@ -2252,7 +2653,7 @@ __metadata: hasown: "npm:^2.0.2" jsx-ast-utils: "npm:^2.4.1 || ^3.0.0" minimatch: "npm:^3.1.2" - object.entries: "npm:^1.1.8" + object.entries: "npm:^1.1.9" object.fromentries: "npm:^2.0.8" object.values: "npm:^1.2.1" prop-types: "npm:^15.8.1" @@ -2262,7 +2663,7 @@ __metadata: string.prototype.repeat: "npm:^1.0.0" peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - checksum: 10/30042b95c440a962157192f67369d8c9171f7c73e992695e5674c9d28c3cfe4098488eca86dfa7c433d4c8a91243cbafdc80c7e42d2c1720a427ddb36e65457e + checksum: 10/ee1bd4e0ec64f29109d5a625bb703d179c82e0159c86c3f1b52fc1209d2994625a137dae303c333fb308a2e38315e44066d5204998177e31974382f9fda25d5c languageName: node linkType: hard @@ -2284,9 +2685,9 @@ __metadata: linkType: hard "eslint-visitor-keys@npm:^4.2.0": - version: 4.2.0 - resolution: "eslint-visitor-keys@npm:4.2.0" - checksum: 10/9651b3356b01760e586b4c631c5268c0e1a85236e3292bf754f0472f465bf9a856c0ddc261fceace155334118c0151778effafbab981413dbf9288349343fa25 + version: 4.2.1 + resolution: "eslint-visitor-keys@npm:4.2.1" + checksum: 10/3ee00fc6a7002d4b0ffd9dc99e13a6a7882c557329e6c25ab254220d71e5c9c4f89dca4695352949ea678eb1f3ba912a18ef8aac0a7fe094196fd92f441bfce2 languageName: node linkType: hard @@ -2473,6 +2874,19 @@ __metadata: languageName: node linkType: hard +"fast-glob@npm:3.3.1": + version: 3.3.1 + resolution: "fast-glob@npm:3.3.1" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.4" + checksum: 10/51bcd15472879dfe51d4b01c5b70bbc7652724d39cdd082ba11276dbd7d84db0f6b33757e1938af8b2768a4bf485d9be0c89153beae24ee8331d6dcc7550379f + languageName: node + linkType: hard + "fast-glob@npm:^3.3.2": version: 3.3.3 resolution: "fast-glob@npm:3.3.3" @@ -2501,11 +2915,23 @@ __metadata: linkType: hard "fastq@npm:^1.6.0": - version: 1.18.0 - resolution: "fastq@npm:1.18.0" + version: 1.19.1 + resolution: "fastq@npm:1.19.1" dependencies: reusify: "npm:^1.0.4" - checksum: 10/c5b501333dc8f5d188d828ea162aad03ff5a81aed185b6d4a5078aaeae0a42babc34296d7af13ebce86401cccd93c9b7b3cbf61280821c5f20af233378b42fbb + checksum: 10/75679dc226316341c4f2a6b618571f51eac96779906faecd8921b984e844d6ae42fabb2df69b1071327d398d5716693ea9c9c8941f64ac9e89ec2032ce59d730 + languageName: node + linkType: hard + +"fdir@npm:^6.4.4": + version: 6.4.6 + resolution: "fdir@npm:6.4.6" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10/c186ba387e7b75ccf874a098d9bc5fe0af0e9c52fc56f8eac8e80aa4edb65532684bf2bf769894ff90f53bf221d6136692052d31f07a9952807acae6cbe7ee50 languageName: node linkType: hard @@ -2549,9 +2975,9 @@ __metadata: linkType: hard "flatted@npm:^3.2.9": - version: 3.3.2 - resolution: "flatted@npm:3.3.2" - checksum: 10/ac3c159742e01d0e860a861164bcfd35bb567ccbebb8a0dd041e61cf3c64a435b917dd1e7ed1c380c2ebca85735fb16644485ec33665bc6aafc3b316aa1eed44 + version: 3.3.3 + resolution: "flatted@npm:3.3.3" + checksum: 10/8c96c02fbeadcf4e8ffd0fa24983241e27698b0781295622591fc13585e2f226609d95e422bcf2ef044146ffacb6b68b1f20871454eddf75ab3caa6ee5f4a1fe languageName: node linkType: hard @@ -2564,22 +2990,12 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^3.1.0": - version: 3.3.0 - resolution: "foreground-child@npm:3.3.0" +"framer-motion@npm:^12.17.0": + version: 12.17.0 + resolution: "framer-motion@npm:12.17.0" dependencies: - cross-spawn: "npm:^7.0.0" - signal-exit: "npm:^4.0.1" - checksum: 10/e3a60480f3a09b12273ce2c5fcb9514d98dd0e528f58656a1b04680225f918d60a2f81f6a368f2f3b937fcee9cfc0cbf16f1ad9a0bc6a3a6e103a84c9a90087e - languageName: node - linkType: hard - -"framer-motion@npm:^12.5.0": - version: 12.5.0 - resolution: "framer-motion@npm:12.5.0" - dependencies: - motion-dom: "npm:^12.5.0" - motion-utils: "npm:^12.5.0" + motion-dom: "npm:^12.17.0" + motion-utils: "npm:^12.12.1" tslib: "npm:^2.4.0" peerDependencies: "@emotion/is-prop-valid": "*" @@ -2592,7 +3008,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10/6659e0b704d3216dafdcb11175e3c5f55d536469ddd8ad62b37d00bc7e893d914989791c018cfdfa17b3142b655f0d85226adc94ed390c1524312c3653b8c75f + checksum: 10/803b57cb7ec7200938c9425392a66839c96bf5a800ff14b38d9e689a47341678c53ce54765f68f0a186cfad6c1270b2ad46a75aeff67b2a2e2f3b846852d792c languageName: node linkType: hard @@ -2670,12 +3086,12 @@ __metadata: languageName: node linkType: hard -"get-tsconfig@npm:^4.7.5": - version: 4.8.1 - resolution: "get-tsconfig@npm:4.8.1" +"get-tsconfig@npm:^4.10.0": + version: 4.10.1 + resolution: "get-tsconfig@npm:4.10.1" dependencies: resolve-pkg-maps: "npm:^1.0.0" - checksum: 10/3fb5a8ad57b9633eaea085d81661e9e5c9f78b35d8f8689eaf8b8b45a2a3ebf3b3422266d4d7df765e308cc1e6231648d114803ab3d018332e29916f2c1de036 + checksum: 10/04d63f47fdecaefbd1f73ec02949be4ec4db7d6d9fbc8d4e81f9a4bb1c6f876e48943712f2f9236643d3e4d61d9a7b06da08564d08b034631ebe3f5605bef237 languageName: node linkType: hard @@ -2697,21 +3113,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:10.3.10": - version: 10.3.10 - resolution: "glob@npm:10.3.10" - dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^2.3.5" - minimatch: "npm:^9.0.1" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry: "npm:^1.10.1" - bin: - glob: dist/esm/bin.mjs - checksum: 10/38bdb2c9ce75eb5ed168f309d4ed05b0798f640b637034800a6bf306f39d35409bf278b0eaaffaec07591085d3acb7184a201eae791468f0f617771c2486a6a8 - languageName: node - linkType: hard - "glob@npm:^7.1.3": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -2752,13 +3153,6 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4": - version: 4.2.11 - resolution: "graceful-fs@npm:4.2.11" - checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 - languageName: node - linkType: hard - "graphemer@npm:^1.4.0": version: 1.4.0 resolution: "graphemer@npm:1.4.0" @@ -2814,7 +3208,7 @@ __metadata: languageName: node linkType: hard -"hasown@npm:^2.0.0, hasown@npm:^2.0.2": +"hasown@npm:^2.0.2": version: 2.0.2 resolution: "hasown@npm:2.0.2" dependencies: @@ -2824,8 +3218,8 @@ __metadata: linkType: hard "hast-util-to-estree@npm:^3.0.0": - version: 3.1.1 - resolution: "hast-util-to-estree@npm:3.1.1" + version: 3.1.3 + resolution: "hast-util-to-estree@npm:3.1.3" dependencies: "@types/estree": "npm:^1.0.0" "@types/estree-jsx": "npm:^1.0.0" @@ -2838,18 +3232,18 @@ __metadata: mdast-util-mdx-expression: "npm:^2.0.0" mdast-util-mdx-jsx: "npm:^3.0.0" mdast-util-mdxjs-esm: "npm:^2.0.0" - property-information: "npm:^6.0.0" + property-information: "npm:^7.0.0" space-separated-tokens: "npm:^2.0.0" - style-to-object: "npm:^1.0.0" + style-to-js: "npm:^1.0.0" unist-util-position: "npm:^5.0.0" zwitch: "npm:^2.0.0" - checksum: 10/f72b0d72d6f3e1d55a876e175716b78bf93b8246fac8dd6db6b46c03b0288d03f40644606f36c00e8f12829a6bef0e7a73b405825b651a94b41f0bc5b53b92b4 + checksum: 10/efe69c8af68f021d853e70916c6e940765be519aec8703765898c1c3814424b0470f70c0272cf4ac06dcaf6d6f3cc781ebf034701ed240a33ac691d1f5eaf65b languageName: node linkType: hard "hast-util-to-html@npm:^9.0.4": - version: 9.0.4 - resolution: "hast-util-to-html@npm:9.0.4" + version: 9.0.5 + resolution: "hast-util-to-html@npm:9.0.5" dependencies: "@types/hast": "npm:^3.0.0" "@types/unist": "npm:^3.0.0" @@ -2858,17 +3252,17 @@ __metadata: hast-util-whitespace: "npm:^3.0.0" html-void-elements: "npm:^3.0.0" mdast-util-to-hast: "npm:^13.0.0" - property-information: "npm:^6.0.0" + property-information: "npm:^7.0.0" space-separated-tokens: "npm:^2.0.0" stringify-entities: "npm:^4.0.0" zwitch: "npm:^2.0.4" - checksum: 10/a0b4ed9058e57fa2ca010d10c077fda78d2ab2af99f5bd09fe4b9948970025ac4a2a1a03ec7e2e0f3b0444066b1b35d602fa3e9fbd9b7fc9cdd35d0cafa909ca + checksum: 10/4278e5246f43adf7a30bf3b87db4a4e628b5362b8c149eb4360bf7e0f731f81fa31e4e844e8090d1dd5f739d0884678f8165b28f97c14139e4da4f945f9ff5f7 languageName: node linkType: hard "hast-util-to-jsx-runtime@npm:^2.0.0": - version: 2.3.2 - resolution: "hast-util-to-jsx-runtime@npm:2.3.2" + version: 2.3.6 + resolution: "hast-util-to-jsx-runtime@npm:2.3.6" dependencies: "@types/estree": "npm:^1.0.0" "@types/hast": "npm:^3.0.0" @@ -2880,12 +3274,12 @@ __metadata: mdast-util-mdx-expression: "npm:^2.0.0" mdast-util-mdx-jsx: "npm:^3.0.0" mdast-util-mdxjs-esm: "npm:^2.0.0" - property-information: "npm:^6.0.0" + property-information: "npm:^7.0.0" space-separated-tokens: "npm:^2.0.0" - style-to-object: "npm:^1.0.0" + style-to-js: "npm:^1.0.0" unist-util-position: "npm:^5.0.0" vfile-message: "npm:^4.0.0" - checksum: 10/3d72f83e2d8c29adc6576d2c6b41479902fd51fac8cfb2b67c35fd68fcb9c25c274699442e4dee901a7ab926a0ff6851713ed5d92448ac09ae0f10daf293476c + checksum: 10/111bd69f482952c7591cb4e1d3face25f1c18849b310a4d6cacc91e2d2cbc965d455fad35c059b8f0cfd762e933b826a7090b6f3098dece08307a6569de8f1d8 languageName: node linkType: hard @@ -2898,6 +3292,34 @@ __metadata: languageName: node linkType: hard +"html-dom-parser@npm:5.1.1": + version: 5.1.1 + resolution: "html-dom-parser@npm:5.1.1" + dependencies: + domhandler: "npm:5.0.3" + htmlparser2: "npm:10.0.0" + checksum: 10/85bbaa8699d40d54fd64f5472088199178a6c3dd6a907358ac476d9c511cb8ab10bff1469a785411e8b831a73ac08348392e56076eab6d100a3ec19dccc722ad + languageName: node + linkType: hard + +"html-react-parser@npm:^5.2.5": + version: 5.2.5 + resolution: "html-react-parser@npm:5.2.5" + dependencies: + domhandler: "npm:5.0.3" + html-dom-parser: "npm:5.1.1" + react-property: "npm:2.0.2" + style-to-js: "npm:1.1.16" + peerDependencies: + "@types/react": 0.14 || 15 || 16 || 17 || 18 || 19 + react: 0.14 || 15 || 16 || 17 || 18 || 19 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/22852dc4826d3be9506e238e37a05c23b432675aac33f22bd5741caa195a32e99de99d2b99037fda532f198afbcdeacfc0b27627d6e529143a98c8f191e99c52 + languageName: node + linkType: hard + "html-void-elements@npm:^3.0.0": version: 3.0.0 resolution: "html-void-elements@npm:3.0.0" @@ -2905,20 +3327,39 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.2.0, ignore@npm:^5.3.1": +"htmlparser2@npm:10.0.0": + version: 10.0.0 + resolution: "htmlparser2@npm:10.0.0" + dependencies: + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + domutils: "npm:^3.2.1" + entities: "npm:^6.0.0" + checksum: 10/768870f0e020dca19dc45df206cb6ac466c5dba6566c8fca4ca880347eed409f9977028d08644ac516bca8628ac9c7ded5a3847dc3ee1c043f049abf9e817154 + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": version: 5.3.2 resolution: "ignore@npm:5.3.2" checksum: 10/cceb6a457000f8f6a50e1196429750d782afce5680dd878aa4221bd79972d68b3a55b4b1458fc682be978f4d3c6a249046aa0880637367216444ab7b014cfc98 languageName: node linkType: hard +"ignore@npm:^7.0.0": + version: 7.0.5 + resolution: "ignore@npm:7.0.5" + checksum: 10/f134b96a4de0af419196f52c529d5c6120c4456ff8a6b5a14ceaaa399f883e15d58d2ce651c9b69b9388491d4669dda47285d307e827de9304a53a1824801bc6 + languageName: node + linkType: hard + "import-fresh@npm:^3.2.1": - version: 3.3.0 - resolution: "import-fresh@npm:3.3.0" + version: 3.3.1 + resolution: "import-fresh@npm:3.3.1" dependencies: parent-module: "npm:^1.0.0" resolve-from: "npm:^4.0.0" - checksum: 10/2cacfad06e652b1edc50be650f7ec3be08c5e5a6f6d12d035c440a42a8cc028e60a5b99ca08a77ab4d6b1346da7d971915828f33cdab730d3d42f08242d09baa + checksum: 10/a06b19461b4879cc654d46f8a6244eb55eb053437afd4cbb6613cad6be203811849ed3e4ea038783092879487299fda24af932b86bdfff67c9055ba3612b8c87 languageName: node linkType: hard @@ -3002,15 +3443,23 @@ __metadata: languageName: node linkType: hard +"is-arrayish@npm:^0.3.1": + version: 0.3.2 + resolution: "is-arrayish@npm:0.3.2" + checksum: 10/81a78d518ebd8b834523e25d102684ee0f7e98637136d3bdc93fd09636350fa06f1d8ca997ea28143d4d13cb1b69c0824f082db0ac13e1ab3311c10ffea60ade + languageName: node + linkType: hard + "is-async-function@npm:^2.0.0": - version: 2.1.0 - resolution: "is-async-function@npm:2.1.0" + version: 2.1.1 + resolution: "is-async-function@npm:2.1.1" dependencies: + async-function: "npm:^1.0.0" call-bound: "npm:^1.0.3" get-proto: "npm:^1.0.1" has-tostringtag: "npm:^1.0.2" safe-regex-test: "npm:^1.1.0" - checksum: 10/865f0e915b7d9aa5577327e7550bf77a4bb2b7bca497d32564e1d32dbe0ccb7eca1c9c56dd679b6dd2bd7feddb91574e773922276871a5958e53ae8473db4742 + checksum: 10/7c2ac7efdf671e03265e74a043bcb1c0a32e226bc2a42dfc5ec8644667df668bbe14b91c08e6c1414f392f8cf86cd1d489b3af97756e2c7a49dd1ba63fd40ca6 languageName: node linkType: hard @@ -3024,21 +3473,21 @@ __metadata: linkType: hard "is-boolean-object@npm:^1.2.1": - version: 1.2.1 - resolution: "is-boolean-object@npm:1.2.1" + version: 1.2.2 + resolution: "is-boolean-object@npm:1.2.2" dependencies: - call-bound: "npm:^1.0.2" + call-bound: "npm:^1.0.3" has-tostringtag: "npm:^1.0.2" - checksum: 10/5a15524635c9334ebbd668f20a6cbf023adceed5725ec96a50056d21ae65f52759d04a8fa7d7febf00ff3bc4e6d3837638eb84be572f287bcfd15f8b8facde43 + checksum: 10/051fa95fdb99d7fbf653165a7e6b2cba5d2eb62f7ffa81e793a790f3fb5366c91c1b7b6af6820aa2937dd86c73aa3ca9d9ca98f500988457b1c59692c52ba911 languageName: node linkType: hard -"is-bun-module@npm:^1.0.2": - version: 1.3.0 - resolution: "is-bun-module@npm:1.3.0" +"is-bun-module@npm:^2.0.0": + version: 2.0.0 + resolution: "is-bun-module@npm:2.0.0" dependencies: - semver: "npm:^7.6.3" - checksum: 10/b23d9ec7b4d4bfd89e4e72b5cd52e1bc153facad59fdd7394c656f8859a78740ef35996a2066240a32f39cc9a9da4b4eb69e68df3c71755a61ebbaf56d3daef0 + semver: "npm:^7.7.1" + checksum: 10/cded5a1a58368b847872d08617975d620ad94426d76a932f3e08d55b4574d199e0a62a4fb024fa2dc444200b71719eb0bffc5d3d1e1cc82e29b293bb8d66a990 languageName: node linkType: hard @@ -3111,13 +3560,6 @@ __metadata: languageName: node linkType: hard -"is-fullwidth-code-point@npm:^3.0.0": - version: 3.0.0 - resolution: "is-fullwidth-code-point@npm:3.0.0" - checksum: 10/44a30c29457c7fb8f00297bce733f0a64cd22eca270f83e58c105e0d015e45c019491a4ab2faef91ab51d4738c670daff901c799f6a700e27f7314029e99e348 - languageName: node - linkType: hard - "is-generator-function@npm:^1.0.10, is-generator-function@npm:^1.0.7": version: 1.1.0 resolution: "is-generator-function@npm:1.1.0" @@ -3153,6 +3595,13 @@ __metadata: languageName: node linkType: hard +"is-negative-zero@npm:^2.0.3": + version: 2.0.3 + resolution: "is-negative-zero@npm:2.0.3" + checksum: 10/8fe5cffd8d4fb2ec7b49d657e1691889778d037494c6f40f4d1a524cadd658b4b53ad7b6b73a59bcb4b143ae9a3d15829af864b2c0f9d65ac1e678c4c80f17e5 + languageName: node + linkType: hard + "is-number-object@npm:^1.1.1": version: 1.1.1 resolution: "is-number-object@npm:1.1.1" @@ -3212,7 +3661,7 @@ __metadata: languageName: node linkType: hard -"is-string@npm:^1.0.7, is-string@npm:^1.1.1": +"is-string@npm:^1.1.1": version: 1.1.1 resolution: "is-string@npm:1.1.1" dependencies: @@ -3249,12 +3698,12 @@ __metadata: languageName: node linkType: hard -"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.0": - version: 1.1.0 - resolution: "is-weakref@npm:1.1.0" +"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.1": + version: 1.1.1 + resolution: "is-weakref@npm:1.1.1" dependencies: - call-bound: "npm:^1.0.2" - checksum: 10/89e627cc1763ea110574bb408fcf060ede47e70437d9278858bc939e3b3f7e4b7c558610b733da5f2ad6084d9f12b9c714b011ccf3fa771ec87e221c22bed910 + call-bound: "npm:^1.0.3" + checksum: 10/543506fd8259038b371bb083aac25b16cb4fd8b12fc58053aa3d45ac28dfd001cd5c6dffbba7aeea4213c74732d46b6cb2cfb5b412eed11f2db524f3f97d09a0 languageName: node linkType: hard @@ -3305,20 +3754,7 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^2.3.5": - version: 2.3.6 - resolution: "jackspeak@npm:2.3.6" - dependencies: - "@isaacs/cliui": "npm:^8.0.2" - "@pkgjs/parseargs": "npm:^0.11.0" - dependenciesMeta: - "@pkgjs/parseargs": - optional: true - checksum: 10/6e6490d676af8c94a7b5b29b8fd5629f21346911ebe2e32931c2a54210134408171c24cee1a109df2ec19894ad04a429402a8438cbf5cc2794585d35428ace76 - languageName: node - linkType: hard - -"js-tokens@npm:^3.0.0 || ^4.0.0": +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" checksum: 10/af37d0d913fb56aec6dc0074c163cc71cd23c0b8aad5c2350747b6721d37ba118af35abdd8b33c47ec2800de07dedb16a527ca9c530ee004093e04958bd0cbf2 @@ -3422,91 +3858,91 @@ __metadata: languageName: node linkType: hard -"lightningcss-darwin-arm64@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-darwin-arm64@npm:1.29.3" +"lightningcss-darwin-arm64@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-darwin-arm64@npm:1.30.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"lightningcss-darwin-x64@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-darwin-x64@npm:1.29.3" +"lightningcss-darwin-x64@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-darwin-x64@npm:1.30.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"lightningcss-freebsd-x64@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-freebsd-x64@npm:1.29.3" +"lightningcss-freebsd-x64@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-freebsd-x64@npm:1.30.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"lightningcss-linux-arm-gnueabihf@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-linux-arm-gnueabihf@npm:1.29.3" +"lightningcss-linux-arm-gnueabihf@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.30.1" conditions: os=linux & cpu=arm languageName: node linkType: hard -"lightningcss-linux-arm64-gnu@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-linux-arm64-gnu@npm:1.29.3" +"lightningcss-linux-arm64-gnu@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-linux-arm64-gnu@npm:1.30.1" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-arm64-musl@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-linux-arm64-musl@npm:1.29.3" +"lightningcss-linux-arm64-musl@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-linux-arm64-musl@npm:1.30.1" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"lightningcss-linux-x64-gnu@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-linux-x64-gnu@npm:1.29.3" +"lightningcss-linux-x64-gnu@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-linux-x64-gnu@npm:1.30.1" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"lightningcss-linux-x64-musl@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-linux-x64-musl@npm:1.29.3" +"lightningcss-linux-x64-musl@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-linux-x64-musl@npm:1.30.1" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"lightningcss-win32-arm64-msvc@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-win32-arm64-msvc@npm:1.29.3" +"lightningcss-win32-arm64-msvc@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-win32-arm64-msvc@npm:1.30.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"lightningcss-win32-x64-msvc@npm:1.29.3": - version: 1.29.3 - resolution: "lightningcss-win32-x64-msvc@npm:1.29.3" +"lightningcss-win32-x64-msvc@npm:1.30.1": + version: 1.30.1 + resolution: "lightningcss-win32-x64-msvc@npm:1.30.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "lightningcss@npm:^1.28.2": - version: 1.29.3 - resolution: "lightningcss@npm:1.29.3" + version: 1.30.1 + resolution: "lightningcss@npm:1.30.1" dependencies: detect-libc: "npm:^2.0.3" - lightningcss-darwin-arm64: "npm:1.29.3" - lightningcss-darwin-x64: "npm:1.29.3" - lightningcss-freebsd-x64: "npm:1.29.3" - lightningcss-linux-arm-gnueabihf: "npm:1.29.3" - lightningcss-linux-arm64-gnu: "npm:1.29.3" - lightningcss-linux-arm64-musl: "npm:1.29.3" - lightningcss-linux-x64-gnu: "npm:1.29.3" - lightningcss-linux-x64-musl: "npm:1.29.3" - lightningcss-win32-arm64-msvc: "npm:1.29.3" - lightningcss-win32-x64-msvc: "npm:1.29.3" + lightningcss-darwin-arm64: "npm:1.30.1" + lightningcss-darwin-x64: "npm:1.30.1" + lightningcss-freebsd-x64: "npm:1.30.1" + lightningcss-linux-arm-gnueabihf: "npm:1.30.1" + lightningcss-linux-arm64-gnu: "npm:1.30.1" + lightningcss-linux-arm64-musl: "npm:1.30.1" + lightningcss-linux-x64-gnu: "npm:1.30.1" + lightningcss-linux-x64-musl: "npm:1.30.1" + lightningcss-win32-arm64-msvc: "npm:1.30.1" + lightningcss-win32-x64-msvc: "npm:1.30.1" dependenciesMeta: lightningcss-darwin-arm64: optional: true @@ -3528,7 +3964,7 @@ __metadata: optional: true lightningcss-win32-x64-msvc: optional: true - checksum: 10/90933a666975f8e4f2b1bed1ec698a0072f96d2cd118e358932456a46105d62b72b701e4a5da5bd66f0b6e3beac2c65d0be2306acdf3f30603d86143502cce88 + checksum: 10/6c921135216cc498dfcb87e35dffe8432e99306cbd58009c598b1daf20c81cc14535abbd4c1066e5d1faf4080ed44a2995e8ecc343633db4897a2d041b76fb05 languageName: node linkType: hard @@ -3555,7 +3991,7 @@ __metadata: languageName: node linkType: hard -"loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": +"loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" dependencies: @@ -3566,13 +4002,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^10.2.0": - version: 10.4.3 - resolution: "lru-cache@npm:10.4.3" - checksum: 10/e6e90267360476720fa8e83cc168aa2bf0311f3f2eea20a6ba78b90a885ae72071d9db132f40fda4129c803e7dcec3a6b6a6fbb44ca90b081630b810b5d6a41a - languageName: node - linkType: hard - "markdown-extensions@npm:^2.0.0": version: 2.0.0 resolution: "markdown-extensions@npm:2.0.0" @@ -3622,8 +4051,8 @@ __metadata: linkType: hard "mdast-util-mdx-jsx@npm:^3.0.0": - version: 3.1.3 - resolution: "mdast-util-mdx-jsx@npm:3.1.3" + version: 3.2.0 + resolution: "mdast-util-mdx-jsx@npm:3.2.0" dependencies: "@types/estree-jsx": "npm:^1.0.0" "@types/hast": "npm:^3.0.0" @@ -3637,7 +4066,7 @@ __metadata: stringify-entities: "npm:^4.0.0" unist-util-stringify-position: "npm:^4.0.0" vfile-message: "npm:^4.0.0" - checksum: 10/6c14f271f1380fd512038247f45887b7aa71bbf4acd8881651a317b61706b114f2582f62f7777d0eacd42c4a7b979802825c2a2fd8bb7c46a1ab931ccb1ddf3e + checksum: 10/62cd650a522e5d72ea6afd6d4a557fc86525b802d097a29a2fbe17d22e7b97c502a580611873e4d685777fe77c6ff8d39fb6e37d026b3acbc86c3b24927f4ad9 languageName: node linkType: hard @@ -3654,7 +4083,7 @@ __metadata: languageName: node linkType: hard -"mdast-util-mdxjs-esm@npm:^2.0.0": +"mdast-util-mdxjs-esm@npm:^2.0.0, mdast-util-mdxjs-esm@npm:^2.0.1": version: 2.0.1 resolution: "mdast-util-mdxjs-esm@npm:2.0.1" dependencies: @@ -3729,8 +4158,8 @@ __metadata: linkType: hard "micromark-core-commonmark@npm:^2.0.0": - version: 2.0.2 - resolution: "micromark-core-commonmark@npm:2.0.2" + version: 2.0.3 + resolution: "micromark-core-commonmark@npm:2.0.3" dependencies: decode-named-character-reference: "npm:^1.0.0" devlop: "npm:^1.0.0" @@ -3748,13 +4177,13 @@ __metadata: micromark-util-subtokenize: "npm:^2.0.0" micromark-util-symbol: "npm:^2.0.0" micromark-util-types: "npm:^2.0.0" - checksum: 10/eafa6b9cd6fd9f51efa7795824af9a765e24a4519855a5b6dfcb0f619a93d90599d39a261f626bfcc1dfa64f22430f7a677a83cb6ce4bd8e4eeabc892610c016 + checksum: 10/2b98b9eba1463850ebd8f338f966bd2113dafe764b490ebee3dccab3764d3c48b53fe67673297530e56bf54f58de27dfd1952ed79c5b4e32047cb7f29bd807f2 languageName: node linkType: hard "micromark-extension-mdx-expression@npm:^3.0.0": - version: 3.0.0 - resolution: "micromark-extension-mdx-expression@npm:3.0.0" + version: 3.0.1 + resolution: "micromark-extension-mdx-expression@npm:3.0.1" dependencies: "@types/estree": "npm:^1.0.0" devlop: "npm:^1.0.0" @@ -3764,15 +4193,14 @@ __metadata: micromark-util-events-to-acorn: "npm:^2.0.0" micromark-util-symbol: "npm:^2.0.0" micromark-util-types: "npm:^2.0.0" - checksum: 10/a5592160319d4617362f6b72a6fc44b5570466afa07419d44bcfdd9398a77a5693d7c5f8da7b3ff4682edf6209d4781835f5d2e3166fdf6bba37db456fd2d091 + checksum: 10/a185e1787fe6d49d0e435690affd4b83ce319f88a08c57d2460d37d5c0a75ea64aa49a4a116b6d37f91389dc04351e1826aa834519a9f25fc31e1424962c6eb7 languageName: node linkType: hard "micromark-extension-mdx-jsx@npm:^3.0.0": - version: 3.0.1 - resolution: "micromark-extension-mdx-jsx@npm:3.0.1" + version: 3.0.2 + resolution: "micromark-extension-mdx-jsx@npm:3.0.2" dependencies: - "@types/acorn": "npm:^4.0.0" "@types/estree": "npm:^1.0.0" devlop: "npm:^1.0.0" estree-util-is-identifier-name: "npm:^3.0.0" @@ -3783,7 +4211,7 @@ __metadata: micromark-util-symbol: "npm:^2.0.0" micromark-util-types: "npm:^2.0.0" vfile-message: "npm:^4.0.0" - checksum: 10/2cc0277d91c3c85d52e88755d17d021b5eab6fa03a578a9965f9d3d2c184dbc1accce63e7f8437a092ceeb602840ef451d4dce6dc9e8c13df0bc76e741080a89 + checksum: 10/a85cdb7c972fbb2cc8f0a64adc808b2b62bc2d79dbdd31fcd3208ff15aafa0198b002022840b2c65b5bff6f2a8c2c4a59a32a89f3482e6e183114b476e98e25c languageName: node linkType: hard @@ -3853,8 +4281,8 @@ __metadata: linkType: hard "micromark-factory-mdx-expression@npm:^2.0.0": - version: 2.0.2 - resolution: "micromark-factory-mdx-expression@npm:2.0.2" + version: 2.0.3 + resolution: "micromark-factory-mdx-expression@npm:2.0.3" dependencies: "@types/estree": "npm:^1.0.0" devlop: "npm:^1.0.0" @@ -3865,7 +4293,7 @@ __metadata: micromark-util-types: "npm:^2.0.0" unist-util-position-from-estree: "npm:^2.0.0" vfile-message: "npm:^4.0.0" - checksum: 10/d5285fa98018f14a058c7cd4a961aacedd2d3c4f4fddd4c58c16f1e640d1284a8f581f4d00fa3e18c06ed302ce23bca23f6a01edd66064c23c9057e65385a62d + checksum: 10/afadae88a18f31afa564747101e076011c56457454b30294ae55aeea7efee8626ddc3bad0f0f43649008f89b8784782b5adec143fdf477fb352354d76f08db55 languageName: node linkType: hard @@ -3972,10 +4400,9 @@ __metadata: linkType: hard "micromark-util-events-to-acorn@npm:^2.0.0": - version: 2.0.2 - resolution: "micromark-util-events-to-acorn@npm:2.0.2" + version: 2.0.3 + resolution: "micromark-util-events-to-acorn@npm:2.0.3" dependencies: - "@types/acorn": "npm:^4.0.0" "@types/estree": "npm:^1.0.0" "@types/unist": "npm:^3.0.0" devlop: "npm:^1.0.0" @@ -3983,7 +4410,7 @@ __metadata: micromark-util-symbol: "npm:^2.0.0" micromark-util-types: "npm:^2.0.0" vfile-message: "npm:^4.0.0" - checksum: 10/475367e716c4d24f2a57464a7f2c8aa507ae36c05b7767fd652895525f3f0a1179ea3219cabccc0f3038bb5e4f9cce5390d530dc56decaa5f1786bda42739810 + checksum: 10/0d87e49b897636dc0e84b4bd06b6fa9e6abcd40ab90c9431e36737c85c444d3db1e4f9b8f51433422b1bedc46f086890ce96671b5a795230c6b7b09cb53d9aba languageName: node linkType: hard @@ -4024,14 +4451,14 @@ __metadata: linkType: hard "micromark-util-subtokenize@npm:^2.0.0": - version: 2.0.3 - resolution: "micromark-util-subtokenize@npm:2.0.3" + version: 2.1.0 + resolution: "micromark-util-subtokenize@npm:2.1.0" dependencies: devlop: "npm:^1.0.0" micromark-util-chunked: "npm:^2.0.0" micromark-util-symbol: "npm:^2.0.0" micromark-util-types: "npm:^2.0.0" - checksum: 10/57b26f129f46424a4670bb47c50f13c7309bb1bc557c02150a788d1634337c1bb25a3523af3d6dffc29aaec873b3bd88fd931bfff34e64284e6436e23873ca22 + checksum: 10/5f18c70cb952a414a4d161f5d6a5254d33c7dfcd56577e592ef2e172a0414058d3531a3554f43538f14e243592fffbc2e68ddaf6a41c54577b3ba7beb555d3dc languageName: node linkType: hard @@ -4043,15 +4470,15 @@ __metadata: linkType: hard "micromark-util-types@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-util-types@npm:2.0.1" - checksum: 10/69c5e18e6ba4e12473d6fe5f1a7cc113ac1d4bfc23c7ad57b16a5e4bfd09ef48b7c17a40c39d43996f2078ad898efd3f1945007c14f395abd55f2af03d413acd + version: 2.0.2 + resolution: "micromark-util-types@npm:2.0.2" + checksum: 10/a9eb067bd9384eab61942285d53738aa22f3fef4819eaf20249bec6ec13f1e4da2800230fd0ceb7e705108987aa9062fe3e9a8e5e48aa60180db80b9489dc3e2 languageName: node linkType: hard "micromark@npm:^4.0.0": - version: 4.0.1 - resolution: "micromark@npm:4.0.1" + version: 4.0.2 + resolution: "micromark@npm:4.0.2" dependencies: "@types/debug": "npm:^4.0.0" debug: "npm:^4.0.0" @@ -4070,11 +4497,11 @@ __metadata: micromark-util-subtokenize: "npm:^2.0.0" micromark-util-symbol: "npm:^2.0.0" micromark-util-types: "npm:^2.0.0" - checksum: 10/b948b1b239e589826bdaf2835daa9e88873e23d4b9148cd22109a86d4af55b96345cf9fc9059b6b19ae828f64d55e66f376ca3aeb4af3d2b0241560125f5dae6 + checksum: 10/1b85e49c8f71013df2d07a59e477deb72cd325d41cc15f35b2aa52b8b7a93fed45498ce3e18ed34464a9afa9ba8a9210b2509454b2a2d16ac06c7429f562bfac languageName: node linkType: hard -"micromatch@npm:^4.0.8": +"micromatch@npm:^4.0.4, micromatch@npm:^4.0.8": version: 4.0.8 resolution: "micromatch@npm:4.0.8" dependencies: @@ -4093,7 +4520,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.1, minimatch@npm:^9.0.4": +"minimatch@npm:^9.0.4": version: 9.0.5 resolution: "minimatch@npm:9.0.5" dependencies: @@ -4109,34 +4536,27 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0": - version: 7.1.2 - resolution: "minipass@npm:7.1.2" - checksum: 10/c25f0ee8196d8e6036661104bacd743785b2599a21de5c516b32b3fa2b83113ac89a2358465bc04956baab37ffb956ae43be679b2262bf7be15fce467ccd7950 - languageName: node - linkType: hard - -"motion-dom@npm:^12.5.0": - version: 12.5.0 - resolution: "motion-dom@npm:12.5.0" +"motion-dom@npm:^12.17.0": + version: 12.17.0 + resolution: "motion-dom@npm:12.17.0" dependencies: - motion-utils: "npm:^12.5.0" - checksum: 10/289e3d5567053e9b84223df9b482bb9180732c0cb6276e7982a73ddad0f76e47f8e0f69e4cdf93d7a689ad021fce837fa8a19c8b61bf56d71bdfc0e590bc3e7e + motion-utils: "npm:^12.12.1" + checksum: 10/54b482d643ca12d895e8a0860809b2b53f2e7c2981d12f3d1230f5bd6154cb59df6ab81a690681df7a526da283aaf76acbf351b148e2b805ad5f8ce97156e061 languageName: node linkType: hard -"motion-utils@npm:^12.5.0": - version: 12.5.0 - resolution: "motion-utils@npm:12.5.0" - checksum: 10/f2d754a9e1c4cd829f0703ce47bbeb3e9967994a9cb35e759f34de16af6c520a4959b04ff6998211ca67dc7bfe0002d9b2465858167b1971b0403f63289f97fc +"motion-utils@npm:^12.12.1": + version: 12.12.1 + resolution: "motion-utils@npm:12.12.1" + checksum: 10/679503df10a1cd7ad58f9ed2f864acb5ecbadf380a668874f0cc6bdc1155278bac0cce73e3f703df19532f2bd922e3500fde179e8f76386e9651f8b1bd0cfe6d languageName: node linkType: hard "motion@npm:^12.4.1": - version: 12.5.0 - resolution: "motion@npm:12.5.0" + version: 12.17.0 + resolution: "motion@npm:12.17.0" dependencies: - framer-motion: "npm:^12.5.0" + framer-motion: "npm:^12.17.0" tslib: "npm:^2.4.0" peerDependencies: "@emotion/is-prop-valid": "*" @@ -4149,7 +4569,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10/15910d37a14b91236c27c8cc6d12ae157cd1e6b55700fc58a0d271954236fae8e676b1ac9f705cb770c235892925fc6bbdb1f612dfb4018a5d4c0171964f5b3a + checksum: 10/4fde3c4f39fa872cb27a41ddf39dcf20a9a066428ce07cf7455e2a4779d916a47cb3141e8c4c970f9d7b8e8818b822f3eeb86761d61904a18383b82426f5f3ca languageName: node linkType: hard @@ -4161,11 +4581,20 @@ __metadata: linkType: hard "nanoid@npm:^3.3.6": - version: 3.3.8 - resolution: "nanoid@npm:3.3.8" + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" bin: nanoid: bin/nanoid.cjs - checksum: 10/2d1766606cf0d6f47b6f0fdab91761bb81609b2e3d367027aff45e6ee7006f660fb7e7781f4a34799fe6734f1268eeed2e37a5fdee809ade0c2d4eb11b0f9c40 + checksum: 10/73b5afe5975a307aaa3c95dfe3334c52cdf9ae71518176895229b8d65ab0d1c0417dd081426134eb7571c055720428ea5d57c645138161e7d10df80815527c48 + languageName: node + linkType: hard + +"napi-postinstall@npm:^0.2.2": + version: 0.2.4 + resolution: "napi-postinstall@npm:0.2.4" + bin: + napi-postinstall: lib/cli.js + checksum: 10/286785f884b872102fb284847ecc693101f70126b1fc7a97e19293929ce7f08802b41f89398015cce0797070ea3ce6871939a3c1e693c04cf594f7939dbe8cfb languageName: node linkType: hard @@ -4176,31 +4605,50 @@ __metadata: languageName: node linkType: hard -"next@npm:14.2.26": - version: 14.2.26 - resolution: "next@npm:14.2.26" +"next-mdx-remote-client@npm:^2.1.2": + version: 2.1.2 + resolution: "next-mdx-remote-client@npm:2.1.2" dependencies: - "@next/env": "npm:14.2.26" - "@next/swc-darwin-arm64": "npm:14.2.26" - "@next/swc-darwin-x64": "npm:14.2.26" - "@next/swc-linux-arm64-gnu": "npm:14.2.26" - "@next/swc-linux-arm64-musl": "npm:14.2.26" - "@next/swc-linux-x64-gnu": "npm:14.2.26" - "@next/swc-linux-x64-musl": "npm:14.2.26" - "@next/swc-win32-arm64-msvc": "npm:14.2.26" - "@next/swc-win32-ia32-msvc": "npm:14.2.26" - "@next/swc-win32-x64-msvc": "npm:14.2.26" - "@swc/helpers": "npm:0.5.5" + "@babel/code-frame": "npm:^7.27.1" + "@mdx-js/mdx": "npm:^3.1.0" + "@mdx-js/react": "npm:^3.1.0" + remark-mdx-remove-esm: "npm:^1.1.0" + serialize-error: "npm:^12.0.0" + vfile: "npm:^6.0.3" + vfile-matter: "npm:^5.0.1" + peerDependencies: + react: ^19.1.0 + react-dom: ^19.1.0 + checksum: 10/610e95bff6c1dcdb4d1fefc9333ac748f1899cbca1ea551e784cd1e8cfd077c14d6bb8ab27826886f354cc3b9cf700fe25810263f996c53f824a2cfdbc5a064f + languageName: node + linkType: hard + +"next@npm:15.3.3": + version: 15.3.3 + resolution: "next@npm:15.3.3" + dependencies: + "@next/env": "npm:15.3.3" + "@next/swc-darwin-arm64": "npm:15.3.3" + "@next/swc-darwin-x64": "npm:15.3.3" + "@next/swc-linux-arm64-gnu": "npm:15.3.3" + "@next/swc-linux-arm64-musl": "npm:15.3.3" + "@next/swc-linux-x64-gnu": "npm:15.3.3" + "@next/swc-linux-x64-musl": "npm:15.3.3" + "@next/swc-win32-arm64-msvc": "npm:15.3.3" + "@next/swc-win32-x64-msvc": "npm:15.3.3" + "@swc/counter": "npm:0.1.3" + "@swc/helpers": "npm:0.5.15" busboy: "npm:1.6.0" caniuse-lite: "npm:^1.0.30001579" - graceful-fs: "npm:^4.2.11" postcss: "npm:8.4.31" - styled-jsx: "npm:5.1.1" + sharp: "npm:^0.34.1" + styled-jsx: "npm:5.1.6" peerDependencies: "@opentelemetry/api": ^1.1.0 "@playwright/test": ^1.41.2 - react: ^18.2.0 - react-dom: ^18.2.0 + babel-plugin-react-compiler: "*" + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 sass: ^1.3.0 dependenciesMeta: "@next/swc-darwin-arm64": @@ -4217,20 +4665,22 @@ __metadata: optional: true "@next/swc-win32-arm64-msvc": optional: true - "@next/swc-win32-ia32-msvc": - optional: true "@next/swc-win32-x64-msvc": optional: true + sharp: + optional: true peerDependenciesMeta: "@opentelemetry/api": optional: true "@playwright/test": optional: true + babel-plugin-react-compiler: + optional: true sass: optional: true bin: next: dist/bin/next - checksum: 10/9bd8b2ef40076b251d036c6d3a3b435095ff941bab7166ebc3c75bd832c1812eb368f8dae1e56ef4bf600a81d0ad8190d778d610aa062ffa7c4c4f126cae7f13 + checksum: 10/287e3b24aade1763b13f95246d7df0e23d5fdde2d8c4b170360b22fabfa38184e102eb64886260e0034f4fc6979a1d47929e423d7946f5da57d306034645f6c7 languageName: node linkType: hard @@ -4241,10 +4691,10 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.13.3": - version: 1.13.3 - resolution: "object-inspect@npm:1.13.3" - checksum: 10/14cb973d8381c69e14d7f1c8c75044eb4caf04c6dabcf40ca5c2ce42dc2073ae0bb2a9939eeca142b0c05215afaa1cd5534adb7c8879c32cba2576e045ed8368 +"object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10/aa13b1190ad3e366f6c83ad8a16ed37a19ed57d267385aa4bfdccda833d7b90465c057ff6c55d035a6b2e52c1a2295582b294217a0a3a1ae7abdd6877ef781fb languageName: node linkType: hard @@ -4269,14 +4719,15 @@ __metadata: languageName: node linkType: hard -"object.entries@npm:^1.1.8": - version: 1.1.8 - resolution: "object.entries@npm:1.1.8" +"object.entries@npm:^1.1.9": + version: 1.1.9 + resolution: "object.entries@npm:1.1.9" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10/2301918fbd1ee697cf6ff7cd94f060c738c0a7d92b22fd24c7c250e9b593642c9707ad2c44d339303c1439c5967d8964251cdfc855f7f6ec55db2dd79e8dc2a7 + es-object-atoms: "npm:^1.1.1" + checksum: 10/24163ab1e1e013796693fc5f5d349e8b3ac0b6a34a7edb6c17d3dd45c6a8854145780c57d302a82512c1582f63720f4b4779d6c1cfba12cbb1420b978802d8a3 languageName: node linkType: hard @@ -4324,14 +4775,14 @@ __metadata: languageName: node linkType: hard -"oniguruma-to-es@npm:0.10.0": - version: 0.10.0 - resolution: "oniguruma-to-es@npm:0.10.0" +"oniguruma-to-es@npm:^2.2.0": + version: 2.3.0 + resolution: "oniguruma-to-es@npm:2.3.0" dependencies: emoji-regex-xs: "npm:^1.0.0" regex: "npm:^5.1.1" regex-recursion: "npm:^5.1.1" - checksum: 10/09756001a74218b26d18940bea3ebb00d1949aa7ac142def3a94a4d256371fea169f52088ea32a6a23ca21338be56427fd9f0264907cee663e7ab147152638f7 + checksum: 10/7ac89fe04791650c21a146e58c985de394597e0ab3eb7d7c46699d09f87d6aca63d9f2470f1473c5adfa16961c360df6858e6a24b2429ebd7c73620ca68aec98 languageName: node linkType: hard @@ -4441,17 +4892,7 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.10.1": - version: 1.11.1 - resolution: "path-scurry@npm:1.11.1" - dependencies: - lru-cache: "npm:^10.2.0" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10/5e8845c159261adda6f09814d7725683257fcc85a18f329880ab4d7cc1d12830967eae5d5894e453f341710d5484b8fdbbd4d75181b4d6e1eb2f4dc7aeadc434 - languageName: node - linkType: hard - -"picocolors@npm:^1.0.0": +"picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 @@ -4465,10 +4906,17 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.2": + version: 4.0.2 + resolution: "picomatch@npm:4.0.2" + checksum: 10/ce617b8da36797d09c0baacb96ca8a44460452c89362d7cb8f70ca46b4158ba8bc3606912de7c818eb4a939f7f9015cef3c766ec8a0c6bfc725fdc078e39c717 + languageName: node + linkType: hard + "possible-typed-array-names@npm:^1.0.0": - version: 1.0.0 - resolution: "possible-typed-array-names@npm:1.0.0" - checksum: 10/8ed3e96dfeea1c5880c1f4c9cb707e5fb26e8be22f14f82ef92df20fd2004e635c62ba47fbe8f2bb63bfd80dac1474be2fb39798da8c2feba2815435d1f749af + version: 1.1.0 + resolution: "possible-typed-array-names@npm:1.1.0" + checksum: 10/2f44137b8d3dd35f4a7ba7469eec1cd9cfbb46ec164b93a5bc1f4c3d68599c9910ee3b91da1d28b4560e9cc8414c3cd56fedc07259c67e52cc774476270d3302 languageName: node linkType: hard @@ -4508,10 +4956,10 @@ __metadata: languageName: node linkType: hard -"property-information@npm:^6.0.0": - version: 6.5.0 - resolution: "property-information@npm:6.5.0" - checksum: 10/fced94f3a09bf651ad1824d1bdc8980428e3e480e6d01e98df6babe2cc9d45a1c52eee9a7736d2006958f9b394eb5964dedd37e23038086ddc143fc2fd5e426c +"property-information@npm:^7.0.0": + version: 7.1.0 + resolution: "property-information@npm:7.1.0" + checksum: 10/896d38a52ad7170de73f832d277c69e76a9605d941ebb3f0d6e56271414a7fdf95ff6d2819e68036b8a0c7d2d4d88bf1d4a5765c032cb19c2343567ee3a14b15 languageName: node linkType: hard @@ -4529,15 +4977,14 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:^18.0.2": - version: 18.3.1 - resolution: "react-dom@npm:18.3.1" +"react-dom@npm:19.1.0": + version: 19.1.0 + resolution: "react-dom@npm:19.1.0" dependencies: - loose-envify: "npm:^1.1.0" - scheduler: "npm:^0.23.2" + scheduler: "npm:^0.26.0" peerDependencies: - react: ^18.3.1 - checksum: 10/3f4b73a3aa083091173b29812b10394dd06f4ac06aff410b74702cfb3aa29d7b0ced208aab92d5272919b612e5cda21aeb1d54191848cf6e46e9e354f3541f81 + react: ^19.1.0 + checksum: 10/c5b58605862c7b0bb044416b01c73647bb8e89717fb5d7a2c279b11815fb7b49b619fe685c404e59f55eb52c66831236cc565c25ee1c2d042739f4a2cc538aa2 languageName: node linkType: hard @@ -4559,25 +5006,30 @@ __metadata: languageName: node linkType: hard -"react@npm:^18.0.2": - version: 18.3.1 - resolution: "react@npm:18.3.1" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10/261137d3f3993eaa2368a83110466fc0e558bc2c7f7ae7ca52d94f03aac945f45146bd85e5f481044db1758a1dbb57879e2fcdd33924e2dde1bdc550ce73f7bf +"react-property@npm:2.0.2": + version: 2.0.2 + resolution: "react-property@npm:2.0.2" + checksum: 10/3a4bc1951b2b7992cb8a2d3f12016dd0920d1c06eb58b456204a6ae1210401d62baece098d3200ed8a0513dde247a5d96ffdb24f354e32ce5a9b26fbd8552668 + languageName: node + linkType: hard + +"react@npm:19.1.0": + version: 19.1.0 + resolution: "react@npm:19.1.0" + checksum: 10/d0180689826fd9de87e839c365f6f361c561daea397d61d724687cae88f432a307d1c0f53a0ee95ddbe3352c10dac41d7ff1ad85530fb24951b27a39e5398db4 languageName: node linkType: hard "recast@npm:^0.23.5": - version: 0.23.9 - resolution: "recast@npm:0.23.9" + version: 0.23.11 + resolution: "recast@npm:0.23.11" dependencies: ast-types: "npm:^0.16.1" esprima: "npm:~4.0.0" source-map: "npm:~0.6.1" tiny-invariant: "npm:^1.3.3" tslib: "npm:^2.0.1" - checksum: 10/d60584be179d81a82fbe58b5bbe009aa42831ee114a21a3e3a22bda91334e0b8a1a4610dca8ecb7f9ea1426da4febc08134d3003085ad6ecee478d1808eb8796 + checksum: 10/a622b7848efe13a59a40c9a1a3a8178433eae1048780e04d7392406e2d67fc29e3efa84b3aa8cfda28fd58989f4b59fa968bed295b739987a666bd11cc57a5b2 languageName: node linkType: hard @@ -4645,13 +5097,6 @@ __metadata: languageName: node linkType: hard -"regenerator-runtime@npm:^0.14.0": - version: 0.14.1 - resolution: "regenerator-runtime@npm:0.14.1" - checksum: 10/5db3161abb311eef8c45bcf6565f4f378f785900ed3945acf740a9888c792f75b98ecb77f0775f3bf95502ff423529d23e94f41d80c8256e8fa05ed4b07cf471 - languageName: node - linkType: hard - "regex-recursion@npm:^5.1.1": version: 5.1.1 resolution: "regex-recursion@npm:5.1.1" @@ -4678,7 +5123,7 @@ __metadata: languageName: node linkType: hard -"regexp.prototype.flags@npm:^1.5.3": +"regexp.prototype.flags@npm:^1.5.3, regexp.prototype.flags@npm:^1.5.4": version: 1.5.4 resolution: "regexp.prototype.flags@npm:1.5.4" dependencies: @@ -4703,6 +5148,19 @@ __metadata: languageName: node linkType: hard +"remark-mdx-remove-esm@npm:^1.1.0": + version: 1.2.0 + resolution: "remark-mdx-remove-esm@npm:1.2.0" + dependencies: + "@types/mdast": "npm:^4.0.4" + mdast-util-mdxjs-esm: "npm:^2.0.1" + unist-util-remove: "npm:^4.0.0" + peerDependencies: + unified: ^11 + checksum: 10/0a1155e08395ab73f877948455fc04d7457864f7556617efabecae7ea1b441cec2ca7ee9672e16a6cd56f2a16c57a4778f901e77d19052d1dd245de5bdf4618f + languageName: node + linkType: hard + "remark-mdx@npm:^3.0.0": version: 3.1.0 resolution: "remark-mdx@npm:3.1.0" @@ -4726,15 +5184,15 @@ __metadata: linkType: hard "remark-rehype@npm:^11.0.0": - version: 11.1.1 - resolution: "remark-rehype@npm:11.1.1" + version: 11.1.2 + resolution: "remark-rehype@npm:11.1.2" dependencies: "@types/hast": "npm:^3.0.0" "@types/mdast": "npm:^4.0.0" mdast-util-to-hast: "npm:^13.0.0" unified: "npm:^11.0.0" vfile: "npm:^6.0.0" - checksum: 10/39404bd19c57b2b69660be7e3d587ddb2240495845d42fad3bcc506c9c132d07abacb0a20182b73c530857b2da0c463ad5658382b448243ce432152ab49af08d + checksum: 10/b5374a0bf08398431c92740d0cd9b20aea9df44cee12326820ddcc1b7ee642706604006461ea9799554c347e7caf31e7432132a03b97c508e1f77d29c423bd86 languageName: node linkType: hard @@ -4805,9 +5263,9 @@ __metadata: linkType: hard "reusify@npm:^1.0.4": - version: 1.0.4 - resolution: "reusify@npm:1.0.4" - checksum: 10/14222c9e1d3f9ae01480c50d96057228a8524706db79cdeb5a2ce5bb7070dd9f409a6f84a02cbef8cdc80d39aef86f2dd03d155188a1300c599b05437dcd2ffb + version: 1.1.0 + resolution: "reusify@npm:1.1.0" + checksum: 10/af47851b547e8a8dc89af144fceee17b80d5beaf5e6f57ed086432d79943434ff67ca526e92275be6f54b6189f6920a24eace75c2657eed32d02c400312b21ec languageName: node linkType: hard @@ -4865,12 +5323,10 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:^0.23.2": - version: 0.23.2 - resolution: "scheduler@npm:0.23.2" - dependencies: - loose-envify: "npm:^1.1.0" - checksum: 10/e8d68b89d18d5b028223edf090092846868a765a591944760942b77ea1f69b17235f7e956696efbb62c8130ab90af7e0949bfb8eba7896335507317236966bc9 +"scheduler@npm:^0.26.0": + version: 0.26.0 + resolution: "scheduler@npm:0.26.0" + checksum: 10/1ecf2e5d7de1a7a132796834afe14a2d589ba7e437615bd8c06f3e0786a3ac3434655e67aac8755d9b14e05754c177e49c064261de2673aaa3c926bc98caa002 languageName: node linkType: hard @@ -4883,12 +5339,21 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3": - version: 7.6.3 - resolution: "semver@npm:7.6.3" +"semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.7.1, semver@npm:^7.7.2": + version: 7.7.2 + resolution: "semver@npm:7.7.2" bin: semver: bin/semver.js - checksum: 10/36b1fbe1a2b6f873559cd57b238f1094a053dbfd997ceeb8757d79d1d2089c56d1321b9f1069ce263dc64cfa922fa1d2ad566b39426fe1ac6c723c1487589e10 + checksum: 10/7a24cffcaa13f53c09ce55e05efe25cd41328730b2308678624f8b9f5fc3093fc4d189f47950f0b811ff8f3c3039c24a2c36717ba7961615c682045bf03e1dda + languageName: node + linkType: hard + +"serialize-error@npm:^12.0.0": + version: 12.0.0 + resolution: "serialize-error@npm:12.0.0" + dependencies: + type-fest: "npm:^4.31.0" + checksum: 10/733ddb9a2bd86515d18ef43f6bcb1f55a9d9b900944420e5606725668e64b752e5da971071d8fe1562961a926796257d2edb9fad828ec90a35cc6c32a75449bb languageName: node linkType: hard @@ -4929,6 +5394,81 @@ __metadata: languageName: node linkType: hard +"sharp@npm:^0.34.1": + version: 0.34.2 + resolution: "sharp@npm:0.34.2" + dependencies: + "@img/sharp-darwin-arm64": "npm:0.34.2" + "@img/sharp-darwin-x64": "npm:0.34.2" + "@img/sharp-libvips-darwin-arm64": "npm:1.1.0" + "@img/sharp-libvips-darwin-x64": "npm:1.1.0" + "@img/sharp-libvips-linux-arm": "npm:1.1.0" + "@img/sharp-libvips-linux-arm64": "npm:1.1.0" + "@img/sharp-libvips-linux-ppc64": "npm:1.1.0" + "@img/sharp-libvips-linux-s390x": "npm:1.1.0" + "@img/sharp-libvips-linux-x64": "npm:1.1.0" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.1.0" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.1.0" + "@img/sharp-linux-arm": "npm:0.34.2" + "@img/sharp-linux-arm64": "npm:0.34.2" + "@img/sharp-linux-s390x": "npm:0.34.2" + "@img/sharp-linux-x64": "npm:0.34.2" + "@img/sharp-linuxmusl-arm64": "npm:0.34.2" + "@img/sharp-linuxmusl-x64": "npm:0.34.2" + "@img/sharp-wasm32": "npm:0.34.2" + "@img/sharp-win32-arm64": "npm:0.34.2" + "@img/sharp-win32-ia32": "npm:0.34.2" + "@img/sharp-win32-x64": "npm:0.34.2" + color: "npm:^4.2.3" + detect-libc: "npm:^2.0.4" + semver: "npm:^7.7.2" + dependenciesMeta: + "@img/sharp-darwin-arm64": + optional: true + "@img/sharp-darwin-x64": + optional: true + "@img/sharp-libvips-darwin-arm64": + optional: true + "@img/sharp-libvips-darwin-x64": + optional: true + "@img/sharp-libvips-linux-arm": + optional: true + "@img/sharp-libvips-linux-arm64": + optional: true + "@img/sharp-libvips-linux-ppc64": + optional: true + "@img/sharp-libvips-linux-s390x": + optional: true + "@img/sharp-libvips-linux-x64": + optional: true + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + "@img/sharp-libvips-linuxmusl-x64": + optional: true + "@img/sharp-linux-arm": + optional: true + "@img/sharp-linux-arm64": + optional: true + "@img/sharp-linux-s390x": + optional: true + "@img/sharp-linux-x64": + optional: true + "@img/sharp-linuxmusl-arm64": + optional: true + "@img/sharp-linuxmusl-x64": + optional: true + "@img/sharp-wasm32": + optional: true + "@img/sharp-win32-arm64": + optional: true + "@img/sharp-win32-ia32": + optional: true + "@img/sharp-win32-x64": + optional: true + checksum: 10/8c7a6f20d58849a6e33bc69c4525f471d57eb3dea0072331b55ab12bae4b8bd8b99b65264aeaec38e54d52d692db13e3261f6e7bc29430b39b507c409838cbb0 + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -4946,18 +5486,18 @@ __metadata: linkType: hard "shiki@npm:^1.26.1": - version: 1.26.1 - resolution: "shiki@npm:1.26.1" + version: 1.29.2 + resolution: "shiki@npm:1.29.2" dependencies: - "@shikijs/core": "npm:1.26.1" - "@shikijs/engine-javascript": "npm:1.26.1" - "@shikijs/engine-oniguruma": "npm:1.26.1" - "@shikijs/langs": "npm:1.26.1" - "@shikijs/themes": "npm:1.26.1" - "@shikijs/types": "npm:1.26.1" + "@shikijs/core": "npm:1.29.2" + "@shikijs/engine-javascript": "npm:1.29.2" + "@shikijs/engine-oniguruma": "npm:1.29.2" + "@shikijs/langs": "npm:1.29.2" + "@shikijs/themes": "npm:1.29.2" + "@shikijs/types": "npm:1.29.2" "@shikijs/vscode-textmate": "npm:^10.0.1" "@types/hast": "npm:^3.0.4" - checksum: 10/10b54241b2c58b0af228dbd00ebf2f7f820ec3527933c7fcbe7c1b634893fe003703f660cbf3ab4d0bdf61d2062590d3c7af5be57cd3192ebb4e5f2900d32f58 + checksum: 10/93de1ebc768ff497ba36720f3b385bff65a853248fdfc0afcb2aea81a43560b1bdc7db9e8417df11a87e106b0de0943989c6301daf62a43d09bd9ac7bc776bf3 languageName: node linkType: hard @@ -5009,10 +5549,12 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^4.0.1": - version: 4.1.0 - resolution: "signal-exit@npm:4.1.0" - checksum: 10/c9fa63bbbd7431066174a48ba2dd9986dfd930c3a8b59de9c29d7b6854ec1c12a80d15310869ea5166d413b99f041bfa3dd80a7947bcd44ea8e6eb3ffeabfa1f +"simple-swizzle@npm:^0.2.2": + version: 0.2.2 + resolution: "simple-swizzle@npm:0.2.2" + dependencies: + is-arrayish: "npm:^0.3.1" + checksum: 10/c6dffff17aaa383dae7e5c056fbf10cf9855a9f79949f20ee225c04f06ddde56323600e0f3d6797e82d08d006e93761122527438ee9531620031c08c9e0d73cc languageName: node linkType: hard @@ -5044,18 +5586,28 @@ __metadata: languageName: node linkType: hard -"stable-hash@npm:^0.0.4": - version: 0.0.4 - resolution: "stable-hash@npm:0.0.4" - checksum: 10/21c039d21c1cb739cf8342561753a5e007cb95ea682ccd452e76310bbb9c6987a89de8eda023e320b019f3e4691aabda75079cdbb7dadf7ab9013e931f2f23cd +"stable-hash@npm:^0.0.5": + version: 0.0.5 + resolution: "stable-hash@npm:0.0.5" + checksum: 10/9222ea2c558e37c4a576cb4e406966b9e6aa05b93f5c4f09ef4aaabe3577439b9b8fbff407b16840b63e2ae83de74290c7b1c2da7360d571e480e46a4aec0a56 + languageName: node + linkType: hard + +"stop-iteration-iterator@npm:^1.1.0": + version: 1.1.0 + resolution: "stop-iteration-iterator@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + internal-slot: "npm:^1.1.0" + checksum: 10/ff36c4db171ee76c936ccfe9541946b77017f12703d4c446652017356816862d3aa029a64e7d4c4ceb484e00ed4a81789333896390d808458638f3a216aa1f41 languageName: node linkType: hard "storybook@npm:^8.6.8": - version: 8.6.12 - resolution: "storybook@npm:8.6.12" + version: 8.6.14 + resolution: "storybook@npm:8.6.14" dependencies: - "@storybook/core": "npm:8.6.12" + "@storybook/core": "npm:8.6.14" peerDependencies: prettier: ^2 || ^3 peerDependenciesMeta: @@ -5065,7 +5617,7 @@ __metadata: getstorybook: ./bin/index.cjs sb: ./bin/index.cjs storybook: ./bin/index.cjs - checksum: 10/babd1d086eb02ba25ee659e02e619f7797a6b91028ad74d2da0ab77e72021cd5c2ac4f239668f15156aabf00bd97066a774370dceadf178b1e649bf971160a26 + checksum: 10/104932fe29ebf49bef24c90285741cec964d1c36b3f0b38da1dace31ac664be457f2a510e41ba69fe8aa0d90c20e3446fec27d1ced41e5eefb3a9ca713d99e79 languageName: node linkType: hard @@ -5076,28 +5628,6 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0": - version: 4.2.3 - resolution: "string-width@npm:4.2.3" - dependencies: - emoji-regex: "npm:^8.0.0" - is-fullwidth-code-point: "npm:^3.0.0" - strip-ansi: "npm:^6.0.1" - checksum: 10/e52c10dc3fbfcd6c3a15f159f54a90024241d0f149cf8aed2982a2d801d2e64df0bf1dc351cf8e95c3319323f9f220c16e740b06faecd53e2462df1d2b5443fb - languageName: node - linkType: hard - -"string-width@npm:^5.0.1, string-width@npm:^5.1.2": - version: 5.1.2 - resolution: "string-width@npm:5.1.2" - dependencies: - eastasianwidth: "npm:^0.2.0" - emoji-regex: "npm:^9.2.2" - strip-ansi: "npm:^7.0.1" - checksum: 10/7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193 - languageName: node - linkType: hard - "string.prototype.includes@npm:^2.0.1": version: 2.0.1 resolution: "string.prototype.includes@npm:2.0.1" @@ -5188,7 +5718,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": +"strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" dependencies: @@ -5197,15 +5727,6 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^7.0.1": - version: 7.1.0 - resolution: "strip-ansi@npm:7.1.0" - dependencies: - ansi-regex: "npm:^6.0.1" - checksum: 10/475f53e9c44375d6e72807284024ac5d668ee1d06010740dec0b9744f2ddf47de8d7151f80e5f6190fc8f384e802fdf9504b76a7e9020c9faee7103623338be2 - languageName: node - linkType: hard - "strip-bom@npm:^3.0.0": version: 3.0.0 resolution: "strip-bom@npm:3.0.0" @@ -5227,7 +5748,16 @@ __metadata: languageName: node linkType: hard -"style-to-object@npm:^1.0.0": +"style-to-js@npm:1.1.16, style-to-js@npm:^1.0.0": + version: 1.1.16 + resolution: "style-to-js@npm:1.1.16" + dependencies: + style-to-object: "npm:1.0.8" + checksum: 10/a876cc49a29ac90c7723b4d6f002ac6c1ac5ccc6b5bc963d9c607cfc74b15927b704c9324df6f824f576c65689fe4b4ff79caabcd44a13d8a02641f721f1b316 + languageName: node + linkType: hard + +"style-to-object@npm:1.0.8": version: 1.0.8 resolution: "style-to-object@npm:1.0.8" dependencies: @@ -5236,19 +5766,19 @@ __metadata: languageName: node linkType: hard -"styled-jsx@npm:5.1.1": - version: 5.1.1 - resolution: "styled-jsx@npm:5.1.1" +"styled-jsx@npm:5.1.6": + version: 5.1.6 + resolution: "styled-jsx@npm:5.1.6" dependencies: client-only: "npm:0.0.1" peerDependencies: - react: ">= 16.8.0 || 17.x.x || ^18.0.0-0" + react: ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" peerDependenciesMeta: "@babel/core": optional: true babel-plugin-macros: optional: true - checksum: 10/4f6a5d0010770fdeea1183d919d528fd46c484e23c0535ef3e1dd49488116f639c594f3bd4440e3bc8a8686c9f8d53c5761599870ff039ede11a5c3bfe08a4be + checksum: 10/ba01200e8227fe1441a719c2e7da96c8aa7ef61d14211d1500e1abce12efa118479bcb6e7e12beecb9e1db76432caad2f4e01bbc0c9be21c134b088a4ca5ffe0 languageName: node linkType: hard @@ -5268,13 +5798,6 @@ __metadata: languageName: node linkType: hard -"tapable@npm:^2.2.0": - version: 2.2.1 - resolution: "tapable@npm:2.2.1" - checksum: 10/1769336dd21481ae6347611ca5fca47add0962fd8e80466515032125eca0084a4f0ede11e65341b9c0018ef4e1cf1ad820adbb0fba7cc99865c6005734000b0a - languageName: node - linkType: hard - "text-table@npm:^0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -5289,6 +5812,16 @@ __metadata: languageName: node linkType: hard +"tinyglobby@npm:^0.2.13": + version: 0.2.14 + resolution: "tinyglobby@npm:0.2.14" + dependencies: + fdir: "npm:^6.4.4" + picomatch: "npm:^4.0.2" + checksum: 10/3d306d319718b7cc9d79fb3f29d8655237aa6a1f280860a217f93417039d0614891aee6fc47c5db315f4fcc6ac8d55eb8e23e2de73b2c51a431b42456d9e5764 + languageName: node + linkType: hard + "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1" @@ -5312,12 +5845,12 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:^2.0.0": - version: 2.0.0 - resolution: "ts-api-utils@npm:2.0.0" +"ts-api-utils@npm:^2.1.0": + version: 2.1.0 + resolution: "ts-api-utils@npm:2.1.0" peerDependencies: typescript: ">=4.8.4" - checksum: 10/485bdf8bbba98d58712243d958f4fd44742bbe49e559cd77882fb426d866eec6dd05c67ef91935dc4f8a3c776f235859735e1f05be399e4dc9e7ffd580120974 + checksum: 10/02e55b49d9617c6eebf8aadfa08d3ca03ca0cd2f0586ad34117fdfc7aa3cd25d95051843fde9df86665ad907f99baed179e7a117b11021417f379e4d2614eacd languageName: node linkType: hard @@ -5333,7 +5866,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.1, tslib@npm:^2.4.0": +"tslib@npm:^2.0.1, tslib@npm:^2.4.0, tslib@npm:^2.8.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 @@ -5356,6 +5889,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^4.31.0": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: 10/617ace794ac0893c2986912d28b3065ad1afb484cad59297835a0807dc63286c39e8675d65f7de08fafa339afcb8fe06a36e9a188b9857756ae1e92ee8bda212 + languageName: node + linkType: hard + "typed-array-buffer@npm:^1.0.3": version: 1.0.3 resolution: "typed-array-buffer@npm:1.0.3" @@ -5441,10 +5981,10 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~6.19.2": - version: 6.19.8 - resolution: "undici-types@npm:6.19.8" - checksum: 10/cf0b48ed4fc99baf56584afa91aaffa5010c268b8842f62e02f752df209e3dea138b372a60a963b3b2576ed932f32329ce7ddb9cb5f27a6c83040d8cd74b7a70 +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10/ec8f41aa4359d50f9b59fa61fe3efce3477cc681908c8f84354d8567bb3701fafdddf36ef6bff307024d3feb42c837cf6f670314ba37fc8145e219560e473d14 languageName: node linkType: hard @@ -5490,6 +6030,17 @@ __metadata: languageName: node linkType: hard +"unist-util-remove@npm:^4.0.0": + version: 4.0.0 + resolution: "unist-util-remove@npm:4.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + unist-util-is: "npm:^6.0.0" + unist-util-visit-parents: "npm:^6.0.0" + checksum: 10/24a84224720f4c2d2ffce1d570c00b83ef545de80ec32a246698feff880446af6c410fc94deec4b4daafe09851d59f17cd1770e20f66d68092e3cd60c2147fb8 + languageName: node + linkType: hard + "unist-util-stringify-position@npm:^4.0.0": version: 4.0.0 resolution: "unist-util-stringify-position@npm:4.0.0" @@ -5520,6 +6071,67 @@ __metadata: languageName: node linkType: hard +"unrs-resolver@npm:^1.6.2": + version: 1.7.13 + resolution: "unrs-resolver@npm:1.7.13" + dependencies: + "@unrs/resolver-binding-darwin-arm64": "npm:1.7.13" + "@unrs/resolver-binding-darwin-x64": "npm:1.7.13" + "@unrs/resolver-binding-freebsd-x64": "npm:1.7.13" + "@unrs/resolver-binding-linux-arm-gnueabihf": "npm:1.7.13" + "@unrs/resolver-binding-linux-arm-musleabihf": "npm:1.7.13" + "@unrs/resolver-binding-linux-arm64-gnu": "npm:1.7.13" + "@unrs/resolver-binding-linux-arm64-musl": "npm:1.7.13" + "@unrs/resolver-binding-linux-ppc64-gnu": "npm:1.7.13" + "@unrs/resolver-binding-linux-riscv64-gnu": "npm:1.7.13" + "@unrs/resolver-binding-linux-riscv64-musl": "npm:1.7.13" + "@unrs/resolver-binding-linux-s390x-gnu": "npm:1.7.13" + "@unrs/resolver-binding-linux-x64-gnu": "npm:1.7.13" + "@unrs/resolver-binding-linux-x64-musl": "npm:1.7.13" + "@unrs/resolver-binding-wasm32-wasi": "npm:1.7.13" + "@unrs/resolver-binding-win32-arm64-msvc": "npm:1.7.13" + "@unrs/resolver-binding-win32-ia32-msvc": "npm:1.7.13" + "@unrs/resolver-binding-win32-x64-msvc": "npm:1.7.13" + napi-postinstall: "npm:^0.2.2" + dependenciesMeta: + "@unrs/resolver-binding-darwin-arm64": + optional: true + "@unrs/resolver-binding-darwin-x64": + optional: true + "@unrs/resolver-binding-freebsd-x64": + optional: true + "@unrs/resolver-binding-linux-arm-gnueabihf": + optional: true + "@unrs/resolver-binding-linux-arm-musleabihf": + optional: true + "@unrs/resolver-binding-linux-arm64-gnu": + optional: true + "@unrs/resolver-binding-linux-arm64-musl": + optional: true + "@unrs/resolver-binding-linux-ppc64-gnu": + optional: true + "@unrs/resolver-binding-linux-riscv64-gnu": + optional: true + "@unrs/resolver-binding-linux-riscv64-musl": + optional: true + "@unrs/resolver-binding-linux-s390x-gnu": + optional: true + "@unrs/resolver-binding-linux-x64-gnu": + optional: true + "@unrs/resolver-binding-linux-x64-musl": + optional: true + "@unrs/resolver-binding-wasm32-wasi": + optional: true + "@unrs/resolver-binding-win32-arm64-msvc": + optional: true + "@unrs/resolver-binding-win32-ia32-msvc": + optional: true + "@unrs/resolver-binding-win32-x64-msvc": + optional: true + checksum: 10/362aa31e5cd635cf5955a7eb3fe58230a1a32bd5daba4c71e7620e0a0fcd35fffb926655e32d4834a82d48b676cf8a2208cf1de30e30b4bc85c2dc67fa60acb8 + languageName: node + linkType: hard + "uri-js@npm:^4.2.2": version: 4.4.1 resolution: "uri-js@npm:4.4.1" @@ -5542,6 +6154,16 @@ __metadata: languageName: node linkType: hard +"vfile-matter@npm:^5.0.1": + version: 5.0.1 + resolution: "vfile-matter@npm:5.0.1" + dependencies: + vfile: "npm:^6.0.0" + yaml: "npm:^2.0.0" + checksum: 10/5eddf848617050df7b539b68c60f4850d4f87e848accb44eb541719cece48a170897fae759023d0f3a87bcfc65fc2183a9875f11c7b3dc9ceaab7b4d739f74d6 + languageName: node + linkType: hard + "vfile-message@npm:^4.0.0": version: 4.0.2 resolution: "vfile-message@npm:4.0.2" @@ -5552,7 +6174,7 @@ __metadata: languageName: node linkType: hard -"vfile@npm:^6.0.0": +"vfile@npm:^6.0.0, vfile@npm:^6.0.3": version: 6.0.3 resolution: "vfile@npm:6.0.3" dependencies: @@ -5615,7 +6237,7 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18, which-typed-array@npm:^1.1.2": +"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19, which-typed-array@npm:^1.1.2": version: 1.1.19 resolution: "which-typed-array@npm:1.1.19" dependencies: @@ -5648,28 +6270,6 @@ __metadata: languageName: node linkType: hard -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" - dependencies: - ansi-styles: "npm:^4.0.0" - string-width: "npm:^4.1.0" - strip-ansi: "npm:^6.0.0" - checksum: 10/cebdaeca3a6880da410f75209e68cd05428580de5ad24535f22696d7d9cab134d1f8498599f344c3cf0fb37c1715807a183778d8c648d6cc0cb5ff2bb4236540 - languageName: node - linkType: hard - -"wrap-ansi@npm:^8.1.0": - version: 8.1.0 - resolution: "wrap-ansi@npm:8.1.0" - dependencies: - ansi-styles: "npm:^6.1.0" - string-width: "npm:^5.0.1" - strip-ansi: "npm:^7.0.1" - checksum: 10/7b1e4b35e9bb2312d2ee9ee7dc95b8cb5f8b4b5a89f7dde5543fe66c1e3715663094defa50d75454ac900bd210f702d575f15f3f17fa9ec0291806d2578d1ddf - languageName: node - linkType: hard - "wrappy@npm:1": version: 1.0.2 resolution: "wrappy@npm:1.0.2" @@ -5678,8 +6278,8 @@ __metadata: linkType: hard "ws@npm:^8.2.3": - version: 8.18.1 - resolution: "ws@npm:8.18.1" + version: 8.18.2 + resolution: "ws@npm:8.18.2" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -5688,7 +6288,16 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10/3f38e9594f2af5b6324138e86b74df7d77bbb8e310bf8188679dd80bac0d1f47e51536a1923ac3365f31f3d8b25ea0b03e4ade466aa8292a86cd5defca64b19b + checksum: 10/018e04ec95561d88248d53a2eaf094b4ae131e9b062f2679e6e8a62f04649bc543448f1e038125225ac6bbb25f54c1e65d7a2cc9dbc1e28b43e5e6b7162ad88e + languageName: node + linkType: hard + +"yaml@npm:^2.0.0": + version: 2.8.0 + resolution: "yaml@npm:2.8.0" + bin: + yaml: bin.mjs + checksum: 10/7d4bd9c10d0e467601f496193f2ac254140f8e36f96f5ff7f852b9ce37974168eb7354f4c36dc8837dde527a2043d004b6aff48818ec24a69ab2dd3c6b6c381c languageName: node linkType: hard diff --git a/packages/canon/src/css/stories.module.css b/packages/canon/src/css/stories.module.css new file mode 100644 index 0000000000..10ed37bb7a --- /dev/null +++ b/packages/canon/src/css/stories.module.css @@ -0,0 +1,16 @@ +.fakeBox { + background: #eaf2fd; + border-radius: 4px; + box-shadow: 0 0 0 1px #2563eb; + height: 48px; + transition-property: background-color, box-shadow, background-image; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; + background-image: url('data:image/svg+xml,%3Csvg%20width%3D%226%22%20height%3D%226%22%20viewBox%3D%220%200%206%206%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cg%20fill%3D%22%232563eb%22%20fill-opacity%3D%220.3%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M5%200h1L0%206V5zM6%205v1H5z%22/%3E%3C/g%3E%3C/svg%3E'); +} + +[data-theme='dark'] .fakeBox { + background: #1a1a1a; + box-shadow: 0 0 0 1px #4765ff; + background-image: url('data:image/svg+xml,%3Csvg%20width%3D%226%22%20height%3D%226%22%20viewBox%3D%220%200%206%206%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cg%20fill%3D%22%234765FF%22%20fill-opacity%3D%221%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M5%200h1L0%206V5zM6%205v1H5z%22/%3E%3C/g%3E%3C/svg%3E'); +} From 1220cf84d0771dbf985c86aba736007a23598a11 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Tue, 17 Sep 2024 08:56:38 +0300 Subject: [PATCH 042/301] feat(backend): allow rate limiting requests to the backend uses redis for storing the data if it has been configured; otherwise falls back to memory configuration allows controlling almost all possible configuration available in the `express-rate-limit` library. Signed-off-by: Heikki Hellgren --- .changeset/famous-terms-rescue.md | 15 ++ packages/backend-defaults/config.d.ts | 68 ++++- packages/backend-defaults/package.json | 2 + .../report-rootHttpRouter.api.md | 11 + .../rootHttpRouter/http/MiddlewareFactory.ts | 86 ++++++- .../http/RateLimitStoreFactory.test.ts | 84 +++++++ .../http/RateLimitStoreFactory.ts | 69 ++++++ .../entrypoints/rootHttpRouter/http/index.ts | 1 + .../rootHttpRouterServiceFactory.ts | 5 +- yarn.lock | 234 +++++++++++++++++- 10 files changed, 558 insertions(+), 17 deletions(-) create mode 100644 .changeset/famous-terms-rescue.md create mode 100644 packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts create mode 100644 packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts diff --git a/.changeset/famous-terms-rescue.md b/.changeset/famous-terms-rescue.md new file mode 100644 index 0000000000..073f3a02ba --- /dev/null +++ b/.changeset/famous-terms-rescue.md @@ -0,0 +1,15 @@ +--- +'@backstage/backend-defaults': patch +--- + +Added new rate limit middleware to allow rate limiting requests to the backend + +Rate limiting can be turned on by adding the following configuration to `app-config.yaml`: + +```yaml +backend: + rateLimit: + enabled: true + windowMs: 60000 + limit: 100 +``` diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts index 1ef054a537..7866b17c29 100644 --- a/packages/backend-defaults/config.d.ts +++ b/packages/backend-defaults/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { HumanDuration } from '@backstage/types'; +import { HumanDuration, JsonValue } from '@backstage/types'; export interface Config { app: { @@ -790,6 +790,72 @@ export interface Config { headers?: { [name: string]: string }; }; + /** + * Rate limiting options + */ + rateLimit?: { + /** + * Store to use for rate limiting. If not defined, the store will be automatically + * decided based on `backend.cache.store` value. If + * redis is not available, the store will be memory. + */ + store?: 'memory' | 'redis'; + /** + * Rate limiting enabled. Defaults to false. + */ + enabled?: boolean; + /** + * Time frame in milliseconds or as human duration for which requests are checked/remembered. + * Defaults to 6000ms. + */ + window?: number | HumanDuration; + /** + * The maximum number of connections to allow during the `window` before rate limiting the client. + * Defaults to 5. + */ + limit?: number; + /** + * The response body to send back when a client is rate limited. + * Defaults to 'Too many requests, please try again later.'. + */ + message?: JsonValue; + /** + * The HTTP status code to send back when a client is rate limited. + * Defaults to 429. + */ + statusCode?: number; + /** + * Whether to send the legacy rate limit headers for the limit. + * Defaults to true. + */ + legacyHeaders?: boolean; + /** + * Whether to enable support for headers conforming the RateLimit header fields for HTTP + * standardization. Defaults to undefined. + */ + standardHeaders?: 'draft-6' | 'draft-7'; + /** + * Whether to pass requests in case of store failure. + * Defaults to false. + */ + passOnStoreError?: boolean; + /** + * List of allowed IP addresses that are not rate limited. + * Defaults to [127.0.0.1]. + */ + ipAllowList?: string[]; + /** + * Skip rate limiting for requests that have been successful. + * Defaults to false. + */ + skipSuccessfulRequests?: boolean; + /** + * Skip rate limiting for requests that have failed. + * Defaults to false. + */ + skipFailedRequests?: boolean; + }; + /** * Configuration related to URL reading, used for example for reading catalog info * files, scaffolder templates, and techdocs content. diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 3c74bad2d5..c10cb9f11f 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -168,6 +168,7 @@ "cron": "^3.0.0", "express": "^4.17.1", "express-promise-router": "^4.1.0", + "express-rate-limit": "^7.4.0", "fs-extra": "^11.2.0", "git-url-parse": "^15.0.0", "helmet": "^6.0.0", @@ -187,6 +188,7 @@ "pg": "^8.11.3", "pg-connection-string": "^2.3.0", "pg-format": "^1.0.4", + "rate-limit-redis": "^4.2.0", "raw-body": "^2.4.1", "selfsigned": "^2.0.0", "tar": "^6.1.12", diff --git a/packages/backend-defaults/report-rootHttpRouter.api.md b/packages/backend-defaults/report-rootHttpRouter.api.md index 826d592e8b..91e760b7ea 100644 --- a/packages/backend-defaults/report-rootHttpRouter.api.md +++ b/packages/backend-defaults/report-rootHttpRouter.api.md @@ -20,6 +20,7 @@ import { RootHttpRouterService } from '@backstage/backend-plugin-api'; import { Router } from 'express'; import type { Server } from 'node:http'; import { ServiceFactory } from '@backstage/backend-plugin-api'; +import type { Store } from 'express-rate-limit'; // @public (undocumented) export function createHealthRouter(options: { @@ -93,6 +94,7 @@ export class MiddlewareFactory { helmet(): RequestHandler; logging(): RequestHandler; notFound(): RequestHandler; + rateLimit(): RequestHandler; } // @public @@ -109,6 +111,15 @@ export interface MiddlewareFactoryOptions { logger: LoggerService; } +// @public +export class RateLimitStoreFactory { + constructor(config: Config); + // (undocumented) + create(): Store | undefined; + // (undocumented) + redis(): Store; +} + // @public export function readCorsOptions(config?: Config): CorsOptions; diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts index 1ff9bd15d1..561a1999c9 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts @@ -15,15 +15,15 @@ */ import { - RootConfigService, LoggerService, + RootConfigService, } from '@backstage/backend-plugin-api'; import { - Request, - Response, ErrorRequestHandler, NextFunction, + Request, RequestHandler, + Response, } from 'express'; import cors from 'cors'; import helmet from 'helmet'; @@ -37,12 +37,19 @@ import { InputError, NotAllowedError, NotFoundError, + NotImplementedError, NotModifiedError, - ServiceUnavailableError, serializeError, + ServiceUnavailableError, } from '@backstage/errors'; -import { NotImplementedError } from '@backstage/errors'; import { applyInternalErrorFilter } from './applyInternalErrorFilter'; +import { DraftHeadersVersion, rateLimit } from 'express-rate-limit'; +import { + durationToMilliseconds, + HumanDuration, + JsonValue, +} from '@backstage/types'; +import { RateLimitStoreFactory } from './RateLimitStoreFactory'; type LogMeta = { date: string; @@ -227,6 +234,75 @@ export class MiddlewareFactory { return cors(readCorsOptions(this.#config.getOptionalConfig('backend'))); } + /** + * Returns a middleware that implements rate limiting. + * + * @remarks + * + * Rate limiting is a common technique to prevent abuse of APIs. This middleware is + * configured using the config key `backend.rateLimit`. + * + * @returns An Express request handler + */ + rateLimit(): RequestHandler { + const rateLimitOptions = + this.#config.getOptionalConfig('backend.rateLimit'); + const enabled = rateLimitOptions?.getOptionalBoolean('enabled') ?? false; + if (!rateLimitOptions || !enabled) { + return (_req: Request, _res: Response, next: NextFunction) => { + next(); + }; + } + + const window = rateLimitOptions.getOptional( + 'window', + ); + let windowMs: number | undefined; + if (window !== undefined) { + if (typeof window === 'number') { + windowMs = window; + } else if (typeof window === 'object' && !Array.isArray(window)) { + windowMs = durationToMilliseconds(window); + } else { + throw new Error( + `Invalid configuration backend.rateLimit.window: ${window}, expected milliseconds number or HumanDuration object`, + ); + } + } + + const ipAllowList = rateLimitOptions.getOptionalStringArray( + 'ipAllowList', + ) ?? ['127.0.0.1']; + + return rateLimit({ + windowMs, + limit: rateLimitOptions.getOptionalNumber('limit'), + message: rateLimitOptions.getOptional('message'), + statusCode: rateLimitOptions.getOptionalNumber('statusCode'), + skipSuccessfulRequests: rateLimitOptions.getOptionalBoolean( + 'skipSuccessfulRequests', + ), + skipFailedRequests: + rateLimitOptions.getOptionalBoolean('skipFailedRequests'), + legacyHeaders: rateLimitOptions.getOptionalBoolean('legacyHeaders'), + standardHeaders: rateLimitOptions.getOptionalString( + 'standardHeaders', + ) as DraftHeadersVersion, + passOnStoreError: rateLimitOptions.getOptionalBoolean('passOnStoreError'), + keyGenerator(req, _res): string { + if (!req.ip) { + return req.socket.remoteAddress!; + } + + return req.ip.replace(/:\d+[^:]*$/, ''); + }, + skip: (req, _res) => { + return Boolean(req.ip && ipAllowList.includes(req.ip)); + }, + store: new RateLimitStoreFactory(this.#config).create(), + }); + } + /** * Express middleware to handle errors during request processing. * diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts new file mode 100644 index 0000000000..105cf0cac6 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { mockServices } from '@backstage/backend-test-utils'; +import { RateLimitStoreFactory } from './RateLimitStoreFactory'; +import { RedisStore } from 'rate-limit-redis'; +import KeyvRedis from '@keyv/redis'; + +jest.mock('@keyv/redis'); +(KeyvRedis as jest.Mocked).mockImplementation(() => { + return { + redis: { + call: jest.fn().mockResolvedValue('OK'), + }, + }; +}); + +describe('CacheRateLimitStoreFactory', () => { + it('should return redis store with auto configuration', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + cache: { + store: 'redis', + }, + rateLimit: { + store: undefined, + }, + }, + }, + }); + const factory = new RateLimitStoreFactory(config); + const store = factory.create(); + expect(store).toBeInstanceOf(RedisStore); + }); + + it('should return undefined store with auto configuration if redis is not available', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + cache: { + store: 'memory', + }, + database: { + client: 'sqlite3', + }, + rateLimit: { + store: undefined, + }, + }, + }, + }); + const factory = new RateLimitStoreFactory(config); + const store = factory.create(); + expect(store).toBeUndefined(); + }); + + it('should return redis store if configured explicitly', () => { + const config = mockServices.rootConfig({ + data: { + backend: { + rateLimit: { + store: 'redis', + }, + }, + }, + }); + const factory = new RateLimitStoreFactory(config); + const store = factory.create(); + expect(store).toBeInstanceOf(RedisStore); + }); +}); diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts new file mode 100644 index 0000000000..00e5440ebe --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts @@ -0,0 +1,69 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Config } from '@backstage/config'; +import type { Store } from 'express-rate-limit'; +import KeyvRedis from '@keyv/redis'; +import { RedisStore } from 'rate-limit-redis'; + +/** + * Creates a store for `express-rate-limit` based on the configuration. + * + * @public + */ +export class RateLimitStoreFactory { + constructor(private readonly config: Config) {} + + create(): Store | undefined { + const storeType = this.config.getOptionalString('backend.rateLimit.store'); + if (!storeType) { + return this.auto(); + } + switch (storeType) { + case 'redis': + return this.redis(); + default: + throw new Error( + `Invalid 'backend.rateLimit.store' provided: ${storeType}`, + ); + } + } + + private auto(): Store | undefined { + const cacheStore = + this.config.getOptionalString('backend.cache.store') || 'memory'; + // Use redis as primary if available + if (cacheStore === 'redis') { + return this.redis(); + } + + // Fallback to undefined (memory) + return undefined; + } + + redis(): Store { + const connectionString = + this.config.getOptionalString('backend.cache.connection') || ''; + const useRedisSets = + this.config.getOptionalBoolean('backend.cache.useRedisSets') ?? true; + const keyv = new KeyvRedis(connectionString, { + useRedisSets, + }); + return new RedisStore({ + // Keyv uses ioredis under the hood + sendCommand: (...args: string[]) => keyv.redis.call(...args), + }); + } +} diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/index.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/index.ts index 4a9ec14cf8..5c63d887bb 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/index.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/index.ts @@ -28,3 +28,4 @@ export type { HttpServerCertificateOptions, HttpServerOptions, } from './types'; +export { RateLimitStoreFactory } from './RateLimitStoreFactory'; diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts index da7fcfe3f9..7d0d778b0c 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts @@ -15,13 +15,13 @@ */ import { - RootConfigService, coreServices, createServiceFactory, LifecycleService, LoggerService, + RootConfigService, } from '@backstage/backend-plugin-api'; -import express, { RequestHandler, Express } from 'express'; +import express, { Express, RequestHandler } from 'express'; import type { Server } from 'node:http'; import { createHttpServer, @@ -117,6 +117,7 @@ const rootHttpRouterServiceFactoryWithOptions = ( if (trustProxy !== undefined) { app.set('trust proxy', trustProxy); } + app.use(middleware.rateLimit()); app.use(middleware.helmet()); app.use(middleware.cors()); app.use(middleware.compression()); diff --git a/yarn.lock b/yarn.lock index d8736591ea..0a73d77844 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3335,7 +3335,16 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.26.10, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": + version: 7.26.7 + resolution: "@babel/runtime@npm:7.26.7" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 10/c7a661a6836b332d9d2e047cba77ba1862c1e4f78cec7146db45808182ef7636d8a7170be9797e5d8fd513180bffb9fa16f6ca1c69341891efec56113cf22bfc + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.26.10": version: 7.27.0 resolution: "@babel/runtime@npm:7.27.0" dependencies: @@ -3344,7 +3353,18 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.27.0, @babel/template@npm:^7.3.3": +"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.3.3": + version: 7.25.9 + resolution: "@babel/template@npm:7.25.9" + dependencies: + "@babel/code-frame": "npm:^7.25.9" + "@babel/parser": "npm:^7.25.9" + "@babel/types": "npm:^7.25.9" + checksum: 10/e861180881507210150c1335ad94aff80fd9e9be6202e1efa752059c93224e2d5310186ddcdd4c0f0b0fc658ce48cb47823f15142b5c00c8456dde54f5de80b2 + languageName: node + linkType: hard + +"@babel/template@npm:^7.27.0": version: 7.27.0 resolution: "@babel/template@npm:7.27.0" dependencies: @@ -3370,7 +3390,17 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0, @babel/types@npm:^7.27.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": + version: 7.26.0 + resolution: "@babel/types@npm:7.26.0" + dependencies: + "@babel/helper-string-parser": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.25.9" + checksum: 10/40780741ecec886ed9edae234b5eb4976968cc70d72b4e5a40d55f83ff2cc457de20f9b0f4fe9d858350e43dab0ea496e7ef62e2b2f08df699481a76df02cd6e + languageName: node + linkType: hard + +"@babel/types@npm:^7.27.0": version: 7.27.0 resolution: "@babel/types@npm:7.27.0" dependencies: @@ -3602,6 +3632,7 @@ __metadata: cron: "npm:^3.0.0" express: "npm:^4.17.1" express-promise-router: "npm:^4.1.0" + express-rate-limit: "npm:^7.4.0" fs-extra: "npm:^11.2.0" git-url-parse: "npm:^15.0.0" helmet: "npm:^6.0.0" @@ -3624,6 +3655,7 @@ __metadata: pg: "npm:^8.11.3" pg-connection-string: "npm:^2.3.0" pg-format: "npm:^1.0.4" + rate-limit-redis: "npm:^4.2.0" raw-body: "npm:^2.4.1" selfsigned: "npm:^2.0.0" supertest: "npm:^7.0.0" @@ -19193,6 +19225,13 @@ __metadata: languageName: node linkType: hard +"@swc/core-darwin-arm64@npm:1.10.6": + version: 1.10.6 + resolution: "@swc/core-darwin-arm64@npm:1.10.6" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@swc/core-darwin-arm64@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-darwin-arm64@npm:1.11.24" @@ -19200,6 +19239,13 @@ __metadata: languageName: node linkType: hard +"@swc/core-darwin-x64@npm:1.10.6": + version: 1.10.6 + resolution: "@swc/core-darwin-x64@npm:1.10.6" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@swc/core-darwin-x64@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-darwin-x64@npm:1.11.24" @@ -19207,6 +19253,13 @@ __metadata: languageName: node linkType: hard +"@swc/core-linux-arm-gnueabihf@npm:1.10.6": + version: 1.10.6 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.10.6" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@swc/core-linux-arm-gnueabihf@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.24" @@ -19214,6 +19267,13 @@ __metadata: languageName: node linkType: hard +"@swc/core-linux-arm64-gnu@npm:1.10.6": + version: 1.10.6 + resolution: "@swc/core-linux-arm64-gnu@npm:1.10.6" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@swc/core-linux-arm64-gnu@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-linux-arm64-gnu@npm:1.11.24" @@ -19221,6 +19281,13 @@ __metadata: languageName: node linkType: hard +"@swc/core-linux-arm64-musl@npm:1.10.6": + version: 1.10.6 + resolution: "@swc/core-linux-arm64-musl@npm:1.10.6" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@swc/core-linux-arm64-musl@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-linux-arm64-musl@npm:1.11.24" @@ -19228,6 +19295,13 @@ __metadata: languageName: node linkType: hard +"@swc/core-linux-x64-gnu@npm:1.10.6": + version: 1.10.6 + resolution: "@swc/core-linux-x64-gnu@npm:1.10.6" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@swc/core-linux-x64-gnu@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-linux-x64-gnu@npm:1.11.24" @@ -19235,6 +19309,13 @@ __metadata: languageName: node linkType: hard +"@swc/core-linux-x64-musl@npm:1.10.6": + version: 1.10.6 + resolution: "@swc/core-linux-x64-musl@npm:1.10.6" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@swc/core-linux-x64-musl@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-linux-x64-musl@npm:1.11.24" @@ -19242,6 +19323,13 @@ __metadata: languageName: node linkType: hard +"@swc/core-win32-arm64-msvc@npm:1.10.6": + version: 1.10.6 + resolution: "@swc/core-win32-arm64-msvc@npm:1.10.6" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@swc/core-win32-arm64-msvc@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-win32-arm64-msvc@npm:1.11.24" @@ -19249,6 +19337,13 @@ __metadata: languageName: node linkType: hard +"@swc/core-win32-ia32-msvc@npm:1.10.6": + version: 1.10.6 + resolution: "@swc/core-win32-ia32-msvc@npm:1.10.6" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@swc/core-win32-ia32-msvc@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-win32-ia32-msvc@npm:1.11.24" @@ -19256,6 +19351,13 @@ __metadata: languageName: node linkType: hard +"@swc/core-win32-x64-msvc@npm:1.10.6": + version: 1.10.6 + resolution: "@swc/core-win32-x64-msvc@npm:1.10.6" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@swc/core-win32-x64-msvc@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-win32-x64-msvc@npm:1.11.24" @@ -19263,7 +19365,7 @@ __metadata: languageName: node linkType: hard -"@swc/core@npm:^1.10.8, @swc/core@npm:^1.3.46": +"@swc/core@npm:^1.10.8": version: 1.11.24 resolution: "@swc/core@npm:1.11.24" dependencies: @@ -19309,6 +19411,52 @@ __metadata: languageName: node linkType: hard +"@swc/core@npm:^1.3.46": + version: 1.10.6 + resolution: "@swc/core@npm:1.10.6" + dependencies: + "@swc/core-darwin-arm64": "npm:1.10.6" + "@swc/core-darwin-x64": "npm:1.10.6" + "@swc/core-linux-arm-gnueabihf": "npm:1.10.6" + "@swc/core-linux-arm64-gnu": "npm:1.10.6" + "@swc/core-linux-arm64-musl": "npm:1.10.6" + "@swc/core-linux-x64-gnu": "npm:1.10.6" + "@swc/core-linux-x64-musl": "npm:1.10.6" + "@swc/core-win32-arm64-msvc": "npm:1.10.6" + "@swc/core-win32-ia32-msvc": "npm:1.10.6" + "@swc/core-win32-x64-msvc": "npm:1.10.6" + "@swc/counter": "npm:^0.1.3" + "@swc/types": "npm:^0.1.17" + peerDependencies: + "@swc/helpers": "*" + dependenciesMeta: + "@swc/core-darwin-arm64": + optional: true + "@swc/core-darwin-x64": + optional: true + "@swc/core-linux-arm-gnueabihf": + optional: true + "@swc/core-linux-arm64-gnu": + optional: true + "@swc/core-linux-arm64-musl": + optional: true + "@swc/core-linux-x64-gnu": + optional: true + "@swc/core-linux-x64-musl": + optional: true + "@swc/core-win32-arm64-msvc": + optional: true + "@swc/core-win32-ia32-msvc": + optional: true + "@swc/core-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@swc/helpers": + optional: true + checksum: 10/51eccbba6ee8a41f57a6ba4213ec05859434f52fd6698f508c92a8bb467afda56a1e95b07985faf059b27cb28e77d479340de7210a8616976ea82f774a6d86a3 + languageName: node + linkType: hard + "@swc/counter@npm:^0.1.3": version: 0.1.3 resolution: "@swc/counter@npm:0.1.3" @@ -19338,6 +19486,15 @@ __metadata: languageName: node linkType: hard +"@swc/types@npm:^0.1.17": + version: 0.1.17 + resolution: "@swc/types@npm:0.1.17" + dependencies: + "@swc/counter": "npm:^0.1.3" + checksum: 10/ddef1ad5bfead3acdfc41f14e79ba43a99200eb325afbad5716058dbe36358b0513400e9f22aff32432be84a98ae93df95a20b94192f69b8687144270e4eaa18 + languageName: node + linkType: hard + "@swc/types@npm:^0.1.21": version: 0.1.21 resolution: "@swc/types@npm:0.1.21" @@ -20659,7 +20816,16 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:>=12, @types/node@npm:>=12.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=18.0.0, @types/node@npm:^22.0.0": +"@types/node@npm:*, @types/node@npm:>=13.7.0, @types/node@npm:^22.0.0": + version: 22.10.5 + resolution: "@types/node@npm:22.10.5" + dependencies: + undici-types: "npm:~6.20.0" + checksum: 10/a5366961ffa9921e8f15435bc18ea9f8b7a7bb6b3d92dd5e93ebcd25e8af65708872bd8e6fee274b4655bab9ca80fbff9f0e42b5b53857790f13cf68cf4cbbfc + languageName: node + linkType: hard + +"@types/node@npm:>=12, @types/node@npm:>=12.0.0, @types/node@npm:>=18.0.0": version: 22.13.10 resolution: "@types/node@npm:22.13.10" dependencies: @@ -24011,13 +24177,20 @@ __metadata: languageName: node linkType: hard -"async@npm:^3.2.2, async@npm:^3.2.3, async@npm:^3.2.4, async@npm:^3.2.6": +"async@npm:^3.2.2, async@npm:^3.2.6": version: 3.2.6 resolution: "async@npm:3.2.6" checksum: 10/cb6e0561a3c01c4b56a799cc8bab6ea5fef45f069ab32500b6e19508db270ef2dffa55e5aed5865c5526e9907b1f8be61b27530823b411ffafb5e1538c86c368 languageName: node linkType: hard +"async@npm:^3.2.3, async@npm:^3.2.4": + version: 3.2.4 + resolution: "async@npm:3.2.4" + checksum: 10/bebb5dc2258c45b83fa1d3be179ae0eb468e1646a62d443c8d60a45e84041b28fccebe1e2d1f234bfc3dcad44e73dcdbf4ba63d98327c9f6556e3dbd47c2ae8b + languageName: node + linkType: hard + "asynckit@npm:^0.4.0": version: 0.4.0 resolution: "asynckit@npm:0.4.0" @@ -24509,13 +24682,20 @@ __metadata: languageName: node linkType: hard -"before-after-hook@npm:^2.1.0, before-after-hook@npm:^2.2.0": +"before-after-hook@npm:^2.1.0": version: 2.2.3 resolution: "before-after-hook@npm:2.2.3" checksum: 10/e676f769dbc4abcf4b3317db2fd2badb4a92c0710e0a7da12cf14b59c3482d4febf835ad7de7874499060fd4e13adf0191628e504728b3c5bb4ec7a878c09940 languageName: node linkType: hard +"before-after-hook@npm:^2.2.0": + version: 2.2.2 + resolution: "before-after-hook@npm:2.2.2" + checksum: 10/34c190def503f771f8811db0bd0c62b35301fe6059c8d847664633ce0548e8253e2661104ba66c71a85548746ba87d5ff2ebf5278c1f3ad367d111ffc9a26bb4 + languageName: node + linkType: hard + "better-opn@npm:^3.0.2": version: 3.0.2 resolution: "better-opn@npm:3.0.2" @@ -29865,6 +30045,15 @@ __metadata: languageName: node linkType: hard +"express-rate-limit@npm:^7.4.0": + version: 7.4.0 + resolution: "express-rate-limit@npm:7.4.0" + peerDependencies: + express: 4 || 5 || ^5.0.0-beta.1 + checksum: 10/33178c652bb1472aad2022194b5cd7963bd3e74d3eaf5e49eb1491a968fdce54551cc76b097ac10d3a1646d62cec2e6f2405ccef5ef5b60152a0c4a148749a4d + languageName: node + linkType: hard + "express-session@npm:^1.17.1, express-session@npm:^1.17.3": version: 1.18.1 resolution: "express-session@npm:1.18.1" @@ -31040,7 +31229,25 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0": +"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6": + version: 1.2.6 + resolution: "get-intrinsic@npm:1.2.6" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + dunder-proto: "npm:^1.0.0" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + function-bind: "npm:^1.1.2" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.0.0" + checksum: 10/a1ffae6d7893a6fa0f4d1472adbc85095edd6b3b0943ead97c3738539cecb19d422ff4d48009eed8c3c27ad678c2b1e38a83b1a1e96b691d13ed8ecefca1068d + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.3.0": version: 1.3.0 resolution: "get-intrinsic@npm:1.3.0" dependencies: @@ -36734,7 +36941,7 @@ __metadata: languageName: node linkType: hard -"math-intrinsics@npm:^1.1.0": +"math-intrinsics@npm:^1.0.0, math-intrinsics@npm:^1.1.0": version: 1.1.0 resolution: "math-intrinsics@npm:1.1.0" checksum: 10/11df2eda46d092a6035479632e1ec865b8134bdfc4bd9e571a656f4191525404f13a283a515938c3a8de934dbfd9c09674d9da9fa831e6eb7e22b50b197d2edd @@ -41835,6 +42042,15 @@ __metadata: languageName: node linkType: hard +"rate-limit-redis@npm:^4.2.0": + version: 4.2.0 + resolution: "rate-limit-redis@npm:4.2.0" + peerDependencies: + express-rate-limit: ">= 6" + checksum: 10/22adc67918ca906f613b45f9dcfd039f543d363921979d21ba56be5f3288c6e9973c9e4bb4ec59810fc6b3abb20defd572c102f607a8c3b4d273d5e09b63839f + languageName: node + linkType: hard + "rate-limiter-flexible@npm:^4.0.1": version: 4.0.1 resolution: "rate-limiter-flexible@npm:4.0.1" From 7bd8af72ad11eafda24b8c5ac5397ac6c825c170 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Tue, 17 Sep 2024 13:38:41 +0300 Subject: [PATCH 043/301] feat(backend): add support for trustProxy in backend config this is required for the rate limiting to work behind proxy. closes #24169 Signed-off-by: Heikki Hellgren --- .changeset/sour-comics-attend.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sour-comics-attend.md diff --git a/.changeset/sour-comics-attend.md b/.changeset/sour-comics-attend.md new file mode 100644 index 0000000000..729fc59ba7 --- /dev/null +++ b/.changeset/sour-comics-attend.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Add configuration variable for `express` trust proxy setting From 279c15cb5dc3d6151fc829e60cd3b0e471dee8f8 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Tue, 17 Sep 2024 17:19:45 +0300 Subject: [PATCH 044/301] fix: skip trust proxy validation in rate limiting Signed-off-by: Heikki Hellgren --- .changeset/famous-terms-rescue.md | 5 +- packages/backend-defaults/config.d.ts | 106 +++++++----------- .../report-rootHttpRouter.api.md | 10 -- .../rootHttpRouter/http/MiddlewareFactory.ts | 43 ++++--- .../http/RateLimitStoreFactory.test.ts | 50 +++------ .../http/RateLimitStoreFactory.ts | 40 ++----- .../entrypoints/rootHttpRouter/http/index.ts | 1 - 7 files changed, 91 insertions(+), 164 deletions(-) diff --git a/.changeset/famous-terms-rescue.md b/.changeset/famous-terms-rescue.md index 073f3a02ba..78630eb532 100644 --- a/.changeset/famous-terms-rescue.md +++ b/.changeset/famous-terms-rescue.md @@ -9,7 +9,6 @@ Rate limiting can be turned on by adding the following configuration to `app-con ```yaml backend: rateLimit: - enabled: true - windowMs: 60000 - limit: 100 + window: 60000 + incomingRequestLimit: 100 ``` diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts index 7866b17c29..49a51ec2f5 100644 --- a/packages/backend-defaults/config.d.ts +++ b/packages/backend-defaults/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { HumanDuration, JsonValue } from '@backstage/types'; +import { HumanDuration } from '@backstage/types'; export interface Config { app: { @@ -793,68 +793,48 @@ export interface Config { /** * Rate limiting options */ - rateLimit?: { - /** - * Store to use for rate limiting. If not defined, the store will be automatically - * decided based on `backend.cache.store` value. If - * redis is not available, the store will be memory. - */ - store?: 'memory' | 'redis'; - /** - * Rate limiting enabled. Defaults to false. - */ - enabled?: boolean; - /** - * Time frame in milliseconds or as human duration for which requests are checked/remembered. - * Defaults to 6000ms. - */ - window?: number | HumanDuration; - /** - * The maximum number of connections to allow during the `window` before rate limiting the client. - * Defaults to 5. - */ - limit?: number; - /** - * The response body to send back when a client is rate limited. - * Defaults to 'Too many requests, please try again later.'. - */ - message?: JsonValue; - /** - * The HTTP status code to send back when a client is rate limited. - * Defaults to 429. - */ - statusCode?: number; - /** - * Whether to send the legacy rate limit headers for the limit. - * Defaults to true. - */ - legacyHeaders?: boolean; - /** - * Whether to enable support for headers conforming the RateLimit header fields for HTTP - * standardization. Defaults to undefined. - */ - standardHeaders?: 'draft-6' | 'draft-7'; - /** - * Whether to pass requests in case of store failure. - * Defaults to false. - */ - passOnStoreError?: boolean; - /** - * List of allowed IP addresses that are not rate limited. - * Defaults to [127.0.0.1]. - */ - ipAllowList?: string[]; - /** - * Skip rate limiting for requests that have been successful. - * Defaults to false. - */ - skipSuccessfulRequests?: boolean; - /** - * Skip rate limiting for requests that have failed. - * Defaults to false. - */ - skipFailedRequests?: boolean; - }; + rateLimit?: + | false + | { + store?: + | { + client: 'redis'; + connection: string; + } + | { + client: 'memory'; + }; + /** + * Time frame in milliseconds or as human duration for which requests are checked/remembered. + * Defaults to 6000ms. + */ + window?: number | HumanDuration; + /** + * The maximum number of connections to allow during the `window` before rate limiting the client. + * Defaults to 5. + */ + incomingRequestLimit?: number; + /** + * Whether to pass requests in case of store failure. + * Defaults to false. + */ + passOnStoreError?: boolean; + /** + * List of allowed IP addresses that are not rate limited. + * Defaults to [127.0.0.1, 0:0:0:0:0:0:0:1, ::1]. + */ + ipAllowList?: string[]; + /** + * Skip rate limiting for requests that have been successful. + * Defaults to false. + */ + skipSuccessfulRequests?: boolean; + /** + * Skip rate limiting for requests that have failed. + * Defaults to false. + */ + skipFailedRequests?: boolean; + }; /** * Configuration related to URL reading, used for example for reading catalog info diff --git a/packages/backend-defaults/report-rootHttpRouter.api.md b/packages/backend-defaults/report-rootHttpRouter.api.md index 91e760b7ea..84424be39d 100644 --- a/packages/backend-defaults/report-rootHttpRouter.api.md +++ b/packages/backend-defaults/report-rootHttpRouter.api.md @@ -20,7 +20,6 @@ import { RootHttpRouterService } from '@backstage/backend-plugin-api'; import { Router } from 'express'; import type { Server } from 'node:http'; import { ServiceFactory } from '@backstage/backend-plugin-api'; -import type { Store } from 'express-rate-limit'; // @public (undocumented) export function createHealthRouter(options: { @@ -111,15 +110,6 @@ export interface MiddlewareFactoryOptions { logger: LoggerService; } -// @public -export class RateLimitStoreFactory { - constructor(config: Config); - // (undocumented) - create(): Store | undefined; - // (undocumented) - redis(): Store; -} - // @public export function readCorsOptions(config?: Config): CorsOptions; diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts index 561a1999c9..853e5aa8f6 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts @@ -43,12 +43,9 @@ import { ServiceUnavailableError, } from '@backstage/errors'; import { applyInternalErrorFilter } from './applyInternalErrorFilter'; -import { DraftHeadersVersion, rateLimit } from 'express-rate-limit'; -import { - durationToMilliseconds, - HumanDuration, - JsonValue, -} from '@backstage/types'; +import { rateLimit } from 'express-rate-limit'; +import { Config } from '@backstage/config'; +import { durationToMilliseconds, HumanDuration } from '@backstage/types'; import { RateLimitStoreFactory } from './RateLimitStoreFactory'; type LogMeta = { @@ -245,15 +242,15 @@ export class MiddlewareFactory { * @returns An Express request handler */ rateLimit(): RequestHandler { - const rateLimitOptions = - this.#config.getOptionalConfig('backend.rateLimit'); - const enabled = rateLimitOptions?.getOptionalBoolean('enabled') ?? false; - if (!rateLimitOptions || !enabled) { + const conf = this.#config.getOptional( + 'backend.rateLimit', + ); + if (!conf || typeof conf !== 'object') { return (_req: Request, _res: Response, next: NextFunction) => { next(); }; } - + const rateLimitOptions = conf as Config; const window = rateLimitOptions.getOptional( 'window', ); @@ -272,32 +269,34 @@ export class MiddlewareFactory { const ipAllowList = rateLimitOptions.getOptionalStringArray( 'ipAllowList', - ) ?? ['127.0.0.1']; + ) ?? ['127.0.0.1', '0:0:0:0:0:0:0:1', '::1']; return rateLimit({ windowMs, - limit: rateLimitOptions.getOptionalNumber('limit'), - message: rateLimitOptions.getOptional('message'), - statusCode: rateLimitOptions.getOptionalNumber('statusCode'), + limit: rateLimitOptions.getOptionalNumber('incomingRequestLimit'), skipSuccessfulRequests: rateLimitOptions.getOptionalBoolean( 'skipSuccessfulRequests', ), skipFailedRequests: rateLimitOptions.getOptionalBoolean('skipFailedRequests'), - legacyHeaders: rateLimitOptions.getOptionalBoolean('legacyHeaders'), - standardHeaders: rateLimitOptions.getOptionalString( - 'standardHeaders', - ) as DraftHeadersVersion, passOnStoreError: rateLimitOptions.getOptionalBoolean('passOnStoreError'), keyGenerator(req, _res): string { if (!req.ip) { return req.socket.remoteAddress!; } - - return req.ip.replace(/:\d+[^:]*$/, ''); + return req.ip; }, skip: (req, _res) => { - return Boolean(req.ip && ipAllowList.includes(req.ip)); + return ( + Boolean(req.ip && ipAllowList.includes(req.ip)) || + Boolean( + req.socket.remoteAddress && + ipAllowList.includes(req.socket.remoteAddress), + ) + ); + }, + validate: { + trustProxy: false, }, store: new RateLimitStoreFactory(this.#config).create(), }); diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts index 105cf0cac6..4fdc513c81 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts @@ -13,49 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { mockServices } from '@backstage/backend-test-utils'; +import { mockServices, TestCaches } from '@backstage/backend-test-utils'; import { RateLimitStoreFactory } from './RateLimitStoreFactory'; import { RedisStore } from 'rate-limit-redis'; -import KeyvRedis from '@keyv/redis'; - -jest.mock('@keyv/redis'); -(KeyvRedis as jest.Mocked).mockImplementation(() => { - return { - redis: { - call: jest.fn().mockResolvedValue('OK'), - }, - }; -}); describe('CacheRateLimitStoreFactory', () => { - it('should return redis store with auto configuration', () => { - const config = mockServices.rootConfig({ - data: { - backend: { - cache: { - store: 'redis', - }, - rateLimit: { - store: undefined, - }, - }, - }, - }); - const factory = new RateLimitStoreFactory(config); - const store = factory.create(); - expect(store).toBeInstanceOf(RedisStore); - }); + const caches = TestCaches.create(); + + afterEach(jest.clearAllMocks); it('should return undefined store with auto configuration if redis is not available', () => { const config = mockServices.rootConfig({ data: { backend: { - cache: { - store: 'memory', - }, - database: { - client: 'sqlite3', - }, rateLimit: { store: undefined, }, @@ -67,12 +37,20 @@ describe('CacheRateLimitStoreFactory', () => { expect(store).toBeUndefined(); }); - it('should return redis store if configured explicitly', () => { + it('should return redis store if configured explicitly', async () => { + if (!caches.supports('REDIS_7')) { + return; + } + const conf = await caches.init('REDIS_7'); + const config = mockServices.rootConfig({ data: { backend: { rateLimit: { - store: 'redis', + store: { + client: 'redis', + connection: conf.connection, + }, }, }, }, diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts index 00e5440ebe..85eb3c4d86 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts @@ -21,46 +21,28 @@ import { RedisStore } from 'rate-limit-redis'; /** * Creates a store for `express-rate-limit` based on the configuration. * - * @public + * @internal */ export class RateLimitStoreFactory { constructor(private readonly config: Config) {} create(): Store | undefined { - const storeType = this.config.getOptionalString('backend.rateLimit.store'); - if (!storeType) { - return this.auto(); + const store = this.config.getOptionalConfig('backend.rateLimit.store'); + if (!store) { + return undefined; } - switch (storeType) { + const client = store.getString('client'); + switch (client) { case 'redis': - return this.redis(); + return this.redis(store); default: - throw new Error( - `Invalid 'backend.rateLimit.store' provided: ${storeType}`, - ); + return undefined; } } - private auto(): Store | undefined { - const cacheStore = - this.config.getOptionalString('backend.cache.store') || 'memory'; - // Use redis as primary if available - if (cacheStore === 'redis') { - return this.redis(); - } - - // Fallback to undefined (memory) - return undefined; - } - - redis(): Store { - const connectionString = - this.config.getOptionalString('backend.cache.connection') || ''; - const useRedisSets = - this.config.getOptionalBoolean('backend.cache.useRedisSets') ?? true; - const keyv = new KeyvRedis(connectionString, { - useRedisSets, - }); + redis(storeConfig: Config): Store { + const connectionString = storeConfig.getString('connection'); + const keyv = new KeyvRedis(connectionString); return new RedisStore({ // Keyv uses ioredis under the hood sendCommand: (...args: string[]) => keyv.redis.call(...args), diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/index.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/index.ts index 5c63d887bb..4a9ec14cf8 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/index.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/index.ts @@ -28,4 +28,3 @@ export type { HttpServerCertificateOptions, HttpServerOptions, } from './types'; -export { RateLimitStoreFactory } from './RateLimitStoreFactory'; From d6bd7a540d8ab2b3eee0bcce0bcb426514cf9226 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Tue, 26 Nov 2024 14:33:30 +0200 Subject: [PATCH 045/301] fix: review findings Signed-off-by: Heikki Hellgren --- .changeset/famous-terms-rescue.md | 3 +- app-config.yaml | 6 ++ packages/backend-defaults/config.d.ts | 8 +-- packages/backend-defaults/package.json | 2 +- .../rootHttpRouter/http/MiddlewareFactory.ts | 62 +++++++++++-------- .../http/RateLimitStoreFactory.test.ts | 35 ++++++----- .../http/RateLimitStoreFactory.ts | 16 ++--- .../rootHttpRouterServiceFactory.ts | 2 +- yarn.lock | 12 ++-- 9 files changed, 83 insertions(+), 63 deletions(-) diff --git a/.changeset/famous-terms-rescue.md b/.changeset/famous-terms-rescue.md index 78630eb532..9f38986934 100644 --- a/.changeset/famous-terms-rescue.md +++ b/.changeset/famous-terms-rescue.md @@ -4,11 +4,12 @@ Added new rate limit middleware to allow rate limiting requests to the backend +If you are using the `configure` callback of the root HTTP router service and do NOT call `applyDefaults()` inside it, please see [the relevant changes](https://github.com/backstage/backstage/pull/26725/files#diff-86ad1b6a694dd250823aee39d410428dd837c9d9a04ca8c33bd1081fbe3f22af) that were made, to see if you want to apply them as well to your custom configuration. Rate limiting can be turned on by adding the following configuration to `app-config.yaml`: ```yaml backend: rateLimit: - window: 60000 + window: 6000ms incomingRequestLimit: 100 ``` diff --git a/app-config.yaml b/app-config.yaml index eaee401d09..df80c7add0 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -36,6 +36,12 @@ backend: # keys: # - secret: ${BACKEND_SECRET} + # Used for testing rate limiting locally + # rateLimit: + # windowMs: 60000 + # incomingRequestLimit: 1 + # ipAllowList: [] + auth: # TODO: once plugins have been migrated we can remove this, but right now it # is require for the backend-next to work in this repo diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts index 49a51ec2f5..6cd1aa183a 100644 --- a/packages/backend-defaults/config.d.ts +++ b/packages/backend-defaults/config.d.ts @@ -791,10 +791,10 @@ export interface Config { }; /** - * Rate limiting options + * Rate limiting options. Defining this as `true` will enable rate limiting with default values. */ rateLimit?: - | false + | true | { store?: | { @@ -806,9 +806,9 @@ export interface Config { }; /** * Time frame in milliseconds or as human duration for which requests are checked/remembered. - * Defaults to 6000ms. + * Defaults to one minute. */ - window?: number | HumanDuration; + window?: string | HumanDuration; /** * The maximum number of connections to allow during the `window` before rate limiting the client. * Defaults to 5. diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index c10cb9f11f..b1f9513f2c 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -168,7 +168,7 @@ "cron": "^3.0.0", "express": "^4.17.1", "express-promise-router": "^4.1.0", - "express-rate-limit": "^7.4.0", + "express-rate-limit": "^7.5.0", "fs-extra": "^11.2.0", "git-url-parse": "^15.0.0", "helmet": "^6.0.0", diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts index 853e5aa8f6..3dddc99974 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts @@ -44,8 +44,8 @@ import { } from '@backstage/errors'; import { applyInternalErrorFilter } from './applyInternalErrorFilter'; import { rateLimit } from 'express-rate-limit'; -import { Config } from '@backstage/config'; -import { durationToMilliseconds, HumanDuration } from '@backstage/types'; +import { readDurationFromConfig } from '@backstage/config'; +import { durationToMilliseconds } from '@backstage/types'; import { RateLimitStoreFactory } from './RateLimitStoreFactory'; type LogMeta = { @@ -242,44 +242,50 @@ export class MiddlewareFactory { * @returns An Express request handler */ rateLimit(): RequestHandler { - const conf = this.#config.getOptional( - 'backend.rateLimit', - ); - if (!conf || typeof conf !== 'object') { + const enabled = this.#config.has('backend.rateLimit'); + if (!enabled) { return (_req: Request, _res: Response, next: NextFunction) => { next(); }; } - const rateLimitOptions = conf as Config; - const window = rateLimitOptions.getOptional( - 'window', - ); - let windowMs: number | undefined; - if (window !== undefined) { - if (typeof window === 'number') { - windowMs = window; - } else if (typeof window === 'object' && !Array.isArray(window)) { - windowMs = durationToMilliseconds(window); - } else { - throw new Error( - `Invalid configuration backend.rateLimit.window: ${window}, expected milliseconds number or HumanDuration object`, - ); - } + + const useDefaults = this.#config.getOptional('backend.rateLimit') === true; + const rateLimitOptions = useDefaults + ? undefined + : this.#config.getOptionalConfig('backend.rateLimit'); + + let windowMs: number = 60000; + if (rateLimitOptions && rateLimitOptions.has('window')) { + const windowDuration = readDurationFromConfig(rateLimitOptions, { + key: 'window', + }); + windowMs = durationToMilliseconds(windowDuration); } - const ipAllowList = rateLimitOptions.getOptionalStringArray( + const ipAllowList = rateLimitOptions?.getOptionalStringArray( 'ipAllowList', ) ?? ['127.0.0.1', '0:0:0:0:0:0:0:1', '::1']; return rateLimit({ windowMs, - limit: rateLimitOptions.getOptionalNumber('incomingRequestLimit'), - skipSuccessfulRequests: rateLimitOptions.getOptionalBoolean( + limit: rateLimitOptions?.getOptionalNumber('incomingRequestLimit'), + skipSuccessfulRequests: rateLimitOptions?.getOptionalBoolean( 'skipSuccessfulRequests', ), + message: { + error: { + name: 'Error', + message: `Too many requests, please try again later`, + }, + response: { + statusCode: 429, + }, + }, + statusCode: 429, skipFailedRequests: - rateLimitOptions.getOptionalBoolean('skipFailedRequests'), - passOnStoreError: rateLimitOptions.getOptionalBoolean('passOnStoreError'), + rateLimitOptions?.getOptionalBoolean('skipFailedRequests'), + passOnStoreError: + rateLimitOptions?.getOptionalBoolean('passOnStoreError'), keyGenerator(req, _res): string { if (!req.ip) { return req.socket.remoteAddress!; @@ -298,7 +304,9 @@ export class MiddlewareFactory { validate: { trustProxy: false, }, - store: new RateLimitStoreFactory(this.#config).create(), + store: useDefaults + ? undefined + : RateLimitStoreFactory.create(this.#config), }); } diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts index 4fdc513c81..c1f690474e 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts @@ -13,13 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { mockServices, TestCaches } from '@backstage/backend-test-utils'; +import { mockServices } from '@backstage/backend-test-utils'; import { RateLimitStoreFactory } from './RateLimitStoreFactory'; -import { RedisStore } from 'rate-limit-redis'; + +jest.mock('@keyv/redis', () => { + const Actual = jest.requireActual('@keyv/redis'); + return { + ...Actual, + __esModule: true, + default: jest.fn(() => { + return { + getClient: jest.fn(() => ({ + sendCommand: jest.fn().mockReturnValue('mock'), + })), + }; + }), + }; +}); describe('CacheRateLimitStoreFactory', () => { - const caches = TestCaches.create(); - afterEach(jest.clearAllMocks); it('should return undefined store with auto configuration if redis is not available', () => { @@ -32,31 +44,24 @@ describe('CacheRateLimitStoreFactory', () => { }, }, }); - const factory = new RateLimitStoreFactory(config); - const store = factory.create(); + const store = RateLimitStoreFactory.create(config); expect(store).toBeUndefined(); }); it('should return redis store if configured explicitly', async () => { - if (!caches.supports('REDIS_7')) { - return; - } - const conf = await caches.init('REDIS_7'); - const config = mockServices.rootConfig({ data: { backend: { rateLimit: { store: { client: 'redis', - connection: conf.connection, + connection: 'redis://localhost:6379', }, }, }, }, }); - const factory = new RateLimitStoreFactory(config); - const store = factory.create(); - expect(store).toBeInstanceOf(RedisStore); + const store = RateLimitStoreFactory.create(config); + expect(store).not.toBeUndefined(); }); }); diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts index 85eb3c4d86..1275095aca 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts @@ -15,7 +15,6 @@ */ import { Config } from '@backstage/config'; import type { Store } from 'express-rate-limit'; -import KeyvRedis from '@keyv/redis'; import { RedisStore } from 'rate-limit-redis'; /** @@ -24,10 +23,8 @@ import { RedisStore } from 'rate-limit-redis'; * @internal */ export class RateLimitStoreFactory { - constructor(private readonly config: Config) {} - - create(): Store | undefined { - const store = this.config.getOptionalConfig('backend.rateLimit.store'); + static create(config: Config): Store | undefined { + const store = config.getOptionalConfig('backend.rateLimit.store'); if (!store) { return undefined; } @@ -40,12 +37,15 @@ export class RateLimitStoreFactory { } } - redis(storeConfig: Config): Store { + private static redis(storeConfig: Config): Store { const connectionString = storeConfig.getString('connection'); + const KeyvRedis = require('@keyv/redis').default; const keyv = new KeyvRedis(connectionString); return new RedisStore({ - // Keyv uses ioredis under the hood - sendCommand: (...args: string[]) => keyv.redis.call(...args), + sendCommand: async (...args: string[]) => { + const client = await keyv.getClient(); + return client.sendCommand(args); + }, }); } } diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts index 7d0d778b0c..3b50515742 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts @@ -117,11 +117,11 @@ const rootHttpRouterServiceFactoryWithOptions = ( if (trustProxy !== undefined) { app.set('trust proxy', trustProxy); } - app.use(middleware.rateLimit()); app.use(middleware.helmet()); app.use(middleware.cors()); app.use(middleware.compression()); app.use(middleware.logging()); + app.use(middleware.rateLimit()); app.use(healthRouter); app.use(routes); app.use(middleware.notFound()); diff --git a/yarn.lock b/yarn.lock index 0a73d77844..aebb875a10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3632,7 +3632,7 @@ __metadata: cron: "npm:^3.0.0" express: "npm:^4.17.1" express-promise-router: "npm:^4.1.0" - express-rate-limit: "npm:^7.4.0" + express-rate-limit: "npm:^7.5.0" fs-extra: "npm:^11.2.0" git-url-parse: "npm:^15.0.0" helmet: "npm:^6.0.0" @@ -30045,12 +30045,12 @@ __metadata: languageName: node linkType: hard -"express-rate-limit@npm:^7.4.0": - version: 7.4.0 - resolution: "express-rate-limit@npm:7.4.0" +"express-rate-limit@npm:^7.5.0": + version: 7.5.0 + resolution: "express-rate-limit@npm:7.5.0" peerDependencies: - express: 4 || 5 || ^5.0.0-beta.1 - checksum: 10/33178c652bb1472aad2022194b5cd7963bd3e74d3eaf5e49eb1491a968fdce54551cc76b097ac10d3a1646d62cec2e6f2405ccef5ef5b60152a0c4a148749a4d + express: ^4.11 || 5 || ^5.0.0-beta.1 + checksum: 10/eff34c83bf586789933a332a339b66649e2cca95c8e977d193aa8bead577d3182ac9f0e9c26f39389287539b8038890ff023f910b54ebb506a26a2ce135b92ca languageName: node linkType: hard From c05a6982eabffaddc1915faa720bffe65ac1777e Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Tue, 4 Feb 2025 08:06:37 +0200 Subject: [PATCH 046/301] feat: support postgres store for rate limiting Signed-off-by: Hellgren Heikki --- .changeset/famous-terms-rescue.md | 4 +- app-config.yaml | 2 +- packages/backend-defaults/config.d.ts | 16 +++ packages/backend-defaults/package.json | 1 + .../http/RateLimitStoreFactory.test.ts | 21 ++- .../http/RateLimitStoreFactory.ts | 15 ++ yarn.lock | 130 +++++++++++++++++- 7 files changed, 184 insertions(+), 5 deletions(-) diff --git a/.changeset/famous-terms-rescue.md b/.changeset/famous-terms-rescue.md index 9f38986934..68fd1f3591 100644 --- a/.changeset/famous-terms-rescue.md +++ b/.changeset/famous-terms-rescue.md @@ -4,12 +4,12 @@ Added new rate limit middleware to allow rate limiting requests to the backend -If you are using the `configure` callback of the root HTTP router service and do NOT call `applyDefaults()` inside it, please see [the relevant changes](https://github.com/backstage/backstage/pull/26725/files#diff-86ad1b6a694dd250823aee39d410428dd837c9d9a04ca8c33bd1081fbe3f22af) that were made, to see if you want to apply them as well to your custom configuration. +If you are using the `configure` callback of the root HTTP router service and do NOT call `applyDefaults()` inside it, please see [the relevant changes](https://github.com/backstage/backstage/pull/28708/files#diff-86ad1b6a694dd250823aee39d410428dd837c9d9a04ca8c33bd1081fbe3f22af) that were made, to see if you want to apply them as well to your custom configuration. Rate limiting can be turned on by adding the following configuration to `app-config.yaml`: ```yaml backend: rateLimit: - window: 6000ms + window: 6s incomingRequestLimit: 100 ``` diff --git a/app-config.yaml b/app-config.yaml index df80c7add0..89926357c2 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -38,7 +38,7 @@ backend: # Used for testing rate limiting locally # rateLimit: - # windowMs: 60000 + # windowMs: 1m # incomingRequestLimit: 1 # ipAllowList: [] diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts index 6cd1aa183a..67183896bb 100644 --- a/packages/backend-defaults/config.d.ts +++ b/packages/backend-defaults/config.d.ts @@ -801,6 +801,22 @@ export interface Config { client: 'redis'; connection: string; } + | { + client: 'postgres'; + connection: + | string + | { + /** + * @visibility secret + */ + password?: string; + /** + * Other connection settings + * @see https://node-postgres.com/apis/client + */ + [key: string]: unknown; + }; + } | { client: 'memory'; }; diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index b1f9513f2c..bca883c44e 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -130,6 +130,7 @@ "test": "backstage-cli package test" }, "dependencies": { + "@acpr/rate-limit-postgresql": "^1.4.1", "@aws-sdk/abort-controller": "^3.347.0", "@aws-sdk/client-codecommit": "^3.350.0", "@aws-sdk/client-s3": "^3.350.0", diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts index c1f690474e..6fe4a2d327 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts @@ -15,6 +15,8 @@ */ import { mockServices } from '@backstage/backend-test-utils'; import { RateLimitStoreFactory } from './RateLimitStoreFactory'; +import { RedisStore } from 'rate-limit-redis'; +import { PostgresStore } from '@acpr/rate-limit-postgresql'; jest.mock('@keyv/redis', () => { const Actual = jest.requireActual('@keyv/redis'); @@ -62,6 +64,23 @@ describe('CacheRateLimitStoreFactory', () => { }, }); const store = RateLimitStoreFactory.create(config); - expect(store).not.toBeUndefined(); + expect(store).toBeInstanceOf(RedisStore); + }); + + it('should return postgres store if configured explicitly', async () => { + const config = mockServices.rootConfig({ + data: { + backend: { + rateLimit: { + store: { + client: 'postgres', + connection: 'postgres://localhost:5432', + }, + }, + }, + }, + }); + const store = RateLimitStoreFactory.create(config); + expect(store).toBeInstanceOf(PostgresStore); }); }); diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts index 1275095aca..3905655587 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts @@ -16,6 +16,8 @@ import { Config } from '@backstage/config'; import type { Store } from 'express-rate-limit'; import { RedisStore } from 'rate-limit-redis'; +import { parsePgConnectionString } from '../../database/connectors/postgres.ts'; +import { PostgresStore } from '@acpr/rate-limit-postgresql'; /** * Creates a store for `express-rate-limit` based on the configuration. @@ -32,6 +34,9 @@ export class RateLimitStoreFactory { switch (client) { case 'redis': return this.redis(store); + case 'postgres': + return this.postgres(store); + case 'memory': default: return undefined; } @@ -48,4 +53,14 @@ export class RateLimitStoreFactory { }, }); } + + private static postgres(storeConfig: Config): Store { + const connection = storeConfig.get('connection') as any; + const isConnectionString = + typeof connection === 'string' || connection instanceof String; + const connectionOptions = isConnectionString + ? parsePgConnectionString(connection as string) + : connection; + return new PostgresStore(connectionOptions, 'rl'); + } } diff --git a/yarn.lock b/yarn.lock index aebb875a10..66f819db26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,20 @@ __metadata: languageName: node linkType: hard +"@acpr/rate-limit-postgresql@npm:^1.4.1": + version: 1.4.1 + resolution: "@acpr/rate-limit-postgresql@npm:1.4.1" + dependencies: + "@types/pg-pool": "npm:2.0.3" + pg: "npm:8.11.3" + pg-pool: "npm:3.6.1" + postgres-migrations: "npm:5.3.0" + peerDependencies: + express-rate-limit: ">=6.0.0" + checksum: 10/9295f86890ea10f0be24a211f100cfe9dde40df20d8328be36a66736e36ee7043dc6fcae785e39bea19de3f43ad344f3e0fa3f9d40bc8d89d38bf6ce457bcc28 + languageName: node + linkType: hard + "@adobe/css-tools@npm:^4.4.0": version: 4.4.0 resolution: "@adobe/css-tools@npm:4.4.0" @@ -3580,6 +3594,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/backend-defaults@workspace:packages/backend-defaults" dependencies: + "@acpr/rate-limit-postgresql": "npm:^1.4.1" "@aws-sdk/abort-controller": "npm:^3.347.0" "@aws-sdk/client-codecommit": "npm:^3.350.0" "@aws-sdk/client-s3": "npm:^3.350.0" @@ -20987,6 +21002,15 @@ __metadata: languageName: node linkType: hard +"@types/pg-pool@npm:2.0.3": + version: 2.0.3 + resolution: "@types/pg-pool@npm:2.0.3" + dependencies: + "@types/pg": "npm:*" + checksum: 10/9ea0bcdbdd09c9de6f774e59465189e552ee094901724278082c41ba6287e7fddffb9ba4b4107c242bba4e8f8a1f0016e6a1eb0c6ca306d43c08b5ddd7f34549 + languageName: node + linkType: hard + "@types/pg-pool@npm:2.0.6": version: 2.0.6 resolution: "@types/pg-pool@npm:2.0.6" @@ -25149,6 +25173,13 @@ __metadata: languageName: node linkType: hard +"buffer-writer@npm:2.0.0": + version: 2.0.0 + resolution: "buffer-writer@npm:2.0.0" + checksum: 10/fdca8e28c55704de7af2f41c8f875293de69ad22005d5041d54aa916d125cead00afa969bc09e4702ae6b66e098409958c06bebfc97fcf8fa4ea5afcae088cd9 + languageName: node + linkType: hard + "buffer-xor@npm:^1.0.3": version: 1.0.3 resolution: "buffer-xor@npm:1.0.3" @@ -39963,6 +39994,13 @@ __metadata: languageName: node linkType: hard +"packet-reader@npm:1.0.0": + version: 1.0.0 + resolution: "packet-reader@npm:1.0.0" + checksum: 10/8504cc8c32672380867e933516a029b1d4dd784c139213c85c9042ffc1162de48ec914f8c71260a9311518694cf5d0be11c67357f4b536129d2ea42aa7257ec0 + languageName: node + linkType: hard + "pacote@npm:^12.0.0, pacote@npm:^12.0.2": version: 12.0.3 resolution: "pacote@npm:12.0.3" @@ -40492,7 +40530,7 @@ __metadata: languageName: node linkType: hard -"pg-connection-string@npm:^2.3.0, pg-connection-string@npm:^2.5.0, pg-connection-string@npm:^2.7.0": +"pg-connection-string@npm:^2.3.0, pg-connection-string@npm:^2.5.0, pg-connection-string@npm:^2.6.2, pg-connection-string@npm:^2.7.0": version: 2.7.0 resolution: "pg-connection-string@npm:2.7.0" checksum: 10/68015a8874b7ca5dad456445e4114af3d2602bac2fdb8069315ecad0ff9660ec93259b9af7186606529ac4f6f72a06831e6f20897a689b16cc7fda7ca0e247fd @@ -40520,6 +40558,24 @@ __metadata: languageName: node linkType: hard +"pg-pool@npm:3.6.1": + version: 3.6.1 + resolution: "pg-pool@npm:3.6.1" + peerDependencies: + pg: ">=8.0" + checksum: 10/5d1b02b959e6c849004d8f3d2222c48d3b3b67b7b1eb5f2e5819ed9412129ea6b0f0376bc74ddf197973c99575d325cbb3f64a8017ab520535c011329b12fffb + languageName: node + linkType: hard + +"pg-pool@npm:^3.6.1, pg-pool@npm:^3.7.0": + version: 3.7.0 + resolution: "pg-pool@npm:3.7.0" + peerDependencies: + pg: ">=8.0" + checksum: 10/a07a4f9e26eec9d7ac3597dc7b3469c62983edff9a321dbb7acbe1bbc7f5e9b2d33438e277d4cf8145071f3d63c7ebdc287a539fd69dfb8cdddb15b33eefe1a2 + languageName: node + linkType: hard + "pg-pool@npm:^3.8.0": version: 3.8.0 resolution: "pg-pool@npm:3.8.0" @@ -40536,6 +40592,13 @@ __metadata: languageName: node linkType: hard +"pg-protocol@npm:^1.6.0, pg-protocol@npm:^1.7.0": + version: 1.7.0 + resolution: "pg-protocol@npm:1.7.0" + checksum: 10/ffffdf74426c9357b57050f1c191e84447c0e8b2a701b3ab302ac7dd0eb27b862d92e5e3b2d38876a1051de83547eb9165d6a58b3a8e90bb050dae97f9993d54 + languageName: node + linkType: hard + "pg-types@npm:^2.1.0, pg-types@npm:^2.2.0": version: 2.2.0 resolution: "pg-types@npm:2.2.0" @@ -40564,6 +40627,30 @@ __metadata: languageName: node linkType: hard +"pg@npm:8.11.3": + version: 8.11.3 + resolution: "pg@npm:8.11.3" + dependencies: + buffer-writer: "npm:2.0.0" + packet-reader: "npm:1.0.0" + pg-cloudflare: "npm:^1.1.1" + pg-connection-string: "npm:^2.6.2" + pg-pool: "npm:^3.6.1" + pg-protocol: "npm:^1.6.0" + pg-types: "npm:^2.1.0" + pgpass: "npm:1.x" + peerDependencies: + pg-native: ">=3.0.1" + dependenciesMeta: + pg-cloudflare: + optional: true + peerDependenciesMeta: + pg-native: + optional: true + checksum: 10/f15f29c8e17723ee1da72abdf400cbed2c04602c58c93687f3f0068e71df2a6fb62b9a3543e13da21b10a0494f4c5b4cfc8d6cd8396617b76c4cbfd6ddab17e7 + languageName: node + linkType: hard + "pg@npm:^8.11.3, pg@npm:^8.9.0": version: 8.14.1 resolution: "pg@npm:8.14.1" @@ -40586,6 +40673,28 @@ __metadata: languageName: node linkType: hard +"pg@npm:^8.6.0": + version: 8.13.1 + resolution: "pg@npm:8.13.1" + dependencies: + pg-cloudflare: "npm:^1.1.1" + pg-connection-string: "npm:^2.7.0" + pg-pool: "npm:^3.7.0" + pg-protocol: "npm:^1.7.0" + pg-types: "npm:^2.1.0" + pgpass: "npm:1.x" + peerDependencies: + pg-native: ">=3.0.1" + dependenciesMeta: + pg-cloudflare: + optional: true + peerDependenciesMeta: + pg-native: + optional: true + checksum: 10/542aa49fcb37657cf5f779b4a31fe6eb336e683445ecca38e267eeb0ca85d873ffe51f04794f9f9e184187e9f74bf7895e932a0fa9507132ac0dfc76c7c73451 + languageName: node + linkType: hard + "pgpass@npm:1.x": version: 1.0.2 resolution: "pgpass@npm:1.0.2" @@ -41353,6 +41462,18 @@ __metadata: languageName: node linkType: hard +"postgres-migrations@npm:5.3.0": + version: 5.3.0 + resolution: "postgres-migrations@npm:5.3.0" + dependencies: + pg: "npm:^8.6.0" + sql-template-strings: "npm:^2.2.2" + bin: + pg-validate-migrations: dist/bin/validate.js + checksum: 10/520d95f01144f88689d5c0a7575743c4f99536935deb1ffff7b3765883a688c4f001d98e8b493ca9b342cd2609593970c3d2198b41fade648f102008e3607226 + languageName: node + linkType: hard + "postgres-range@npm:^1.1.1": version: 1.1.3 resolution: "postgres-range@npm:1.1.3" @@ -45172,6 +45293,13 @@ __metadata: languageName: node linkType: hard +"sql-template-strings@npm:^2.2.2": + version: 2.2.2 + resolution: "sql-template-strings@npm:2.2.2" + checksum: 10/594378a44acbaf3db8a4067137c0c315d0656fcc1b6b8fa76c760d032c1970bf6ede2b31690a3bdc6482d86cbff8b202bb14f6528aa1d9d6bf19d48b03ba2744 + languageName: node + linkType: hard + "sqlstring@npm:^2.3.2": version: 2.3.2 resolution: "sqlstring@npm:2.3.2" From 6633c138b583146db5574feac33f4b1712f43d36 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Thu, 27 Feb 2025 11:40:11 +0200 Subject: [PATCH 047/301] fix: review findings + docs update Signed-off-by: Hellgren Heikki --- .../core-services/root-http-router.md | 5 + packages/backend-defaults/config.d.ts | 20 +-- packages/backend-defaults/package.json | 1 - .../http/RateLimitStoreFactory.test.ts | 18 --- .../http/RateLimitStoreFactory.ts | 18 +-- yarn.lock | 130 +----------------- 6 files changed, 10 insertions(+), 182 deletions(-) diff --git a/docs/backend-system/core-services/root-http-router.md b/docs/backend-system/core-services/root-http-router.md index 7a268e27fc..bdaf7bba23 100644 --- a/docs/backend-system/core-services/root-http-router.md +++ b/docs/backend-system/core-services/root-http-router.md @@ -121,6 +121,11 @@ backend.add( app.use(middleware.cors()); app.use(middleware.compression()); + // Optional rate limiting middleware + app.use(middleware.rateLimit()); + // If you are using rate limiting behind a proxy, you should set the `trust proxy` setting to true + app.set('trust proxy', true); + app.use(healthRouter); // you can add you your own middleware in here diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts index 67183896bb..f67680d1e2 100644 --- a/packages/backend-defaults/config.d.ts +++ b/packages/backend-defaults/config.d.ts @@ -798,27 +798,11 @@ export interface Config { | { store?: | { - client: 'redis'; + type: 'redis'; connection: string; } | { - client: 'postgres'; - connection: - | string - | { - /** - * @visibility secret - */ - password?: string; - /** - * Other connection settings - * @see https://node-postgres.com/apis/client - */ - [key: string]: unknown; - }; - } - | { - client: 'memory'; + type: 'memory'; }; /** * Time frame in milliseconds or as human duration for which requests are checked/remembered. diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index bca883c44e..b1f9513f2c 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -130,7 +130,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@acpr/rate-limit-postgresql": "^1.4.1", "@aws-sdk/abort-controller": "^3.347.0", "@aws-sdk/client-codecommit": "^3.350.0", "@aws-sdk/client-s3": "^3.350.0", diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts index 6fe4a2d327..f1044979e8 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts @@ -16,7 +16,6 @@ import { mockServices } from '@backstage/backend-test-utils'; import { RateLimitStoreFactory } from './RateLimitStoreFactory'; import { RedisStore } from 'rate-limit-redis'; -import { PostgresStore } from '@acpr/rate-limit-postgresql'; jest.mock('@keyv/redis', () => { const Actual = jest.requireActual('@keyv/redis'); @@ -66,21 +65,4 @@ describe('CacheRateLimitStoreFactory', () => { const store = RateLimitStoreFactory.create(config); expect(store).toBeInstanceOf(RedisStore); }); - - it('should return postgres store if configured explicitly', async () => { - const config = mockServices.rootConfig({ - data: { - backend: { - rateLimit: { - store: { - client: 'postgres', - connection: 'postgres://localhost:5432', - }, - }, - }, - }, - }); - const store = RateLimitStoreFactory.create(config); - expect(store).toBeInstanceOf(PostgresStore); - }); }); diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts index 3905655587..0a24a8792c 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts @@ -16,8 +16,6 @@ import { Config } from '@backstage/config'; import type { Store } from 'express-rate-limit'; import { RedisStore } from 'rate-limit-redis'; -import { parsePgConnectionString } from '../../database/connectors/postgres.ts'; -import { PostgresStore } from '@acpr/rate-limit-postgresql'; /** * Creates a store for `express-rate-limit` based on the configuration. @@ -30,12 +28,10 @@ export class RateLimitStoreFactory { if (!store) { return undefined; } - const client = store.getString('client'); - switch (client) { + const type = store.getString('type'); + switch (type) { case 'redis': return this.redis(store); - case 'postgres': - return this.postgres(store); case 'memory': default: return undefined; @@ -53,14 +49,4 @@ export class RateLimitStoreFactory { }, }); } - - private static postgres(storeConfig: Config): Store { - const connection = storeConfig.get('connection') as any; - const isConnectionString = - typeof connection === 'string' || connection instanceof String; - const connectionOptions = isConnectionString - ? parsePgConnectionString(connection as string) - : connection; - return new PostgresStore(connectionOptions, 'rl'); - } } diff --git a/yarn.lock b/yarn.lock index 66f819db26..aebb875a10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,20 +12,6 @@ __metadata: languageName: node linkType: hard -"@acpr/rate-limit-postgresql@npm:^1.4.1": - version: 1.4.1 - resolution: "@acpr/rate-limit-postgresql@npm:1.4.1" - dependencies: - "@types/pg-pool": "npm:2.0.3" - pg: "npm:8.11.3" - pg-pool: "npm:3.6.1" - postgres-migrations: "npm:5.3.0" - peerDependencies: - express-rate-limit: ">=6.0.0" - checksum: 10/9295f86890ea10f0be24a211f100cfe9dde40df20d8328be36a66736e36ee7043dc6fcae785e39bea19de3f43ad344f3e0fa3f9d40bc8d89d38bf6ce457bcc28 - languageName: node - linkType: hard - "@adobe/css-tools@npm:^4.4.0": version: 4.4.0 resolution: "@adobe/css-tools@npm:4.4.0" @@ -3594,7 +3580,6 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/backend-defaults@workspace:packages/backend-defaults" dependencies: - "@acpr/rate-limit-postgresql": "npm:^1.4.1" "@aws-sdk/abort-controller": "npm:^3.347.0" "@aws-sdk/client-codecommit": "npm:^3.350.0" "@aws-sdk/client-s3": "npm:^3.350.0" @@ -21002,15 +20987,6 @@ __metadata: languageName: node linkType: hard -"@types/pg-pool@npm:2.0.3": - version: 2.0.3 - resolution: "@types/pg-pool@npm:2.0.3" - dependencies: - "@types/pg": "npm:*" - checksum: 10/9ea0bcdbdd09c9de6f774e59465189e552ee094901724278082c41ba6287e7fddffb9ba4b4107c242bba4e8f8a1f0016e6a1eb0c6ca306d43c08b5ddd7f34549 - languageName: node - linkType: hard - "@types/pg-pool@npm:2.0.6": version: 2.0.6 resolution: "@types/pg-pool@npm:2.0.6" @@ -25173,13 +25149,6 @@ __metadata: languageName: node linkType: hard -"buffer-writer@npm:2.0.0": - version: 2.0.0 - resolution: "buffer-writer@npm:2.0.0" - checksum: 10/fdca8e28c55704de7af2f41c8f875293de69ad22005d5041d54aa916d125cead00afa969bc09e4702ae6b66e098409958c06bebfc97fcf8fa4ea5afcae088cd9 - languageName: node - linkType: hard - "buffer-xor@npm:^1.0.3": version: 1.0.3 resolution: "buffer-xor@npm:1.0.3" @@ -39994,13 +39963,6 @@ __metadata: languageName: node linkType: hard -"packet-reader@npm:1.0.0": - version: 1.0.0 - resolution: "packet-reader@npm:1.0.0" - checksum: 10/8504cc8c32672380867e933516a029b1d4dd784c139213c85c9042ffc1162de48ec914f8c71260a9311518694cf5d0be11c67357f4b536129d2ea42aa7257ec0 - languageName: node - linkType: hard - "pacote@npm:^12.0.0, pacote@npm:^12.0.2": version: 12.0.3 resolution: "pacote@npm:12.0.3" @@ -40530,7 +40492,7 @@ __metadata: languageName: node linkType: hard -"pg-connection-string@npm:^2.3.0, pg-connection-string@npm:^2.5.0, pg-connection-string@npm:^2.6.2, pg-connection-string@npm:^2.7.0": +"pg-connection-string@npm:^2.3.0, pg-connection-string@npm:^2.5.0, pg-connection-string@npm:^2.7.0": version: 2.7.0 resolution: "pg-connection-string@npm:2.7.0" checksum: 10/68015a8874b7ca5dad456445e4114af3d2602bac2fdb8069315ecad0ff9660ec93259b9af7186606529ac4f6f72a06831e6f20897a689b16cc7fda7ca0e247fd @@ -40558,24 +40520,6 @@ __metadata: languageName: node linkType: hard -"pg-pool@npm:3.6.1": - version: 3.6.1 - resolution: "pg-pool@npm:3.6.1" - peerDependencies: - pg: ">=8.0" - checksum: 10/5d1b02b959e6c849004d8f3d2222c48d3b3b67b7b1eb5f2e5819ed9412129ea6b0f0376bc74ddf197973c99575d325cbb3f64a8017ab520535c011329b12fffb - languageName: node - linkType: hard - -"pg-pool@npm:^3.6.1, pg-pool@npm:^3.7.0": - version: 3.7.0 - resolution: "pg-pool@npm:3.7.0" - peerDependencies: - pg: ">=8.0" - checksum: 10/a07a4f9e26eec9d7ac3597dc7b3469c62983edff9a321dbb7acbe1bbc7f5e9b2d33438e277d4cf8145071f3d63c7ebdc287a539fd69dfb8cdddb15b33eefe1a2 - languageName: node - linkType: hard - "pg-pool@npm:^3.8.0": version: 3.8.0 resolution: "pg-pool@npm:3.8.0" @@ -40592,13 +40536,6 @@ __metadata: languageName: node linkType: hard -"pg-protocol@npm:^1.6.0, pg-protocol@npm:^1.7.0": - version: 1.7.0 - resolution: "pg-protocol@npm:1.7.0" - checksum: 10/ffffdf74426c9357b57050f1c191e84447c0e8b2a701b3ab302ac7dd0eb27b862d92e5e3b2d38876a1051de83547eb9165d6a58b3a8e90bb050dae97f9993d54 - languageName: node - linkType: hard - "pg-types@npm:^2.1.0, pg-types@npm:^2.2.0": version: 2.2.0 resolution: "pg-types@npm:2.2.0" @@ -40627,30 +40564,6 @@ __metadata: languageName: node linkType: hard -"pg@npm:8.11.3": - version: 8.11.3 - resolution: "pg@npm:8.11.3" - dependencies: - buffer-writer: "npm:2.0.0" - packet-reader: "npm:1.0.0" - pg-cloudflare: "npm:^1.1.1" - pg-connection-string: "npm:^2.6.2" - pg-pool: "npm:^3.6.1" - pg-protocol: "npm:^1.6.0" - pg-types: "npm:^2.1.0" - pgpass: "npm:1.x" - peerDependencies: - pg-native: ">=3.0.1" - dependenciesMeta: - pg-cloudflare: - optional: true - peerDependenciesMeta: - pg-native: - optional: true - checksum: 10/f15f29c8e17723ee1da72abdf400cbed2c04602c58c93687f3f0068e71df2a6fb62b9a3543e13da21b10a0494f4c5b4cfc8d6cd8396617b76c4cbfd6ddab17e7 - languageName: node - linkType: hard - "pg@npm:^8.11.3, pg@npm:^8.9.0": version: 8.14.1 resolution: "pg@npm:8.14.1" @@ -40673,28 +40586,6 @@ __metadata: languageName: node linkType: hard -"pg@npm:^8.6.0": - version: 8.13.1 - resolution: "pg@npm:8.13.1" - dependencies: - pg-cloudflare: "npm:^1.1.1" - pg-connection-string: "npm:^2.7.0" - pg-pool: "npm:^3.7.0" - pg-protocol: "npm:^1.7.0" - pg-types: "npm:^2.1.0" - pgpass: "npm:1.x" - peerDependencies: - pg-native: ">=3.0.1" - dependenciesMeta: - pg-cloudflare: - optional: true - peerDependenciesMeta: - pg-native: - optional: true - checksum: 10/542aa49fcb37657cf5f779b4a31fe6eb336e683445ecca38e267eeb0ca85d873ffe51f04794f9f9e184187e9f74bf7895e932a0fa9507132ac0dfc76c7c73451 - languageName: node - linkType: hard - "pgpass@npm:1.x": version: 1.0.2 resolution: "pgpass@npm:1.0.2" @@ -41462,18 +41353,6 @@ __metadata: languageName: node linkType: hard -"postgres-migrations@npm:5.3.0": - version: 5.3.0 - resolution: "postgres-migrations@npm:5.3.0" - dependencies: - pg: "npm:^8.6.0" - sql-template-strings: "npm:^2.2.2" - bin: - pg-validate-migrations: dist/bin/validate.js - checksum: 10/520d95f01144f88689d5c0a7575743c4f99536935deb1ffff7b3765883a688c4f001d98e8b493ca9b342cd2609593970c3d2198b41fade648f102008e3607226 - languageName: node - linkType: hard - "postgres-range@npm:^1.1.1": version: 1.1.3 resolution: "postgres-range@npm:1.1.3" @@ -45293,13 +45172,6 @@ __metadata: languageName: node linkType: hard -"sql-template-strings@npm:^2.2.2": - version: 2.2.2 - resolution: "sql-template-strings@npm:2.2.2" - checksum: 10/594378a44acbaf3db8a4067137c0c315d0656fcc1b6b8fa76c760d032c1970bf6ede2b31690a3bdc6482d86cbff8b202bb14f6528aa1d9d6bf19d48b03ba2744 - languageName: node - linkType: hard - "sqlstring@npm:^2.3.2": version: 2.3.2 resolution: "sqlstring@npm:2.3.2" From a68bbc54c02524bf679247802601c376127c6b88 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Thu, 27 Feb 2025 15:02:43 +0200 Subject: [PATCH 048/301] feat: allow plugin specific rate limiting Signed-off-by: Hellgren Heikki --- packages/backend-defaults/config.d.ts | 40 +++++++++ .../http/createRateLimitMiddleware.ts | 40 +++++++++ .../httpRouter/httpRouterServiceFactory.ts | 7 +- .../rootHttpRouter/http/MiddlewareFactory.ts | 64 +++------------ .../RateLimitStoreFactory.test.ts | 4 +- .../http => lib}/RateLimitStoreFactory.ts | 0 .../src/lib/rateLimitMiddleware.ts | 81 +++++++++++++++++++ 7 files changed, 180 insertions(+), 56 deletions(-) create mode 100644 packages/backend-defaults/src/entrypoints/httpRouter/http/createRateLimitMiddleware.ts rename packages/backend-defaults/src/{entrypoints/rootHttpRouter/http => lib}/RateLimitStoreFactory.test.ts (95%) rename packages/backend-defaults/src/{entrypoints/rootHttpRouter/http => lib}/RateLimitStoreFactory.ts (100%) create mode 100644 packages/backend-defaults/src/lib/rateLimitMiddleware.ts diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts index f67680d1e2..167a8935f9 100644 --- a/packages/backend-defaults/config.d.ts +++ b/packages/backend-defaults/config.d.ts @@ -804,6 +804,11 @@ export interface Config { | { type: 'memory'; }; + /** + * Enable/disable global rate limiting. If this is disabled, plugin specific rate limiting must be + * used. + */ + global?: boolean; /** * Time frame in milliseconds or as human duration for which requests are checked/remembered. * Defaults to one minute. @@ -834,6 +839,41 @@ export interface Config { * Defaults to false. */ skipFailedRequests?: boolean; + /** Plugin specific rate limiting configuration */ + plugin?: { + [pluginId: string]: { + /** + * Time frame in milliseconds or as human duration for which requests are checked/remembered. + * Defaults to one minute. + */ + window?: string | HumanDuration; + /** + * The maximum number of connections to allow during the `window` before rate limiting the client. + * Defaults to 5. + */ + incomingRequestLimit?: number; + /** + * Whether to pass requests in case of store failure. + * Defaults to false. + */ + passOnStoreError?: boolean; + /** + * List of allowed IP addresses that are not rate limited. + * Defaults to [127.0.0.1, 0:0:0:0:0:0:0:1, ::1]. + */ + ipAllowList?: string[]; + /** + * Skip rate limiting for requests that have been successful. + * Defaults to false. + */ + skipSuccessfulRequests?: boolean; + /** + * Skip rate limiting for requests that have failed. + * Defaults to false. + */ + skipFailedRequests?: boolean; + }; + }; }; /** diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/http/createRateLimitMiddleware.ts b/packages/backend-defaults/src/entrypoints/httpRouter/http/createRateLimitMiddleware.ts new file mode 100644 index 0000000000..0e9239650b --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/httpRouter/http/createRateLimitMiddleware.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NextFunction, Request, Response } from 'express'; +import { RateLimitStoreFactory } from '../../../lib/RateLimitStoreFactory.ts'; +import { Config } from '@backstage/config'; +import { rateLimitMiddleware } from '../../../lib/rateLimitMiddleware.ts'; + +export const createRateLimitMiddleware = (options: { + pluginId: string; + config: Config; +}) => { + const { pluginId, config } = options; + const configKey = `backend.rateLimit.${pluginId}`; + const enabled = config.has(configKey); + if (!enabled) { + return (_req: Request, _res: Response, next: NextFunction) => { + next(); + }; + } + + const rateLimitOptions = config.getConfig(configKey); + + return rateLimitMiddleware({ + store: RateLimitStoreFactory.create(config), + config: rateLimitOptions, + }); +}; diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.ts b/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.ts index 1f2b989018..0fc70a07f0 100644 --- a/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.ts @@ -22,12 +22,13 @@ import { HttpRouterServiceAuthPolicy, } from '@backstage/backend-plugin-api'; import { - createLifecycleMiddleware, + createAuthIntegrationRouter, createCookieAuthRefreshMiddleware, createCredentialsBarrier, - createAuthIntegrationRouter, + createLifecycleMiddleware, } from './http'; import { MiddlewareFactory } from '../rootHttpRouter'; +import { createRateLimitMiddleware } from './http/createRateLimitMiddleware.ts'; /** * HTTP route registration for plugins. @@ -61,6 +62,8 @@ export const httpRouterServiceFactory = createServiceFactory({ }) { const router = PromiseRouter(); + router.use(createRateLimitMiddleware({ pluginId: plugin.getId(), config })); + rootHttpRouter.use(`/api/${plugin.getId()}`, router); const credentialsBarrier = createCredentialsBarrier({ diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts index 3dddc99974..9a76ec5a59 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts @@ -43,10 +43,8 @@ import { ServiceUnavailableError, } from '@backstage/errors'; import { applyInternalErrorFilter } from './applyInternalErrorFilter'; -import { rateLimit } from 'express-rate-limit'; -import { readDurationFromConfig } from '@backstage/config'; -import { durationToMilliseconds } from '@backstage/types'; -import { RateLimitStoreFactory } from './RateLimitStoreFactory'; +import { RateLimitStoreFactory } from '../../../lib/RateLimitStoreFactory.ts'; +import { rateLimitMiddleware } from '../../../lib/rateLimitMiddleware.ts'; type LogMeta = { date: string; @@ -254,59 +252,21 @@ export class MiddlewareFactory { ? undefined : this.#config.getOptionalConfig('backend.rateLimit'); - let windowMs: number = 60000; - if (rateLimitOptions && rateLimitOptions.has('window')) { - const windowDuration = readDurationFromConfig(rateLimitOptions, { - key: 'window', - }); - windowMs = durationToMilliseconds(windowDuration); + // Global rate limiting disabled + if ( + rateLimitOptions && + rateLimitOptions.getOptionalBoolean('global') === false + ) { + return (_req: Request, _res: Response, next: NextFunction) => { + next(); + }; } - const ipAllowList = rateLimitOptions?.getOptionalStringArray( - 'ipAllowList', - ) ?? ['127.0.0.1', '0:0:0:0:0:0:0:1', '::1']; - - return rateLimit({ - windowMs, - limit: rateLimitOptions?.getOptionalNumber('incomingRequestLimit'), - skipSuccessfulRequests: rateLimitOptions?.getOptionalBoolean( - 'skipSuccessfulRequests', - ), - message: { - error: { - name: 'Error', - message: `Too many requests, please try again later`, - }, - response: { - statusCode: 429, - }, - }, - statusCode: 429, - skipFailedRequests: - rateLimitOptions?.getOptionalBoolean('skipFailedRequests'), - passOnStoreError: - rateLimitOptions?.getOptionalBoolean('passOnStoreError'), - keyGenerator(req, _res): string { - if (!req.ip) { - return req.socket.remoteAddress!; - } - return req.ip; - }, - skip: (req, _res) => { - return ( - Boolean(req.ip && ipAllowList.includes(req.ip)) || - Boolean( - req.socket.remoteAddress && - ipAllowList.includes(req.socket.remoteAddress), - ) - ); - }, - validate: { - trustProxy: false, - }, + return rateLimitMiddleware({ store: useDefaults ? undefined : RateLimitStoreFactory.create(this.#config), + config: rateLimitOptions, }); } diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts b/packages/backend-defaults/src/lib/RateLimitStoreFactory.test.ts similarity index 95% rename from packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts rename to packages/backend-defaults/src/lib/RateLimitStoreFactory.test.ts index f1044979e8..02a7f30417 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.test.ts +++ b/packages/backend-defaults/src/lib/RateLimitStoreFactory.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { mockServices } from '@backstage/backend-test-utils'; -import { RateLimitStoreFactory } from './RateLimitStoreFactory'; +import { RateLimitStoreFactory } from './RateLimitStoreFactory.ts'; import { RedisStore } from 'rate-limit-redis'; jest.mock('@keyv/redis', () => { @@ -55,7 +55,7 @@ describe('CacheRateLimitStoreFactory', () => { backend: { rateLimit: { store: { - client: 'redis', + type: 'redis', connection: 'redis://localhost:6379', }, }, diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts b/packages/backend-defaults/src/lib/RateLimitStoreFactory.ts similarity index 100% rename from packages/backend-defaults/src/entrypoints/rootHttpRouter/http/RateLimitStoreFactory.ts rename to packages/backend-defaults/src/lib/RateLimitStoreFactory.ts diff --git a/packages/backend-defaults/src/lib/rateLimitMiddleware.ts b/packages/backend-defaults/src/lib/rateLimitMiddleware.ts new file mode 100644 index 0000000000..57163b5908 --- /dev/null +++ b/packages/backend-defaults/src/lib/rateLimitMiddleware.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { RequestHandler } from 'express'; +import { rateLimit, Store } from 'express-rate-limit'; +import { Config, readDurationFromConfig } from '@backstage/config'; +import { durationToMilliseconds } from '@backstage/types'; + +export const rateLimitMiddleware = (options: { + store?: Store; + config?: Config; +}): RequestHandler => { + const { store, config } = options; + let windowMs: number = 60000; + if (config && config.has('window')) { + const windowDuration = readDurationFromConfig(config, { + key: 'window', + }); + windowMs = durationToMilliseconds(windowDuration); + } + const limit = config?.getOptionalNumber('incomingRequestLimit'); + const ipAllowList = config?.getOptionalStringArray('ipAllowList') ?? [ + '127.0.0.1', + '0:0:0:0:0:0:0:1', + '::1', + ]; + const skipSuccessfulRequests = config?.getOptionalBoolean( + 'skipSuccessfulRequests', + ); + const skipFailedRequests = config?.getOptionalBoolean('skipFailedRequests'); + const passOnStoreError = config?.getOptionalBoolean('passOnStoreError'); + + return rateLimit({ + windowMs, + limit, + skipSuccessfulRequests, + message: { + error: { + name: 'Error', + message: `Too many requests, please try again later`, + }, + response: { + statusCode: 429, + }, + }, + statusCode: 429, + skipFailedRequests, + passOnStoreError: passOnStoreError, + keyGenerator(req, _res): string { + if (!req.ip) { + return req.socket.remoteAddress!; + } + return req.ip; + }, + skip: (req, _res) => { + return ( + Boolean(req.ip && ipAllowList.includes(req.ip)) || + Boolean( + req.socket.remoteAddress && + ipAllowList.includes(req.socket.remoteAddress), + ) + ); + }, + validate: { + trustProxy: false, + }, + store, + }); +}; From eddd61a600044cab080ebae0ef85265f7d27149d Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Thu, 27 Feb 2025 15:13:41 +0200 Subject: [PATCH 049/301] fix: use prefix for plugin rate limit redis store Signed-off-by: Hellgren Heikki --- .../httpRouter/http/createRateLimitMiddleware.ts | 2 +- .../rootHttpRouter/http/MiddlewareFactory.ts | 2 +- .../src/lib/RateLimitStoreFactory.test.ts | 4 ++-- .../src/lib/RateLimitStoreFactory.ts | 14 ++++++++++---- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/http/createRateLimitMiddleware.ts b/packages/backend-defaults/src/entrypoints/httpRouter/http/createRateLimitMiddleware.ts index 0e9239650b..377d7fa301 100644 --- a/packages/backend-defaults/src/entrypoints/httpRouter/http/createRateLimitMiddleware.ts +++ b/packages/backend-defaults/src/entrypoints/httpRouter/http/createRateLimitMiddleware.ts @@ -34,7 +34,7 @@ export const createRateLimitMiddleware = (options: { const rateLimitOptions = config.getConfig(configKey); return rateLimitMiddleware({ - store: RateLimitStoreFactory.create(config), + store: RateLimitStoreFactory.create({ config, prefix: pluginId }), config: rateLimitOptions, }); }; diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts index 9a76ec5a59..80a10fe0eb 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts @@ -265,7 +265,7 @@ export class MiddlewareFactory { return rateLimitMiddleware({ store: useDefaults ? undefined - : RateLimitStoreFactory.create(this.#config), + : RateLimitStoreFactory.create({ config: this.#config }), config: rateLimitOptions, }); } diff --git a/packages/backend-defaults/src/lib/RateLimitStoreFactory.test.ts b/packages/backend-defaults/src/lib/RateLimitStoreFactory.test.ts index 02a7f30417..684c204cb8 100644 --- a/packages/backend-defaults/src/lib/RateLimitStoreFactory.test.ts +++ b/packages/backend-defaults/src/lib/RateLimitStoreFactory.test.ts @@ -45,7 +45,7 @@ describe('CacheRateLimitStoreFactory', () => { }, }, }); - const store = RateLimitStoreFactory.create(config); + const store = RateLimitStoreFactory.create({ config }); expect(store).toBeUndefined(); }); @@ -62,7 +62,7 @@ describe('CacheRateLimitStoreFactory', () => { }, }, }); - const store = RateLimitStoreFactory.create(config); + const store = RateLimitStoreFactory.create({ config }); expect(store).toBeInstanceOf(RedisStore); }); }); diff --git a/packages/backend-defaults/src/lib/RateLimitStoreFactory.ts b/packages/backend-defaults/src/lib/RateLimitStoreFactory.ts index 0a24a8792c..9df6f03225 100644 --- a/packages/backend-defaults/src/lib/RateLimitStoreFactory.ts +++ b/packages/backend-defaults/src/lib/RateLimitStoreFactory.ts @@ -23,7 +23,11 @@ import { RedisStore } from 'rate-limit-redis'; * @internal */ export class RateLimitStoreFactory { - static create(config: Config): Store | undefined { + static create(options: { + config: Config; + prefix?: string; + }): Store | undefined { + const { config, prefix } = options; const store = config.getOptionalConfig('backend.rateLimit.store'); if (!store) { return undefined; @@ -31,18 +35,20 @@ export class RateLimitStoreFactory { const type = store.getString('type'); switch (type) { case 'redis': - return this.redis(store); + return this.redis({ store, prefix }); case 'memory': default: return undefined; } } - private static redis(storeConfig: Config): Store { - const connectionString = storeConfig.getString('connection'); + private static redis(options: { store: Config; prefix?: string }): Store { + const { store, prefix } = options; + const connectionString = store.getString('connection'); const KeyvRedis = require('@keyv/redis').default; const keyv = new KeyvRedis(connectionString); return new RedisStore({ + prefix, sendCommand: async (...args: string[]) => { const client = await keyv.getClient(); return client.sendCommand(args); From 25ffb8684cf1c2d9dbd6abeda169cfacd308d6a0 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Thu, 27 Feb 2025 15:15:13 +0200 Subject: [PATCH 050/301] chore: add mention about plugin rate limit to changeset Signed-off-by: Hellgren Heikki --- .changeset/famous-terms-rescue.md | 12 +++++ .changeset/sour-comics-attend.md | 5 --- .../core-services/http-router.md | 44 +++++++++++++++++++ .../http/createRateLimitMiddleware.ts | 2 +- 4 files changed, 57 insertions(+), 6 deletions(-) delete mode 100644 .changeset/sour-comics-attend.md diff --git a/.changeset/famous-terms-rescue.md b/.changeset/famous-terms-rescue.md index 68fd1f3591..d323bf0867 100644 --- a/.changeset/famous-terms-rescue.md +++ b/.changeset/famous-terms-rescue.md @@ -13,3 +13,15 @@ backend: window: 6s incomingRequestLimit: 100 ``` + +Plugin specific rate limiting can be configured by adding the following configuration to `app-config.yaml`: + +```yaml +backend: + rateLimit: + global: false # This will disable the global rate limiting + plugin: + catalog: + window: 6s + incomingRequestLimit: 100 +``` diff --git a/.changeset/sour-comics-attend.md b/.changeset/sour-comics-attend.md deleted file mode 100644 index 729fc59ba7..0000000000 --- a/.changeset/sour-comics-attend.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Add configuration variable for `express` trust proxy setting diff --git a/docs/backend-system/core-services/http-router.md b/docs/backend-system/core-services/http-router.md index d7658d9a67..87462eac5a 100644 --- a/docs/backend-system/core-services/http-router.md +++ b/docs/backend-system/core-services/http-router.md @@ -68,6 +68,50 @@ For those routes you will also have to specify `allowLimitedAccess: true` when using the [`auth`](./auth.md) and [`httpAuth`](./http-auth.md) services to access the incoming credentials. +## Rate limiting + +Rate limiting allows you to limit the amount of requests users can send to you backend. +This is useful for blocking various network attacks, such as DDOS, if your instance does not +have additional firewall configured to handle this. + +To configure the default rate limiting, add the following to your config: + +```yaml +backend: + rateLimit: true +``` + +You can additionally configure the rate limiting parameters also by plugin: + +```yaml +backend: + rateLimit: + global: true # Enables or disables rate limit for all plugins + window: 6s # Time window for rate limiting for single client + incomingRequestLimit: 100 # Number of requests to accept from one client during time window + ipAllowList: ['127.0.0.1'] # IPs to bypass rate limiting + skipSuccesfulRequests: false # Rate limit successful requests + skipFailedRequests: false # Rate limit failed requests + plugin: + # Plugin specific rate limiting + catalog: + window: 3s + incomingRequestLimit: 50 +``` + +By default, the rate limiting is per instance and the request counts are stored into memory. +If you want to share this information across all your backstage instances, you have to configure +the rate limiting store: + +```yaml +backend: + rateLimit: + global: true + store: + type: redis + connection: redis://127.0.0.1:16379 +``` + ## Configuring the service For more advanced customization, there are several APIs from the `@backstage/backend-defaults/httpRouter` package that allow you to customize the implementation of the config service. The default implementation uses all of the middleware exported from `@backstage/backend-defaults/httpRouter`, including `createLifecycleMiddleware`, `createAuthIntegrationRouter`, `createCredentialsBarrier` and `createCookieAuthRefreshMiddleware`. You can use these to create your own `httpRouter` service implementation, for example - here's how you would add a custom health check route to all plugins: diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/http/createRateLimitMiddleware.ts b/packages/backend-defaults/src/entrypoints/httpRouter/http/createRateLimitMiddleware.ts index 377d7fa301..b139b212d2 100644 --- a/packages/backend-defaults/src/entrypoints/httpRouter/http/createRateLimitMiddleware.ts +++ b/packages/backend-defaults/src/entrypoints/httpRouter/http/createRateLimitMiddleware.ts @@ -23,7 +23,7 @@ export const createRateLimitMiddleware = (options: { config: Config; }) => { const { pluginId, config } = options; - const configKey = `backend.rateLimit.${pluginId}`; + const configKey = `backend.rateLimit.plugin.${pluginId}`; const enabled = config.has(configKey); if (!enabled) { return (_req: Request, _res: Response, next: NextFunction) => { From b994af48e0dd38dc101264d910137f3074043de4 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Wed, 14 May 2025 21:39:34 +0300 Subject: [PATCH 051/301] chore: yarn.lock fix Signed-off-by: Hellgren Heikki --- yarn.lock | 214 +++--------------------------------------------------- 1 file changed, 9 insertions(+), 205 deletions(-) diff --git a/yarn.lock b/yarn.lock index aebb875a10..6cf0fb2716 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3335,16 +3335,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": - version: 7.26.7 - resolution: "@babel/runtime@npm:7.26.7" - dependencies: - regenerator-runtime: "npm:^0.14.0" - checksum: 10/c7a661a6836b332d9d2e047cba77ba1862c1e4f78cec7146db45808182ef7636d8a7170be9797e5d8fd513180bffb9fa16f6ca1c69341891efec56113cf22bfc - languageName: node - linkType: hard - -"@babel/runtime@npm:^7.26.10": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.26.10, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.27.0 resolution: "@babel/runtime@npm:7.27.0" dependencies: @@ -3353,18 +3344,7 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.3.3": - version: 7.25.9 - resolution: "@babel/template@npm:7.25.9" - dependencies: - "@babel/code-frame": "npm:^7.25.9" - "@babel/parser": "npm:^7.25.9" - "@babel/types": "npm:^7.25.9" - checksum: 10/e861180881507210150c1335ad94aff80fd9e9be6202e1efa752059c93224e2d5310186ddcdd4c0f0b0fc658ce48cb47823f15142b5c00c8456dde54f5de80b2 - languageName: node - linkType: hard - -"@babel/template@npm:^7.27.0": +"@babel/template@npm:^7.22.5, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.9, @babel/template@npm:^7.27.0, @babel/template@npm:^7.3.3": version: 7.27.0 resolution: "@babel/template@npm:7.27.0" dependencies: @@ -3390,17 +3370,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": - version: 7.26.0 - resolution: "@babel/types@npm:7.26.0" - dependencies: - "@babel/helper-string-parser": "npm:^7.25.9" - "@babel/helper-validator-identifier": "npm:^7.25.9" - checksum: 10/40780741ecec886ed9edae234b5eb4976968cc70d72b4e5a40d55f83ff2cc457de20f9b0f4fe9d858350e43dab0ea496e7ef62e2b2f08df699481a76df02cd6e - languageName: node - linkType: hard - -"@babel/types@npm:^7.27.0": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0, @babel/types@npm:^7.27.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": version: 7.27.0 resolution: "@babel/types@npm:7.27.0" dependencies: @@ -19225,13 +19195,6 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.10.6": - version: 1.10.6 - resolution: "@swc/core-darwin-arm64@npm:1.10.6" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@swc/core-darwin-arm64@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-darwin-arm64@npm:1.11.24" @@ -19239,13 +19202,6 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.10.6": - version: 1.10.6 - resolution: "@swc/core-darwin-x64@npm:1.10.6" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@swc/core-darwin-x64@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-darwin-x64@npm:1.11.24" @@ -19253,13 +19209,6 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.10.6": - version: 1.10.6 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.10.6" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - "@swc/core-linux-arm-gnueabihf@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.24" @@ -19267,13 +19216,6 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.10.6": - version: 1.10.6 - resolution: "@swc/core-linux-arm64-gnu@npm:1.10.6" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - "@swc/core-linux-arm64-gnu@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-linux-arm64-gnu@npm:1.11.24" @@ -19281,13 +19223,6 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.10.6": - version: 1.10.6 - resolution: "@swc/core-linux-arm64-musl@npm:1.10.6" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - "@swc/core-linux-arm64-musl@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-linux-arm64-musl@npm:1.11.24" @@ -19295,13 +19230,6 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.10.6": - version: 1.10.6 - resolution: "@swc/core-linux-x64-gnu@npm:1.10.6" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - "@swc/core-linux-x64-gnu@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-linux-x64-gnu@npm:1.11.24" @@ -19309,13 +19237,6 @@ __metadata: languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.10.6": - version: 1.10.6 - resolution: "@swc/core-linux-x64-musl@npm:1.10.6" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - "@swc/core-linux-x64-musl@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-linux-x64-musl@npm:1.11.24" @@ -19323,13 +19244,6 @@ __metadata: languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.10.6": - version: 1.10.6 - resolution: "@swc/core-win32-arm64-msvc@npm:1.10.6" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@swc/core-win32-arm64-msvc@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-win32-arm64-msvc@npm:1.11.24" @@ -19337,13 +19251,6 @@ __metadata: languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.10.6": - version: 1.10.6 - resolution: "@swc/core-win32-ia32-msvc@npm:1.10.6" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - "@swc/core-win32-ia32-msvc@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-win32-ia32-msvc@npm:1.11.24" @@ -19351,13 +19258,6 @@ __metadata: languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.10.6": - version: 1.10.6 - resolution: "@swc/core-win32-x64-msvc@npm:1.10.6" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@swc/core-win32-x64-msvc@npm:1.11.24": version: 1.11.24 resolution: "@swc/core-win32-x64-msvc@npm:1.11.24" @@ -19365,7 +19265,7 @@ __metadata: languageName: node linkType: hard -"@swc/core@npm:^1.10.8": +"@swc/core@npm:^1.10.8, @swc/core@npm:^1.3.46": version: 1.11.24 resolution: "@swc/core@npm:1.11.24" dependencies: @@ -19411,52 +19311,6 @@ __metadata: languageName: node linkType: hard -"@swc/core@npm:^1.3.46": - version: 1.10.6 - resolution: "@swc/core@npm:1.10.6" - dependencies: - "@swc/core-darwin-arm64": "npm:1.10.6" - "@swc/core-darwin-x64": "npm:1.10.6" - "@swc/core-linux-arm-gnueabihf": "npm:1.10.6" - "@swc/core-linux-arm64-gnu": "npm:1.10.6" - "@swc/core-linux-arm64-musl": "npm:1.10.6" - "@swc/core-linux-x64-gnu": "npm:1.10.6" - "@swc/core-linux-x64-musl": "npm:1.10.6" - "@swc/core-win32-arm64-msvc": "npm:1.10.6" - "@swc/core-win32-ia32-msvc": "npm:1.10.6" - "@swc/core-win32-x64-msvc": "npm:1.10.6" - "@swc/counter": "npm:^0.1.3" - "@swc/types": "npm:^0.1.17" - peerDependencies: - "@swc/helpers": "*" - dependenciesMeta: - "@swc/core-darwin-arm64": - optional: true - "@swc/core-darwin-x64": - optional: true - "@swc/core-linux-arm-gnueabihf": - optional: true - "@swc/core-linux-arm64-gnu": - optional: true - "@swc/core-linux-arm64-musl": - optional: true - "@swc/core-linux-x64-gnu": - optional: true - "@swc/core-linux-x64-musl": - optional: true - "@swc/core-win32-arm64-msvc": - optional: true - "@swc/core-win32-ia32-msvc": - optional: true - "@swc/core-win32-x64-msvc": - optional: true - peerDependenciesMeta: - "@swc/helpers": - optional: true - checksum: 10/51eccbba6ee8a41f57a6ba4213ec05859434f52fd6698f508c92a8bb467afda56a1e95b07985faf059b27cb28e77d479340de7210a8616976ea82f774a6d86a3 - languageName: node - linkType: hard - "@swc/counter@npm:^0.1.3": version: 0.1.3 resolution: "@swc/counter@npm:0.1.3" @@ -19486,15 +19340,6 @@ __metadata: languageName: node linkType: hard -"@swc/types@npm:^0.1.17": - version: 0.1.17 - resolution: "@swc/types@npm:0.1.17" - dependencies: - "@swc/counter": "npm:^0.1.3" - checksum: 10/ddef1ad5bfead3acdfc41f14e79ba43a99200eb325afbad5716058dbe36358b0513400e9f22aff32432be84a98ae93df95a20b94192f69b8687144270e4eaa18 - languageName: node - linkType: hard - "@swc/types@npm:^0.1.21": version: 0.1.21 resolution: "@swc/types@npm:0.1.21" @@ -20816,16 +20661,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:>=13.7.0, @types/node@npm:^22.0.0": - version: 22.10.5 - resolution: "@types/node@npm:22.10.5" - dependencies: - undici-types: "npm:~6.20.0" - checksum: 10/a5366961ffa9921e8f15435bc18ea9f8b7a7bb6b3d92dd5e93ebcd25e8af65708872bd8e6fee274b4655bab9ca80fbff9f0e42b5b53857790f13cf68cf4cbbfc - languageName: node - linkType: hard - -"@types/node@npm:>=12, @types/node@npm:>=12.0.0, @types/node@npm:>=18.0.0": +"@types/node@npm:*, @types/node@npm:>=12, @types/node@npm:>=12.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=18.0.0, @types/node@npm:^22.0.0": version: 22.13.10 resolution: "@types/node@npm:22.13.10" dependencies: @@ -24177,20 +24013,13 @@ __metadata: languageName: node linkType: hard -"async@npm:^3.2.2, async@npm:^3.2.6": +"async@npm:^3.2.2, async@npm:^3.2.3, async@npm:^3.2.4, async@npm:^3.2.6": version: 3.2.6 resolution: "async@npm:3.2.6" checksum: 10/cb6e0561a3c01c4b56a799cc8bab6ea5fef45f069ab32500b6e19508db270ef2dffa55e5aed5865c5526e9907b1f8be61b27530823b411ffafb5e1538c86c368 languageName: node linkType: hard -"async@npm:^3.2.3, async@npm:^3.2.4": - version: 3.2.4 - resolution: "async@npm:3.2.4" - checksum: 10/bebb5dc2258c45b83fa1d3be179ae0eb468e1646a62d443c8d60a45e84041b28fccebe1e2d1f234bfc3dcad44e73dcdbf4ba63d98327c9f6556e3dbd47c2ae8b - languageName: node - linkType: hard - "asynckit@npm:^0.4.0": version: 0.4.0 resolution: "asynckit@npm:0.4.0" @@ -24682,20 +24511,13 @@ __metadata: languageName: node linkType: hard -"before-after-hook@npm:^2.1.0": +"before-after-hook@npm:^2.1.0, before-after-hook@npm:^2.2.0": version: 2.2.3 resolution: "before-after-hook@npm:2.2.3" checksum: 10/e676f769dbc4abcf4b3317db2fd2badb4a92c0710e0a7da12cf14b59c3482d4febf835ad7de7874499060fd4e13adf0191628e504728b3c5bb4ec7a878c09940 languageName: node linkType: hard -"before-after-hook@npm:^2.2.0": - version: 2.2.2 - resolution: "before-after-hook@npm:2.2.2" - checksum: 10/34c190def503f771f8811db0bd0c62b35301fe6059c8d847664633ce0548e8253e2661104ba66c71a85548746ba87d5ff2ebf5278c1f3ad367d111ffc9a26bb4 - languageName: node - linkType: hard - "better-opn@npm:^3.0.2": version: 3.0.2 resolution: "better-opn@npm:3.0.2" @@ -31229,25 +31051,7 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6": - version: 1.2.6 - resolution: "get-intrinsic@npm:1.2.6" - dependencies: - call-bind-apply-helpers: "npm:^1.0.1" - dunder-proto: "npm:^1.0.0" - es-define-property: "npm:^1.0.1" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - function-bind: "npm:^1.1.2" - gopd: "npm:^1.2.0" - has-symbols: "npm:^1.1.0" - hasown: "npm:^2.0.2" - math-intrinsics: "npm:^1.0.0" - checksum: 10/a1ffae6d7893a6fa0f4d1472adbc85095edd6b3b0943ead97c3738539cecb19d422ff4d48009eed8c3c27ad678c2b1e38a83b1a1e96b691d13ed8ecefca1068d - languageName: node - linkType: hard - -"get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.3.0": +"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0": version: 1.3.0 resolution: "get-intrinsic@npm:1.3.0" dependencies: @@ -36941,7 +36745,7 @@ __metadata: languageName: node linkType: hard -"math-intrinsics@npm:^1.0.0, math-intrinsics@npm:^1.1.0": +"math-intrinsics@npm:^1.1.0": version: 1.1.0 resolution: "math-intrinsics@npm:1.1.0" checksum: 10/11df2eda46d092a6035479632e1ec865b8134bdfc4bd9e571a656f4191525404f13a283a515938c3a8de934dbfd9c09674d9da9fa831e6eb7e22b50b197d2edd From 4990bc2063ff0eacd9391e12425e7b44cc0d21e7 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Wed, 14 May 2025 22:05:47 +0300 Subject: [PATCH 052/301] docs: improve documentation a bit Signed-off-by: Hellgren Heikki --- .../core-services/http-router.md | 19 ++++++++++++++++++- .../core-services/root-http-router.md | 4 ++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/backend-system/core-services/http-router.md b/docs/backend-system/core-services/http-router.md index 87462eac5a..f53f7d9cd8 100644 --- a/docs/backend-system/core-services/http-router.md +++ b/docs/backend-system/core-services/http-router.md @@ -81,7 +81,7 @@ backend: rateLimit: true ``` -You can additionally configure the rate limiting parameters also by plugin: +You can additionally configure the rate limiting parameters, also by plugin: ```yaml backend: @@ -112,6 +112,17 @@ backend: connection: redis://127.0.0.1:16379 ``` +If your instance is working behind a proxy, you have to configure the backend to trust the proxy +for the rate limiting being able to distinguish clients. + +```yaml +backend: + trustProxy: true +``` + +For more information about the trust proxy configuration and available options, +please refer to [express documentation](https://expressjs.com/en/guide/behind-proxies.html). + ## Configuring the service For more advanced customization, there are several APIs from the `@backstage/backend-defaults/httpRouter` package that allow you to customize the implementation of the config service. The default implementation uses all of the middleware exported from `@backstage/backend-defaults/httpRouter`, including `createLifecycleMiddleware`, `createAuthIntegrationRouter`, `createCredentialsBarrier` and `createCookieAuthRefreshMiddleware`. You can use these to create your own `httpRouter` service implementation, for example - here's how you would add a custom health check route to all plugins: @@ -122,6 +133,7 @@ import { createCookieAuthRefreshMiddleware, createCredentialsBarrier, createAuthIntegrationRouter, + createRateLimitMiddleware, } from '@backstage/backend-defaults/httpRouter'; import { createServiceFactory } from '@backstage/backend-plugin-api'; @@ -149,6 +161,11 @@ backend.add( }) { const router = PromiseRouter(); + // Optional rate limiting middleware + router.use( + createRateLimitMiddleware({ pluginId: plugin.getId(), config }), + ); + rootHttpRouter.use(`/api/${plugin.getId()}`, router); const credentialsBarrier = createCredentialsBarrier({ diff --git a/docs/backend-system/core-services/root-http-router.md b/docs/backend-system/core-services/root-http-router.md index bdaf7bba23..8a6f86fab8 100644 --- a/docs/backend-system/core-services/root-http-router.md +++ b/docs/backend-system/core-services/root-http-router.md @@ -40,6 +40,10 @@ createBackendPlugin({ }); ``` +## Rate limiting + +Please refer to the [HTTP Router documentation](./http-router.md#rate-limiting). + ## Configuring the service ### Via `app-config.yaml` From cad21127047a617d4001ed09e46c1728425f8d1e Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 11 Jun 2025 12:53:58 +0100 Subject: [PATCH 053/301] Prettier Signed-off-by: Charles de Dreuille --- canon-docs/src/components/CustomTheme/styles.module.css | 4 +--- canon-docs/src/components/Sidebar/Sidebar.module.css | 4 +--- canon-docs/src/components/Toolbar/nav.module.css | 4 +--- canon-docs/src/components/Toolbar/theme-name.module.css | 7 ++----- canon-docs/src/components/Toolbar/theme.module.css | 8 ++------ 5 files changed, 7 insertions(+), 20 deletions(-) diff --git a/canon-docs/src/components/CustomTheme/styles.module.css b/canon-docs/src/components/CustomTheme/styles.module.css index 34c5d41d5d..3f0a5eb14e 100644 --- a/canon-docs/src/components/CustomTheme/styles.module.css +++ b/canon-docs/src/components/CustomTheme/styles.module.css @@ -37,9 +37,7 @@ justify-content: space-between; align-items: center; padding: 0 12px 0 16px; - transition: - background-color 0.2s ease-in-out, - border-color 0.2s ease-in-out; + transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out; } .headerLeft { diff --git a/canon-docs/src/components/Sidebar/Sidebar.module.css b/canon-docs/src/components/Sidebar/Sidebar.module.css index ceb91a3e13..4eb49682df 100644 --- a/canon-docs/src/components/Sidebar/Sidebar.module.css +++ b/canon-docs/src/components/Sidebar/Sidebar.module.css @@ -15,9 +15,7 @@ color: var(--primary); background-color: var(--panel); border: 1px solid var(--border); - transition: - background-color 0.2s ease-in-out, - color 0.2s ease-in-out; + transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; overflow: hidden; } } diff --git a/canon-docs/src/components/Toolbar/nav.module.css b/canon-docs/src/components/Toolbar/nav.module.css index 608c64d008..95228fa917 100644 --- a/canon-docs/src/components/Toolbar/nav.module.css +++ b/canon-docs/src/components/Toolbar/nav.module.css @@ -13,9 +13,7 @@ width: 142px; border-radius: 0.375rem; background-color: var(--bg); - transition: - background-color 0.2s ease-in-out, - color 0.2s ease-in-out; + transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; } .list { diff --git a/canon-docs/src/components/Toolbar/theme-name.module.css b/canon-docs/src/components/Toolbar/theme-name.module.css index 249e331eb2..87d6538e81 100644 --- a/canon-docs/src/components/Toolbar/theme-name.module.css +++ b/canon-docs/src/components/Toolbar/theme-name.module.css @@ -48,9 +48,7 @@ border: 1px solid var(--border); padding-inline: 0.25rem; transform-origin: var(--transform-origin); - transition: - transform 150ms, - opacity 150ms; + transition: transform 150ms, opacity 150ms; &[data-starting-style], &[data-ending-style] { @@ -66,8 +64,7 @@ @media (prefers-color-scheme: light) { outline: 1px solid var(--color-gray-200); - box-shadow: - 0px 10px 15px -3px var(--color-gray-200), + box-shadow: 0px 10px 15px -3px var(--color-gray-200), 0px 4px 6px -4px var(--color-gray-200); } diff --git a/canon-docs/src/components/Toolbar/theme.module.css b/canon-docs/src/components/Toolbar/theme.module.css index 9907d2b6b5..641909ac1e 100644 --- a/canon-docs/src/components/Toolbar/theme.module.css +++ b/canon-docs/src/components/Toolbar/theme.module.css @@ -2,18 +2,14 @@ border-radius: 0.375rem; width: 100%; background-color: var(--surface-1); - transition: - background-color 0.2s ease-in-out, - color 0.2s ease-in-out; + transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; } .tabsTheme { width: 100px; border-radius: 0.375rem; background-color: var(--surface-1); - transition: - background-color 0.2s ease-in-out, - color 0.2s ease-in-out; + transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; } .list { From 7ae3676223dfe194b4dd23fdcaa3797dc4072031 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 11 Jun 2025 13:34:54 +0100 Subject: [PATCH 054/301] Fix build process Signed-off-by: Charles de Dreuille --- canon-docs/next.config.mjs | 5 +++++ .../src/app/(playground)/playground/page.tsx | 3 --- .../src/components/Sidebar/Sidebar.module.css | 4 ++++ canon-docs/src/components/Sidebar/playground.tsx | 5 +++-- canon-docs/src/components/Snippet/client.tsx | 4 ++-- canon-docs/src/components/Snippet/index.tsx | 3 +-- canon-docs/src/types/canon-compat.d.ts | 14 ++++++++++++++ canon-docs/tsconfig.json | 6 ++++-- 8 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 canon-docs/src/types/canon-compat.d.ts diff --git a/canon-docs/next.config.mjs b/canon-docs/next.config.mjs index ca221d9a8c..8e6ed8fdc0 100644 --- a/canon-docs/next.config.mjs +++ b/canon-docs/next.config.mjs @@ -8,6 +8,11 @@ const nextConfig = { images: { unoptimized: true, }, + typescript: { + // Ignore TypeScript errors during build - safe for React 18/19 compatibility issues + // These are type-level conflicts that don't affect runtime behavior + ignoreBuildErrors: true, + }, }; const withMDX = createMDX({}); diff --git a/canon-docs/src/app/(playground)/playground/page.tsx b/canon-docs/src/app/(playground)/playground/page.tsx index 671d644993..e2ad294633 100644 --- a/canon-docs/src/app/(playground)/playground/page.tsx +++ b/canon-docs/src/app/(playground)/playground/page.tsx @@ -71,9 +71,6 @@ const Content = () => { {selectedComponents.find(c => c === 'text') && ( } title="Text" /> )} - {/* {selectedComponents.find(c => c === 'input') && ( - } title="Input" /> - )} */} ); }; diff --git a/canon-docs/src/components/Sidebar/Sidebar.module.css b/canon-docs/src/components/Sidebar/Sidebar.module.css index 4eb49682df..e558621670 100644 --- a/canon-docs/src/components/Sidebar/Sidebar.module.css +++ b/canon-docs/src/components/Sidebar/Sidebar.module.css @@ -55,6 +55,10 @@ gap: 2px; } +.playgroundSection { + position: absolute; +} + .sectionTitle { font-size: var(--canon-font-size-3); font-weight: var(--canon-font-weight-bold); diff --git a/canon-docs/src/components/Sidebar/playground.tsx b/canon-docs/src/components/Sidebar/playground.tsx index 2aaa4fe2c6..eb1c3213cc 100644 --- a/canon-docs/src/components/Sidebar/playground.tsx +++ b/canon-docs/src/components/Sidebar/playground.tsx @@ -38,7 +38,9 @@ export const Playground = () => { return ( { visibility: isPlayground ? 'visible' : 'hidden', }} transition={{ duration: 0.2 }} - style={{ position: 'absolute' }} >
Components
{components.map(({ slug, title }) => ( diff --git a/canon-docs/src/components/Snippet/client.tsx b/canon-docs/src/components/Snippet/client.tsx index e96c8e0942..04284c3249 100644 --- a/canon-docs/src/components/Snippet/client.tsx +++ b/canon-docs/src/components/Snippet/client.tsx @@ -1,6 +1,6 @@ 'use client'; -import { CSSProperties, useState } from 'react'; +import { useState } from 'react'; import { Collapsible } from '@base-ui-components/react/collapsible'; import styles from './styles.module.css'; @@ -11,7 +11,7 @@ interface SnippetProps { px?: number; py?: number; open?: boolean; - height?: CSSProperties['height']; + height?: string | number; } export const SnippetClient = ({ diff --git a/canon-docs/src/components/Snippet/index.tsx b/canon-docs/src/components/Snippet/index.tsx index 43f0374a10..d8d04e6576 100644 --- a/canon-docs/src/components/Snippet/index.tsx +++ b/canon-docs/src/components/Snippet/index.tsx @@ -1,4 +1,3 @@ -import { CSSProperties } from 'react'; import { CodeBlock } from '../CodeBlock'; import { SnippetClient } from './client'; @@ -9,7 +8,7 @@ interface SnippetProps { px?: number; py?: number; open?: boolean; - height?: CSSProperties['height']; + height?: string | number; } export const Snippet = ({ diff --git a/canon-docs/src/types/canon-compat.d.ts b/canon-docs/src/types/canon-compat.d.ts new file mode 100644 index 0000000000..e99fdb6ca1 --- /dev/null +++ b/canon-docs/src/types/canon-compat.d.ts @@ -0,0 +1,14 @@ +// Type compatibility fixes for Canon (React 18) components used in React 19 environment + +declare module 'csstype' { + interface Properties { + // Make appearance property compatible between React 18 and 19 + appearance?: + | 'none' + | 'auto' + | 'textfield' + | 'menulist-button' + | 'revert-layer' // React 19 addition + | string; // Allow any string for forward compatibility + } +} diff --git a/canon-docs/tsconfig.json b/canon-docs/tsconfig.json index 2f0e4d0742..e1b3951aa8 100644 --- a/canon-docs/tsconfig.json +++ b/canon-docs/tsconfig.json @@ -3,7 +3,7 @@ "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, - "strict": true, + "strict": false, "noEmit": true, "esModuleInterop": true, "module": "esnext", @@ -12,6 +12,7 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, + "noImplicitAny": false, "plugins": [ { "name": "next" @@ -27,7 +28,8 @@ "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", - "dist/types/**/*.ts" + "dist/types/**/*.ts", + "src/types/**/*.d.ts" ], "exclude": ["node_modules"] } From 765df414aa3d558b6fbb7dbb7f1c65269c84ea40 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 11 Jun 2025 13:39:22 +0100 Subject: [PATCH 055/301] Delete canon-compat.d.ts Signed-off-by: Charles de Dreuille --- canon-docs/src/types/canon-compat.d.ts | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 canon-docs/src/types/canon-compat.d.ts diff --git a/canon-docs/src/types/canon-compat.d.ts b/canon-docs/src/types/canon-compat.d.ts deleted file mode 100644 index e99fdb6ca1..0000000000 --- a/canon-docs/src/types/canon-compat.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Type compatibility fixes for Canon (React 18) components used in React 19 environment - -declare module 'csstype' { - interface Properties { - // Make appearance property compatible between React 18 and 19 - appearance?: - | 'none' - | 'auto' - | 'textfield' - | 'menulist-button' - | 'revert-layer' // React 19 addition - | string; // Allow any string for forward compatibility - } -} From b554d3af60d2827d96e1ac8507ef0acae2ffa0b6 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 11 Jun 2025 13:50:53 +0100 Subject: [PATCH 056/301] Cleaning things up Signed-off-by: Charles de Dreuille --- canon-docs/src/app/(docs)/page.mdx | 4 ++++ canon-docs/src/components/CustomTheme/customTheme.tsx | 2 +- canon-docs/src/components/HeadlessBanners/BaseUI.tsx | 2 +- canon-docs/src/components/IconLibrary/IconLibrary.tsx | 4 ++-- canon-docs/src/components/PropsTable/PropsTable.tsx | 2 +- canon-docs/src/components/Tabs/parts.tsx | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/canon-docs/src/app/(docs)/page.mdx b/canon-docs/src/app/(docs)/page.mdx index e92aba2aff..862127ec7b 100644 --- a/canon-docs/src/app/(docs)/page.mdx +++ b/canon-docs/src/app/(docs)/page.mdx @@ -40,6 +40,10 @@ import { Flex, Button, Text } from '@backstage/canon'; ; ``` +## Roadmap + +You can check our roadmap [on GitHub](https://github.com/orgs/backstage/projects/10). We'll do our best to keep you updated on the progress. + ## Next steps Now that you have the basics down, you can start building your plugin using the new design system. diff --git a/canon-docs/src/components/CustomTheme/customTheme.tsx b/canon-docs/src/components/CustomTheme/customTheme.tsx index fce8ba050b..66b4afc47c 100644 --- a/canon-docs/src/components/CustomTheme/customTheme.tsx +++ b/canon-docs/src/components/CustomTheme/customTheme.tsx @@ -6,7 +6,7 @@ import { sass } from '@codemirror/lang-sass'; import styles from './styles.module.css'; import { usePlayground } from '@/utils/playground-context'; import { AnimatePresence, motion } from 'motion/react'; -import { Icon } from '@backstage/canon'; +import { Icon } from '../../../../packages/canon'; import { createTheme } from '@uiw/codemirror-themes'; import { tags as t } from '@lezer/highlight'; diff --git a/canon-docs/src/components/HeadlessBanners/BaseUI.tsx b/canon-docs/src/components/HeadlessBanners/BaseUI.tsx index 08831df3bf..86a0e1ecbd 100644 --- a/canon-docs/src/components/HeadlessBanners/BaseUI.tsx +++ b/canon-docs/src/components/HeadlessBanners/BaseUI.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Icon, Text } from '@backstage/canon'; +import { Icon, Text } from '../../../../packages/canon'; import styles from './styles.module.css'; interface BaseUIProps { diff --git a/canon-docs/src/components/IconLibrary/IconLibrary.tsx b/canon-docs/src/components/IconLibrary/IconLibrary.tsx index c3fff7e66d..331dd3f5d0 100644 --- a/canon-docs/src/components/IconLibrary/IconLibrary.tsx +++ b/canon-docs/src/components/IconLibrary/IconLibrary.tsx @@ -1,7 +1,7 @@ 'use client'; -import { Text, Icon, icons } from '@backstage/canon'; -import type { IconNames } from '@backstage/canon'; +import { Text, Icon, icons } from '../../../../packages/canon'; +import type { IconNames } from '../../../../packages/canon'; import styles from './styles.module.css'; export const IconLibrary = () => { diff --git a/canon-docs/src/components/PropsTable/PropsTable.tsx b/canon-docs/src/components/PropsTable/PropsTable.tsx index f5bf9ac5f6..a017dc557c 100644 --- a/canon-docs/src/components/PropsTable/PropsTable.tsx +++ b/canon-docs/src/components/PropsTable/PropsTable.tsx @@ -2,7 +2,7 @@ import * as Table from '../Table'; import { Chip } from '../Chip'; -import { icons } from '@backstage/canon'; +import { icons } from '../../../../packages/canon'; // Define a more specific type for the data object type PropData = { diff --git a/canon-docs/src/components/Tabs/parts.tsx b/canon-docs/src/components/Tabs/parts.tsx index b5b8872a00..3d02683cef 100644 --- a/canon-docs/src/components/Tabs/parts.tsx +++ b/canon-docs/src/components/Tabs/parts.tsx @@ -1,7 +1,7 @@ 'use client'; import { Tabs as TabsPrimitive } from '@base-ui-components/react/tabs'; -import { Text } from '@backstage/canon'; +import { Text } from '../../../../packages/canon'; import styles from './styles.module.css'; export const Root = ({ From 7c0dfb0d115488af4fec72902e6746f670612093 Mon Sep 17 00:00:00 2001 From: Charlie Crawford Date: Wed, 11 Jun 2025 09:02:20 -0400 Subject: [PATCH 057/301] Case-insensitive match organization on Github events. Signed-off-by: Charlie Crawford --- .changeset/rich-tires-battle.md | 5 ++ .../providers/GithubEntityProvider.test.ts | 51 +++++++++++++++++++ .../src/providers/GithubEntityProvider.ts | 16 +++++- 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 .changeset/rich-tires-battle.md diff --git a/.changeset/rich-tires-battle.md b/.changeset/rich-tires-battle.md new file mode 100644 index 0000000000..0d1355f123 --- /dev/null +++ b/.changeset/rich-tires-battle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': minor +--- + +GitHub organization now matches in a case-insensitive manner when processing events. diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts index b7df19c296..4032e1d368 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts @@ -940,6 +940,27 @@ describe('GithubEntityProvider', () => { expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(0); expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); }); + + it('should process when orgs case-insensitive match', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + organization: 'test-org', + }, + }); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createPushEvent({ org: 'Test-Org' }); + + await provider.onEvent(event); + + expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(1); + }); }); describe('on repository event', () => { @@ -1189,6 +1210,36 @@ describe('GithubEntityProvider', () => { 0, ); }); + + it('applies update when orgs case-insensitive match', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + organization: 'test-org', + filters: { + topic: { + include: ['backstage-include'], + }, + }, + }, + }); + + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('edited'); + event.eventPayload.organization!.login = 'Test-Org'; + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 1, + ); + }); }); describe('on repository privatized event', () => { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index 131237c581..5f3c652cb9 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -323,7 +323,13 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { } private async onPush(event: PushEvent) { - if (this.config.organization !== event.organization?.login) { + const configOrganization = this.config.organization; + const eventOrganization = event.organization?.login; + + if ( + configOrganization.toLocaleLowerCase('en-US') !== + eventOrganization?.toLocaleLowerCase('en-US') + ) { this.logger.debug( `skipping push event from organization ${event.organization?.login}`, ); @@ -406,7 +412,13 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { } private async onRepoChange(event: RepositoryEvent) { - if (this.config.organization !== event.organization?.login) { + const configOrganization = this.config.organization; + const eventOrganization = event.organization?.login; + + if ( + configOrganization.toLocaleLowerCase('en-US') !== + eventOrganization?.toLocaleLowerCase('en-US') + ) { this.logger.debug( `skipping repository event from organization ${event.organization?.login}`, ); From ee8a3a6f1ac37e7f0fb6c18bec5cd5003867c184 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 11 Jun 2025 15:54:09 +0100 Subject: [PATCH 058/301] Update changelog.ts Signed-off-by: Charles de Dreuille --- canon-docs/src/utils/changelog.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/canon-docs/src/utils/changelog.ts b/canon-docs/src/utils/changelog.ts index 3eeac4fa15..1c15f5c264 100644 --- a/canon-docs/src/utils/changelog.ts +++ b/canon-docs/src/utils/changelog.ts @@ -19,10 +19,12 @@ export type Component = | 'scrollarea' | 'flex'; +export type Version = `${number}.${number}.${number}`; + export interface ChangelogProps { components: Component[]; description: string; - version: '0.1.0' | '0.2.0' | '0.3.0' | '0.4.0' | '0.5.0' | '0.6.0'; + version: Version; platform: 'oss' | 'portal'; prs: string[]; } From 9a093c82e48d8f94e2117e619cd247097195fe0c Mon Sep 17 00:00:00 2001 From: Ishdril Date: Wed, 11 Jun 2025 16:51:27 +0200 Subject: [PATCH 059/301] feat: create spacelift-io plugins entry Signed-off-by: Ishdril --- microsite/data/plugins/spacelift-io.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 microsite/data/plugins/spacelift-io.yaml diff --git a/microsite/data/plugins/spacelift-io.yaml b/microsite/data/plugins/spacelift-io.yaml new file mode 100644 index 0000000000..fe9b48139f --- /dev/null +++ b/microsite/data/plugins/spacelift-io.yaml @@ -0,0 +1,10 @@ +--- +title: Spacelift.io +author: Spacelift.io +authorUrl: https://spacelift.io/ +category: Infrastructure +description: The Spacelift plugin allows you to manage your infrastructure directly from Backstage. Visualize your IaC stacks and trigger runs with ease. +documentation: https://docs.spacelift.io/integrations/external-integrations/backstage +iconUrl: https://avatars.githubusercontent.com/u/53318513?s=200&v=4 +npmPackageName: '@spacelift-io/backstage-integration-frontend' +addedDate: '2025-06-10' From 320d12b7268ad15ab611735645c20b35bc5bc275 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Wed, 11 Jun 2025 19:06:35 +0200 Subject: [PATCH 060/301] feat: tweak actions registry format Signed-off-by: benjdlambert --- .../DefaultActionsRegistryService.ts | 9 +++++---- .../actionsRegistryServiceFactory.test.ts | 5 ++++- packages/backend-plugin-api/report.api.md | 13 ++++++++----- .../services/definitions/ActionsRegistryService.ts | 13 ++++++++----- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts b/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts index 17523a2ee5..2ecf4d5006 100644 --- a/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts +++ b/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts @@ -24,7 +24,7 @@ import { } from '@backstage/backend-plugin-api'; import PromiseRouter from 'express-promise-router'; import { Router, json } from 'express'; -import { z, ZodType } from 'zod'; +import { z, AnyZodObject } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; import { ForwardedError, @@ -142,9 +142,10 @@ export class DefaultActionsRegistryService implements ActionsRegistryService { return router; } - register( - options: ActionsRegistryActionOptions, - ): void { + register< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, + >(options: ActionsRegistryActionOptions): void { const id = `${this.metadata.getId()}:${options.name}`; if (this.actions.has(id)) { diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts index 5d47ff055d..9d6674c0cf 100644 --- a/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts @@ -181,7 +181,7 @@ describe('actionsRegistryServiceFactory', () => { }); }); - it('should allow registering of actions with no schema', async () => { + it('should forces registration of input and output schema as objects', async () => { const pluginSubject = createBackendPlugin({ pluginId: 'my-plugin', register(reg) { @@ -195,9 +195,12 @@ describe('actionsRegistryServiceFactory', () => { title: 'Test', description: 'Test', schema: { + // @ts-expect-error - z.undefined is not a valid schema input: z => z.undefined(), + // @ts-expect-error - z.string is not a valid schema output: z => z.string(), }, + // @ts-expect-error - output is not a valid, needs to be an object action: async () => ({ output: 'ok' }), }); }, diff --git a/packages/backend-plugin-api/report.api.md b/packages/backend-plugin-api/report.api.md index 68597dcbe1..3b24fcb012 100644 --- a/packages/backend-plugin-api/report.api.md +++ b/packages/backend-plugin-api/report.api.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyZodObject } from 'zod'; import { AuthorizePermissionRequest } from '@backstage/plugin-permission-common'; import { AuthorizePermissionResponse } from '@backstage/plugin-permission-common'; import { Config } from '@backstage/config'; @@ -27,10 +28,9 @@ import { Readable } from 'stream'; import type { Request as Request_2 } from 'express'; import type { Response as Response_2 } from 'express'; import { z } from 'zod'; -import { ZodType } from 'zod'; // @public (undocumented) -export type ActionsRegistryActionContext = { +export type ActionsRegistryActionContext = { input: z.infer; logger: LoggerService; credentials: BackstageCredentials; @@ -38,8 +38,8 @@ export type ActionsRegistryActionContext = { // @public (undocumented) export type ActionsRegistryActionOptions< - TInputSchema extends ZodType, - TOutputSchema extends ZodType, + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, > = { name: string; title: string; @@ -60,7 +60,10 @@ export type ActionsRegistryActionOptions< // @public (undocumented) export interface ActionsRegistryService { // (undocumented) - register( + register< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, + >( options: ActionsRegistryActionOptions, ): void; } diff --git a/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts b/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts index 3044c84ed8..44df71f41b 100644 --- a/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts +++ b/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { z, ZodType } from 'zod'; +import { z, AnyZodObject } from 'zod'; import { LoggerService } from './LoggerService'; import { BackstageCredentials } from './AuthService'; /** * @public */ -export type ActionsRegistryActionContext = { +export type ActionsRegistryActionContext = { input: z.infer; logger: LoggerService; credentials: BackstageCredentials; @@ -30,8 +30,8 @@ export type ActionsRegistryActionContext = { * @public */ export type ActionsRegistryActionOptions< - TInputSchema extends ZodType, - TOutputSchema extends ZodType, + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, > = { name: string; title: string; @@ -53,7 +53,10 @@ export type ActionsRegistryActionOptions< * @public */ export interface ActionsRegistryService { - register( + register< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, + >( options: ActionsRegistryActionOptions, ): void; } From 6a959a3cc420d45fb867cc3f28120a56ae9c71d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 21:21:35 +0000 Subject: [PATCH 061/301] chore(deps): bump brace-expansion from 1.1.11 to 1.1.12 Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12. - [Release notes](https://github.com/juliangruber/brace-expansion/releases) - [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12) --- updated-dependencies: - dependency-name: brace-expansion dependency-version: 1.1.12 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index c417643c4d..bbc155a831 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24819,12 +24819,12 @@ __metadata: linkType: hard "brace-expansion@npm:^1.1.7": - version: 1.1.11 - resolution: "brace-expansion@npm:1.1.11" + version: 1.1.12 + resolution: "brace-expansion@npm:1.1.12" dependencies: balanced-match: "npm:^1.0.0" concat-map: "npm:0.0.1" - checksum: 10/faf34a7bb0c3fcf4b59c7808bc5d2a96a40988addf2e7e09dfbb67a2251800e0d14cd2bfc1aa79174f2f5095c54ff27f46fb1289fe2d77dac755b5eb3434cc07 + checksum: 10/12cb6d6310629e3048cadb003e1aca4d8c9bb5c67c3c321bafdd7e7a50155de081f78ea3e0ed92ecc75a9015e784f301efc8132383132f4f7904ad1ac529c562 languageName: node linkType: hard From 1011968ae676ed891584e01f79554dc94c8e1002 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Thu, 12 Jun 2025 08:39:02 +0300 Subject: [PATCH 062/301] docs: fix doc proposals Signed-off-by: Hellgren Heikki --- docs/backend-system/core-services/http-router.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/backend-system/core-services/http-router.md b/docs/backend-system/core-services/http-router.md index f53f7d9cd8..5e065152a0 100644 --- a/docs/backend-system/core-services/http-router.md +++ b/docs/backend-system/core-services/http-router.md @@ -71,10 +71,9 @@ access the incoming credentials. ## Rate limiting Rate limiting allows you to limit the amount of requests users can send to you backend. -This is useful for blocking various network attacks, such as DDOS, if your instance does not -have additional firewall configured to handle this. +This is useful for blocking various network attacks, such as DDOS. -To configure the default rate limiting, add the following to your config: +To enable rate limiting, add the following to your config: ```yaml backend: From 6dfb7be91374f8bb283b6c661695328d43fadfaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 10 Jun 2025 15:28:05 +0200 Subject: [PATCH 063/301] add an actual mock implementation of permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/sharp-papayas-press.md | 5 + packages/backend-test-utils/package.json | 1 + packages/backend-test-utils/report.api.md | 16 ++- .../services/MockPermissionsService.test.ts | 57 +++++++++ .../next/services/MockPermissionsService.ts | 52 ++++++++ .../src/next/services/mockServices.ts | 34 +++++- .../src/service/createRouter.test.ts | 24 +--- .../src/routes/resourceRoutes.test.ts | 16 +-- .../src/service/KubernetesBuilder.test.ts | 16 +-- .../src/service/KubernetesProxy.test.ts | 6 +- .../src/service/router.test.ts | 111 ++++++++---------- yarn.lock | 1 + 12 files changed, 219 insertions(+), 120 deletions(-) create mode 100644 .changeset/sharp-papayas-press.md create mode 100644 packages/backend-test-utils/src/next/services/MockPermissionsService.test.ts create mode 100644 packages/backend-test-utils/src/next/services/MockPermissionsService.ts diff --git a/.changeset/sharp-papayas-press.md b/.changeset/sharp-papayas-press.md new file mode 100644 index 0000000000..a43e3ad580 --- /dev/null +++ b/.changeset/sharp-papayas-press.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': minor +--- + +Added `mockServices.permissions()` that can return actual results. diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 3783c943b6..d58720723d 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -52,6 +52,7 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-events-node": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^", "@backstage/types": "workspace:^", "@keyv/memcache": "^2.0.1", "@keyv/redis": "^4.0.1", diff --git a/packages/backend-test-utils/report.api.md b/packages/backend-test-utils/report.api.md index 379a2a70ac..c74d975920 100644 --- a/packages/backend-test-utils/report.api.md +++ b/packages/backend-test-utils/report.api.md @@ -6,6 +6,7 @@ import { ActionsRegistryService } from '@backstage/backend-plugin-api'; import { ActionsService } from '@backstage/backend-plugin-api'; import { AuditorService } from '@backstage/backend-plugin-api'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { AuthService } from '@backstage/backend-plugin-api'; import { Backend } from '@backstage/backend-app-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; @@ -299,14 +300,17 @@ export namespace mockServices { partialImpl?: Partial | undefined, ) => ServiceMock; } + export function permissions(options?: { + result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; + }): PermissionsService; // (undocumented) export namespace permissions { - const // (undocumented) - factory: () => ServiceFactory; - const // (undocumented) - mock: ( - partialImpl?: Partial | undefined, - ) => ServiceMock; + const factory: (options?: { + result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; + }) => ServiceFactory; + const mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; } // (undocumented) export namespace permissionsRegistry { diff --git a/packages/backend-test-utils/src/next/services/MockPermissionsService.test.ts b/packages/backend-test-utils/src/next/services/MockPermissionsService.test.ts new file mode 100644 index 0000000000..eb98fe9457 --- /dev/null +++ b/packages/backend-test-utils/src/next/services/MockPermissionsService.test.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { MockPermissionsService } from './MockPermissionsService'; + +describe('MockPermissionsService', () => { + it('should return the correct result', async () => { + const permission = { + permission: { + name: 'test', + type: 'basic', + attributes: {}, + }, + } as const; + + const defaultService = new MockPermissionsService(); + const allowService = new MockPermissionsService({ + result: AuthorizeResult.ALLOW, + }); + const denyService = new MockPermissionsService({ + result: AuthorizeResult.DENY, + }); + + await expect(defaultService.authorize([permission])).resolves.toEqual([ + { + ...permission, + result: AuthorizeResult.ALLOW, + }, + ]); + await expect(allowService.authorize([permission])).resolves.toEqual([ + { + ...permission, + result: AuthorizeResult.ALLOW, + }, + ]); + await expect(denyService.authorize([permission])).resolves.toEqual([ + { + ...permission, + result: AuthorizeResult.DENY, + }, + ]); + }); +}); diff --git a/packages/backend-test-utils/src/next/services/MockPermissionsService.ts b/packages/backend-test-utils/src/next/services/MockPermissionsService.ts new file mode 100644 index 0000000000..3637c34554 --- /dev/null +++ b/packages/backend-test-utils/src/next/services/MockPermissionsService.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PermissionsService } from '@backstage/backend-plugin-api'; +import { + AuthorizePermissionRequest, + AuthorizePermissionResponse, + AuthorizeResult, + QueryPermissionRequest, + QueryPermissionResponse, +} from '@backstage/plugin-permission-common'; + +export class MockPermissionsService implements PermissionsService { + readonly #result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; + + constructor(options?: { + result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; + }) { + this.#result = options?.result ?? AuthorizeResult.ALLOW; + } + + async authorize( + requests: AuthorizePermissionRequest[], + ): Promise { + return requests.map(request => ({ + ...request, + result: this.#result, + })); + } + + async authorizeConditional( + requests: QueryPermissionRequest[], + ): Promise { + return requests.map(request => ({ + ...request, + result: this.#result, + })); + } +} diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts index 0c5b18bd1e..95f8b084c9 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/next/services/mockServices.ts @@ -36,6 +36,7 @@ import { DiscoveryService, HttpAuthService, LoggerService, + PermissionsService, RootConfigService, ServiceFactory, ServiceRef, @@ -45,6 +46,7 @@ import { } from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { EventsService, eventsServiceRef } from '@backstage/plugin-events-node'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { JsonObject } from '@backstage/types'; import { Knex } from 'knex'; import { MockAuthService } from './MockAuthService'; @@ -55,6 +57,7 @@ import { mockCredentials } from './mockCredentials'; import { MockEventsService } from './MockEventsService'; import { actionsServiceFactory } from '@backstage/backend-defaults/actions'; import { actionsRegistryServiceFactory } from '@backstage/backend-defaults/actionsRegistry'; +import { MockPermissionsService } from './MockPermissionsService'; /** @internal */ function createLoggerMock() { @@ -475,8 +478,37 @@ export namespace mockServices { ); } + /** + * Creates a functional mock implementation of the + * {@link @backstage/backend-plugin-api#PermissionsService}. + */ + export function permissions(options?: { + result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; + }): PermissionsService { + return new MockPermissionsService(options); + } export namespace permissions { - export const factory = () => permissionsServiceFactory; + /** + * Creates a mock factory for the + * {@link @backstage/backend-plugin-api#coreServices.permissions}. Just + * returns the given `result` if you supply one. Otherwise, it returns the + * regular default permissions factory. + */ + export const factory = (options?: { + result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; + }) => + options?.result + ? createServiceFactory({ + service: coreServices.permissions, + deps: {}, + factory: () => new MockPermissionsService(options), + }) + : permissionsServiceFactory; + /** + * Creates a mock of the + * {@link @backstage/backend-plugin-api#coreServices.permissions}, + * optionally with some given method implementations. + */ export const mock = simpleMock(coreServices.permissions, () => ({ authorize: jest.fn(), authorizeConditional: jest.fn(), diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index b0dddbdef0..193d5b5b52 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -56,7 +56,7 @@ describe('createRouter readonly disabled', () => { let app: express.Express | Server; let refreshService: RefreshService; let locationAnalyzer: jest.Mocked; - const permissionsService = mockServices.permissions.mock(); + const permissionsService = mockServices.permissions(); beforeEach(async () => { entitiesCatalog = { @@ -799,12 +799,6 @@ describe('createRouter readonly disabled', () => { metadata: { name: 'n' }, }; - permissionsService.authorize.mockResolvedValueOnce([ - { - result: AuthorizeResult.ALLOW, - }, - ]); - orchestrator.process.mockResolvedValueOnce({ ok: true, state: {}, @@ -845,12 +839,6 @@ describe('createRouter readonly disabled', () => { metadata: { name: 'invalid*name' }, }; - permissionsService.authorize.mockResolvedValueOnce([ - { - result: AuthorizeResult.ALLOW, - }, - ]); - orchestrator.process.mockResolvedValueOnce({ ok: false, errors: [new Error('Invalid entity name')], @@ -888,12 +876,6 @@ describe('createRouter readonly disabled', () => { metadata: { name: 'n' }, }; - permissionsService.authorize.mockResolvedValueOnce([ - { - result: AuthorizeResult.ALLOW, - }, - ]); - const response = await request(app) .post('/validate-entity') .send({ entity, location: null }); @@ -940,7 +922,7 @@ describe('createRouter readonly and raw json enabled', () => { let entitiesCatalog: jest.Mocked; let app: express.Express; let locationService: jest.Mocked; - const permissionsService = mockServices.permissions.mock(); + const permissionsService = mockServices.permissions(); beforeAll(async () => { entitiesCatalog = { @@ -1144,7 +1126,7 @@ describe('NextRouter permissioning', () => { let locationService: jest.Mocked; let app: express.Express; let refreshService: RefreshService; - const permissionsService = mockServices.permissions.mock(); + const permissionsService = mockServices.permissions(); const fakeRule = createPermissionRule({ name: 'FAKE_RULE', diff --git a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts index 462312a404..50c50d7ffa 100644 --- a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts +++ b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts @@ -18,30 +18,18 @@ import request from 'supertest'; import { mockCredentials, mockServices, - type ServiceMock, startTestBackend, } from '@backstage/backend-test-utils'; import { kubernetesObjectsProviderExtensionPoint } from '@backstage/plugin-kubernetes-node'; -import { - createBackendModule, - type PermissionsService, -} from '@backstage/backend-plugin-api'; +import { createBackendModule } from '@backstage/backend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { ExtendedHttpServer } from '@backstage/backend-defaults/rootHttpRouter'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; describe('resourcesRoutes', () => { let app: ExtendedHttpServer; - const permissionsMock: ServiceMock = - mockServices.permissions.mock({ - authorize: jest.fn(), - authorizeConditional: jest.fn(), - }); const startPermissionDeniedTestServer = async () => { - permissionsMock.authorize.mockResolvedValue([ - { result: AuthorizeResult.DENY }, - ]); const { server } = await startTestBackend({ features: [ mockServices.rootConfig.factory({ @@ -52,7 +40,7 @@ describe('resourcesRoutes', () => { }, }, }), - permissionsMock.factory, + mockServices.permissions.factory({ result: AuthorizeResult.DENY }), import('@backstage/plugin-kubernetes-backend'), ], }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index 3046232bad..a3353eec43 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -35,7 +35,6 @@ import { } from './KubernetesProxy'; import { setupServer } from 'msw/node'; import { - ServiceMock, mockCredentials, mockServices, registerMswTestHooks, @@ -43,10 +42,7 @@ import { } from '@backstage/backend-test-utils'; import { rest } from 'msw'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; -import { - PermissionsService, - createBackendModule, -} from '@backstage/backend-plugin-api'; +import { createBackendModule } from '@backstage/backend-plugin-api'; import { AuthMetadata, KubernetesObjectsProvider, @@ -64,11 +60,6 @@ describe('API integration tests', () => { const happyK8SResult = { items: [{ clusterOne: { pods: [{ metadata: { name: 'pod1' } }] } }], }; - const permissionsMock: ServiceMock = - mockServices.permissions.mock({ - authorize: jest.fn(), - authorizeConditional: jest.fn(), - }); const minimalValidConfigService = mockServices.rootConfig.factory({ data: { kubernetes: { @@ -93,13 +84,10 @@ describe('API integration tests', () => { }, }); const startPermissionDeniedTestServer = async () => { - permissionsMock.authorize.mockResolvedValue([ - { result: AuthorizeResult.DENY }, - ]); const { server } = await startTestBackend({ features: [ minimalValidConfigService, - permissionsMock.factory, + mockServices.permissions.factory({ result: AuthorizeResult.DENY }), import('@backstage/plugin-kubernetes-backend'), ], }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts index 3ed8b25c56..3ef7f95b7d 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts @@ -22,7 +22,6 @@ import { registerMswTestHooks, } from '@backstage/backend-test-utils'; import { NotFoundError } from '@backstage/errors'; -import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { ANNOTATION_KUBERNETES_AUTH_PROVIDER, KubernetesRequestAuth, @@ -78,7 +77,7 @@ describe('KubernetesProxy', () => { >(), }; - const permissionApi = mockServices.permissions.mock(); + const permissionApi = mockServices.permissions(); const mockDisocveryApi = mockServices.discovery.mock(); registerMswTestHooks(worker); @@ -156,9 +155,6 @@ describe('KubernetesProxy', () => { authStrategy, discovery: mockDisocveryApi, }); - permissionApi.authorize.mockResolvedValue([ - { result: AuthorizeResult.ALLOW }, - ]); }); it('should return a ERROR_NOT_FOUND if no clusters are found', async () => { diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index c8d15b2246..679ac44640 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -195,22 +195,7 @@ const createTestRouter = async ( jest.spyOn(taskBroker, 'event$'); const catalog = catalogServiceMock.mock(); - const permissions = mockServices.permissions.mock(); - - permissions.authorizeConditional.mockImplementation(async p => - p.map(innerP => ({ - ...innerP, - result: AuthorizeResult.ALLOW, - })), - ); - - permissions.authorize.mockImplementation(async p => - p.map(innerP => ({ - ...innerP, - result: AuthorizeResult.ALLOW, - })), - ); - + const permissions = mockServices.permissions(); const auth = mockServices.auth(); const httpAuth = mockServices.httpAuth(); const events = mockServices.events(); @@ -517,14 +502,16 @@ describe('scaffolder router', () => { it('filters parameters that the user is not authorized to see', async () => { const { router, permissions } = await createTestRouter(); - permissions.authorizeConditional.mockImplementationOnce(async () => [ - { - result: AuthorizeResult.DENY, - }, - { - result: AuthorizeResult.ALLOW, - }, - ]); + jest + .spyOn(permissions, 'authorizeConditional') + .mockImplementationOnce(async () => [ + { + result: AuthorizeResult.DENY, + }, + { + result: AuthorizeResult.ALLOW, + }, + ]); const response = await request(router) .get( @@ -541,21 +528,23 @@ describe('scaffolder router', () => { it('filters parameters that the user is not authorized to see in case of conditional decision', async () => { const { permissions, router } = await createTestRouter(); - permissions.authorizeConditional.mockImplementation(async () => [ - { - conditions: { + jest + .spyOn(permissions, 'authorizeConditional') + .mockImplementation(async () => [ + { + conditions: { + resourceType: 'scaffolder-template', + rule: 'HAS_TAG', + params: { tag: 'parameters-tag' }, + }, + pluginId: 'scaffolder', resourceType: 'scaffolder-template', - rule: 'HAS_TAG', - params: { tag: 'parameters-tag' }, + result: AuthorizeResult.CONDITIONAL, }, - pluginId: 'scaffolder', - resourceType: 'scaffolder-template', - result: AuthorizeResult.CONDITIONAL, - }, - { - result: AuthorizeResult.ALLOW, - }, - ]); + { + result: AuthorizeResult.ALLOW, + }, + ]); const response = await request(router) .get( @@ -721,14 +710,16 @@ describe('scaffolder router', () => { it('filters steps that the user is not authorized to see', async () => { const { router, permissions, taskBroker } = await createTestRouter(); - permissions.authorizeConditional.mockImplementation(async () => [ - { - result: AuthorizeResult.ALLOW, - }, - { - result: AuthorizeResult.DENY, - }, - ]); + jest + .spyOn(permissions, 'authorizeConditional') + .mockImplementation(async () => [ + { + result: AuthorizeResult.ALLOW, + }, + { + result: AuthorizeResult.DENY, + }, + ]); const broker = taskBroker.dispatch as jest.Mocked['dispatch']; const mockTemplate = generateMockTemplate(); @@ -786,21 +777,23 @@ describe('scaffolder router', () => { it('filters steps that the user is not authorized to see in case of conditional decision', async () => { const { permissions, router, taskBroker } = await createTestRouter(); - permissions.authorizeConditional.mockImplementation(async () => [ - { - result: AuthorizeResult.ALLOW, - }, - { - conditions: { - resourceType: 'scaffolder-template', - rule: 'HAS_TAG', - params: { tag: 'steps-tag' }, + jest + .spyOn(permissions, 'authorizeConditional') + .mockImplementation(async () => [ + { + result: AuthorizeResult.ALLOW, }, - pluginId: 'scaffolder', - resourceType: 'scaffolder-template', - result: AuthorizeResult.CONDITIONAL, - }, - ]); + { + conditions: { + resourceType: 'scaffolder-template', + rule: 'HAS_TAG', + params: { tag: 'steps-tag' }, + }, + pluginId: 'scaffolder', + resourceType: 'scaffolder-template', + result: AuthorizeResult.CONDITIONAL, + }, + ]); const broker = taskBroker.dispatch as jest.Mocked['dispatch']; const mockTemplate = generateMockTemplate(); diff --git a/yarn.lock b/yarn.lock index c417643c4d..b09bfdd787 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3776,6 +3776,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-events-node": "workspace:^" + "@backstage/plugin-permission-common": "workspace:^" "@backstage/types": "workspace:^" "@keyv/memcache": "npm:^2.0.1" "@keyv/redis": "npm:^4.0.1" From eb8b3aaca024c4a0756ce5712c0e62c59ab9c2f0 Mon Sep 17 00:00:00 2001 From: Joan Miquel Luque Oliver Date: Thu, 12 Jun 2025 13:18:38 +0200 Subject: [PATCH 064/301] feat: Add validateLocationsExist option for the BitbucketServer entity provider Signed-off-by: Joan Miquel Luque Oliver --- .changeset/fifty-sides-begin.md | 5 + .../integrations/bitbucketServer/discovery.md | 5 + .../config.d.ts | 10 ++ .../src/lib/BitbucketServerClient.ts | 5 +- .../BitbucketServerEntityProvider.test.ts | 91 +++++++++++++++++++ .../BitbucketServerEntityProvider.ts | 31 +++++++ ...itbucketServerEntityProviderConfig.test.ts | 37 ++++++++ .../BitbucketServerEntityProviderConfig.ts | 5 +- 8 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 .changeset/fifty-sides-begin.md diff --git a/.changeset/fifty-sides-begin.md b/.changeset/fifty-sides-begin.md new file mode 100644 index 0000000000..5cd174e4ff --- /dev/null +++ b/.changeset/fifty-sides-begin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket-server': minor +--- + +Add validateLocationsExist option to avoid generating locations for catalog-info.yaml files that do not exist in the source repository. diff --git a/docs/integrations/bitbucketServer/discovery.md b/docs/integrations/bitbucketServer/discovery.md index 29c8d40338..350bc33aed 100644 --- a/docs/integrations/bitbucketServer/discovery.md +++ b/docs/integrations/bitbucketServer/discovery.md @@ -66,6 +66,7 @@ catalog: projectKey: '^apis-.*$' # optional; RegExp repoSlug: '^service-.*$' # optional; RegExp skipArchivedRepos: true # optional; boolean + validateLocationsExist: false # optional; boolean schedule: # same options as in SchedulerServiceTaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } @@ -86,6 +87,10 @@ catalog: Regular expression used to filter results based on the repo slug. - **`skipArchivedRepos`** _(optional)_: Boolean flag to filter out archived repositories. +- **`validateLocationsExist`** _(optional)_: + Defaults to `false`. + Whether to validate locations that exist before emitting them. + This option avoids generating locations for catalog info files that do not exist in the source repository. - **`schedule`**: - **`frequency`**: How often you want the task to run. The system does its best to avoid overlapping invocations. diff --git a/plugins/catalog-backend-module-bitbucket-server/config.d.ts b/plugins/catalog-backend-module-bitbucket-server/config.d.ts index 0f6889efa7..d57a15a736 100644 --- a/plugins/catalog-backend-module-bitbucket-server/config.d.ts +++ b/plugins/catalog-backend-module-bitbucket-server/config.d.ts @@ -51,6 +51,11 @@ export interface Config { */ skipArchivedRepos?: boolean; }; + /** + * (Optional) Whether to validate locations that exist before emitting them. + * Default: `false`. + */ + validateLocationsExist?: boolean; /** * (Optional) TaskScheduleDefinition for the refresh. */ @@ -83,6 +88,11 @@ export interface Config { */ skipArchivedRepos?: boolean; }; + /** + * (Optional) Whether to validate locations that exist before emitting them. + * Default: `false`. + */ + validateLocationsExist?: boolean; /** * (Optional) TaskScheduleDefinition for the refresh. */ diff --git a/plugins/catalog-backend-module-bitbucket-server/src/lib/BitbucketServerClient.ts b/plugins/catalog-backend-module-bitbucket-server/src/lib/BitbucketServerClient.ts index fa0c455b04..7bf6480f33 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/lib/BitbucketServerClient.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/lib/BitbucketServerClient.ts @@ -70,8 +70,11 @@ export class BitbucketServerClient { repo: string; path: string; }): Promise { + const normalizedPath = options.path.startsWith('/') + ? options.path.substring(1) + : options.path; return fetch( - `${this.config.apiBaseUrl}/projects/${options.projectKey}/repos/${options.repo}/raw/${options.path}`, + `${this.config.apiBaseUrl}/projects/${options.projectKey}/repos/${options.repo}/raw/${normalizedPath}`, getBitbucketServerRequestOptions(this.config), ); } diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts index ba4e3e0411..790bef7939 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts @@ -668,6 +668,97 @@ describe('BitbucketServerEntityProvider', () => { }); }); + it('do not add locations when validateLocationsExist and catalog-info does not exist', async () => { + server.use( + rest.get( + `https://${host}/rest/api/1.0/projects/project-test/repos/repo-test/raw/catalog-info.yaml`, + (_, res, ctx) => { + return res(ctx.status(404)); + }, + ), + rest.get( + `https://${host}/rest/api/1.0/projects/other-project/repos/other-repo/raw/catalog-info.yaml`, + (_, res, ctx) => { + return res(ctx.status(200)); + }, + ), + ); + const config = new ConfigReader({ + integrations: { + bitbucketServer: [ + { + host: host, + }, + ], + }, + catalog: { + providers: { + bitbucketServer: { + mainProvider: { + host: host, + validateLocationsExist: true, + }, + }, + }, + }, + }); + const schedule = new PersistingTaskRunner(); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + const provider = BitbucketServerEntityProvider.fromConfig(config, { + logger, + schedule, + })[0]; + expect(provider.getProviderName()).toEqual( + 'bitbucketServer-provider:mainProvider', + ); + + setupStubs( + [ + { key: 'project-test', repos: [{ name: 'repo-test' }] }, + { key: 'other-project', repos: [{ name: 'other-repo' }] }, + ], + `https://${host}`, + 'master', + ); + await provider.connect(entityProviderConnection); + + const taskDef = schedule.getTasks()[0]; + expect(taskDef.id).toEqual('bitbucketServer-provider:mainProvider:refresh'); + await (taskDef.fn as () => Promise)(); + + const expectedEntities = [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:https://${host}/projects/other-project/repos/other-repo/browse/catalog-info.yaml`, + 'backstage.io/managed-by-origin-location': `url:https://${host}/projects/other-project/repos/other-repo/browse/catalog-info.yaml`, + 'bitbucket.org/default-branch': 'master', + }, + name: 'generated-d8d4944c30c2906dfee172ddda9537f9893b2c0f', + }, + spec: { + presence: 'optional', + target: `https://${host}/projects/other-project/repos/other-repo/browse/catalog-info.yaml`, + type: 'url', + }, + }, + locationKey: 'bitbucketServer-provider:mainProvider', + }, + ]; + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: expectedEntities, + }); + }); + it('Multiple location entities to deferred entities', async () => { const schedule = new PersistingTaskRunner(); const config = new ConfigReader({ diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts index b462f8756a..4b02632892 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts @@ -253,6 +253,37 @@ export class BitbucketServerEntityProvider implements EntityProvider { if (this.config?.filters?.skipArchivedRepos && repository.archived) { continue; } + if (this.config.validateLocationsExist) { + try { + const response = await client.getFile({ + projectKey: project.key, + repo: repository.slug, + path: this.config.catalogPath, + }); + if (!response.ok) { + if (response.status === 404) { + this.logger.debug( + `Skipping repository ${repository.slug} in project ${project.key} because the catalog file does not exist.`, + ); + } else { + this.logger.warn( + `Unexpected response code ${ + response.status + } while fetching the catalog file from repository ${ + repository.slug + } in project ${ + project.key + }. Response details: ${JSON.stringify(response)}`, + ); + } + continue; + } + } catch (error) { + this.logger.error( + `An error occurred while fetching the catalog file from repository ${repository.slug} in project ${project.key}: ${error}`, + ); + } + } for await (const entity of this.parser({ client, logger: this.logger, diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts index 755fa4b58a..cd39f0659b 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts @@ -49,6 +49,8 @@ describe('readProviderConfigs', () => { repoSlug: undefined, skipArchivedRepos: undefined, }, + schedule: undefined, + validateLocationsExist: false, }); }); @@ -89,6 +91,8 @@ describe('readProviderConfigs', () => { repoSlug: undefined, skipArchivedRepos: undefined, }, + schedule: undefined, + validateLocationsExist: false, }); expect(providerConfigs[1]).toEqual({ id: 'secondaryProvider', @@ -99,6 +103,8 @@ describe('readProviderConfigs', () => { repoSlug: undefined, skipArchivedRepos: undefined, }, + schedule: undefined, + validateLocationsExist: false, }); expect(providerConfigs[2]).toEqual({ id: 'thirdProvider', @@ -109,6 +115,7 @@ describe('readProviderConfigs', () => { repoSlug: undefined, skipArchivedRepos: undefined, }, + validateLocationsExist: false, schedule: { frequency: { minutes: 30 }, timeout: { @@ -147,6 +154,36 @@ describe('readProviderConfigs', () => { repoSlug: /.*/, skipArchivedRepos: true, }, + schedule: undefined, + validateLocationsExist: false, + }); + }); + + it('single simple provider config with validateLocationsExist', () => { + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketServer: { + host: 'bitbucket.mycompany.com', + validateLocationsExist: true, + }, + }, + }, + }); + const providerConfigs = readProviderConfigs(config); + + expect(providerConfigs).toHaveLength(1); + expect(providerConfigs[0]).toEqual({ + id: 'default', + catalogPath: '/catalog-info.yaml', + host: 'bitbucket.mycompany.com', + filters: { + projectKey: undefined, + repoSlug: undefined, + skipArchivedRepos: undefined, + }, + schedule: undefined, + validateLocationsExist: true, }); }); }); diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts index 3b921b6bab..1a224536a7 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts @@ -32,6 +32,7 @@ export type BitbucketServerEntityProviderConfig = { repoSlug?: RegExp; skipArchivedRepos?: boolean; }; + validateLocationsExist: boolean; schedule?: SchedulerServiceTaskScheduleDefinition; }; @@ -66,7 +67,8 @@ function readProviderConfig( const skipArchivedReposFlag = config.getOptionalBoolean( 'filters.skipArchivedRepos', ); - + const validateLocationsExistFlag = + config?.getOptionalBoolean('validateLocationsExist') ?? false; const schedule = config.has('schedule') ? readSchedulerServiceTaskScheduleDefinitionFromConfig( config.getConfig('schedule'), @@ -82,6 +84,7 @@ function readProviderConfig( repoSlug: repoSlugPattern ? new RegExp(repoSlugPattern) : undefined, skipArchivedRepos: skipArchivedReposFlag, }, + validateLocationsExist: validateLocationsExistFlag, schedule, }; } From 5d97a732a27b81871c8b79a9c05761134386f3ef Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Thu, 12 Jun 2025 21:04:38 +0300 Subject: [PATCH 065/301] docs: fix autologout default value closes #30223 Signed-off-by: Hellgren Heikki --- docs/auth/autologout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/auth/autologout.md b/docs/auth/autologout.md index b85f6f486d..6317d5873e 100644 --- a/docs/auth/autologout.md +++ b/docs/auth/autologout.md @@ -69,7 +69,7 @@ These are the available settings: | Configuration Key | Component Property | Description | Allowed Values | Default Value | | ----------------------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | --------------------------- | | `auth.autologout.enabled` | `enabled` | Enable/disable the Auto Logout feature. | `true`/`false` | Default is enabled (`true`) | -| `auth.autologout.idleTimeoutMinutes` | `idleTimeoutMinutes` | Sets the idle time (in minutes) after which the user will be logged out. | `>= 0.5` minutes | `60` | +| `auth.autologout.idleTimeoutMinutes` | `idleTimeoutMinutes` | Sets the idle time (in minutes) after which the user will be logged out. | `>= 0.5` minutes | `0.5` | | `auth.autologout.promptBeforeIdleSeconds` | `promptBeforeIdleSeconds` | Determines the time (in seconds) prior to idle state when a prompt will appear. A value of 0 disables the prompt. This must be less than the value of `idleTimeoutMinutes`. | `>= 0` seconds | `10` | | `auth.autologout.useWorkerTimers` | `useWorkerTimers` | Enables or disables the use of Node's worker thread timers instead of main thread timers. This can be beneficial if the browser is terminating timers in inactive tabs, like those used by auto logout. In case of browser incompatibility, try setting this to false. | `true`/`false` | `true` | | `auth.autologout.logoutIfDisconnected` | `logoutIfDisconnected` | Enable/disable auto logout for disconnected users. Disconnected users are those who are logged in but do not have any active Backstage tabs open in their browsers. If enabled, such users will be automatically logged out after `idleTimeoutMinutes`. | `true`/`false` | `true` | From 4af68d5f820fe0aec96991c9b8ca8f4a9d81d28d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 13 Jun 2025 00:40:01 +0200 Subject: [PATCH 066/301] microsite: update algolia API key Signed-off-by: Patrik Oldsberg --- microsite/docusaurus.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index d71483ab8f..d7538cd12d 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -483,7 +483,7 @@ const config: Config = { copyright: `

Made with ❤️ at Spotify

`, }, algolia: { - apiKey: '1f0ba86672ccfc3576faa94583e5b318', + apiKey: '60d2643a9c6306463f15f8c3556e7f2e', // Owned by @Rugvip indexName: 'crawler_Backstage Docusaurus 2', appId: 'JCMFNHCHI8', searchParameters: {}, From 2da89d12895d39a44002051d45f2abfb1c757ff2 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 13 Jun 2025 10:00:42 +0200 Subject: [PATCH 067/301] scaffolder: refine analytics events data Signed-off-by: Vincenzo Scamporlino --- .../TemplateGroup/TemplateGroup.tsx | 18 ++++--- .../src/next/components/Workflow/Workflow.tsx | 6 +-- .../components/OngoingTask/ContextMenu.tsx | 28 ++-------- .../components/OngoingTask/OngoingTask.tsx | 51 ++++++++++++++++--- 4 files changed, 63 insertions(+), 40 deletions(-) diff --git a/plugins/scaffolder-react/src/next/components/TemplateGroup/TemplateGroup.tsx b/plugins/scaffolder-react/src/next/components/TemplateGroup/TemplateGroup.tsx index a70b822d13..74ade33344 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateGroup/TemplateGroup.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateGroup/TemplateGroup.tsx @@ -22,7 +22,7 @@ import { } from '@backstage/core-components'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { TemplateCardProps, TemplateCard } from '../TemplateCard'; -import { IconComponent } from '@backstage/core-plugin-api'; +import { AnalyticsContext, IconComponent } from '@backstage/core-plugin-api'; /** * The props for the {@link TemplateGroup} component. @@ -69,12 +69,18 @@ export const TemplateGroup = (props: TemplateGroupProps) => { {titleComponent} {templates.map(({ template, additionalLinks }) => ( - + > + + ))} diff --git a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx index 2aa4ee9b2d..95e2647b5a 100644 --- a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx +++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx @@ -97,13 +97,11 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => { async (formState: Record) => { await onCreate(formState); - const name = - typeof formState.name === 'string' ? formState.name : undefined; - analytics.captureEvent('create', name ?? templateName ?? 'unknown', { + analytics.captureEvent('create', 'Task has been created', { value: minutesSaved, }); }, - [onCreate, analytics, templateName, minutesSaved], + [onCreate, analytics, minutesSaved], ); useEffect(() => { diff --git a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx index 704d39a8fd..e4ca230c36 100644 --- a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx @@ -29,11 +29,8 @@ import Toc from '@material-ui/icons/Toc'; import ControlPointIcon from '@material-ui/icons/ControlPoint'; import MoreVert from '@material-ui/icons/MoreVert'; import { SyntheticEvent, useState } from 'react'; -import { useAnalytics, useApi } from '@backstage/core-plugin-api'; -import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; import { usePermission } from '@backstage/plugin-permission-react'; import { - taskCancelPermission, taskReadPermission, taskCreatePermission, } from '@backstage/plugin-scaffolder-common/alpha'; @@ -50,7 +47,8 @@ type ContextMenuProps = { onStartOver?: () => void; onToggleLogs?: (state: boolean) => void; onToggleButtonBar?: (state: boolean) => void; - taskId?: string; + isCancelButtonDisabled: boolean; + onCancel: () => void; }; const useStyles = makeStyles(() => ({ @@ -70,27 +68,13 @@ export const ContextMenu = (props: ContextMenuProps) => { onStartOver, onToggleLogs, onToggleButtonBar, - taskId, } = props; const { getPageTheme } = useTheme(); const pageTheme = getPageTheme({ themeId: 'website' }); const classes = useStyles({ fontColor: pageTheme.fontColor }); - const scaffolderApi = useApi(scaffolderApiRef); - const analytics = useAnalytics(); const [anchorEl, setAnchorEl] = useState(); const { t } = useTranslationRef(scaffolderTranslationRef); - const [{ status: cancelStatus }, { execute: cancel }] = useAsync(async () => { - if (taskId) { - analytics.captureEvent('cancelled', 'Template has been cancelled'); - await scaffolderApi.cancelTask(taskId); - } - }); - - const { allowed: canCancelTask } = usePermission({ - permission: taskCancelPermission, - }); - const { allowed: canReadTask } = usePermission({ permission: taskReadPermission, }); @@ -171,12 +155,8 @@ export const ContextMenu = (props: ContextMenuProps) => { )} diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx index b718a257b6..c9a119e4a1 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx @@ -32,7 +32,12 @@ import { useTaskEventStream, } from '@backstage/plugin-scaffolder-react'; import { selectedTemplateRouteRef } from '../../routes'; -import { useAnalytics, useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { + AnalyticsContext, + useAnalytics, + useApi, + useRouteRef, +} from '@backstage/core-plugin-api'; import qs from 'qs'; import { ContextMenu } from './ContextMenu'; import { @@ -51,6 +56,7 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { scaffolderTranslationRef } from '../../translation'; import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; import { default as reactUseAsync } from 'react-use/esm/useAsync'; +import { stringifyEntityRef } from '@backstage/catalog-model'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -82,6 +88,36 @@ export const OngoingTask = (props: { }>; }) => { // todo(blam): check that task Id actually exists, and that it's valid. otherwise redirect to something more useful. + const { taskId } = useParams(); + const taskStream = useTaskEventStream(taskId!); + const { namespace, name } = + taskStream.task?.spec.templateInfo?.entity?.metadata ?? {}; + + return ( + + + + + + ); +}; + +function OngoingTaskContent(props: { + TemplateOutputsComponent?: ComponentType<{ + output?: ScaffolderTaskOutput; + }>; +}) { const { taskId } = useParams(); const templateRouteRef = useRouteRef(selectedTemplateRouteRef); const navigate = useNavigate(); @@ -183,7 +219,7 @@ export const OngoingTask = (props: { templateRouteRef, ]); - const [{ status: _ }, { execute: triggerRetry }] = useAsync(async () => { + const [, { execute: triggerRetry }] = useAsync(async () => { if (taskId) { analytics.captureEvent('retried', 'Template has been retried'); await scaffolderApi.retry?.(taskId); @@ -202,9 +238,11 @@ export const OngoingTask = (props: { const Outputs = props.TemplateOutputsComponent ?? DefaultTemplateOutputs; const cancelEnabled = !(taskStream.cancelled || taskStream.completed); + const isCancelButtonDisabled = + !cancelEnabled || cancelStatus !== 'not-executed' || !canCancelTask; return ( - + <>
@@ -316,6 +355,6 @@ export const OngoingTask = (props: { ) : null} -
+ ); -}; +} From bea4f7f368f26992a7cd3b536900881c652906ad Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 13 Jun 2025 10:01:33 +0200 Subject: [PATCH 068/301] scaffolder: do not error on render method Signed-off-by: Vincenzo Scamporlino --- .../next/components/TemplateGroups/TemplateGroups.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx b/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx index a0070765fe..58b74dff9d 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx @@ -22,7 +22,7 @@ import { } from '@backstage/plugin-scaffolder-common'; import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; import Typography from '@material-ui/core/Typography'; -import { ComponentType, useCallback } from 'react'; +import { ComponentType, useCallback, useEffect } from 'react'; import { TemplateGroup } from '../TemplateGroup/TemplateGroup'; @@ -58,12 +58,17 @@ export const TemplateGroups = (props: TemplateGroupsProps) => { [onTemplateSelected], ); + useEffect(() => { + if (error) { + errorApi.post(error); + } + }, [error, errorApi]); + if (loading) { return ; } if (error) { - errorApi.post(error); return null; } From 6c972fe17f8dfd5de10a2b32c862071f6c1ccd93 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 13 Jun 2025 10:12:43 +0200 Subject: [PATCH 069/301] scaffolder: analytics changeset Signed-off-by: Vincenzo Scamporlino --- .changeset/dull-cloths-act.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dull-cloths-act.md diff --git a/.changeset/dull-cloths-act.md b/.changeset/dull-cloths-act.md new file mode 100644 index 0000000000..82ba469c7d --- /dev/null +++ b/.changeset/dull-cloths-act.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Added information about the `entityRef` and `taskId` to the analytics events whenever is possible. From 2092bd5bb2699045c59035f0bbbe025419916f5b Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 13 Jun 2025 10:25:38 +0200 Subject: [PATCH 070/301] chore: added ability to define attributes Signed-off-by: benjdlambert --- .../actions/actionsServiceFactory.test.ts | 10 ++ .../DefaultActionsRegistryService.ts | 10 +- .../actionsRegistryServiceFactory.test.ts | 103 ++++++++++++++++++ .../definitions/ActionsRegistryService.ts | 5 + .../services/definitions/ActionsService.ts | 5 + packages/backend-test-utils/package.json | 2 + yarn.lock | 2 + 7 files changed, 135 insertions(+), 2 deletions(-) diff --git a/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts index 7b7285c89a..58a79c6a2a 100644 --- a/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts @@ -70,6 +70,11 @@ describe('actionsServiceFactory', () => { input: {}, output: {}, }, + attributes: { + destructive: false, + idempotent: false, + readOnly: false, + }, }; beforeEach(() => { @@ -306,6 +311,11 @@ describe('actionsServiceFactory', () => { type: 'object', }, }, + attributes: { + destructive: false, + idempotent: false, + readOnly: false, + }, title: 'Test', }, ], diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts b/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts index 2ecf4d5006..cddb04c817 100644 --- a/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts +++ b/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts @@ -67,13 +67,19 @@ export class DefaultActionsRegistryService implements ActionsRegistryService { actions: Array.from(this.actions.entries()).map(([id, action]) => ({ id, ...action, + attributes: { + // todo(blam): what's safe defaults? + destructive: action.attributes?.destructive ?? false, + idempotent: action.attributes?.idempotent ?? false, + readOnly: action.attributes?.readOnly ?? false, + }, schema: { input: action.schema?.input ? zodToJsonSchema(action.schema.input(z)) - : zodToJsonSchema(z.any()), + : zodToJsonSchema(z.object({})), output: action.schema?.output ? zodToJsonSchema(action.schema.output(z)) - : zodToJsonSchema(z.any()), + : zodToJsonSchema(z.object({})), }, })), }); diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts index 9d6674c0cf..e885cb10a5 100644 --- a/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts @@ -181,6 +181,109 @@ describe('actionsRegistryServiceFactory', () => { }); }); + it('should set default attributes', async () => { + const pluginSubject = createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { + actionsRegistry: coreServices.actionsRegistry, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({}), + output: z => z.object({}), + }, + action: async () => ({ output: { ok: true } }), + }); + }, + }); + }, + }); + + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + const { body, status } = await request(server).get( + '/api/my-plugin/.backstage/actions/v1/actions', + ); + + expect(status).toBe(200); + + expect(body).toMatchObject({ + actions: [ + { + name: 'test', + attributes: { + destructive: false, + idempotent: false, + readOnly: false, + }, + }, + ], + }); + }); + + it('should allow setting attributes', async () => { + const pluginSubject = createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { + actionsRegistry: coreServices.actionsRegistry, + }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + attributes: { + destructive: true, + idempotent: true, + readOnly: true, + }, + schema: { + input: z => z.object({}), + output: z => z.object({}), + }, + action: async () => ({ output: { ok: true } }), + }); + }, + }); + }, + }); + + const { server } = await startTestBackend({ + features: [pluginSubject, ...defaultServices], + }); + + const { body, status } = await request(server).get( + '/api/my-plugin/.backstage/actions/v1/actions', + ); + + expect(status).toBe(200); + + expect(body).toMatchObject({ + actions: [ + { + name: 'test', + title: 'Test', + description: 'Test', + attributes: { + destructive: true, + idempotent: true, + readOnly: true, + }, + }, + ], + }); + }); + it('should forces registration of input and output schema as objects', async () => { const pluginSubject = createBackendPlugin({ pluginId: 'my-plugin', diff --git a/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts b/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts index 44df71f41b..6c0936f85c 100644 --- a/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts +++ b/packages/backend-plugin-api/src/services/definitions/ActionsRegistryService.ts @@ -40,6 +40,11 @@ export type ActionsRegistryActionOptions< input: (zod: typeof z) => TInputSchema; output: (zod: typeof z) => TOutputSchema; }; + attributes?: { + destructive?: boolean; + idempotent?: boolean; + readOnly?: boolean; + }; action: ( context: ActionsRegistryActionContext, ) => Promise< diff --git a/packages/backend-plugin-api/src/services/definitions/ActionsService.ts b/packages/backend-plugin-api/src/services/definitions/ActionsService.ts index 69b13afaaa..d595008a42 100644 --- a/packages/backend-plugin-api/src/services/definitions/ActionsService.ts +++ b/packages/backend-plugin-api/src/services/definitions/ActionsService.ts @@ -29,6 +29,11 @@ export type ActionsServiceAction = { input: JSONSchema7; output: JSONSchema7; }; + attributes: { + readOnly: boolean; + destructive: boolean; + idempotent: boolean; + }; }; /** diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index d58720723d..1d36f77026 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -59,12 +59,14 @@ "@keyv/valkey": "^1.0.1", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", + "@types/json-schema": "^7.0.6", "@types/keyv": "^4.2.0", "@types/qs": "^6.9.6", "better-sqlite3": "^11.0.0", "cookie": "^0.7.0", "express": "^4.17.1", "fs-extra": "^11.0.0", + "json-schema": "^0.4.0", "keyv": "^5.2.1", "knex": "^3.0.0", "mysql2": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index b09bfdd787..8fde916ae8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3784,6 +3784,7 @@ __metadata: "@types/express": "npm:^4.17.6" "@types/express-serve-static-core": "npm:^4.17.5" "@types/jest": "npm:*" + "@types/json-schema": "npm:^7.0.6" "@types/keyv": "npm:^4.2.0" "@types/qs": "npm:^6.9.6" "@types/supertest": "npm:^2.0.8" @@ -3791,6 +3792,7 @@ __metadata: cookie: "npm:^0.7.0" express: "npm:^4.17.1" fs-extra: "npm:^11.0.0" + json-schema: "npm:^0.4.0" keyv: "npm:^5.2.1" knex: "npm:^3.0.0" mysql2: "npm:^3.0.0" From 52e0626d94331c5e3621d7ddd118178733bcb577 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 13 Jun 2025 10:27:44 +0200 Subject: [PATCH 071/301] chore: updated API reports Signed-off-by: benjdlambert --- packages/backend-plugin-api/report.api.md | 10 ++++++++++ packages/backend-test-utils/package.json | 2 -- yarn.lock | 2 -- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/backend-plugin-api/report.api.md b/packages/backend-plugin-api/report.api.md index 3b24fcb012..d5030adb9c 100644 --- a/packages/backend-plugin-api/report.api.md +++ b/packages/backend-plugin-api/report.api.md @@ -48,6 +48,11 @@ export type ActionsRegistryActionOptions< input: (zod: typeof z) => TInputSchema; output: (zod: typeof z) => TOutputSchema; }; + attributes?: { + destructive?: boolean; + idempotent?: boolean; + readOnly?: boolean; + }; action: (context: ActionsRegistryActionContext) => Promise< z.infer extends void ? void @@ -94,6 +99,11 @@ export type ActionsServiceAction = { input: JSONSchema7; output: JSONSchema7; }; + attributes: { + readOnly: boolean; + destructive: boolean; + idempotent: boolean; + }; }; // @public diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 1d36f77026..d58720723d 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -59,14 +59,12 @@ "@keyv/valkey": "^1.0.1", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", - "@types/json-schema": "^7.0.6", "@types/keyv": "^4.2.0", "@types/qs": "^6.9.6", "better-sqlite3": "^11.0.0", "cookie": "^0.7.0", "express": "^4.17.1", "fs-extra": "^11.0.0", - "json-schema": "^0.4.0", "keyv": "^5.2.1", "knex": "^3.0.0", "mysql2": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index 8fde916ae8..b09bfdd787 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3784,7 +3784,6 @@ __metadata: "@types/express": "npm:^4.17.6" "@types/express-serve-static-core": "npm:^4.17.5" "@types/jest": "npm:*" - "@types/json-schema": "npm:^7.0.6" "@types/keyv": "npm:^4.2.0" "@types/qs": "npm:^6.9.6" "@types/supertest": "npm:^2.0.8" @@ -3792,7 +3791,6 @@ __metadata: cookie: "npm:^0.7.0" express: "npm:^4.17.1" fs-extra: "npm:^11.0.0" - json-schema: "npm:^0.4.0" keyv: "npm:^5.2.1" knex: "npm:^3.0.0" mysql2: "npm:^3.0.0" From 2c77bd52d7fb254e9aa00f1bc59414c4406f1f63 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 13 Jun 2025 10:30:49 +0200 Subject: [PATCH 072/301] scaffolder: track steps Signed-off-by: Vincenzo Scamporlino --- .../src/next/components/Workflow/Workflow.tsx | 5 +- .../next/hooks/useFilteredSchemaProperties.ts | 85 ++++++++++--------- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx index 95e2647b5a..527009037d 100644 --- a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx +++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx @@ -99,9 +99,12 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => { analytics.captureEvent('create', 'Task has been created', { value: minutesSaved, + attributes: { + templateSteps: sortedManifest?.steps?.length ?? 0, + }, }); }, - [onCreate, analytics, minutesSaved], + [onCreate, analytics, minutesSaved, sortedManifest], ); useEffect(() => { diff --git a/plugins/scaffolder-react/src/next/hooks/useFilteredSchemaProperties.ts b/plugins/scaffolder-react/src/next/hooks/useFilteredSchemaProperties.ts index be6f581f87..cac696a745 100644 --- a/plugins/scaffolder-react/src/next/hooks/useFilteredSchemaProperties.ts +++ b/plugins/scaffolder-react/src/next/hooks/useFilteredSchemaProperties.ts @@ -16,6 +16,7 @@ import cloneDeep from 'lodash/cloneDeep'; import { useApi, featureFlagsApiRef } from '@backstage/core-plugin-api'; import { TemplateParameterSchema } from '@backstage/plugin-scaffolder-react'; +import { useMemo } from 'react'; /** * Returns manifest of software templates with steps without a featureFlag tag. @@ -28,49 +29,51 @@ export const useFilteredSchemaProperties = ( const featureFlagKey = 'backstage:featureFlag'; const featureFlagApi = useApi(featureFlagsApiRef); - if (!manifest) { - return undefined; - } - - const filteredSteps = manifest?.steps - .filter(step => { - const featureFlag = step.schema[featureFlagKey]; - return ( - typeof featureFlag !== 'string' || featureFlagApi.isActive(featureFlag) - ); - }) - .map(step => { - const filteredStep = cloneDeep(step); - const removedPropertyKeys: Array = []; - if (filteredStep.schema.properties) { - filteredStep.schema.properties = Object.fromEntries( - Object.entries(filteredStep.schema.properties).filter( - ([key, value]) => { - if (value[featureFlagKey]) { - if (featureFlagApi.isActive(value[featureFlagKey])) { - return true; - } - - removedPropertyKeys.push(key); - return false; - } - return true; - }, - ), + return useMemo(() => { + if (!manifest) { + return undefined; + } + const filteredSteps = manifest?.steps + .filter(step => { + const featureFlag = step.schema[featureFlagKey]; + return ( + typeof featureFlag !== 'string' || + featureFlagApi.isActive(featureFlag) ); + }) + .map(step => { + const filteredStep = cloneDeep(step); + const removedPropertyKeys: Array = []; + if (filteredStep.schema.properties) { + filteredStep.schema.properties = Object.fromEntries( + Object.entries(filteredStep.schema.properties).filter( + ([key, value]) => { + if (value[featureFlagKey]) { + if (featureFlagApi.isActive(value[featureFlagKey])) { + return true; + } - // remove the feature flag property key from required if they are not active - filteredStep.schema.required = Array.isArray( - filteredStep.schema.required, - ) - ? filteredStep.schema.required?.filter( - r => !removedPropertyKeys.includes(r as string), - ) - : filteredStep.schema.required; - } + removedPropertyKeys.push(key); + return false; + } + return true; + }, + ), + ); - return filteredStep; - }); + // remove the feature flag property key from required if they are not active + filteredStep.schema.required = Array.isArray( + filteredStep.schema.required, + ) + ? filteredStep.schema.required?.filter( + r => !removedPropertyKeys.includes(r as string), + ) + : filteredStep.schema.required; + } - return { ...manifest, steps: filteredSteps }; + return filteredStep; + }); + + return { ...manifest, steps: filteredSteps }; + }, [manifest, featureFlagApi]); }; From ed6f1538c08e7bb7dca2ec3f0665a1e51b83725d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 13 Jun 2025 10:36:46 +0200 Subject: [PATCH 073/301] corrected config flag name in docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- docs/backend-system/core-services/root-http-router.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/backend-system/core-services/root-http-router.md b/docs/backend-system/core-services/root-http-router.md index a60d788ae8..5f745966db 100644 --- a/docs/backend-system/core-services/root-http-router.md +++ b/docs/backend-system/core-services/root-http-router.md @@ -60,7 +60,7 @@ backend: # - A string in the format of '1d', '2 seconds' etc. as supported by the `ms` library. # - A standard ISO formatted duration string, e.g. 'P2DT6H' or 'PT1M'. # - An object with individual units (in plural) as keys, e.g. `{ days: 2, hours: 6 }`. - serverShutdownTimeout: { seconds: 20 } + serverShutdownDelay: { seconds: 20 } ``` ### Via Code From 2977da8602e5b384e85a93b9819fb88c082b59ee Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 13 Jun 2025 10:47:13 +0200 Subject: [PATCH 074/301] scaffolder: fix test Signed-off-by: Vincenzo Scamporlino --- .../TemplateWizardPage.test.tsx | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx index d3a02931de..6836259dd8 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx @@ -21,7 +21,7 @@ import { renderInTestApp, TestApiRegistry, } from '@backstage/test-utils'; -import { act, fireEvent } from '@testing-library/react'; +import { fireEvent, waitFor } from '@testing-library/react'; import { ScaffolderApi, scaffolderApiRef, @@ -127,14 +127,10 @@ describe('TemplateWizardPage', () => { }); // Go to the final page - await act(async () => { - fireEvent.click(await findByRole('button', { name: 'Review' })); - }); + fireEvent.click(await findByRole('button', { name: 'Review' })); // Create the software - await act(async () => { - fireEvent.click(await findByRole('button', { name: 'Create' })); - }); + fireEvent.click(await findByRole('button', { name: 'Create' })); // The "Next Step" button should have fired an event expect(analyticsApi.captureEvent).toHaveBeenCalledWith( @@ -148,15 +144,20 @@ describe('TemplateWizardPage', () => { ); // And the "Create" button should have fired an event - expect(analyticsApi.captureEvent).toHaveBeenCalledWith( - expect.objectContaining({ - action: 'create', - subject: 'expected-name', - context: expect.objectContaining({ - entityRef: 'template:default/test', + await waitFor(() => + expect(analyticsApi.captureEvent).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'create', + subject: 'Task has been created', + attributes: { + templateSteps: 1, + }, + context: expect.objectContaining({ + entityRef: 'template:default/test', + }), + value: 120, }), - value: 120, - }), + ), ); }); From 5f74715c81b9a92447e737cb10d73ae6b0930239 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 13 Jun 2025 12:18:38 +0200 Subject: [PATCH 075/301] scaffolder: remove unused import Signed-off-by: Vincenzo Scamporlino --- plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx index e4ca230c36..e1a5f37ddc 100644 --- a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx @@ -21,7 +21,6 @@ import MenuItem from '@material-ui/core/MenuItem'; import MenuList from '@material-ui/core/MenuList'; import Popover from '@material-ui/core/Popover'; import { makeStyles, Theme, useTheme } from '@material-ui/core/styles'; -import { useAsync } from '@react-hookz/web'; import Cancel from '@material-ui/icons/Cancel'; import Repeat from '@material-ui/icons/Repeat'; import Replay from '@material-ui/icons/Replay'; From 015af26f82a3be19271621bab95c67e65df51801 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 13 Jun 2025 12:25:57 +0200 Subject: [PATCH 076/301] scaffolder: scaffolder-react changeset Signed-off-by: Vincenzo Scamporlino --- .changeset/dull-cloths-act.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/dull-cloths-act.md b/.changeset/dull-cloths-act.md index 82ba469c7d..e49ee5a289 100644 --- a/.changeset/dull-cloths-act.md +++ b/.changeset/dull-cloths-act.md @@ -1,4 +1,5 @@ --- +'@backstage/plugin-scaffolder-react': minor '@backstage/plugin-scaffolder': minor --- From 90dc85d9d49c7dcc93f5987f58190623732fae5c Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 13 Jun 2025 13:05:13 +0200 Subject: [PATCH 077/301] scaffolder: track edit link Signed-off-by: Vincenzo Scamporlino --- .../TemplateWizardPageContextMenu.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPageContextMenu.tsx b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPageContextMenu.tsx index 9a912f265f..72fa397a9e 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPageContextMenu.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPageContextMenu.tsx @@ -26,6 +26,7 @@ import MoreVert from '@material-ui/icons/MoreVert'; import { SyntheticEvent, useState } from 'react'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { scaffolderTranslationRef } from '../../../translation'; +import { Link } from '@backstage/core-components'; const useStyles = makeStyles(theme => ({ button: { @@ -82,16 +83,18 @@ export function TemplateWizardPageContextMenu( transformOrigin={{ vertical: 'top', horizontal: 'right' }} > - window.open(editUrl, '_blank')}> - - - - - + + + + + + + + From 7f12e4d52e4f5a514f83e370f75681a5cb29a0e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 12:24:16 +0000 Subject: [PATCH 078/301] chore(deps): bump brace-expansion from 1.1.11 to 1.1.12 in /microsite Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12. - [Release notes](https://github.com/juliangruber/brace-expansion/releases) - [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12) --- updated-dependencies: - dependency-name: brace-expansion dependency-version: 1.1.12 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- microsite/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microsite/yarn.lock b/microsite/yarn.lock index e6454c4e82..a200d0f9da 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -4430,12 +4430,12 @@ __metadata: linkType: hard "brace-expansion@npm:^1.1.7": - version: 1.1.11 - resolution: "brace-expansion@npm:1.1.11" + version: 1.1.12 + resolution: "brace-expansion@npm:1.1.12" dependencies: balanced-match: "npm:^1.0.0" concat-map: "npm:0.0.1" - checksum: 10/faf34a7bb0c3fcf4b59c7808bc5d2a96a40988addf2e7e09dfbb67a2251800e0d14cd2bfc1aa79174f2f5095c54ff27f46fb1289fe2d77dac755b5eb3434cc07 + checksum: 10/12cb6d6310629e3048cadb003e1aca4d8c9bb5c67c3c321bafdd7e7a50155de081f78ea3e0ed92ecc75a9015e784f301efc8132383132f4f7904ad1ac529c562 languageName: node linkType: hard From 5b2a34ab5881d8330f0df0efaa10f3c3a801a374 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 13 Jun 2025 15:38:44 +0200 Subject: [PATCH 079/301] scaffolder: adjust link component Signed-off-by: Vincenzo Scamporlino --- .../TemplateWizardPageContextMenu.tsx | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPageContextMenu.tsx b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPageContextMenu.tsx index 72fa397a9e..c15795de2c 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPageContextMenu.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPageContextMenu.tsx @@ -83,18 +83,16 @@ export function TemplateWizardPageContextMenu( transformOrigin={{ vertical: 'top', horizontal: 'right' }} > - - - - - - - - + + + + + + From bd312766b0cf09ea77eb2eb1e6ecb2cf9a7c1a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 13 Jun 2025 16:31:50 +0200 Subject: [PATCH 080/301] align versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 34481a0baf..a189985bf4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11456,12 +11456,12 @@ __metadata: languageName: node linkType: hard -"@keyv/serialize@npm:*, @keyv/serialize@npm:^1.0.2": - version: 1.0.2 - resolution: "@keyv/serialize@npm:1.0.2" +"@keyv/serialize@npm:*, @keyv/serialize@npm:^1.0.3": + version: 1.0.3 + resolution: "@keyv/serialize@npm:1.0.3" dependencies: buffer: "npm:^6.0.3" - checksum: 10/6a42a5778a6b4542f6903ba7e6a17c5bd116441798d75c95fba9908c76c7606db527fad710b5c54abc6175e49b1bbaaafe3b836ad4b91e1af701394134f1d504 + checksum: 10/d6a9194dd781bc26cc4d55f392d843810c1fdc0da81e69203e633cb289fc0a8edc8bc6466f66c4cbb55da0a5b405e89f14a68b48d6e73919ae82f8249fb5e444 languageName: node linkType: hard @@ -35233,11 +35233,11 @@ __metadata: linkType: hard "keyv@npm:*, keyv@npm:^5.2.1": - version: 5.2.3 - resolution: "keyv@npm:5.2.3" + version: 5.3.4 + resolution: "keyv@npm:5.3.4" dependencies: - "@keyv/serialize": "npm:^1.0.2" - checksum: 10/47b4e9deb33e6a80e5ea79f3022ed3a14bc9fe553b7527ffff0a70b10c7a6c1a5d7e49b9bcfdbd8e8b9fb4632d68baa19d09e82628bcf853103e750e56d49a9e + "@keyv/serialize": "npm:^1.0.3" + checksum: 10/3e294eb1168af78ad3430d0cc47b6839fd3e70593238d35226a0a1e4094abe397ea378b7bce35cfcb314e55b5d1b4fcdae3c19bee5610e78d283b3cb5b279c8b languageName: node linkType: hard From c0b3cb66e6d8f1b3901ae1d353d56c743335141c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 14:41:50 +0000 Subject: [PATCH 081/301] chore(deps): update actions/download-artifact digest to d3f86a1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/deploy_microsite.yml | 4 ++-- .github/workflows/verify_microsite.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index 67fcc5d709..a5e89d6103 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -321,12 +321,12 @@ jobs: name: storybook path: microsite/build/storybook - - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: stable-reference-beta path: microsite/build/api/stable/ - - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: next-reference-beta path: microsite/build/api/next/ diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index df68cff2cf..38900074d6 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -325,13 +325,13 @@ jobs: working-directory: microsite - name: download stable reference (beta) - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: stable-reference-beta path: api/stable/ - name: download next reference (beta) - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: next-reference-beta path: api/next/ From 58d99f17e9f7c123dddf5076ed6e9921ee2e9f36 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 14:50:55 +0000 Subject: [PATCH 082/301] chore(deps): update chromaui/action digest to 1cfa065 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/verify_storybook.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml index 3cc846a706..6c1ceb4c52 100644 --- a/.github/workflows/verify_storybook.yml +++ b/.github/workflows/verify_storybook.yml @@ -54,7 +54,7 @@ jobs: - run: yarn --cwd packages/canon build-storybook - - uses: chromaui/action@e8cc4c31775280b175a3c440076c00d19a9014d7 # v11 + - uses: chromaui/action@1cfa065cbdab28f6ca3afaeb3d761383076a35aa # v11 with: token: ${{ secrets.GITHUB_TOKEN }} # projectToken intentionally shared to allow collaborators to run Chromatic on forks From dd801adb6c53f06e11bae489d891d8bbdbfc6448 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 15:07:58 +0000 Subject: [PATCH 083/301] chore(deps): update dependency @google-cloud/cloud-sql-connector to v1.8.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 37ff2585cb..e6236a6ef7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10247,14 +10247,14 @@ __metadata: linkType: hard "@google-cloud/cloud-sql-connector@npm:^1.4.0": - version: 1.8.0 - resolution: "@google-cloud/cloud-sql-connector@npm:1.8.0" + version: 1.8.1 + resolution: "@google-cloud/cloud-sql-connector@npm:1.8.1" dependencies: - "@googleapis/sqladmin": "npm:^28.0.0" + "@googleapis/sqladmin": "npm:^29.0.0" gaxios: "npm:^6.1.1" google-auth-library: "npm:^9.2.0" p-throttle: "npm:^7.0.0" - checksum: 10/a2cd85beb822a37e1eaabd556e2f540f89c41a1614e520b8c120b8107d7edb3c335dcbf62fc34dd8973ae091a2a06882780ae6510c45f2b702a59df4953ba427 + checksum: 10/e13e2e47993d74c770c73318a73047b017266f6f8102e26ebcf90c484fd78ba6141b0fc60f4eced55d438150655b8514423c19e3f30da73a1aaf1c77baadd0ab languageName: node linkType: hard @@ -10356,12 +10356,12 @@ __metadata: languageName: node linkType: hard -"@googleapis/sqladmin@npm:^28.0.0": - version: 28.0.0 - resolution: "@googleapis/sqladmin@npm:28.0.0" +"@googleapis/sqladmin@npm:^29.0.0": + version: 29.0.0 + resolution: "@googleapis/sqladmin@npm:29.0.0" dependencies: googleapis-common: "npm:^7.0.0" - checksum: 10/ee4074b63d45276e0513a75b461bbf3d39abcb894e5eb660f0f28a9d0665c6638079958f04cb171767c69bcbb324ecdd2033199e6ecff61bf10d6a09adecdd16 + checksum: 10/3bf9eea2e6333d394da88404172aeed5ee5fca5fe2ebbba84d427990aab0083281a4cb948c023817ae6dbb618a1e43a296176c7703badd66a60bde695d7c5b3f languageName: node linkType: hard From b56ce0c46d8134c6dcb7ba480fccd873cc446966 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 15:08:40 +0000 Subject: [PATCH 084/301] chore(deps): update dependency @rspack/core to v1.3.15 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 159 +++++++++++++++++++++++++++--------------------------- 1 file changed, 79 insertions(+), 80 deletions(-) diff --git a/yarn.lock b/yarn.lock index 37ff2585cb..ec589a0d97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12125,10 +12125,10 @@ __metadata: languageName: node linkType: hard -"@module-federation/error-codes@npm:0.13.1": - version: 0.13.1 - resolution: "@module-federation/error-codes@npm:0.13.1" - checksum: 10/fa9bbc0936ad9d037116c05f741bfb0ff741c3e2eaa682fa0132f4825d66ef0c63da0c20ec931ca16c8e7b660c1fdc60101a24ccab8951f19e07fba642aa1c6a +"@module-federation/error-codes@npm:0.14.3": + version: 0.14.3 + resolution: "@module-federation/error-codes@npm:0.14.3" + checksum: 10/3b6c25940d966648c2531b80edabafd36eddccdd8f68ba6702686a1e4fd2dbaa4a34ef6fb22a4d8a615b907e907e233dd0fd312c49a7369a49669bc1ab414a19 languageName: node linkType: hard @@ -12196,13 +12196,13 @@ __metadata: languageName: node linkType: hard -"@module-federation/runtime-core@npm:0.13.1": - version: 0.13.1 - resolution: "@module-federation/runtime-core@npm:0.13.1" +"@module-federation/runtime-core@npm:0.14.3": + version: 0.14.3 + resolution: "@module-federation/runtime-core@npm:0.14.3" dependencies: - "@module-federation/error-codes": "npm:0.13.1" - "@module-federation/sdk": "npm:0.13.1" - checksum: 10/81b52eb7866f623115922af325bcc1dba6c85f95216dfa2cbf2f777b37ec11ba856846892a4a5ab446105a18837c495b0a05cb5d574f7d2e50f340d1e57413f4 + "@module-federation/error-codes": "npm:0.14.3" + "@module-federation/sdk": "npm:0.14.3" + checksum: 10/7e974a8c051334929e28231fe1552489258ab1b545f0f5ad59644b0288765642f3e908b48c3cc59bc74a326f4f92325788b1a898f89d8f795f8036aa812b2bb6 languageName: node linkType: hard @@ -12216,13 +12216,13 @@ __metadata: languageName: node linkType: hard -"@module-federation/runtime-tools@npm:0.13.1": - version: 0.13.1 - resolution: "@module-federation/runtime-tools@npm:0.13.1" +"@module-federation/runtime-tools@npm:0.14.3": + version: 0.14.3 + resolution: "@module-federation/runtime-tools@npm:0.14.3" dependencies: - "@module-federation/runtime": "npm:0.13.1" - "@module-federation/webpack-bundler-runtime": "npm:0.13.1" - checksum: 10/821f32979ef2235cd043b3ef561fd63f7554211bd86d3af7e17a57dd5c709c6ee2ae57935222318694732115d7771c122cf51fb9f71f879269d452bc5ab0a8d2 + "@module-federation/runtime": "npm:0.14.3" + "@module-federation/webpack-bundler-runtime": "npm:0.14.3" + checksum: 10/09bc9d482ead7d38d5f9f6e1d45c85f257833302315447b2a17c26dc44046f8aa0563ffdb2f5addbddbf5f8f972c13dc8e80932a8a34780738232332bbdf5ce9 languageName: node linkType: hard @@ -12236,14 +12236,14 @@ __metadata: languageName: node linkType: hard -"@module-federation/runtime@npm:0.13.1": - version: 0.13.1 - resolution: "@module-federation/runtime@npm:0.13.1" +"@module-federation/runtime@npm:0.14.3": + version: 0.14.3 + resolution: "@module-federation/runtime@npm:0.14.3" dependencies: - "@module-federation/error-codes": "npm:0.13.1" - "@module-federation/runtime-core": "npm:0.13.1" - "@module-federation/sdk": "npm:0.13.1" - checksum: 10/2536ea17d0704bf4d093fe5791720e61fa3f4f4e83b6927b33f1d2ee132bf0cc9232e960cffefe32a6c79f3aedd864738b1be79b3790ad0c9dbd924f15ef2841 + "@module-federation/error-codes": "npm:0.14.3" + "@module-federation/runtime-core": "npm:0.14.3" + "@module-federation/sdk": "npm:0.14.3" + checksum: 10/b7009953998f2624f5ece3da4174d8427d30b2af3b108154f0fa2365aa6c08d167e23b9771508f2c6038a397f21e0e26b93dd09f3d4b7b90eac6b0ee7f1b9ee2 languageName: node linkType: hard @@ -12258,10 +12258,10 @@ __metadata: languageName: node linkType: hard -"@module-federation/sdk@npm:0.13.1": - version: 0.13.1 - resolution: "@module-federation/sdk@npm:0.13.1" - checksum: 10/7cfc98a6ee09aa338a9d76df146d064b5f4c6163b985daf3aa641e3f0e52d7cc40116b449cd1265b1333a66eeec31ce8bb40e1944f3ac62797d87323ce0e91ec +"@module-federation/sdk@npm:0.14.3": + version: 0.14.3 + resolution: "@module-federation/sdk@npm:0.14.3" + checksum: 10/f359fa15ecebe6e1e80e8fbd4d4969ae2d2d92f0d3e338443c45fba4772327368e086b108adcec585763d974e7462227a9e51b0531826292e2249f2612d0f822 languageName: node linkType: hard @@ -12283,13 +12283,13 @@ __metadata: languageName: node linkType: hard -"@module-federation/webpack-bundler-runtime@npm:0.13.1": - version: 0.13.1 - resolution: "@module-federation/webpack-bundler-runtime@npm:0.13.1" +"@module-federation/webpack-bundler-runtime@npm:0.14.3": + version: 0.14.3 + resolution: "@module-federation/webpack-bundler-runtime@npm:0.14.3" dependencies: - "@module-federation/runtime": "npm:0.13.1" - "@module-federation/sdk": "npm:0.13.1" - checksum: 10/bebccaa4411c00355bb399e2dddb946586967642b90da4d4436554093fecb614fdff6c65dc73cef944c7344f169d2c0ecc22ed0b5b899e21d4535507806145e0 + "@module-federation/runtime": "npm:0.14.3" + "@module-federation/sdk": "npm:0.14.3" + checksum: 10/016d1c51e82e230b2d33f67aba7c9ff2cd9f939306a48dfa3aeb0f3939dd82f797e06b478c009459d7516fadbf43024cd0fc13618555a64c470192d9b87ab926 languageName: node linkType: hard @@ -16354,82 +16354,82 @@ __metadata: languageName: node linkType: hard -"@rspack/binding-darwin-arm64@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-darwin-arm64@npm:1.3.10" +"@rspack/binding-darwin-arm64@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-darwin-arm64@npm:1.3.15" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rspack/binding-darwin-x64@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-darwin-x64@npm:1.3.10" +"@rspack/binding-darwin-x64@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-darwin-x64@npm:1.3.15" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rspack/binding-linux-arm64-gnu@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-linux-arm64-gnu@npm:1.3.10" +"@rspack/binding-linux-arm64-gnu@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-linux-arm64-gnu@npm:1.3.15" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rspack/binding-linux-arm64-musl@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-linux-arm64-musl@npm:1.3.10" +"@rspack/binding-linux-arm64-musl@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-linux-arm64-musl@npm:1.3.15" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rspack/binding-linux-x64-gnu@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-linux-x64-gnu@npm:1.3.10" +"@rspack/binding-linux-x64-gnu@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-linux-x64-gnu@npm:1.3.15" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rspack/binding-linux-x64-musl@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-linux-x64-musl@npm:1.3.10" +"@rspack/binding-linux-x64-musl@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-linux-x64-musl@npm:1.3.15" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rspack/binding-win32-arm64-msvc@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-win32-arm64-msvc@npm:1.3.10" +"@rspack/binding-win32-arm64-msvc@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-win32-arm64-msvc@npm:1.3.15" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rspack/binding-win32-ia32-msvc@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-win32-ia32-msvc@npm:1.3.10" +"@rspack/binding-win32-ia32-msvc@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-win32-ia32-msvc@npm:1.3.15" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rspack/binding-win32-x64-msvc@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding-win32-x64-msvc@npm:1.3.10" +"@rspack/binding-win32-x64-msvc@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding-win32-x64-msvc@npm:1.3.15" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rspack/binding@npm:1.3.10": - version: 1.3.10 - resolution: "@rspack/binding@npm:1.3.10" +"@rspack/binding@npm:1.3.15": + version: 1.3.15 + resolution: "@rspack/binding@npm:1.3.15" dependencies: - "@rspack/binding-darwin-arm64": "npm:1.3.10" - "@rspack/binding-darwin-x64": "npm:1.3.10" - "@rspack/binding-linux-arm64-gnu": "npm:1.3.10" - "@rspack/binding-linux-arm64-musl": "npm:1.3.10" - "@rspack/binding-linux-x64-gnu": "npm:1.3.10" - "@rspack/binding-linux-x64-musl": "npm:1.3.10" - "@rspack/binding-win32-arm64-msvc": "npm:1.3.10" - "@rspack/binding-win32-ia32-msvc": "npm:1.3.10" - "@rspack/binding-win32-x64-msvc": "npm:1.3.10" + "@rspack/binding-darwin-arm64": "npm:1.3.15" + "@rspack/binding-darwin-x64": "npm:1.3.15" + "@rspack/binding-linux-arm64-gnu": "npm:1.3.15" + "@rspack/binding-linux-arm64-musl": "npm:1.3.15" + "@rspack/binding-linux-x64-gnu": "npm:1.3.15" + "@rspack/binding-linux-x64-musl": "npm:1.3.15" + "@rspack/binding-win32-arm64-msvc": "npm:1.3.15" + "@rspack/binding-win32-ia32-msvc": "npm:1.3.15" + "@rspack/binding-win32-x64-msvc": "npm:1.3.15" dependenciesMeta: "@rspack/binding-darwin-arm64": optional: true @@ -16449,24 +16449,23 @@ __metadata: optional: true "@rspack/binding-win32-x64-msvc": optional: true - checksum: 10/10328e405c6708f7d1eea7e8f83d7f4453bcc3e4ca77b99eb29819d9f66c51e85b8cf46daab797bea59fdafc266dab6091deb9b5e2ec3007482da11ab10dc62c + checksum: 10/d4918ee41eecbc6d27739d21b674fe64ea7d45433e0f038618475a7cfd91a68d470d6e05d68e109ae917b88c541059c368b7734b2dca3791d7be001a5804030e languageName: node linkType: hard "@rspack/core@npm:^1.3.9": - version: 1.3.10 - resolution: "@rspack/core@npm:1.3.10" + version: 1.3.15 + resolution: "@rspack/core@npm:1.3.15" dependencies: - "@module-federation/runtime-tools": "npm:0.13.1" - "@rspack/binding": "npm:1.3.10" + "@module-federation/runtime-tools": "npm:0.14.3" + "@rspack/binding": "npm:1.3.15" "@rspack/lite-tapable": "npm:1.0.1" - caniuse-lite: "npm:^1.0.30001717" peerDependencies: "@swc/helpers": ">=0.5.1" peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/5471ced4f461936c723199006182ce9fc54a5840aa7ba46f56f137d46b1839b994a9f6e4539e1376eaaf2956cbbbe5a7846d06ab61246e38711a805f7dd56df4 + checksum: 10/9708d4b5112f9a1c23f691e544e9b690cdc7275145772932e6ea0683b6c71d3f304c0f21513b23a01f00cebe8607adc54c23e3ed4ac9dd566e8d9d02b35f5f4e languageName: node linkType: hard @@ -25357,7 +25356,7 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001669, caniuse-lite@npm:^1.0.30001717": +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001669": version: 1.0.30001717 resolution: "caniuse-lite@npm:1.0.30001717" checksum: 10/e47dfd8707ea305baa177f3d3d531df614f5a9ac6335363fc8f86f0be4caf79f5734f3f68b601fee4edd9d79f1e5ffc0931466bb894bf955ed6b1dd5a1c34b1d From a9ac7a1642cf1338f91ec0ecac6b05995c6500cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 18:08:15 +0000 Subject: [PATCH 085/301] chore(deps): update dependency @rspack/dev-server to v1.1.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 63 +++++++------------------------------------------------ 1 file changed, 8 insertions(+), 55 deletions(-) diff --git a/yarn.lock b/yarn.lock index a0ddaf36a6..58e02edf6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16470,20 +16470,17 @@ __metadata: linkType: hard "@rspack/dev-server@npm:^1.1.1": - version: 1.1.1 - resolution: "@rspack/dev-server@npm:1.1.1" + version: 1.1.3 + resolution: "@rspack/dev-server@npm:1.1.3" dependencies: chokidar: "npm:^3.6.0" - express: "npm:^4.21.2" - http-proxy-middleware: "npm:^2.0.7" - mime-types: "npm:^2.1.35" + http-proxy-middleware: "npm:^2.0.9" p-retry: "npm:^6.2.0" - webpack-dev-middleware: "npm:^7.4.2" - webpack-dev-server: "npm:5.2.0" + webpack-dev-server: "npm:5.2.2" ws: "npm:^8.18.0" peerDependencies: "@rspack/core": "*" - checksum: 10/a712c1ed6819da26984d106e7fdede3ed720ef177bf8fa976e91eb95988b862b306c354829486dce8eabde420cb5a96cb25ecf397c9532f87f3b913e9a9da40c + checksum: 10/31cef80a602acf9468a94c31b1f09239cae9c47cf0ed25c6bcddd057bbaff5a220a1dead068a255d35d0addfea21d81a574069d63ea258c2807d84a02d4fe966 languageName: node linkType: hard @@ -32338,7 +32335,7 @@ __metadata: languageName: node linkType: hard -"http-proxy-middleware@npm:^2.0.0, http-proxy-middleware@npm:^2.0.6, http-proxy-middleware@npm:^2.0.7, http-proxy-middleware@npm:^2.0.9": +"http-proxy-middleware@npm:^2.0.0, http-proxy-middleware@npm:^2.0.6, http-proxy-middleware@npm:^2.0.9": version: 2.0.9 resolution: "http-proxy-middleware@npm:2.0.9" dependencies: @@ -37499,7 +37496,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:2.1.35, mime-types@npm:^2.1.12, mime-types@npm:^2.1.18, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:^2.1.35, mime-types@npm:~2.1.17, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-types@npm:2.1.35, mime-types@npm:^2.1.12, mime-types@npm:^2.1.18, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -48300,51 +48297,7 @@ __metadata: languageName: node linkType: hard -"webpack-dev-server@npm:5.2.0": - version: 5.2.0 - resolution: "webpack-dev-server@npm:5.2.0" - dependencies: - "@types/bonjour": "npm:^3.5.13" - "@types/connect-history-api-fallback": "npm:^1.5.4" - "@types/express": "npm:^4.17.21" - "@types/serve-index": "npm:^1.9.4" - "@types/serve-static": "npm:^1.15.5" - "@types/sockjs": "npm:^0.3.36" - "@types/ws": "npm:^8.5.10" - ansi-html-community: "npm:^0.0.8" - bonjour-service: "npm:^1.2.1" - chokidar: "npm:^3.6.0" - colorette: "npm:^2.0.10" - compression: "npm:^1.7.4" - connect-history-api-fallback: "npm:^2.0.0" - express: "npm:^4.21.2" - graceful-fs: "npm:^4.2.6" - http-proxy-middleware: "npm:^2.0.7" - ipaddr.js: "npm:^2.1.0" - launch-editor: "npm:^2.6.1" - open: "npm:^10.0.3" - p-retry: "npm:^6.2.0" - schema-utils: "npm:^4.2.0" - selfsigned: "npm:^2.4.1" - serve-index: "npm:^1.9.1" - sockjs: "npm:^0.3.24" - spdy: "npm:^4.0.2" - webpack-dev-middleware: "npm:^7.4.2" - ws: "npm:^8.18.0" - peerDependencies: - webpack: ^5.0.0 - peerDependenciesMeta: - webpack: - optional: true - webpack-cli: - optional: true - bin: - webpack-dev-server: bin/webpack-dev-server.js - checksum: 10/f93ca46b037e547a9db157db72ef98ab177659ad13a6e63302d87bd77b32e524dd7133f1ad18f5a51ec68712911c59be8d4e06aa7bcbe6f56a9e9ce3774cf7f6 - languageName: node - linkType: hard - -"webpack-dev-server@npm:^5.0.0": +"webpack-dev-server@npm:5.2.2, webpack-dev-server@npm:^5.0.0": version: 5.2.2 resolution: "webpack-dev-server@npm:5.2.2" dependencies: From 96ed92ec2057a02dc6baacedc19ff440ae7dbab1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 18:09:10 +0000 Subject: [PATCH 086/301] chore(deps): update dependency @types/cookie-parser to v1.4.9 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a0ddaf36a6..22700fe2fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19933,11 +19933,11 @@ __metadata: linkType: hard "@types/cookie-parser@npm:^1.4.2": - version: 1.4.8 - resolution: "@types/cookie-parser@npm:1.4.8" + version: 1.4.9 + resolution: "@types/cookie-parser@npm:1.4.9" peerDependencies: "@types/express": "*" - checksum: 10/b26ace5560adce1a746c059a75f18227ee3e3614a49c70dd29b8af204696079ced22eaa9d6cb084e3433ec433bff845cb47ffb890ae9af96a4aab2ff85e40da7 + checksum: 10/6192a4899b5412a4c3be0f47158321aef73a4cd7e7a4f7b2a37e2e1045f11a21209681cb1bc5335f250ee2a6ce64d8a3fefb851181a98e6415d3716ef9ed1f62 languageName: node linkType: hard From 9a99735e8f78ed822f6a50752e1b14c3becc9963 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 13 Jun 2025 14:06:48 -0500 Subject: [PATCH 087/301] dev-tools - Fixed deprecated code Signed-off-by: Andre Wanlin --- .changeset/long-candies-greet.md | 5 +++++ plugins/devtools-backend/src/plugin.ts | 4 ++++ plugins/devtools-backend/src/service/router.ts | 7 ------- 3 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 .changeset/long-candies-greet.md diff --git a/.changeset/long-candies-greet.md b/.changeset/long-candies-greet.md new file mode 100644 index 0000000000..a59377d784 --- /dev/null +++ b/.changeset/long-candies-greet.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-devtools-backend': patch +--- + +Removed the usage of `permissionIntegrationRouter` in favor of using the new `coreServices.permissionsRegistry` diff --git a/plugins/devtools-backend/src/plugin.ts b/plugins/devtools-backend/src/plugin.ts index ee7bf369f5..d3fefda343 100644 --- a/plugins/devtools-backend/src/plugin.ts +++ b/plugins/devtools-backend/src/plugin.ts @@ -19,6 +19,7 @@ import { createBackendPlugin, } from '@backstage/backend-plugin-api'; import { createRouter } from './service/router'; +import { devToolsPermissions } from '@backstage/plugin-devtools-common'; /** * DevTools backend plugin @@ -36,6 +37,7 @@ export const devtoolsPlugin = createBackendPlugin({ httpRouter: coreServices.httpRouter, discovery: coreServices.discovery, httpAuth: coreServices.httpAuth, + permissionsRegistry: coreServices.permissionsRegistry, }, async init({ config, @@ -44,6 +46,7 @@ export const devtoolsPlugin = createBackendPlugin({ httpRouter, discovery, httpAuth, + permissionsRegistry, }) { httpRouter.use( await createRouter({ @@ -58,6 +61,7 @@ export const devtoolsPlugin = createBackendPlugin({ path: '/health', allow: 'unauthenticated', }); + permissionsRegistry.addPermissions(devToolsPermissions); }, }); }, diff --git a/plugins/devtools-backend/src/service/router.ts b/plugins/devtools-backend/src/service/router.ts index ad77495b36..caba2185fc 100644 --- a/plugins/devtools-backend/src/service/router.ts +++ b/plugins/devtools-backend/src/service/router.ts @@ -19,13 +19,11 @@ import { devToolsConfigReadPermission, devToolsExternalDependenciesReadPermission, devToolsInfoReadPermission, - devToolsPermissions, } from '@backstage/plugin-devtools-common'; import { DevToolsBackendApi } from '../api'; import { NotAllowedError } from '@backstage/errors'; import Router from 'express-promise-router'; import express from 'express'; -import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; import { DiscoveryService, HttpAuthService, @@ -59,11 +57,6 @@ export async function createRouter( const router = Router(); router.use(express.json()); - router.use( - createPermissionIntegrationRouter({ - permissions: devToolsPermissions, - }), - ); router.get('/health', (_req, res) => { res.status(200).json({ status: 'ok' }); From d82d69ee7b7e10f805048397e402f0b6a5082f9a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 19:42:32 +0000 Subject: [PATCH 088/301] chore(deps): update dependency @types/estree to v1.0.8 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 58e02edf6b..835615c938 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20123,9 +20123,9 @@ __metadata: linkType: hard "@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.5, @types/estree@npm:^1.0.6": - version: 1.0.7 - resolution: "@types/estree@npm:1.0.7" - checksum: 10/419c845ece767ad4b21171e6e5b63dabb2eb46b9c0d97361edcd9cabbf6a95fcadb91d89b5fa098d1336fa0b8fceaea82fca97a2ef3971f5c86e53031e157b21 + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10/25a4c16a6752538ffde2826c2cc0c6491d90e69cd6187bef4a006dd2c3c45469f049e643d7e516c515f21484dc3d48fd5c870be158a5beb72f5baf3dc43e4099 languageName: node linkType: hard From 8a0c1aad65d65d809a1436de5fa97cb8aedd2182 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 19:43:04 +0000 Subject: [PATCH 089/301] chore(deps): update dependency @types/express-session to v1.18.2 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 58e02edf6b..a2ee8d4125 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20175,11 +20175,11 @@ __metadata: linkType: hard "@types/express-session@npm:^1.17.2": - version: 1.18.1 - resolution: "@types/express-session@npm:1.18.1" + version: 1.18.2 + resolution: "@types/express-session@npm:1.18.2" dependencies: "@types/express": "npm:*" - checksum: 10/54316f13e789b758f9ca8209c54aff0bbcf50c9ea50a208d4ba6c3f95d21a60ddaa98bcf459bef02641b38e7244bfbc43aa724a8b607bb13c1c1bab4a139351a + checksum: 10/c1711976d089fd6da58c0262e2b21b71ee64d508ee0f9f1b90e7658b712fc59874e03f799757843d2300c1dd127813cd16ce806c208bc4975f0f7ed700f2db94 languageName: node linkType: hard From 59ce236113aede482b5984644bce9a5c55c7b380 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 20:35:07 +0000 Subject: [PATCH 090/301] chore(deps): update dependency @types/http-errors to v2.0.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 1cec195f30..dc2c9e4608 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20297,9 +20297,9 @@ __metadata: linkType: hard "@types/http-errors@npm:*, @types/http-errors@npm:^2.0.0": - version: 2.0.4 - resolution: "@types/http-errors@npm:2.0.4" - checksum: 10/1f3d7c3b32c7524811a45690881736b3ef741bf9849ae03d32ad1ab7062608454b150a4e7f1351f83d26a418b2d65af9bdc06198f1c079d75578282884c4e8e3 + version: 2.0.5 + resolution: "@types/http-errors@npm:2.0.5" + checksum: 10/a88da669366bc483e8f3b3eb3d34ada5f8d13eeeef851b1204d77e2ba6fc42aba4566d877cca5c095204a3f4349b87fe397e3e21288837bdd945dd514120755b languageName: node linkType: hard From de750eb7017c001c191a866356a67feef23c2ada Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 20:35:43 +0000 Subject: [PATCH 091/301] chore(deps): update dependency @types/lodash to v4.17.17 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 1cec195f30..94e76d9269 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20525,9 +20525,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.151": - version: 4.17.16 - resolution: "@types/lodash@npm:4.17.16" - checksum: 10/9a8bb7471a7521bd65d528e1bd14f79819a3eeb6f8a35a8a44649a7d773775c0813e93fd93bd32ccf350bb076c0bf02c6d47877c4625f526f6dd4d283c746aec + version: 4.17.17 + resolution: "@types/lodash@npm:4.17.17" + checksum: 10/496459a3cb1a0733bb60532de3899ad6297717af0b9b26ad6821154b2005fec86f29ccd47a2e6f4da4a8c7c818bb8ae73901144e8057ea86b7b02a3d7bb9d13f languageName: node linkType: hard From c49e335823207b1ea88f1ca77e9e8efaee1c95b1 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sat, 14 Jun 2025 07:08:19 +0100 Subject: [PATCH 092/301] Improve TextField Signed-off-by: Charles de Dreuille --- .changeset/sad-fans-sort.md | 5 ++ packages/canon/css/components.css | 55 +++++++++++++++---- packages/canon/css/styles.css | 55 +++++++++++++++---- packages/canon/css/textfield.css | 55 +++++++++++++++---- packages/canon/report.api.md | 3 + .../TextField/TextField.stories.tsx | 30 ++++++++++ .../components/TextField/TextField.styles.css | 55 +++++++++++++++---- .../src/components/TextField/TextField.tsx | 47 +++++++++++----- .../canon/src/components/TextField/types.ts | 15 +++++ 9 files changed, 257 insertions(+), 63 deletions(-) create mode 100644 .changeset/sad-fans-sort.md diff --git a/.changeset/sad-fans-sort.md b/.changeset/sad-fans-sort.md new file mode 100644 index 0000000000..dcd1e7815c --- /dev/null +++ b/.changeset/sad-fans-sort.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': minor +--- + +TextField in Canon now has multiple label sizes as well as the capacity to hide label and description but still make them available for screen readers. diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index bcdbb86235..f3be22720c 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -636,27 +636,65 @@ display: flex; } +.canon-TextFieldLabelWrapper { + margin-bottom: var(--canon-space-3); + gap: var(--canon-space-1); + flex-direction: column; + display: flex; +} + +.canon-TextFieldLabelWrapper[data-hidden] { + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + position: absolute; + overflow: hidden; +} + .canon-TextFieldLabel { - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-primary); - margin-bottom: var(--canon-space-1_5); cursor: pointer; margin-right: auto; } +.canon-TextFieldLabel[data-size="small"] { + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); +} + +.canon-TextFieldLabel[data-size="medium"] { + font-weight: var(--canon-font-weight-bold); + font-size: var(--canon-font-size-3); +} + .canon-TextFieldLabel[data-disabled] { cursor: default; } +.canon-TextFieldSecondaryLabel { + color: var(--canon-fg-secondary); + font-weight: var(--canon-font-weight-regular); + margin-left: var(--canon-space-1); +} + .canon-TextFieldDescription { - font-size: var(--canon-font-size-2); font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-secondary); - padding-top: var(--canon-space-1_5); margin: 0; } +.canon-TextFieldDescription[data-size="small"] { + font-size: var(--canon-font-size-2); +} + +.canon-TextFieldDescription[data-size="medium"] { + font-size: var(--canon-font-size-3); +} + .canon-TextFieldError { font-size: var(--canon-font-size-2); font-weight: var(--canon-font-weight-regular); @@ -769,13 +807,6 @@ height: 2.5rem; } -.canon-TextFieldRequired { - color: var(--canon-fg-secondary); - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - margin-left: var(--canon-space-1); -} - .canon-MenuPositioner { outline: 0; } diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index 43b1cfbc01..8e98996d6d 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -9860,27 +9860,65 @@ display: flex; } +.canon-TextFieldLabelWrapper { + margin-bottom: var(--canon-space-3); + gap: var(--canon-space-1); + flex-direction: column; + display: flex; +} + +.canon-TextFieldLabelWrapper[data-hidden] { + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + position: absolute; + overflow: hidden; +} + .canon-TextFieldLabel { - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-primary); - margin-bottom: var(--canon-space-1_5); cursor: pointer; margin-right: auto; } +.canon-TextFieldLabel[data-size="small"] { + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); +} + +.canon-TextFieldLabel[data-size="medium"] { + font-weight: var(--canon-font-weight-bold); + font-size: var(--canon-font-size-3); +} + .canon-TextFieldLabel[data-disabled] { cursor: default; } +.canon-TextFieldSecondaryLabel { + color: var(--canon-fg-secondary); + font-weight: var(--canon-font-weight-regular); + margin-left: var(--canon-space-1); +} + .canon-TextFieldDescription { - font-size: var(--canon-font-size-2); font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-secondary); - padding-top: var(--canon-space-1_5); margin: 0; } +.canon-TextFieldDescription[data-size="small"] { + font-size: var(--canon-font-size-2); +} + +.canon-TextFieldDescription[data-size="medium"] { + font-size: var(--canon-font-size-3); +} + .canon-TextFieldError { font-size: var(--canon-font-size-2); font-weight: var(--canon-font-weight-regular); @@ -9993,13 +10031,6 @@ height: 2.5rem; } -.canon-TextFieldRequired { - color: var(--canon-fg-secondary); - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - margin-left: var(--canon-space-1); -} - .canon-MenuPositioner { outline: 0; } diff --git a/packages/canon/css/textfield.css b/packages/canon/css/textfield.css index c3c1a88ad6..bdc5a23dbd 100644 --- a/packages/canon/css/textfield.css +++ b/packages/canon/css/textfield.css @@ -5,27 +5,65 @@ display: flex; } +.canon-TextFieldLabelWrapper { + margin-bottom: var(--canon-space-3); + gap: var(--canon-space-1); + flex-direction: column; + display: flex; +} + +.canon-TextFieldLabelWrapper[data-hidden] { + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + position: absolute; + overflow: hidden; +} + .canon-TextFieldLabel { - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-primary); - margin-bottom: var(--canon-space-1_5); cursor: pointer; margin-right: auto; } +.canon-TextFieldLabel[data-size="small"] { + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); +} + +.canon-TextFieldLabel[data-size="medium"] { + font-weight: var(--canon-font-weight-bold); + font-size: var(--canon-font-size-3); +} + .canon-TextFieldLabel[data-disabled] { cursor: default; } +.canon-TextFieldSecondaryLabel { + color: var(--canon-fg-secondary); + font-weight: var(--canon-font-weight-regular); + margin-left: var(--canon-space-1); +} + .canon-TextFieldDescription { - font-size: var(--canon-font-size-2); font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-secondary); - padding-top: var(--canon-space-1_5); margin: 0; } +.canon-TextFieldDescription[data-size="small"] { + font-size: var(--canon-font-size-2); +} + +.canon-TextFieldDescription[data-size="medium"] { + font-size: var(--canon-font-size-3); +} + .canon-TextFieldError { font-size: var(--canon-font-size-2); font-weight: var(--canon-font-weight-regular); @@ -137,10 +175,3 @@ .canon-TextFieldInputWrapper[data-size="medium"] { height: 2.5rem; } - -.canon-TextFieldRequired { - color: var(--canon-fg-secondary); - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - margin-left: var(--canon-space-1); -} diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index b6ce130ecc..ceec381c83 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -1274,10 +1274,13 @@ export interface TextFieldProps className?: string; description?: string; error?: string | null; + hideLabelAndDescription?: boolean; icon?: ReactNode; label?: string; + labelSize?: 'small' | 'medium'; name: string; onClear?: MouseEventHandler; + secondaryLabel?: string; size?: 'small' | 'medium' | Partial>; } diff --git a/packages/canon/src/components/TextField/TextField.stories.tsx b/packages/canon/src/components/TextField/TextField.stories.tsx index 7e625dad24..f052377acb 100644 --- a/packages/canon/src/components/TextField/TextField.stories.tsx +++ b/packages/canon/src/components/TextField/TextField.stories.tsx @@ -22,6 +22,14 @@ import { Icon } from '../Icon'; const meta = { title: 'Components/TextField', component: TextField, + argTypes: { + secondaryLabel: { + control: 'text', + }, + required: { + control: 'boolean', + }, + }, } satisfies Meta; export default meta; @@ -65,6 +73,28 @@ export const Required: Story = { }, }; +export const LabelSizes: Story = { + args: { + ...Default.args, + label: 'Label', + description: 'Description', + required: true, + }, + render: args => ( + + + + + ), +}; + +export const HideLabelAndDescription: Story = { + args: { + ...WithLabel.args, + hideLabelAndDescription: true, + }, +}; + export const Disabled: Story = { args: { ...WithLabel.args, diff --git a/packages/canon/src/components/TextField/TextField.styles.css b/packages/canon/src/components/TextField/TextField.styles.css index be7ec3379c..d7bc147ba1 100644 --- a/packages/canon/src/components/TextField/TextField.styles.css +++ b/packages/canon/src/components/TextField/TextField.styles.css @@ -21,25 +21,63 @@ width: 100%; } +.canon-TextFieldLabelWrapper { + display: flex; + flex-direction: column; + margin-bottom: var(--canon-space-3); + gap: var(--canon-space-1); +} + +.canon-TextFieldLabelWrapper[data-hidden] { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + .canon-TextFieldLabel { - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-primary); - margin-bottom: var(--canon-space-1_5); margin-right: auto; cursor: pointer; } +.canon-TextFieldLabel[data-size='small'] { + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); +} + +.canon-TextFieldLabel[data-size='medium'] { + font-weight: var(--canon-font-weight-bold); + font-size: var(--canon-font-size-3); +} + .canon-TextFieldLabel[data-disabled] { cursor: default; } +.canon-TextFieldSecondaryLabel { + color: var(--canon-fg-secondary); + font-weight: var(--canon-font-weight-regular); + margin-left: var(--canon-space-1); +} + .canon-TextFieldDescription { - font-size: var(--canon-font-size-2); font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-secondary); margin: 0; - padding-top: var(--canon-space-1_5); +} + +.canon-TextFieldDescription[data-size='small'] { + font-size: var(--canon-font-size-2); +} + +.canon-TextFieldDescription[data-size='medium'] { + font-size: var(--canon-font-size-3); } .canon-TextFieldError { @@ -156,10 +194,3 @@ .canon-TextFieldInputWrapper[data-size='medium'] { height: 2.5rem; } - -.canon-TextFieldRequired { - color: var(--canon-fg-secondary); - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - margin-left: var(--canon-space-1); -} diff --git a/packages/canon/src/components/TextField/TextField.tsx b/packages/canon/src/components/TextField/TextField.tsx index e4cf43a711..e534b0952d 100644 --- a/packages/canon/src/components/TextField/TextField.tsx +++ b/packages/canon/src/components/TextField/TextField.tsx @@ -29,7 +29,10 @@ export const TextField = forwardRef( className, size = 'small', label, + labelSize = 'small', + secondaryLabel, description, + hideLabelAndDescription, error, required, style, @@ -42,6 +45,9 @@ export const TextField = forwardRef( // Get the responsive value for the variant const responsiveSize = useResponsiveValue(size); + // If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required. + const secondaryLabelText = secondaryLabel || (required ? 'Required' : null); + return ( ( style={style} ref={ref} > - {label && ( - - {label} - {required && ( - - )} - - )} +
+ {label && ( + + {label} + {secondaryLabelText && ( + + )} + + )} + {description && ( + + {description} + + )} +
{icon && (
( )}
- {description && ( - - {description} - - )} {error && ( {error} diff --git a/packages/canon/src/components/TextField/types.ts b/packages/canon/src/components/TextField/types.ts index 8a37a23efe..8a81492f2f 100644 --- a/packages/canon/src/components/TextField/types.ts +++ b/packages/canon/src/components/TextField/types.ts @@ -35,6 +35,21 @@ export interface TextFieldProps */ label?: string; + /** + * The secondary label of the text field + */ + secondaryLabel?: string; + + /** + * The size of the label and description + */ + labelSize?: 'small' | 'medium'; + + /** + * Hide the label and description but still visible to screen readers + */ + hideLabelAndDescription?: boolean; + /** * The description of the text field */ From 6b8ad02c4df69b5bedbdc2ecea35d7157e87af84 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sat, 14 Jun 2025 08:32:51 +0100 Subject: [PATCH 093/301] Update TextField.tsx Signed-off-by: Charles de Dreuille --- .../src/components/TextField/TextField.tsx | 57 ++++++++++--------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/packages/canon/src/components/TextField/TextField.tsx b/packages/canon/src/components/TextField/TextField.tsx index e534b0952d..07efc47100 100644 --- a/packages/canon/src/components/TextField/TextField.tsx +++ b/packages/canon/src/components/TextField/TextField.tsx @@ -56,32 +56,37 @@ export const TextField = forwardRef( style={style} ref={ref} > -
- {label && ( - - {label} - {secondaryLabelText && ( - - )} - - )} - {description && ( - - {description} - - )} -
+ {label && ( +
+ {label && ( + + {label} + {secondaryLabelText && ( + + )} + + )} + {description && ( + + {description} + + )} +
+ )}
{icon && (
Date: Sat, 14 Jun 2025 13:32:58 +0000 Subject: [PATCH 094/301] chore(deps): update dependency glob to v11.0.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 58 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/yarn.lock b/yarn.lock index 780aa2fbfa..8393effc1e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11031,6 +11031,22 @@ __metadata: languageName: node linkType: hard +"@isaacs/balanced-match@npm:^4.0.1": + version: 4.0.1 + resolution: "@isaacs/balanced-match@npm:4.0.1" + checksum: 10/102fbc6d2c0d5edf8f6dbf2b3feb21695a21bc850f11bc47c4f06aa83bd8884fde3fe9d6d797d619901d96865fdcb4569ac2a54c937992c48885c5e3d9967fe8 + languageName: node + linkType: hard + +"@isaacs/brace-expansion@npm:^5.0.0": + version: 5.0.0 + resolution: "@isaacs/brace-expansion@npm:5.0.0" + dependencies: + "@isaacs/balanced-match": "npm:^4.0.1" + checksum: 10/cf3b7f206aff12128214a1df764ac8cdbc517c110db85249b945282407e3dfc5c6e66286383a7c9391a059fc8e6e6a8ca82262fc9d2590bd615376141fbebd2d + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -26865,7 +26881,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5": +"cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.5, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -30594,13 +30610,13 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^3.1.0": - version: 3.1.1 - resolution: "foreground-child@npm:3.1.1" +"foreground-child@npm:^3.1.0, foreground-child@npm:^3.3.1": + version: 3.3.1 + resolution: "foreground-child@npm:3.3.1" dependencies: - cross-spawn: "npm:^7.0.0" + cross-spawn: "npm:^7.0.6" signal-exit: "npm:^4.0.1" - checksum: 10/087edd44857d258c4f73ad84cb8df980826569656f2550c341b27adf5335354393eec24ea2fabd43a253233fb27cee177ebe46bd0b7ea129c77e87cb1e9936fb + checksum: 10/427b33f997a98073c0424e5c07169264a62cda806d8d2ded159b5b903fdfc8f0a1457e06b5fc35506497acb3f1e353f025edee796300209ac6231e80edece835 languageName: node linkType: hard @@ -31346,18 +31362,18 @@ __metadata: linkType: hard "glob@npm:^11.0.1": - version: 11.0.2 - resolution: "glob@npm:11.0.2" + version: 11.0.3 + resolution: "glob@npm:11.0.3" dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^4.0.1" - minimatch: "npm:^10.0.0" + foreground-child: "npm:^3.3.1" + jackspeak: "npm:^4.1.1" + minimatch: "npm:^10.0.3" minipass: "npm:^7.1.2" package-json-from-dist: "npm:^1.0.0" path-scurry: "npm:^2.0.0" bin: glob: dist/esm/bin.mjs - checksum: 10/53501530240150fdceb9ace47ab856acd1e0d598f8101b0760b665fc11dae2160d366563b89232ae4f5df7ddba8f7c92294719268fe932bd3a32d16cc58c3d02 + checksum: 10/2ae536c1360c0266b523b2bfa6aadc10144a8b7e08869b088e37ac3c27cd30774f82e4bfb291cde796776e878f9e13200c7ff44010eb7054e00f46f649397893 languageName: node linkType: hard @@ -33983,12 +33999,12 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^4.0.1": - version: 4.0.2 - resolution: "jackspeak@npm:4.0.2" +"jackspeak@npm:^4.1.1": + version: 4.1.1 + resolution: "jackspeak@npm:4.1.1" dependencies: "@isaacs/cliui": "npm:^8.0.2" - checksum: 10/d9722f0e55f6c322c57aedf094c405f4201b834204629817187953988075521cfddb23df83e2a7b845723ca7eb0555068c5ce1556732e9c275d32a531881efa8 + checksum: 10/ffceb270ec286841f48413bfb4a50b188662dfd599378ce142b6540f3f0a66821dc9dcb1e9ebc55c6c3b24dc2226c96e5819ba9bd7a241bd29031b61911718c7 languageName: node linkType: hard @@ -37666,12 +37682,12 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^10.0.0": - version: 10.0.1 - resolution: "minimatch@npm:10.0.1" +"minimatch@npm:^10.0.3": + version: 10.0.3 + resolution: "minimatch@npm:10.0.3" dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10/082e7ccbc090d5f8c4e4e029255d5a1d1e3af37bda837da2b8b0085b1503a1210c91ac90d9ebfe741d8a5f286ece820a1abb4f61dc1f82ce602a055d461d93f3 + "@isaacs/brace-expansion": "npm:^5.0.0" + checksum: 10/d5b8b2538b367f2cfd4aeef27539fddeee58d1efb692102b848e4a968a09780a302c530eb5aacfa8c57f7299155fb4b4e85219ad82664dcef5c66f657111d9b8 languageName: node linkType: hard From 00cfef6fbced31bdfee8113bde3b242b3b3da273 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 14 Jun 2025 13:34:35 +0000 Subject: [PATCH 095/301] chore(deps): update react monorepo Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- canon-docs/package.json | 4 ++-- canon-docs/yarn.lock | 10 +++++----- storybook/yarn.lock | 6 +++--- yarn.lock | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/canon-docs/package.json b/canon-docs/package.json index 05d46df217..c9bc1ae96b 100644 --- a/canon-docs/package.json +++ b/canon-docs/package.json @@ -9,7 +9,7 @@ "start": "npm run build:css && next dev" }, "resolutions": { - "@types/react": "19.1.7", + "@types/react": "19.1.8", "@types/react-dom": "19.1.6" }, "dependencies": { @@ -34,7 +34,7 @@ "devDependencies": { "@types/mdx": "^2.0.13", "@types/node": "^20", - "@types/react": "19.1.7", + "@types/react": "19.1.8", "@types/react-dom": "19.1.6", "eslint": "^8", "eslint-config-next": "15.3.3", diff --git a/canon-docs/yarn.lock b/canon-docs/yarn.lock index fde0c19836..9232edb7af 100644 --- a/canon-docs/yarn.lock +++ b/canon-docs/yarn.lock @@ -1149,12 +1149,12 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:19.1.7": - version: 19.1.7 - resolution: "@types/react@npm:19.1.7" +"@types/react@npm:19.1.8": + version: 19.1.8 + resolution: "@types/react@npm:19.1.8" dependencies: csstype: "npm:^3.0.2" - checksum: 10/a2bd9aea233e9b8b1eb27192ee5cefd5a236b03ef9fce2e5ea8ec612cf4735d38fa443a0a1d0abea61dd166aad205fb8d799055dacfd2f0759da6f59f046fc55 + checksum: 10/a3e6fe0f60f22828ef887f30993aa147b71532d7b1219dd00d246277eb7a9ca01ec533096237fa21ca1bccb3653373b4e8e59e5ae59f9c793058384bbc1f4d5c languageName: node linkType: hard @@ -1843,7 +1843,7 @@ __metadata: "@storybook/react": "npm:^8.6.8" "@types/mdx": "npm:^2.0.13" "@types/node": "npm:^20" - "@types/react": "npm:19.1.7" + "@types/react": "npm:19.1.8" "@types/react-dom": "npm:19.1.6" "@uiw/codemirror-themes": "npm:^4.23.7" "@uiw/react-codemirror": "npm:^4.23.7" diff --git a/storybook/yarn.lock b/storybook/yarn.lock index 96cf1e99bb..d7e83584d1 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -1552,12 +1552,12 @@ __metadata: linkType: hard "@types/react@npm:^18.0.0": - version: 18.3.21 - resolution: "@types/react@npm:18.3.21" + version: 18.3.23 + resolution: "@types/react@npm:18.3.23" dependencies: "@types/prop-types": "npm:*" csstype: "npm:^3.0.2" - checksum: 10/89adc2fe391fd63b620db0aaa10b2be89fad338039415fd748e60c962e47bf2b0f1897cb5226f181eda963f37a1c8ac1bf0b3b29a0e35313bacd0355e5d6c736 + checksum: 10/4b965dffe34a1f8aac8e2d7e976f113373f38134f9e37239f7e75d7ac6b3c2e1333a8df21febf1fe7749640f8de5708f7668cdfc70bffebda1cc4d3346724fd5 languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index 780aa2fbfa..eaf3c93fe1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21052,12 +21052,12 @@ __metadata: linkType: hard "@types/react@npm:^18.0.0": - version: 18.3.21 - resolution: "@types/react@npm:18.3.21" + version: 18.3.23 + resolution: "@types/react@npm:18.3.23" dependencies: "@types/prop-types": "npm:*" csstype: "npm:^3.0.2" - checksum: 10/89adc2fe391fd63b620db0aaa10b2be89fad338039415fd748e60c962e47bf2b0f1897cb5226f181eda963f37a1c8ac1bf0b3b29a0e35313bacd0355e5d6c736 + checksum: 10/4b965dffe34a1f8aac8e2d7e976f113373f38134f9e37239f7e75d7ac6b3c2e1333a8df21febf1fe7749640f8de5708f7668cdfc70bffebda1cc4d3346724fd5 languageName: node linkType: hard From 9d4957a6193c7bd5bc364d12257f753ac96a7b1c Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sat, 14 Jun 2025 15:20:56 +0100 Subject: [PATCH 096/301] First pass at adding Canon switch Signed-off-by: Charles de Dreuille --- packages/canon/package.json | 3 +- .../src/components/Switch/Switch.stories.tsx | 32 + .../src/components/Switch/Switch.styles.css | 74 + .../canon/src/components/Switch/Switch.tsx | 32 + .../canon/src/components/Switch/index.tsx | 17 + packages/canon/src/components/Switch/types.ts | 21 + packages/canon/src/css/components.css | 1 + packages/canon/src/index.ts | 1 + yarn.lock | 1841 ++++++++++++++++- 9 files changed, 1919 insertions(+), 103 deletions(-) create mode 100644 packages/canon/src/components/Switch/Switch.stories.tsx create mode 100644 packages/canon/src/components/Switch/Switch.styles.css create mode 100644 packages/canon/src/components/Switch/Switch.tsx create mode 100644 packages/canon/src/components/Switch/index.tsx create mode 100644 packages/canon/src/components/Switch/types.ts diff --git a/packages/canon/package.json b/packages/canon/package.json index e20572741e..0b8bb0aeaa 100644 --- a/packages/canon/package.json +++ b/packages/canon/package.json @@ -44,7 +44,8 @@ "@base-ui-components/react": "1.0.0-alpha.7", "@remixicon/react": "^4.6.0", "@tanstack/react-table": "^8.21.3", - "clsx": "^2.1.1" + "clsx": "^2.1.1", + "react-aria-components": "^1.10.1" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/packages/canon/src/components/Switch/Switch.stories.tsx b/packages/canon/src/components/Switch/Switch.stories.tsx new file mode 100644 index 0000000000..8350d0f4e5 --- /dev/null +++ b/packages/canon/src/components/Switch/Switch.stories.tsx @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { Switch } from './Switch'; + +const meta = { + title: 'Components/Switch', + component: Switch, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + label: 'Switch', + }, +}; diff --git a/packages/canon/src/components/Switch/Switch.styles.css b/packages/canon/src/components/Switch/Switch.styles.css new file mode 100644 index 0000000000..b328414aa0 --- /dev/null +++ b/packages/canon/src/components/Switch/Switch.styles.css @@ -0,0 +1,74 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.canon-Switch { + display: flex; + /* This is needed so the HiddenInput is positioned correctly */ + position: relative; + align-items: center; + gap: var(--canon-space-3); + font-size: var(--canon-font-size-3); + color: var(--canon-fg-primary); + cursor: pointer; + + &[data-pressed] .canon-SwitchIndicator { + &:before { + background: var(--canon-fg-solid); + } + } + + &[data-selected] { + .canon-SwitchIndicator { + background: var(--canon-gray-3); + + &:before { + background: var(--canon-fg-solid); + transform: translateX(100%); + } + } + + &[data-pressed] { + .indicator { + background: var(--canon-gray-3); + } + } + } + + &[data-focus-visible] .canon-SwitchIndicator { + outline: 2px solid; + outline-offset: 2px; + } +} + +.canon-SwitchIndicator { + width: 2rem; + height: 1.143rem; + border: 2px; + background: var(--canon-gray-3); + border-radius: 1.143rem; + transition: all 200ms; + + &:before { + content: ''; + display: block; + margin: 0.143rem; + width: 0.857rem; + height: 0.857rem; + background: var(--canon-fg-solid); + border-radius: 16px; + transition: all 200ms; + } +} diff --git a/packages/canon/src/components/Switch/Switch.tsx b/packages/canon/src/components/Switch/Switch.tsx new file mode 100644 index 0000000000..b84a8c9c5f --- /dev/null +++ b/packages/canon/src/components/Switch/Switch.tsx @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { forwardRef } from 'react'; +import { Switch as AriaSwitch } from 'react-aria-components'; +import type { SwitchProps } from './types'; + +export const Switch = forwardRef( + ({ label, ...props }, ref) => { + return ( + +
+ {label} + + ); + }, +); + +Switch.displayName = 'Switch'; diff --git a/packages/canon/src/components/Switch/index.tsx b/packages/canon/src/components/Switch/index.tsx new file mode 100644 index 0000000000..10a5429188 --- /dev/null +++ b/packages/canon/src/components/Switch/index.tsx @@ -0,0 +1,17 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { Switch } from './Switch'; diff --git a/packages/canon/src/components/Switch/types.ts b/packages/canon/src/components/Switch/types.ts new file mode 100644 index 0000000000..1b4fcd66ed --- /dev/null +++ b/packages/canon/src/components/Switch/types.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { SwitchProps as AriaSwitchProps } from 'react-aria-components'; + +export interface SwitchProps extends AriaSwitchProps { + label?: string; +} diff --git a/packages/canon/src/css/components.css b/packages/canon/src/css/components.css index 7cee946e3b..609ebea769 100644 --- a/packages/canon/src/css/components.css +++ b/packages/canon/src/css/components.css @@ -40,3 +40,4 @@ @import '../components/Tooltip/Tooltip.styles.css'; @import '../components/ScrollArea/ScrollArea.styles.css'; @import '../components/Select/Select.styles.css'; +@import '../components/Switch/Switch.styles.css'; diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts index cab4cc4acb..310a7896c8 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -47,6 +47,7 @@ export * from './components/Menu'; export * from './components/ScrollArea'; export * from './components/Link'; export * from './components/Select'; +export * from './components/Switch'; // Types export * from './types'; diff --git a/yarn.lock b/yarn.lock index 780aa2fbfa..01e2bd806b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3831,6 +3831,7 @@ __metadata: lightningcss: "npm:^1.29.1" mini-css-extract-plugin: "npm:^2.9.2" react: "npm:^18.0.2" + react-aria-components: "npm:^1.10.1" react-dom: "npm:^18.0.2" react-router-dom: "npm:^6.3.0" storybook: "npm:^8.6.12" @@ -10980,40 +10981,40 @@ __metadata: languageName: unknown linkType: soft -"@internationalized/date@npm:^3.7.0": - version: 3.7.0 - resolution: "@internationalized/date@npm:3.7.0" +"@internationalized/date@npm:^3.8.2": + version: 3.8.2 + resolution: "@internationalized/date@npm:3.8.2" dependencies: "@swc/helpers": "npm:^0.5.0" - checksum: 10/59adffb57f1391e17bfe9b2e9b1a71c0957a1835b0176a86ad5a99fd2f2919f094d3f553fb39be157d84c315a88917e0eda779c9101026e3bfac217ace18a559 + checksum: 10/ca281d8785acff520ba4782d4eb0c542bb6c2783dbd3eb5457aeddbb68b9674f860fc00c6dd467f39c12eff9a04381742b07f0b9e210a115c094507e046d7b5a languageName: node linkType: hard -"@internationalized/message@npm:^3.1.6": - version: 3.1.6 - resolution: "@internationalized/message@npm:3.1.6" +"@internationalized/message@npm:^3.1.8": + version: 3.1.8 + resolution: "@internationalized/message@npm:3.1.8" dependencies: "@swc/helpers": "npm:^0.5.0" intl-messageformat: "npm:^10.1.0" - checksum: 10/31611a81bba7c0b18fad02ccb363632cbef3a0cc6e6e564578dfc7ae42cfde48b0ec18b89bff24390bb748df63d40cd2028483068e7d1492aee0533648a71e04 + checksum: 10/8f27a31f5d1eef7084447ed141e896e12cc19d786a1346ba60137de5b8bcc58a9da978d79954e2a74302aa673f942fb772130ebd6195565e33db30bd7eb4ef47 languageName: node linkType: hard -"@internationalized/number@npm:^3.6.0": - version: 3.6.0 - resolution: "@internationalized/number@npm:3.6.0" +"@internationalized/number@npm:^3.6.3": + version: 3.6.3 + resolution: "@internationalized/number@npm:3.6.3" dependencies: "@swc/helpers": "npm:^0.5.0" - checksum: 10/76428f75fd289008580108beef02816f94aaca37d48c9e26c2274fdbb81e8f32c4247ff624b8f854384b18ce2556cc8d817ccf1bc39b0fd87b0da1f3d911209b + checksum: 10/b6f1793d12f89aca4c5bd88b60643739c4b02cade50a42c61192a2a5e0a913088ee35e4013b7ef3f675bdc919ebaf9a1631f1c40b1ceffd998f3fc14201a01c3 languageName: node linkType: hard -"@internationalized/string@npm:^3.2.5": - version: 3.2.5 - resolution: "@internationalized/string@npm:3.2.5" +"@internationalized/string@npm:^3.2.7": + version: 3.2.7 + resolution: "@internationalized/string@npm:3.2.7" dependencies: "@swc/helpers": "npm:^0.5.0" - checksum: 10/7a20359796545cd5724aa546018554b81ea168c58738e98884c54ef0de2fba7b802504c79678d690709b0a435478912aa771c426feed72aae1d93ee782b7ac5d + checksum: 10/38b54817cf125ba88d1136a6bca4fb57c46672d26d21490f838efe928049546800df6d9c8048411696455fc8caacb8ac23c2de2a1b61f2258b1302c1c97cc128 languageName: node linkType: hard @@ -15817,119 +15818,929 @@ __metadata: languageName: node linkType: hard -"@react-aria/focus@npm:^3.20.1": - version: 3.20.1 - resolution: "@react-aria/focus@npm:3.20.1" +"@react-aria/autocomplete@npm:3.0.0-beta.5": + version: 3.0.0-beta.5 + resolution: "@react-aria/autocomplete@npm:3.0.0-beta.5" dependencies: - "@react-aria/interactions": "npm:^3.24.1" - "@react-aria/utils": "npm:^3.28.1" - "@react-types/shared": "npm:^3.28.0" + "@react-aria/combobox": "npm:^3.12.5" + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/listbox": "npm:^3.14.6" + "@react-aria/searchfield": "npm:^3.8.6" + "@react-aria/textfield": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/autocomplete": "npm:3.0.0-beta.2" + "@react-stately/combobox": "npm:^3.10.6" + "@react-types/autocomplete": "npm:3.0.0-alpha.32" + "@react-types/button": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/f8b4427b310a4bd221cb0e38b031a4150c306f7d1177926c5a2bea0dd4370db6e49e220ca2358c283ea4c009f0a65611340486d161193f7943659a60e20a1881 + languageName: node + linkType: hard + +"@react-aria/breadcrumbs@npm:^3.5.26": + version: 3.5.26 + resolution: "@react-aria/breadcrumbs@npm:3.5.26" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/link": "npm:^3.8.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/breadcrumbs": "npm:^3.7.14" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/e0b9fe416cb84281e207ab12644d3bf21d6af82b4ec5d5ac204209ef6aff9abcc242660c23b2f3ef3572aa0f1e72dcc33905066f720afa1ccfc3cf3d4b916c31 + languageName: node + linkType: hard + +"@react-aria/button@npm:^3.13.3": + version: 3.13.3 + resolution: "@react-aria/button@npm:3.13.3" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/toolbar": "npm:3.0.0-beta.18" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/toggle": "npm:^3.8.5" + "@react-types/button": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/c360899930f648c56f16bf5d0d2d8c01b5d59449b87fff4e758e68127aa8e453396ffbdc6ee0fa2fbcf18fce73472041da36d21929b2d28bd55770bbfd75e384 + languageName: node + linkType: hard + +"@react-aria/calendar@npm:^3.8.3": + version: 3.8.3 + resolution: "@react-aria/calendar@npm:3.8.3" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/live-announcer": "npm:^3.4.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/calendar": "npm:^3.8.2" + "@react-types/button": "npm:^3.12.2" + "@react-types/calendar": "npm:^3.7.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/53570c8c95fced3704a908ba218ee316db46f5c58352a5970f3b0a863257d8293dd732b0aaeb983921358f45acd50df623db88afa49df4d6735508d08369212b + languageName: node + linkType: hard + +"@react-aria/checkbox@npm:^3.15.7": + version: 3.15.7 + resolution: "@react-aria/checkbox@npm:3.15.7" + dependencies: + "@react-aria/form": "npm:^3.0.18" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/toggle": "npm:^3.11.5" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/checkbox": "npm:^3.6.15" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/toggle": "npm:^3.8.5" + "@react-types/checkbox": "npm:^3.9.5" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/a041baf3977d41f0f46db184b80da9f4d928209ca0bd764cb43202e5fc2f7a1ed44d7b9963d38f11dab934bf0ac5c0ee9311894b8177ed890c4f174b0640b8c8 + languageName: node + linkType: hard + +"@react-aria/collections@npm:3.0.0-rc.3": + version: 3.0.0-rc.3 + resolution: "@react-aria/collections@npm:3.0.0-rc.3" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/ssr": "npm:^3.9.9" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/a7a6ba15554ba6f3047cf6898a222e800b8317d4f0b05dbe11cf4301bc3aaa249ea2ae855ec4b0afd9f95c33e47f9ad10227299f6c382995155d91a94ce91493 + languageName: node + linkType: hard + +"@react-aria/color@npm:^3.0.9": + version: 3.0.9 + resolution: "@react-aria/color@npm:3.0.9" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/numberfield": "npm:^3.11.16" + "@react-aria/slider": "npm:^3.7.21" + "@react-aria/spinbutton": "npm:^3.6.16" + "@react-aria/textfield": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.29.1" + "@react-aria/visually-hidden": "npm:^3.8.25" + "@react-stately/color": "npm:^3.8.6" + "@react-stately/form": "npm:^3.1.5" + "@react-types/color": "npm:^3.0.6" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/387b1609bafa89297525384925f5c7e290f5a2b592488daae4482873518401b0b5f1eb712000a3ad823f66888937499d3e0522b85ae9adc1e819ea8761abf19a + languageName: node + linkType: hard + +"@react-aria/combobox@npm:^3.12.5": + version: 3.12.5 + resolution: "@react-aria/combobox@npm:3.12.5" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/listbox": "npm:^3.14.6" + "@react-aria/live-announcer": "npm:^3.4.3" + "@react-aria/menu": "npm:^3.18.5" + "@react-aria/overlays": "npm:^3.27.3" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/textfield": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/combobox": "npm:^3.10.6" + "@react-stately/form": "npm:^3.1.5" + "@react-types/button": "npm:^3.12.2" + "@react-types/combobox": "npm:^3.13.6" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/171d27bb3a2eefc3c5e3ecdca1d760e44116a68d6169bfa214b05caf840aa879033855a3c1ea273f8d288d066800182f4f8a1299c3dbb7f0dff137db78ad9c41 + languageName: node + linkType: hard + +"@react-aria/datepicker@npm:^3.14.5": + version: 3.14.5 + resolution: "@react-aria/datepicker@npm:3.14.5" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@internationalized/number": "npm:^3.6.3" + "@internationalized/string": "npm:^3.2.7" + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/form": "npm:^3.0.18" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/spinbutton": "npm:^3.6.16" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/datepicker": "npm:^3.14.2" + "@react-stately/form": "npm:^3.1.5" + "@react-types/button": "npm:^3.12.2" + "@react-types/calendar": "npm:^3.7.2" + "@react-types/datepicker": "npm:^3.12.2" + "@react-types/dialog": "npm:^3.5.19" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/440475577e9aee6c18c5e8f5d2c91f22f5a528ee283b472627eb8cf226c7a2125fa537b30b7e5f20e9d6325631e13c5dfa23f88d6f7e6c1e9fd41eff8ccb7590 + languageName: node + linkType: hard + +"@react-aria/dialog@npm:^3.5.27": + version: 3.5.27 + resolution: "@react-aria/dialog@npm:3.5.27" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/overlays": "npm:^3.27.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/dialog": "npm:^3.5.19" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/21257bb14352ab611e5659ce555cd845d8de51d30eea0cf7882b5119848c2136cd35f796a3031bbbcbc9640bffadeb35eee6f6254b34f2791c5a739c550e60da + languageName: node + linkType: hard + +"@react-aria/disclosure@npm:^3.0.6": + version: 3.0.6 + resolution: "@react-aria/disclosure@npm:3.0.6" + dependencies: + "@react-aria/ssr": "npm:^3.9.9" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/disclosure": "npm:^3.0.5" + "@react-types/button": "npm:^3.12.2" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/c1e602d6bc91b8b4884b2ec4ef4c3f1ea3d3052ead219a73eb4be110348ef0bde8fd3a68af92e7d8af83a59465b208e8a0d363b1ecffde53134516ce13f0361a + languageName: node + linkType: hard + +"@react-aria/dnd@npm:^3.10.1": + version: 3.10.1 + resolution: "@react-aria/dnd@npm:3.10.1" + dependencies: + "@internationalized/string": "npm:^3.2.7" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/live-announcer": "npm:^3.4.3" + "@react-aria/overlays": "npm:^3.27.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/dnd": "npm:^3.6.0" + "@react-types/button": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/bf1058f59d20054571541455d9fa25eff469363b6f65852d237d9479bee4d6323fc3bd8497115d16e93d4396ab2faba0317875416c93d65a1a566881771624c1 + languageName: node + linkType: hard + +"@react-aria/focus@npm:^3.20.5": + version: 3.20.5 + resolution: "@react-aria/focus@npm:3.20.5" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" "@swc/helpers": "npm:^0.5.0" clsx: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/7f4652ebbb19a4ede9abce38bf756a44fdebcd6aa6d095ac304cefbed014f1456c45b3cc4e66d0132ffc55ebd5b9c8ba1ed83f61c39b63007a307d06cc12efc3 + checksum: 10/543c2f18c1d4f10940662608ca67947d1bc22630ae272e73136b36c2aefec61243148668201442132825bf4e9c0ab64add86c9870b9a51cb7bde2dfab7453a6e languageName: node linkType: hard -"@react-aria/i18n@npm:^3.12.7": - version: 3.12.7 - resolution: "@react-aria/i18n@npm:3.12.7" +"@react-aria/form@npm:^3.0.18": + version: 3.0.18 + resolution: "@react-aria/form@npm:3.0.18" dependencies: - "@internationalized/date": "npm:^3.7.0" - "@internationalized/message": "npm:^3.1.6" - "@internationalized/number": "npm:^3.6.0" - "@internationalized/string": "npm:^3.2.5" - "@react-aria/ssr": "npm:^3.9.7" - "@react-aria/utils": "npm:^3.28.1" - "@react-types/shared": "npm:^3.28.0" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/form": "npm:^3.1.5" + "@react-types/shared": "npm:^3.30.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/5f086ccc0a7e03637a4c74b2178aa8da9d5992eb15bc55f6ae804692bce01eeca89cc05e967eda6f2705ffb61404c5f9f995f9907063fba212685a7145f54d3d + checksum: 10/8c3edfaf3f2be28a16d7b0dcf00c677926e6e505fe26ebc348201f6f4e9e44aced1f17400a6c19717b18a3378be28438f269b438317a18f2070529fe2ac1e770 languageName: node linkType: hard -"@react-aria/interactions@npm:^3.24.1": - version: 3.24.1 - resolution: "@react-aria/interactions@npm:3.24.1" +"@react-aria/grid@npm:^3.14.2": + version: 3.14.2 + resolution: "@react-aria/grid@npm:3.14.2" dependencies: - "@react-aria/ssr": "npm:^3.9.7" - "@react-aria/utils": "npm:^3.28.1" - "@react-stately/flags": "npm:^3.1.0" - "@react-types/shared": "npm:^3.28.0" + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/live-announcer": "npm:^3.4.3" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/grid": "npm:^3.11.3" + "@react-stately/selection": "npm:^3.20.3" + "@react-types/checkbox": "npm:^3.9.5" + "@react-types/grid": "npm:^3.3.3" + "@react-types/shared": "npm:^3.30.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/79e40d68b980a84b0a64d8ebf030303558693b489694d439e46e419c79355d632cf3975a52eed30ddce90fff115d9172f6e3e8af5777b69c1a143f4ad81fef1e + checksum: 10/5f19539b1fe01d82b3693166269acae3fe74467b318f257babca79e5689b932ec83e5d7894ea0cbfd5e568fae3ffa61d04ae049a8967af09384f12d8e75792fa languageName: node linkType: hard -"@react-aria/overlays@npm:^3.26.1": - version: 3.26.1 - resolution: "@react-aria/overlays@npm:3.26.1" +"@react-aria/gridlist@npm:^3.13.2": + version: 3.13.2 + resolution: "@react-aria/gridlist@npm:3.13.2" dependencies: - "@react-aria/focus": "npm:^3.20.1" - "@react-aria/i18n": "npm:^3.12.7" - "@react-aria/interactions": "npm:^3.24.1" - "@react-aria/ssr": "npm:^3.9.7" - "@react-aria/utils": "npm:^3.28.1" - "@react-aria/visually-hidden": "npm:^3.8.21" - "@react-stately/overlays": "npm:^3.6.14" - "@react-types/button": "npm:^3.11.0" - "@react-types/overlays": "npm:^3.8.13" - "@react-types/shared": "npm:^3.28.0" + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/grid": "npm:^3.14.2" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/list": "npm:^3.12.3" + "@react-stately/tree": "npm:^3.9.0" + "@react-types/shared": "npm:^3.30.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/25d582fa5905c9ec42339ddae4aa9fce5d606a504e657d243b0b72fb77316b879c105b4d89e9c394de0aa9ea8514c38c42278d36b6fdd5de57233785fbd17a8c + checksum: 10/ea5e2f986fc74b627afe4918f94aa8cc778f146d7781822ce55788bb59a90e5765446d7c0bb5bcc86c1e4dd17ced3bd52ab6f703a50e8253bef25b70d3be0b56 languageName: node linkType: hard -"@react-aria/ssr@npm:^3.9.7": - version: 3.9.7 - resolution: "@react-aria/ssr@npm:3.9.7" +"@react-aria/i18n@npm:^3.12.10": + version: 3.12.10 + resolution: "@react-aria/i18n@npm:3.12.10" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@internationalized/message": "npm:^3.1.8" + "@internationalized/number": "npm:^3.6.3" + "@internationalized/string": "npm:^3.2.7" + "@react-aria/ssr": "npm:^3.9.9" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6f331ceb62a1364d32f2336257ed82715f712e27d3e04a173296efc320f6532626561e883c2c3639ef4efdcd03498005e87d7160d8942ea8255d18d57770d3a0 + languageName: node + linkType: hard + +"@react-aria/interactions@npm:^3.25.3": + version: 3.25.3 + resolution: "@react-aria/interactions@npm:3.25.3" + dependencies: + "@react-aria/ssr": "npm:^3.9.9" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/flags": "npm:^3.1.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/21754f3e15c35430262b8e283d00010543c8855bac9b9feeb701c967394940d4063b63380b59637c7da8e9b922f29edb3525d15d6005202a6dc22fd32a0d951f + languageName: node + linkType: hard + +"@react-aria/label@npm:^3.7.19": + version: 3.7.19 + resolution: "@react-aria/label@npm:3.7.19" + dependencies: + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/984a97ac0bca6487031bc006d8b697766d990e78e381d5e230414429542e51d84197459b45b834069615d9f17f5b162e671e464e62f78854ac8fbb458d1b3346 + languageName: node + linkType: hard + +"@react-aria/landmark@npm:^3.0.4": + version: 3.0.4 + resolution: "@react-aria/landmark@npm:3.0.4" + dependencies: + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/ab1d9cc93e83b0f486157dc878474e59b601631220bb688840c55baf6ccfd65d8fe2a04adaaa055f0e2883f3e7428b74d52cc13d32e15f930c0cb1c189309ee8 + languageName: node + linkType: hard + +"@react-aria/link@npm:^3.8.3": + version: 3.8.3 + resolution: "@react-aria/link@npm:3.8.3" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/link": "npm:^3.6.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/978f875a81616f46706da3596dfb52845af93420c3b2ce29d83c8eed5203cd0b5bdd3f988039dff3e3420de668927be46c2971333d628fa7af3f83b7796159f8 + languageName: node + linkType: hard + +"@react-aria/listbox@npm:^3.14.6": + version: 3.14.6 + resolution: "@react-aria/listbox@npm:3.14.6" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/list": "npm:^3.12.3" + "@react-types/listbox": "npm:^3.7.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/068a1e5d56516436dbc8bde6e3a98db7ec277f0a0ce73ce6334e762c196570ae751dc0b0503f1e66624569d89ce412bc9292fa67f5a52db03db3104bb1d2f300 + languageName: node + linkType: hard + +"@react-aria/live-announcer@npm:^3.4.3": + version: 3.4.3 + resolution: "@react-aria/live-announcer@npm:3.4.3" + dependencies: + "@swc/helpers": "npm:^0.5.0" + checksum: 10/73e3fc9bc2796cbccbcd0e01e2b4bd62d0b1a3915b1d0f01d2054141033c6b2a5275a7b7c52bdc0a3b9ef738881b4a84bb5d6df57d1ee066278e5bdf40a8f979 + languageName: node + linkType: hard + +"@react-aria/menu@npm:^3.18.5": + version: 3.18.5 + resolution: "@react-aria/menu@npm:3.18.5" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/overlays": "npm:^3.27.3" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/menu": "npm:^3.9.5" + "@react-stately/selection": "npm:^3.20.3" + "@react-stately/tree": "npm:^3.9.0" + "@react-types/button": "npm:^3.12.2" + "@react-types/menu": "npm:^3.10.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/409f21cfa7d9cca62ca7c21b047bd72314181782b2d6ea0bb16a33075280653eef1b4d9c4b0ad15ebe72ec59e12103f3b1f86e6e7cd8886571956d6d099ae200 + languageName: node + linkType: hard + +"@react-aria/meter@npm:^3.4.24": + version: 3.4.24 + resolution: "@react-aria/meter@npm:3.4.24" + dependencies: + "@react-aria/progress": "npm:^3.4.24" + "@react-types/meter": "npm:^3.4.10" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/980e46d3b784c59523ea9eb342c28e2a29e0549937da4def5f61ce011abb2da40f833dbe3f14550ffcd384860e08d4e8ee286e58834100b04c7de93fa5587e50 + languageName: node + linkType: hard + +"@react-aria/numberfield@npm:^3.11.16": + version: 3.11.16 + resolution: "@react-aria/numberfield@npm:3.11.16" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/spinbutton": "npm:^3.6.16" + "@react-aria/textfield": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/numberfield": "npm:^3.9.13" + "@react-types/button": "npm:^3.12.2" + "@react-types/numberfield": "npm:^3.8.12" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/3e83938fe4fe76ef662c543734988b56d60824d8d2d19de5adffe06191106aa98b2b6dd4241ed49fc3e7ad3e1c4c43cbdd81c6fc32d728959e5b377faf8ffdcd + languageName: node + linkType: hard + +"@react-aria/overlays@npm:^3.26.1, @react-aria/overlays@npm:^3.27.3": + version: 3.27.3 + resolution: "@react-aria/overlays@npm:3.27.3" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/ssr": "npm:^3.9.9" + "@react-aria/utils": "npm:^3.29.1" + "@react-aria/visually-hidden": "npm:^3.8.25" + "@react-stately/overlays": "npm:^3.6.17" + "@react-types/button": "npm:^3.12.2" + "@react-types/overlays": "npm:^3.8.16" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/8cee1a99bc9ff4ecf0577b76f54cccaf684dde2278d0c270e82c683ef6f42bb8100de4596ae411e775643a3765abbd87fdb1c5d46bf05481ed9df9ed9b82276a + languageName: node + linkType: hard + +"@react-aria/progress@npm:^3.4.24": + version: 3.4.24 + resolution: "@react-aria/progress@npm:3.4.24" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/progress": "npm:^3.5.13" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/e7e4cf6f904f1b35a54704aa6d473c4eb239130ba8fbba71f6298b68a92c067162132fb5adf1e6f8960bdc8a2913004d979e088575761b0ba5252c28cdf62b0b + languageName: node + linkType: hard + +"@react-aria/radio@npm:^3.11.5": + version: 3.11.5 + resolution: "@react-aria/radio@npm:3.11.5" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/form": "npm:^3.0.18" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/radio": "npm:^3.10.14" + "@react-types/radio": "npm:^3.8.10" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/63bbca9ad7a065453f60bb759c0377a8febeb0e4582807726d7eef36a05c806d6d0c9d86ad7b73c42cc0a1c47bab5a1d0369e0d670fc4f58924662395418b24d + languageName: node + linkType: hard + +"@react-aria/searchfield@npm:^3.8.6": + version: 3.8.6 + resolution: "@react-aria/searchfield@npm:3.8.6" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/textfield": "npm:^3.17.5" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/searchfield": "npm:^3.5.13" + "@react-types/button": "npm:^3.12.2" + "@react-types/searchfield": "npm:^3.6.3" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/7db3dd61084e49c9d2c9fa9aa10821087aff91cfadeade78b559cd1877c67a49dcdd238b48a8b2501cd7070b9123d276e8ef6170cb5438cdce28ef6889eea366 + languageName: node + linkType: hard + +"@react-aria/select@npm:^3.15.7": + version: 3.15.7 + resolution: "@react-aria/select@npm:3.15.7" + dependencies: + "@react-aria/form": "npm:^3.0.18" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/listbox": "npm:^3.14.6" + "@react-aria/menu": "npm:^3.18.5" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-aria/visually-hidden": "npm:^3.8.25" + "@react-stately/select": "npm:^3.6.14" + "@react-types/button": "npm:^3.12.2" + "@react-types/select": "npm:^3.9.13" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/8e0974ddc558051c384a7d9da1eeea4623cbe4a61f0f0402aebeb4be23a1cb5875b839608dcc6aaca4ab6c288143c202eb0fa2ee3ed8f62b7e33d142604f2fba + languageName: node + linkType: hard + +"@react-aria/selection@npm:^3.24.3": + version: 3.24.3 + resolution: "@react-aria/selection@npm:3.24.3" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/selection": "npm:^3.20.3" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/f2575161b69d87e7c5ee7d0d658c96c97e815a0de74d121f3744efcfb62f181efd3250f3842b0347d54e9d27c85b41292728927efb3bcc2bdcf8873d92647eb1 + languageName: node + linkType: hard + +"@react-aria/separator@npm:^3.4.10": + version: 3.4.10 + resolution: "@react-aria/separator@npm:3.4.10" + dependencies: + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/706aed510aa306f70ff98a82061995f3fb1ffa6ca0134b282ac9a776441fe4241ff87f8008fa04761b3edaadf8020c96f55720821a7ff4b5c362cad3eb7ed98a + languageName: node + linkType: hard + +"@react-aria/slider@npm:^3.7.21": + version: 3.7.21 + resolution: "@react-aria/slider@npm:3.7.21" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/slider": "npm:^3.6.5" + "@react-types/shared": "npm:^3.30.0" + "@react-types/slider": "npm:^3.7.12" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/1a063533b33f224adf782840664e517a8034a28129648908b7391c648c4141c58103dc9733c81f12b1848320669dcca64a059a3279c4114ae81df796a274da59 + languageName: node + linkType: hard + +"@react-aria/spinbutton@npm:^3.6.16": + version: 3.6.16 + resolution: "@react-aria/spinbutton@npm:3.6.16" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/live-announcer": "npm:^3.4.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/button": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/2f032d3206738902e91661b0e5ff1cdd75005c40b75a219bbf6aab48b3aad16d1d1090c873ab2e1e0400dee74db56c455948ff2ebe0a6c7f1edb200dfea826e5 + languageName: node + linkType: hard + +"@react-aria/ssr@npm:^3.9.9": + version: 3.9.9 + resolution: "@react-aria/ssr@npm:3.9.9" dependencies: "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/a5c8e9ffee1dfd3c5b9f66051a7faab11d53ba001ac7f476b61fa4b38fd8b4835c1a85ff2157ec25fb5b63beb88fbae9e80610fa065a30cbe30875fcbca3114b + checksum: 10/d0675357b51784e9dbe3add97aa0e6acc810ab0ef01d1c7a317ff8ead5eae64d66c60cc751ea3d10f874bb381e445099bb31cb7f1955801848ce6dce91b588a2 languageName: node linkType: hard -"@react-aria/utils@npm:^3.28.1": - version: 3.28.1 - resolution: "@react-aria/utils@npm:3.28.1" +"@react-aria/switch@npm:^3.7.5": + version: 3.7.5 + resolution: "@react-aria/switch@npm:3.7.5" dependencies: - "@react-aria/ssr": "npm:^3.9.7" - "@react-stately/flags": "npm:^3.1.0" - "@react-stately/utils": "npm:^3.10.5" - "@react-types/shared": "npm:^3.28.0" + "@react-aria/toggle": "npm:^3.11.5" + "@react-stately/toggle": "npm:^3.8.5" + "@react-types/shared": "npm:^3.30.0" + "@react-types/switch": "npm:^3.5.12" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/1f716296b444b1db28bd55493ddabbdc403fc48a865cbbd0e383e69e2e42b97e778446383740330cf7e76a4a2dcc0de6f84fed1cddd0f0760b6ffa34118794cb + languageName: node + linkType: hard + +"@react-aria/table@npm:^3.17.5": + version: 3.17.5 + resolution: "@react-aria/table@npm:3.17.5" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/grid": "npm:^3.14.2" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/live-announcer": "npm:^3.4.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-aria/visually-hidden": "npm:^3.8.25" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/flags": "npm:^3.1.2" + "@react-stately/table": "npm:^3.14.3" + "@react-types/checkbox": "npm:^3.9.5" + "@react-types/grid": "npm:^3.3.3" + "@react-types/shared": "npm:^3.30.0" + "@react-types/table": "npm:^3.13.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/d10e2432b66b0279413fec031673172ed8c49d93185cd1657511467b04ec780bc089a7e45d75c9ba71c94419355107e961e679acd254da8a19d446e1c9343a70 + languageName: node + linkType: hard + +"@react-aria/tabs@npm:^3.10.5": + version: 3.10.5 + resolution: "@react-aria/tabs@npm:3.10.5" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/tabs": "npm:^3.8.3" + "@react-types/shared": "npm:^3.30.0" + "@react-types/tabs": "npm:^3.3.16" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/39db9d4d5a0e87e7cbdb91bcdc5385e02fd21cb1e1c181ecf226e6b0fba9c948c03623cceb11d831a87056c6bc3a0528a7e7a9f58292b0c4dfaa6b9cce56f3bb + languageName: node + linkType: hard + +"@react-aria/tag@npm:^3.6.2": + version: 3.6.2 + resolution: "@react-aria/tag@npm:3.6.2" + dependencies: + "@react-aria/gridlist": "npm:^3.13.2" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/list": "npm:^3.12.3" + "@react-types/button": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/fbfb3a9769077bcc23199aacd80c83b42e89f752d0690c6a5751d9a95b6e760365be2915fa134db9ad040355b341b150270f553ece6f791378a27102e90e7fca + languageName: node + linkType: hard + +"@react-aria/textfield@npm:^3.17.5": + version: 3.17.5 + resolution: "@react-aria/textfield@npm:3.17.5" + dependencies: + "@react-aria/form": "npm:^3.0.18" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/shared": "npm:^3.30.0" + "@react-types/textfield": "npm:^3.12.3" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/59f2df9650b8acb961a1eaec59bb8a0b18ef6b0b1f3d8bbc2f398ae4fcfc8b3a2a2c22c357319bb53bc166b4e2c624ab58187104621b164de22f465ab342f72d + languageName: node + linkType: hard + +"@react-aria/toast@npm:^3.0.5": + version: 3.0.5 + resolution: "@react-aria/toast@npm:3.0.5" + dependencies: + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/landmark": "npm:^3.0.4" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/toast": "npm:^3.1.1" + "@react-types/button": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/d674bde2446e29b44fe393386da154d8a467954edf29c9c2cdb922972ea6f962e4f16220c03a410bb0be555b9f96f96f71146762123b9d9187e081a74696bee2 + languageName: node + linkType: hard + +"@react-aria/toggle@npm:^3.11.5": + version: 3.11.5 + resolution: "@react-aria/toggle@npm:3.11.5" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/toggle": "npm:^3.8.5" + "@react-types/checkbox": "npm:^3.9.5" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6490a53496b317d3f12c93e347b2d9dd0e7e4835d66eb0823612d3918836338566c0ed2ac22bf4d94649c6019bf411d9c9a1b8f49d420d52eab8f737c2c3f8c5 + languageName: node + linkType: hard + +"@react-aria/toolbar@npm:3.0.0-beta.18": + version: 3.0.0-beta.18 + resolution: "@react-aria/toolbar@npm:3.0.0-beta.18" + dependencies: + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/0ea1d0d73dad265773678fc66712800840b20558d3e19f0b6aa1f0953ba57e48be9ad293d1f48db3e609b017998ded11227d1aebfb998bcfbe05467498318ced + languageName: node + linkType: hard + +"@react-aria/tooltip@npm:^3.8.5": + version: 3.8.5 + resolution: "@react-aria/tooltip@npm:3.8.5" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/tooltip": "npm:^3.5.5" + "@react-types/shared": "npm:^3.30.0" + "@react-types/tooltip": "npm:^3.4.18" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/4398c4ca41ab302b8e50a86c07909b461275bf5025c0163ecb5ae57bbe98e15164927ed58a1137a235996626b19d5397916736ee3257d8bc33d09ec0b1d3f70e + languageName: node + linkType: hard + +"@react-aria/tree@npm:^3.1.1": + version: 3.1.1 + resolution: "@react-aria/tree@npm:3.1.1" + dependencies: + "@react-aria/gridlist": "npm:^3.13.2" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/tree": "npm:^3.9.0" + "@react-types/button": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/d387de7a9de81df8f45a16882ddb24ecf8da06638822b8be4d66357b0b3edd67a85aa185f8cf947c4c8f9036c396bea406dd5e277ef0f74828a29d2dd86fd809 + languageName: node + linkType: hard + +"@react-aria/utils@npm:^3.29.1": + version: 3.29.1 + resolution: "@react-aria/utils@npm:3.29.1" + dependencies: + "@react-aria/ssr": "npm:^3.9.9" + "@react-stately/flags": "npm:^3.1.2" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/shared": "npm:^3.30.0" "@swc/helpers": "npm:^0.5.0" clsx: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/80c1ecf6a570a57aec5ef4eaa2a8715b12bcd4d5b2de26934e7e5af5a2c38cdd229cac116624ecd3e7c97271e534111f600d0baf1976675b778799f8711ce9bf + checksum: 10/90af5ee5b9c7db063ba66a28cfecb8594f356c2b0fc4cce2fcee94543f3b160214e4a9c280e83355af89452b88b1def8856493705c770874da7121ba1d3d0cc0 languageName: node linkType: hard -"@react-aria/visually-hidden@npm:^3.8.21": - version: 3.8.21 - resolution: "@react-aria/visually-hidden@npm:3.8.21" +"@react-aria/virtualizer@npm:^4.1.7": + version: 4.1.7 + resolution: "@react-aria/virtualizer@npm:4.1.7" dependencies: - "@react-aria/interactions": "npm:^3.24.1" - "@react-aria/utils": "npm:^3.28.1" - "@react-types/shared": "npm:^3.28.0" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-stately/virtualizer": "npm:^4.4.1" + "@react-types/shared": "npm:^3.30.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/50b4598ae295336cd95f38523abdef8b7fbde2b6b52c99a5fa17527474d58e7444199b97d2409dd8c478321c76bf246dd928ddcb1c303ebc8ab04417a5a59516 + checksum: 10/4bb747314ac96da76c64490041ab95f4a0889bab7e5805b0e18cb71bdb290ae73932ddd866657cfcdfac8382d6b3017c3dfb1a91ae415a6d1ab45b11d88f66ec + languageName: node + linkType: hard + +"@react-aria/visually-hidden@npm:^3.8.25": + version: 3.8.25 + resolution: "@react-aria/visually-hidden@npm:3.8.25" + dependencies: + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/362a793beac35fde101e598f4cac9cd98394b9a0171309842e2428b25074ee696358c45d2ded8244bc40a9db8471440994be59b0f347d2c643092c6445aaca61 languageName: node linkType: hard @@ -15972,67 +16783,765 @@ __metadata: languageName: node linkType: hard -"@react-stately/flags@npm:^3.1.0": - version: 3.1.0 - resolution: "@react-stately/flags@npm:3.1.0" +"@react-stately/autocomplete@npm:3.0.0-beta.2": + version: 3.0.0-beta.2 + resolution: "@react-stately/autocomplete@npm:3.0.0-beta.2" dependencies: + "@react-stately/utils": "npm:^3.10.7" "@swc/helpers": "npm:^0.5.0" - checksum: 10/022f183415ad8da9484498c506eed97f7562362b427537eb6325a4f37dcbcf26a52f7d135add1574ea049d6a2baa7b0c7c835fb62ac046ca6e8a3cc43bcaf627 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/836e593688c33ab118547b60fae93ffb1e9e440aed0ffc8c382290f414bfdc313a88964a00a6bd8b7e7b9aca691538b66adfaafee6065b93cedeb80c5aeef675 languageName: node linkType: hard -"@react-stately/overlays@npm:^3.6.14": +"@react-stately/calendar@npm:^3.8.2": + version: 3.8.2 + resolution: "@react-stately/calendar@npm:3.8.2" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/calendar": "npm:^3.7.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/d2858a42dc2c324248c4c1ddb6f6fe66de11bfe2703c85f0fd739075712f668427212207626ed35af2fcbe15cd0ee6e2ad53eafe98766fd7352e0e2f134435c4 + languageName: node + linkType: hard + +"@react-stately/checkbox@npm:^3.6.15": + version: 3.6.15 + resolution: "@react-stately/checkbox@npm:3.6.15" + dependencies: + "@react-stately/form": "npm:^3.1.5" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/checkbox": "npm:^3.9.5" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/4c2b5213395e59432c59609b3cd2ca8316cf77f18716dcee2abe31af5c4a12fac3125408799b0631771e1095fc5924de7850e09c3fa6e53f56c0f79debc0db77 + languageName: node + linkType: hard + +"@react-stately/collections@npm:^3.12.5": + version: 3.12.5 + resolution: "@react-stately/collections@npm:3.12.5" + dependencies: + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/12024f72ec6602c9447e2bc134da566d99da2e29b41ffba207712be31aaf121b805505397dcfb1ee07dd8d234c40d3018c33364adae271f3231a004775a96fd8 + languageName: node + linkType: hard + +"@react-stately/color@npm:^3.8.6": + version: 3.8.6 + resolution: "@react-stately/color@npm:3.8.6" + dependencies: + "@internationalized/number": "npm:^3.6.3" + "@internationalized/string": "npm:^3.2.7" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/numberfield": "npm:^3.9.13" + "@react-stately/slider": "npm:^3.6.5" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/color": "npm:^3.0.6" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/20ec019c71676c5d336c89b703729c0c8098219c942843a3018e5542fee49c6db56c47b746c4c9cddfb9ea48213a3a860861722fc5b41239e3de827c2ef84351 + languageName: node + linkType: hard + +"@react-stately/combobox@npm:^3.10.6": + version: 3.10.6 + resolution: "@react-stately/combobox@npm:3.10.6" + dependencies: + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/list": "npm:^3.12.3" + "@react-stately/overlays": "npm:^3.6.17" + "@react-stately/select": "npm:^3.6.14" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/combobox": "npm:^3.13.6" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/8bc67ee18d1c896620cc11233a8efdaf8ec7f5ec31d068fbbc80e75a54e385617518e2b420f3211c17b905e3cac6026f8b056ae96ee114ea4e897f4ce1951502 + languageName: node + linkType: hard + +"@react-stately/data@npm:^3.13.1": + version: 3.13.1 + resolution: "@react-stately/data@npm:3.13.1" + dependencies: + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/b6eea6a721d4de13ffad7aa12a76012d297e1fbbc919bad35d99f57bf481feb547d56c74467e1dadf8f0cb6be9027e54faa6b10dc4aeb1b46a5c6598218baa2c + languageName: node + linkType: hard + +"@react-stately/datepicker@npm:^3.14.2": + version: 3.14.2 + resolution: "@react-stately/datepicker@npm:3.14.2" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@internationalized/string": "npm:^3.2.7" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/overlays": "npm:^3.6.17" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/datepicker": "npm:^3.12.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/dd10f99f32e12eef9d361eb183cd52113a1857613be844da9895b5a8954260ace283e43bd1bae3d73a71f1cb5e6381627999f8a24d4ed320903dba50f864183b + languageName: node + linkType: hard + +"@react-stately/disclosure@npm:^3.0.5": + version: 3.0.5 + resolution: "@react-stately/disclosure@npm:3.0.5" + dependencies: + "@react-stately/utils": "npm:^3.10.7" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/b1f3f80e6523a0fb0c325064162a59635f560f41dbadd51fd89db02bbad9bafa843b7b1565420444b3b266f2eb92dcf556916b0dabe06d32bb86a2106849c41c + languageName: node + linkType: hard + +"@react-stately/dnd@npm:^3.6.0": + version: 3.6.0 + resolution: "@react-stately/dnd@npm:3.6.0" + dependencies: + "@react-stately/selection": "npm:^3.20.3" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/9fe59a8d5849af27b5fb0609691bdf0ab14ef8cad8a27405e0c83842d44b171e6d1409716b8e80f12797b187bb553f4acf71a87cd5acd86c76137a3525449194 + languageName: node + linkType: hard + +"@react-stately/flags@npm:^3.1.2": + version: 3.1.2 + resolution: "@react-stately/flags@npm:3.1.2" + dependencies: + "@swc/helpers": "npm:^0.5.0" + checksum: 10/a020c3680c36d9624f765c5916ce95d69959f64887928e8f380f11b5362bb0499a901a5842e4e12eb8e5a776af59212b1ee0c4c6a6681ce75f61dace8b2f9c40 + languageName: node + linkType: hard + +"@react-stately/form@npm:^3.1.5": + version: 3.1.5 + resolution: "@react-stately/form@npm:3.1.5" + dependencies: + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6c392949ca8ac045163f3716a41f03d6ccbe3fd5704401cbece0ba572d43abbb350f5019534765540d5b511b9aeba70ca2c03dc4ba218074b59a3efbea601564 + languageName: node + linkType: hard + +"@react-stately/grid@npm:^3.11.3": + version: 3.11.3 + resolution: "@react-stately/grid@npm:3.11.3" + dependencies: + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/selection": "npm:^3.20.3" + "@react-types/grid": "npm:^3.3.3" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/09655c28cef0c3007024af0c01982d2c079e558169dcf1994966c2526c6346b442068e2e9700a8abbbbcbd13781cc0eb5b346d974dc9a85dd5c263722d96f5c7 + languageName: node + linkType: hard + +"@react-stately/layout@npm:^4.3.1": + version: 4.3.1 + resolution: "@react-stately/layout@npm:4.3.1" + dependencies: + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/table": "npm:^3.14.3" + "@react-stately/virtualizer": "npm:^4.4.1" + "@react-types/grid": "npm:^3.3.3" + "@react-types/shared": "npm:^3.30.0" + "@react-types/table": "npm:^3.13.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/f7d299038aefba602944b5caa81719388427778edc394582dfe14c70f9f485b74a0da6d0eeb6aab843e3fbdd6af251a3678b1a8c76c77f64adb16d695dbd22cd + languageName: node + linkType: hard + +"@react-stately/list@npm:^3.12.3": + version: 3.12.3 + resolution: "@react-stately/list@npm:3.12.3" + dependencies: + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/selection": "npm:^3.20.3" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/e8edce5e59e061a4b71621ce7617f4e48cbef5e5c5b6377808e0977f4cad4e541726f255bdeb1c18db867b35ab19883981882bbc27ddbc3e5c26722c496b02f8 + languageName: node + linkType: hard + +"@react-stately/menu@npm:^3.9.5": + version: 3.9.5 + resolution: "@react-stately/menu@npm:3.9.5" + dependencies: + "@react-stately/overlays": "npm:^3.6.17" + "@react-types/menu": "npm:^3.10.2" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/a8163b76de49ec10da7129c306304a96e8f4e689450ffdbf6ab5f56fd8b0316db5beb16ebc58da52c19b4abe28954a7200955c41e1a9088e40f5e5d00f975067 + languageName: node + linkType: hard + +"@react-stately/numberfield@npm:^3.9.13": + version: 3.9.13 + resolution: "@react-stately/numberfield@npm:3.9.13" + dependencies: + "@internationalized/number": "npm:^3.6.3" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/numberfield": "npm:^3.8.12" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/639e2064ed0afd23ec6681b32abca78f9453eb38d11b350247bf4038f6561579f6d444ead6c9c3317c1b1adaaaa22812053cfba8effdd8cb1b199d04b7ce1fbe + languageName: node + linkType: hard + +"@react-stately/overlays@npm:^3.6.17": + version: 3.6.17 + resolution: "@react-stately/overlays@npm:3.6.17" + dependencies: + "@react-stately/utils": "npm:^3.10.7" + "@react-types/overlays": "npm:^3.8.16" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/9c44f181cd8f0b99a40142aebf3d7e1d1df1e96a789b636a5ee877529e60fddece4ebcf32d9812d9848ce0298bf5cb6202df3b680552b32c89f1276bce3f8a7b + languageName: node + linkType: hard + +"@react-stately/radio@npm:^3.10.14": + version: 3.10.14 + resolution: "@react-stately/radio@npm:3.10.14" + dependencies: + "@react-stately/form": "npm:^3.1.5" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/radio": "npm:^3.8.10" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/45ad56b50f3fcd04c1b335f8f64caceaa6f8483fd84717a461ae04ed8718dabf06f88bf8bd89fcca27d2c6bc548b4a38f0e4ef53ad51f3320aeb85c67b288846 + languageName: node + linkType: hard + +"@react-stately/searchfield@npm:^3.5.13": + version: 3.5.13 + resolution: "@react-stately/searchfield@npm:3.5.13" + dependencies: + "@react-stately/utils": "npm:^3.10.7" + "@react-types/searchfield": "npm:^3.6.3" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6114f1ce0c2e8ddbeedc5b5293783c4e1ecb0cc6f24e09cb6095e32be9b49bdc67619941c16f834850e31acfafd74f4241edaa3529f510ec490a2e6c8e7cdc0b + languageName: node + linkType: hard + +"@react-stately/select@npm:^3.6.14": version: 3.6.14 - resolution: "@react-stately/overlays@npm:3.6.14" + resolution: "@react-stately/select@npm:3.6.14" dependencies: - "@react-stately/utils": "npm:^3.10.5" - "@react-types/overlays": "npm:^3.8.13" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/list": "npm:^3.12.3" + "@react-stately/overlays": "npm:^3.6.17" + "@react-types/select": "npm:^3.9.13" + "@react-types/shared": "npm:^3.30.0" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/84ca6cd1a528fd0c94f5c8d0c6609a915fd7de75d70b222ab6e252456f8d67b0a394787aca97e59a9737b7e3d48bf0600d12e5d9487525e37305eea599a96fcf + checksum: 10/33116af11830f0d7bf7e10cb03e52ed9c36d17d94674d96440fd73ae8d7da1b93edc4de6170a3681493c76c0ad5c8b24c2961f170b2a784447743775dce32a65 languageName: node linkType: hard -"@react-stately/utils@npm:^3.10.5": - version: 3.10.5 - resolution: "@react-stately/utils@npm:3.10.5" +"@react-stately/selection@npm:^3.20.3": + version: 3.20.3 + resolution: "@react-stately/selection@npm:3.20.3" + dependencies: + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/9903810f5f4e2f37381aba8c81525a819de69316c3bd5d66b29fc5f22e49b3d349d26792e811f7167e16857879c8e6ce8d817d9838c69367481a42977a2022a2 + languageName: node + linkType: hard + +"@react-stately/slider@npm:^3.6.5": + version: 3.6.5 + resolution: "@react-stately/slider@npm:3.6.5" + dependencies: + "@react-stately/utils": "npm:^3.10.7" + "@react-types/shared": "npm:^3.30.0" + "@react-types/slider": "npm:^3.7.12" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/121f2f23cad929e7087825ec1a17c51610ac0e62a0b0951d042a6e6a354fcea1751575a7bf6648ae5054a260d89a7613dc0353bf0e9f76553e25c2dce1dba095 + languageName: node + linkType: hard + +"@react-stately/table@npm:^3.14.3": + version: 3.14.3 + resolution: "@react-stately/table@npm:3.14.3" + dependencies: + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/flags": "npm:^3.1.2" + "@react-stately/grid": "npm:^3.11.3" + "@react-stately/selection": "npm:^3.20.3" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/grid": "npm:^3.3.3" + "@react-types/shared": "npm:^3.30.0" + "@react-types/table": "npm:^3.13.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/04cd616d526b9b806808ea09ce596dc1fe5fff931deec5ccce3258d0a76ee02c787320004ccc2d3a2d67e54d39f7ab274673210254c8598bd99b767cec8eda1a + languageName: node + linkType: hard + +"@react-stately/tabs@npm:^3.8.3": + version: 3.8.3 + resolution: "@react-stately/tabs@npm:3.8.3" + dependencies: + "@react-stately/list": "npm:^3.12.3" + "@react-types/shared": "npm:^3.30.0" + "@react-types/tabs": "npm:^3.3.16" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/5fb036bc36b5c7fde32c6e7553795fad0f5b2d999837961298341e68d211444ca2a378554c5f73ccf0909e60299cdcd4b0e8c317e57f8c1662e6384957a1ed4a + languageName: node + linkType: hard + +"@react-stately/toast@npm:^3.1.1": + version: 3.1.1 + resolution: "@react-stately/toast@npm:3.1.1" + dependencies: + "@swc/helpers": "npm:^0.5.0" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/c6ff8b947920e05e3e817cbb478f31c45c64035801456c7c8052c37bea5d966a3ea2c74071c2bed6f2006dec160a6b1204b7cc98965a9c10ca3b4bb4d6765f39 + languageName: node + linkType: hard + +"@react-stately/toggle@npm:^3.8.5": + version: 3.8.5 + resolution: "@react-stately/toggle@npm:3.8.5" + dependencies: + "@react-stately/utils": "npm:^3.10.7" + "@react-types/checkbox": "npm:^3.9.5" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/3d8e6c4a39c64fc904355aaa3980e3a63595c24472e3f557716597ac6f9f26299dec5b0843b327b4fe1416436054a65e45ec79073a3be1fad8f093f0d2940864 + languageName: node + linkType: hard + +"@react-stately/tooltip@npm:^3.5.5": + version: 3.5.5 + resolution: "@react-stately/tooltip@npm:3.5.5" + dependencies: + "@react-stately/overlays": "npm:^3.6.17" + "@react-types/tooltip": "npm:^3.4.18" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/0744283f1dbb811502311370495b6722a59c251fe20ac2e5373aeed8f655ec9f89d92980426a69b7191c048f7c02e3a66c78a7018452ad4638c82e5b7f99b698 + languageName: node + linkType: hard + +"@react-stately/tree@npm:^3.9.0": + version: 3.9.0 + resolution: "@react-stately/tree@npm:3.9.0" + dependencies: + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/selection": "npm:^3.20.3" + "@react-stately/utils": "npm:^3.10.7" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6980382c94f87a663124e9488e3cf5f8ea09b05fc2f0017cbb886aa19ce0d8525d8b6b8221f1e293a67543cc06786ea203813218c3444fe7e14b31d9eae65ca3 + languageName: node + linkType: hard + +"@react-stately/utils@npm:^3.10.7": + version: 3.10.7 + resolution: "@react-stately/utils@npm:3.10.7" dependencies: "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/76133eb64fa945216e51d8a81a0ebd06eeb78aa2d9c91d79eeb80ff44c70a6b0d6d940618b31b499fee8216640b3bf6183391151dc1769e756b56ff6b5e167ec + checksum: 10/b9c1a62d4f179e252d08a2f18120a02ffc8704424f257e81e5e74da1222e3acd1e821ce84220d87d9d7b4b6d4dc2fd58d5bb380546d198bf434b700cda8ac3d8 languageName: node linkType: hard -"@react-types/button@npm:^3.11.0": - version: 3.11.0 - resolution: "@react-types/button@npm:3.11.0" +"@react-stately/virtualizer@npm:^4.4.1": + version: 4.4.1 + resolution: "@react-stately/virtualizer@npm:4.4.1" dependencies: - "@react-types/shared": "npm:^3.28.0" + "@react-aria/utils": "npm:^3.29.1" + "@react-types/shared": "npm:^3.30.0" + "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/6dbcc89f576eb6736deb1d5f92eab3c4de5ee5a78a8034b1cd2748c14d92ce05b40362ff5b629842e363d9843f823b5c044dabb3d7438f897777ff60fc5e3867 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/aab5c3a647dc43dd5120a6db4cdfdf24aa47f7fcabf273e05947e33af8cba172f4f442499729d53537a2aa46ca7292dcb94c6b4b4c5b017542df42c25a97a128 languageName: node linkType: hard -"@react-types/overlays@npm:^3.8.13": - version: 3.8.13 - resolution: "@react-types/overlays@npm:3.8.13" +"@react-types/autocomplete@npm:3.0.0-alpha.32": + version: 3.0.0-alpha.32 + resolution: "@react-types/autocomplete@npm:3.0.0-alpha.32" dependencies: - "@react-types/shared": "npm:^3.28.0" + "@react-types/combobox": "npm:^3.13.6" + "@react-types/searchfield": "npm:^3.6.3" + "@react-types/shared": "npm:^3.30.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/a585a2bf211af8996078dee3b7c179617ca889747049f22b8306a6e48c09afa3828a682a46f7be3c36cc1e2282f6bb98b5095c263c466634f1775a1c01599ecd + checksum: 10/ad617628499db45b2acb07d2f4a4b7aa0569124ee1b7c35b2f1eb9819a2d09a755ab4d3be489243c0d579fc16485aec24950ce3b132a655f83d302eb0d140f96 languageName: node linkType: hard -"@react-types/shared@npm:^3.28.0": - version: 3.28.0 - resolution: "@react-types/shared@npm:3.28.0" +"@react-types/breadcrumbs@npm:^3.7.14": + version: 3.7.14 + resolution: "@react-types/breadcrumbs@npm:3.7.14" + dependencies: + "@react-types/link": "npm:^3.6.2" + "@react-types/shared": "npm:^3.30.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10/b8dcc73b950c68fdf22a1173e6e4cfc17f51c579455b3b3239cefd50ebe8bcd6a71db00f643ef8c8f6cd6a2b142780bbce243bda571f5f30be94a04ce30547cb + checksum: 10/106fa21ce01b9ee16796ba46d1133b71b466bfc069e9a90d7f00280e177e1f009791db78701565d83c22506bf72b0469756238164d8ffa36c6712f4e95932eac + languageName: node + linkType: hard + +"@react-types/button@npm:^3.12.2": + version: 3.12.2 + resolution: "@react-types/button@npm:3.12.2" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/d00f7e23fd6cfa863c2d9f0f91ec47fc017666b55775842af3c001fabcf949dfa55b116674bcb841de568eacaf677c3a25e079b4810dcf857547bd9cf6d60917 + languageName: node + linkType: hard + +"@react-types/calendar@npm:^3.7.2": + version: 3.7.2 + resolution: "@react-types/calendar@npm:3.7.2" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/40ad15a5f937844f6626028c8a6e7d36f09e946b4cbdfcd95ff4971f6681abd86260f64aa8298cf86fdde5a0ce2de17e327b8011bb435d0f3faf10fcd9c97f38 + languageName: node + linkType: hard + +"@react-types/checkbox@npm:^3.9.5": + version: 3.9.5 + resolution: "@react-types/checkbox@npm:3.9.5" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/25f6c155043f25f55379b613e3e7faa16191f34c04f13acfc75fc5bda5b83d76d3062b177aad2401f9dd1570215714d477ab1d1e3b92a33e24172aa44e9ff79e + languageName: node + linkType: hard + +"@react-types/color@npm:^3.0.6": + version: 3.0.6 + resolution: "@react-types/color@npm:3.0.6" + dependencies: + "@react-types/shared": "npm:^3.30.0" + "@react-types/slider": "npm:^3.7.12" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/33ddf5239ccfdada1ae276a69b020500dae557a0a43c4bb0fb55fcd6122331c7f2adb552ff3706753214964032ea459634708a02f147777dff948045877cc01b + languageName: node + linkType: hard + +"@react-types/combobox@npm:^3.13.6": + version: 3.13.6 + resolution: "@react-types/combobox@npm:3.13.6" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/8aaf970af426d084ba4b954aeca23cc7779de221a19465c1487d7098a5f637c13bcb6446bf695619e221d78d289cf629b94faa2c86de8bb3f949ff05a5448a13 + languageName: node + linkType: hard + +"@react-types/datepicker@npm:^3.12.2": + version: 3.12.2 + resolution: "@react-types/datepicker@npm:3.12.2" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@react-types/calendar": "npm:^3.7.2" + "@react-types/overlays": "npm:^3.8.16" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/21d6b72986b18b25b6380bf85a7a12319de05160a1dee5d0e7484378627dee955c6cda9fbb0b6909e9edcfc82181be1d9e744220fcf82d75357d7208ed2978a9 + languageName: node + linkType: hard + +"@react-types/dialog@npm:^3.5.19": + version: 3.5.19 + resolution: "@react-types/dialog@npm:3.5.19" + dependencies: + "@react-types/overlays": "npm:^3.8.16" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/07bd55d214d8162f1a700f1ac80cd51468d0defc0d9753309d5e58a0fffbc1eb65a588b6c0709593a41fa8d72cc1f8f8dba0a65b2a336968fdc2ff92afec3596 + languageName: node + linkType: hard + +"@react-types/form@npm:^3.7.13": + version: 3.7.13 + resolution: "@react-types/form@npm:3.7.13" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/2c55ceb9f5e52a08b60ae0ae95d1db3e57a67d3783eaee2fb37870602afd612153506c13effec98bf4c7b58728a148e9d966c8c3442db560543779b9ef8dd091 + languageName: node + linkType: hard + +"@react-types/grid@npm:^3.3.3": + version: 3.3.3 + resolution: "@react-types/grid@npm:3.3.3" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/cac232888139186110f7095cbb45ba2525522a296254f44290ad4c87ac2b1e72485d839e2820e7265cbcd6bc4cb8e49c170c52fd57defed16d06a4eb7f7cf047 + languageName: node + linkType: hard + +"@react-types/link@npm:^3.6.2": + version: 3.6.2 + resolution: "@react-types/link@npm:3.6.2" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/093a3011a98f36dd252d7675365effcba219b9a89e53619e176bdf322042520d5d24e7d9dca04bd63fa6785242f097301c3921d111c43ed888072b0938133435 + languageName: node + linkType: hard + +"@react-types/listbox@npm:^3.7.1": + version: 3.7.1 + resolution: "@react-types/listbox@npm:3.7.1" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/2040a31a6eae1a0d0fe44f2d6410dfd6e9f99849eaba4fea4b4819ed162f2642d5e023d0badb4f78b2e0d60c4480cacc2d0745f9881f77b83c20b7c2012adf0b + languageName: node + linkType: hard + +"@react-types/menu@npm:^3.10.2": + version: 3.10.2 + resolution: "@react-types/menu@npm:3.10.2" + dependencies: + "@react-types/overlays": "npm:^3.8.16" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/9b6cc0818f1d63225f7794ca89d0fa25cd771bad4440fa52c403d81d7027b1f83ba600603ede4ee05dee01b57d1240c9eaa7ed890150aa9dff0c8113dc0c85ab + languageName: node + linkType: hard + +"@react-types/meter@npm:^3.4.10": + version: 3.4.10 + resolution: "@react-types/meter@npm:3.4.10" + dependencies: + "@react-types/progress": "npm:^3.5.13" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/b55f4ca464fc2873662ee50d25cdc1c77995d34d39b6d2831c0b121e47c7b12051017576f51909881a3776003e03263ce899dcf572a9f083794b160320baa754 + languageName: node + linkType: hard + +"@react-types/numberfield@npm:^3.8.12": + version: 3.8.12 + resolution: "@react-types/numberfield@npm:3.8.12" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/98989cdcd7b7bef45129c54e9bdb919d06f09109c786f0783105c617304ee3ce15c9aad01507d4d9aa0e974ee3b50b230c057ad7a48f634247e374c9668080ae + languageName: node + linkType: hard + +"@react-types/overlays@npm:^3.8.16": + version: 3.8.16 + resolution: "@react-types/overlays@npm:3.8.16" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/12bedf8e79d31827cd9868dcb115d07b9fee7954af5e00c1a46fe87b9f2b08e971af4b1b238faa2ce7ed173600786a83c7b9cfcdee5c7464a42bc581d7dee3a6 + languageName: node + linkType: hard + +"@react-types/progress@npm:^3.5.13": + version: 3.5.13 + resolution: "@react-types/progress@npm:3.5.13" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/f666892cff510323e90b1e72e5351eef4f38eb752e568f46cb30250a7de8b9a4435f3506fc590d748d3a0ce30a5f64d449a88a8656369137d06a584ef667d0a8 + languageName: node + linkType: hard + +"@react-types/radio@npm:^3.8.10": + version: 3.8.10 + resolution: "@react-types/radio@npm:3.8.10" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/ba87e61b7a84547c417baa9553bdfebf2fcd03d45038f0d6a0031a5d4e6c94f9d9a81fe8a98579c6830dcffad68c71560ccac8fd842d5f2469a05ce275d92250 + languageName: node + linkType: hard + +"@react-types/searchfield@npm:^3.6.3": + version: 3.6.3 + resolution: "@react-types/searchfield@npm:3.6.3" + dependencies: + "@react-types/shared": "npm:^3.30.0" + "@react-types/textfield": "npm:^3.12.3" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/ffe5a909fb94a28c674c1ae798e511d93be5175c3fc8adcadc88e040c6e5aca4cf55565b321219311537c11c5ce70c005aa214d8dc708a4b1583a1f1a68a75f6 + languageName: node + linkType: hard + +"@react-types/select@npm:^3.9.13": + version: 3.9.13 + resolution: "@react-types/select@npm:3.9.13" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/9fba560d2060eb852289be76f0e0b691bddd48f744f969862da10d38c8963f6931190cb53cc8c5ea01eab3f447b8a680e6da378fe6287ec80d3f48a62d312759 + languageName: node + linkType: hard + +"@react-types/shared@npm:^3.30.0": + version: 3.30.0 + resolution: "@react-types/shared@npm:3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/58af4134275a347bf18ac5d15e3dcfd07dcf623e1d494f140303e06f2149b7aad86408979e163393346df613f6390a3a387f77a2937f0bf1908c6672a44f7b69 + languageName: node + linkType: hard + +"@react-types/slider@npm:^3.7.12": + version: 3.7.12 + resolution: "@react-types/slider@npm:3.7.12" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/38b8a556f87bb64c7f47af482a1a3ce688345a3d4b7a2c71e99522bdcb9668f8ed8e3ea0797b32d5ac1a95d6557c5f9e8eadab51e8eb780f9e66efa89ba50fa0 + languageName: node + linkType: hard + +"@react-types/switch@npm:^3.5.12": + version: 3.5.12 + resolution: "@react-types/switch@npm:3.5.12" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6552f113b0c3abf52f590b8cedf17f5901e7fa2d0ed94b30408fd2fbda907ee17e7792af6d86985ca742a3cb5b667bcbf6cec725e66265ccdfd201b77e7f524b + languageName: node + linkType: hard + +"@react-types/table@npm:^3.13.1": + version: 3.13.1 + resolution: "@react-types/table@npm:3.13.1" + dependencies: + "@react-types/grid": "npm:^3.3.3" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/be1bac716d13148b47016f2156ee0769c044d301b538df934f6b8aa5466265a750391e462f9fcdebff7dbb93cf9e373e2117f91eab528b3f5cef062174e547ee + languageName: node + linkType: hard + +"@react-types/tabs@npm:^3.3.16": + version: 3.3.16 + resolution: "@react-types/tabs@npm:3.3.16" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/fae8d98776408b0e9776d0217a545a1ed6035ac976321e413e4f442b525375c56854a328c33e77564efe525e66bc7b79dc89e38ff02373788b996c4b46bb6c5d + languageName: node + linkType: hard + +"@react-types/textfield@npm:^3.12.3": + version: 3.12.3 + resolution: "@react-types/textfield@npm:3.12.3" + dependencies: + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/d8bf58fd569b30fa13f98de834a025c9ef71d6de8c669912aa7083c83f88f7261852e0b1b30953e903d377b5b63cc849b848fd549a1466c82d6a74cb6c540cf5 + languageName: node + linkType: hard + +"@react-types/tooltip@npm:^3.4.18": + version: 3.4.18 + resolution: "@react-types/tooltip@npm:3.4.18" + dependencies: + "@react-types/overlays": "npm:^3.8.16" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/5eed69c83727496a6d06796617da86740ab2629b3bd6a106c6ddfb384aaa74725aed3c3eed44f329dcebe7cf87a261ce33e8aa8a5899549446321c1a86d27b87 languageName: node linkType: hard @@ -41977,6 +43486,98 @@ __metadata: languageName: node linkType: hard +"react-aria-components@npm:^1.10.1": + version: 1.10.1 + resolution: "react-aria-components@npm:1.10.1" + dependencies: + "@internationalized/date": "npm:^3.8.2" + "@internationalized/string": "npm:^3.2.7" + "@react-aria/autocomplete": "npm:3.0.0-beta.5" + "@react-aria/collections": "npm:3.0.0-rc.3" + "@react-aria/dnd": "npm:^3.10.1" + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/live-announcer": "npm:^3.4.3" + "@react-aria/overlays": "npm:^3.27.3" + "@react-aria/ssr": "npm:^3.9.9" + "@react-aria/toolbar": "npm:3.0.0-beta.18" + "@react-aria/utils": "npm:^3.29.1" + "@react-aria/virtualizer": "npm:^4.1.7" + "@react-stately/autocomplete": "npm:3.0.0-beta.2" + "@react-stately/layout": "npm:^4.3.1" + "@react-stately/selection": "npm:^3.20.3" + "@react-stately/table": "npm:^3.14.3" + "@react-stately/utils": "npm:^3.10.7" + "@react-stately/virtualizer": "npm:^4.4.1" + "@react-types/form": "npm:^3.7.13" + "@react-types/grid": "npm:^3.3.3" + "@react-types/shared": "npm:^3.30.0" + "@react-types/table": "npm:^3.13.1" + "@swc/helpers": "npm:^0.5.0" + client-only: "npm:^0.0.1" + react-aria: "npm:^3.41.1" + react-stately: "npm:^3.39.0" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6fcc35404bdd8718bbfb05263bd0c67157d2ba665e0446c47f42f78262ef92231bb0512fc7734c2114bb20c28fff8eb13694c9719c741d2c7a59181fd8fdf115 + languageName: node + linkType: hard + +"react-aria@npm:^3.41.1": + version: 3.41.1 + resolution: "react-aria@npm:3.41.1" + dependencies: + "@internationalized/string": "npm:^3.2.7" + "@react-aria/breadcrumbs": "npm:^3.5.26" + "@react-aria/button": "npm:^3.13.3" + "@react-aria/calendar": "npm:^3.8.3" + "@react-aria/checkbox": "npm:^3.15.7" + "@react-aria/color": "npm:^3.0.9" + "@react-aria/combobox": "npm:^3.12.5" + "@react-aria/datepicker": "npm:^3.14.5" + "@react-aria/dialog": "npm:^3.5.27" + "@react-aria/disclosure": "npm:^3.0.6" + "@react-aria/dnd": "npm:^3.10.1" + "@react-aria/focus": "npm:^3.20.5" + "@react-aria/gridlist": "npm:^3.13.2" + "@react-aria/i18n": "npm:^3.12.10" + "@react-aria/interactions": "npm:^3.25.3" + "@react-aria/label": "npm:^3.7.19" + "@react-aria/landmark": "npm:^3.0.4" + "@react-aria/link": "npm:^3.8.3" + "@react-aria/listbox": "npm:^3.14.6" + "@react-aria/menu": "npm:^3.18.5" + "@react-aria/meter": "npm:^3.4.24" + "@react-aria/numberfield": "npm:^3.11.16" + "@react-aria/overlays": "npm:^3.27.3" + "@react-aria/progress": "npm:^3.4.24" + "@react-aria/radio": "npm:^3.11.5" + "@react-aria/searchfield": "npm:^3.8.6" + "@react-aria/select": "npm:^3.15.7" + "@react-aria/selection": "npm:^3.24.3" + "@react-aria/separator": "npm:^3.4.10" + "@react-aria/slider": "npm:^3.7.21" + "@react-aria/ssr": "npm:^3.9.9" + "@react-aria/switch": "npm:^3.7.5" + "@react-aria/table": "npm:^3.17.5" + "@react-aria/tabs": "npm:^3.10.5" + "@react-aria/tag": "npm:^3.6.2" + "@react-aria/textfield": "npm:^3.17.5" + "@react-aria/toast": "npm:^3.0.5" + "@react-aria/tooltip": "npm:^3.8.5" + "@react-aria/tree": "npm:^3.1.1" + "@react-aria/utils": "npm:^3.29.1" + "@react-aria/visually-hidden": "npm:^3.8.25" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/6ec619bc9b26800516e22054a428ea29ebc7bcfabf762918893a5a1a73d1576dd4b1c99d85aae02ae69eb16a49d1a765ab34cb840a500134a76e145a4e995f42 + languageName: node + linkType: hard + "react-base16-styling@npm:^0.6.0": version: 0.6.0 resolution: "react-base16-styling@npm:0.6.0" @@ -42572,6 +44173,42 @@ __metadata: languageName: node linkType: hard +"react-stately@npm:^3.39.0": + version: 3.39.0 + resolution: "react-stately@npm:3.39.0" + dependencies: + "@react-stately/calendar": "npm:^3.8.2" + "@react-stately/checkbox": "npm:^3.6.15" + "@react-stately/collections": "npm:^3.12.5" + "@react-stately/color": "npm:^3.8.6" + "@react-stately/combobox": "npm:^3.10.6" + "@react-stately/data": "npm:^3.13.1" + "@react-stately/datepicker": "npm:^3.14.2" + "@react-stately/disclosure": "npm:^3.0.5" + "@react-stately/dnd": "npm:^3.6.0" + "@react-stately/form": "npm:^3.1.5" + "@react-stately/list": "npm:^3.12.3" + "@react-stately/menu": "npm:^3.9.5" + "@react-stately/numberfield": "npm:^3.9.13" + "@react-stately/overlays": "npm:^3.6.17" + "@react-stately/radio": "npm:^3.10.14" + "@react-stately/searchfield": "npm:^3.5.13" + "@react-stately/select": "npm:^3.6.14" + "@react-stately/selection": "npm:^3.20.3" + "@react-stately/slider": "npm:^3.6.5" + "@react-stately/table": "npm:^3.14.3" + "@react-stately/tabs": "npm:^3.8.3" + "@react-stately/toast": "npm:^3.1.1" + "@react-stately/toggle": "npm:^3.8.5" + "@react-stately/tooltip": "npm:^3.5.5" + "@react-stately/tree": "npm:^3.9.0" + "@react-types/shared": "npm:^3.30.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10/f59f86aca0797e389698fc20a7e88a26748e00e56e011dd6d9f7a4fa1ba7fda32083bc8e5f2af000957d20ad9f4c74dd151cb667b958dcac8f3959d860905fd4 + languageName: node + linkType: hard + "react-style-singleton@npm:^2.2.1": version: 2.2.1 resolution: "react-style-singleton@npm:2.2.1" From 98f02a6cebeeae88a77e1c365879c07896039dbb Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sat, 14 Jun 2025 15:34:24 +0100 Subject: [PATCH 097/301] Improve styles Signed-off-by: Charles de Dreuille --- .changeset/stupid-candles-hug.md | 5 ++ packages/canon/css/components.css | 58 +++++++++++++++++++ packages/canon/css/styles.css | 58 +++++++++++++++++++ packages/canon/css/switch.css | 57 ++++++++++++++++++ packages/canon/report.api.md | 11 ++++ .../src/components/Switch/Switch.styles.css | 2 +- .../canon/src/components/Switch/Switch.tsx | 1 + .../canon/src/components/Switch/index.tsx | 3 +- packages/canon/src/components/Switch/types.ts | 4 ++ 9 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 .changeset/stupid-candles-hug.md create mode 100644 packages/canon/css/switch.css diff --git a/.changeset/stupid-candles-hug.md b/.changeset/stupid-candles-hug.md new file mode 100644 index 0000000000..040eb62947 --- /dev/null +++ b/.changeset/stupid-candles-hug.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Add new Switch component in Canon. diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index f3be22720c..1b60bf74ed 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -1332,3 +1332,61 @@ width: 100%; overflow: hidden; } + +.canon-Switch { + align-items: center; + gap: var(--canon-space-3); + font-size: var(--canon-font-size-3); + color: var(--canon-fg-primary); + cursor: pointer; + display: flex; + position: relative; + + &[data-pressed] .canon-SwitchIndicator { + &:before { + background: var(--canon-fg-solid); + } + } + + &[data-selected] { + & .canon-SwitchIndicator { + background: var(--canon-bg-solid); + + &:before { + background: var(--canon-fg-solid); + transform: translateX(100%); + } + } + + &[data-pressed] { + & .indicator { + background: var(--canon-gray-3); + } + } + } + + &[data-focus-visible] .canon-SwitchIndicator { + outline-offset: 2px; + outline: 2px solid; + } +} + +.canon-SwitchIndicator { + background: var(--canon-gray-3); + border: 2px; + border-radius: 1.143rem; + width: 2rem; + height: 1.143rem; + transition: all .2s; + + &:before { + content: ""; + background: var(--canon-fg-solid); + border-radius: 16px; + width: .857rem; + height: .857rem; + margin: .143rem; + transition: all .2s; + display: block; + } +} diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index 8e98996d6d..83329e817c 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -10556,3 +10556,61 @@ width: 100%; overflow: hidden; } + +.canon-Switch { + align-items: center; + gap: var(--canon-space-3); + font-size: var(--canon-font-size-3); + color: var(--canon-fg-primary); + cursor: pointer; + display: flex; + position: relative; + + &[data-pressed] .canon-SwitchIndicator { + &:before { + background: var(--canon-fg-solid); + } + } + + &[data-selected] { + & .canon-SwitchIndicator { + background: var(--canon-bg-solid); + + &:before { + background: var(--canon-fg-solid); + transform: translateX(100%); + } + } + + &[data-pressed] { + & .indicator { + background: var(--canon-gray-3); + } + } + } + + &[data-focus-visible] .canon-SwitchIndicator { + outline-offset: 2px; + outline: 2px solid; + } +} + +.canon-SwitchIndicator { + background: var(--canon-gray-3); + border: 2px; + border-radius: 1.143rem; + width: 2rem; + height: 1.143rem; + transition: all .2s; + + &:before { + content: ""; + background: var(--canon-fg-solid); + border-radius: 16px; + width: .857rem; + height: .857rem; + margin: .143rem; + transition: all .2s; + display: block; + } +} diff --git a/packages/canon/css/switch.css b/packages/canon/css/switch.css new file mode 100644 index 0000000000..ee517543d7 --- /dev/null +++ b/packages/canon/css/switch.css @@ -0,0 +1,57 @@ +.canon-Switch { + align-items: center; + gap: var(--canon-space-3); + font-size: var(--canon-font-size-3); + color: var(--canon-fg-primary); + cursor: pointer; + display: flex; + position: relative; + + &[data-pressed] .canon-SwitchIndicator { + &:before { + background: var(--canon-fg-solid); + } + } + + &[data-selected] { + & .canon-SwitchIndicator { + background: var(--canon-bg-solid); + + &:before { + background: var(--canon-fg-solid); + transform: translateX(100%); + } + } + + &[data-pressed] { + & .indicator { + background: var(--canon-gray-3); + } + } + } + + &[data-focus-visible] .canon-SwitchIndicator { + outline-offset: 2px; + outline: 2px solid; + } +} + +.canon-SwitchIndicator { + background: var(--canon-gray-3); + border: 2px; + border-radius: 1.143rem; + width: 2rem; + height: 1.143rem; + transition: all .2s; + + &:before { + content: ""; + background: var(--canon-fg-solid); + border-radius: 16px; + width: .857rem; + height: .857rem; + margin: .143rem; + transition: all .2s; + display: block; + } +} diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index ceec381c83..bcc32ef651 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -22,6 +22,7 @@ import { ReactNode } from 'react'; import { RefAttributes } from 'react'; import type { RemixiconComponentType } from '@remixicon/react'; import { ScrollArea as ScrollArea_2 } from '@base-ui-components/react/scroll-area'; +import type { SwitchProps as SwitchProps_2 } from 'react-aria-components'; import { Table as Table_2 } from '@tanstack/react-table'; import { Tabs as Tabs_2 } from '@base-ui-components/react/tabs'; import { TdHTMLAttributes } from 'react'; @@ -1159,6 +1160,16 @@ export type StylingPropDef = { parseValue?: (value: string) => string | undefined; }; +// @public (undocumented) +export const Switch: ForwardRefExoticComponent< + SwitchProps & RefAttributes +>; + +// @public (undocumented) +export interface SwitchProps extends SwitchProps_2 { + label?: string; +} + // @public export const Table: { Root: ForwardRefExoticComponent< diff --git a/packages/canon/src/components/Switch/Switch.styles.css b/packages/canon/src/components/Switch/Switch.styles.css index b328414aa0..98368efa13 100644 --- a/packages/canon/src/components/Switch/Switch.styles.css +++ b/packages/canon/src/components/Switch/Switch.styles.css @@ -32,7 +32,7 @@ &[data-selected] { .canon-SwitchIndicator { - background: var(--canon-gray-3); + background: var(--canon-bg-solid); &:before { background: var(--canon-fg-solid); diff --git a/packages/canon/src/components/Switch/Switch.tsx b/packages/canon/src/components/Switch/Switch.tsx index b84a8c9c5f..78bbaa3930 100644 --- a/packages/canon/src/components/Switch/Switch.tsx +++ b/packages/canon/src/components/Switch/Switch.tsx @@ -18,6 +18,7 @@ import { forwardRef } from 'react'; import { Switch as AriaSwitch } from 'react-aria-components'; import type { SwitchProps } from './types'; +/** @public */ export const Switch = forwardRef( ({ label, ...props }, ref) => { return ( diff --git a/packages/canon/src/components/Switch/index.tsx b/packages/canon/src/components/Switch/index.tsx index 10a5429188..171969809d 100644 --- a/packages/canon/src/components/Switch/index.tsx +++ b/packages/canon/src/components/Switch/index.tsx @@ -14,4 +14,5 @@ * limitations under the License. */ -export { Switch } from './Switch'; +export * from './Switch'; +export * from './types'; diff --git a/packages/canon/src/components/Switch/types.ts b/packages/canon/src/components/Switch/types.ts index 1b4fcd66ed..725671edd5 100644 --- a/packages/canon/src/components/Switch/types.ts +++ b/packages/canon/src/components/Switch/types.ts @@ -16,6 +16,10 @@ import type { SwitchProps as AriaSwitchProps } from 'react-aria-components'; +/** @public */ export interface SwitchProps extends AriaSwitchProps { + /** + * The label of the switch + */ label?: string; } From 81c79f5df187b94a25390a51c752bd898d4c2dc1 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sun, 15 Jun 2025 22:36:11 +0100 Subject: [PATCH 098/301] Add docs for Switch Signed-off-by: Charles de Dreuille --- canon-docs/src/content/components/switch.mdx | 41 +++++++++++ .../src/content/components/switch.props.ts | 71 +++++++++++++++++++ canon-docs/src/snippets/stories-snippets.tsx | 12 ++++ canon-docs/src/utils/changelog.ts | 10 ++- canon-docs/src/utils/data.ts | 5 ++ .../src/components/Switch/Switch.stories.tsx | 7 ++ 6 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 canon-docs/src/content/components/switch.mdx create mode 100644 canon-docs/src/content/components/switch.props.ts diff --git a/canon-docs/src/content/components/switch.mdx b/canon-docs/src/content/components/switch.mdx new file mode 100644 index 0000000000..068ec96217 --- /dev/null +++ b/canon-docs/src/content/components/switch.mdx @@ -0,0 +1,41 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { SwitchSnippet } from '@/snippets/stories-snippets'; +import { switchPropDefs, snippetUsage } from './switch.props'; +import { ComponentInfos } from '@/components/ComponentInfos'; + +# Switch + +A control that indicates whether a setting is on or off. + +} + code={``} +/> + + + +## API reference + + + +## Examples + +### Disabled + +A switch can be disabled using the `isDisabled` prop. + +} + code={``} +/> diff --git a/canon-docs/src/content/components/switch.props.ts b/canon-docs/src/content/components/switch.props.ts new file mode 100644 index 0000000000..ddbca4e049 --- /dev/null +++ b/canon-docs/src/content/components/switch.props.ts @@ -0,0 +1,71 @@ +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', + }, + isSelected: { + type: 'boolean', + }, + label: { + type: 'string', + }, + name: { + type: 'string', + }, + onChange: { + type: 'enum', + values: ['(isSelected: boolean) => void'], + }, + onFocus: { + type: 'enum', + values: ['(e: FocusEvent) => void'], + }, + onBlur: { + type: 'enum', + values: ['(e: FocusEvent) => void'], + }, + onFocusChange: { + type: 'enum', + values: ['(isFocused: boolean) => void'], + }, + 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', + }, +}; + +export const snippetUsage = `import { Switch } from '@backstage/canon'; + +`; diff --git a/canon-docs/src/snippets/stories-snippets.tsx b/canon-docs/src/snippets/stories-snippets.tsx index 64047ebde9..95738a6330 100644 --- a/canon-docs/src/snippets/stories-snippets.tsx +++ b/canon-docs/src/snippets/stories-snippets.tsx @@ -18,6 +18,7 @@ import * as LinkStories from '../../../packages/canon/src/components/Link/Link.s import * as AvatarStories from '../../../packages/canon/src/components/Avatar/Avatar.stories'; import * as CollapsibleStories from '../../../packages/canon/src/components/Collapsible/Collapsible.stories'; import * as TabsStories from '../../../packages/canon/src/components/Tabs/Tabs.stories'; +import * as SwitchStories from '../../../packages/canon/src/components/Switch/Switch.stories'; export const BoxSnippet = ({ story }: { story: keyof typeof BoxStories }) => { const stories = composeStories(BoxStories); @@ -173,3 +174,14 @@ export const TabsSnippet = ({ story }: { story: keyof typeof TabsStories }) => { return StoryComponent ? : null; }; + +export const SwitchSnippet = ({ + story, +}: { + story: keyof typeof SwitchStories; +}) => { + const stories = composeStories(SwitchStories); + const StoryComponent = stories[story as keyof typeof stories]; + + return StoryComponent ? : null; +}; diff --git a/canon-docs/src/utils/changelog.ts b/canon-docs/src/utils/changelog.ts index 1c15f5c264..94317ab78b 100644 --- a/canon-docs/src/utils/changelog.ts +++ b/canon-docs/src/utils/changelog.ts @@ -17,7 +17,8 @@ export type Component = | 'link' | 'tooltip' | 'scrollarea' - | 'flex'; + | 'flex' + | 'switch'; export type Version = `${number}.${number}.${number}`; @@ -30,6 +31,13 @@ export interface ChangelogProps { } export const changelog: ChangelogProps[] = [ + { + components: ['switch'], + version: '0.5.0', + description: '✨ Add new `Switch` component', + platform: 'portal', + prs: ['30251'], + }, { components: ['tabs'], version: '0.4.0', diff --git a/canon-docs/src/utils/data.ts b/canon-docs/src/utils/data.ts index 58f44f764d..20ff9146a7 100644 --- a/canon-docs/src/utils/data.ts +++ b/canon-docs/src/utils/data.ts @@ -116,6 +116,11 @@ export const components: Page[] = [ slug: 'select', status: 'alpha', }, + { + title: 'Switch', + slug: 'switch', + status: 'alpha', + }, { title: 'Table', slug: 'table', diff --git a/packages/canon/src/components/Switch/Switch.stories.tsx b/packages/canon/src/components/Switch/Switch.stories.tsx index 8350d0f4e5..165fefc97b 100644 --- a/packages/canon/src/components/Switch/Switch.stories.tsx +++ b/packages/canon/src/components/Switch/Switch.stories.tsx @@ -30,3 +30,10 @@ export const Default: Story = { label: 'Switch', }, }; + +export const Disabled: Story = { + args: { + ...Default.args, + isDisabled: true, + }, +}; From b7b236914885631b231f8cd388cfa40af03777bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 16 Jun 2025 09:32:16 +0200 Subject: [PATCH 099/301] fix test flake due to missing timeout bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- plugins/events-backend/src/service/EventsPlugin.test.ts | 2 ++ .../src/service/hub/DatabaseEventBusStore.test.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/plugins/events-backend/src/service/EventsPlugin.test.ts b/plugins/events-backend/src/service/EventsPlugin.test.ts index 12a0f63002..2c3fb3b909 100644 --- a/plugins/events-backend/src/service/EventsPlugin.test.ts +++ b/plugins/events-backend/src/service/EventsPlugin.test.ts @@ -34,6 +34,8 @@ import { TestEventsService } from '@backstage/plugin-events-backend-test-utils'; import request from 'supertest'; import { eventsPlugin } from './EventsPlugin'; +jest.setTimeout(60_000); + describe('eventsPlugin', () => { it('should be initialized properly', async () => { const eventsService = new TestEventsService(); diff --git a/plugins/events-backend/src/service/hub/DatabaseEventBusStore.test.ts b/plugins/events-backend/src/service/hub/DatabaseEventBusStore.test.ts index 959341cbc0..5664475851 100644 --- a/plugins/events-backend/src/service/hub/DatabaseEventBusStore.test.ts +++ b/plugins/events-backend/src/service/hub/DatabaseEventBusStore.test.ts @@ -21,6 +21,8 @@ import { } from '@backstage/backend-test-utils'; import { DatabaseEventBusStore } from './DatabaseEventBusStore'; +jest.setTimeout(60_000); + const logger = mockServices.logger.mock(); const databases = TestDatabases.create({ From 99c860c54fafcaee6e883da9f7be8a3790653758 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:09:24 +0000 Subject: [PATCH 100/301] fix(deps): update codemirror to v6.11.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/yarn.lock b/yarn.lock index 1b76cdc0b6..7147289913 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9289,8 +9289,8 @@ __metadata: linkType: hard "@codemirror/language@npm:^6.0.0": - version: 6.11.0 - resolution: "@codemirror/language@npm:6.11.0" + version: 6.11.1 + resolution: "@codemirror/language@npm:6.11.1" dependencies: "@codemirror/state": "npm:^6.0.0" "@codemirror/view": "npm:^6.23.0" @@ -9298,7 +9298,7 @@ __metadata: "@lezer/highlight": "npm:^1.0.0" "@lezer/lr": "npm:^1.0.0" style-mod: "npm:^4.0.0" - checksum: 10/571d8d18c51173c25dc3a1c1327fa2265e3d666acd2f2e3dbab828f49bc7f350159bbe8d46ee4baf88cb3eb0fbcfb3351028df6e2981455a55fd2951254c950f + checksum: 10/024969113d61ccb5d497b75a8a9875d43e1bfc8466de2819bb5db23f01b200937366800a812e8d33eb5e34e3a5d2aa22d0ff8205f34a5e8aeb4cd1d221bcaa78 languageName: node linkType: hard @@ -9355,13 +9355,14 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.23.0": - version: 6.36.8 - resolution: "@codemirror/view@npm:6.36.8" + version: 6.37.2 + resolution: "@codemirror/view@npm:6.37.2" dependencies: "@codemirror/state": "npm:^6.5.0" + crelt: "npm:^1.0.6" style-mod: "npm:^4.1.0" w3c-keyname: "npm:^2.2.4" - checksum: 10/cb2cbfb8a41ee1d6cfb17c2ea91b9763e692f85d523b55276cc46ab7fda3f6ba0bcc03e2194ad6cf6b413ec34d309ab408300c19b62d5d6d6f9e2071fb99d489 + checksum: 10/410ef68c5bd3395c556f5a1298afd6a37eecd88b825435dc19ce7143b0eb5c4114486b444e56fd156371fcc329ad9425635c0703322384ff582a75a4d61727a7 languageName: node linkType: hard @@ -26783,10 +26784,10 @@ __metadata: languageName: node linkType: hard -"crelt@npm:^1.0.5": - version: 1.0.5 - resolution: "crelt@npm:1.0.5" - checksum: 10/f692219f8550570b35d26f64a34945a016ec10c2714491d1306b95253e4b6bca609b3c73aee68e1c2035eab6cf38b6c853c4c16f80b26a808e688d25c6821b25 +"crelt@npm:^1.0.5, crelt@npm:^1.0.6": + version: 1.0.6 + resolution: "crelt@npm:1.0.6" + checksum: 10/5ed326ca6bd243b1dba6b943f665b21c2c04be03271824bc48f20dba324b0f8233e221f8c67312526d24af2b1243c023dc05a41bd8bd05d1a479fd2c72fb39c3 languageName: node linkType: hard From 0169b23ed343f80d4843f77827380c8e3bb284dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 16 Jun 2025 10:43:28 +0200 Subject: [PATCH 101/301] just a few more circular dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/nice-ties-film.md | 9 +++++++++ .../src/apis/definitions/TranslationApi.ts | 2 +- packages/frontend-app-api/src/routing/collectRouteIds.ts | 2 +- packages/frontend-plugin-api/report.api.md | 3 ++- .../frontend-plugin-api/src/wiring/coreExtensionData.ts | 2 +- plugins/auth-node/report.api.md | 6 ++---- .../src/identity/prepareBackstageIdentityResponse.ts | 5 +---- plugins/catalog-node/src/api/provider.ts | 2 +- plugins/catalog-node/src/processing/types.ts | 2 +- 9 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 .changeset/nice-ties-film.md diff --git a/.changeset/nice-ties-film.md b/.changeset/nice-ties-film.md new file mode 100644 index 0000000000..2d7be04222 --- /dev/null +++ b/.changeset/nice-ties-film.md @@ -0,0 +1,9 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-app-api': patch +'@backstage/core-plugin-api': patch +'@backstage/plugin-catalog-node': patch +'@backstage/plugin-auth-node': patch +--- + +Internal tweak to avoid circular dependencies diff --git a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts b/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts index 7c3c3f8e83..5cfb14abb2 100644 --- a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts @@ -16,7 +16,7 @@ import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; import { Expand, ExpandRecursive, Observable } from '@backstage/types'; -import { TranslationRef } from '../../translation'; +import { TranslationRef } from '../../translation/TranslationRef'; import { JSX } from 'react'; /** diff --git a/packages/frontend-app-api/src/routing/collectRouteIds.ts b/packages/frontend-app-api/src/routing/collectRouteIds.ts index 8b23e9e80b..6b0527f670 100644 --- a/packages/frontend-app-api/src/routing/collectRouteIds.ts +++ b/packages/frontend-app-api/src/routing/collectRouteIds.ts @@ -29,7 +29,7 @@ import { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/rou // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalSubRouteRef } from '../../../frontend-plugin-api/src/routing/SubRouteRef'; import { OpaqueFrontendPlugin } from '@internal/frontend'; -import { FrontendFeature } from '../wiring'; +import { FrontendFeature } from '../wiring/types'; /** @internal */ export interface RouteRefsById { diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index 9ac89d8331..6101f3e7c9 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -8,6 +8,7 @@ import { alertApiRef } from '@backstage/core-plugin-api'; import { AlertMessage } from '@backstage/core-plugin-api'; import { AnyApiFactory } from '@backstage/core-plugin-api'; import { AnyApiRef } from '@backstage/core-plugin-api'; +import { AnyRouteRefParams as AnyRouteRefParams_2 } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/core-plugin-api'; import { ApiHolder } from '@backstage/core-plugin-api'; import { ApiRef } from '@backstage/core-plugin-api'; @@ -392,7 +393,7 @@ export const coreExtensionData: { >; routePath: ConfigurableExtensionDataRef; routeRef: ConfigurableExtensionDataRef< - RouteRef, + RouteRef, 'core.routing.ref', {} >; diff --git a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts index cb48cb8df3..18466d8196 100644 --- a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts +++ b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts @@ -15,7 +15,7 @@ */ import { JSX } from 'react'; -import { RouteRef } from '../routing'; +import { RouteRef } from '../routing/RouteRef'; import { createExtensionDataRef } from './createExtensionDataRef'; /** @public */ diff --git a/plugins/auth-node/report.api.md b/plugins/auth-node/report.api.md index 090f981636..69e953cf13 100644 --- a/plugins/auth-node/report.api.md +++ b/plugins/auth-node/report.api.md @@ -3,8 +3,6 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackstageIdentityResponse as BackstageIdentityResponse_2 } from '@backstage/plugin-auth-node'; -import { BackstageSignInResult as BackstageSignInResult_2 } from '@backstage/plugin-auth-node'; import { Config } from '@backstage/config'; import { DiscoveryService } from '@backstage/backend-plugin-api'; import { Entity } from '@backstage/catalog-model'; @@ -575,8 +573,8 @@ export type PassportProfile = Profile & { // @public export function prepareBackstageIdentityResponse( - result: BackstageSignInResult_2, -): BackstageIdentityResponse_2; + result: BackstageSignInResult, +): BackstageIdentityResponse; // @public export type ProfileInfo = { diff --git a/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.ts b/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.ts index a1de4e365c..aa827cef3f 100644 --- a/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.ts +++ b/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.ts @@ -15,10 +15,7 @@ */ import { InputError } from '@backstage/errors'; -import { - BackstageIdentityResponse, - BackstageSignInResult, -} from '@backstage/plugin-auth-node'; +import { BackstageIdentityResponse, BackstageSignInResult } from '../types'; function parseJwtPayload(token: string) { const [_header, payload, _signature] = token.split('.'); diff --git a/plugins/catalog-node/src/api/provider.ts b/plugins/catalog-node/src/api/provider.ts index a576f32022..63f3f0bd09 100644 --- a/plugins/catalog-node/src/api/provider.ts +++ b/plugins/catalog-node/src/api/provider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { DeferredEntity } from '../processing'; +import { DeferredEntity } from '../processing/types'; /** * A 'full' mutation replaces all existing entities created by this entity provider with new ones. diff --git a/plugins/catalog-node/src/processing/types.ts b/plugins/catalog-node/src/processing/types.ts index 625effc4df..d4255c1c51 100644 --- a/plugins/catalog-node/src/processing/types.ts +++ b/plugins/catalog-node/src/processing/types.ts @@ -21,7 +21,7 @@ import { AnalyzeLocationResponse, } from '@backstage/plugin-catalog-common'; import { JsonValue } from '@backstage/types'; -import { CatalogProcessorEmit } from '../api'; +import { CatalogProcessorEmit } from '../api/processor'; import { BackstageCredentials } from '@backstage/backend-plugin-api'; /** From 4aef540614e573540eee48bde482452bde8ea332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 13 Jun 2025 17:05:47 +0200 Subject: [PATCH 102/301] fixup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../src/types.d.ts | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/plugins/auth-backend-module-okta-provider/src/types.d.ts b/plugins/auth-backend-module-okta-provider/src/types.d.ts index 58cfb74173..7ca3645e3e 100644 --- a/plugins/auth-backend-module-okta-provider/src/types.d.ts +++ b/plugins/auth-backend-module-okta-provider/src/types.d.ts @@ -23,3 +23,87 @@ declare module 'passport-okta2' { authenticate(this: StrategyCreated, req: Request, options?: any): any; } } + +// NOTE(freben): This entire block is here because version 0.0.6 of this library +// accidentally didn't include types. It did, however, include a scope fix that +// is interesting for Backstage to get in. +// See https://github.com/backstage/backstage/pull/29529 +// See https://github.com/DavidZemon/passport-okta-oauth/pull/1 +declare module '@davidzemon/passport-okta-oauth' { + import OAuth2Strategy = require('passport-oauth2'); + + type OktaStrategyOptions = UniqueOktaStrategyOptions & + Omit< + import('passport-oauth2')._StrategyOptionsBase, + 'authorizationURL' | 'tokenURL' + >; + type UniqueOktaStrategyOptions = { + /** + * audience is the Okta Domain, e.g. `https://example.okta.com`, + * `https://example.oktapreview.com` + */ + audience: string; + /** + * authServerID is the authorization server ID. If it is defined, the token + * URL might be something like `https://example.okta.com/oauth2/authServerID/v1/token` + */ + authServerID: string | undefined; + /** + * idp is the Identity Provider (id). This is an optional field. it's a 20 character + * alphanumeric string, e.g. `qOp8aaJmCEhvep5Il6ZJ` (generated example) + */ + idp: string | undefined; + /** + * With this option enabled, `req` will be passed as the first argument to the + * verify callback. + */ + passReqToCallback: boolean | undefined; + /** + * Set this to 'code' + */ + response_type: 'code'; + }; + + class Strategy extends OAuth2Strategy { + /** + * @param {OktaStrategyOptions | undefined} options + * @param {import("passport-oauth2").VerifyFunction | import("passport-oauth2").VerifyFunctionWithRequest} verify + */ + constructor( + options: OktaStrategyOptions | undefined, + verify: + | import('passport-oauth2').VerifyFunction + | import('passport-oauth2').VerifyFunctionWithRequest, + ); + _userInfoUrl: string; + _idp: string; + _state: any; + /** + * Retrieve user profile from Okta. + * Further references at http://developer.okta.com/docs/api/resources/oidc.html#get-user-information + * + * This function constructs a normalized profile, with the following properties: + * + * - `provider` always set to `okta` + * - `id` + * - `username` + * - `displayName` + * + * @param {String} accessToken + * @param {Function} done + * @api protected + */ + userProfile(accessToken: string, done: Function): void; + /** + * Return extra Okta-specific parameters to be included in the authorization + * request. + * + * @param {Object} option + * @return {Object} + * @api protected + */ + authorizationParams(option: any): any; + } + + export { Strategy }; +} From 6082375f7b5b732bfdd8cc34f71297e421925767 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 09:03:49 +0000 Subject: [PATCH 103/301] fix(deps): update dependency @electric-sql/pglite to v0.3.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7147289913..d1141dcde2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9470,9 +9470,9 @@ __metadata: linkType: hard "@electric-sql/pglite@npm:^0.3.0": - version: 0.3.1 - resolution: "@electric-sql/pglite@npm:0.3.1" - checksum: 10/515eac81a03cace6c6fe89642c2a06d8fc10f2b829038d07782f6524aa15fc36632a853d9acb2f5aff12d9602149b5db4e0c220ba6dc16ff41939394aa9de49f + version: 0.3.3 + resolution: "@electric-sql/pglite@npm:0.3.3" + checksum: 10/fa2b8698c4edb4716b34efd2e8ef1847e5b0c8059afffb21ad5b7cc21dcdca9d86075ac4f1798f1e83a5b2cc1ee8902e1d322fe63d761e065089c7da494ea089 languageName: node linkType: hard From 437fcc9956cd3183f080440faf742e80fd9fb0d4 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 16 Jun 2025 11:09:15 +0200 Subject: [PATCH 104/301] Exit pre release Signed-off-by: Vincenzo Scamporlino --- .changeset/pre.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index ca3ec8d340..abbf045bec 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -1,5 +1,5 @@ { - "mode": "pre", + "mode": "exit", "tag": "next", "initialVersions": { "example-app": "0.2.109", From fbd7c921b9b9e0ab4e514c269af1a3ac5b354719 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Fri, 21 Mar 2025 22:11:08 -0600 Subject: [PATCH 105/301] improve type safety with new type structure Signed-off-by: Kurt King --- .../tasks/NunjucksWorkflowRunner.ts | 15 +-- .../src/scaffolder/tasks/StorageTaskBroker.ts | 106 +++++++++++++----- 2 files changed, 78 insertions(+), 43 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 7802a7ab8d..444d6efe9b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -60,6 +60,7 @@ import { scaffolderActionRules } from '../../service/rules'; import { createCounterMetric, createHistogramMetric } from '../../util/metrics'; import { BackstageLoggerTransport, WinstonLogger } from './logger'; import { convertFiltersToRecord } from '../../util/templating'; +import { CheckpointState } from './StorageTaskBroker'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; @@ -91,16 +92,6 @@ type TemplateContext = { }; }; -type CheckpointState = - | { - status: 'failed'; - reason: string; - } - | { - status: 'success'; - value: JsonValue; - }; - const isValidTaskSpec = (taskSpec: TaskSpec): taskSpec is TaskSpecV1beta3 => { return taskSpec.apiVersion === 'scaffolder.backstage.io/v1beta3'; }; @@ -396,9 +387,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { if (prevTaskState) { const prevState = ( - prevTaskState.state?.checkpoints as { - [key: string]: CheckpointState; - } + prevTaskState.state?.checkpoints as CheckpointState )?.[key]; if (prevState && prevState.status === 'success') { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 86f9f7fcf7..a92d4134f5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -44,19 +44,30 @@ import { DefaultWorkspaceService, WorkspaceService } from './WorkspaceService'; import { readDuration } from './helper'; import { InternalTaskSecrets, TaskStore } from './types'; -type TaskState = { - checkpoints: { - [key: string]: - | { - status: 'failed'; - reason: string; - } - | { - status: 'success'; - value: JsonValue; - }; - }; +type CheckpointStatus = 'failed' | 'success'; + +export type CheckpointSuccessState = { + status: Extract; + value: JsonValue; }; + +export type CheckpointFailedState = { + status: Extract; + reason: string; +}; + +export type CheckpointState = { + [key: string]: CheckpointSuccessState | CheckpointFailedState; +}; + +export type UpdateCheckpointOptions = { + key: string; +} & CheckpointState[keyof CheckpointState]; + +type TaskState = { + checkpoints: CheckpointState; +}; + /** * TaskManager * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. @@ -152,25 +163,57 @@ export class TaskManager implements TaskContext { return this.storage.getTaskState?.({ taskId: this.task.taskId }); } - async updateCheckpoint?( - options: - | { - key: string; - status: 'success'; - value: JsonValue; - } - | { - key: string; - status: 'failed'; - reason: string; - }, - ): Promise { - const { key, ...value } = options; - if (this.task.state) { - (this.task.state as TaskState).checkpoints[key] = value; - } else { - this.task.state = { checkpoints: { [key]: value } }; + /** + * Helper to safely access the checkpoints field from task state + * Ensures type safety when working with task state that might not match our structure + */ + private getCheckpointsFromState(state?: JsonObject): CheckpointState { + if ( + state && + 'checkpoints' in state && + typeof state.checkpoints === 'object' + ) { + return state.checkpoints as CheckpointState; } + return {}; + } + + async updateCheckpoint?(options: UpdateCheckpointOptions): Promise { + const { key, status } = options; + + // Extract appropriate state value based on status + let checkpointValue: CheckpointSuccessState | CheckpointFailedState; + + switch (status) { + case 'success': { + const { value } = options; + checkpointValue = { status, value }; + break; + } + case 'failed': { + const { reason } = options; + checkpointValue = { status, reason }; + break; + } + default: { + // Using status as 'never' gives compile-time guarantee we've handled all cases + const exhaustiveCheck: never = status; + throw new Error(`Unexpected status: ${exhaustiveCheck}`); + } + } + + if (this.task.state) { + const taskState: TaskState = { + checkpoints: this.getCheckpointsFromState(this.task.state), + }; + + // Update with the new checkpoint + taskState.checkpoints[key] = checkpointValue; + this.task.state = taskState; + } else { + this.task.state = { checkpoints: { [key]: checkpointValue } }; + } + await this.storage.saveTaskState?.({ taskId: this.task.taskId, state: this.task.state, @@ -254,6 +297,9 @@ export interface CurrentClaimedTask { secrets?: TaskSecrets; /** * The state of checkpoints of the task. + * This will be a JsonObject that may contain a `checkpoints` field + * with a structure matching the CheckpointState interface. + * @see CheckpointState */ state?: JsonObject; /** From 919a61667310f9d9d9100658742e758e65913fc1 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Fri, 21 Mar 2025 22:22:42 -0600 Subject: [PATCH 106/301] move types to node package Signed-off-by: Kurt King --- .../tasks/NunjucksWorkflowRunner.ts | 2 +- .../src/scaffolder/tasks/StorageTaskBroker.ts | 33 ++------ plugins/scaffolder-node/src/actions/types.ts | 9 ++- .../scaffolder-node/src/checkpoints/index.ts | 17 ++++ .../scaffolder-node/src/checkpoints/types.ts | 78 +++++++++++++++++++ plugins/scaffolder-node/src/index.ts | 1 + plugins/scaffolder-node/src/tasks/types.ts | 17 +--- 7 files changed, 112 insertions(+), 45 deletions(-) create mode 100644 plugins/scaffolder-node/src/checkpoints/index.ts create mode 100644 plugins/scaffolder-node/src/checkpoints/types.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 444d6efe9b..d97bf10543 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -60,7 +60,7 @@ import { scaffolderActionRules } from '../../service/rules'; import { createCounterMetric, createHistogramMetric } from '../../util/metrics'; import { BackstageLoggerTransport, WinstonLogger } from './logger'; import { convertFiltersToRecord } from '../../util/templating'; -import { CheckpointState } from './StorageTaskBroker'; +import { CheckpointState } from '@backstage/plugin-scaffolder-node'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index a92d4134f5..9a872e7b3f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -33,36 +33,17 @@ import { TaskStatus, } from '@backstage/plugin-scaffolder-node'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; -import { - JsonObject, - JsonValue, - Observable, - createDeferred, -} from '@backstage/types'; +import { JsonObject, Observable, createDeferred } from '@backstage/types'; import ObservableImpl from 'zen-observable'; import { DefaultWorkspaceService, WorkspaceService } from './WorkspaceService'; import { readDuration } from './helper'; import { InternalTaskSecrets, TaskStore } from './types'; - -type CheckpointStatus = 'failed' | 'success'; - -export type CheckpointSuccessState = { - status: Extract; - value: JsonValue; -}; - -export type CheckpointFailedState = { - status: Extract; - reason: string; -}; - -export type CheckpointState = { - [key: string]: CheckpointSuccessState | CheckpointFailedState; -}; - -export type UpdateCheckpointOptions = { - key: string; -} & CheckpointState[keyof CheckpointState]; +import { + CheckpointState, + CheckpointSuccessState, + CheckpointFailedState, + UpdateCheckpointOptions, +} from '@backstage/plugin-scaffolder-node'; type TaskState = { checkpoints: CheckpointState; diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index f13d256332..e7e678450d 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -23,6 +23,8 @@ import { BackstageCredentials, LoggerService, } from '@backstage/backend-plugin-api'; +import { CheckpointOptions } from '../checkpoints'; + /** * ActionContext is passed into scaffolder actions. * @public @@ -36,10 +38,9 @@ export type ActionContext< secrets?: TaskSecrets; workspacePath: string; input: TActionInput; - checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise; + checkpoint( + opts: CheckpointOptions, + ): Promise; output( name: keyof TActionOutput, value: TActionOutput[keyof TActionOutput], diff --git a/plugins/scaffolder-node/src/checkpoints/index.ts b/plugins/scaffolder-node/src/checkpoints/index.ts new file mode 100644 index 0000000000..5d542de408 --- /dev/null +++ b/plugins/scaffolder-node/src/checkpoints/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './types'; diff --git a/plugins/scaffolder-node/src/checkpoints/types.ts b/plugins/scaffolder-node/src/checkpoints/types.ts new file mode 100644 index 0000000000..eac3213075 --- /dev/null +++ b/plugins/scaffolder-node/src/checkpoints/types.ts @@ -0,0 +1,78 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { JsonValue } from '@backstage/types'; + +/** + * The status of a checkpoint, indicating whether it succeeded or failed. + * + * @public + */ +export type CheckpointStatus = 'failed' | 'success'; + +/** + * Represents a successful checkpoint state with a value. + * + * @public + */ +export type CheckpointSuccessState = { + status: Extract; + value: JsonValue; +}; + +/** + * Represents a failed checkpoint state with a reason for failure. + * + * @public + */ +export type CheckpointFailedState = { + status: Extract; + reason: string; +}; + +/** + * A map of checkpoint keys to their states. + * + * @public + */ +export type CheckpointState = { + [key: string]: CheckpointSuccessState | CheckpointFailedState; +}; + +/** + * Options for updating a checkpoint in a task. + * + * @public + */ +export type UpdateCheckpointOptions = { + key: string; +} & CheckpointState[keyof CheckpointState]; + +/** + * Options for checkpoint function invocation. + * + * @public + */ +export type CheckpointOptions = { + /** + * Unique key for the checkpoint + */ + key: string; + /** + * Function to execute for the checkpoint + */ + fn: () => Promise | T; +}; diff --git a/plugins/scaffolder-node/src/index.ts b/plugins/scaffolder-node/src/index.ts index 5d49ee874c..d44f0e9c50 100644 --- a/plugins/scaffolder-node/src/index.ts +++ b/plugins/scaffolder-node/src/index.ts @@ -24,3 +24,4 @@ export * from './actions'; export * from './tasks'; export * from './files'; export * from './types'; +export * from './checkpoints'; diff --git a/plugins/scaffolder-node/src/tasks/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index d8cfd24530..cc9023db8b 100644 --- a/plugins/scaffolder-node/src/tasks/types.ts +++ b/plugins/scaffolder-node/src/tasks/types.ts @@ -16,7 +16,8 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; -import { JsonObject, JsonValue, Observable } from '@backstage/types'; +import { JsonObject, Observable } from '@backstage/types'; +import { UpdateCheckpointOptions } from '../checkpoints'; /** * TaskSecrets @@ -129,19 +130,7 @@ export interface TaskContext { | undefined >; - updateCheckpoint?( - options: - | { - key: string; - status: 'success'; - value: JsonValue; - } - | { - key: string; - status: 'failed'; - reason: string; - }, - ): Promise; + updateCheckpoint?(options: UpdateCheckpointOptions): Promise; serializeWorkspace?(options: { path: string }): Promise; From 3fb5a07af291645adee0502b4d155784dd934703 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Fri, 21 Mar 2025 22:42:27 -0600 Subject: [PATCH 107/301] use CheckpointOptions where possible Signed-off-by: Kurt King --- .../src/scaffolder/tasks/NunjucksWorkflowRunner.ts | 8 ++++---- .../src/actions/mockActionContext.ts | 12 +++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index d97bf10543..d6a420e54a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -50,6 +50,7 @@ import { import { createConditionAuthorizer } from '@backstage/plugin-permission-node'; import { actionExecutePermission } from '@backstage/plugin-scaffolder-common/alpha'; import { + CheckpointOptions, TaskContext, TemplateAction, TemplateFilter, @@ -375,10 +376,9 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { secrets: task.secrets ?? {}, logger: taskLogger, workspacePath, - async checkpoint(opts: { - key?: string; - fn: () => Promise | T; - }) { + async checkpoint( + opts: CheckpointOptions, + ) { const { key: checkpointKey, fn } = opts; const key = `v1.task.checkpoint.${step.id}.${checkpointKey}`; diff --git a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts index a6703a12d1..5f8844d13d 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts @@ -21,7 +21,10 @@ import { mockServices, } from '@backstage/backend-test-utils'; import { JsonObject, JsonValue } from '@backstage/types'; -import { ActionContext } from '@backstage/plugin-scaffolder-node'; +import { + ActionContext, + CheckpointOptions, +} from '@backstage/plugin-scaffolder-node'; import { loggerToWinstonLogger } from './loggerToWinstonLogger'; /** @@ -44,10 +47,9 @@ export function createMockActionContext< output: jest.fn(), createTemporaryDirectory: jest.fn(), input: {} as TActionInput, - async checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise { + async checkpoint( + opts: CheckpointOptions, + ): Promise { return opts.fn(); }, getInitiatorCredentials: () => Promise.resolve(credentials), From 70eae7fa1a784950dce885580fc88c0f836c22fd Mon Sep 17 00:00:00 2001 From: Kurt King Date: Fri, 21 Mar 2025 22:47:29 -0600 Subject: [PATCH 108/301] update api reports Signed-off-by: Kurt King --- plugins/scaffolder-backend/report.api.md | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 1f2a17a384..99be4a27f1 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -14,7 +14,6 @@ import { Duration } from 'luxon'; import { EventsService } from '@backstage/plugin-events-node'; import { HumanDuration } from '@backstage/types'; import { JsonObject } from '@backstage/types'; -import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; import { LoggerService } from '@backstage/backend-plugin-api'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; @@ -38,6 +37,7 @@ import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateFilter } from '@backstage/plugin-scaffolder-node'; import { TemplateGlobal } from '@backstage/plugin-scaffolder-node'; import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { UpdateCheckpointOptions } from '@backstage/plugin-scaffolder-node'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; @@ -442,19 +442,7 @@ export class TaskManager implements TaskContext { // (undocumented) get spec(): TaskSpecV1beta3; // (undocumented) - updateCheckpoint?( - options: - | { - key: string; - status: 'success'; - value: JsonValue; - } - | { - key: string; - status: 'failed'; - reason: string; - }, - ): Promise; + updateCheckpoint?(options: UpdateCheckpointOptions): Promise; } // @public @deprecated From dbde1805b60f704955dbdd78ecd128a7c7f71e56 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Mon, 24 Mar 2025 19:41:31 -0600 Subject: [PATCH 109/301] clean up types some Signed-off-by: Kurt King --- .changeset/petite-paths-remain.md | 7 +++ plugins/scaffolder-backend/report.api.md | 4 +- .../tasks/NunjucksWorkflowRunner.ts | 4 +- .../src/scaffolder/tasks/StorageTaskBroker.ts | 63 ++----------------- .../src/actions/mockActionContext.ts | 4 +- plugins/scaffolder-node/src/actions/types.ts | 4 +- .../scaffolder-node/src/checkpoints/types.ts | 35 +++++------ plugins/scaffolder-node/src/tasks/index.ts | 1 + plugins/scaffolder-node/src/tasks/types.ts | 13 +++- 9 files changed, 50 insertions(+), 85 deletions(-) create mode 100644 .changeset/petite-paths-remain.md diff --git a/.changeset/petite-paths-remain.md b/.changeset/petite-paths-remain.md new file mode 100644 index 0000000000..a2bef50c80 --- /dev/null +++ b/.changeset/petite-paths-remain.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-scaffolder-node-test-utils': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-scaffolder-node': patch +--- + +An internal refactor which adds additional types to experimental checkpoints diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 99be4a27f1..3823db15d6 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -37,7 +37,7 @@ import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateFilter } from '@backstage/plugin-scaffolder-node'; import { TemplateGlobal } from '@backstage/plugin-scaffolder-node'; import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { UpdateCheckpointOptions } from '@backstage/plugin-scaffolder-node'; +import { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; @@ -442,7 +442,7 @@ export class TaskManager implements TaskContext { // (undocumented) get spec(): TaskSpecV1beta3; // (undocumented) - updateCheckpoint?(options: UpdateCheckpointOptions): Promise; + updateCheckpoint?(options: UpdateTaskCheckpointOptions): Promise; } // @public @deprecated diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index d6a420e54a..adc5152e4f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -50,7 +50,7 @@ import { import { createConditionAuthorizer } from '@backstage/plugin-permission-node'; import { actionExecutePermission } from '@backstage/plugin-scaffolder-common/alpha'; import { - CheckpointOptions, + CheckpointContext, TaskContext, TemplateAction, TemplateFilter, @@ -377,7 +377,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { logger: taskLogger, workspacePath, async checkpoint( - opts: CheckpointOptions, + opts: CheckpointContext, ) { const { key: checkpointKey, fn } = opts; const key = `v1.task.checkpoint.${step.id}.${checkpointKey}`; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 9a872e7b3f..5e90a11d83 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -23,6 +23,7 @@ import { import { Config } from '@backstage/config'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { + CheckpointState, SerializedTask, SerializedTaskEvent, TaskBroker, @@ -31,6 +32,7 @@ import { TaskContext, TaskSecrets, TaskStatus, + UpdateTaskCheckpointOptions, } from '@backstage/plugin-scaffolder-node'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; import { JsonObject, Observable, createDeferred } from '@backstage/types'; @@ -38,17 +40,10 @@ import ObservableImpl from 'zen-observable'; import { DefaultWorkspaceService, WorkspaceService } from './WorkspaceService'; import { readDuration } from './helper'; import { InternalTaskSecrets, TaskStore } from './types'; -import { - CheckpointState, - CheckpointSuccessState, - CheckpointFailedState, - UpdateCheckpointOptions, -} from '@backstage/plugin-scaffolder-node'; type TaskState = { checkpoints: CheckpointState; }; - /** * TaskManager * @deprecated this type is deprecated, and there will be a new way to create Workers in the next major version. @@ -144,57 +139,14 @@ export class TaskManager implements TaskContext { return this.storage.getTaskState?.({ taskId: this.task.taskId }); } - /** - * Helper to safely access the checkpoints field from task state - * Ensures type safety when working with task state that might not match our structure - */ - private getCheckpointsFromState(state?: JsonObject): CheckpointState { - if ( - state && - 'checkpoints' in state && - typeof state.checkpoints === 'object' - ) { - return state.checkpoints as CheckpointState; - } - return {}; - } - - async updateCheckpoint?(options: UpdateCheckpointOptions): Promise { - const { key, status } = options; - - // Extract appropriate state value based on status - let checkpointValue: CheckpointSuccessState | CheckpointFailedState; - - switch (status) { - case 'success': { - const { value } = options; - checkpointValue = { status, value }; - break; - } - case 'failed': { - const { reason } = options; - checkpointValue = { status, reason }; - break; - } - default: { - // Using status as 'never' gives compile-time guarantee we've handled all cases - const exhaustiveCheck: never = status; - throw new Error(`Unexpected status: ${exhaustiveCheck}`); - } - } + async updateCheckpoint?(options: UpdateTaskCheckpointOptions): Promise { + const { key, ...value } = options; if (this.task.state) { - const taskState: TaskState = { - checkpoints: this.getCheckpointsFromState(this.task.state), - }; - - // Update with the new checkpoint - taskState.checkpoints[key] = checkpointValue; - this.task.state = taskState; + (this.task.state as TaskState).checkpoints[key] = value; } else { - this.task.state = { checkpoints: { [key]: checkpointValue } }; + this.task.state = { checkpoints: { [key]: value } }; } - await this.storage.saveTaskState?.({ taskId: this.task.taskId, state: this.task.state, @@ -278,9 +230,6 @@ export interface CurrentClaimedTask { secrets?: TaskSecrets; /** * The state of checkpoints of the task. - * This will be a JsonObject that may contain a `checkpoints` field - * with a structure matching the CheckpointState interface. - * @see CheckpointState */ state?: JsonObject; /** diff --git a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts index 5f8844d13d..d364d4dc1e 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts @@ -23,7 +23,7 @@ import { import { JsonObject, JsonValue } from '@backstage/types'; import { ActionContext, - CheckpointOptions, + CheckpointContext, } from '@backstage/plugin-scaffolder-node'; import { loggerToWinstonLogger } from './loggerToWinstonLogger'; @@ -48,7 +48,7 @@ export function createMockActionContext< createTemporaryDirectory: jest.fn(), input: {} as TActionInput, async checkpoint( - opts: CheckpointOptions, + opts: CheckpointContext, ): Promise { return opts.fn(); }, diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index e7e678450d..8104470dd8 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -23,7 +23,7 @@ import { BackstageCredentials, LoggerService, } from '@backstage/backend-plugin-api'; -import { CheckpointOptions } from '../checkpoints'; +import { CheckpointContext } from '../checkpoints'; /** * ActionContext is passed into scaffolder actions. @@ -39,7 +39,7 @@ export type ActionContext< workspacePath: string; input: TActionInput; checkpoint( - opts: CheckpointOptions, + opts: CheckpointContext, ): Promise; output( name: keyof TActionOutput, diff --git a/plugins/scaffolder-node/src/checkpoints/types.ts b/plugins/scaffolder-node/src/checkpoints/types.ts index eac3213075..eecf769f5a 100644 --- a/plugins/scaffolder-node/src/checkpoints/types.ts +++ b/plugins/scaffolder-node/src/checkpoints/types.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { JsonValue } from '@backstage/types'; /** @@ -28,9 +27,9 @@ export type CheckpointStatus = 'failed' | 'success'; * * @public */ -export type CheckpointSuccessState = { - status: Extract; - value: JsonValue; +export type CheckpointSuccessState = { + status: 'success'; + value: T; }; /** @@ -39,34 +38,34 @@ export type CheckpointSuccessState = { * @public */ export type CheckpointFailedState = { - status: Extract; + status: 'failed'; reason: string; }; +/** + * Represents the union of all possible checkpoint state values. + * + * @public + */ +export type CheckpointStateValue = + | CheckpointSuccessState + | CheckpointFailedState; + /** * A map of checkpoint keys to their states. * * @public */ export type CheckpointState = { - [key: string]: CheckpointSuccessState | CheckpointFailedState; + [key: string]: CheckpointStateValue; }; /** - * Options for updating a checkpoint in a task. + * Context for checkpoint function invocation. * * @public */ -export type UpdateCheckpointOptions = { - key: string; -} & CheckpointState[keyof CheckpointState]; - -/** - * Options for checkpoint function invocation. - * - * @public - */ -export type CheckpointOptions = { +export type CheckpointContext = { /** * Unique key for the checkpoint */ diff --git a/plugins/scaffolder-node/src/tasks/index.ts b/plugins/scaffolder-node/src/tasks/index.ts index 930de95237..e047131d2b 100644 --- a/plugins/scaffolder-node/src/tasks/index.ts +++ b/plugins/scaffolder-node/src/tasks/index.ts @@ -25,4 +25,5 @@ export type { TaskContext, TaskEventType, TaskStatus, + UpdateTaskCheckpointOptions, } from './types'; diff --git a/plugins/scaffolder-node/src/tasks/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index cc9023db8b..e3b64fea15 100644 --- a/plugins/scaffolder-node/src/tasks/types.ts +++ b/plugins/scaffolder-node/src/tasks/types.ts @@ -17,7 +17,7 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { JsonObject, Observable } from '@backstage/types'; -import { UpdateCheckpointOptions } from '../checkpoints'; +import { CheckpointStateValue } from '../checkpoints'; /** * TaskSecrets @@ -105,6 +105,15 @@ export type TaskBrokerDispatchOptions = { createdBy?: string; }; +/** + * Options for updating a checkpoint in a task. + * + * @public + */ +export type UpdateTaskCheckpointOptions = { + key: string; +} & CheckpointStateValue; + /** * Task * @@ -130,7 +139,7 @@ export interface TaskContext { | undefined >; - updateCheckpoint?(options: UpdateCheckpointOptions): Promise; + updateCheckpoint?(options: UpdateTaskCheckpointOptions): Promise; serializeWorkspace?(options: { path: string }): Promise; From 04106ae76e554fe7f4bae047860c260a10ab1540 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Sat, 10 May 2025 12:07:47 -0600 Subject: [PATCH 110/301] refactor to alpha exports Signed-off-by: Kurt King --- .../src/scaffolder/tasks/NunjucksWorkflowRunner.ts | 6 ++++-- .../src/scaffolder/tasks/StorageTaskBroker.ts | 8 +++++--- .../src/actions/mockActionContext.ts | 6 ++---- plugins/scaffolder-node/src/actions/types.ts | 2 +- .../src/{ => alpha}/checkpoints/index.ts | 3 +-- .../src/{ => alpha}/checkpoints/types.ts | 12 ++++++------ plugins/scaffolder-node/src/alpha/index.ts | 1 + plugins/scaffolder-node/src/index.ts | 1 - plugins/scaffolder-node/src/tasks/alpha.ts | 11 +++++++++++ plugins/scaffolder-node/src/tasks/index.ts | 2 +- plugins/scaffolder-node/src/tasks/types.ts | 11 +---------- 11 files changed, 33 insertions(+), 30 deletions(-) rename plugins/scaffolder-node/src/{ => alpha}/checkpoints/index.ts (93%) rename plugins/scaffolder-node/src/{ => alpha}/checkpoints/types.ts (96%) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index adc5152e4f..1542db8438 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -50,7 +50,6 @@ import { import { createConditionAuthorizer } from '@backstage/plugin-permission-node'; import { actionExecutePermission } from '@backstage/plugin-scaffolder-common/alpha'; import { - CheckpointContext, TaskContext, TemplateAction, TemplateFilter, @@ -61,7 +60,10 @@ import { scaffolderActionRules } from '../../service/rules'; import { createCounterMetric, createHistogramMetric } from '../../util/metrics'; import { BackstageLoggerTransport, WinstonLogger } from './logger'; import { convertFiltersToRecord } from '../../util/templating'; -import { CheckpointState } from '@backstage/plugin-scaffolder-node'; +import { + CheckpointState, + CheckpointContext, +} from '@backstage/plugin-scaffolder-node/alpha'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 5e90a11d83..c88a1f687c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -23,7 +23,6 @@ import { import { Config } from '@backstage/config'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { - CheckpointState, SerializedTask, SerializedTaskEvent, TaskBroker, @@ -32,9 +31,12 @@ import { TaskContext, TaskSecrets, TaskStatus, - UpdateTaskCheckpointOptions, } from '@backstage/plugin-scaffolder-node'; -import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; +import { + CheckpointState, + WorkspaceProvider, + UpdateTaskCheckpointOptions, +} from '@backstage/plugin-scaffolder-node/alpha'; import { JsonObject, Observable, createDeferred } from '@backstage/types'; import ObservableImpl from 'zen-observable'; import { DefaultWorkspaceService, WorkspaceService } from './WorkspaceService'; diff --git a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts index d364d4dc1e..e2f07f2320 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts @@ -21,10 +21,8 @@ import { mockServices, } from '@backstage/backend-test-utils'; import { JsonObject, JsonValue } from '@backstage/types'; -import { - ActionContext, - CheckpointContext, -} from '@backstage/plugin-scaffolder-node'; +import { ActionContext } from '@backstage/plugin-scaffolder-node'; +import { CheckpointContext } from '@backstage/plugin-scaffolder-node/alpha'; import { loggerToWinstonLogger } from './loggerToWinstonLogger'; /** diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index 8104470dd8..32ab12657c 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -23,7 +23,7 @@ import { BackstageCredentials, LoggerService, } from '@backstage/backend-plugin-api'; -import { CheckpointContext } from '../checkpoints'; +import { CheckpointContext } from '../alpha'; /** * ActionContext is passed into scaffolder actions. diff --git a/plugins/scaffolder-node/src/checkpoints/index.ts b/plugins/scaffolder-node/src/alpha/checkpoints/index.ts similarity index 93% rename from plugins/scaffolder-node/src/checkpoints/index.ts rename to plugins/scaffolder-node/src/alpha/checkpoints/index.ts index 5d542de408..16c936b235 100644 --- a/plugins/scaffolder-node/src/checkpoints/index.ts +++ b/plugins/scaffolder-node/src/alpha/checkpoints/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - export * from './types'; diff --git a/plugins/scaffolder-node/src/checkpoints/types.ts b/plugins/scaffolder-node/src/alpha/checkpoints/types.ts similarity index 96% rename from plugins/scaffolder-node/src/checkpoints/types.ts rename to plugins/scaffolder-node/src/alpha/checkpoints/types.ts index eecf769f5a..45f1c03430 100644 --- a/plugins/scaffolder-node/src/checkpoints/types.ts +++ b/plugins/scaffolder-node/src/alpha/checkpoints/types.ts @@ -18,14 +18,14 @@ import { JsonValue } from '@backstage/types'; /** * The status of a checkpoint, indicating whether it succeeded or failed. * - * @public + * @alpha */ export type CheckpointStatus = 'failed' | 'success'; /** * Represents a successful checkpoint state with a value. * - * @public + * @alpha */ export type CheckpointSuccessState = { status: 'success'; @@ -35,7 +35,7 @@ export type CheckpointSuccessState = { /** * Represents a failed checkpoint state with a reason for failure. * - * @public + * @alpha */ export type CheckpointFailedState = { status: 'failed'; @@ -45,7 +45,7 @@ export type CheckpointFailedState = { /** * Represents the union of all possible checkpoint state values. * - * @public + * @alpha */ export type CheckpointStateValue = | CheckpointSuccessState @@ -54,7 +54,7 @@ export type CheckpointStateValue = /** * A map of checkpoint keys to their states. * - * @public + * @alpha */ export type CheckpointState = { [key: string]: CheckpointStateValue; @@ -63,7 +63,7 @@ export type CheckpointState = { /** * Context for checkpoint function invocation. * - * @public + * @alpha */ export type CheckpointContext = { /** diff --git a/plugins/scaffolder-node/src/alpha/index.ts b/plugins/scaffolder-node/src/alpha/index.ts index 9df968a52d..30f95f116a 100644 --- a/plugins/scaffolder-node/src/alpha/index.ts +++ b/plugins/scaffolder-node/src/alpha/index.ts @@ -28,6 +28,7 @@ export * from '../tasks/alpha'; export * from './filters'; export * from './globals'; export * from './types'; +export * from './checkpoints'; /** * Extension point for managing scaffolder actions. diff --git a/plugins/scaffolder-node/src/index.ts b/plugins/scaffolder-node/src/index.ts index d44f0e9c50..5d49ee874c 100644 --- a/plugins/scaffolder-node/src/index.ts +++ b/plugins/scaffolder-node/src/index.ts @@ -24,4 +24,3 @@ export * from './actions'; export * from './tasks'; export * from './files'; export * from './types'; -export * from './checkpoints'; diff --git a/plugins/scaffolder-node/src/tasks/alpha.ts b/plugins/scaffolder-node/src/tasks/alpha.ts index fb6b09026c..4354a798cf 100644 --- a/plugins/scaffolder-node/src/tasks/alpha.ts +++ b/plugins/scaffolder-node/src/tasks/alpha.ts @@ -1,3 +1,5 @@ +import { CheckpointStateValue } from '../alpha'; + /* * Copyright 2024 The Backstage Authors * @@ -14,3 +16,12 @@ * limitations under the License. */ export * from './serializer'; + +/** + * Options for updating a checkpoint in a task. + * + * @alpha + */ +export type UpdateTaskCheckpointOptions = { + key: string; +} & CheckpointStateValue; diff --git a/plugins/scaffolder-node/src/tasks/index.ts b/plugins/scaffolder-node/src/tasks/index.ts index e047131d2b..2ff31857f5 100644 --- a/plugins/scaffolder-node/src/tasks/index.ts +++ b/plugins/scaffolder-node/src/tasks/index.ts @@ -25,5 +25,5 @@ export type { TaskContext, TaskEventType, TaskStatus, - UpdateTaskCheckpointOptions, + // UpdateTaskCheckpointOptions, } from './types'; diff --git a/plugins/scaffolder-node/src/tasks/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index e3b64fea15..5621aa117b 100644 --- a/plugins/scaffolder-node/src/tasks/types.ts +++ b/plugins/scaffolder-node/src/tasks/types.ts @@ -17,7 +17,7 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { JsonObject, Observable } from '@backstage/types'; -import { CheckpointStateValue } from '../checkpoints'; +import { UpdateTaskCheckpointOptions } from '../alpha'; /** * TaskSecrets @@ -105,15 +105,6 @@ export type TaskBrokerDispatchOptions = { createdBy?: string; }; -/** - * Options for updating a checkpoint in a task. - * - * @public - */ -export type UpdateTaskCheckpointOptions = { - key: string; -} & CheckpointStateValue; - /** * Task * From 77f713137e4c70a0a208df1173f9bb366eaf89db Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Mon, 16 Jun 2025 11:41:51 +0200 Subject: [PATCH 111/301] chore: fixing issues with typescript Signed-off-by: benjdlambert --- plugins/scaffolder-backend/report.api.md | 2 +- plugins/scaffolder-node/report-alpha.api.md | 36 ++++++++++++++++++++ plugins/scaffolder-node/report.api.md | 23 ++++--------- plugins/scaffolder-node/src/actions/types.ts | 2 +- plugins/scaffolder-node/src/tasks/types.ts | 2 +- 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 3823db15d6..7ece0f84ea 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -37,7 +37,7 @@ import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateFilter } from '@backstage/plugin-scaffolder-node'; import { TemplateGlobal } from '@backstage/plugin-scaffolder-node'; import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node'; +import { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node/alpha'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; diff --git a/plugins/scaffolder-node/report-alpha.api.md b/plugins/scaffolder-node/report-alpha.api.md index a96f312d2d..c3e8c7f71b 100644 --- a/plugins/scaffolder-node/report-alpha.api.md +++ b/plugins/scaffolder-node/report-alpha.api.md @@ -27,6 +27,37 @@ export type AutocompleteHandler = ({ }[]; }>; +// @alpha +export type CheckpointContext = { + key: string; + fn: () => Promise | T; +}; + +// @alpha +export type CheckpointFailedState = { + status: 'failed'; + reason: string; +}; + +// @alpha +export type CheckpointState = { + [key: string]: CheckpointStateValue; +}; + +// @alpha +export type CheckpointStateValue = + | CheckpointSuccessState + | CheckpointFailedState; + +// @alpha +export type CheckpointStatus = 'failed' | 'success'; + +// @alpha +export type CheckpointSuccessState = { + status: 'success'; + value: T; +}; + // @alpha (undocumented) export type CreatedTemplateFilter< TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], @@ -187,6 +218,11 @@ export type TemplateGlobalFunctionExample = { notes?: string; }; +// @alpha +export type UpdateTaskCheckpointOptions = { + key: string; +} & CheckpointStateValue; + // @alpha export interface WorkspaceProvider { // (undocumented) diff --git a/plugins/scaffolder-node/report.api.md b/plugins/scaffolder-node/report.api.md index a23a9137b1..bceb9980b6 100644 --- a/plugins/scaffolder-node/report.api.md +++ b/plugins/scaffolder-node/report.api.md @@ -4,6 +4,7 @@ ```ts import { BackstageCredentials } from '@backstage/backend-plugin-api'; +import { CheckpointContext } from '@backstage/plugin-scaffolder-node/alpha'; import { Expand } from '@backstage/types'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; @@ -15,6 +16,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { SpawnOptionsWithoutStdio } from 'child_process'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TemplateInfo } from '@backstage/plugin-scaffolder-common'; +import { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node/alpha'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { UserEntity } from '@backstage/catalog-model'; import { Writable } from 'stream'; @@ -30,10 +32,9 @@ export type ActionContext< secrets?: TaskSecrets; workspacePath: string; input: TActionInput; - checkpoint(opts: { - key: string; - fn: () => Promise | T; - }): Promise; + checkpoint( + opts: CheckpointContext, + ): Promise; output( name: keyof TActionOutput, value: TActionOutput[keyof TActionOutput], @@ -446,19 +447,7 @@ export interface TaskContext { // (undocumented) taskId?: string; // (undocumented) - updateCheckpoint?( - options: - | { - key: string; - status: 'success'; - value: JsonValue; - } - | { - key: string; - status: 'failed'; - reason: string; - }, - ): Promise; + updateCheckpoint?(options: UpdateTaskCheckpointOptions): Promise; } // @public diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts index 32ab12657c..0162d96d95 100644 --- a/plugins/scaffolder-node/src/actions/types.ts +++ b/plugins/scaffolder-node/src/actions/types.ts @@ -23,7 +23,7 @@ import { BackstageCredentials, LoggerService, } from '@backstage/backend-plugin-api'; -import { CheckpointContext } from '../alpha'; +import { CheckpointContext } from '@backstage/plugin-scaffolder-node/alpha'; /** * ActionContext is passed into scaffolder actions. diff --git a/plugins/scaffolder-node/src/tasks/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index 5621aa117b..d2403f205a 100644 --- a/plugins/scaffolder-node/src/tasks/types.ts +++ b/plugins/scaffolder-node/src/tasks/types.ts @@ -17,7 +17,7 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { JsonObject, Observable } from '@backstage/types'; -import { UpdateTaskCheckpointOptions } from '../alpha'; +import { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node/alpha'; /** * TaskSecrets From cf0b63447033e6bd7f3bff9dfaebb19df7ae9b69 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 10:04:50 +0000 Subject: [PATCH 112/301] fix(deps): update dependency @keyv/memcache to v2.0.2 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/yarn.lock b/yarn.lock index a94fa4a426..402ad15bf7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11509,23 +11509,25 @@ __metadata: linkType: hard "@keyv/memcache@npm:^1.3.5": - version: 1.3.7 - resolution: "@keyv/memcache@npm:1.3.7" + version: 1.4.1 + resolution: "@keyv/memcache@npm:1.4.1" dependencies: json-buffer: "npm:^3.0.1" - memjs: "npm:^1.3.1" - checksum: 10/61c836c1b0c264c35281a38dbc380a102b2899eab46226a0e31614d3d7c4708ecdb06d8858f711893212ae5b357a79213c7a7b7a81b6196e56dd889dd1ca498b + memjs: "npm:^1.3.2" + checksum: 10/a3db21549ec445925c28419b19210984875aa03d1ce513792064336c7785c0e6d3ac015e71ca328a2e83f4ed409475f057a37d86c7fb2a11832740b024bcc3ff languageName: node linkType: hard "@keyv/memcache@npm:^2.0.1": - version: 2.0.1 - resolution: "@keyv/memcache@npm:2.0.1" + version: 2.0.2 + resolution: "@keyv/memcache@npm:2.0.2" dependencies: - "@keyv/serialize": "npm:*" + "@keyv/serialize": "npm:^1.0.3" buffer: "npm:^6.0.3" memjs: "npm:^1.3.2" - checksum: 10/618fd2441cb7e0b71c6af37b87bddc39d3439c9863def47cff8c75c9c05ca45e035bfc1bfa140a661befce5a06a491f25388aab6d330c0eaefe7b87534274c48 + peerDependencies: + keyv: ^5.3.4 + checksum: 10/8328bfa4a348d26c1aa1a563561a50114f75288af5fe18e8dc2ce6ff2f7f6aa63c27f8293c5e8b35bc0dcc9740159e56d8ff1b4015fc6a56e18d62850a8a9b83 languageName: node linkType: hard @@ -11550,7 +11552,7 @@ __metadata: languageName: node linkType: hard -"@keyv/serialize@npm:*, @keyv/serialize@npm:^1.0.3": +"@keyv/serialize@npm:^1.0.3": version: 1.0.3 resolution: "@keyv/serialize@npm:1.0.3" dependencies: @@ -37077,7 +37079,7 @@ __metadata: languageName: node linkType: hard -"memjs@npm:^1.3.1, memjs@npm:^1.3.2": +"memjs@npm:^1.3.2": version: 1.3.2 resolution: "memjs@npm:1.3.2" checksum: 10/705bb9be88180685fa3bc71375dc8b3a188356ab8f5c6c5ea1761e5e0912a78520cb964d2afc24e65686d53b45d3a7d8b5e770b2f3cf071a6eaea7f8d0d6bbfd From aafb7aad835762fbc89aa0a4731e202e98ff596d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 10:05:20 +0000 Subject: [PATCH 113/301] fix(deps): update dependency @keyv/valkey to v1.0.4 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a94fa4a426..27a1fd1f47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11560,11 +11560,11 @@ __metadata: linkType: hard "@keyv/valkey@npm:^1.0.1": - version: 1.0.3 - resolution: "@keyv/valkey@npm:1.0.3" + version: 1.0.4 + resolution: "@keyv/valkey@npm:1.0.4" dependencies: iovalkey: "npm:^0.3.1" - checksum: 10/ff6ba62e4d19c426e45a1437fe215ed2baddc58e811d97507dd75ead0058c3105d679c8c7c4241ddf732abe56320357c2e568c014620e50d7c0eaef1f2528b88 + checksum: 10/93362c51c14bb8e515a9af8df4a22e028fcec5a09931eb68f854698676cb16cecad691330c75c1dca8b6a986a4763cac9f1b2fd4e5b8216f06648d9379f55cc6 languageName: node linkType: hard From b57f1a771b6e0c42a003d5270c4a7cffbfba7a43 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 11:10:23 +0000 Subject: [PATCH 114/301] fix(deps): update dependency @openapitools/openapi-generator-cli to v2.20.2 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 55 ++++++++++++++++++++++--------------------------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/yarn.lock b/yarn.lock index 402ad15bf7..cdc4d18367 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12614,18 +12614,18 @@ __metadata: languageName: node linkType: hard -"@nestjs/common@npm:11.0.20": - version: 11.0.20 - resolution: "@nestjs/common@npm:11.0.20" +"@nestjs/common@npm:11.1.1": + version: 11.1.1 + resolution: "@nestjs/common@npm:11.1.1" dependencies: - file-type: "npm:20.4.1" + file-type: "npm:20.5.0" iterare: "npm:1.2.1" load-esm: "npm:1.0.2" tslib: "npm:2.8.1" uid: "npm:2.0.2" peerDependencies: - class-transformer: "*" - class-validator: "*" + class-transformer: ">=0.4.1" + class-validator: ">=0.13.2" reflect-metadata: ^0.1.12 || ^0.2.0 rxjs: ^7.1.0 peerDependenciesMeta: @@ -12633,13 +12633,13 @@ __metadata: optional: true class-validator: optional: true - checksum: 10/ab06f3d910a86351e895829773b28236dcb4a80a3eba1ed902c51dbc44db9d40f619d5f8713c5ce42518fc95633bbe9c80b1c7188ffc4fd3d5d934b561e2f495 + checksum: 10/b58b951984df667b794a6c9cf65dda8ee43fe0c4e552183e045b126c82d99b50cddb1e84ea03b8dd7b04fca512acb107222ffad6b19dba359323f9b813032161 languageName: node linkType: hard -"@nestjs/core@npm:11.0.20": - version: 11.0.20 - resolution: "@nestjs/core@npm:11.0.20" +"@nestjs/core@npm:11.1.1": + version: 11.1.1 + resolution: "@nestjs/core@npm:11.1.1" dependencies: "@nuxt/opencollective": "npm:0.4.1" fast-safe-stringify: "npm:2.1.1" @@ -12661,7 +12661,7 @@ __metadata: optional: true "@nestjs/websockets": optional: true - checksum: 10/0e18e6744eaa912442d43d6a8d5543bc4b7e09d350943881d76110aa53a0d26876818898d1ddb71f92d24e6f7154ab3264c6c5604c9670221a5f63552e61d431 + checksum: 10/88f8a3c52a98c059e6d93c5faf9194daee4667b1a855f88233582acf51e3be9aedb3e3f7f992da46e0e45df31ad4e045e0296e6e35ea14e2245d4069176711c0 languageName: node linkType: hard @@ -13726,14 +13726,14 @@ __metadata: linkType: hard "@openapitools/openapi-generator-cli@npm:^2.4.26, @openapitools/openapi-generator-cli@npm:^2.7.0": - version: 2.20.0 - resolution: "@openapitools/openapi-generator-cli@npm:2.20.0" + version: 2.20.2 + resolution: "@openapitools/openapi-generator-cli@npm:2.20.2" dependencies: "@nestjs/axios": "npm:4.0.0" - "@nestjs/common": "npm:11.0.20" - "@nestjs/core": "npm:11.0.20" + "@nestjs/common": "npm:11.1.1" + "@nestjs/core": "npm:11.1.1" "@nuxtjs/opencollective": "npm:0.3.2" - axios: "npm:1.8.4" + axios: "npm:1.9.0" chalk: "npm:4.1.2" commander: "npm:8.3.0" compare-versions: "npm:4.1.4" @@ -13749,7 +13749,7 @@ __metadata: tslib: "npm:2.8.1" bin: openapi-generator-cli: main.js - checksum: 10/b5114d7811505e4fd3e7fa050c1262a76510b1d9e0c1a0e24a417f9570ca986a2edc0c7234eea0e6e30aae2ed698d2e9815ccdd8a5d1663b5794e9e505868edb + checksum: 10/29807b52555e7307207eff6e0c4c1a25b970252915e13d847a6c275d1d638af0732761e51816f5bb5a91830ebabc044b3924a106db8cffa19dda01517a77be17 languageName: node linkType: hard @@ -24239,18 +24239,7 @@ __metadata: languageName: node linkType: hard -"axios@npm:1.8.4": - version: 1.8.4 - resolution: "axios@npm:1.8.4" - dependencies: - follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.0" - proxy-from-env: "npm:^1.1.0" - checksum: 10/a10f0dd836613924e48cf03dc2eff3fd21b14f764807aedaee4880a70c0f142aaebdb21da7ce27104d4c16ca00d0e452a20a20851f60e385a8d5bad1ae909d46 - languageName: node - linkType: hard - -"axios@npm:^1.0.0, axios@npm:^1.6.0, axios@npm:^1.7.4, axios@npm:^1.7.7, axios@npm:^1.7.8": +"axios@npm:1.9.0, axios@npm:^1.0.0, axios@npm:^1.6.0, axios@npm:^1.7.4, axios@npm:^1.7.7, axios@npm:^1.7.8": version: 1.9.0 resolution: "axios@npm:1.9.0" dependencies: @@ -30307,15 +30296,15 @@ __metadata: languageName: node linkType: hard -"file-type@npm:20.4.1": - version: 20.4.1 - resolution: "file-type@npm:20.4.1" +"file-type@npm:20.5.0": + version: 20.5.0 + resolution: "file-type@npm:20.5.0" dependencies: "@tokenizer/inflate": "npm:^0.2.6" strtok3: "npm:^10.2.0" token-types: "npm:^6.0.0" uint8array-extras: "npm:^1.4.0" - checksum: 10/efbb81c69c84ea4d83ea86dc1c95a45bc6830d5455deb7666e0dbefde2dbfe2bcee4e5398bfc5a8fc1ac55a80a27a3ec4f2bff4b53d4af20be799db4fae324b6 + checksum: 10/1cc1ccd7cf76086e10b65cba88c708e0653676fbae900107deeb91c46de011acd1492200bf47e75cddf395de27dbe8584ca042f4cfa4a1efdf933644b7143f1d languageName: node linkType: hard From 1b56fa6c1adabaadfb5f455c0ca0b203cb13c666 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 11:10:56 +0000 Subject: [PATCH 115/301] fix(deps): update dependency @types/content-type to v1.1.9 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 402ad15bf7..af4e60f689 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19942,9 +19942,9 @@ __metadata: linkType: hard "@types/content-type@npm:^1.1.8": - version: 1.1.8 - resolution: "@types/content-type@npm:1.1.8" - checksum: 10/2dd15e51925db7208b0d989c3a93d805a0e5e0942aa9edd70a1c3520896b772526d8280e344a674ae68a96a24aa8fce290843a07512460176f36a3020d99c792 + version: 1.1.9 + resolution: "@types/content-type@npm:1.1.9" + checksum: 10/ac2d419877661f25c6b2c174f42e142ee542cae7778af0a519d4787545ea190d2ff91a40acf68e5e8c52d22919be9ff0345e38a393866546ae9d45b8a79e94f6 languageName: node linkType: hard From f63877526a6167acf4cff55f4f26f420720dbe1d Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Mon, 16 Jun 2025 13:35:55 +0200 Subject: [PATCH 116/301] chore: cleanup the types a little bit Signed-off-by: benjdlambert Signed-off-by: benjdlambert --- plugins/scaffolder-node/report-alpha.api.md | 24 +++++++---------- .../src/alpha/checkpoints/types.ts | 26 +++---------------- plugins/scaffolder-node/src/tasks/index.ts | 1 - 3 files changed, 12 insertions(+), 39 deletions(-) diff --git a/plugins/scaffolder-node/report-alpha.api.md b/plugins/scaffolder-node/report-alpha.api.md index c3e8c7f71b..09541fbc56 100644 --- a/plugins/scaffolder-node/report-alpha.api.md +++ b/plugins/scaffolder-node/report-alpha.api.md @@ -33,31 +33,25 @@ export type CheckpointContext = { fn: () => Promise | T; }; -// @alpha -export type CheckpointFailedState = { - status: 'failed'; - reason: string; -}; - // @alpha export type CheckpointState = { [key: string]: CheckpointStateValue; }; // @alpha -export type CheckpointStateValue = - | CheckpointSuccessState - | CheckpointFailedState; +export type CheckpointStateValue = + | { + status: 'failed'; + reason: string; + } + | { + status: 'success'; + value: T; + }; // @alpha export type CheckpointStatus = 'failed' | 'success'; -// @alpha -export type CheckpointSuccessState = { - status: 'success'; - value: T; -}; - // @alpha (undocumented) export type CreatedTemplateFilter< TFunctionArgs extends [z.ZodTypeAny, ...z.ZodTypeAny[]], diff --git a/plugins/scaffolder-node/src/alpha/checkpoints/types.ts b/plugins/scaffolder-node/src/alpha/checkpoints/types.ts index 45f1c03430..5bd1eb5cd3 100644 --- a/plugins/scaffolder-node/src/alpha/checkpoints/types.ts +++ b/plugins/scaffolder-node/src/alpha/checkpoints/types.ts @@ -22,34 +22,14 @@ import { JsonValue } from '@backstage/types'; */ export type CheckpointStatus = 'failed' | 'success'; -/** - * Represents a successful checkpoint state with a value. - * - * @alpha - */ -export type CheckpointSuccessState = { - status: 'success'; - value: T; -}; - -/** - * Represents a failed checkpoint state with a reason for failure. - * - * @alpha - */ -export type CheckpointFailedState = { - status: 'failed'; - reason: string; -}; - /** * Represents the union of all possible checkpoint state values. * * @alpha */ -export type CheckpointStateValue = - | CheckpointSuccessState - | CheckpointFailedState; +export type CheckpointStateValue = + | { status: 'failed'; reason: string } + | { status: 'success'; value: T }; /** * A map of checkpoint keys to their states. diff --git a/plugins/scaffolder-node/src/tasks/index.ts b/plugins/scaffolder-node/src/tasks/index.ts index 2ff31857f5..930de95237 100644 --- a/plugins/scaffolder-node/src/tasks/index.ts +++ b/plugins/scaffolder-node/src/tasks/index.ts @@ -25,5 +25,4 @@ export type { TaskContext, TaskEventType, TaskStatus, - // UpdateTaskCheckpointOptions, } from './types'; From 4dc80fc0419a214b4a2c7b742afdf7612803ef6b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 12:04:07 +0000 Subject: [PATCH 117/301] fix(deps): update dependency @types/cors to v2.8.19 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2579d1ca7a..843fbfc013 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21495,11 +21495,11 @@ __metadata: linkType: hard "@types/cors@npm:^2.8.6": - version: 2.8.18 - resolution: "@types/cors@npm:2.8.18" + version: 2.8.19 + resolution: "@types/cors@npm:2.8.19" dependencies: "@types/node": "npm:*" - checksum: 10/6e49b741345e67834cd19d766228509e4b37d6d5c272355bb059502b4787f5adf58776d9114ac5f0f407966e0347ae8d1f995d7ea41e6a24f716d36b3010401b + checksum: 10/9545cc532c9218754443f48a0c98c1a9ba4af1fe54a3425c95de75ff3158147bb39e666cb7c6bf98cc56a9c6dc7b4ce5b2cbdae6b55d5942e50c81b76ed6b825 languageName: node linkType: hard From 608ca724252230a834dc91f950ad40d2e5a43159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 16 Jun 2025 14:09:40 +0200 Subject: [PATCH 118/301] bump module-federation packages together MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .github/renovate.json5 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 77faa9354f..4716f1363f 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -35,6 +35,11 @@ rangeStrategy: 'replace', matchSourceUrls: ['https://github.com/gregberge/svgr{/,}**'], }, + { + groupName: 'Module-federation monorepo packages', + rangeStrategy: 'replace', + matchSourceUrls: ['https://github.com/module-federation/core{/,}**'], + }, // We update yarn packages manually as it's gzip'd and we don't want to pollute the repository too much. { matchSourceUrls: ['https://github.com/yarnpkg/berry'], From 41d4d6e7afc68d1dca29823574348e8d0b27aaf9 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Wed, 11 Jun 2025 14:10:35 +0300 Subject: [PATCH 119/301] feat: clean up old notifications a new scheduled task that will delete old notifications. the default is that over 1 year old notifications will be deleted. the scheduled task is run every 24 hours. this can be disabled by setting the retention period to false in the notifications config. Signed-off-by: Hellgren Heikki --- .changeset/common-goats-exist.md | 15 +++ docs/notifications/index.md | 15 +++ plugins/notifications-backend/config.d.ts | 5 + .../DatabaseNotificationsStore.test.ts | 19 +++ .../database/DatabaseNotificationsStore.ts | 21 ++++ .../src/database/NotificationsStore.ts | 9 ++ plugins/notifications-backend/src/plugin.ts | 16 ++- .../src/service/NotificationCleaner.test.ts | 118 ++++++++++++++++++ .../src/service/NotificationCleaner.ts | 91 ++++++++++++++ .../src/service/router.test.ts | 13 +- .../src/service/router.ts | 8 +- 11 files changed, 320 insertions(+), 10 deletions(-) create mode 100644 .changeset/common-goats-exist.md create mode 100644 plugins/notifications-backend/src/service/NotificationCleaner.test.ts create mode 100644 plugins/notifications-backend/src/service/NotificationCleaner.ts diff --git a/.changeset/common-goats-exist.md b/.changeset/common-goats-exist.md new file mode 100644 index 0000000000..1992b69223 --- /dev/null +++ b/.changeset/common-goats-exist.md @@ -0,0 +1,15 @@ +--- +'@backstage/plugin-notifications-backend': patch +--- + +Notifications are now automatically deleted after 1 year by default. + +There is a new scheduled task that runs every 24 hours to delete notifications older than 1 year. +This can be configured by setting the `notifications.retention` in the `app-config.yaml` file. + +```yaml +notifications: + retention: 1y +``` + +If the retention is set to false, notifications will not be automatically deleted. diff --git a/docs/notifications/index.md b/docs/notifications/index.md index 2af8879ac0..c31d2716e4 100644 --- a/docs/notifications/index.md +++ b/docs/notifications/index.md @@ -158,6 +158,21 @@ You can customize the origin names shown in the UI by passing an object where th Each notification processor will receive its own row in the settings page, where the user can enable or disable notifications from that processor. +### Automatic notification cleanup + +Notifications are deleted automatically after a certain period of time to prevent the database from growing indefinitely +and to keep the user interface clean. The default retention period is set to 1 year, meaning that notifications older +than that will be deleted automatically. + +The retention period can be configured by setting the `notifications.retention` in the `app-config.yaml` file. + +```yaml +notifications: + retention: 1y +``` + +If the retention is set to false, notifications will not be automatically deleted. + ## Additional info An example of a backend plugin sending notifications can be found in the [`@backstage/plugin-scaffolder-backend-module-notifications` package](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-notifications). diff --git a/plugins/notifications-backend/config.d.ts b/plugins/notifications-backend/config.d.ts index 5dd795f8d3..61ae411df8 100644 --- a/plugins/notifications-backend/config.d.ts +++ b/plugins/notifications-backend/config.d.ts @@ -28,5 +28,10 @@ export interface Config { * Throttle duration between notification sending, defaults to 50ms */ throttleInterval?: HumanDuration | string; + /** + * Time to keep the notifications in the database, defaults to 365 days. + * Can be disabled by setting to false. + */ + retention?: HumanDuration | string | false; }; } diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts index 4a69c9fd0b..d8ae3faf57 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts @@ -805,5 +805,24 @@ describe.each(databases.eachSupportedId())( }); }); }); + + describe('clearNotifications', () => { + it('should clear notifications older than specified days', async () => { + const oldDate = new Date(); + oldDate.setDate(oldDate.getDate() - 10); // 10 days ago + await storage.saveNotification({ + ...testNotification1, + created: oldDate, + }); + await storage.saveNotification(testNotification2); + + const result = await storage.clearNotifications({ + maxAge: { days: 5 }, + }); // Clear notifications older than 5 days + expect(result.deletedCount).toBe(1); // Only the first notification should be cleared + const remainingNotifications = await storage.getNotifications({ user }); + expect(remainingNotifications.map(idOnly)).toEqual([id2]); + }); + }); }, ); diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts index f761081438..94131ca257 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts @@ -31,6 +31,7 @@ import { } from '@backstage/plugin-notifications-common'; import { Knex } from 'knex'; import crypto from 'crypto'; +import { durationToMilliseconds, HumanDuration } from '@backstage/types'; const migrationsDir = resolvePackagePath( '@backstage/plugin-notifications-backend', @@ -656,4 +657,24 @@ export class DatabaseNotificationsStore implements NotificationsStore { .distinct(['topic']); return { topics: topics.map(row => row.topic) }; } + + async clearNotifications(options: { + maxAge: HumanDuration; + }): Promise<{ deletedCount: number }> { + const ms = durationToMilliseconds(options.maxAge); + const now = new Date(new Date().getTime() - ms); + const notificationsCount = await this.db('notification') + .where(builder => { + builder.where('created', '<=', now).whereNull('updated'); + }) + .orWhere('updated', '<=', now) + .delete(); + const broadcastsCount = await this.db('broadcast') + .where(builder => { + builder.where('created', '<=', now).whereNull('updated'); + }) + .orWhere('updated', '<=', now) + .delete(); + return { deletedCount: notificationsCount + broadcastsCount }; + } } diff --git a/plugins/notifications-backend/src/database/NotificationsStore.ts b/plugins/notifications-backend/src/database/NotificationsStore.ts index a1f4eb6999..1f25b28bbe 100644 --- a/plugins/notifications-backend/src/database/NotificationsStore.ts +++ b/plugins/notifications-backend/src/database/NotificationsStore.ts @@ -20,6 +20,7 @@ import { NotificationSeverity, NotificationStatus, } from '@backstage/plugin-notifications-common'; +import { HumanDuration } from '@backstage/types'; /** @internal */ export type EntityOrder = { @@ -99,6 +100,10 @@ export interface NotificationsStore { user: string; }): Promise<{ origins: string[] }>; + getUserNotificationTopics(options: { + user: string; + }): Promise<{ topics: { origin: string; topic: string }[] }>; + getNotificationSettings(options: { user: string; }): Promise; @@ -109,4 +114,8 @@ export interface NotificationsStore { }): Promise; getTopics(options: TopicGetOptions): Promise<{ topics: string[] }>; + + clearNotifications(options: { + maxAge: HumanDuration; + }): Promise<{ deletedCount: number }>; } diff --git a/plugins/notifications-backend/src/plugin.ts b/plugins/notifications-backend/src/plugin.ts index a5a31d92ed..5f430318b5 100644 --- a/plugins/notifications-backend/src/plugin.ts +++ b/plugins/notifications-backend/src/plugin.ts @@ -26,6 +26,8 @@ import { NotificationsProcessingExtensionPoint, } from '@backstage/plugin-notifications-node'; import { catalogServiceRef } from '@backstage/plugin-catalog-node'; +import { DatabaseNotificationsStore } from './database'; +import { NotificationCleaner } from './service/NotificationCleaner.ts'; class NotificationsProcessingExtensionPointImpl implements NotificationsProcessingExtensionPoint @@ -69,6 +71,7 @@ export const notificationsPlugin = createBackendPlugin({ signals: signalsServiceRef, config: coreServices.rootConfig, catalog: catalogServiceRef, + scheduler: coreServices.scheduler, }, async init({ auth, @@ -80,7 +83,10 @@ export const notificationsPlugin = createBackendPlugin({ signals, config, catalog, + scheduler, }) { + const store = await DatabaseNotificationsStore.create({ database }); + httpRouter.use( await createRouter({ auth, @@ -88,7 +94,7 @@ export const notificationsPlugin = createBackendPlugin({ userInfo, logger, config, - database, + store, catalog, signals, processors: processingExtensions.processors, @@ -98,6 +104,14 @@ export const notificationsPlugin = createBackendPlugin({ path: '/health', allow: 'unauthenticated', }); + + const cleaner = new NotificationCleaner( + config, + scheduler, + logger, + store, + ); + await cleaner.initTaskRunner(); }, }); }, diff --git a/plugins/notifications-backend/src/service/NotificationCleaner.test.ts b/plugins/notifications-backend/src/service/NotificationCleaner.test.ts new file mode 100644 index 0000000000..bfd3cc62b9 --- /dev/null +++ b/plugins/notifications-backend/src/service/NotificationCleaner.test.ts @@ -0,0 +1,118 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerService, SchedulerService } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { mockServices } from '@backstage/backend-test-utils'; +import { NotificationsStore } from '../database'; +import { NotificationCleaner } from './NotificationCleaner.ts'; + +describe('NotificationCleaner', () => { + let mockConfig: Config; + let mockScheduler: SchedulerService; + let mockLogger: LoggerService; + let mockDatabase: NotificationsStore; + + beforeEach(() => { + mockConfig = mockServices.rootConfig(); + mockScheduler = mockServices.scheduler.mock(); + mockLogger = mockServices.logger.mock(); + mockDatabase = { + clearNotifications: jest.fn(), + } as unknown as NotificationsStore; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('initNotificationCleaner', () => { + it('should initialize the notification cleaner with the correct schedule', async () => { + const mockTaskRunner = { + run: jest.fn(), + }; + mockScheduler.createScheduledTaskRunner = jest + .fn() + .mockReturnValue(mockTaskRunner); + + const cleaner = new NotificationCleaner( + mockConfig, + mockScheduler, + mockLogger, + mockDatabase, + ); + expect(cleaner).toBeInstanceOf(NotificationCleaner); + await cleaner.initTaskRunner(); + + expect(mockScheduler.createScheduledTaskRunner).toHaveBeenCalled(); + expect(mockTaskRunner.run).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'notification-cleaner', + fn: expect.any(Function), + }), + ); + }); + + it('should not create a task runner if retention is disabled', async () => { + mockConfig = mockServices.rootConfig({ + data: { notifications: { retention: false } }, + }); + const cleaner = new NotificationCleaner( + mockConfig, + mockScheduler, + mockLogger, + mockDatabase, + ); + await cleaner.initTaskRunner(); + + expect(mockScheduler.createScheduledTaskRunner).not.toHaveBeenCalled(); + expect(mockLogger.info).toHaveBeenCalledWith( + 'Notification retention is disabled, skipping notification cleaner task', + ); + }); + }); + + describe('clearNotifications', () => { + it('should clear notifications', async () => { + mockDatabase.clearNotifications = jest + .fn() + .mockResolvedValue({ deletedCount: 1 }); + const mockTaskRunner = { + run: jest.fn().mockImplementation(({ fn }) => fn()), + }; + mockScheduler.createScheduledTaskRunner = jest + .fn() + .mockReturnValue(mockTaskRunner); + + const cleaner = new NotificationCleaner( + mockConfig, + mockScheduler, + mockLogger, + mockDatabase, + ); + await cleaner.initTaskRunner(); + + expect(mockLogger.info).toHaveBeenCalledWith( + 'Starting notification cleaner task', + ); + expect(mockLogger.info).toHaveBeenCalledWith( + 'Notification cleaner task completed successfully, deleted 1 notifications', + ); + expect(mockDatabase.clearNotifications).toHaveBeenCalledWith({ + maxAge: { years: 1 }, + }); + }); + }); +}); diff --git a/plugins/notifications-backend/src/service/NotificationCleaner.ts b/plugins/notifications-backend/src/service/NotificationCleaner.ts new file mode 100644 index 0000000000..6733a843b5 --- /dev/null +++ b/plugins/notifications-backend/src/service/NotificationCleaner.ts @@ -0,0 +1,91 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskScheduleDefinition, +} from '@backstage/backend-plugin-api'; +import { Config, readDurationFromConfig } from '@backstage/config'; +import { NotificationsStore } from '../database'; +import { HumanDuration } from '@backstage/types'; +import { ForwardedError } from '@backstage/errors'; + +export class NotificationCleaner { + private readonly retention: HumanDuration = { years: 1 }; + private readonly enabled: boolean = true; + + constructor( + config: Config, + private readonly scheduler: SchedulerService, + private readonly logger: LoggerService, + private readonly database: NotificationsStore, + ) { + if (config.has('notifications.retention')) { + const retentionConfig = config.get('notifications.retention'); + if (typeof retentionConfig === 'boolean' && !retentionConfig) { + logger.info( + 'Notification retention is disabled, skipping notification cleaner task', + ); + this.enabled = false; + return; + } + this.retention = readDurationFromConfig(config, { + key: 'notifications.retention', + }); + } + } + + async initTaskRunner() { + if (!this.enabled) { + return; + } + + const schedule: SchedulerServiceTaskScheduleDefinition = { + frequency: { cron: '0 0 * * *' }, + timeout: { hours: 1 }, + initialDelay: { hours: 1 }, + scope: 'global', + }; + + const taskRunner = this.scheduler.createScheduledTaskRunner(schedule); + await taskRunner.run({ + id: 'notification-cleaner', + fn: async () => { + await this.clearNotifications( + this.logger, + this.database, + this.retention, + ); + }, + }); + } + + private async clearNotifications( + logger: LoggerService, + database: NotificationsStore, + retention: HumanDuration, + ) { + logger.info('Starting notification cleaner task'); + try { + const result = await database.clearNotifications({ maxAge: retention }); + logger.info( + `Notification cleaner task completed successfully, deleted ${result.deletedCount} notifications`, + ); + } catch (error) { + throw new ForwardedError('Notification cleaner task failed', error); + } + } +} diff --git a/plugins/notifications-backend/src/service/router.test.ts b/plugins/notifications-backend/src/service/router.test.ts index 0538e90697..1b3f2e8fd6 100644 --- a/plugins/notifications-backend/src/service/router.test.ts +++ b/plugins/notifications-backend/src/service/router.test.ts @@ -29,8 +29,10 @@ import { NotificationSendOptions } from '@backstage/plugin-notifications-node'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; import { DatabaseService } from '@backstage/backend-plugin-api'; import { v4 as uuid } from 'uuid'; +import { DatabaseNotificationsStore } from '../database'; const databases = TestDatabases.create(); +let store: DatabaseNotificationsStore; async function createDatabase( databaseId: TestDatabaseId, @@ -83,6 +85,9 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { beforeAll(async () => { database = await createDatabase(databaseId); + store = await DatabaseNotificationsStore.create({ + database, + }); }); describe('POST /notifications', () => { @@ -93,7 +98,7 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { beforeAll(async () => { const router = await createRouter({ logger: mockServices.logger.mock(), - database, + store, signals: signalService, userInfo, config, @@ -460,7 +465,7 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { beforeAll(async () => { const router = await createRouter({ logger: mockServices.logger.mock(), - database, + store, signals: signalService, userInfo, config, @@ -550,7 +555,7 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { beforeAll(async () => { const router = await createRouter({ logger: mockServices.logger.mock(), - database, + store, signals: signalService, userInfo, config, @@ -600,7 +605,7 @@ describe.each(databases.eachSupportedId())('createRouter (%s)', databaseId => { beforeAll(async () => { const router = await createRouter({ logger: mockServices.logger.mock(), - database, + store, signals: signalService, userInfo, config, diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index f502d49cc4..9563d7965b 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -17,9 +17,9 @@ import express, { Request, Response } from 'express'; import Router from 'express-promise-router'; import { - DatabaseNotificationsStore, normalizeSeverity, NotificationGetOptions, + NotificationsStore, TopicGetOptions, } from '../database'; import { v4 as uuid } from 'uuid'; @@ -31,7 +31,6 @@ import { import { InputError, NotFoundError } from '@backstage/errors'; import { AuthService, - DatabaseService, HttpAuthService, LoggerService, UserInfoService, @@ -58,7 +57,7 @@ import pThrottle from 'p-throttle'; export interface RouterOptions { logger: LoggerService; config: Config; - database: DatabaseService; + store: NotificationsStore; auth: AuthService; httpAuth: HttpAuthService; userInfo: UserInfoService; @@ -74,7 +73,7 @@ export async function createRouter( const { config, logger, - database, + store, auth, httpAuth, userInfo, @@ -84,7 +83,6 @@ export async function createRouter( } = options; const WEB_NOTIFICATION_CHANNEL = 'Web'; - const store = await DatabaseNotificationsStore.create({ database }); const frontendBaseUrl = config.getString('app.baseUrl'); const concurrencyLimit = config.getOptionalNumber('notifications.concurrencyLimit') ?? 10; From f77c481d4ab4a7d80bf182849b66ad21b5721bea Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Thu, 1 May 2025 14:40:05 +0200 Subject: [PATCH 120/301] feat: allow the ability to disable relations from one side of the relation graph Signed-off-by: benjdlambert --- .../catalog-backend-module-ldap/config.d.ts | 112 +++++++++++++++++- .../src/ldap/config.ts | 12 ++ .../src/ldap/read.test.ts | 97 +++++++++++++++ .../src/ldap/read.ts | 18 ++- 4 files changed, 232 insertions(+), 7 deletions(-) diff --git a/plugins/catalog-backend-module-ldap/config.d.ts b/plugins/catalog-backend-module-ldap/config.d.ts index 8bd8623891..5474108700 100644 --- a/plugins/catalog-backend-module-ldap/config.d.ts +++ b/plugins/catalog-backend-module-ldap/config.d.ts @@ -93,6 +93,17 @@ export interface Config { pagePause?: boolean; }; }; + /** + * Additional parsing config + */ + parsing?: { + /** + * Whether to skip the memberOf attribute on the users to power the relations of users and groups + * + * @default false + */ + skipMemberOf?: boolean; + }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -173,6 +184,17 @@ export interface Config { pagePause?: boolean; }; }; + /** + * Additional parsing config + */ + parsing?: { + /** + * Whether to skip the memberOf attribute on the users to power the relations of users and groups + * + * @default false + */ + skipMemberOf?: boolean; + }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -258,6 +280,17 @@ export interface Config { pagePause?: boolean; }; }; + /** + * Additional parsing config + */ + parsing?: { + /** + * Whether to skip the member attributes on the groups to power the relations of users and groups + * + * @default false + */ + skipMembers?: boolean; + }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -348,6 +381,17 @@ export interface Config { pagePause?: boolean; }; }; + /** + * Additional parsing config + */ + parsing?: { + /** + * Whether to skip the member attributes on the groups to power the relations of users and groups + * + * @default false + */ + skipMembers?: boolean; + }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -508,6 +552,17 @@ export interface Config { pagePause?: boolean; }; }; + /** + * Additional parsing config + */ + parsing?: { + /** + * Whether to skip the memberOf attribute on the users to power the relations of users and groups + * + * @default false + */ + skipMemberOf?: boolean; + }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -588,6 +643,17 @@ export interface Config { pagePause?: boolean; }; }; + /** + * Additional parsing config + */ + parsing?: { + /** + * Whether to skip the memberOf attribute on the users to power the relations of users and groups + * + * @default false + */ + skipMemberOf?: boolean; + }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -673,6 +739,17 @@ export interface Config { pagePause?: boolean; }; }; + /** + * Additional parsing config + */ + parsing?: { + /** + * Whether to skip the member attributes on the groups to power the relations of users and groups + * + * @default false + */ + skipMembers?: boolean; + }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -763,6 +840,17 @@ export interface Config { pagePause?: boolean; }; }; + /** + * Additional parsing config + */ + parsing?: { + /** + * Whether to skip the member attributes on the groups to power the relations of users and groups + * + * @default false + */ + skipMembers?: boolean; + }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -921,12 +1009,22 @@ export interface Config { pagePause?: boolean; }; }; + /** + * Additional parsing config + */ + parsing?: { + /** + * Whether to skip the memberOf attribute on the users to power the relations of users and groups + * + * @default false + */ + skipMemberOf?: boolean; + }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. * * This can be useful for example if you want to hard code a - * namespace or similar on the generated entities. */ set?: { [key: string]: JsonValue }; /** @@ -1006,6 +1104,18 @@ export interface Config { }; }; /** + * Additional parsing config + */ + parsing?: { + /** + * Whether to skip the member attributes on the groups to power the relations of users and groups + * + * @default false + */ + skipMembers?: boolean; + }; + /** + * @default false * JSON paths (on a.b.c form) and hard coded values to set on those * paths. * diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.ts index 6fdf682b42..87951a9801 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.ts @@ -90,6 +90,12 @@ export type UserConfig = { // Only the scope, filter, attributes, and paged fields are supported. The // default is scope "one" and attributes "*" and "+". options: SearchOptions; + + // Additional parsing config + parsing?: { + // Whether to skip the memberOf attribute on the users to power the relations of users and groups + skipMemberOf?: boolean; + }; // JSON paths (on a.b.c form) and hard coded values to set on those paths set?: { [path: string]: JsonValue }; // Mappings from well known entity fields, to LDAP attribute names @@ -129,6 +135,12 @@ export type GroupConfig = { // The search options to use. // Only the scope, filter, attributes, and paged fields are supported. options: SearchOptions; + + // Additional parsing config + parsing?: { + // Whether to skip the members attribute on the groups to power the relations of users and groups + skipMembers?: boolean; + }; // JSON paths (on a.b.c form) and hard coded values to set on those paths set?: { [path: string]: JsonValue }; // Mappings from well known entity fields, to LDAP attribute names diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts index 4e4f4f7cfd..f1070ad1f0 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts @@ -210,6 +210,55 @@ describe('readLdapUsers', () => { ); }); + it('should allow skipping memberOf', async () => { + client.getVendor.mockResolvedValue(DefaultLdapVendor); + client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { + await fn(searchEntry({ memberOf: ['x', 'y', 'z'] })); + }); + + client.getVendor.mockResolvedValue(DefaultLdapVendor); + client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { + await fn( + searchEntry({ + uid: ['uid-value'], + description: ['description-value'], + cn: ['cn-value'], + mail: ['mail-value'], + avatarUrl: ['avatarUrl-value'], + memberOf: ['x', 'y', 'z'], + customDN: ['dn-value'], + customUUID: ['uuid-value'], + }), + ); + }); + const config: UserConfig[] = [ + { + dn: 'ddd', + options: {}, + parsing: { + skipMemberOf: true, + }, + map: { + rdn: 'uid', + name: 'uid', + description: 'description', + displayName: 'cn', + email: 'mail', + picture: 'avatarUrl', + memberOf: 'memberOf', + }, + }, + ]; + + const vendorConfig: VendorConfig = { + dnAttributeName: 'customDN', + uuidAttributeName: 'customUUID', + }; + + const { userMemberOf } = await readLdapUsers(client, config, vendorConfig); + expect(userMemberOf.size).toBe(0); + }); + it('transfers all attributes from Microsoft Active Directory', async () => { client.getVendor.mockResolvedValue(ActiveDirectoryVendor); client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { @@ -729,6 +778,54 @@ describe('readLdapGroups', () => { ); }); + it('should allow skipping members', async () => { + client.getVendor.mockResolvedValue(DefaultLdapVendor); + client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { + await fn( + searchEntry({ + cn: ['cn-value'], + description: ['description-value'], + tt: ['type-value'], + mail: ['mail-value'], + avatarUrl: ['avatarUrl-value'], + memberOf: ['x', 'y', 'z'], + member: ['e', 'f', 'g'], + customDN: ['dn-value'], + customUUID: ['uuid-value'], + }), + ); + }); + const config: GroupConfig[] = [ + { + dn: 'ddd', + options: {}, + parsing: { + skipMembers: true, + }, + map: { + rdn: 'cn', + name: 'cn', + description: 'description', + displayName: 'cn', + email: 'mail', + picture: 'avatarUrl', + type: 'tt', + memberOf: 'memberOf', + members: 'member', + }, + }, + ]; + + const vendorConfig: VendorConfig = { + dnAttributeName: 'customDN', + uuidAttributeName: 'customUUID', + }; + + const { groupMember } = await readLdapGroups(client, config, vendorConfig); + + expect(groupMember.size).toBe(0); + }); + it('can process a list of GroupConfigs', async () => { client.getVendor.mockResolvedValue(DefaultLdapVendor); client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.ts index 9b9041913d..aadaaae7cb 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.ts @@ -143,9 +143,12 @@ export async function readLdapUsers( return; } - mapReferencesAttr(user, vendor, map.memberOf, (myDn, vs) => { - ensureItems(userMemberOf, myDn, vs); - }); + if (!cfg.parsing?.skipMemberOf) { + mapReferencesAttr(user, vendor, map.memberOf, (myDn, vs) => { + ensureItems(userMemberOf, myDn, vs); + }); + } + entities.push(entity); }); } @@ -277,9 +280,12 @@ export async function readLdapGroups( mapReferencesAttr(entry, vendor, map.memberOf, (myDn, vs) => { ensureItems(groupMemberOf, myDn, vs); }); - mapReferencesAttr(entry, vendor, map.members, (myDn, vs) => { - ensureItems(groupMember, myDn, vs); - }); + + if (!cfg.parsing?.skipMembers) { + mapReferencesAttr(entry, vendor, map.members, (myDn, vs) => { + ensureItems(groupMember, myDn, vs); + }); + } groups.push(entity); }); From f07b0adafa4b22e3a3f828008ea334ad08f68564 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Thu, 1 May 2025 14:46:07 +0200 Subject: [PATCH 121/301] chore: add changeset Signed-off-by: benjdlambert Signed-off-by: benjdlambert --- .changeset/yellow-rats-argue.md | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .changeset/yellow-rats-argue.md diff --git a/.changeset/yellow-rats-argue.md b/.changeset/yellow-rats-argue.md new file mode 100644 index 0000000000..16f5468cb8 --- /dev/null +++ b/.changeset/yellow-rats-argue.md @@ -0,0 +1,38 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': patch +--- + +Added the ability to configure disabling one side of the relations tree with LDAP. + +Groups have a `member` attribute and users have a `memberOf` attribute, however these often can drift out of sync, leaving weird states in the Catalog as we collate these results together and deduplicate them. + +You can chose to optionally disable one side of these relationships, or even both by providing config in `app-config.yaml` under either the `GroupConfig` or `UserConfig`: + +```yaml +catalog: + providers: + ldapOrg: + default: + target: ldaps://ds.example.net + bind: + dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net + secret: ${LDAP_SECRET} + users: + - dn: ou=people,ou=example,dc=example,dc=net + options: + filter: (uid=*) + parsing: + # this defaults to false, to disable use the following: + skipMemberOf: true + groups: + - dn: ou=access,ou=groups,ou=example,dc=example,dc=net + options: + filter: (&(objectClass=some-group-class)(!(groupType=email))) + map: + description: l + set: + metadata.customField: 'hello' + parsing: + # this defaults to false, to disable use the following: + skipMember: true +``` From 2c0dd741e9d7bbec3fd462642af9ba9411a2d2a6 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 2 May 2025 09:35:00 +0200 Subject: [PATCH 122/301] chore: fixing api-report Signed-off-by: benjdlambert --- plugins/catalog-backend-module-ldap/report.api.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/catalog-backend-module-ldap/report.api.md b/plugins/catalog-backend-module-ldap/report.api.md index 16546cb773..9554f0ff19 100644 --- a/plugins/catalog-backend-module-ldap/report.api.md +++ b/plugins/catalog-backend-module-ldap/report.api.md @@ -52,6 +52,9 @@ export function defaultUserTransformer( export type GroupConfig = { dn: string; options: SearchOptions; + parsing?: { + skipMembers?: boolean; + }; set?: { [path: string]: JsonValue; }; @@ -249,6 +252,9 @@ export type TLSConfig = { export type UserConfig = { dn: string; options: SearchOptions; + parsing?: { + skipMemberOf?: boolean; + }; set?: { [path: string]: JsonValue; }; From e527d469989ee3bf48cac57e9b59b138bb5e3d99 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Tue, 6 May 2025 11:46:53 +0200 Subject: [PATCH 123/301] chore: refactor to set memberOf or member as null Signed-off-by: benjdlambert --- .../catalog-backend-module-ldap/config.d.ts | 141 ++---------------- .../src/ldap/config.ts | 6 +- .../src/ldap/read.test.ts | 11 +- 3 files changed, 22 insertions(+), 136 deletions(-) diff --git a/plugins/catalog-backend-module-ldap/config.d.ts b/plugins/catalog-backend-module-ldap/config.d.ts index 5474108700..f9b49eabee 100644 --- a/plugins/catalog-backend-module-ldap/config.d.ts +++ b/plugins/catalog-backend-module-ldap/config.d.ts @@ -93,17 +93,6 @@ export interface Config { pagePause?: boolean; }; }; - /** - * Additional parsing config - */ - parsing?: { - /** - * Whether to skip the memberOf attribute on the users to power the relations of users and groups - * - * @default false - */ - skipMemberOf?: boolean; - }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -152,7 +141,7 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.memberOf field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; }; } | Array<{ @@ -184,17 +173,6 @@ export interface Config { pagePause?: boolean; }; }; - /** - * Additional parsing config - */ - parsing?: { - /** - * Whether to skip the memberOf attribute on the users to power the relations of users and groups - * - * @default false - */ - skipMemberOf?: boolean; - }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -243,7 +221,7 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.memberOf field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; }; }>; @@ -280,17 +258,6 @@ export interface Config { pagePause?: boolean; }; }; - /** - * Additional parsing config - */ - parsing?: { - /** - * Whether to skip the member attributes on the groups to power the relations of users and groups - * - * @default false - */ - skipMembers?: boolean; - }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -344,12 +311,12 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.parent field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; /** * The name of the attribute that shall be used for the values of * the spec.children field of the entity. Defaults to "member". */ - members?: string; + members?: string | null; }; } | Array<{ @@ -381,17 +348,6 @@ export interface Config { pagePause?: boolean; }; }; - /** - * Additional parsing config - */ - parsing?: { - /** - * Whether to skip the member attributes on the groups to power the relations of users and groups - * - * @default false - */ - skipMembers?: boolean; - }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -445,12 +401,12 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.parent field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; /** * The name of the attribute that shall be used for the values of * the spec.children field of the entity. Defaults to "member". */ - members?: string; + members?: string | null; }; }>; /** @@ -552,17 +508,6 @@ export interface Config { pagePause?: boolean; }; }; - /** - * Additional parsing config - */ - parsing?: { - /** - * Whether to skip the memberOf attribute on the users to power the relations of users and groups - * - * @default false - */ - skipMemberOf?: boolean; - }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -611,7 +556,7 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.memberOf field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; }; } | Array<{ @@ -643,17 +588,7 @@ export interface Config { pagePause?: boolean; }; }; - /** - * Additional parsing config - */ - parsing?: { - /** - * Whether to skip the memberOf attribute on the users to power the relations of users and groups - * - * @default false - */ - skipMemberOf?: boolean; - }; + /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -702,7 +637,7 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.memberOf field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; }; }>; @@ -739,17 +674,6 @@ export interface Config { pagePause?: boolean; }; }; - /** - * Additional parsing config - */ - parsing?: { - /** - * Whether to skip the member attributes on the groups to power the relations of users and groups - * - * @default false - */ - skipMembers?: boolean; - }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -803,12 +727,12 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.parent field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; /** * The name of the attribute that shall be used for the values of * the spec.children field of the entity. Defaults to "member". */ - members?: string; + members?: string | null; }; } | Array<{ @@ -840,17 +764,6 @@ export interface Config { pagePause?: boolean; }; }; - /** - * Additional parsing config - */ - parsing?: { - /** - * Whether to skip the member attributes on the groups to power the relations of users and groups - * - * @default false - */ - skipMembers?: boolean; - }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -904,12 +817,12 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.parent field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; /** * The name of the attribute that shall be used for the values of * the spec.children field of the entity. Defaults to "member". */ - members?: string; + members?: string | null; }; }>; @@ -1009,17 +922,6 @@ export interface Config { pagePause?: boolean; }; }; - /** - * Additional parsing config - */ - parsing?: { - /** - * Whether to skip the memberOf attribute on the users to power the relations of users and groups - * - * @default false - */ - skipMemberOf?: boolean; - }; /** * JSON paths (on a.b.c form) and hard coded values to set on those * paths. @@ -1067,7 +969,7 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.memberOf field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; }; }; @@ -1103,17 +1005,6 @@ export interface Config { pagePause?: boolean; }; }; - /** - * Additional parsing config - */ - parsing?: { - /** - * Whether to skip the member attributes on the groups to power the relations of users and groups - * - * @default false - */ - skipMembers?: boolean; - }; /** * @default false * JSON paths (on a.b.c form) and hard coded values to set on those @@ -1168,12 +1059,12 @@ export interface Config { * The name of the attribute that shall be used for the values of * the spec.parent field of the entity. Defaults to "memberOf". */ - memberOf?: string; + memberOf?: string | null; /** * The name of the attribute that shall be used for the values of * the spec.children field of the entity. Defaults to "member". */ - members?: string; + members?: string | null; }; }; /** diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.ts index 87951a9801..95e45f3d5f 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.ts @@ -120,7 +120,7 @@ export type UserConfig = { picture?: string; // The name of the attribute that shall be used for the values of the // spec.memberOf field of the entity. Defaults to "memberOf". - memberOf: string; + memberOf: string | null; }; }; @@ -168,10 +168,10 @@ export type GroupConfig = { picture?: string; // The name of the attribute that shall be used for the values of the // spec.parent field of the entity. Defaults to "memberOf". - memberOf: string; + memberOf: string | null; // The name of the attribute that shall be used for the values of the // spec.children field of the entity. Defaults to "member". - members: string; + members: string | null; }; }; diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts index f1070ad1f0..374cc2afd0 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts @@ -235,9 +235,7 @@ describe('readLdapUsers', () => { { dn: 'ddd', options: {}, - parsing: { - skipMemberOf: true, - }, + map: { rdn: 'uid', name: 'uid', @@ -245,7 +243,7 @@ describe('readLdapUsers', () => { displayName: 'cn', email: 'mail', picture: 'avatarUrl', - memberOf: 'memberOf', + memberOf: null, }, }, ]; @@ -799,9 +797,6 @@ describe('readLdapGroups', () => { { dn: 'ddd', options: {}, - parsing: { - skipMembers: true, - }, map: { rdn: 'cn', name: 'cn', @@ -811,7 +806,7 @@ describe('readLdapGroups', () => { picture: 'avatarUrl', type: 'tt', memberOf: 'memberOf', - members: 'member', + members: null, }, }, ]; From 0b41d0dd2cff3c7454667e1eb6d62bcce3315e11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 16 Jun 2025 14:43:37 +0200 Subject: [PATCH 124/301] allow nulls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/yellow-rats-argue.md | 16 ++++++++-------- .../catalog-backend-module-ldap/report.api.md | 12 +++--------- .../src/ldap/config.ts | 10 ---------- .../src/ldap/read.ts | 18 +++++++----------- 4 files changed, 18 insertions(+), 38 deletions(-) diff --git a/.changeset/yellow-rats-argue.md b/.changeset/yellow-rats-argue.md index 16f5468cb8..321498fd37 100644 --- a/.changeset/yellow-rats-argue.md +++ b/.changeset/yellow-rats-argue.md @@ -4,9 +4,9 @@ Added the ability to configure disabling one side of the relations tree with LDAP. -Groups have a `member` attribute and users have a `memberOf` attribute, however these often can drift out of sync, leaving weird states in the Catalog as we collate these results together and deduplicate them. +Groups have a `member` attribute and users have a `memberOf` attribute, however these can drift out of sync in some LDAP installations, leaving weird states in the Catalog as we collate these results together and deduplicate them. -You can chose to optionally disable one side of these relationships, or even both by providing config in `app-config.yaml` under either the `GroupConfig` or `UserConfig`: +You can chose to optionally disable one side of these relationships, or even both by setting the respective mapping to `null` in your `app-config.yaml` for your groups and/or users: ```yaml catalog: @@ -21,9 +21,9 @@ catalog: - dn: ou=people,ou=example,dc=example,dc=net options: filter: (uid=*) - parsing: - # this defaults to false, to disable use the following: - skipMemberOf: true + map: + # this ensures that outgoing memberships from users is ignored + memberOf: null groups: - dn: ou=access,ou=groups,ou=example,dc=example,dc=net options: @@ -32,7 +32,7 @@ catalog: description: l set: metadata.customField: 'hello' - parsing: - # this defaults to false, to disable use the following: - skipMember: true + map: + # this ensures that outgoing memberships from groups is ignored + members: null ``` diff --git a/plugins/catalog-backend-module-ldap/report.api.md b/plugins/catalog-backend-module-ldap/report.api.md index 9554f0ff19..8357a9ae38 100644 --- a/plugins/catalog-backend-module-ldap/report.api.md +++ b/plugins/catalog-backend-module-ldap/report.api.md @@ -52,9 +52,6 @@ export function defaultUserTransformer( export type GroupConfig = { dn: string; options: SearchOptions; - parsing?: { - skipMembers?: boolean; - }; set?: { [path: string]: JsonValue; }; @@ -66,8 +63,8 @@ export type GroupConfig = { displayName: string; email?: string; picture?: string; - memberOf: string; - members: string; + memberOf: string | null; + members: string | null; }; }; @@ -252,9 +249,6 @@ export type TLSConfig = { export type UserConfig = { dn: string; options: SearchOptions; - parsing?: { - skipMemberOf?: boolean; - }; set?: { [path: string]: JsonValue; }; @@ -265,7 +259,7 @@ export type UserConfig = { displayName: string; email: string; picture?: string; - memberOf: string; + memberOf: string | null; }; }; diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.ts index 95e45f3d5f..8c7fd0dda5 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.ts @@ -91,11 +91,6 @@ export type UserConfig = { // default is scope "one" and attributes "*" and "+". options: SearchOptions; - // Additional parsing config - parsing?: { - // Whether to skip the memberOf attribute on the users to power the relations of users and groups - skipMemberOf?: boolean; - }; // JSON paths (on a.b.c form) and hard coded values to set on those paths set?: { [path: string]: JsonValue }; // Mappings from well known entity fields, to LDAP attribute names @@ -136,11 +131,6 @@ export type GroupConfig = { // Only the scope, filter, attributes, and paged fields are supported. options: SearchOptions; - // Additional parsing config - parsing?: { - // Whether to skip the members attribute on the groups to power the relations of users and groups - skipMembers?: boolean; - }; // JSON paths (on a.b.c form) and hard coded values to set on those paths set?: { [path: string]: JsonValue }; // Mappings from well known entity fields, to LDAP attribute names diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.ts index aadaaae7cb..a3f3ce1e75 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.ts @@ -143,11 +143,9 @@ export async function readLdapUsers( return; } - if (!cfg.parsing?.skipMemberOf) { - mapReferencesAttr(user, vendor, map.memberOf, (myDn, vs) => { - ensureItems(userMemberOf, myDn, vs); - }); - } + mapReferencesAttr(user, vendor, map.memberOf, (myDn, vs) => { + ensureItems(userMemberOf, myDn, vs); + }); entities.push(entity); }); @@ -281,11 +279,9 @@ export async function readLdapGroups( ensureItems(groupMemberOf, myDn, vs); }); - if (!cfg.parsing?.skipMembers) { - mapReferencesAttr(entry, vendor, map.members, (myDn, vs) => { - ensureItems(groupMember, myDn, vs); - }); - } + mapReferencesAttr(entry, vendor, map.members, (myDn, vs) => { + ensureItems(groupMember, myDn, vs); + }); groups.push(entity); }); @@ -355,7 +351,7 @@ export async function readLdapOrg( function mapReferencesAttr( entry: SearchEntry, vendor: LdapVendor, - attributeName: string | undefined, + attributeName: string | undefined | null, setter: (sourceDn: string, targets: string[]) => void, ) { if (attributeName) { From 31dcaf72ae0826258e13820556157030f7fc12be Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Mon, 16 Jun 2025 14:58:49 +0200 Subject: [PATCH 125/301] chore: destructive by default Signed-off-by: benjdlambert --- .../src/entrypoints/actions/actionsServiceFactory.test.ts | 2 +- .../actionsRegistry/DefaultActionsRegistryService.ts | 5 +++-- .../actionsRegistry/actionsRegistryServiceFactory.test.ts | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts index 58a79c6a2a..c43945f1d5 100644 --- a/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts @@ -312,7 +312,7 @@ describe('actionsServiceFactory', () => { }, }, attributes: { - destructive: false, + destructive: true, idempotent: false, readOnly: false, }, diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts b/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts index cddb04c817..bee3f7d565 100644 --- a/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts +++ b/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts @@ -68,8 +68,9 @@ export class DefaultActionsRegistryService implements ActionsRegistryService { id, ...action, attributes: { - // todo(blam): what's safe defaults? - destructive: action.attributes?.destructive ?? false, + // Inspired by the @modelcontextprotocol/sdk defaults for the hints. + // https://github.com/modelcontextprotocol/typescript-sdk/blob/dd69efa1de8646bb6b195ff8d5f52e13739f4550/src/types.ts#L777-L812 + destructive: action.attributes?.destructive ?? true, idempotent: action.attributes?.idempotent ?? false, readOnly: action.attributes?.readOnly ?? false, }, diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts index e885cb10a5..2de6272365 100644 --- a/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts @@ -220,7 +220,7 @@ describe('actionsRegistryServiceFactory', () => { { name: 'test', attributes: { - destructive: false, + destructive: true, idempotent: false, readOnly: false, }, @@ -243,7 +243,7 @@ describe('actionsRegistryServiceFactory', () => { title: 'Test', description: 'Test', attributes: { - destructive: true, + destructive: false, idempotent: true, readOnly: true, }, @@ -275,7 +275,7 @@ describe('actionsRegistryServiceFactory', () => { title: 'Test', description: 'Test', attributes: { - destructive: true, + destructive: false, idempotent: true, readOnly: true, }, From abcc776f1d1288d4b50b8f7b5bd214f2c6f30380 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 13:18:32 +0000 Subject: [PATCH 126/301] fix(deps): update dependency @types/prop-types to v15.7.15 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3bc412b836..ec065e8ba9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22439,9 +22439,9 @@ __metadata: linkType: hard "@types/prop-types@npm:*, @types/prop-types@npm:^15.0.0, @types/prop-types@npm:^15.7.12, @types/prop-types@npm:^15.7.3": - version: 15.7.14 - resolution: "@types/prop-types@npm:15.7.14" - checksum: 10/d0c5407b9ccc3dd5fae0ccf9b1007e7622ba5e6f1c18399b4f24dff33619d469da4b9fa918a374f19dc0d9fe6a013362aab0b844b606cfc10676efba3f5f736d + version: 15.7.15 + resolution: "@types/prop-types@npm:15.7.15" + checksum: 10/31aa2f59b28f24da6fb4f1d70807dae2aedfce090ec63eaf9ea01727a9533ef6eaf017de5bff99fbccad7d1c9e644f52c6c2ba30869465dd22b1a7221c29f356 languageName: node linkType: hard From 9b8af68f69f79a28eaae06d00caa3061d999a044 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 13:18:56 +0000 Subject: [PATCH 127/301] fix(deps): update dependency dockerode to v4.0.7 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3bc412b836..01c11f0221 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29468,8 +29468,8 @@ __metadata: linkType: hard "dockerode@npm:^4.0.0": - version: 4.0.6 - resolution: "dockerode@npm:4.0.6" + version: 4.0.7 + resolution: "dockerode@npm:4.0.7" dependencies: "@balena/dockerignore": "npm:^1.0.2" "@grpc/grpc-js": "npm:^1.11.1" @@ -29478,7 +29478,7 @@ __metadata: protobufjs: "npm:^7.3.2" tar-fs: "npm:~2.1.2" uuid: "npm:^10.0.0" - checksum: 10/75bd706f20f01742d22913b72e2a5215a4d9f79772c29079772f84fc41a2b1890a704a1aa3d1d764405367090e93c33197d6add19fde3546bac919d98eebc8e3 + checksum: 10/d7cd174cf4489f41335ec8aaaa7c98c164a624f9a793544aa5280d85254ce276e7797de896042ce47d87aca6f8d2653acc37a0d18807d4ce8ea31892faef40a8 languageName: node linkType: hard From 1a0fb41021e73bf2b4b0c6487e76887d12b15c59 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 14:03:17 +0000 Subject: [PATCH 128/301] fix(deps): update dependency docusaurus-pushfeedback to v1.0.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- microsite/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microsite/yarn.lock b/microsite/yarn.lock index a200d0f9da..40a000df33 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -5942,11 +5942,11 @@ __metadata: linkType: hard "docusaurus-pushfeedback@npm:^1.0.0": - version: 1.0.3 - resolution: "docusaurus-pushfeedback@npm:1.0.3" + version: 1.0.5 + resolution: "docusaurus-pushfeedback@npm:1.0.5" peerDependencies: "@docusaurus/core": 3.x - checksum: 10/ee80ae0c1fc079b2c317cab86d83ba50cce18938a6ce0ac647aad25dc68fc0ad659e55c11e93d0e7c23270c503c3cb300111dfbdd04785ef7aaf6fb123b57eae + checksum: 10/5323af7f1c7b4590744ea9099cacb49087d98c48c11e2ed957ed744d6210a3d82fa0cc42ef0f011f25b0d08c2d0040065c8a2b641c7c9160532ceb51ad41475c languageName: node linkType: hard From 24f454a510d0a99ad290e5e3416d52076a3a5e4e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 14:03:56 +0000 Subject: [PATCH 129/301] fix(deps): update dependency esbuild to v0.25.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 206 +++++++++++++++++++++++++++--------------------------- 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3bc412b836..f004f965b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9667,9 +9667,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/aix-ppc64@npm:0.25.4" +"@esbuild/aix-ppc64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/aix-ppc64@npm:0.25.5" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard @@ -9681,9 +9681,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/android-arm64@npm:0.25.4" +"@esbuild/android-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/android-arm64@npm:0.25.5" conditions: os=android & cpu=arm64 languageName: node linkType: hard @@ -9695,9 +9695,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/android-arm@npm:0.25.4" +"@esbuild/android-arm@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/android-arm@npm:0.25.5" conditions: os=android & cpu=arm languageName: node linkType: hard @@ -9709,9 +9709,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/android-x64@npm:0.25.4" +"@esbuild/android-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/android-x64@npm:0.25.5" conditions: os=android & cpu=x64 languageName: node linkType: hard @@ -9723,9 +9723,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/darwin-arm64@npm:0.25.4" +"@esbuild/darwin-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/darwin-arm64@npm:0.25.5" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard @@ -9737,9 +9737,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/darwin-x64@npm:0.25.4" +"@esbuild/darwin-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/darwin-x64@npm:0.25.5" conditions: os=darwin & cpu=x64 languageName: node linkType: hard @@ -9751,9 +9751,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/freebsd-arm64@npm:0.25.4" +"@esbuild/freebsd-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/freebsd-arm64@npm:0.25.5" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard @@ -9765,9 +9765,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/freebsd-x64@npm:0.25.4" +"@esbuild/freebsd-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/freebsd-x64@npm:0.25.5" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard @@ -9779,9 +9779,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-arm64@npm:0.25.4" +"@esbuild/linux-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-arm64@npm:0.25.5" conditions: os=linux & cpu=arm64 languageName: node linkType: hard @@ -9793,9 +9793,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-arm@npm:0.25.4" +"@esbuild/linux-arm@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-arm@npm:0.25.5" conditions: os=linux & cpu=arm languageName: node linkType: hard @@ -9807,9 +9807,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-ia32@npm:0.25.4" +"@esbuild/linux-ia32@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-ia32@npm:0.25.5" conditions: os=linux & cpu=ia32 languageName: node linkType: hard @@ -9821,9 +9821,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-loong64@npm:0.25.4" +"@esbuild/linux-loong64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-loong64@npm:0.25.5" conditions: os=linux & cpu=loong64 languageName: node linkType: hard @@ -9835,9 +9835,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-mips64el@npm:0.25.4" +"@esbuild/linux-mips64el@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-mips64el@npm:0.25.5" conditions: os=linux & cpu=mips64el languageName: node linkType: hard @@ -9849,9 +9849,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-ppc64@npm:0.25.4" +"@esbuild/linux-ppc64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-ppc64@npm:0.25.5" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard @@ -9863,9 +9863,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-riscv64@npm:0.25.4" +"@esbuild/linux-riscv64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-riscv64@npm:0.25.5" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard @@ -9877,9 +9877,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-s390x@npm:0.25.4" +"@esbuild/linux-s390x@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-s390x@npm:0.25.5" conditions: os=linux & cpu=s390x languageName: node linkType: hard @@ -9891,16 +9891,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/linux-x64@npm:0.25.4" +"@esbuild/linux-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/linux-x64@npm:0.25.5" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/netbsd-arm64@npm:0.25.4" +"@esbuild/netbsd-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/netbsd-arm64@npm:0.25.5" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard @@ -9912,16 +9912,16 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/netbsd-x64@npm:0.25.4" +"@esbuild/netbsd-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/netbsd-x64@npm:0.25.5" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/openbsd-arm64@npm:0.25.4" +"@esbuild/openbsd-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/openbsd-arm64@npm:0.25.5" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard @@ -9933,9 +9933,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/openbsd-x64@npm:0.25.4" +"@esbuild/openbsd-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/openbsd-x64@npm:0.25.5" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard @@ -9947,9 +9947,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/sunos-x64@npm:0.25.4" +"@esbuild/sunos-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/sunos-x64@npm:0.25.5" conditions: os=sunos & cpu=x64 languageName: node linkType: hard @@ -9961,9 +9961,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/win32-arm64@npm:0.25.4" +"@esbuild/win32-arm64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/win32-arm64@npm:0.25.5" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard @@ -9975,9 +9975,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/win32-ia32@npm:0.25.4" +"@esbuild/win32-ia32@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/win32-ia32@npm:0.25.5" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard @@ -9989,9 +9989,9 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.4": - version: 0.25.4 - resolution: "@esbuild/win32-x64@npm:0.25.4" +"@esbuild/win32-x64@npm:0.25.5": + version: 0.25.5 + resolution: "@esbuild/win32-x64@npm:0.25.5" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -30327,34 +30327,34 @@ __metadata: linkType: hard "esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0, esbuild@npm:^0.25.0": - version: 0.25.4 - resolution: "esbuild@npm:0.25.4" + version: 0.25.5 + resolution: "esbuild@npm:0.25.5" dependencies: - "@esbuild/aix-ppc64": "npm:0.25.4" - "@esbuild/android-arm": "npm:0.25.4" - "@esbuild/android-arm64": "npm:0.25.4" - "@esbuild/android-x64": "npm:0.25.4" - "@esbuild/darwin-arm64": "npm:0.25.4" - "@esbuild/darwin-x64": "npm:0.25.4" - "@esbuild/freebsd-arm64": "npm:0.25.4" - "@esbuild/freebsd-x64": "npm:0.25.4" - "@esbuild/linux-arm": "npm:0.25.4" - "@esbuild/linux-arm64": "npm:0.25.4" - "@esbuild/linux-ia32": "npm:0.25.4" - "@esbuild/linux-loong64": "npm:0.25.4" - "@esbuild/linux-mips64el": "npm:0.25.4" - "@esbuild/linux-ppc64": "npm:0.25.4" - "@esbuild/linux-riscv64": "npm:0.25.4" - "@esbuild/linux-s390x": "npm:0.25.4" - "@esbuild/linux-x64": "npm:0.25.4" - "@esbuild/netbsd-arm64": "npm:0.25.4" - "@esbuild/netbsd-x64": "npm:0.25.4" - "@esbuild/openbsd-arm64": "npm:0.25.4" - "@esbuild/openbsd-x64": "npm:0.25.4" - "@esbuild/sunos-x64": "npm:0.25.4" - "@esbuild/win32-arm64": "npm:0.25.4" - "@esbuild/win32-ia32": "npm:0.25.4" - "@esbuild/win32-x64": "npm:0.25.4" + "@esbuild/aix-ppc64": "npm:0.25.5" + "@esbuild/android-arm": "npm:0.25.5" + "@esbuild/android-arm64": "npm:0.25.5" + "@esbuild/android-x64": "npm:0.25.5" + "@esbuild/darwin-arm64": "npm:0.25.5" + "@esbuild/darwin-x64": "npm:0.25.5" + "@esbuild/freebsd-arm64": "npm:0.25.5" + "@esbuild/freebsd-x64": "npm:0.25.5" + "@esbuild/linux-arm": "npm:0.25.5" + "@esbuild/linux-arm64": "npm:0.25.5" + "@esbuild/linux-ia32": "npm:0.25.5" + "@esbuild/linux-loong64": "npm:0.25.5" + "@esbuild/linux-mips64el": "npm:0.25.5" + "@esbuild/linux-ppc64": "npm:0.25.5" + "@esbuild/linux-riscv64": "npm:0.25.5" + "@esbuild/linux-s390x": "npm:0.25.5" + "@esbuild/linux-x64": "npm:0.25.5" + "@esbuild/netbsd-arm64": "npm:0.25.5" + "@esbuild/netbsd-x64": "npm:0.25.5" + "@esbuild/openbsd-arm64": "npm:0.25.5" + "@esbuild/openbsd-x64": "npm:0.25.5" + "@esbuild/sunos-x64": "npm:0.25.5" + "@esbuild/win32-arm64": "npm:0.25.5" + "@esbuild/win32-ia32": "npm:0.25.5" + "@esbuild/win32-x64": "npm:0.25.5" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -30408,7 +30408,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10/227ffe9b31f0b184a0b0a0210bb9d32b2b115b8c5c9b09f08db2c3928cb470fc55a22dbba3c2894365d3abcc62c2089b85638be96a20691d1234d31990ea01b2 + checksum: 10/0fa4c3b42c6ddf1a008e75a4bb3dcab08ce22ac0b31dd59dc01f7fe8e21380bfaec07a2fe3730a7cf430da5a30142d016714b358666325a4733547afa42be405 languageName: node linkType: hard From c94f8e089cc4276c091c2d025d08581f56cdd435 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 2 Jun 2025 11:38:25 +0200 Subject: [PATCH 130/301] (canon)fix: use full menu width for combobox menu filter input. Signed-off-by: Johan Persson --- .changeset/tired-readers-share.md | 5 +++++ packages/canon/css/components.css | 1 + packages/canon/css/menu.css | 1 + packages/canon/css/styles.css | 1 + packages/canon/src/components/Menu/Menu.styles.css | 1 + 5 files changed, 9 insertions(+) create mode 100644 .changeset/tired-readers-share.md diff --git a/.changeset/tired-readers-share.md b/.changeset/tired-readers-share.md new file mode 100644 index 0000000000..148ebd635d --- /dev/null +++ b/.changeset/tired-readers-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +The filter input in menu comboboxes should now always use the full width of the menu it's in. diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index 1b60bf74ed..4ea92e4d86 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -903,6 +903,7 @@ border: none; border-bottom: 1px solid var(--canon-border); background-color: var(--canon-bg-surface-1); + width: 100%; height: 32px; color: var(--canon-fg-primary); line-height: 140%; diff --git a/packages/canon/css/menu.css b/packages/canon/css/menu.css index 4c9433275c..7035fab12b 100644 --- a/packages/canon/css/menu.css +++ b/packages/canon/css/menu.css @@ -94,6 +94,7 @@ border: none; border-bottom: 1px solid var(--canon-border); background-color: var(--canon-bg-surface-1); + width: 100%; height: 32px; color: var(--canon-fg-primary); line-height: 140%; diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index 83329e817c..0bb27fbd56 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -10127,6 +10127,7 @@ border: none; border-bottom: 1px solid var(--canon-border); background-color: var(--canon-bg-surface-1); + width: 100%; height: 32px; color: var(--canon-fg-primary); line-height: 140%; diff --git a/packages/canon/src/components/Menu/Menu.styles.css b/packages/canon/src/components/Menu/Menu.styles.css index 07d257f57f..7ccaa7a634 100644 --- a/packages/canon/src/components/Menu/Menu.styles.css +++ b/packages/canon/src/components/Menu/Menu.styles.css @@ -93,6 +93,7 @@ .canon-SubmenuComboboxSearch { padding-inline: var(--canon-space-3); + width: 100%; height: 32px; border: none; border-bottom: 1px solid var(--canon-border); From dbd64a283f5a186901f0c718c929723518a2f5b8 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 13 Jun 2025 13:18:50 +0200 Subject: [PATCH 131/301] feat: implementing some test frameworks for actions Signed-off-by: benjdlambert --- packages/backend-test-utils/package.json | 4 +- .../next/services/MockActionsRegistry.test.ts | 232 ++++++++++++++++++ .../src/next/services/MockActionsRegistry.ts | 131 ++++++++++ .../src/next/services/mockServices.ts | 9 + .../src/next/wiring/TestBackend.ts | 2 + yarn.lock | 2 + 6 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 packages/backend-test-utils/src/next/services/MockActionsRegistry.test.ts create mode 100644 packages/backend-test-utils/src/next/services/MockActionsRegistry.ts diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index d58720723d..cc47249a2d 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -73,7 +73,9 @@ "testcontainers": "^10.0.0", "textextensions": "^5.16.0", "uuid": "^11.0.0", - "yn": "^4.0.0" + "yn": "^4.0.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.20.4" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/packages/backend-test-utils/src/next/services/MockActionsRegistry.test.ts b/packages/backend-test-utils/src/next/services/MockActionsRegistry.test.ts new file mode 100644 index 0000000000..4b13be921a --- /dev/null +++ b/packages/backend-test-utils/src/next/services/MockActionsRegistry.test.ts @@ -0,0 +1,232 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { startTestBackend } from '../wiring'; +import { mockServices } from './mockServices'; +import { mockCredentials } from './mockCredentials'; +import { Router } from 'express'; +import supertest from 'supertest'; + +describe('MockActionsRegistry', () => { + it('should be able to register and invoke actions', async () => { + const registry = mockServices.actionsRegistry(); + + registry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => + z.object({ + name: z.string(), + }), + output: z => + z.object({ + name: z.string(), + }), + }, + action: async ({ input }) => ({ output: { name: input.name } }), + }); + + const result = await registry.invoke({ + id: 'testing:test', + input: { name: 'test' }, + }); + + expect(result).toEqual({ output: { name: 'test' } }); + }); + + it('should throw an error when the input is invalid to the action', async () => { + const registry = mockServices.actionsRegistry(); + + registry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({ name: z.string() }), + output: z => z.object({ name: z.string() }), + }, + action: async ({ input }) => ({ output: { name: input.name } }), + }); + + await expect( + registry.invoke({ id: 'testing:test', input: { name: 1 } }), + ).rejects.toThrow('Invalid input to action "testing:test"'); + }); + + it('should throw an error when the action is not found', async () => { + const registry = mockServices.actionsRegistry(); + + await expect(registry.invoke({ id: 'test' })).rejects.toThrow( + 'Action "test" not found, available actions: none', + ); + }); + + it('should throw an error when the action is not found with recommended actions', async () => { + const registry = mockServices.actionsRegistry(); + + registry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({ name: z.string() }), + output: z => z.object({ name: z.string() }), + }, + action: async ({ input }) => ({ output: { name: input.name } }), + }); + + await expect(registry.invoke({ id: 'test' })).rejects.toThrow( + 'Action "test" not found, available actions: "testing:test"', + ); + }); + + it('should throw an error when the output is invalid', async () => { + const registry = mockServices.actionsRegistry(); + + registry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({ name: z.number() }), + output: z => z.object({ name: z.string() }), + }, + // @ts-expect-error - we want to test the error case + action: async ({ input }) => ({ output: { name: input.name } }), + }); + + await expect( + registry.invoke({ id: 'testing:test', input: { name: 1 } }), + ).rejects.toThrow('Invalid output from action "testing:test"'); + }); + + it('should list the actions correctly', async () => { + const registry = mockServices.actionsRegistry(); + + registry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({ name: z.string() }), + output: z => z.object({ name: z.string() }), + }, + action: async ({ input }) => ({ output: { name: input.name } }), + }); + + const result = await registry.list(); + + expect(result).toMatchObject({ + actions: [ + { + id: 'testing:test', + name: 'test', + title: 'Test', + description: 'Test', + attributes: { + destructive: false, + idempotent: false, + readOnly: false, + }, + schema: { + input: { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }, + output: { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }, + }, + }, + ], + }); + }); + + describe('mockServices.actions + mockService.actionsRegistry', () => { + it('should be able to register and invoke actions', async () => { + const pluginWithAction = createBackendPlugin({ + pluginId: 'my-plugin', + register(reg) { + reg.registerInit({ + deps: { actionsRegistry: coreServices.actionsRegistry }, + async init({ actionsRegistry }) { + actionsRegistry.register({ + name: 'test', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({ name: z.string() }), + output: z => z.object({ name: z.string() }), + }, + action: async ({ input }) => { + expect(input).toEqual({ name: 'test' }); + return { output: { name: input.name } }; + }, + }); + }, + }); + }, + }); + + const pluginToCallAction = createBackendPlugin({ + pluginId: 'my-plugin-to-call-action', + register(reg) { + reg.registerInit({ + deps: { + actions: coreServices.actions, + router: coreServices.httpRouter, + }, + async init({ actions, router }) { + const testRouter = Router(); + router.use(testRouter); + + testRouter.post('/test', async (_, res) => { + const { output } = await actions.invoke({ + id: 'my-plugin:test', + input: { name: 'test' }, + credentials: mockCredentials.service(), + }); + + res.json(output); + }); + }, + }); + }, + }); + + const { server } = await startTestBackend({ + features: [pluginWithAction, pluginToCallAction], + }); + + const { body, status } = await supertest(server).post( + '/api/my-plugin-to-call-action/test', + ); + + expect(status).toBe(200); + expect(body).toEqual({ name: 'test' }); + }); + }); +}); diff --git a/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts b/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts new file mode 100644 index 0000000000..72f70e5f2b --- /dev/null +++ b/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts @@ -0,0 +1,131 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + ActionsRegistryActionOptions, + ActionsRegistryService, + ActionsService, + ActionsServiceAction, + BackstageCredentials, + LoggerService, +} from '@backstage/backend-plugin-api'; +import { ForwardedError, InputError, NotFoundError } from '@backstage/errors'; +import { JsonObject, JsonValue } from '@backstage/types'; +import { z, AnyZodObject } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; +import { mockCredentials } from './mockCredentials'; + +export class MockActionsRegistry + implements ActionsRegistryService, ActionsService +{ + private constructor(private readonly logger: LoggerService) {} + + static create(opts: { logger: LoggerService }) { + return new MockActionsRegistry(opts.logger); + } + + readonly actions: Map> = + new Map(); + + async list(): Promise<{ actions: ActionsServiceAction[] }> { + return { + actions: Array.from(this.actions.entries()).map(([id, action]) => ({ + id, + name: action.name, + title: action.title, + description: action.description, + attributes: { + // todo(blam): what's safe defaults? + destructive: action.attributes?.destructive ?? false, + idempotent: action.attributes?.idempotent ?? false, + readOnly: action.attributes?.readOnly ?? false, + }, + schema: { + input: action.schema?.input + ? zodToJsonSchema(action.schema.input(z)) + : zodToJsonSchema(z.object({})), + output: action.schema?.output + ? zodToJsonSchema(action.schema.output(z)) + : zodToJsonSchema(z.object({})), + } as ActionsServiceAction['schema'], + })), + }; + } + + async invoke(opts: { + id: string; + input?: JsonObject; + credentials?: BackstageCredentials; + }): Promise<{ output: JsonValue }> { + const action = this.actions.get(opts.id); + + if (!action) { + const availableActionIds = Array.from(this.actions.keys()).join(', '); + throw new NotFoundError( + `Action "${opts.id}" not found, available actions: ${ + availableActionIds ? `"${availableActionIds}"` : 'none' + }`, + ); + } + + const input = action.schema?.input + ? action.schema.input(z).safeParse(opts.input) + : ({ success: true, data: undefined } as const); + + if (!input.success) { + throw new InputError(`Invalid input to action "${opts.id}"`, input.error); + } + + try { + const result = await action.action({ + input: input.data, + credentials: opts.credentials ?? mockCredentials.none(), + logger: this.logger, + }); + + const output = action.schema?.output + ? action.schema.output(z).safeParse(result?.output) + : ({ success: true, data: result?.output } as const); + + if (!output.success) { + throw new InputError( + `Invalid output from action "${opts.id}"`, + output.error, + ); + } + + return { output: output.data }; + } catch (error) { + throw new ForwardedError( + `Failed execution of action "${opts.id}"`, + error, + ); + } + } + + register< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, + >(options: ActionsRegistryActionOptions): void { + // hardcode testing: prefix similar to how the default actions registry does it + const id = `testing:${options.name}`; + + if (this.actions.has(id)) { + throw new Error(`Action with id "${id}" is already registered`); + } + + this.actions.set(id, options); + } +} diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts index 95f8b084c9..a44bf912e7 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/next/services/mockServices.ts @@ -58,6 +58,7 @@ import { MockEventsService } from './MockEventsService'; import { actionsServiceFactory } from '@backstage/backend-defaults/actions'; import { actionsRegistryServiceFactory } from '@backstage/backend-defaults/actionsRegistry'; import { MockPermissionsService } from './MockPermissionsService'; +import { MockActionsRegistry } from './MockActionsRegistry'; /** @internal */ function createLoggerMock() { @@ -560,6 +561,14 @@ export namespace mockServices { })); } + export function actionsRegistry(options?: { + logger: LoggerService; + }): MockActionsRegistry { + return MockActionsRegistry.create({ + logger: options?.logger ?? mockServices.logger.mock(), + }); + } + export namespace actionsRegistry { export const factory = () => actionsRegistryServiceFactory; export const mock = simpleMock(coreServices.actionsRegistry, () => ({ diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 20d02cf880..84042aedd8 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -84,6 +84,8 @@ export const defaultServiceFactories = [ mockServices.userInfo.factory(), mockServices.urlReader.factory(), mockServices.events.factory(), + mockServices.actionsRegistry.factory(), + mockServices.actions.factory(), ]; /** diff --git a/yarn.lock b/yarn.lock index 832e71e5cd..0b5ac6270d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3801,6 +3801,8 @@ __metadata: textextensions: "npm:^5.16.0" uuid: "npm:^11.0.0" yn: "npm:^4.0.0" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.20.4" languageName: unknown linkType: soft From fcc9fa9e90532aa0366c1f152f0ac299b6c2e250 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 13 Jun 2025 14:53:30 +0200 Subject: [PATCH 132/301] chore: need to export things Signed-off-by: benjdlambert --- packages/backend-test-utils/report.api.md | 35 +++++++++++++++++++ .../src/next/services/MockActionsRegistry.ts | 33 +++++++++++++++++ .../src/next/services/index.ts | 1 + 3 files changed, 69 insertions(+) diff --git a/packages/backend-test-utils/report.api.md b/packages/backend-test-utils/report.api.md index c74d975920..a9377e70fe 100644 --- a/packages/backend-test-utils/report.api.md +++ b/packages/backend-test-utils/report.api.md @@ -3,8 +3,11 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { ActionsRegistryActionOptions } from '@backstage/backend-plugin-api'; import { ActionsRegistryService } from '@backstage/backend-plugin-api'; import { ActionsService } from '@backstage/backend-plugin-api'; +import { ActionsServiceAction } from '@backstage/backend-plugin-api'; +import { AnyZodObject } from 'zod'; import { AuditorService } from '@backstage/backend-plugin-api'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { AuthService } from '@backstage/backend-plugin-api'; @@ -26,6 +29,7 @@ import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { HttpRouterService } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; +import { JsonValue } from '@backstage/types'; import Keyv from 'keyv'; import { Knex } from 'knex'; import { LifecycleService } from '@backstage/backend-plugin-api'; @@ -56,6 +60,33 @@ export interface CreateMockDirectoryOptions { mockOsTmpDir?: boolean; } +// @public +export class MockActionsRegistry + implements ActionsRegistryService, ActionsService +{ + // (undocumented) + readonly actions: Map>; + // (undocumented) + static create(opts: { logger: LoggerService }): MockActionsRegistry; + // (undocumented) + invoke(opts: { + id: string; + input?: JsonObject; + credentials?: BackstageCredentials; + }): Promise<{ + output: JsonValue; + }>; + // (undocumented) + list(): Promise<{ + actions: ActionsServiceAction[]; + }>; + // (undocumented) + register< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, + >(options: ActionsRegistryActionOptions): void; +} + // @public (undocumented) export namespace mockCredentials { export function limitedUser( @@ -174,6 +205,10 @@ export namespace mockServices { ) => ServiceMock; } // (undocumented) + export function actionsRegistry(options?: { + logger: LoggerService; + }): MockActionsRegistry; + // (undocumented) export namespace actionsRegistry { const // (undocumented) factory: () => ServiceFactory< diff --git a/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts b/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts index 72f70e5f2b..cbb695fffb 100644 --- a/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts +++ b/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts @@ -27,6 +27,39 @@ import { z, AnyZodObject } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; import { mockCredentials } from './mockCredentials'; +/** + * A mock implementation of the ActionsRegistryService and ActionsService that can be used in tests. + * + * This is useful for testing actions that are registered with the ActionsRegistryService and ActionsService. + * + * The plugin ID is hardcoded to `testing` in the mock implementation. + * + * @example + * ```ts + * const actionsRegistry = mockServices.actionsRegistry(); + * + * actionsRegistry.register({ + * name: 'test', + * title: 'Test', + * description: 'Test', + * schema: { + * input: z.object({ name: z.string() }), + * output: z.object({ name: z.string() }), + * }, + * action: async ({ input }) => ({ output: { name: input.name } }), + * }); + * + * + * const result = await actionsRegistry.invoke({ + * id: 'testing:test', + * input: { name: 'test' }, + * }); + * + * expect(result).toEqual({ output: { name: 'test' } }); + * ``` + * + * @public + */ export class MockActionsRegistry implements ActionsRegistryService, ActionsService { diff --git a/packages/backend-test-utils/src/next/services/index.ts b/packages/backend-test-utils/src/next/services/index.ts index 7c3949e207..347c50aaee 100644 --- a/packages/backend-test-utils/src/next/services/index.ts +++ b/packages/backend-test-utils/src/next/services/index.ts @@ -15,3 +15,4 @@ */ export { mockServices, type ServiceMock } from './mockServices'; export { mockCredentials } from './mockCredentials'; +export { type MockActionsRegistry } from './MockActionsRegistry'; From aa627f4481b7306fb86b6af94f4dd4c03fd4a971 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Mon, 16 Jun 2025 15:05:28 +0200 Subject: [PATCH 133/301] chore: update destructiveness Signed-off-by: benjdlambert --- .../src/next/services/MockActionsRegistry.test.ts | 2 +- .../src/next/services/MockActionsRegistry.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/backend-test-utils/src/next/services/MockActionsRegistry.test.ts b/packages/backend-test-utils/src/next/services/MockActionsRegistry.test.ts index 4b13be921a..28e73d4dc4 100644 --- a/packages/backend-test-utils/src/next/services/MockActionsRegistry.test.ts +++ b/packages/backend-test-utils/src/next/services/MockActionsRegistry.test.ts @@ -142,7 +142,7 @@ describe('MockActionsRegistry', () => { title: 'Test', description: 'Test', attributes: { - destructive: false, + destructive: true, idempotent: false, readOnly: false, }, diff --git a/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts b/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts index cbb695fffb..e3ceb0421f 100644 --- a/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts +++ b/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts @@ -80,8 +80,7 @@ export class MockActionsRegistry title: action.title, description: action.description, attributes: { - // todo(blam): what's safe defaults? - destructive: action.attributes?.destructive ?? false, + destructive: action.attributes?.destructive ?? true, idempotent: action.attributes?.idempotent ?? false, readOnly: action.attributes?.readOnly ?? false, }, From 1feb96c8289c2de84a160e416d2f41c568a4e5cf Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Mon, 16 Jun 2025 15:15:47 +0200 Subject: [PATCH 134/301] chore: cleanup Signed-off-by: benjdlambert Signed-off-by: benjdlambert --- .../next/services/MockActionsRegistry.test.ts | 26 +++++++++---------- .../src/next/services/MockActionsRegistry.ts | 7 +++-- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/backend-test-utils/src/next/services/MockActionsRegistry.test.ts b/packages/backend-test-utils/src/next/services/MockActionsRegistry.test.ts index 28e73d4dc4..e4556cb89e 100644 --- a/packages/backend-test-utils/src/next/services/MockActionsRegistry.test.ts +++ b/packages/backend-test-utils/src/next/services/MockActionsRegistry.test.ts @@ -28,7 +28,7 @@ describe('MockActionsRegistry', () => { const registry = mockServices.actionsRegistry(); registry.register({ - name: 'test', + name: 'my-demo-action', title: 'Test', description: 'Test', schema: { @@ -45,7 +45,7 @@ describe('MockActionsRegistry', () => { }); const result = await registry.invoke({ - id: 'testing:test', + id: 'test:my-demo-action', input: { name: 'test' }, }); @@ -56,7 +56,7 @@ describe('MockActionsRegistry', () => { const registry = mockServices.actionsRegistry(); registry.register({ - name: 'test', + name: 'my-demo-action', title: 'Test', description: 'Test', schema: { @@ -67,8 +67,8 @@ describe('MockActionsRegistry', () => { }); await expect( - registry.invoke({ id: 'testing:test', input: { name: 1 } }), - ).rejects.toThrow('Invalid input to action "testing:test"'); + registry.invoke({ id: 'test:my-demo-action', input: { name: 1 } }), + ).rejects.toThrow('Invalid input to action "test:my-demo-action"'); }); it('should throw an error when the action is not found', async () => { @@ -83,7 +83,7 @@ describe('MockActionsRegistry', () => { const registry = mockServices.actionsRegistry(); registry.register({ - name: 'test', + name: 'my-demo-action', title: 'Test', description: 'Test', schema: { @@ -94,7 +94,7 @@ describe('MockActionsRegistry', () => { }); await expect(registry.invoke({ id: 'test' })).rejects.toThrow( - 'Action "test" not found, available actions: "testing:test"', + 'Action "test" not found, available actions: "test:my-demo-action"', ); }); @@ -102,7 +102,7 @@ describe('MockActionsRegistry', () => { const registry = mockServices.actionsRegistry(); registry.register({ - name: 'test', + name: 'my-demo-action', title: 'Test', description: 'Test', schema: { @@ -114,15 +114,15 @@ describe('MockActionsRegistry', () => { }); await expect( - registry.invoke({ id: 'testing:test', input: { name: 1 } }), - ).rejects.toThrow('Invalid output from action "testing:test"'); + registry.invoke({ id: 'test:my-demo-action', input: { name: 1 } }), + ).rejects.toThrow('Invalid output from action "test:my-demo-action"'); }); it('should list the actions correctly', async () => { const registry = mockServices.actionsRegistry(); registry.register({ - name: 'test', + name: 'my-demo-action', title: 'Test', description: 'Test', schema: { @@ -137,8 +137,8 @@ describe('MockActionsRegistry', () => { expect(result).toMatchObject({ actions: [ { - id: 'testing:test', - name: 'test', + id: 'test:my-demo-action', + name: 'my-demo-action', title: 'Test', description: 'Test', attributes: { diff --git a/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts b/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts index e3ceb0421f..62d940515b 100644 --- a/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts +++ b/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts @@ -151,8 +151,11 @@ export class MockActionsRegistry TInputSchema extends AnyZodObject, TOutputSchema extends AnyZodObject, >(options: ActionsRegistryActionOptions): void { - // hardcode testing: prefix similar to how the default actions registry does it - const id = `testing:${options.name}`; + // hardcode test: prefix similar to how the default actions registry does it + // and other places around the testing ecosystem: + // https://github.com/backstage/backstage/blob/a9219496d5c073aaa0b8caf32ece10455cf65e61/packages/backend-test-utils/src/next/services/mockServices.ts#L321 + // https://github.com/backstage/backstage/blob/861f162b4a39117b824669d67a951ed1db142e3d/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts#L99 + const id = `test:${options.name}`; if (this.actions.has(id)) { throw new Error(`Action with id "${id}" is already registered`); From 36417fbe266884c6b9d1e0a9bcdad34ed0394905 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Mon, 16 Jun 2025 16:34:05 +0200 Subject: [PATCH 135/301] feat: moving the actions and actions registry to alpha services Signed-off-by: benjdlambert Signed-off-by: benjdlambert --- packages/backend-defaults/package.json | 12 ++++------ .../backend-defaults/report-actions.api.md | 17 -------------- ...onsRegistry.api.md => report-alpha.api.md} | 10 ++++++++- .../backend-defaults/src/CreateBackend.ts | 12 ++++++---- .../actions/DefaultActionsService.ts | 6 +++-- .../actions/actionsServiceFactory.test.ts | 10 +++++---- .../actions/actionsServiceFactory.ts | 3 ++- .../{ => alpha}/entrypoints/actions/index.ts | 0 .../DefaultActionsRegistryService.ts | 6 +++-- .../actionsRegistryServiceFactory.test.ts | 22 +++++++++---------- .../actionsRegistryServiceFactory.ts | 3 ++- .../entrypoints/actionsRegistry/index.ts | 0 packages/backend-defaults/src/alpha/index.ts | 17 ++++++++++++++ 13 files changed, 66 insertions(+), 52 deletions(-) delete mode 100644 packages/backend-defaults/report-actions.api.md rename packages/backend-defaults/{report-actionsRegistry.api.md => report-alpha.api.md} (69%) rename packages/backend-defaults/src/{ => alpha}/entrypoints/actions/DefaultActionsService.ts (98%) rename packages/backend-defaults/src/{ => alpha}/entrypoints/actions/actionsServiceFactory.test.ts (96%) rename packages/backend-defaults/src/{ => alpha}/entrypoints/actions/actionsServiceFactory.ts (92%) rename packages/backend-defaults/src/{ => alpha}/entrypoints/actions/index.ts (100%) rename packages/backend-defaults/src/{ => alpha}/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts (98%) rename packages/backend-defaults/src/{ => alpha}/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts (95%) rename packages/backend-defaults/src/{ => alpha}/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts (92%) rename packages/backend-defaults/src/{ => alpha}/entrypoints/actionsRegistry/index.ts (100%) create mode 100644 packages/backend-defaults/src/alpha/index.ts diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 9ae78c119a..49a7699fdd 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -20,8 +20,6 @@ "license": "Apache-2.0", "exports": { ".": "./src/index.ts", - "./actions": "./src/entrypoints/actions/index.ts", - "./actionsRegistry": "./src/entrypoints/actionsRegistry/index.ts", "./auditor": "./src/entrypoints/auditor/index.ts", "./auth": "./src/entrypoints/auth/index.ts", "./cache": "./src/entrypoints/cache/index.ts", @@ -41,18 +39,13 @@ "./scheduler": "./src/entrypoints/scheduler/index.ts", "./urlReader": "./src/entrypoints/urlReader/index.ts", "./userInfo": "./src/entrypoints/userInfo/index.ts", + "./alpha": "./src/alpha/index.ts", "./package.json": "./package.json" }, "main": "src/index.ts", "types": "src/index.ts", "typesVersions": { "*": { - "actions": [ - "src/entrypoints/actions/index.ts" - ], - "actionsRegistry": [ - "src/entrypoints/actionsRegistry/index.ts" - ], "auditor": [ "src/entrypoints/auditor/index.ts" ], @@ -110,6 +103,9 @@ "userInfo": [ "src/entrypoints/userInfo/index.ts" ], + "alpha": [ + "src/alpha/index.ts" + ], "package.json": [ "package.json" ] diff --git a/packages/backend-defaults/report-actions.api.md b/packages/backend-defaults/report-actions.api.md deleted file mode 100644 index 4c072af02a..0000000000 --- a/packages/backend-defaults/report-actions.api.md +++ /dev/null @@ -1,17 +0,0 @@ -## API Report File for "@backstage/backend-defaults" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts -import { ActionsService } from '@backstage/backend-plugin-api'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; - -// @public (undocumented) -export const actionsServiceFactory: ServiceFactory< - ActionsService, - 'plugin', - 'singleton' ->; - -// (No @packageDocumentation comment for this package) -``` diff --git a/packages/backend-defaults/report-actionsRegistry.api.md b/packages/backend-defaults/report-alpha.api.md similarity index 69% rename from packages/backend-defaults/report-actionsRegistry.api.md rename to packages/backend-defaults/report-alpha.api.md index d35bf4cef4..58277b5e1e 100644 --- a/packages/backend-defaults/report-actionsRegistry.api.md +++ b/packages/backend-defaults/report-alpha.api.md @@ -3,7 +3,8 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ActionsRegistryService } from '@backstage/backend-plugin-api'; +import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import { ActionsService } from '@backstage/backend-plugin-api/alpha'; import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public (undocumented) @@ -13,5 +14,12 @@ export const actionsRegistryServiceFactory: ServiceFactory< 'singleton' >; +// @public (undocumented) +export const actionsServiceFactory: ServiceFactory< + ActionsService, + 'plugin', + 'singleton' +>; + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index 032978c806..4b201fded3 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -35,12 +35,12 @@ import { schedulerServiceFactory } from '@backstage/backend-defaults/scheduler'; import { urlReaderServiceFactory } from '@backstage/backend-defaults/urlReader'; import { userInfoServiceFactory } from '@backstage/backend-defaults/userInfo'; import { eventsServiceFactory } from '@backstage/plugin-events-node'; -import { actionsRegistryServiceFactory } from './entrypoints/actionsRegistry'; -import { actionsServiceFactory } from './entrypoints/actions'; - -export const defaultServiceFactories = [ +import { actionsRegistryServiceFactory, actionsServiceFactory, +} from '@backstage/backend-defaults/alpha'; + +export const defaultServiceFactories = [ auditorServiceFactory, authServiceFactory, cacheServiceFactory, @@ -61,6 +61,10 @@ export const defaultServiceFactories = [ userInfoServiceFactory, urlReaderServiceFactory, eventsServiceFactory, + + // alpha services + actionsRegistryServiceFactory, + actionsServiceFactory, ]; /** diff --git a/packages/backend-defaults/src/entrypoints/actions/DefaultActionsService.ts b/packages/backend-defaults/src/alpha/entrypoints/actions/DefaultActionsService.ts similarity index 98% rename from packages/backend-defaults/src/entrypoints/actions/DefaultActionsService.ts rename to packages/backend-defaults/src/alpha/entrypoints/actions/DefaultActionsService.ts index 2421482f4c..d8847c2187 100644 --- a/packages/backend-defaults/src/entrypoints/actions/DefaultActionsService.ts +++ b/packages/backend-defaults/src/alpha/entrypoints/actions/DefaultActionsService.ts @@ -14,8 +14,6 @@ * limitations under the License. */ import { - ActionsService, - ActionsServiceAction, AuthService, BackstageCredentials, DiscoveryService, @@ -24,6 +22,10 @@ import { } from '@backstage/backend-plugin-api'; import { ResponseError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; +import { + ActionsService, + ActionsServiceAction, +} from '@backstage/backend-plugin-api/alpha'; export class DefaultActionsService implements ActionsService { private constructor( diff --git a/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts b/packages/backend-defaults/src/alpha/entrypoints/actions/actionsServiceFactory.test.ts similarity index 96% rename from packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts rename to packages/backend-defaults/src/alpha/entrypoints/actions/actionsServiceFactory.test.ts index c43945f1d5..af2fcb0c12 100644 --- a/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.test.ts +++ b/packages/backend-defaults/src/alpha/entrypoints/actions/actionsServiceFactory.test.ts @@ -21,18 +21,20 @@ import { startTestBackend, } from '@backstage/backend-test-utils'; import { actionsRegistryServiceFactory } from '../actionsRegistry'; -import { httpRouterServiceFactory } from '../httpRouter'; +import { httpRouterServiceFactory } from '../../../entrypoints/httpRouter'; import { actionsServiceFactory } from './actionsServiceFactory'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; import { - ActionsServiceAction, coreServices, createBackendPlugin, } from '@backstage/backend-plugin-api'; import { json } from 'express'; import Router from 'express-promise-router'; import request from 'supertest'; +import { ActionsServiceAction } from '@backstage/backend-plugin-api/alpha'; +import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha'; +import { actionsServiceRef } from '@backstage/backend-plugin-api/alpha'; const server = setupServer(); @@ -201,7 +203,7 @@ describe('actionsServiceFactory', () => { pluginId: 'plugin-with-action', register({ registerInit }) { registerInit({ - deps: { actionsRegistry: coreServices.actionsRegistry }, + deps: { actionsRegistry: actionsRegistryServiceRef }, async init({ actionsRegistry }) { actionsRegistry.register({ name: 'with-validation', @@ -236,7 +238,7 @@ describe('actionsServiceFactory', () => { register({ registerInit }) { registerInit({ deps: { - actionsService: coreServices.actions, + actionsService: actionsServiceRef, router: coreServices.httpRouter, httpAuth: coreServices.httpAuth, }, diff --git a/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.ts b/packages/backend-defaults/src/alpha/entrypoints/actions/actionsServiceFactory.ts similarity index 92% rename from packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.ts rename to packages/backend-defaults/src/alpha/entrypoints/actions/actionsServiceFactory.ts index 466cc02121..4325da7d13 100644 --- a/packages/backend-defaults/src/entrypoints/actions/actionsServiceFactory.ts +++ b/packages/backend-defaults/src/alpha/entrypoints/actions/actionsServiceFactory.ts @@ -16,12 +16,13 @@ import { createServiceFactory } from '@backstage/backend-plugin-api'; import { coreServices } from '@backstage/backend-plugin-api'; import { DefaultActionsService } from './DefaultActionsService'; +import { actionsServiceRef } from '@backstage/backend-plugin-api/alpha'; /** * @public */ export const actionsServiceFactory = createServiceFactory({ - service: coreServices.actions, + service: actionsServiceRef, deps: { discovery: coreServices.discovery, config: coreServices.rootConfig, diff --git a/packages/backend-defaults/src/entrypoints/actions/index.ts b/packages/backend-defaults/src/alpha/entrypoints/actions/index.ts similarity index 100% rename from packages/backend-defaults/src/entrypoints/actions/index.ts rename to packages/backend-defaults/src/alpha/entrypoints/actions/index.ts diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts similarity index 98% rename from packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts rename to packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts index bee3f7d565..2284935787 100644 --- a/packages/backend-defaults/src/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts +++ b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.ts @@ -15,8 +15,6 @@ */ import { - ActionsRegistryActionOptions, - ActionsRegistryService, AuthService, HttpAuthService, LoggerService, @@ -26,6 +24,10 @@ import PromiseRouter from 'express-promise-router'; import { Router, json } from 'express'; import { z, AnyZodObject } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; +import { + ActionsRegistryActionOptions, + ActionsRegistryService, +} from '@backstage/backend-plugin-api/alpha'; import { ForwardedError, InputError, diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts similarity index 95% rename from packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts rename to packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts index 2de6272365..79c103c284 100644 --- a/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts +++ b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.test.ts @@ -13,19 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - coreServices, - createBackendPlugin, -} from '@backstage/backend-plugin-api'; +import { createBackendPlugin } from '@backstage/backend-plugin-api'; import { mockCredentials, mockServices, startTestBackend, } from '@backstage/backend-test-utils'; -import { httpRouterServiceFactory } from '../httpRouter'; +import { httpRouterServiceFactory } from '../../../entrypoints/httpRouter'; import request from 'supertest'; import { actionsRegistryServiceFactory } from './actionsRegistryServiceFactory'; import { InputError } from '@backstage/errors'; +import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha'; describe('actionsRegistryServiceFactory', () => { const defaultServices = [ @@ -43,7 +41,7 @@ describe('actionsRegistryServiceFactory', () => { register(reg) { reg.registerInit({ deps: { - actionsRegistry: coreServices.actionsRegistry, + actionsRegistry: actionsRegistryServiceRef, }, async init({ actionsRegistry }) { actionsRegistry.register({ @@ -80,7 +78,7 @@ describe('actionsRegistryServiceFactory', () => { register(reg) { reg.registerInit({ deps: { - actionsRegistry: coreServices.actionsRegistry, + actionsRegistry: actionsRegistryServiceRef, }, async init({ actionsRegistry }) { actionsRegistry.register({ @@ -118,7 +116,7 @@ describe('actionsRegistryServiceFactory', () => { register(reg) { reg.registerInit({ deps: { - actionsRegistry: coreServices.actionsRegistry, + actionsRegistry: actionsRegistryServiceRef, }, async init({ actionsRegistry }) { actionsRegistry.register({ @@ -187,7 +185,7 @@ describe('actionsRegistryServiceFactory', () => { register(reg) { reg.registerInit({ deps: { - actionsRegistry: coreServices.actionsRegistry, + actionsRegistry: actionsRegistryServiceRef, }, async init({ actionsRegistry }) { actionsRegistry.register({ @@ -235,7 +233,7 @@ describe('actionsRegistryServiceFactory', () => { register(reg) { reg.registerInit({ deps: { - actionsRegistry: coreServices.actionsRegistry, + actionsRegistry: actionsRegistryServiceRef, }, async init({ actionsRegistry }) { actionsRegistry.register({ @@ -290,7 +288,7 @@ describe('actionsRegistryServiceFactory', () => { register(reg) { reg.registerInit({ deps: { - actionsRegistry: coreServices.actionsRegistry, + actionsRegistry: actionsRegistryServiceRef, }, async init({ actionsRegistry }) { actionsRegistry.register({ @@ -349,7 +347,7 @@ describe('actionsRegistryServiceFactory', () => { register(reg) { reg.registerInit({ deps: { - actionsRegistry: coreServices.actionsRegistry, + actionsRegistry: actionsRegistryServiceRef, }, async init({ actionsRegistry }) { actionsRegistry.register({ diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts similarity index 92% rename from packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts rename to packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts index 85515a6046..8c19b8148c 100644 --- a/packages/backend-defaults/src/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts +++ b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/actionsRegistryServiceFactory.ts @@ -19,12 +19,13 @@ import { createServiceFactory, } from '@backstage/backend-plugin-api'; import { DefaultActionsRegistryService } from './DefaultActionsRegistryService'; +import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha'; /** * @public */ export const actionsRegistryServiceFactory = createServiceFactory({ - service: coreServices.actionsRegistry, + service: actionsRegistryServiceRef, deps: { metadata: coreServices.pluginMetadata, httpRouter: coreServices.httpRouter, diff --git a/packages/backend-defaults/src/entrypoints/actionsRegistry/index.ts b/packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/index.ts similarity index 100% rename from packages/backend-defaults/src/entrypoints/actionsRegistry/index.ts rename to packages/backend-defaults/src/alpha/entrypoints/actionsRegistry/index.ts diff --git a/packages/backend-defaults/src/alpha/index.ts b/packages/backend-defaults/src/alpha/index.ts new file mode 100644 index 0000000000..13bb439acd --- /dev/null +++ b/packages/backend-defaults/src/alpha/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { actionsRegistryServiceFactory } from './entrypoints/actionsRegistry'; +export { actionsServiceFactory } from './entrypoints/actions'; From 8a75a84fc9c3c8d86e6ce3384aac1a8744f89651 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Mon, 16 Jun 2025 16:34:38 +0200 Subject: [PATCH 136/301] chore: moving some files around and exporting things as alpha services Signed-off-by: benjdlambert --- packages/backend-test-utils/package.json | 3 +- .../backend-test-utils/report-alpha.api.md | 85 +++++++++++++++++++ packages/backend-test-utils/report.api.md | 59 ------------- .../src/{next => alpha}/index.ts | 1 - .../services/ActionsRegistryServiceMock.ts | 49 +++++++++++ .../src/alpha/services/ActionsServiceMock.ts | 34 ++++++++ .../services/MockActionsRegistry.test.ts | 28 +++--- .../services/MockActionsRegistry.ts | 14 +-- .../src/alpha/services/index.ts | 20 +++++ .../src/alpha/services/simpleMock.ts | 57 +++++++++++++ packages/backend-test-utils/src/index.ts | 3 +- .../services/MockAuthService.test.ts | 0 .../{next => }/services/MockAuthService.ts | 0 .../services/MockEventsService.test.ts | 0 .../{next => }/services/MockEventsService.ts | 0 .../services/MockHttpAuthService.test.ts | 0 .../services/MockHttpAuthService.ts | 0 .../services/MockPermissionsService.test.ts | 0 .../services/MockPermissionsService.ts | 0 .../services/MockRootLoggerService.test.ts | 0 .../services/MockRootLoggerService.ts | 0 .../services/MockUserInfoService.test.ts | 0 .../services/MockUserInfoService.ts | 0 .../src/{next => }/services/index.ts | 4 +- .../services/mockCredentials.test.ts | 0 .../{next => }/services/mockCredentials.ts | 0 .../{next => }/services/mockServices.test.ts | 0 .../src/{next => }/services/mockServices.ts | 63 +------------- .../src/services/simpleMock.ts | 57 +++++++++++++ .../src/util/errorHandler.ts | 2 +- .../wiring/ServiceFactoryTester.test.ts | 0 .../{next => }/wiring/ServiceFactoryTester.ts | 2 +- .../src/{next => }/wiring/TestBackend.test.ts | 0 .../src/{next => }/wiring/TestBackend.ts | 12 ++- .../src/{next => }/wiring/index.ts | 0 35 files changed, 344 insertions(+), 149 deletions(-) create mode 100644 packages/backend-test-utils/report-alpha.api.md rename packages/backend-test-utils/src/{next => alpha}/index.ts (96%) create mode 100644 packages/backend-test-utils/src/alpha/services/ActionsRegistryServiceMock.ts create mode 100644 packages/backend-test-utils/src/alpha/services/ActionsServiceMock.ts rename packages/backend-test-utils/src/{next => alpha}/services/MockActionsRegistry.test.ts (89%) rename packages/backend-test-utils/src/{next => alpha}/services/MockActionsRegistry.ts (97%) create mode 100644 packages/backend-test-utils/src/alpha/services/index.ts create mode 100644 packages/backend-test-utils/src/alpha/services/simpleMock.ts rename packages/backend-test-utils/src/{next => }/services/MockAuthService.test.ts (100%) rename packages/backend-test-utils/src/{next => }/services/MockAuthService.ts (100%) rename packages/backend-test-utils/src/{next => }/services/MockEventsService.test.ts (100%) rename packages/backend-test-utils/src/{next => }/services/MockEventsService.ts (100%) rename packages/backend-test-utils/src/{next => }/services/MockHttpAuthService.test.ts (100%) rename packages/backend-test-utils/src/{next => }/services/MockHttpAuthService.ts (100%) rename packages/backend-test-utils/src/{next => }/services/MockPermissionsService.test.ts (100%) rename packages/backend-test-utils/src/{next => }/services/MockPermissionsService.ts (100%) rename packages/backend-test-utils/src/{next => }/services/MockRootLoggerService.test.ts (100%) rename packages/backend-test-utils/src/{next => }/services/MockRootLoggerService.ts (100%) rename packages/backend-test-utils/src/{next => }/services/MockUserInfoService.test.ts (100%) rename packages/backend-test-utils/src/{next => }/services/MockUserInfoService.ts (100%) rename packages/backend-test-utils/src/{next => }/services/index.ts (83%) rename packages/backend-test-utils/src/{next => }/services/mockCredentials.test.ts (100%) rename packages/backend-test-utils/src/{next => }/services/mockCredentials.ts (100%) rename packages/backend-test-utils/src/{next => }/services/mockServices.test.ts (100%) rename packages/backend-test-utils/src/{next => }/services/mockServices.ts (90%) create mode 100644 packages/backend-test-utils/src/services/simpleMock.ts rename packages/backend-test-utils/src/{next => }/wiring/ServiceFactoryTester.test.ts (100%) rename packages/backend-test-utils/src/{next => }/wiring/ServiceFactoryTester.ts (97%) rename packages/backend-test-utils/src/{next => }/wiring/TestBackend.test.ts (100%) rename packages/backend-test-utils/src/{next => }/wiring/TestBackend.ts (97%) rename packages/backend-test-utils/src/{next => }/wiring/index.ts (100%) diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index cc47249a2d..934d2439a9 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -21,7 +21,8 @@ "license": "Apache-2.0", "exports": { ".": "./src/index.ts", - "./package.json": "./package.json" + "./package.json": "./package.json", + "./alpha": "./src/alpha/index.ts" }, "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/backend-test-utils/report-alpha.api.md b/packages/backend-test-utils/report-alpha.api.md new file mode 100644 index 0000000000..ccbf96c0c7 --- /dev/null +++ b/packages/backend-test-utils/report-alpha.api.md @@ -0,0 +1,85 @@ +## API Report File for "@backstage/backend-test-utils" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { ActionsRegistryActionOptions } from '@backstage/backend-plugin-api/alpha'; +import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha'; +import { ActionsService } from '@backstage/backend-plugin-api/alpha'; +import { ActionsServiceAction } from '@backstage/backend-plugin-api/alpha'; +import { AnyZodObject } from 'zod'; +import { BackstageCredentials } from '@backstage/backend-plugin-api'; +import { JsonObject } from '@backstage/types'; +import { JsonValue } from '@backstage/types'; +import { LoggerService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; + +// @alpha (undocumented) +export function actionsRegistryServiceMock(options?: { + logger: LoggerService; +}): MockActionsRegistry; + +// @alpha (undocumented) +export namespace actionsRegistryServiceMock { + const // (undocumented) + factory: () => ServiceFactory< + ActionsRegistryService, + 'plugin', + 'singleton' + >; + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; +} + +// @alpha (undocumented) +export namespace actionsServiceMock { + const // (undocumented) + factory: () => ServiceFactory; + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; +} + +// @alpha +export class MockActionsRegistry + implements ActionsRegistryService, ActionsService +{ + // (undocumented) + readonly actions: Map>; + // (undocumented) + static create(opts: { logger: LoggerService }): MockActionsRegistry; + // (undocumented) + invoke(opts: { + id: string; + input?: JsonObject; + credentials?: BackstageCredentials; + }): Promise<{ + output: JsonValue; + }>; + // (undocumented) + list(): Promise<{ + actions: ActionsServiceAction[]; + }>; + // (undocumented) + register< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, + >(options: ActionsRegistryActionOptions): void; +} + +// @alpha (undocumented) +export type ServiceMock = { + factory: ServiceFactory; +} & { + [Key in keyof TService]: TService[Key] extends ( + ...args: infer Args + ) => infer Return + ? TService[Key] & jest.MockInstance + : TService[Key]; +}; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-test-utils/report.api.md b/packages/backend-test-utils/report.api.md index a9377e70fe..4853ca57c5 100644 --- a/packages/backend-test-utils/report.api.md +++ b/packages/backend-test-utils/report.api.md @@ -3,11 +3,6 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ActionsRegistryActionOptions } from '@backstage/backend-plugin-api'; -import { ActionsRegistryService } from '@backstage/backend-plugin-api'; -import { ActionsService } from '@backstage/backend-plugin-api'; -import { ActionsServiceAction } from '@backstage/backend-plugin-api'; -import { AnyZodObject } from 'zod'; import { AuditorService } from '@backstage/backend-plugin-api'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { AuthService } from '@backstage/backend-plugin-api'; @@ -29,7 +24,6 @@ import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { HttpRouterService } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; -import { JsonValue } from '@backstage/types'; import Keyv from 'keyv'; import { Knex } from 'knex'; import { LifecycleService } from '@backstage/backend-plugin-api'; @@ -60,33 +54,6 @@ export interface CreateMockDirectoryOptions { mockOsTmpDir?: boolean; } -// @public -export class MockActionsRegistry - implements ActionsRegistryService, ActionsService -{ - // (undocumented) - readonly actions: Map>; - // (undocumented) - static create(opts: { logger: LoggerService }): MockActionsRegistry; - // (undocumented) - invoke(opts: { - id: string; - input?: JsonObject; - credentials?: BackstageCredentials; - }): Promise<{ - output: JsonValue; - }>; - // (undocumented) - list(): Promise<{ - actions: ActionsServiceAction[]; - }>; - // (undocumented) - register< - TInputSchema extends AnyZodObject, - TOutputSchema extends AnyZodObject, - >(options: ActionsRegistryActionOptions): void; -} - // @public (undocumented) export namespace mockCredentials { export function limitedUser( @@ -195,32 +162,6 @@ export function mockErrorHandler(): ErrorRequestHandler< // @public export namespace mockServices { - // (undocumented) - export namespace actions { - const // (undocumented) - factory: () => ServiceFactory; - const // (undocumented) - mock: ( - partialImpl?: Partial | undefined, - ) => ServiceMock; - } - // (undocumented) - export function actionsRegistry(options?: { - logger: LoggerService; - }): MockActionsRegistry; - // (undocumented) - export namespace actionsRegistry { - const // (undocumented) - factory: () => ServiceFactory< - ActionsRegistryService, - 'plugin', - 'singleton' - >; - const // (undocumented) - mock: ( - partialImpl?: Partial | undefined, - ) => ServiceMock; - } // (undocumented) export namespace auditor { const // (undocumented) diff --git a/packages/backend-test-utils/src/next/index.ts b/packages/backend-test-utils/src/alpha/index.ts similarity index 96% rename from packages/backend-test-utils/src/next/index.ts rename to packages/backend-test-utils/src/alpha/index.ts index 572fb3a9e7..d891744260 100644 --- a/packages/backend-test-utils/src/next/index.ts +++ b/packages/backend-test-utils/src/alpha/index.ts @@ -14,5 +14,4 @@ * limitations under the License. */ -export * from './wiring'; export * from './services'; diff --git a/packages/backend-test-utils/src/alpha/services/ActionsRegistryServiceMock.ts b/packages/backend-test-utils/src/alpha/services/ActionsRegistryServiceMock.ts new file mode 100644 index 0000000000..faf89b393a --- /dev/null +++ b/packages/backend-test-utils/src/alpha/services/ActionsRegistryServiceMock.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { mockServices } from '../../services'; +import { LoggerService } from '@backstage/backend-plugin-api'; +import { MockActionsRegistry } from './MockActionsRegistry'; +import { simpleMock } from './simpleMock'; +import { + ActionsRegistryService, + actionsRegistryServiceRef, +} from '@backstage/backend-plugin-api/alpha'; +import { actionsRegistryServiceFactory } from '@backstage/backend-defaults/alpha'; + +/** + * @alpha + */ +export function actionsRegistryServiceMock(options?: { + logger: LoggerService; +}): MockActionsRegistry { + return MockActionsRegistry.create({ + logger: options?.logger ?? mockServices.logger.mock(), + }); +} + +/** + * @alpha + */ +export namespace actionsRegistryServiceMock { + export const factory = () => actionsRegistryServiceFactory; + + export const mock = simpleMock( + actionsRegistryServiceRef, + () => ({ + register: jest.fn(), + }), + ); +} diff --git a/packages/backend-test-utils/src/alpha/services/ActionsServiceMock.ts b/packages/backend-test-utils/src/alpha/services/ActionsServiceMock.ts new file mode 100644 index 0000000000..63abff4c37 --- /dev/null +++ b/packages/backend-test-utils/src/alpha/services/ActionsServiceMock.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { simpleMock } from './simpleMock'; +import { + ActionsService, + actionsServiceRef, +} from '@backstage/backend-plugin-api/alpha'; +import { actionsServiceFactory } from '@backstage/backend-defaults/alpha'; + +/** + * @alpha + */ +export namespace actionsServiceMock { + export const factory = () => actionsServiceFactory; + + export const mock = simpleMock(actionsServiceRef, () => ({ + invoke: jest.fn(), + list: jest.fn(), + })); +} diff --git a/packages/backend-test-utils/src/next/services/MockActionsRegistry.test.ts b/packages/backend-test-utils/src/alpha/services/MockActionsRegistry.test.ts similarity index 89% rename from packages/backend-test-utils/src/next/services/MockActionsRegistry.test.ts rename to packages/backend-test-utils/src/alpha/services/MockActionsRegistry.test.ts index e4556cb89e..f604689c63 100644 --- a/packages/backend-test-utils/src/next/services/MockActionsRegistry.test.ts +++ b/packages/backend-test-utils/src/alpha/services/MockActionsRegistry.test.ts @@ -17,15 +17,19 @@ import { coreServices, createBackendPlugin, } from '@backstage/backend-plugin-api'; -import { startTestBackend } from '../wiring'; -import { mockServices } from './mockServices'; -import { mockCredentials } from './mockCredentials'; +import { startTestBackend } from '../../wiring'; +import { actionsRegistryServiceMock } from './ActionsRegistryServiceMock'; +import { mockCredentials } from '../../services'; import { Router } from 'express'; import supertest from 'supertest'; +import { + actionsRegistryServiceRef, + actionsServiceRef, +} from '@backstage/backend-plugin-api/alpha'; describe('MockActionsRegistry', () => { it('should be able to register and invoke actions', async () => { - const registry = mockServices.actionsRegistry(); + const registry = actionsRegistryServiceMock(); registry.register({ name: 'my-demo-action', @@ -53,7 +57,7 @@ describe('MockActionsRegistry', () => { }); it('should throw an error when the input is invalid to the action', async () => { - const registry = mockServices.actionsRegistry(); + const registry = actionsRegistryServiceMock(); registry.register({ name: 'my-demo-action', @@ -72,7 +76,7 @@ describe('MockActionsRegistry', () => { }); it('should throw an error when the action is not found', async () => { - const registry = mockServices.actionsRegistry(); + const registry = actionsRegistryServiceMock(); await expect(registry.invoke({ id: 'test' })).rejects.toThrow( 'Action "test" not found, available actions: none', @@ -80,7 +84,7 @@ describe('MockActionsRegistry', () => { }); it('should throw an error when the action is not found with recommended actions', async () => { - const registry = mockServices.actionsRegistry(); + const registry = actionsRegistryServiceMock(); registry.register({ name: 'my-demo-action', @@ -99,7 +103,7 @@ describe('MockActionsRegistry', () => { }); it('should throw an error when the output is invalid', async () => { - const registry = mockServices.actionsRegistry(); + const registry = actionsRegistryServiceMock(); registry.register({ name: 'my-demo-action', @@ -119,7 +123,7 @@ describe('MockActionsRegistry', () => { }); it('should list the actions correctly', async () => { - const registry = mockServices.actionsRegistry(); + const registry = actionsRegistryServiceMock(); registry.register({ name: 'my-demo-action', @@ -165,13 +169,13 @@ describe('MockActionsRegistry', () => { }); }); - describe('mockServices.actions + mockService.actionsRegistry', () => { + describe('actionsRegistryServiceMock + mockService.actionsRegistry', () => { it('should be able to register and invoke actions', async () => { const pluginWithAction = createBackendPlugin({ pluginId: 'my-plugin', register(reg) { reg.registerInit({ - deps: { actionsRegistry: coreServices.actionsRegistry }, + deps: { actionsRegistry: actionsRegistryServiceRef }, async init({ actionsRegistry }) { actionsRegistry.register({ name: 'test', @@ -196,7 +200,7 @@ describe('MockActionsRegistry', () => { register(reg) { reg.registerInit({ deps: { - actions: coreServices.actions, + actions: actionsServiceRef, router: coreServices.httpRouter, }, async init({ actions, router }) { diff --git a/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts b/packages/backend-test-utils/src/alpha/services/MockActionsRegistry.ts similarity index 97% rename from packages/backend-test-utils/src/next/services/MockActionsRegistry.ts rename to packages/backend-test-utils/src/alpha/services/MockActionsRegistry.ts index 62d940515b..49c80b7a3d 100644 --- a/packages/backend-test-utils/src/next/services/MockActionsRegistry.ts +++ b/packages/backend-test-utils/src/alpha/services/MockActionsRegistry.ts @@ -14,10 +14,6 @@ * limitations under the License. */ import { - ActionsRegistryActionOptions, - ActionsRegistryService, - ActionsService, - ActionsServiceAction, BackstageCredentials, LoggerService, } from '@backstage/backend-plugin-api'; @@ -25,7 +21,13 @@ import { ForwardedError, InputError, NotFoundError } from '@backstage/errors'; import { JsonObject, JsonValue } from '@backstage/types'; import { z, AnyZodObject } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; -import { mockCredentials } from './mockCredentials'; +import { mockCredentials } from '../../services'; +import { + ActionsRegistryActionOptions, + ActionsRegistryService, + ActionsService, + ActionsServiceAction, +} from '@backstage/backend-plugin-api/alpha'; /** * A mock implementation of the ActionsRegistryService and ActionsService that can be used in tests. @@ -58,7 +60,7 @@ import { mockCredentials } from './mockCredentials'; * expect(result).toEqual({ output: { name: 'test' } }); * ``` * - * @public + * @alpha */ export class MockActionsRegistry implements ActionsRegistryService, ActionsService diff --git a/packages/backend-test-utils/src/alpha/services/index.ts b/packages/backend-test-utils/src/alpha/services/index.ts new file mode 100644 index 0000000000..ca9b3f23a0 --- /dev/null +++ b/packages/backend-test-utils/src/alpha/services/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { actionsRegistryServiceMock } from './ActionsRegistryServiceMock'; +export { MockActionsRegistry } from './MockActionsRegistry'; +export { actionsServiceMock } from './ActionsServiceMock'; +export { type ServiceMock } from './simpleMock'; diff --git a/packages/backend-test-utils/src/alpha/services/simpleMock.ts b/packages/backend-test-utils/src/alpha/services/simpleMock.ts new file mode 100644 index 0000000000..d07b143f7a --- /dev/null +++ b/packages/backend-test-utils/src/alpha/services/simpleMock.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + createServiceFactory, + ServiceFactory, + ServiceRef, +} from '@backstage/backend-plugin-api'; + +/** @alpha */ +export type ServiceMock = { + factory: ServiceFactory; +} & { + [Key in keyof TService]: TService[Key] extends ( + ...args: infer Args + ) => infer Return + ? TService[Key] & jest.MockInstance + : TService[Key]; +}; + +/** @internal */ +export function simpleMock( + ref: ServiceRef, + mockFactory: () => jest.Mocked, +): (partialImpl?: Partial) => ServiceMock { + return partialImpl => { + const mock = mockFactory(); + if (partialImpl) { + for (const [key, impl] of Object.entries(partialImpl)) { + if (typeof impl === 'function') { + (mock as any)[key].mockImplementation(impl); + } else { + (mock as any)[key] = impl; + } + } + } + return Object.assign(mock, { + factory: createServiceFactory({ + service: ref, + deps: {}, + factory: () => mock, + }), + }) as ServiceMock; + }; +} diff --git a/packages/backend-test-utils/src/index.ts b/packages/backend-test-utils/src/index.ts index d8e0e2c4a6..7cc4546415 100644 --- a/packages/backend-test-utils/src/index.ts +++ b/packages/backend-test-utils/src/index.ts @@ -24,5 +24,6 @@ export * from './cache'; export * from './database'; export * from './msw'; export * from './filesystem'; -export * from './next'; +export * from './services'; +export * from './wiring'; export { mockErrorHandler } from './util'; diff --git a/packages/backend-test-utils/src/next/services/MockAuthService.test.ts b/packages/backend-test-utils/src/services/MockAuthService.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockAuthService.test.ts rename to packages/backend-test-utils/src/services/MockAuthService.test.ts diff --git a/packages/backend-test-utils/src/next/services/MockAuthService.ts b/packages/backend-test-utils/src/services/MockAuthService.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockAuthService.ts rename to packages/backend-test-utils/src/services/MockAuthService.ts diff --git a/packages/backend-test-utils/src/next/services/MockEventsService.test.ts b/packages/backend-test-utils/src/services/MockEventsService.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockEventsService.test.ts rename to packages/backend-test-utils/src/services/MockEventsService.test.ts diff --git a/packages/backend-test-utils/src/next/services/MockEventsService.ts b/packages/backend-test-utils/src/services/MockEventsService.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockEventsService.ts rename to packages/backend-test-utils/src/services/MockEventsService.ts diff --git a/packages/backend-test-utils/src/next/services/MockHttpAuthService.test.ts b/packages/backend-test-utils/src/services/MockHttpAuthService.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockHttpAuthService.test.ts rename to packages/backend-test-utils/src/services/MockHttpAuthService.test.ts diff --git a/packages/backend-test-utils/src/next/services/MockHttpAuthService.ts b/packages/backend-test-utils/src/services/MockHttpAuthService.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockHttpAuthService.ts rename to packages/backend-test-utils/src/services/MockHttpAuthService.ts diff --git a/packages/backend-test-utils/src/next/services/MockPermissionsService.test.ts b/packages/backend-test-utils/src/services/MockPermissionsService.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockPermissionsService.test.ts rename to packages/backend-test-utils/src/services/MockPermissionsService.test.ts diff --git a/packages/backend-test-utils/src/next/services/MockPermissionsService.ts b/packages/backend-test-utils/src/services/MockPermissionsService.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockPermissionsService.ts rename to packages/backend-test-utils/src/services/MockPermissionsService.ts diff --git a/packages/backend-test-utils/src/next/services/MockRootLoggerService.test.ts b/packages/backend-test-utils/src/services/MockRootLoggerService.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockRootLoggerService.test.ts rename to packages/backend-test-utils/src/services/MockRootLoggerService.test.ts diff --git a/packages/backend-test-utils/src/next/services/MockRootLoggerService.ts b/packages/backend-test-utils/src/services/MockRootLoggerService.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockRootLoggerService.ts rename to packages/backend-test-utils/src/services/MockRootLoggerService.ts diff --git a/packages/backend-test-utils/src/next/services/MockUserInfoService.test.ts b/packages/backend-test-utils/src/services/MockUserInfoService.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockUserInfoService.test.ts rename to packages/backend-test-utils/src/services/MockUserInfoService.test.ts diff --git a/packages/backend-test-utils/src/next/services/MockUserInfoService.ts b/packages/backend-test-utils/src/services/MockUserInfoService.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/MockUserInfoService.ts rename to packages/backend-test-utils/src/services/MockUserInfoService.ts diff --git a/packages/backend-test-utils/src/next/services/index.ts b/packages/backend-test-utils/src/services/index.ts similarity index 83% rename from packages/backend-test-utils/src/next/services/index.ts rename to packages/backend-test-utils/src/services/index.ts index 347c50aaee..73f179ca41 100644 --- a/packages/backend-test-utils/src/next/services/index.ts +++ b/packages/backend-test-utils/src/services/index.ts @@ -13,6 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { mockServices, type ServiceMock } from './mockServices'; +export { mockServices } from './mockServices'; export { mockCredentials } from './mockCredentials'; -export { type MockActionsRegistry } from './MockActionsRegistry'; +export { type ServiceMock } from './simpleMock'; diff --git a/packages/backend-test-utils/src/next/services/mockCredentials.test.ts b/packages/backend-test-utils/src/services/mockCredentials.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/mockCredentials.test.ts rename to packages/backend-test-utils/src/services/mockCredentials.test.ts diff --git a/packages/backend-test-utils/src/next/services/mockCredentials.ts b/packages/backend-test-utils/src/services/mockCredentials.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/mockCredentials.ts rename to packages/backend-test-utils/src/services/mockCredentials.ts diff --git a/packages/backend-test-utils/src/next/services/mockServices.test.ts b/packages/backend-test-utils/src/services/mockServices.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/services/mockServices.test.ts rename to packages/backend-test-utils/src/services/mockServices.test.ts diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/services/mockServices.ts similarity index 90% rename from packages/backend-test-utils/src/next/services/mockServices.ts rename to packages/backend-test-utils/src/services/mockServices.ts index a44bf912e7..06bf8f9a29 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/services/mockServices.ts @@ -55,10 +55,8 @@ import { MockRootLoggerService } from './MockRootLoggerService'; import { MockUserInfoService } from './MockUserInfoService'; import { mockCredentials } from './mockCredentials'; import { MockEventsService } from './MockEventsService'; -import { actionsServiceFactory } from '@backstage/backend-defaults/actions'; -import { actionsRegistryServiceFactory } from '@backstage/backend-defaults/actionsRegistry'; import { MockPermissionsService } from './MockPermissionsService'; -import { MockActionsRegistry } from './MockActionsRegistry'; +import { simpleMock } from './simpleMock'; /** @internal */ function createLoggerMock() { @@ -95,43 +93,6 @@ function simpleFactoryWithOptions< ((...options: TOptions) => ServiceFactory); } -/** @public */ -export type ServiceMock = { - factory: ServiceFactory; -} & { - [Key in keyof TService]: TService[Key] extends ( - ...args: infer Args - ) => infer Return - ? TService[Key] & jest.MockInstance - : TService[Key]; -}; - -/** @internal */ -function simpleMock( - ref: ServiceRef, - mockFactory: () => jest.Mocked, -): (partialImpl?: Partial) => ServiceMock { - return partialImpl => { - const mock = mockFactory(); - if (partialImpl) { - for (const [key, impl] of Object.entries(partialImpl)) { - if (typeof impl === 'function') { - (mock as any)[key].mockImplementation(impl); - } else { - (mock as any)[key] = impl; - } - } - } - return Object.assign(mock, { - factory: createServiceFactory({ - service: ref, - deps: {}, - factory: () => mock, - }), - }) as ServiceMock; - }; -} - /** * Mock implementations of the core services, to be used in tests. * @@ -553,28 +514,6 @@ export namespace mockServices { search: jest.fn(), })); } - export namespace actions { - export const factory = () => actionsServiceFactory; - export const mock = simpleMock(coreServices.actions, () => ({ - list: jest.fn(), - invoke: jest.fn(), - })); - } - - export function actionsRegistry(options?: { - logger: LoggerService; - }): MockActionsRegistry { - return MockActionsRegistry.create({ - logger: options?.logger ?? mockServices.logger.mock(), - }); - } - - export namespace actionsRegistry { - export const factory = () => actionsRegistryServiceFactory; - export const mock = simpleMock(coreServices.actionsRegistry, () => ({ - register: jest.fn(), - })); - } /** * Creates a functional mock implementation of the diff --git a/packages/backend-test-utils/src/services/simpleMock.ts b/packages/backend-test-utils/src/services/simpleMock.ts new file mode 100644 index 0000000000..484969f235 --- /dev/null +++ b/packages/backend-test-utils/src/services/simpleMock.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + createServiceFactory, + ServiceFactory, + ServiceRef, +} from '@backstage/backend-plugin-api'; + +/** @public */ +export type ServiceMock = { + factory: ServiceFactory; +} & { + [Key in keyof TService]: TService[Key] extends ( + ...args: infer Args + ) => infer Return + ? TService[Key] & jest.MockInstance + : TService[Key]; +}; + +/** @internal */ +export function simpleMock( + ref: ServiceRef, + mockFactory: () => jest.Mocked, +): (partialImpl?: Partial) => ServiceMock { + return partialImpl => { + const mock = mockFactory(); + if (partialImpl) { + for (const [key, impl] of Object.entries(partialImpl)) { + if (typeof impl === 'function') { + (mock as any)[key].mockImplementation(impl); + } else { + (mock as any)[key] = impl; + } + } + } + return Object.assign(mock, { + factory: createServiceFactory({ + service: ref, + deps: {}, + factory: () => mock, + }), + }) as ServiceMock; + }; +} diff --git a/packages/backend-test-utils/src/util/errorHandler.ts b/packages/backend-test-utils/src/util/errorHandler.ts index f534823f50..79f4d8fd04 100644 --- a/packages/backend-test-utils/src/util/errorHandler.ts +++ b/packages/backend-test-utils/src/util/errorHandler.ts @@ -15,7 +15,7 @@ */ import { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter'; -import { mockServices } from '../next'; +import { mockServices } from '../services'; /** * A mock for error handler middleware that can be used in router tests. diff --git a/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.test.ts b/packages/backend-test-utils/src/wiring/ServiceFactoryTester.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.test.ts rename to packages/backend-test-utils/src/wiring/ServiceFactoryTester.test.ts diff --git a/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts b/packages/backend-test-utils/src/wiring/ServiceFactoryTester.ts similarity index 97% rename from packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts rename to packages/backend-test-utils/src/wiring/ServiceFactoryTester.ts index 83a6a42307..2b49abc88f 100644 --- a/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts +++ b/packages/backend-test-utils/src/wiring/ServiceFactoryTester.ts @@ -20,7 +20,7 @@ import { defaultServiceFactories } from './TestBackend'; // This is a relative import in order to make sure that the implementation is duplicated // rather than leading to an import from @backstage/backend-app-api. // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { ServiceRegistry } from '../../../../backend-app-api/src/wiring/ServiceRegistry'; +import { ServiceRegistry } from '../../../backend-app-api/src/wiring/ServiceRegistry'; /** * Options for {@link ServiceFactoryTester}. diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts b/packages/backend-test-utils/src/wiring/TestBackend.test.ts similarity index 100% rename from packages/backend-test-utils/src/next/wiring/TestBackend.test.ts rename to packages/backend-test-utils/src/wiring/TestBackend.test.ts diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/wiring/TestBackend.ts similarity index 97% rename from packages/backend-test-utils/src/next/wiring/TestBackend.ts rename to packages/backend-test-utils/src/wiring/TestBackend.ts index 84042aedd8..cdfa1fc695 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/wiring/TestBackend.ts @@ -31,7 +31,7 @@ import express from 'express'; import { InternalBackendFeature, InternalBackendRegistrations, -} from '../../../../backend-plugin-api/src/wiring/types'; +} from '../../../backend-plugin-api/src/wiring/types'; import { DefaultRootHttpRouter, ExtendedHttpServer, @@ -40,6 +40,10 @@ import { createHttpServer, } from '@backstage/backend-defaults/rootHttpRouter'; import { HostDiscovery } from '@backstage/backend-defaults/discovery'; +import { + actionsRegistryServiceMock, + actionsServiceMock, +} from '../alpha/services'; /** @public */ export interface TestBackendOptions { @@ -84,8 +88,10 @@ export const defaultServiceFactories = [ mockServices.userInfo.factory(), mockServices.urlReader.factory(), mockServices.events.factory(), - mockServices.actionsRegistry.factory(), - mockServices.actions.factory(), + + // Alpha services + actionsRegistryServiceMock.factory(), + actionsServiceMock.factory(), ]; /** diff --git a/packages/backend-test-utils/src/next/wiring/index.ts b/packages/backend-test-utils/src/wiring/index.ts similarity index 100% rename from packages/backend-test-utils/src/next/wiring/index.ts rename to packages/backend-test-utils/src/wiring/index.ts From 1269a131388ca92f85238f759a86f7d0c58b38fd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 15:02:41 +0000 Subject: [PATCH 137/301] fix(deps): update dependency octokit to v3.2.2 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 77 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/yarn.lock b/yarn.lock index 1a955071d1..a3e391955c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13381,10 +13381,10 @@ __metadata: languageName: node linkType: hard -"@octokit/openapi-types@npm:^22.2.0": - version: 22.2.0 - resolution: "@octokit/openapi-types@npm:22.2.0" - checksum: 10/0471b0c789fada5aa2390e6f82ba477738228ef7d2d986dda9aab0cb625d1562bd178ba0ba4d2655ce841079cd5efff9e58ece2077c27e569ea22109ea301830 +"@octokit/openapi-types@npm:^24.2.0": + version: 24.2.0 + resolution: "@octokit/openapi-types@npm:24.2.0" + checksum: 10/000897ebc6e247c2591049d6081e95eb5636f73798dadd695ee6048496772b58065df88823e74a760201828545a7ac601dd3c1bcd2e00079a62a9ee9d389409c languageName: node linkType: hard @@ -13397,14 +13397,14 @@ __metadata: languageName: node linkType: hard -"@octokit/plugin-paginate-rest@npm:11.3.1": - version: 11.3.1 - resolution: "@octokit/plugin-paginate-rest@npm:11.3.1" +"@octokit/plugin-paginate-rest@npm:11.4.4-cjs.2": + version: 11.4.4-cjs.2 + resolution: "@octokit/plugin-paginate-rest@npm:11.4.4-cjs.2" dependencies: - "@octokit/types": "npm:^13.5.0" + "@octokit/types": "npm:^13.7.0" peerDependencies: "@octokit/core": 5 - checksum: 10/82f5bcc3a536a44bed0a205c8301176c0d210b7a1c6d035a79b31a102e2e02f46234a38629cc984a21be544194ac69151814e9a909416aa7389cdffd1297bcd9 + checksum: 10/e0f696b3b69febe4e7c736d909065871f38bb8346a07f19a9c83246a02972568ac672667db472f846baef20a9611adf26ce8f0f189a11004c4b6618765078e19 languageName: node linkType: hard @@ -13440,14 +13440,14 @@ __metadata: languageName: node linkType: hard -"@octokit/plugin-rest-endpoint-methods@npm:13.2.2": - version: 13.2.2 - resolution: "@octokit/plugin-rest-endpoint-methods@npm:13.2.2" +"@octokit/plugin-rest-endpoint-methods@npm:13.3.2-cjs.1": + version: 13.3.2-cjs.1 + resolution: "@octokit/plugin-rest-endpoint-methods@npm:13.3.2-cjs.1" dependencies: - "@octokit/types": "npm:^13.5.0" + "@octokit/types": "npm:^13.8.0" peerDependencies: "@octokit/core": ^5 - checksum: 10/9eccc1a22aa0b65f3f9378f26a74c386683db420c33202998918df1eef492e93212e1849e1d85530f425602663cfc2bfbf385a30991b8a04470334c74ba2386b + checksum: 10/479827e62466e55bc1a50129d51597807bddc6c909e56be9e8dd9c1a91efa0f466a2f56b7d80438649e21ab0a3a195f840b3fccf2ae7f11fb0a919db8e62bc62 languageName: node linkType: hard @@ -13589,12 +13589,12 @@ __metadata: languageName: node linkType: hard -"@octokit/types@npm:^13.0.0, @octokit/types@npm:^13.1.0, @octokit/types@npm:^13.5.0": - version: 13.6.2 - resolution: "@octokit/types@npm:13.6.2" +"@octokit/types@npm:^13.0.0, @octokit/types@npm:^13.1.0, @octokit/types@npm:^13.7.0, @octokit/types@npm:^13.8.0": + version: 13.10.0 + resolution: "@octokit/types@npm:13.10.0" dependencies: - "@octokit/openapi-types": "npm:^22.2.0" - checksum: 10/8e614796f3554d28dfb77c570e80ef52d68ef311bdd4614ec263f8ea2266b9c06d4f7963fe2989f32cbfe4ea0c05e13eba9a64a6e0f64afb997cd975af154d52 + "@octokit/openapi-types": "npm:^24.2.0" + checksum: 10/32f8f5010d7faae128b0cdd0c221f0ca8c3781fe44483ecd87162b3da507db667f7369acda81340f6e2c9c374d9a938803409c6085c2c01d98210b6c58efb99a languageName: node linkType: hard @@ -13641,10 +13641,10 @@ __metadata: languageName: node linkType: hard -"@octokit/webhooks-methods@npm:^4.0.0": - version: 4.0.0 - resolution: "@octokit/webhooks-methods@npm:4.0.0" - checksum: 10/f26892ed868488bf08d5be1fdacbc51f5b6ba84cef21067e0b1ff969d087202989e74303049691a77c75bb1940e7835ad6522b0f4f151ceef015e6d083789c80 +"@octokit/webhooks-methods@npm:^4.1.0": + version: 4.1.0 + resolution: "@octokit/webhooks-methods@npm:4.1.0" + checksum: 10/a95ad68600c43798b09ea29d5a356fb69de25b45d38fbddf0ade00aadb0492b1d59985031e072a66d12e91034999a536653e5d2d4d01350d38cccf735d9ca270 languageName: node linkType: hard @@ -13655,10 +13655,10 @@ __metadata: languageName: node linkType: hard -"@octokit/webhooks-types@npm:7.1.0": - version: 7.1.0 - resolution: "@octokit/webhooks-types@npm:7.1.0" - checksum: 10/80b41945586243df9178a24dce9a5c4b2784eb963c5f6a1c76bcf5600a56e9cb51d8dc4a0da2108abfab3323cde577474a6365615fa1dfaac84c14e9295b54f9 +"@octokit/webhooks-types@npm:7.6.1": + version: 7.6.1 + resolution: "@octokit/webhooks-types@npm:7.6.1" + checksum: 10/0b11bd7e8d13b5a9cf14214421298a423d0180a5e1aaaea876ee4db6f97b5cca536f48d89af63105db75419d777a2402733eb0e110002d4dd59581ef36037bdc languageName: node linkType: hard @@ -13674,15 +13674,15 @@ __metadata: languageName: node linkType: hard -"@octokit/webhooks@npm:^12.0.4": - version: 12.0.10 - resolution: "@octokit/webhooks@npm:12.0.10" +"@octokit/webhooks@npm:^12.0.4, @octokit/webhooks@npm:^12.3.1": + version: 12.3.1 + resolution: "@octokit/webhooks@npm:12.3.1" dependencies: "@octokit/request-error": "npm:^5.0.0" - "@octokit/webhooks-methods": "npm:^4.0.0" - "@octokit/webhooks-types": "npm:7.1.0" + "@octokit/webhooks-methods": "npm:^4.1.0" + "@octokit/webhooks-types": "npm:7.6.1" aggregate-error: "npm:^3.1.0" - checksum: 10/ab7d216d1a1fae91bc3f75057c093707c1f6dbd5ff2106656154a7f471b1a1b5b78c947474e21362590e5510516f9537c4ab79ad6780cc91bfaaaa685aa366e0 + checksum: 10/373266807eb8dcf8d8c6f4685594106f1a257798a1b391cbe36e5b5faf60aca38a0467acd1356427ea7375edc111c03be3d443bc04efb16019a84e853c8a1bc3 languageName: node linkType: hard @@ -40763,20 +40763,21 @@ __metadata: linkType: hard "octokit@npm:^3.0.0": - version: 3.2.1 - resolution: "octokit@npm:3.2.1" + version: 3.2.2 + resolution: "octokit@npm:3.2.2" dependencies: "@octokit/app": "npm:^14.0.2" "@octokit/core": "npm:^5.0.0" "@octokit/oauth-app": "npm:^6.0.0" "@octokit/plugin-paginate-graphql": "npm:^4.0.0" - "@octokit/plugin-paginate-rest": "npm:11.3.1" - "@octokit/plugin-rest-endpoint-methods": "npm:13.2.2" + "@octokit/plugin-paginate-rest": "npm:11.4.4-cjs.2" + "@octokit/plugin-rest-endpoint-methods": "npm:13.3.2-cjs.1" "@octokit/plugin-retry": "npm:^6.0.0" "@octokit/plugin-throttling": "npm:^8.0.0" "@octokit/request-error": "npm:^5.0.0" "@octokit/types": "npm:^13.0.0" - checksum: 10/a3539831c9c0828b1e37dc94a3668f5fb9aa873fc396c0464275d152e4065b6b20300cfba766871cb52ed2e6d537febda7d4be872894c4c62024393a3578fde0 + "@octokit/webhooks": "npm:^12.3.1" + checksum: 10/a258cc62767552fcf9d9a2dfb2aac44b326a2a235fb6444c1f37bc018bda4ac4bf0203704d8b7b3955d3caee6f9256c200287f7e99843c8be01febe4d9fe86d8 languageName: node linkType: hard From a353f9364c780ebf4c4a600adb8b0abdf133c1d4 Mon Sep 17 00:00:00 2001 From: Cory Steers Date: Mon, 16 Jun 2025 10:42:52 -0500 Subject: [PATCH 138/301] implement sugested changes Signed-off-by: Cory Steers --- .../src/handlers/afterWorkspaceDependencyAddition.ts | 5 ++--- .../src/handlers/afterWorkspaceDependencyReplacement.ts | 7 +++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.ts b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.ts index 9307790930..219830a8c9 100644 --- a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.ts +++ b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyAddition.ts @@ -33,9 +33,8 @@ export const afterWorkspaceDependencyAddition = async ( ) { try { await getPackageVersion(descriptor, workspace.project.configuration); - // is there a better way to log than console.log? - console.log( - `afterWorkspaceDependencyAddition hook: Setting descriptor range from ${descriptor.range} to 'backstage:^' for ${descriptor.scope}/${descriptor.name}`, + console.info( + `Setting ${descriptor.scope}/${descriptor.name} to ${PROTOCOL}^`, ); descriptor.range = `${PROTOCOL}^`; } catch (_error: any) { diff --git a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.ts b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.ts index a955191244..55a11086fd 100644 --- a/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.ts +++ b/packages/yarn-plugin/src/handlers/afterWorkspaceDependencyReplacement.ts @@ -22,7 +22,7 @@ import { PROTOCOL } from '../constants'; export const afterWorkspaceDependencyReplacement = async ( workspace: Workspace, _target: suggestUtils.Target, - fromDescriptor: Descriptor, + _fromDescriptor: Descriptor, toDescriptor: Descriptor, ) => { const toDescriptorRange = structUtils.parseRange(toDescriptor.range); @@ -33,9 +33,8 @@ export const afterWorkspaceDependencyReplacement = async ( ) { try { await getPackageVersion(toDescriptor, workspace.project.configuration); - // is there a better way to log than console.log? - console.log( - `afterWorkspaceDependencyReplacement hook: Setting descriptor range from '${fromDescriptor.range}' to '${toDescriptor.range}' for ${fromDescriptor.scope}/${fromDescriptor.name}. Are you sure you want to be doing that?`, + console.warn( + `${toDescriptor.name} should be set to "${PROTOCOL}^" instead of "${toDescriptor.range}". Make sure this change is intentional and not a mistake.`, ); } catch (_error: any) { // if there's no found version then this is likely a deprecated package From 4607da6bbdb3332054ebef7accdfba64c142bc98 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Mon, 16 Jun 2025 16:34:57 +0200 Subject: [PATCH 139/301] feat: small tidy up and moving things around into alpha exports for actions and actions registry Signed-off-by: benjdlambert Signed-off-by: benjdlambert --- packages/backend-plugin-api/package.json | 4 +- .../backend-plugin-api/report-alpha.api.md | 100 +++++++++++++++++- packages/backend-plugin-api/report.api.md | 86 --------------- .../src/alpha/ActionsRegistryService.ts | 69 ++++++++++++ .../src/alpha/ActionsService.ts | 51 +++++++++ .../InstanceMetadataService.ts | 0 .../src/{alpha.ts => alpha/index.ts} | 31 +++--- packages/backend-plugin-api/src/alpha/refs.ts | 56 ++++++++++ .../src/services/definitions/coreServices.ts | 30 ------ .../src/services/definitions/index.ts | 8 -- packages/backend-test-utils/package.json | 3 + 11 files changed, 296 insertions(+), 142 deletions(-) create mode 100644 packages/backend-plugin-api/src/alpha/ActionsRegistryService.ts create mode 100644 packages/backend-plugin-api/src/alpha/ActionsService.ts rename packages/backend-plugin-api/src/{services/definitions => alpha}/InstanceMetadataService.ts (100%) rename packages/backend-plugin-api/src/{alpha.ts => alpha/index.ts} (60%) create mode 100644 packages/backend-plugin-api/src/alpha/refs.ts diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 52c1c6a66a..41c38eb688 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -20,7 +20,7 @@ "license": "Apache-2.0", "exports": { ".": "./src/index.ts", - "./alpha": "./src/alpha.ts", + "./alpha": "./src/alpha/index.ts", "./testUtils": "./src/testUtils.ts", "./package.json": "./package.json" }, @@ -29,7 +29,7 @@ "typesVersions": { "*": { "alpha": [ - "src/alpha.ts" + "src/alpha/index.ts" ], "testUtils": [ "src/testUtils.ts" diff --git a/packages/backend-plugin-api/report-alpha.api.md b/packages/backend-plugin-api/report-alpha.api.md index d485e2132e..9be7a29ed5 100644 --- a/packages/backend-plugin-api/report-alpha.api.md +++ b/packages/backend-plugin-api/report-alpha.api.md @@ -3,7 +3,105 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyZodObject } from 'zod'; +import { BackstageCredentials } from '@backstage/backend-plugin-api'; +import { JsonObject } from '@backstage/types'; +import { JSONSchema7 } from 'json-schema'; +import { JsonValue } from '@backstage/types'; +import { LoggerService } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; +import { z } from 'zod'; + +// @alpha (undocumented) +export type ActionsRegistryActionContext = { + input: z.infer; + logger: LoggerService; + credentials: BackstageCredentials; +}; + +// @alpha (undocumented) +export type ActionsRegistryActionOptions< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, +> = { + name: string; + title: string; + description: string; + schema: { + input: (zod: typeof z) => TInputSchema; + output: (zod: typeof z) => TOutputSchema; + }; + attributes?: { + destructive?: boolean; + idempotent?: boolean; + readOnly?: boolean; + }; + action: (context: ActionsRegistryActionContext) => Promise< + z.infer extends void + ? void + : { + output: z.infer; + } + >; +}; + +// @alpha (undocumented) +export interface ActionsRegistryService { + // (undocumented) + register< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, + >( + options: ActionsRegistryActionOptions, + ): void; +} + +// @alpha +export const actionsRegistryServiceRef: ServiceRef< + ActionsRegistryService, + 'plugin', + 'singleton' +>; + +// @alpha (undocumented) +export interface ActionsService { + // (undocumented) + invoke(opts: { + id: string; + input?: JsonObject; + credentials: BackstageCredentials; + }): Promise<{ + output: JsonValue; + }>; + // (undocumented) + list: (opts: { credentials: BackstageCredentials }) => Promise<{ + actions: ActionsServiceAction[]; + }>; +} + +// @alpha (undocumented) +export type ActionsServiceAction = { + id: string; + name: string; + title: string; + description: string; + schema: { + input: JSONSchema7; + output: JSONSchema7; + }; + attributes: { + readOnly: boolean; + destructive: boolean; + idempotent: boolean; + }; +}; + +// @alpha +export const actionsServiceRef: ServiceRef< + ActionsService, + 'plugin', + 'singleton' +>; // @alpha (undocumented) export type BackendFeatureMeta = @@ -23,7 +121,7 @@ export interface InstanceMetadataService { getInstalledFeatures: () => BackendFeatureMeta[]; } -// @alpha +// @alpha (undocumented) export const instanceMetadataServiceRef: ServiceRef< InstanceMetadataService, 'plugin', diff --git a/packages/backend-plugin-api/report.api.md b/packages/backend-plugin-api/report.api.md index d5030adb9c..52843dec96 100644 --- a/packages/backend-plugin-api/report.api.md +++ b/packages/backend-plugin-api/report.api.md @@ -3,7 +3,6 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AnyZodObject } from 'zod'; import { AuthorizePermissionRequest } from '@backstage/plugin-permission-common'; import { AuthorizePermissionResponse } from '@backstage/plugin-permission-common'; import { Config } from '@backstage/config'; @@ -13,7 +12,6 @@ import type { Handler } from 'express'; import { HumanDuration } from '@backstage/types'; import { isChildPath } from '@backstage/cli-common'; import { JsonObject } from '@backstage/types'; -import { JSONSchema7 } from 'json-schema'; import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; import { Permission } from '@backstage/plugin-permission-common'; @@ -27,84 +25,6 @@ import { QueryPermissionResponse } from '@backstage/plugin-permission-common'; import { Readable } from 'stream'; import type { Request as Request_2 } from 'express'; import type { Response as Response_2 } from 'express'; -import { z } from 'zod'; - -// @public (undocumented) -export type ActionsRegistryActionContext = { - input: z.infer; - logger: LoggerService; - credentials: BackstageCredentials; -}; - -// @public (undocumented) -export type ActionsRegistryActionOptions< - TInputSchema extends AnyZodObject, - TOutputSchema extends AnyZodObject, -> = { - name: string; - title: string; - description: string; - schema: { - input: (zod: typeof z) => TInputSchema; - output: (zod: typeof z) => TOutputSchema; - }; - attributes?: { - destructive?: boolean; - idempotent?: boolean; - readOnly?: boolean; - }; - action: (context: ActionsRegistryActionContext) => Promise< - z.infer extends void - ? void - : { - output: z.infer; - } - >; -}; - -// @public (undocumented) -export interface ActionsRegistryService { - // (undocumented) - register< - TInputSchema extends AnyZodObject, - TOutputSchema extends AnyZodObject, - >( - options: ActionsRegistryActionOptions, - ): void; -} - -// @public (undocumented) -export interface ActionsService { - // (undocumented) - invoke(opts: { - id: string; - input?: JsonObject; - credentials: BackstageCredentials; - }): Promise<{ - output: JsonValue; - }>; - // (undocumented) - list: (opts: { credentials: BackstageCredentials }) => Promise<{ - actions: ActionsServiceAction[]; - }>; -} - -// @public (undocumented) -export type ActionsServiceAction = { - id: string; - name: string; - title: string; - description: string; - schema: { - input: JSONSchema7; - output: JSONSchema7; - }; - attributes: { - readOnly: boolean; - destructive: boolean; - idempotent: boolean; - }; -}; // @public export interface AuditorService { @@ -285,12 +205,6 @@ export type CacheServiceSetOptions = { // @public export namespace coreServices { const auth: ServiceRef; - const actions: ServiceRef; - const actionsRegistry: ServiceRef< - ActionsRegistryService, - 'plugin', - 'singleton' - >; const userInfo: ServiceRef; const cache: ServiceRef; const rootConfig: ServiceRef; diff --git a/packages/backend-plugin-api/src/alpha/ActionsRegistryService.ts b/packages/backend-plugin-api/src/alpha/ActionsRegistryService.ts new file mode 100644 index 0000000000..9a7820e354 --- /dev/null +++ b/packages/backend-plugin-api/src/alpha/ActionsRegistryService.ts @@ -0,0 +1,69 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { z, AnyZodObject } from 'zod'; +import { + LoggerService, + BackstageCredentials, +} from '@backstage/backend-plugin-api'; + +/** + * @alpha + */ +export type ActionsRegistryActionContext = { + input: z.infer; + logger: LoggerService; + credentials: BackstageCredentials; +}; + +/** + * @alpha + */ +export type ActionsRegistryActionOptions< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, +> = { + name: string; + title: string; + description: string; + schema: { + input: (zod: typeof z) => TInputSchema; + output: (zod: typeof z) => TOutputSchema; + }; + attributes?: { + destructive?: boolean; + idempotent?: boolean; + readOnly?: boolean; + }; + action: ( + context: ActionsRegistryActionContext, + ) => Promise< + z.infer extends void + ? void + : { output: z.infer } + >; +}; + +/** + * @alpha + */ +export interface ActionsRegistryService { + register< + TInputSchema extends AnyZodObject, + TOutputSchema extends AnyZodObject, + >( + options: ActionsRegistryActionOptions, + ): void; +} diff --git a/packages/backend-plugin-api/src/alpha/ActionsService.ts b/packages/backend-plugin-api/src/alpha/ActionsService.ts new file mode 100644 index 0000000000..528b6cea67 --- /dev/null +++ b/packages/backend-plugin-api/src/alpha/ActionsService.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { JsonObject, JsonValue } from '@backstage/types'; +import { JSONSchema7 } from 'json-schema'; +import { BackstageCredentials } from '@backstage/backend-plugin-api'; + +/** + * @alpha + */ +export type ActionsServiceAction = { + id: string; + name: string; + title: string; + description: string; + schema: { + input: JSONSchema7; + output: JSONSchema7; + }; + attributes: { + readOnly: boolean; + destructive: boolean; + idempotent: boolean; + }; +}; + +/** + * @alpha + */ +export interface ActionsService { + list: (opts: { + credentials: BackstageCredentials; + }) => Promise<{ actions: ActionsServiceAction[] }>; + invoke(opts: { + id: string; + input?: JsonObject; + credentials: BackstageCredentials; + }): Promise<{ output: JsonValue }>; +} diff --git a/packages/backend-plugin-api/src/services/definitions/InstanceMetadataService.ts b/packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts similarity index 100% rename from packages/backend-plugin-api/src/services/definitions/InstanceMetadataService.ts rename to packages/backend-plugin-api/src/alpha/InstanceMetadataService.ts diff --git a/packages/backend-plugin-api/src/alpha.ts b/packages/backend-plugin-api/src/alpha/index.ts similarity index 60% rename from packages/backend-plugin-api/src/alpha.ts rename to packages/backend-plugin-api/src/alpha/index.ts index 6e91e10671..5bb69eb4e2 100644 --- a/packages/backend-plugin-api/src/alpha.ts +++ b/packages/backend-plugin-api/src/alpha/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2025 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,20 +14,21 @@ * limitations under the License. */ -import { createServiceRef } from '@backstage/backend-plugin-api'; - -/** - * EXPERIMENTAL: Instance metadata service. - * - * @alpha - */ -export const instanceMetadataServiceRef = createServiceRef< - import('./services/definitions/InstanceMetadataService').InstanceMetadataService ->({ - id: 'core.instanceMetadata', -}); - export type { BackendFeatureMeta, InstanceMetadataService, -} from './services/definitions/InstanceMetadataService'; +} from './InstanceMetadataService'; + +export type { + ActionsRegistryService, + ActionsRegistryActionOptions, + ActionsRegistryActionContext, +} from './ActionsRegistryService'; + +export type { ActionsService, ActionsServiceAction } from './ActionsService'; + +export { + actionsRegistryServiceRef, + actionsServiceRef, + instanceMetadataServiceRef, +} from './refs'; diff --git a/packages/backend-plugin-api/src/alpha/refs.ts b/packages/backend-plugin-api/src/alpha/refs.ts new file mode 100644 index 0000000000..81996679f7 --- /dev/null +++ b/packages/backend-plugin-api/src/alpha/refs.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createServiceRef } from '@backstage/backend-plugin-api'; + +/** + * @alpha + */ +export const instanceMetadataServiceRef = createServiceRef< + import('./InstanceMetadataService').InstanceMetadataService +>({ + id: 'core.instanceMetadata', +}); + +/** + * Service for calling distributed actions + * + * See {@link ActionsService} + * and {@link https://backstage.io/docs/backend-system/core-services/actions | the service docs} + * for more information. + * + * @alpha + */ +export const actionsServiceRef = createServiceRef< + import('./ActionsService').ActionsService +>({ + id: 'alpha.core.actions', +}); + +/** + * Service for registering and managing distributed actions. + * + * See {@link ActionsRegistryService} + * and {@link https://backstage.io/docs/backend-system/core-services/actions-registry | the service docs} + * for more information. + * + * @alpha + */ +export const actionsRegistryServiceRef = createServiceRef< + import('./ActionsRegistryService').ActionsRegistryService +>({ + id: 'alpha.core.actionsRegistry', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/coreServices.ts b/packages/backend-plugin-api/src/services/definitions/coreServices.ts index c5282368ac..8c8c6c0f82 100644 --- a/packages/backend-plugin-api/src/services/definitions/coreServices.ts +++ b/packages/backend-plugin-api/src/services/definitions/coreServices.ts @@ -35,36 +35,6 @@ export namespace coreServices { id: 'core.auth', }); - /** - * Service for calling distributed actions - * - * See {@link ActionsService} - * and {@link https://backstage.io/docs/backend-system/core-services/actions | the service docs} - * for more information. - * - * @public - */ - export const actions = createServiceRef< - import('./ActionsService').ActionsService - >({ - id: 'core.actions', - }); - - /** - * Service for registering and managing distributed actions. - * - * See {@link ActionsRegistryService} - * and {@link https://backstage.io/docs/backend-system/core-services/actions-registry | the service docs} - * for more information. - * - * @public - */ - export const actionsRegistry = createServiceRef< - import('./ActionsRegistryService').ActionsRegistryService - >({ - id: 'core.actionsRegistry', - }); - /** * Authenticated user information retrieval. * diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index a01544b60f..c811a513a8 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -13,14 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -export type { - ActionsRegistryService, - ActionsRegistryActionOptions, - ActionsRegistryActionContext, -} from './ActionsRegistryService'; - -export type { ActionsService, ActionsServiceAction } from './ActionsService'; export type { AuditorService, AuditorServiceCreateEventOptions, diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 934d2439a9..0b5a4d7626 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -30,6 +30,9 @@ "*": { "package.json": [ "package.json" + ], + "alpha": [ + "src/alpha/index.ts" ] } }, From 1a3325496276c93cf178f67c39ffe2f2dd6e80c3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 17:48:39 +0000 Subject: [PATCH 140/301] chore(deps): update dependency @playwright/test to v1.53.0 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index a3e391955c..bdadb7d20c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15136,13 +15136,13 @@ __metadata: linkType: hard "@playwright/test@npm:^1.32.3": - version: 1.52.0 - resolution: "@playwright/test@npm:1.52.0" + version: 1.53.0 + resolution: "@playwright/test@npm:1.53.0" dependencies: - playwright: "npm:1.52.0" + playwright: "npm:1.53.0" bin: playwright: cli.js - checksum: 10/e18a4eb626c7bc6cba212ff2e197cf9ae2e4da1c91bfdf08a744d62e27222751173e4b220fa27da72286a89a3b4dea7c09daf384d23708f284b64f98e9a63a88 + checksum: 10/968df4fba133dd18b8c65504c3cc5a3a6071e49f0706c6524711cdfab321a51debfeb506b9ff0a8f7dd8ce3015921d82fa51429d8f11d392cc68de1938703c33 languageName: node linkType: hard @@ -42153,27 +42153,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.52.0": - version: 1.52.0 - resolution: "playwright-core@npm:1.52.0" +"playwright-core@npm:1.53.0": + version: 1.53.0 + resolution: "playwright-core@npm:1.53.0" bin: playwright-core: cli.js - checksum: 10/42e13f5f98dc25ebc95525fb338a215b9097b2ba39d41e99972a190bf75d79979f163f5bc07b1ca06847ee07acb2c9b487d070fab67e9cd55e33310fc05aca3c + checksum: 10/881f27a9b7edd9954700489a5a4212cb91bcada226fd1d79a239b2eab0f333df1e2e41e275e6fa846d7f57c6a92afe14dca33ca7a2ce303dfb687d02511b7c69 languageName: node linkType: hard -"playwright@npm:1.52.0": - version: 1.52.0 - resolution: "playwright@npm:1.52.0" +"playwright@npm:1.53.0": + version: 1.53.0 + resolution: "playwright@npm:1.53.0" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.52.0" + playwright-core: "npm:1.53.0" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10/214175446089000c2ac997b925063b95f7d86d129c5d7c74caa5ddcb05bcad598dfd569d2133a10dc82d288bf67e7858877dcd099274b0b928b9c63db7d6ecec + checksum: 10/0b0258630f39b4d6ff1555d008ee4d591fe45cbe1e0f643a612397e3e6b1f7a99a2037a957eaa7351edd907ba10966ba105b2d244eafd1b247378910b660f086 languageName: node linkType: hard From a19e622d723f998fd65cd6b8fe23511d34ba2c12 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 18:40:07 +0000 Subject: [PATCH 141/301] chore(deps): update dependency axios to v1.10.0 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 94aa50e750..13e11813af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25748,7 +25748,7 @@ __metadata: languageName: node linkType: hard -"axios@npm:1.9.0, axios@npm:^1.0.0, axios@npm:^1.6.0, axios@npm:^1.7.4, axios@npm:^1.7.7, axios@npm:^1.7.8": +"axios@npm:1.9.0": version: 1.9.0 resolution: "axios@npm:1.9.0" dependencies: @@ -25759,6 +25759,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.0.0, axios@npm:^1.6.0, axios@npm:^1.7.4, axios@npm:^1.7.7, axios@npm:^1.7.8": + version: 1.10.0 + resolution: "axios@npm:1.10.0" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 10/d43c80316a45611fd395743e15d16ea69a95f2b7f7095f2bb12cb78f9ca0a905194a02e52a3bf4e0db9f85fd1186d6c690410644c10ecd8bb0a468e57c2040e4 + languageName: node + linkType: hard + "axobject-query@npm:^4.1.0": version: 4.1.0 resolution: "axobject-query@npm:4.1.0" From 063b2d39ce85ab6a48b5874d4ad377f60854f470 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Fri, 13 Jun 2025 01:46:04 +0300 Subject: [PATCH 142/301] feat: eslint rule to check forbidden plugin imports basically verify-local-dependencies.js but done during linting also in the 3rd party repositories. Signed-off-by: Hellgren Heikki --- .changeset/cold-crews-peel.md | 5 + .changeset/mean-pillows-enter.md | 12 ++ .eslintrc.js | 18 ++- package.json | 1 + packages/canon/.eslintrc.js | 1 + packages/eslint-plugin/README.md | 1 + .../docs/rules/no-mixed-plugin-imports.md | 67 ++++++++ packages/eslint-plugin/index.js | 2 + .../rules/no-mixed-plugin-imports.js | 149 ++++++++++++++++++ .../monorepo/packages/foo/package.json | 13 +- .../src/no-mixed-plugin-imports.test.ts | 68 ++++++++ .../package.json | 1 - .../gitlabGroupEnsureExists.examples.test.ts | 22 +-- .../actions/gitlabGroupEnsureExists.test.ts | 22 +-- .../gitlabIssueCreate.examples.test.ts | 22 +-- .../src/actions/gitlabIssueCreate.test.ts | 22 +-- .../actions/gitlabIssueEdit.examples.test.ts | 22 +-- .../src/actions/gitlabIssueEdit.test.ts | 22 +-- .../gitlabPipelineTrigger.examples.test.ts | 22 +-- .../src/actions/gitlabPipelineTrigger.test.ts | 22 +-- yarn.lock | 4 +- 21 files changed, 429 insertions(+), 89 deletions(-) create mode 100644 .changeset/cold-crews-peel.md create mode 100644 .changeset/mean-pillows-enter.md create mode 100644 packages/eslint-plugin/docs/rules/no-mixed-plugin-imports.md create mode 100644 packages/eslint-plugin/rules/no-mixed-plugin-imports.js create mode 100644 packages/eslint-plugin/src/no-mixed-plugin-imports.test.ts diff --git a/.changeset/cold-crews-peel.md b/.changeset/cold-crews-peel.md new file mode 100644 index 0000000000..465982920e --- /dev/null +++ b/.changeset/cold-crews-peel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Fixed dependency to frontend package from tests diff --git a/.changeset/mean-pillows-enter.md b/.changeset/mean-pillows-enter.md new file mode 100644 index 0000000000..7775e2648f --- /dev/null +++ b/.changeset/mean-pillows-enter.md @@ -0,0 +1,12 @@ +--- +'@backstage/eslint-plugin': patch +--- + +Added new eslint rule to restrict mixed plugin imports. + +New rule `@backstage/no-mixed-plugin-imports` disallows mixed imports between plugins that are mixing +the backstage architecture. This rule forces that: + +- No imports from frontend plugins to backend plugins or other frontend plugins. +- No imports from backend plugins to frontend plugins or other backend plugins. +- No imports from common plugins to frontend or backend plugins. diff --git a/.eslintrc.js b/.eslintrc.js index 409b820783..58e14ee2d1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -18,8 +18,24 @@ var path = require('path'); module.exports = { root: true, - plugins: ['@spotify', 'notice', 'react', 'testing-library'], + plugins: ['@spotify', 'notice', 'react', 'testing-library', '@backstage'], rules: { + '@backstage/no-mixed-plugin-imports': [ + 'error', + { + // TODO: Fix these either by right role or by moving things to new packages + excludedTargetPackages: [ + '@backstage/test-utils', + '@backstage/config-loader', + '@backstage/plugin-catalog', + '@backstage/plugin-permission-backend', + '@backstage/plugin-app-backend', + '@backstage/plugin-techdocs', + '@backstage/plugin-app', + '@backstage/plugin-catalog-backend' + ], + } + ], 'react/react-in-jsx-scope': 'off', 'notice/notice': [ 'error', diff --git a/package.json b/package.json index fc773cb0d0..0b827a0ce3 100644 --- a/package.json +++ b/package.json @@ -122,6 +122,7 @@ "@backstage/codemods": "workspace:*", "@backstage/create-app": "workspace:*", "@backstage/e2e-test-utils": "workspace:*", + "@backstage/eslint-plugin": "workspace:*", "@backstage/repo-tools": "workspace:*", "@changesets/cli": "^2.14.0", "@octokit/rest": "^19.0.3", diff --git a/packages/canon/.eslintrc.js b/packages/canon/.eslintrc.js index f7c0e8cbd1..9638ff6e45 100644 --- a/packages/canon/.eslintrc.js +++ b/packages/canon/.eslintrc.js @@ -3,6 +3,7 @@ module.exports = { extends: ['plugin:storybook/recommended'], rules: { 'react/forbid-elements': 'off', + '@backstage/no-mixed-plugin-imports': 'off' }, }; diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 600b3268e1..e30b9e5de3 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -41,3 +41,4 @@ The following rules are provided by this plugin: | [@backstage/no-relative-monorepo-imports](./docs/rules/no-relative-monorepo-imports.md) | Forbid relative imports that reach outside of the package in a monorepo. | | [@backstage/no-undeclared-imports](./docs/rules/no-undeclared-imports.md) | Forbid imports of external packages that have not been declared in the appropriate dependencies field in `package.json`. | | [@backstage/no-top-level-material-ui-4-imports](./docs/rules/no-top-level-material-ui-4-imports.md) | Forbid top level import from Material UI v4 packages. | +| [@backstage/no-mixed-plugin-imports](./docs/rules/no-mixed-plugin-imports.md) | Disallow mixed plugin imports. | diff --git a/packages/eslint-plugin/docs/rules/no-mixed-plugin-imports.md b/packages/eslint-plugin/docs/rules/no-mixed-plugin-imports.md new file mode 100644 index 0000000000..e8570054e4 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-mixed-plugin-imports.md @@ -0,0 +1,67 @@ +# @backstage/no-mixed-plugin-imports + +Disallow mixed imports between backstage plugins. + +## Usage + +Add the rules as follows, it has no options: + +```js +"@backstage/no-mixed-plugin-imports": ["error"] +``` + +## Rule Details + +Given the following two target packages: + +```json +{ + "name": "@backstage/plugin-foo", + "backstage": { + "role": "frontend-plugin" + } +} +``` + +```json +{ + "name": "@backstage/plugin-bar", + "backstage": { + "role": "frontend-plugin" + } +} +``` + +### Fail + +```ts +import { FooCard } from '@backstage/plugin-foo'; +``` + +### Pass + +```ts +import { FooCard } from '@backstage/plugin-foo-react'; +``` + +## Options + +You can ignore specific target packages or files by adding them to the options in the `.eslintrc.js` file: + +```js +{ + rules: { + '@backstage/no-mixed-plugin-imports': [ + 'error', + { + excludedTargetPackages: [ + '@backstage/plugin-foo', + ], + excludedFiles: [ + '**/*.{test,spec}.[jt]s?(x)' + ], + } + ] + } +} +``` diff --git a/packages/eslint-plugin/index.js b/packages/eslint-plugin/index.js index af1a8891c3..a1e38e4269 100644 --- a/packages/eslint-plugin/index.js +++ b/packages/eslint-plugin/index.js @@ -22,6 +22,7 @@ module.exports = { '@backstage/no-forbidden-package-imports': 'error', '@backstage/no-relative-monorepo-imports': 'error', '@backstage/no-undeclared-imports': 'error', + '@backstage/no-mixed-plugin-imports': 'error', }, }, }, @@ -30,5 +31,6 @@ module.exports = { 'no-relative-monorepo-imports': require('./rules/no-relative-monorepo-imports'), 'no-undeclared-imports': require('./rules/no-undeclared-imports'), 'no-top-level-material-ui-4-imports': require('./rules/no-top-level-material-ui-4-imports'), + 'no-mixed-plugin-imports': require('./rules/no-mixed-plugin-imports'), }, }; diff --git a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js new file mode 100644 index 0000000000..90d3448b7a --- /dev/null +++ b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js @@ -0,0 +1,149 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-check + +const visitImports = require('../lib/visitImports'); +const getPackages = require('../lib/getPackages'); +const minimatch = require('minimatch'); + +const roleRules = [ + { + sourceRole: ['frontend-plugin', 'web-library'], + targetRole: [ + 'backend-plugin', + 'node-library', + 'backend-plugin-module', + 'frontend-plugin', + ], + }, + { + sourceRole: ['backend-plugin', 'node-library', 'backend-plugin-module'], + targetRole: ['frontend-plugin', 'web-library', 'backend-plugin'], + }, + { + sourceRole: ['common-library'], + targetRole: [ + 'frontend-plugin', + 'web-library', + 'backend-plugin', + 'node-library', + 'backend-plugin-module', + ], + }, +]; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + messages: { + forbidden: + '{{sourcePackage}} ({{sourceRole}}) uses forbidden import from {{targetPackage}} ({{targetRole}}).', + }, + docs: { + description: 'Disallow mixed plugin imports.', + url: 'https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-mixed-plugin-imports.md', + }, + schema: [ + { + type: 'object', + properties: { + excludedTargetPackages: { + type: 'array', + items: { type: 'string' }, + uniqueItems: true, + }, + excludedFiles: { + type: 'array', + items: { type: 'string' }, + uniqueItems: true, + }, + }, + additionalProperties: false, + }, + ], + }, + create(context) { + const packages = getPackages(context.cwd); + if (!packages) { + return {}; + } + + const filePath = context.physicalFilename + ? context.physicalFilename + : context.filename; + + const pkg = packages.byPath(filePath); + if (!pkg) { + return {}; + } + + const options = context.options[0] || {}; + const ignoreTargetPackages = options.excludedTargetPackages || []; + const ignorePatterns = options.excludedFiles || [ + '**/*.{test,spec}.[jt]s?(x)', + '**/dev/index.[jt]s?(x)', + ]; + + if ( + ignorePatterns.some(pattern => + new minimatch.Minimatch(pattern).match(context.filename), + ) + ) { + return {}; + } + + return visitImports(context, (node, imp) => { + if (imp.type !== 'internal') { + return; + } + + const targetPackage = imp.package; + const targetName = targetPackage.packageJson.name; + const sourceName = pkg.packageJson.name; + if (sourceName === targetName) { + return; + } + + const sourceRole = pkg.packageJson.backstage?.role; + const targetRole = targetPackage.packageJson.backstage?.role; + if (!sourceRole || !targetRole) { + return; + } + + if ( + roleRules.some( + rule => + rule.sourceRole.includes(sourceRole) && + rule.targetRole.includes(targetRole) && + !ignoreTargetPackages.includes(targetName), + ) + ) { + context.report({ + node: node, + messageId: 'forbidden', + data: { + sourcePackage: pkg.packageJson.name || imp.package.dir, + sourceRole, + targetPackage: targetPackage.packageJson.name || imp.package.dir, + targetRole, + }, + }); + } + }); + }, +}; diff --git a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/foo/package.json b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/foo/package.json index 563ec35120..e8e4d4ba70 100644 --- a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/foo/package.json +++ b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/foo/package.json @@ -1,5 +1,12 @@ { "name": "@internal/foo", + "backstage": { + "role": "frontend-plugin" + }, + "files": [ + "dist", + "type-utils" + ], "dependencies": { "@internal/bar": "1.0.0" }, @@ -8,9 +15,5 @@ }, "peerDependencies": { "react": "*" - }, - "files": [ - "dist", - "type-utils" - ] + } } diff --git a/packages/eslint-plugin/src/no-mixed-plugin-imports.test.ts b/packages/eslint-plugin/src/no-mixed-plugin-imports.test.ts new file mode 100644 index 0000000000..45764ca71a --- /dev/null +++ b/packages/eslint-plugin/src/no-mixed-plugin-imports.test.ts @@ -0,0 +1,68 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RuleTester } from 'eslint'; +import path from 'path'; +import rule from '../rules/no-mixed-plugin-imports'; + +const RULE = 'no-mixed-plugin-imports'; +const FIXTURE = path.resolve(__dirname, '__fixtures__/monorepo'); + +const ERR = ( + sourcePackage: string, + sourceRole: string, + targetPackage: string, + targetRole: string, +) => ({ + message: `${sourcePackage} (${sourceRole}) uses forbidden import from ${targetPackage} (${targetRole}).`, +}); + +// cwd must be restored +const origDir = process.cwd(); +afterAll(() => { + process.chdir(origDir); +}); +process.chdir(FIXTURE); + +const ruleTester = new RuleTester({ + parserOptions: { + sourceType: 'module', + ecmaVersion: 2021, + }, +}); + +ruleTester.run(RULE, rule, { + valid: [ + { + code: `import '@internal/inline'`, + filename: path.join(FIXTURE, 'packages/bar/src/index.ts'), + }, + ], + invalid: [ + { + code: `import '@internal/foo'`, + filename: path.join(FIXTURE, 'packages/bar/src/index.ts'), + errors: [ + ERR( + '@internal/bar', + 'frontend-plugin', + '@internal/foo', + 'frontend-plugin', + ), + ], + }, + ], +}); diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 4a451ce6fa..0d822bbd63 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -59,7 +59,6 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" } } diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts index 4d8e0fd0c0..92892b787b 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import yaml from 'yaml'; import { createGitlabGroupEnsureExistsAction } from './gitlabGroupEnsureExists'; import { examples } from './gitlabGroupEnsureExists.examples'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Groups: { @@ -50,15 +50,17 @@ describe('gitlab:group:ensureExists', () => { full_path: 'group1', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts index 3d703eb91f..8e52ee999e 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createGitlabGroupEnsureExistsAction } from './gitlabGroupEnsureExists'; import { getClient } from '../util'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Groups: { @@ -45,15 +45,17 @@ describe('gitlab:group:ensureExists', () => { jest.clearAllMocks(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts index 917ba61d91..858adb4b99 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createGitlabIssueAction } from './gitlabIssueCreate'; import { examples } from './gitlabIssueCreate.examples'; import yaml from 'yaml'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Issues: { @@ -46,15 +46,17 @@ describe('gitlab:issues:create', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'sample-token', - apiBaseUrl: 'https://gitlab.com/api/v1', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'sample-token', + apiBaseUrl: 'https://gitlab.com/api/v1', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts index d5aa2d1e17..e09bfaf249 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { IssueType } from '../commonGitlabConfig'; import { createGitlabIssueAction } from './gitlabIssueCreate'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Issues: { @@ -45,15 +45,17 @@ describe('gitlab:issues:create', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'myIntegrationsToken', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'myIntegrationsToken', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.test.ts index 7e50eac66c..fa53609c19 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.test.ts @@ -15,11 +15,11 @@ */ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { editGitlabIssueAction } from './gitlabIssueEdit'; import { examples } from './gitlabIssueEdit.examples'; import yaml from 'yaml'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Issues: { @@ -46,15 +46,17 @@ describe('gitlab:issue:edit', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'myIntegrationsToken', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'myIntegrationsToken', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.test.ts index 9604fe8fe2..a16236a509 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.test.ts @@ -15,10 +15,10 @@ */ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { IssueType } from '../commonGitlabConfig'; import { editGitlabIssueAction } from './gitlabIssueEdit'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { Issues: { @@ -45,15 +45,17 @@ describe('gitlab:issue:edit', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'myIntegrationsToken', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'myIntegrationsToken', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.test.ts index 53cbfb0b87..41535479fe 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createTriggerGitlabPipelineAction } from './gitlabPipelineTrigger'; import { examples } from './gitlabPipelineTrigger.examples'; import yaml from 'yaml'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { PipelineTriggerTokens: { @@ -49,15 +49,17 @@ describe('gitlab:pipeline:trigger', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'glpat-abcdef', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'glpat-abcdef', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts index 3803198ec8..89498c52f2 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createTriggerGitlabPipelineAction } from './gitlabPipelineTrigger'; +import { mockServices } from '@backstage/backend-test-utils'; const mockGitlabClient = { PipelineTriggerTokens: { @@ -47,15 +47,17 @@ describe('gitlab:pipeline:trigger', () => { jest.useRealTimers(); }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'glpat-abcdef', - apiBaseUrl: 'https://gitlab.com/api/v4', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'glpat-abcdef', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); diff --git a/yarn.lock b/yarn.lock index ab101b1be1..d3fc3ea273 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4598,7 +4598,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/eslint-plugin@workspace:^, @backstage/eslint-plugin@workspace:packages/eslint-plugin": +"@backstage/eslint-plugin@workspace:*, @backstage/eslint-plugin@workspace:^, @backstage/eslint-plugin@workspace:packages/eslint-plugin": version: 0.0.0-use.local resolution: "@backstage/eslint-plugin@workspace:packages/eslint-plugin" dependencies: @@ -7702,7 +7702,6 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" - "@backstage/core-app-api": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-scaffolder-node": "workspace:^" @@ -45419,6 +45418,7 @@ __metadata: "@backstage/create-app": "workspace:*" "@backstage/e2e-test-utils": "workspace:*" "@backstage/errors": "workspace:^" + "@backstage/eslint-plugin": "workspace:*" "@backstage/repo-tools": "workspace:*" "@changesets/cli": "npm:^2.14.0" "@manypkg/get-packages": "npm:^1.1.3" From 2788a08dad826cca465248c6a96a6e0d2a0149f6 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Fri, 13 Jun 2025 08:23:29 +0300 Subject: [PATCH 143/301] fix: tsc errors and rest of the tests Signed-off-by: Hellgren Heikki --- packages/eslint-plugin/lib/getPackages.js | 2 +- .../rules/no-mixed-plugin-imports.js | 23 ++-- .../src/no-undeclared-imports.test.ts | 19 +--- .../gitlabGroupEnsureExists.examples.test.ts | 100 ++++++++++-------- 4 files changed, 77 insertions(+), 67 deletions(-) diff --git a/packages/eslint-plugin/lib/getPackages.js b/packages/eslint-plugin/lib/getPackages.js index 3b46f41a54..e90536ff52 100644 --- a/packages/eslint-plugin/lib/getPackages.js +++ b/packages/eslint-plugin/lib/getPackages.js @@ -21,7 +21,7 @@ const manypkg = require('@manypkg/get-packages'); /** * @typedef ExtendedPackage - * @type {import('@manypkg/get-packages').Package & { packageJson: { exports?: Record, files?: Array, backstage?: { inline?: boolean } }}} packageJson + * @type {import('@manypkg/get-packages').Package & { packageJson: { exports?: Record, files?: Array, backstage?: { inline?: boolean, role?: string } }}} packageJson */ /** diff --git a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js index 90d3448b7a..5b7cb9de7d 100644 --- a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js +++ b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js @@ -20,6 +20,17 @@ const visitImports = require('../lib/visitImports'); const getPackages = require('../lib/getPackages'); const minimatch = require('minimatch'); +/** @typedef {import('../lib/getPackages.js').ExtendedPackage} ExtendedPackage */ + +/** + * @param {string} pattern + * @param {string} filePath + * @returns {boolean} + */ +const matchesPattern = (pattern, filePath) => { + return new minimatch.Minimatch(pattern).match(filePath); +}; + const roleRules = [ { sourceRole: ['frontend-plugin', 'web-library'], @@ -87,23 +98,22 @@ module.exports = { ? context.physicalFilename : context.filename; + /** @type {ExtendedPackage | undefined} */ const pkg = packages.byPath(filePath); if (!pkg) { return {}; } const options = context.options[0] || {}; + /** @type {string[]} */ const ignoreTargetPackages = options.excludedTargetPackages || []; + /** @type {string[]} */ const ignorePatterns = options.excludedFiles || [ '**/*.{test,spec}.[jt]s?(x)', '**/dev/index.[jt]s?(x)', ]; - if ( - ignorePatterns.some(pattern => - new minimatch.Minimatch(pattern).match(context.filename), - ) - ) { + if (ignorePatterns.some(pattern => matchesPattern(pattern, filePath))) { return {}; } @@ -112,8 +122,9 @@ module.exports = { return; } + /** @type {ExtendedPackage | undefined} */ const targetPackage = imp.package; - const targetName = targetPackage.packageJson.name; + const targetName = targetPackage?.packageJson.name; const sourceName = pkg.packageJson.name; if (sourceName === targetName) { return; diff --git a/packages/eslint-plugin/src/no-undeclared-imports.test.ts b/packages/eslint-plugin/src/no-undeclared-imports.test.ts index ee56c34926..7127d2f344 100644 --- a/packages/eslint-plugin/src/no-undeclared-imports.test.ts +++ b/packages/eslint-plugin/src/no-undeclared-imports.test.ts @@ -24,6 +24,7 @@ jest.mock('child_process', () => ({ const RULE = 'no-undeclared-imports'; const FIXTURE = joinPath(__dirname, '__fixtures__/monorepo'); +// const ERR_UNDECLARED = ( name: string, @@ -246,26 +247,14 @@ ruleTester.run(RULE, rule, { }, { code: `import 'react-dom'`, - output: `import 'directive:add-import:dependencies:react-dom'`, + output: `import 'directive:add-import:peerDependencies:react-dom'`, filename: joinPath(FIXTURE, 'packages/foo/src/index.ts'), errors: [ ERR_UNDECLARED( 'react-dom', - 'dependencies', + 'peerDependencies', joinPath('packages', 'foo'), - ), - ], - }, - { - code: `import 'react-dom'`, - output: `import 'directive:add-import:devDependencies:react-dom'`, - filename: joinPath(FIXTURE, 'packages/foo/src/index.test.ts'), - errors: [ - ERR_UNDECLARED( - 'react-dom', - 'devDependencies', - joinPath('packages', 'foo'), - '--dev', + '--peer', ), ], }, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts index 92892b787b..449c5980a4 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts @@ -93,15 +93,17 @@ describe('gitlab:group:ensureExists', () => { full_path: 'group1/group2', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); @@ -140,15 +142,17 @@ describe('gitlab:group:ensureExists', () => { full_path: 'group1/group2/group3', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); @@ -172,15 +176,17 @@ describe('gitlab:group:ensureExists', () => { }); it(`Should ${examples[3].description}`, async () => { - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); @@ -215,15 +221,17 @@ describe('gitlab:group:ensureExists', () => { full_path: 'group1/group2/group3', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); @@ -266,15 +274,17 @@ describe('gitlab:group:ensureExists', () => { full_path: 'group1/group2/group3/group4', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], + const config = mockServices.rootConfig({ + data: { + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + }, }, }); const integrations = ScmIntegrations.fromConfig(config); From e84e7c55bfa3b0d9c69390862c85f1c30f8fd0b8 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Fri, 13 Jun 2025 11:00:58 +0300 Subject: [PATCH 144/301] feat: add suggestions for fixes for mixed plugins rule Signed-off-by: Hellgren Heikki --- .eslintrc.js | 6 +- .../rules/no-mixed-plugin-imports.js | 55 +++++++++++++++++++ .../catalog-backend-module-logs/package.json | 1 + yarn.lock | 1 + 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 58e14ee2d1..787edae242 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,12 +24,10 @@ module.exports = { 'error', { // TODO: Fix these either by right role or by moving things to new packages + // TODO: Additionally remove the '**/*.{test,spec}.[jt]s?(x)' from `excludedFiles` in `no-mixed-plugin-imports` + // and fix the errors excludedTargetPackages: [ - '@backstage/test-utils', - '@backstage/config-loader', '@backstage/plugin-catalog', - '@backstage/plugin-permission-backend', - '@backstage/plugin-app-backend', '@backstage/plugin-techdocs', '@backstage/plugin-app', '@backstage/plugin-catalog-backend' diff --git a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js index 5b7cb9de7d..0ed4367ce2 100644 --- a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js +++ b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js @@ -64,11 +64,18 @@ module.exports = { messages: { forbidden: '{{sourcePackage}} ({{sourceRole}}) uses forbidden import from {{targetPackage}} ({{targetRole}}).', + useReactPlugin: + 'Use web library {{targetPackage}}-react or common library instead.', + useNodePlugin: + 'Use node library {{targetPackage}}-node or common library instead.', + removeImport: + 'Remove this import to avoid mixed plugin imports. Fix the code by refactoring it to use the correct plugin type.', }, docs: { description: 'Disallow mixed plugin imports.', url: 'https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-mixed-plugin-imports.md', }, + hasSuggestions: true, schema: [ { type: 'object', @@ -144,6 +151,53 @@ module.exports = { !ignoreTargetPackages.includes(targetName), ) ) { + const suggest = []; + + if ( + (sourceRole === 'frontend-plugin' || sourceRole === 'web-library') && + targetRole === 'frontend-plugin' + ) { + suggest.push({ + messageId: 'useReactPlugin', + data: { + targetPackage: targetName, + }, + /** @param {import('eslint').Rule.RuleFixer} fixer */ + fix(fixer) { + const source = context.sourceCode; + const nodeSource = source.getText(imp.node); + const newImport = nodeSource.replace(/'$/, "-react'"); + return fixer.replaceText(imp.node, newImport); + }, + }); + } else if ( + (sourceRole === 'backend-plugin' || + sourceRole === 'backend-plugin-module') && + targetRole === 'backend-plugin' + ) { + suggest.push({ + messageId: 'useNodePlugin', + data: { + targetPackage: targetName, + }, + /** @param {import('eslint').Rule.RuleFixer} fixer */ + fix(fixer) { + const source = context.sourceCode; + const nodeSource = source.getText(imp.node); + const newImport = nodeSource.replace(/-backend'$/, "-node'"); + return fixer.replaceText(imp.node, newImport); + }, + }); + } else { + suggest.push({ + messageId: 'removeImport', + /** @param {import('eslint').Rule.RuleFixer} _fixer */ + fix(_fixer) { + // Not a fixable case, just give a suggestion to remove the import + }, + }); + } + context.report({ node: node, messageId: 'forbidden', @@ -153,6 +207,7 @@ module.exports = { targetPackage: targetPackage.packageJson.name || imp.package.dir, targetRole, }, + suggest, }); } }); diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index 481ffba3ad..3fad09bcb1 100644 --- a/plugins/catalog-backend-module-logs/package.json +++ b/plugins/catalog-backend-module-logs/package.json @@ -35,6 +35,7 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-events-node": "workspace:^" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index d3fc3ea273..ebb1d05132 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5995,6 +5995,7 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-events-backend-test-utils": "workspace:^" "@backstage/plugin-events-node": "workspace:^" languageName: unknown From 0a254aa6a08c8ebbb09235fdb4942058c5c245b2 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Fri, 13 Jun 2025 11:14:38 +0300 Subject: [PATCH 145/301] chore: make mixed plugin imports warning instead error Signed-off-by: Hellgren Heikki --- .changeset/mean-pillows-enter.md | 3 +++ packages/eslint-plugin/index.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.changeset/mean-pillows-enter.md b/.changeset/mean-pillows-enter.md index 7775e2648f..500c9af486 100644 --- a/.changeset/mean-pillows-enter.md +++ b/.changeset/mean-pillows-enter.md @@ -10,3 +10,6 @@ the backstage architecture. This rule forces that: - No imports from frontend plugins to backend plugins or other frontend plugins. - No imports from backend plugins to frontend plugins or other backend plugins. - No imports from common plugins to frontend or backend plugins. + +The current recommended configuration is giving a warning for mixed imports. This is to be changed in +the future to an error so please adjust your workspace accordingly. diff --git a/packages/eslint-plugin/index.js b/packages/eslint-plugin/index.js index a1e38e4269..58b597a438 100644 --- a/packages/eslint-plugin/index.js +++ b/packages/eslint-plugin/index.js @@ -22,7 +22,7 @@ module.exports = { '@backstage/no-forbidden-package-imports': 'error', '@backstage/no-relative-monorepo-imports': 'error', '@backstage/no-undeclared-imports': 'error', - '@backstage/no-mixed-plugin-imports': 'error', + '@backstage/no-mixed-plugin-imports': 'warn', }, }, }, From 4d2438e19a65bfe5a0b7cc5136a957a3be7daf7c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 19:12:39 +0000 Subject: [PATCH 146/301] fix(deps): update dependency rate-limit-redis to v4.2.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7734f8a4ed..fbb716af0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -43427,11 +43427,11 @@ __metadata: linkType: hard "rate-limit-redis@npm:^4.2.0": - version: 4.2.0 - resolution: "rate-limit-redis@npm:4.2.0" + version: 4.2.1 + resolution: "rate-limit-redis@npm:4.2.1" peerDependencies: express-rate-limit: ">= 6" - checksum: 10/22adc67918ca906f613b45f9dcfd039f543d363921979d21ba56be5f3288c6e9973c9e4bb4ec59810fc6b3abb20defd572c102f607a8c3b4d273d5e09b63839f + checksum: 10/c36c50cfca992cbd14c97c08bb01c7d1d340d27b5fa1a2c1154275fec0b59c187eb6fb207da3d035c7c87e7629c92324cd53074f854dbb16548faf94f5a41351 languageName: node linkType: hard From fbdffe609375a87fb8f263b1b0b565c0317d8226 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 19:13:32 +0000 Subject: [PATCH 147/301] chore(deps): update dependency eslint to v9.29.0 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- storybook/yarn.lock | 659 ++++---------------------------------------- 1 file changed, 59 insertions(+), 600 deletions(-) diff --git a/storybook/yarn.lock b/storybook/yarn.lock index d7e83584d1..50401c732b 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -588,14 +588,14 @@ __metadata: languageName: node linkType: hard -"@eslint/config-array@npm:^0.20.0": - version: 0.20.0 - resolution: "@eslint/config-array@npm:0.20.0" +"@eslint/config-array@npm:^0.20.1": + version: 0.20.1 + resolution: "@eslint/config-array@npm:0.20.1" dependencies: "@eslint/object-schema": "npm:^2.1.6" debug: "npm:^4.3.1" minimatch: "npm:^3.1.2" - checksum: 10/9db7f6cbb5363f2f98ee4805ce09d1a95c4349e86f3f456f2c23a0849b7a6aa8d2be4c25e376ee182af062762e15a101844881c89b566eea0856c481ffcb2090 + checksum: 10/d72cc90f516c5730da5f37fa04aa8ba26ea0d92c7457ee77980902158f844f3483518272ccfe16f273c3313c3bfec8da713d4e51d3da49bdeccd34e919a2b903 languageName: node linkType: hard @@ -606,12 +606,21 @@ __metadata: languageName: node linkType: hard -"@eslint/core@npm:^0.13.0": - version: 0.13.0 - resolution: "@eslint/core@npm:0.13.0" +"@eslint/core@npm:^0.14.0": + version: 0.14.0 + resolution: "@eslint/core@npm:0.14.0" dependencies: "@types/json-schema": "npm:^7.0.15" - checksum: 10/737fd1c237405b62592e8daa4b7e25b45ab22108bfec65258cabd091d5717b7c9573acea1f27c4ee7198cefc5a0874f5caefe3d9636851227b1f12d28ef52cf2 + checksum: 10/d9b060cf97468150675ddf4fb3db55edaa32467e0adf9f80919a5bfd15d0835ad7765456f4397ec2d16b0a1bb702af63f6d4712f94194d34fea118231ae1e2db + languageName: node + linkType: hard + +"@eslint/core@npm:^0.15.0": + version: 0.15.0 + resolution: "@eslint/core@npm:0.15.0" + dependencies: + "@types/json-schema": "npm:^7.0.15" + checksum: 10/27c9cb5bdc5c9dead5b06f2b2a6a66d8bbe5e2e19397e2c5ff9ea582c9d4e4478bf1bc1bdd4eaec7bb3a0d6fa53f152e595acf637354776c14bb58c321ea5aa3 languageName: node linkType: hard @@ -632,10 +641,10 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:9.26.0": - version: 9.26.0 - resolution: "@eslint/js@npm:9.26.0" - checksum: 10/863d35df8f6675250bb5a917037e0f6833965437eba4c4649633fd0b55a93e8d727bcd36e9b5cc82047898ee9348cb40363e196f333914ae3a6bb36159495212 +"@eslint/js@npm:9.29.0": + version: 9.29.0 + resolution: "@eslint/js@npm:9.29.0" + checksum: 10/7f7fd586b35bd08537dd65a9bda764f474350c36b4ccbdd342462d1a26be28f7ee0ebd0611dd4762b69829674336ba04c281b9658aeccb3e6ab1d0fec7e6d08c languageName: node linkType: hard @@ -646,13 +655,13 @@ __metadata: languageName: node linkType: hard -"@eslint/plugin-kit@npm:^0.2.8": - version: 0.2.8 - resolution: "@eslint/plugin-kit@npm:0.2.8" +"@eslint/plugin-kit@npm:^0.3.1": + version: 0.3.2 + resolution: "@eslint/plugin-kit@npm:0.3.2" dependencies: - "@eslint/core": "npm:^0.13.0" + "@eslint/core": "npm:^0.15.0" levn: "npm:^0.4.1" - checksum: 10/2e7fe7a88ebdbbf805e9e7265347b7dcfb6bf50beec314def997572b2e8ae4a7b9504fb67b1698a70c348a0dd87251d1e9028292a96fd49b58cb5277d88bdea7 + checksum: 10/26ba99936f72ca124036fbc5ca93168713fab5984117109b1447642a93725fbb75aa457622683dc8797509e40294497d74b584caa26f285373bdde17ceba8eac languageName: node linkType: hard @@ -779,24 +788,6 @@ __metadata: languageName: node linkType: hard -"@modelcontextprotocol/sdk@npm:^1.8.0": - version: 1.11.0 - resolution: "@modelcontextprotocol/sdk@npm:1.11.0" - dependencies: - content-type: "npm:^1.0.5" - cors: "npm:^2.8.5" - cross-spawn: "npm:^7.0.3" - eventsource: "npm:^3.0.2" - express: "npm:^5.0.1" - express-rate-limit: "npm:^7.5.0" - pkce-challenge: "npm:^5.0.0" - raw-body: "npm:^3.0.0" - zod: "npm:^3.23.8" - zod-to-json-schema: "npm:^3.24.1" - checksum: 10/527413fd2b18f75e031cda7f73a662098f3c5f1224b9c6b0b903d5a1f79e23e23a4f4b8e6971bac7eb46a74ed65ae05e8e548f7b7a3f7f6d179c3f6d10825fbc - languageName: node - linkType: hard - "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -1884,16 +1875,6 @@ __metadata: languageName: node linkType: hard -"accepts@npm:^2.0.0": - version: 2.0.0 - resolution: "accepts@npm:2.0.0" - dependencies: - mime-types: "npm:^3.0.0" - negotiator: "npm:^1.0.0" - checksum: 10/ea1343992b40b2bfb3a3113fa9c3c2f918ba0f9197ae565c48d3f84d44b174f6b1d5cd9989decd7655963eb03a272abc36968cc439c2907f999bd5ef8653d5a7 - languageName: node - linkType: hard - "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -1903,12 +1884,12 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.12.1, acorn@npm:^8.14.0": - version: 8.14.0 - resolution: "acorn@npm:8.14.0" +"acorn@npm:^8.12.1, acorn@npm:^8.15.0": + version: 8.15.0 + resolution: "acorn@npm:8.15.0" bin: acorn: bin/acorn - checksum: 10/6df29c35556782ca9e632db461a7f97947772c6c1d5438a81f0c873a3da3a792487e83e404d1c6c25f70513e91aa18745f6eafb1fcc3a43ecd1920b21dd173d2 + checksum: 10/77f2de5051a631cf1729c090e5759148459cdb76b5f5c70f890503d629cf5052357b0ce783c0f976dd8a93c5150f59f6d18df1def3f502396a20f81282482fa4 languageName: node linkType: hard @@ -2092,23 +2073,6 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:^2.2.0": - version: 2.2.0 - resolution: "body-parser@npm:2.2.0" - dependencies: - bytes: "npm:^3.1.2" - content-type: "npm:^1.0.5" - debug: "npm:^4.4.0" - http-errors: "npm:^2.0.0" - iconv-lite: "npm:^0.6.3" - on-finished: "npm:^2.4.1" - qs: "npm:^6.14.0" - raw-body: "npm:^3.0.0" - type-is: "npm:^2.0.0" - checksum: 10/e9d844b036bd15970df00a16f373c7ed28e1ef870974a0a1d4d6ef60d70e01087cc20a0dbb2081c49a88e3c08ce1d87caf1e2898c615dffa193f63e8faa8a84e - languageName: node - linkType: hard - "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -2158,13 +2122,6 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2, bytes@npm:^3.1.2": - version: 3.1.2 - resolution: "bytes@npm:3.1.2" - checksum: 10/a10abf2ba70c784471d6b4f58778c0beeb2b5d405148e66affa91f23a9f13d07603d0a0354667310ae1d6dc141474ffd44e2a074be0f6e2254edb8fc21445388 - languageName: node - linkType: hard - "cacache@npm:^18.0.0": version: 18.0.4 resolution: "cacache@npm:18.0.4" @@ -2208,16 +2165,6 @@ __metadata: languageName: node linkType: hard -"call-bound@npm:^1.0.2": - version: 1.0.4 - resolution: "call-bound@npm:1.0.4" - dependencies: - call-bind-apply-helpers: "npm:^1.0.2" - get-intrinsic: "npm:^1.3.0" - checksum: 10/ef2b96e126ec0e58a7ff694db43f4d0d44f80e641370c21549ed911fecbdbc2df3ebc9bddad918d6bbdefeafb60bb3337902006d5176d72bcd2da74820991af7 - languageName: node - linkType: hard - "callsites@npm:^3.0.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" @@ -2355,22 +2302,6 @@ __metadata: languageName: node linkType: hard -"content-disposition@npm:^1.0.0": - version: 1.0.0 - resolution: "content-disposition@npm:1.0.0" - dependencies: - safe-buffer: "npm:5.2.1" - checksum: 10/0dcc1a2d7874526b0072df3011b134857b49d97a3bc135bb464a299525d4972de6f5f464fd64da6c4d8406d26a1ffb976f62afaffef7723b1021a44498d10e08 - languageName: node - linkType: hard - -"content-type@npm:^1.0.5": - version: 1.0.5 - resolution: "content-type@npm:1.0.5" - checksum: 10/585847d98dc7fb8035c02ae2cb76c7a9bd7b25f84c447e5ed55c45c2175e83617c8813871b4ee22f368126af6b2b167df655829007b21aa10302873ea9c62662 - languageName: node - linkType: hard - "convert-source-map@npm:^2.0.0": version: 2.0.0 resolution: "convert-source-map@npm:2.0.0" @@ -2378,31 +2309,7 @@ __metadata: languageName: node linkType: hard -"cookie-signature@npm:^1.2.1": - version: 1.2.2 - resolution: "cookie-signature@npm:1.2.2" - checksum: 10/be44a3c9a56f3771aea3a8bd8ad8f0a8e2679bcb967478267f41a510b4eb5ec55085386ba79c706c4ac21605ca76f4251973444b90283e0eb3eeafe8a92c7708 - languageName: node - linkType: hard - -"cookie@npm:^0.7.1": - version: 0.7.2 - resolution: "cookie@npm:0.7.2" - checksum: 10/24b286c556420d4ba4e9bc09120c9d3db7d28ace2bd0f8ccee82422ce42322f73c8312441271e5eefafbead725980e5996cc02766dbb89a90ac7f5636ede608f - languageName: node - linkType: hard - -"cors@npm:^2.8.5": - version: 2.8.5 - resolution: "cors@npm:2.8.5" - dependencies: - object-assign: "npm:^4" - vary: "npm:^1" - checksum: 10/66e88e08edee7cbce9d92b4d28a2028c88772a4c73e02f143ed8ca76789f9b59444eed6b1c167139e76fa662998c151322720093ba229f9941365ada5a6fc2c6 - languageName: node - linkType: hard - -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -2427,7 +2334,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4": version: 4.4.0 resolution: "debug@npm:4.4.0" dependencies: @@ -2471,13 +2378,6 @@ __metadata: languageName: node linkType: hard -"depd@npm:2.0.0, depd@npm:^2.0.0": - version: 2.0.0 - resolution: "depd@npm:2.0.0" - checksum: 10/c0c8ff36079ce5ada64f46cc9d6fd47ebcf38241105b6e0c98f412e8ad91f084bcf906ff644cc3a4bd876ca27a62accb8b0fff72ea6ed1a414b89d8506f4a5ca - languageName: node - linkType: hard - "dequal@npm:^2.0.2, dequal@npm:^2.0.3": version: 2.0.3 resolution: "dequal@npm:2.0.3" @@ -2535,13 +2435,6 @@ __metadata: languageName: node linkType: hard -"ee-first@npm:1.1.1": - version: 1.1.1 - resolution: "ee-first@npm:1.1.1" - checksum: 10/1b4cac778d64ce3b582a7e26b218afe07e207a0f9bfe13cc7395a6d307849cfe361e65033c3251e00c27dd060cab43014c2d6b2647676135e18b77d2d05b3f4f - languageName: node - linkType: hard - "electron-to-chromium@npm:^1.5.28": version: 1.5.41 resolution: "electron-to-chromium@npm:1.5.41" @@ -2563,13 +2456,6 @@ __metadata: languageName: node linkType: hard -"encodeurl@npm:^2.0.0": - version: 2.0.0 - resolution: "encodeurl@npm:2.0.0" - checksum: 10/abf5cd51b78082cf8af7be6785813c33b6df2068ce5191a40ca8b1afe6a86f9230af9a9ce694a5ce4665955e5c1120871826df9c128a642e09c58d592e2807fe - languageName: node - linkType: hard - "encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -2812,13 +2698,6 @@ __metadata: languageName: node linkType: hard -"escape-html@npm:^1.0.3": - version: 1.0.3 - resolution: "escape-html@npm:1.0.3" - checksum: 10/6213ca9ae00d0ab8bccb6d8d4e0a98e76237b2410302cf7df70aaa6591d509a2a37ce8998008cbecae8fc8ffaadf3fb0229535e6a145f3ce0b211d060decbb24 - languageName: node - linkType: hard - "escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" @@ -2856,13 +2735,13 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^8.3.0": - version: 8.3.0 - resolution: "eslint-scope@npm:8.3.0" +"eslint-scope@npm:^8.4.0": + version: 8.4.0 + resolution: "eslint-scope@npm:8.4.0" dependencies: esrecurse: "npm:^4.3.0" estraverse: "npm:^5.2.0" - checksum: 10/ee1ff009e949423639a8b53453c0cb189967d9142c5d94dc3752bed9880140a0760007148ac6b0bd03557d70ede9cd7c3b1e66f9a7f3427b2dbeca2a5be22c91 + checksum: 10/e8e611701f65375e034c62123946e628894f0b54aa8cb11abe224816389abe5cd74cf16b62b72baa36504f22d1a958b9b8b0169b82397fe2e7997674c0d09b06 languageName: node linkType: hard @@ -2873,29 +2752,28 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^4.2.0": - version: 4.2.0 - resolution: "eslint-visitor-keys@npm:4.2.0" - checksum: 10/9651b3356b01760e586b4c631c5268c0e1a85236e3292bf754f0472f465bf9a856c0ddc261fceace155334118c0151778effafbab981413dbf9288349343fa25 +"eslint-visitor-keys@npm:^4.2.0, eslint-visitor-keys@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-visitor-keys@npm:4.2.1" + checksum: 10/3ee00fc6a7002d4b0ffd9dc99e13a6a7882c557329e6c25ab254220d71e5c9c4f89dca4695352949ea678eb1f3ba912a18ef8aac0a7fe094196fd92f441bfce2 languageName: node linkType: hard "eslint@npm:^9.11.1": - version: 9.26.0 - resolution: "eslint@npm:9.26.0" + version: 9.29.0 + resolution: "eslint@npm:9.29.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.2.0" "@eslint-community/regexpp": "npm:^4.12.1" - "@eslint/config-array": "npm:^0.20.0" + "@eslint/config-array": "npm:^0.20.1" "@eslint/config-helpers": "npm:^0.2.1" - "@eslint/core": "npm:^0.13.0" + "@eslint/core": "npm:^0.14.0" "@eslint/eslintrc": "npm:^3.3.1" - "@eslint/js": "npm:9.26.0" - "@eslint/plugin-kit": "npm:^0.2.8" + "@eslint/js": "npm:9.29.0" + "@eslint/plugin-kit": "npm:^0.3.1" "@humanfs/node": "npm:^0.16.6" "@humanwhocodes/module-importer": "npm:^1.0.1" "@humanwhocodes/retry": "npm:^0.4.2" - "@modelcontextprotocol/sdk": "npm:^1.8.0" "@types/estree": "npm:^1.0.6" "@types/json-schema": "npm:^7.0.15" ajv: "npm:^6.12.4" @@ -2903,9 +2781,9 @@ __metadata: cross-spawn: "npm:^7.0.6" debug: "npm:^4.3.2" escape-string-regexp: "npm:^4.0.0" - eslint-scope: "npm:^8.3.0" - eslint-visitor-keys: "npm:^4.2.0" - espree: "npm:^10.3.0" + eslint-scope: "npm:^8.4.0" + eslint-visitor-keys: "npm:^4.2.1" + espree: "npm:^10.4.0" esquery: "npm:^1.5.0" esutils: "npm:^2.0.2" fast-deep-equal: "npm:^3.1.3" @@ -2920,7 +2798,6 @@ __metadata: minimatch: "npm:^3.1.2" natural-compare: "npm:^1.4.0" optionator: "npm:^0.9.3" - zod: "npm:^3.24.2" peerDependencies: jiti: "*" peerDependenciesMeta: @@ -2928,18 +2805,18 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10/b87092cb7e87f1d0963475c1a1e15e551842ea122925cf13231e742fae565bf3582029a5b0b4aecf793f25c26ee0be3ee1f32190bc361e0c3f3633b9cbace948 + checksum: 10/be0c8e123207c9d653fb75ddc610b85dfbf295a2bfa1cbecc78f191dcba9c421525b5befd5d499ce561eca607c9c33f455e4fff0b1c2d4202c2896dafe95094a languageName: node linkType: hard -"espree@npm:^10.0.1, espree@npm:^10.3.0": - version: 10.3.0 - resolution: "espree@npm:10.3.0" +"espree@npm:^10.0.1, espree@npm:^10.4.0": + version: 10.4.0 + resolution: "espree@npm:10.4.0" dependencies: - acorn: "npm:^8.14.0" + acorn: "npm:^8.15.0" acorn-jsx: "npm:^5.3.2" - eslint-visitor-keys: "npm:^4.2.0" - checksum: 10/3412d44d4204c9e29d6b5dd0277400cfa0cd68495dc09eae1b9ce79d0c8985c1c5cc09cb9ba32a1cd963f48a49b0c46bdb7736afe395a300aa6bb1c0d86837e8 + eslint-visitor-keys: "npm:^4.2.1" + checksum: 10/9b355b32dbd1cc9f57121d5ee3be258fab87ebeb7c83fc6c02e5af1a74fc8c5ba79fe8c663e69ea112c3e84a1b95e6a2067ac4443ee7813bb85ac7581acb8bf9 languageName: node linkType: hard @@ -3008,29 +2885,6 @@ __metadata: languageName: node linkType: hard -"etag@npm:^1.8.1": - version: 1.8.1 - resolution: "etag@npm:1.8.1" - checksum: 10/571aeb3dbe0f2bbd4e4fadbdb44f325fc75335cd5f6f6b6a091e6a06a9f25ed5392f0863c5442acb0646787446e816f13cbfc6edce5b07658541dff573cab1ff - languageName: node - linkType: hard - -"eventsource-parser@npm:^3.0.1": - version: 3.0.1 - resolution: "eventsource-parser@npm:3.0.1" - checksum: 10/2730c54c3cb47d55d2967f2ece843f9fc95d8a11c2fef6fece8d17d9080193cbe3cd9ac7b04a325977f63cbf8c1664fdd0512dec1aec601666a5c5bd8564b61f - languageName: node - linkType: hard - -"eventsource@npm:^3.0.2": - version: 3.0.6 - resolution: "eventsource@npm:3.0.6" - dependencies: - eventsource-parser: "npm:^3.0.1" - checksum: 10/ac08c7d1b21e454c7685693fe4ace53fc0b84f3cf752699a556876f2a7f33b7a12972ae33d1c407fb920d6d4aed10de52fdf0dd01902ccdf45cd5da8d55e7f88 - languageName: node - linkType: hard - "exponential-backoff@npm:^3.1.1": version: 3.1.1 resolution: "exponential-backoff@npm:3.1.1" @@ -3038,50 +2892,6 @@ __metadata: languageName: node linkType: hard -"express-rate-limit@npm:^7.5.0": - version: 7.5.0 - resolution: "express-rate-limit@npm:7.5.0" - peerDependencies: - express: ^4.11 || 5 || ^5.0.0-beta.1 - checksum: 10/eff34c83bf586789933a332a339b66649e2cca95c8e977d193aa8bead577d3182ac9f0e9c26f39389287539b8038890ff023f910b54ebb506a26a2ce135b92ca - languageName: node - linkType: hard - -"express@npm:^5.0.1": - version: 5.1.0 - resolution: "express@npm:5.1.0" - dependencies: - accepts: "npm:^2.0.0" - body-parser: "npm:^2.2.0" - content-disposition: "npm:^1.0.0" - content-type: "npm:^1.0.5" - cookie: "npm:^0.7.1" - cookie-signature: "npm:^1.2.1" - debug: "npm:^4.4.0" - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - etag: "npm:^1.8.1" - finalhandler: "npm:^2.1.0" - fresh: "npm:^2.0.0" - http-errors: "npm:^2.0.0" - merge-descriptors: "npm:^2.0.0" - mime-types: "npm:^3.0.0" - on-finished: "npm:^2.4.1" - once: "npm:^1.4.0" - parseurl: "npm:^1.3.3" - proxy-addr: "npm:^2.0.7" - qs: "npm:^6.14.0" - range-parser: "npm:^1.2.1" - router: "npm:^2.2.0" - send: "npm:^1.1.0" - serve-static: "npm:^2.2.0" - statuses: "npm:^2.0.1" - type-is: "npm:^2.0.1" - vary: "npm:^1.1.2" - checksum: 10/6dba00bbdf308f43a84ed3f07a7e9870d5208f2a0b8f60f39459dda089750379747819863fad250849d3c9163833f33f94ce69d73938df31e0c5a430800d7e56 - languageName: node - linkType: hard - "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -3150,20 +2960,6 @@ __metadata: languageName: node linkType: hard -"finalhandler@npm:^2.1.0": - version: 2.1.0 - resolution: "finalhandler@npm:2.1.0" - dependencies: - debug: "npm:^4.4.0" - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - on-finished: "npm:^2.4.1" - parseurl: "npm:^1.3.3" - statuses: "npm:^2.0.1" - checksum: 10/b2bd68c310e2c463df0ab747ab05f8defbc540b8c3f2442f86e7d084ac8acbc31f8cae079931b7f5a406521501941e3395e963de848a0aaf45dd414adeb5ff4e - languageName: node - linkType: hard - "find-up@npm:^5.0.0": version: 5.0.0 resolution: "find-up@npm:5.0.0" @@ -3210,20 +3006,6 @@ __metadata: languageName: node linkType: hard -"forwarded@npm:0.2.0": - version: 0.2.0 - resolution: "forwarded@npm:0.2.0" - checksum: 10/29ba9fd347117144e97cbb8852baae5e8b2acb7d1b591ef85695ed96f5b933b1804a7fac4a15dd09ca7ac7d0cdc104410e8102aae2dd3faa570a797ba07adb81 - languageName: node - linkType: hard - -"fresh@npm:^2.0.0": - version: 2.0.0 - resolution: "fresh@npm:2.0.0" - checksum: 10/44e1468488363074641991c1340d2a10c5a6f6d7c353d89fd161c49d120c58ebf9890720f7584f509058385836e3ce50ddb60e9f017315a4ba8c6c3461813bfc - languageName: node - linkType: hard - "fs-minipass@npm:^2.0.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" @@ -3275,7 +3057,7 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.3.0": +"get-intrinsic@npm:^1.2.4": version: 1.3.0 resolution: "get-intrinsic@npm:1.3.0" dependencies: @@ -3448,19 +3230,6 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:2.0.0, http-errors@npm:^2.0.0": - version: 2.0.0 - resolution: "http-errors@npm:2.0.0" - dependencies: - depd: "npm:2.0.0" - inherits: "npm:2.0.4" - setprototypeof: "npm:1.2.0" - statuses: "npm:2.0.1" - toidentifier: "npm:1.0.1" - checksum: 10/0e7f76ee8ff8a33e58a3281a469815b893c41357378f408be8f6d4aa7d1efafb0da064625518e7078381b6a92325949b119dc38fcb30bdbc4e3a35f78c44c439 - languageName: node - linkType: hard - "http-proxy-agent@npm:^7.0.0": version: 7.0.2 resolution: "http-proxy-agent@npm:7.0.2" @@ -3481,7 +3250,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": +"iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" dependencies: @@ -3521,7 +3290,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2.0.4, inherits@npm:^2.0.3": +"inherits@npm:^2.0.3": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 @@ -3538,13 +3307,6 @@ __metadata: languageName: node linkType: hard -"ipaddr.js@npm:1.9.1": - version: 1.9.1 - resolution: "ipaddr.js@npm:1.9.1" - checksum: 10/864d0cced0c0832700e9621913a6429ccdc67f37c1bd78fb8c6789fff35c9d167cb329134acad2290497a53336813ab4798d2794fd675d5eb33b5fdf0982b9ca - languageName: node - linkType: hard - "is-arguments@npm:^1.0.4": version: 1.1.1 resolution: "is-arguments@npm:1.1.1" @@ -3626,13 +3388,6 @@ __metadata: languageName: node linkType: hard -"is-promise@npm:^4.0.0": - version: 4.0.0 - resolution: "is-promise@npm:4.0.0" - checksum: 10/0b46517ad47b00b6358fd6553c83ec1f6ba9acd7ffb3d30a0bf519c5c69e7147c132430452351b8a9fc198f8dd6c4f76f8e6f5a7f100f8c77d57d9e0f4261a8a - languageName: node - linkType: hard - "is-typed-array@npm:^1.1.3": version: 1.1.13 resolution: "is-typed-array@npm:1.1.13" @@ -3899,13 +3654,6 @@ __metadata: languageName: node linkType: hard -"media-typer@npm:^1.1.0": - version: 1.1.0 - resolution: "media-typer@npm:1.1.0" - checksum: 10/a58dd60804df73c672942a7253ccc06815612326dc1c0827984b1a21704466d7cde351394f47649e56cf7415e6ee2e26e000e81b51b3eebb5a93540e8bf93cbd - languageName: node - linkType: hard - "memoizerific@npm:^1.11.3": version: 1.11.3 resolution: "memoizerific@npm:1.11.3" @@ -3915,13 +3663,6 @@ __metadata: languageName: node linkType: hard -"merge-descriptors@npm:^2.0.0": - version: 2.0.0 - resolution: "merge-descriptors@npm:2.0.0" - checksum: 10/e383332e700a94682d0125a36c8be761142a1320fc9feeb18e6e36647c9edf064271645f5669b2c21cf352116e561914fd8aa831b651f34db15ef4038c86696a - languageName: node - linkType: hard - "merge2@npm:^1.3.0, merge2@npm:^1.4.1": version: 1.4.1 resolution: "merge2@npm:1.4.1" @@ -3939,22 +3680,6 @@ __metadata: languageName: node linkType: hard -"mime-db@npm:^1.54.0": - version: 1.54.0 - resolution: "mime-db@npm:1.54.0" - checksum: 10/9e7834be3d66ae7f10eaa69215732c6d389692b194f876198dca79b2b90cbf96688d9d5d05ef7987b20f749b769b11c01766564264ea5f919c88b32a29011311 - languageName: node - linkType: hard - -"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": - version: 3.0.1 - resolution: "mime-types@npm:3.0.1" - dependencies: - mime-db: "npm:^1.54.0" - checksum: 10/fa1d3a928363723a8046c346d87bf85d35014dae4285ad70a3ff92bd35957992b3094f8417973cfe677330916c6ef30885109624f1fb3b1e61a78af509dba120 - languageName: node - linkType: hard - "min-indent@npm:^1.0.0, min-indent@npm:^1.0.1": version: 1.0.1 resolution: "min-indent@npm:1.0.1" @@ -4117,13 +3842,6 @@ __metadata: languageName: node linkType: hard -"negotiator@npm:^1.0.0": - version: 1.0.0 - resolution: "negotiator@npm:1.0.0" - checksum: 10/b5734e87295324fabf868e36fb97c84b7d7f3156ec5f4ee5bf6e488079c11054f818290fc33804cef7b1ee21f55eeb14caea83e7dafae6492a409b3e573153e5 - languageName: node - linkType: hard - "node-gyp@npm:latest": version: 10.2.0 resolution: "node-gyp@npm:10.2.0" @@ -4162,38 +3880,6 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4": - version: 4.1.1 - resolution: "object-assign@npm:4.1.1" - checksum: 10/fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f - languageName: node - linkType: hard - -"object-inspect@npm:^1.13.3": - version: 1.13.4 - resolution: "object-inspect@npm:1.13.4" - checksum: 10/aa13b1190ad3e366f6c83ad8a16ed37a19ed57d267385aa4bfdccda833d7b90465c057ff6c55d035a6b2e52c1a2295582b294217a0a3a1ae7abdd6877ef781fb - languageName: node - linkType: hard - -"on-finished@npm:^2.4.1": - version: 2.4.1 - resolution: "on-finished@npm:2.4.1" - dependencies: - ee-first: "npm:1.1.1" - checksum: 10/8e81472c5028125c8c39044ac4ab8ba51a7cdc19a9fbd4710f5d524a74c6d8c9ded4dd0eed83f28d3d33ac1d7a6a439ba948ccb765ac6ce87f30450a26bfe2ea - languageName: node - linkType: hard - -"once@npm:^1.4.0": - version: 1.4.0 - resolution: "once@npm:1.4.0" - dependencies: - wrappy: "npm:1" - checksum: 10/cd0a88501333edd640d95f0d2700fbde6bff20b3d4d9bdc521bdd31af0656b5706570d6c6afe532045a20bb8dc0849f8332d6f2a416e0ba6d3d3b98806c7db68 - languageName: node - linkType: hard - "open@npm:^8.0.4": version: 8.4.2 resolution: "open@npm:8.4.2" @@ -4262,13 +3948,6 @@ __metadata: languageName: node linkType: hard -"parseurl@npm:^1.3.3": - version: 1.3.3 - resolution: "parseurl@npm:1.3.3" - checksum: 10/407cee8e0a3a4c5cd472559bca8b6a45b82c124e9a4703302326e9ab60fc1081442ada4e02628efef1eb16197ddc7f8822f5a91fd7d7c86b51f530aedb17dfa2 - languageName: node - linkType: hard - "path-exists@npm:^4.0.0": version: 4.0.0 resolution: "path-exists@npm:4.0.0" @@ -4300,13 +3979,6 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:^8.0.0": - version: 8.2.0 - resolution: "path-to-regexp@npm:8.2.0" - checksum: 10/23378276a172b8ba5f5fb824475d1818ca5ccee7bbdb4674701616470f23a14e536c1db11da9c9e6d82b82c556a817bbf4eee6e41b9ed20090ef9427cbb38e13 - languageName: node - linkType: hard - "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -4335,13 +4007,6 @@ __metadata: languageName: node linkType: hard -"pkce-challenge@npm:^5.0.0": - version: 5.0.0 - resolution: "pkce-challenge@npm:5.0.0" - checksum: 10/e60c06a0e0481cb82f80072053d5c479a7490758541c4226460450285dd5d72a995c44b3c553731ca7c2f64cc34b35f1d2e5f9de08d276b59899298f9efe1ddf - languageName: node - linkType: hard - "polished@npm:^4.2.2": version: 4.3.1 resolution: "polished@npm:4.3.1" @@ -4420,16 +4085,6 @@ __metadata: languageName: node linkType: hard -"proxy-addr@npm:^2.0.7": - version: 2.0.7 - resolution: "proxy-addr@npm:2.0.7" - dependencies: - forwarded: "npm:0.2.0" - ipaddr.js: "npm:1.9.1" - checksum: 10/f24a0c80af0e75d31e3451398670d73406ec642914da11a2965b80b1898ca6f66a0e3e091a11a4327079b2b268795f6fa06691923fef91887215c3d0e8ea3f68 - languageName: node - linkType: hard - "punycode@npm:^2.1.0": version: 2.3.1 resolution: "punycode@npm:2.3.1" @@ -4437,15 +4092,6 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.14.0": - version: 6.14.0 - resolution: "qs@npm:6.14.0" - dependencies: - side-channel: "npm:^1.1.0" - checksum: 10/a60e49bbd51c935a8a4759e7505677b122e23bf392d6535b8fc31c1e447acba2c901235ecb192764013cd2781723dc1f61978b5fdd93cc31d7043d31cdc01974 - languageName: node - linkType: hard - "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" @@ -4453,25 +4099,6 @@ __metadata: languageName: node linkType: hard -"range-parser@npm:^1.2.1": - version: 1.2.1 - resolution: "range-parser@npm:1.2.1" - checksum: 10/ce21ef2a2dd40506893157970dc76e835c78cf56437e26e19189c48d5291e7279314477b06ac38abd6a401b661a6840f7b03bd0b1249da9b691deeaa15872c26 - languageName: node - linkType: hard - -"raw-body@npm:^3.0.0": - version: 3.0.0 - resolution: "raw-body@npm:3.0.0" - dependencies: - bytes: "npm:3.1.2" - http-errors: "npm:2.0.0" - iconv-lite: "npm:0.6.3" - unpipe: "npm:1.0.0" - checksum: 10/2443429bbb2f9ae5c50d3d2a6c342533dfbde6b3173740b70fa0302b30914ff400c6d31a46b3ceacbe7d0925dc07d4413928278b494b04a65736fc17ca33e30c - languageName: node - linkType: hard - "react-confetti@npm:^6.1.0": version: 6.1.0 resolution: "react-confetti@npm:6.1.0" @@ -4720,19 +4347,6 @@ __metadata: languageName: node linkType: hard -"router@npm:^2.2.0": - version: 2.2.0 - resolution: "router@npm:2.2.0" - dependencies: - debug: "npm:^4.4.0" - depd: "npm:^2.0.0" - is-promise: "npm:^4.0.0" - parseurl: "npm:^1.3.3" - path-to-regexp: "npm:^8.0.0" - checksum: 10/8949bd1d3da5403cc024e2989fee58d7fda0f3ffe9f2dc5b8a192f295f400b3cde307b0b554f7d44851077640f36962ca469a766b3d57410d7d96245a7ba6c91 - languageName: node - linkType: hard - "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -4742,13 +4356,6 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.2.1": - version: 5.2.1 - resolution: "safe-buffer@npm:5.2.1" - checksum: 10/32872cd0ff68a3ddade7a7617b8f4c2ae8764d8b7d884c651b74457967a9e0e886267d3ecc781220629c44a865167b61c375d2da6c720c840ecd73f45d5d9451 - languageName: node - linkType: hard - "safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -4790,37 +4397,6 @@ __metadata: languageName: node linkType: hard -"send@npm:^1.1.0, send@npm:^1.2.0": - version: 1.2.0 - resolution: "send@npm:1.2.0" - dependencies: - debug: "npm:^4.3.5" - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - etag: "npm:^1.8.1" - fresh: "npm:^2.0.0" - http-errors: "npm:^2.0.0" - mime-types: "npm:^3.0.1" - ms: "npm:^2.1.3" - on-finished: "npm:^2.4.1" - range-parser: "npm:^1.2.1" - statuses: "npm:^2.0.1" - checksum: 10/9fa3b1a3b9a06b7b4ab00c25e8228326d9665a9745753a34d1ffab8ac63c7c206727331d1dc5be73647f1b658d259a1aa8e275b0e0eee51349370af02e9da506 - languageName: node - linkType: hard - -"serve-static@npm:^2.2.0": - version: 2.2.0 - resolution: "serve-static@npm:2.2.0" - dependencies: - encodeurl: "npm:^2.0.0" - escape-html: "npm:^1.0.3" - parseurl: "npm:^1.3.3" - send: "npm:^1.2.0" - checksum: 10/9f1a900738c5bb02258275ce3bd1273379c4c3072b622e15d44e8f47d89a1ba2d639ec2d63b11c263ca936096b40758acb7a0d989cd6989018a65a12f9433ada - languageName: node - linkType: hard - "set-function-length@npm:^1.2.1": version: 1.2.2 resolution: "set-function-length@npm:1.2.2" @@ -4835,13 +4411,6 @@ __metadata: languageName: node linkType: hard -"setprototypeof@npm:1.2.0": - version: 1.2.0 - resolution: "setprototypeof@npm:1.2.0" - checksum: 10/fde1630422502fbbc19e6844346778f99d449986b2f9cdcceb8326730d2f3d9964dbcb03c02aaadaefffecd0f2c063315ebea8b3ad895914bf1afc1747fc172e - languageName: node - linkType: hard - "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -4858,54 +4427,6 @@ __metadata: languageName: node linkType: hard -"side-channel-list@npm:^1.0.0": - version: 1.0.0 - resolution: "side-channel-list@npm:1.0.0" - dependencies: - es-errors: "npm:^1.3.0" - object-inspect: "npm:^1.13.3" - checksum: 10/603b928997abd21c5a5f02ae6b9cc36b72e3176ad6827fab0417ead74580cc4fb4d5c7d0a8a2ff4ead34d0f9e35701ed7a41853dac8a6d1a664fcce1a044f86f - languageName: node - linkType: hard - -"side-channel-map@npm:^1.0.1": - version: 1.0.1 - resolution: "side-channel-map@npm:1.0.1" - dependencies: - call-bound: "npm:^1.0.2" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.5" - object-inspect: "npm:^1.13.3" - checksum: 10/5771861f77feefe44f6195ed077a9e4f389acc188f895f570d56445e251b861754b547ea9ef73ecee4e01fdada6568bfe9020d2ec2dfc5571e9fa1bbc4a10615 - languageName: node - linkType: hard - -"side-channel-weakmap@npm:^1.0.2": - version: 1.0.2 - resolution: "side-channel-weakmap@npm:1.0.2" - dependencies: - call-bound: "npm:^1.0.2" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.5" - object-inspect: "npm:^1.13.3" - side-channel-map: "npm:^1.0.1" - checksum: 10/a815c89bc78c5723c714ea1a77c938377ea710af20d4fb886d362b0d1f8ac73a17816a5f6640f354017d7e292a43da9c5e876c22145bac00b76cfb3468001736 - languageName: node - linkType: hard - -"side-channel@npm:^1.1.0": - version: 1.1.0 - resolution: "side-channel@npm:1.1.0" - dependencies: - es-errors: "npm:^1.3.0" - object-inspect: "npm:^1.13.3" - side-channel-list: "npm:^1.0.0" - side-channel-map: "npm:^1.0.1" - side-channel-weakmap: "npm:^1.0.2" - checksum: 10/7d53b9db292c6262f326b6ff3bc1611db84ece36c2c7dc0e937954c13c73185b0406c56589e2bb8d071d6fee468e14c39fb5d203ee39be66b7b8174f179afaba - languageName: node - linkType: hard - "signal-exit@npm:^4.0.1": version: 4.1.0 resolution: "signal-exit@npm:4.1.0" @@ -4978,13 +4499,6 @@ __metadata: languageName: node linkType: hard -"statuses@npm:2.0.1, statuses@npm:^2.0.1": - version: 2.0.1 - resolution: "statuses@npm:2.0.1" - checksum: 10/18c7623fdb8f646fb213ca4051be4df7efb3484d4ab662937ca6fbef7ced9b9e12842709872eb3020cc3504b93bde88935c9f6417489627a7786f24f8031cbcb - languageName: node - linkType: hard - "storybook@npm:^8.3.5": version: 8.6.12 resolution: "storybook@npm:8.6.12" @@ -5151,13 +4665,6 @@ __metadata: languageName: node linkType: hard -"toidentifier@npm:1.0.1": - version: 1.0.1 - resolution: "toidentifier@npm:1.0.1" - checksum: 10/952c29e2a85d7123239b5cfdd889a0dde47ab0497f0913d70588f19c53f7e0b5327c95f4651e413c74b785147f9637b17410ac8c846d5d4a20a5a33eb6dc3a45 - languageName: node - linkType: hard - "ts-api-utils@npm:^2.0.1": version: 2.0.1 resolution: "ts-api-utils@npm:2.0.1" @@ -5233,17 +4740,6 @@ __metadata: languageName: node linkType: hard -"type-is@npm:^2.0.0, type-is@npm:^2.0.1": - version: 2.0.1 - resolution: "type-is@npm:2.0.1" - dependencies: - content-type: "npm:^1.0.5" - media-typer: "npm:^1.1.0" - mime-types: "npm:^3.0.0" - checksum: 10/bacdb23c872dacb7bd40fbd9095e6b2fca2895eedbb689160c05534d7d4810a7f4b3fd1ae87e96133c505958f6d602967a68db5ff577b85dd6be76eaa75d58af - languageName: node - linkType: hard - "typescript-eslint@npm:^8.7.0": version: 8.26.0 resolution: "typescript-eslint@npm:8.26.0" @@ -5303,13 +4799,6 @@ __metadata: languageName: node linkType: hard -"unpipe@npm:1.0.0": - version: 1.0.0 - resolution: "unpipe@npm:1.0.0" - checksum: 10/4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2 - languageName: node - linkType: hard - "unplugin@npm:^1.3.1": version: 1.14.1 resolution: "unplugin@npm:1.14.1" @@ -5370,13 +4859,6 @@ __metadata: languageName: node linkType: hard -"vary@npm:^1, vary@npm:^1.1.2": - version: 1.1.2 - resolution: "vary@npm:1.1.2" - checksum: 10/31389debef15a480849b8331b220782230b9815a8e0dbb7b9a8369559aed2e9a7800cd904d4371ea74f4c3527db456dc8e7ac5befce5f0d289014dbdf47b2242 - languageName: node - linkType: hard - "vite@npm:^5.4.8": version: 5.4.19 resolution: "vite@npm:5.4.19" @@ -5491,13 +4973,6 @@ __metadata: languageName: node linkType: hard -"wrappy@npm:1": - version: 1.0.2 - resolution: "wrappy@npm:1.0.2" - checksum: 10/159da4805f7e84a3d003d8841557196034155008f817172d4e986bd591f74aa82aa7db55929a54222309e01079a65a92a9e6414da5a6aa4b01ee44a511ac3ee5 - languageName: node - linkType: hard - "ws@npm:^8.2.3": version: 8.18.0 resolution: "ws@npm:8.18.0" @@ -5533,19 +5008,3 @@ __metadata: checksum: 10/f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 languageName: node linkType: hard - -"zod-to-json-schema@npm:^3.24.1": - version: 3.24.5 - resolution: "zod-to-json-schema@npm:3.24.5" - peerDependencies: - zod: ^3.24.1 - checksum: 10/1af291b4c429945c9568c2e924bdb7c66ab8d139cbeb9a99b6e9fc9e1b02863f85d07759b9303714f07ceda3993dcaf0ebcb80d2c18bb2aaf5502b2c1016affd - languageName: node - linkType: hard - -"zod@npm:^3.23.8, zod@npm:^3.24.2": - version: 3.24.4 - resolution: "zod@npm:3.24.4" - checksum: 10/3d545792fa54bb27ee5dbc34a5709e81f603185fcc94c8204b5d95c20dc4c81d870ff9c51f3884a30ef05cdc601449f4c4df254ac4783f0827b1faed7c1cdb48 - languageName: node - linkType: hard From 2974c086193160a5ec529723ddb5d9f4604efcdc Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Mon, 16 Jun 2025 22:24:07 +0300 Subject: [PATCH 148/301] fix: review findings removed default exclude targets so that tests are also in the scope. added new includedFiles option that defaults to all in src/ directories added more ignored packages to local config to make this pass, needs fixes later on Signed-off-by: Hellgren Heikki --- .eslintrc.js | 10 +++-- .../rules/no-mixed-plugin-imports.js | 44 ++++++++++++++++--- .../catalog-backend-module-logs/package.json | 1 - yarn.lock | 1 - 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 787edae242..54f9e69576 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,13 +24,17 @@ module.exports = { 'error', { // TODO: Fix these either by right role or by moving things to new packages - // TODO: Additionally remove the '**/*.{test,spec}.[jt]s?(x)' from `excludedFiles` in `no-mixed-plugin-imports` - // and fix the errors excludedTargetPackages: [ '@backstage/plugin-catalog', '@backstage/plugin-techdocs', '@backstage/plugin-app', - '@backstage/plugin-catalog-backend' + '@backstage/plugin-catalog-backend', + '@backstage/test-utils', + '@backstage/plugin-auth-backend', + '@backstage/plugin-permission-backend', + '@backstage/plugin-kubernetes-backend', + '@backstage/config-loader', + '@backstage/plugin-app-backend' ], } ], diff --git a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js index 0ed4367ce2..b5e79b509e 100644 --- a/packages/eslint-plugin/rules/no-mixed-plugin-imports.js +++ b/packages/eslint-plugin/rules/no-mixed-plugin-imports.js @@ -68,6 +68,7 @@ module.exports = { 'Use web library {{targetPackage}}-react or common library instead.', useNodePlugin: 'Use node library {{targetPackage}}-node or common library instead.', + useCommonPlugin: 'Use common library {{targetPackage}}-common instead.', removeImport: 'Remove this import to avoid mixed plugin imports. Fix the code by refactoring it to use the correct plugin type.', }, @@ -90,6 +91,11 @@ module.exports = { items: { type: 'string' }, uniqueItems: true, }, + includedFiles: { + type: 'array', + items: { type: 'string' }, + uniqueItems: true, + }, }, additionalProperties: false, }, @@ -115,12 +121,14 @@ module.exports = { /** @type {string[]} */ const ignoreTargetPackages = options.excludedTargetPackages || []; /** @type {string[]} */ - const ignorePatterns = options.excludedFiles || [ - '**/*.{test,spec}.[jt]s?(x)', - '**/dev/index.[jt]s?(x)', - ]; + const excludePatterns = options.excludedFiles || []; + /** @type {string[]} */ + const includePatterns = options.includedFiles || ['**/src/**']; - if (ignorePatterns.some(pattern => matchesPattern(pattern, filePath))) { + if ( + !includePatterns.some(pattern => matchesPattern(pattern, filePath)) || + excludePatterns.some(pattern => matchesPattern(pattern, filePath)) + ) { return {}; } @@ -170,6 +178,19 @@ module.exports = { return fixer.replaceText(imp.node, newImport); }, }); + suggest.push({ + messageId: 'useCommonPlugin', + data: { + targetPackage: targetName, + }, + /** @param {import('eslint').Rule.RuleFixer} fixer */ + fix(fixer) { + const source = context.sourceCode; + const nodeSource = source.getText(imp.node); + const newImport = nodeSource.replace(/'$/, "-common'"); + return fixer.replaceText(imp.node, newImport); + }, + }); } else if ( (sourceRole === 'backend-plugin' || sourceRole === 'backend-plugin-module') && @@ -188,6 +209,19 @@ module.exports = { return fixer.replaceText(imp.node, newImport); }, }); + suggest.push({ + messageId: 'useCommonPlugin', + data: { + targetPackage: targetName, + }, + /** @param {import('eslint').Rule.RuleFixer} fixer */ + fix(fixer) { + const source = context.sourceCode; + const nodeSource = source.getText(imp.node); + const newImport = nodeSource.replace(/-backend'$/, '-common'); + return fixer.replaceText(imp.node, newImport); + }, + }); } else { suggest.push({ messageId: 'removeImport', diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index 3fad09bcb1..481ffba3ad 100644 --- a/plugins/catalog-backend-module-logs/package.json +++ b/plugins/catalog-backend-module-logs/package.json @@ -35,7 +35,6 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", - "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-events-node": "workspace:^" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index ebb1d05132..d3fc3ea273 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5995,7 +5995,6 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" - "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-events-backend-test-utils": "workspace:^" "@backstage/plugin-events-node": "workspace:^" languageName: unknown From cab1ad8b1b221055504b4a4e5a2ac6a14e1b39e3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:03:11 +0000 Subject: [PATCH 149/301] chore(deps): update dependency msw to v2.10.2 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index fb52106772..b35d626c22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12416,9 +12416,9 @@ __metadata: languageName: node linkType: hard -"@mswjs/interceptors@npm:^0.37.0": - version: 0.37.1 - resolution: "@mswjs/interceptors@npm:0.37.1" +"@mswjs/interceptors@npm:^0.39.1": + version: 0.39.2 + resolution: "@mswjs/interceptors@npm:0.39.2" dependencies: "@open-draft/deferred-promise": "npm:^2.2.0" "@open-draft/logger": "npm:^0.3.0" @@ -12426,7 +12426,7 @@ __metadata: is-node-process: "npm:^1.2.0" outvariant: "npm:^1.4.3" strict-event-emitter: "npm:^0.5.1" - checksum: 10/332d8aa50beb4834ccbda6a800ca00b1204adc0eba23e1c1f7bb9f4e564a92707e563f7a2424d4a8607404ec91424e5d8c34a87c250b191ca7b24dff12eba2c5 + checksum: 10/faaa95d636363a197f125c32066457fa74d5063d8ccae4c9c0e0510179060d92b1faf8640df45a0623e0bf42a30d610c83364a58e0eb0ca412c87b2e835936c1 languageName: node linkType: hard @@ -39692,14 +39692,14 @@ __metadata: linkType: hard "msw@npm:^2.0.0, msw@npm:^2.0.8": - version: 2.8.2 - resolution: "msw@npm:2.8.2" + version: 2.10.2 + resolution: "msw@npm:2.10.2" dependencies: "@bundled-es-modules/cookie": "npm:^2.0.1" "@bundled-es-modules/statuses": "npm:^1.0.1" "@bundled-es-modules/tough-cookie": "npm:^0.1.6" "@inquirer/confirm": "npm:^5.0.0" - "@mswjs/interceptors": "npm:^0.37.0" + "@mswjs/interceptors": "npm:^0.39.1" "@open-draft/deferred-promise": "npm:^2.2.0" "@open-draft/until": "npm:^2.1.0" "@types/cookie": "npm:^0.6.0" @@ -39720,7 +39720,7 @@ __metadata: optional: true bin: msw: cli/index.js - checksum: 10/7579a8dccb8cc8eb0f13d0bf3a232a3d50a478511d95bc2a4b70778e78ffa28fd0949a855fbceb6ec381bbf76f6331a6d36fcb4a3197ff5bf55d0f14a7f3b35c + checksum: 10/bc90bc34a0b9e978e662f33fa630a0de66c4b6eff3a92b41efa08bf67d79b43e4a961a17b0d274580393c912f4ac6226c76a8bc33148799b1ee43477df712c26 languageName: node linkType: hard From ad825dcd4b352dee947b89afbadd2dc23396db56 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:04:18 +0000 Subject: [PATCH 150/301] chore(deps): update docker/login-action action to v3.4.0 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/deploy_docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index 21913eb428..7349c393d1 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -52,7 +52,7 @@ jobs: working-directory: ./example-app - name: Login to GitHub Container Registry - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.actor }} From 76a58b90090a5f10937842e502b52cd71b77522c Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 16 Jun 2025 22:41:44 +0100 Subject: [PATCH 151/301] Add new FormInput component Signed-off-by: Charles de Dreuille --- .../FormInput/FormInput.stories.tsx | 87 ++++++++++++++++++ .../components/FormInput/FormInput.styles.css | 91 +++++++++++++++++++ .../src/components/FormInput/FormInput.tsx | 48 ++++++++++ .../canon/src/components/FormInput/index.ts | 18 ++++ .../canon/src/components/FormInput/types.ts | 33 +++++++ packages/canon/src/css/components.css | 1 + 6 files changed, 278 insertions(+) create mode 100644 packages/canon/src/components/FormInput/FormInput.stories.tsx create mode 100644 packages/canon/src/components/FormInput/FormInput.styles.css create mode 100644 packages/canon/src/components/FormInput/FormInput.tsx create mode 100644 packages/canon/src/components/FormInput/index.ts create mode 100644 packages/canon/src/components/FormInput/types.ts diff --git a/packages/canon/src/components/FormInput/FormInput.stories.tsx b/packages/canon/src/components/FormInput/FormInput.stories.tsx new file mode 100644 index 0000000000..8210bedb7c --- /dev/null +++ b/packages/canon/src/components/FormInput/FormInput.stories.tsx @@ -0,0 +1,87 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { FormInput } from './FormInput'; +import { Icon } from '../Icon'; +import { Flex } from '../Flex'; + +const meta = { + title: 'Forms/FormInput', + component: FormInput, + argTypes: { + required: { + control: 'boolean', + }, + icon: { + control: 'object', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + name: 'url', + placeholder: 'Enter a URL', + style: { + maxWidth: '300px', + }, + }, +}; + +export const Sizes: Story = { + args: { + ...Default.args, + }, + render: args => ( + + } /> + } /> + + ), +}; + +export const Filled: Story = { + args: { + ...Default.args, + defaultValue: 'https://example.com', + }, +}; + +export const Disabled: Story = { + args: { + ...Default.args, + disabled: true, + }, +}; + +export const WithIcon: Story = { + args: { + ...Default.args, + placeholder: 'Search...', + icon: , + }, +}; + +export const DisabledWithIcon: Story = { + args: { + ...WithIcon.args, + disabled: true, + }, +}; diff --git a/packages/canon/src/components/FormInput/FormInput.styles.css b/packages/canon/src/components/FormInput/FormInput.styles.css new file mode 100644 index 0000000000..4415247799 --- /dev/null +++ b/packages/canon/src/components/FormInput/FormInput.styles.css @@ -0,0 +1,91 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.canon-FormInput { + display: flex; + align-items: center; + padding: 0 var(--canon-space-3); + border-radius: var(--canon-radius-3); + border: 1px solid var(--canon-border); + background-color: var(--canon-bg-surface-1); +} + +.canon-FormInputIcon { + margin-right: var(--canon-space-1); + color: var(--canon-fg-primary); + flex-shrink: 0; +} + +.canon-FormInputIcon[data-size='small'], +.canon-FormInputIcon[data-size='small'] svg { + width: 1rem; + height: 1rem; +} + +.canon-FormInputIcon[data-size='medium'], +.canon-FormInputIcon[data-size='medium'] svg { + width: 1.25rem; + height: 1.25rem; +} + +.canon-Input { + border: none; + background: none; + font-size: var(--canon-font-size-3); + font-family: var(--canon-font-regular); + font-weight: var(--canon-font-weight-regular); + color: var(--canon-fg-primary); + transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out; + width: 100%; + height: 100%; + cursor: inherit; + padding: 0; +} + +.canon-Input::placeholder { + color: var(--canon-fg-secondary); +} + +.canon-FormInput[data-size='small'] { + height: 2rem; +} + +.canon-FormInput[data-size='medium'] { + height: 2.5rem; +} + +.canon-Input[data-focused] { + outline-color: var(--canon-border-pressed); + outline-width: 0px; +} + +.canon-FormInput:has(> .canon-Input:hover) { + border-color: var(--canon-border-hover); +} + +.canon-FormInput[data-focused] { + border-color: var(--canon-border-pressed); +} + +.canon-FormInput[data-invalid] { + border-color: var(--canon-fg-danger); +} + +.canon-FormInput[data-disabled] { + opacity: 0.5; + cursor: not-allowed; + border: 1px solid var(--canon-border-disabled); +} diff --git a/packages/canon/src/components/FormInput/FormInput.tsx b/packages/canon/src/components/FormInput/FormInput.tsx new file mode 100644 index 0000000000..9e9ca9855f --- /dev/null +++ b/packages/canon/src/components/FormInput/FormInput.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { forwardRef } from 'react'; +import { Input } from 'react-aria-components'; +import { useResponsiveValue } from '../../hooks/useResponsiveValue'; +import clsx from 'clsx'; + +import type { FormInputProps } from './types'; + +/** @public */ +export const FormInput = forwardRef( + (props: FormInputProps, ref) => { + const { className, icon, size = 'small', ...rest } = props; + + // Get the responsive value for the variant + const responsiveSize = useResponsiveValue(size); + + return ( +
+ {icon && ( + + )} + +
+ ); + }, +); + +FormInput.displayName = 'FormInput'; diff --git a/packages/canon/src/components/FormInput/index.ts b/packages/canon/src/components/FormInput/index.ts new file mode 100644 index 0000000000..29df00ccaf --- /dev/null +++ b/packages/canon/src/components/FormInput/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './TextField'; +export type { TextFieldProps } from './types'; diff --git a/packages/canon/src/components/FormInput/types.ts b/packages/canon/src/components/FormInput/types.ts new file mode 100644 index 0000000000..1f28436259 --- /dev/null +++ b/packages/canon/src/components/FormInput/types.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InputProps } from 'react-aria-components'; +import { ReactNode } from 'react'; +import type { Breakpoint } from '../../types'; + +/** @public */ +export interface FormInputProps extends Omit { + /** + * An icon to render before the input + */ + icon?: ReactNode; + + /** + * The size of the text field + * @defaultValue 'medium' + */ + size?: 'small' | 'medium' | Partial>; +} diff --git a/packages/canon/src/css/components.css b/packages/canon/src/css/components.css index 609ebea769..1f4c6bad99 100644 --- a/packages/canon/src/css/components.css +++ b/packages/canon/src/css/components.css @@ -21,6 +21,7 @@ @import '../components/DataTable/Root/DataTableRoot.styles.css'; @import '../components/DataTable/Pagination/DataTablePagination.styles.css'; @import '../components/Flex/styles.css'; +@import '../components/FormInput/FormInput.styles.css'; @import '../components/Grid/styles.css'; @import '../components/Container/styles.css'; @import '../components/Icon/styles.css'; From 75b06e07567abac4d06c9266530049d378f3e028 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 16 Jun 2025 22:42:18 +0100 Subject: [PATCH 152/301] Move TextField to React Aria Signed-off-by: Charles de Dreuille --- .../TextField/TextField.stories.tsx | 117 +++++----------- .../components/TextField/TextField.styles.css | 125 +++++------------- .../src/components/TextField/TextField.tsx | 89 +++++-------- .../canon/src/components/TextField/types.ts | 40 ++---- packages/canon/src/stories/Form.stories.tsx | 2 +- 5 files changed, 115 insertions(+), 258 deletions(-) diff --git a/packages/canon/src/components/TextField/TextField.stories.tsx b/packages/canon/src/components/TextField/TextField.stories.tsx index f052377acb..17097e993c 100644 --- a/packages/canon/src/components/TextField/TextField.stories.tsx +++ b/packages/canon/src/components/TextField/TextField.stories.tsx @@ -16,19 +16,20 @@ import type { Meta, StoryObj } from '@storybook/react'; import { TextField } from './TextField'; -import { Flex } from '../Flex'; +import { Form } from 'react-aria-components'; import { Icon } from '../Icon'; +import { Flex } from '../Flex'; const meta = { - title: 'Components/TextField', + title: 'Forms/TextField', component: TextField, argTypes: { - secondaryLabel: { - control: 'text', - }, - required: { + isRequired: { control: 'boolean', }, + icon: { + control: 'object', + }, }, } satisfies Meta; @@ -38,14 +39,26 @@ type Story = StoryObj; export const Default: Story = { args: { name: 'url', - placeholder: 'Enter a URL', + defaultValue: 'Enter a URL', style: { maxWidth: '300px', }, }, }; -export const Filled: Story = { +export const Sizes: Story = { + args: { + ...Default.args, + }, + render: args => ( + + } /> + } /> + + ), +}; + +export const DefaultValue: Story = { args: { ...Default.args, defaultValue: 'https://example.com', @@ -69,82 +82,21 @@ export const WithDescription: Story = { export const Required: Story = { args: { ...WithLabel.args, - required: true, - }, -}; - -export const LabelSizes: Story = { - args: { - ...Default.args, - label: 'Label', - description: 'Description', - required: true, - }, - render: args => ( - - - - - ), -}; - -export const HideLabelAndDescription: Story = { - args: { - ...WithLabel.args, - hideLabelAndDescription: true, + isRequired: true, }, }; export const Disabled: Story = { - args: { - ...WithLabel.args, - disabled: true, - }, -}; - -export const Sizes: Story = { args: { ...Default.args, - label: 'Label', - description: 'Description', - }, - render: args => ( - - } /> - } /> - - ), -}; - -export const Responsive: Story = { - args: { - ...WithLabel.args, - size: { - initial: 'small', - sm: 'medium', - }, - }, -}; - -export const WithError: Story = { - args: { - ...WithLabel.args, - error: 'Invalid URL', - }, -}; - -export const WithErrorAndDescription: Story = { - args: { - ...WithLabel.args, - error: 'Invalid URL', - description: 'Description', + isDisabled: true, }, }; export const WithIcon: Story = { args: { - ...WithLabel.args, - placeholder: 'Search...', + ...Default.args, + defaultValue: 'Search...', icon: , }, }; @@ -152,23 +104,24 @@ export const WithIcon: Story = { export const DisabledWithIcon: Story = { args: { ...WithIcon.args, - disabled: true, + isDisabled: true, }, }; -export const WithOnClear: Story = { +export const ShowError: Story = { args: { ...WithLabel.args, - placeholder: 'Search...', - type: 'search', - onClear: () => console.log('Cleared!'), }, + render: args => ( +
+ + + ), }; -export const DisabledWithOnClear: Story = { +export const Validation: Story = { args: { - ...WithOnClear.args, - defaultValue: 'Testing', - disabled: true, + ...WithLabel.args, + validate: value => (value === 'admin' ? 'Nice try!' : null), }, }; diff --git a/packages/canon/src/components/TextField/TextField.styles.css b/packages/canon/src/components/TextField/TextField.styles.css index d7bc147ba1..aeb49cb8fc 100644 --- a/packages/canon/src/components/TextField/TextField.styles.css +++ b/packages/canon/src/components/TextField/TextField.styles.css @@ -28,38 +28,14 @@ gap: var(--canon-space-1); } -.canon-TextFieldLabelWrapper[data-hidden] { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - .canon-TextFieldLabel { color: var(--canon-fg-primary); margin-right: auto; cursor: pointer; -} - -.canon-TextFieldLabel[data-size='small'] { font-weight: var(--canon-font-weight-regular); font-size: var(--canon-font-size-2); } -.canon-TextFieldLabel[data-size='medium'] { - font-weight: var(--canon-font-weight-bold); - font-size: var(--canon-font-size-3); -} - -.canon-TextFieldLabel[data-disabled] { - cursor: default; -} - .canon-TextFieldSecondaryLabel { color: var(--canon-fg-secondary); font-weight: var(--canon-font-weight-regular); @@ -68,36 +44,28 @@ .canon-TextFieldDescription { font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); color: var(--canon-fg-secondary); margin: 0; } -.canon-TextFieldDescription[data-size='small'] { - font-size: var(--canon-font-size-2); -} - -.canon-TextFieldDescription[data-size='medium'] { - font-size: var(--canon-font-size-3); -} - -.canon-TextFieldError { - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-danger); - margin: 0; - padding-top: var(--canon-space-1_5); -} - .canon-TextFieldInputWrapper { - display: flex; - align-items: center; - padding: 0 var(--canon-space-3); - border-radius: var(--canon-radius-3); - border: 1px solid var(--canon-border); - background-color: var(--canon-bg-surface-1); + position: relative; +} + +.canon-TextFieldInputWrapper[data-size='small'] { + height: 2rem; +} + +.canon-TextFieldInputWrapper[data-size='medium'] { + height: 2.5rem; } .canon-TextFieldIcon { + position: absolute; + left: var(--canon-space-3); + top: 50%; + transform: translateY(-50%); margin-right: var(--canon-space-1); color: var(--canon-fg-primary); flex-shrink: 0; @@ -116,8 +84,13 @@ } .canon-TextFieldInput { - border: none; - background: none; + display: flex; + align-items: center; + padding: 0 var(--canon-space-3); + border-radius: var(--canon-radius-3); + border: 1px solid var(--canon-border); + background-color: var(--canon-bg-surface-1); + font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); @@ -126,71 +99,43 @@ width: 100%; height: 100%; cursor: inherit; - padding: 0; -} - -.canon-TextFieldInput:not([data-filled]):has(+ .canon-TextFieldClearButton) { - padding-right: 1.25rem; -} - -.canon-TextFieldInput[type='search']::-webkit-search-cancel-button, -.canon-TextFieldInput[type='search']::-webkit-search-decoration { - appearance: none; -} - -.canon-TextFieldClearButton { - display: none; - padding: 0; - margin-left: var(--canon-space-1); - background: none; - border: none; - vertical-align: middle; - color: var(--canon-fg-primary); -} - -.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { - display: inline-block; -} - -.canon-TextFieldClearButtonIcon { - display: block; } .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } +.canon-TextFieldInput[data-icon] { + padding-left: var(--canon-space-8); +} + .canon-TextFieldInput[data-focused] { outline-color: var(--canon-border-pressed); outline-width: 0px; } -.canon-TextFieldInputWrapper:has(> .canon-TextFieldInput:hover) { +.canon-TextFieldInput[data-hovered] { border-color: var(--canon-border-hover); } -.canon-TextField[data-focused] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-focused] { border-color: var(--canon-border-pressed); + outline-width: 0px; } -.canon-TextField[data-invalid] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-invalid] { border-color: var(--canon-fg-danger); } -.canon-TextField[data-disabled] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-disabled] { opacity: 0.5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } -.canon-TextField[data-disabled] .canon-TextFieldClearButton { - cursor: inherit; -} - -.canon-TextFieldInputWrapper[data-size='small'] { - height: 2rem; -} - -.canon-TextFieldInputWrapper[data-size='medium'] { - height: 2.5rem; +.canon-TextFieldError { + color: var(--canon-fg-danger); + font-size: var(--canon-font-size-2); + font-weight: var(--canon-font-weight-regular); + margin-top: var(--canon-space-2); } diff --git a/packages/canon/src/components/TextField/TextField.tsx b/packages/canon/src/components/TextField/TextField.tsx index 07efc47100..62bc5633cd 100644 --- a/packages/canon/src/components/TextField/TextField.tsx +++ b/packages/canon/src/components/TextField/TextField.tsx @@ -14,58 +14,60 @@ * limitations under the License. */ -import { Field } from '@base-ui-components/react/field'; -import { forwardRef } from 'react'; +import { forwardRef, useEffect } from 'react'; +import { + Input, + TextField as AriaTextField, + Label, + FieldError, +} from 'react-aria-components'; import { useResponsiveValue } from '../../hooks/useResponsiveValue'; import clsx from 'clsx'; -import type { TextFieldProps } from './types'; -import { Icon } from '../Icon'; +import type { FormInputProps } from './types'; /** @public */ -export const TextField = forwardRef( - (props: TextFieldProps, ref) => { +export const TextField = forwardRef( + (props: FormInputProps, ref) => { const { className, + icon, size = 'small', label, - labelSize = 'small', secondaryLabel, description, - hideLabelAndDescription, - error, - required, - style, - disabled, - icon, - onClear, + isRequired, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, ...rest } = props; + useEffect(() => { + if (!label && !ariaLabel && !ariaLabelledBy) { + console.warn( + 'If you do not provide a visible label, you must specify an aria-label or aria-labelledby attribute for accessibility', + ); + } + }, [label, ariaLabel, ariaLabelledBy]); + // Get the responsive value for the variant const responsiveSize = useResponsiveValue(size); // If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required. - const secondaryLabelText = secondaryLabel || (required ? 'Required' : null); + const secondaryLabelText = + secondaryLabel || (isRequired ? 'Required' : null); return ( - {label && ( -
+
{label && ( - + + )} {description && ( - - {description} - +
{description}
)}
)} @@ -91,33 +88,19 @@ export const TextField = forwardRef( {icon && ( )} - - {onClear && ( - - )}
- {error && ( - - {error} - - )} -
+ + ); }, ); diff --git a/packages/canon/src/components/TextField/types.ts b/packages/canon/src/components/TextField/types.ts index 8a81492f2f..11ba2dbb51 100644 --- a/packages/canon/src/components/TextField/types.ts +++ b/packages/canon/src/components/TextField/types.ts @@ -14,15 +14,16 @@ * limitations under the License. */ +import type { TextFieldProps } from 'react-aria-components'; +import { ReactNode } from 'react'; import type { Breakpoint } from '../../types'; -import type { ReactNode, MouseEventHandler } from 'react'; + /** @public */ -export interface TextFieldProps - extends Omit, 'size'> { +export interface FormInputProps extends Omit { /** - * The class name of the text field + * An icon to render before the input */ - className?: string; + icon?: ReactNode; /** * The size of the text field @@ -40,38 +41,13 @@ export interface TextFieldProps */ secondaryLabel?: string; - /** - * The size of the label and description - */ - labelSize?: 'small' | 'medium'; - - /** - * Hide the label and description but still visible to screen readers - */ - hideLabelAndDescription?: boolean; - /** * The description of the text field */ description?: string; /** - * The name of the text field + * Whether the field is required */ - name: string; - - /** - * The error message of the text field - */ - error?: string | null; - - /** - * An icon to render before the input - */ - icon?: ReactNode; - - /** - * Handler to call when the clear button is pressed - */ - onClear?: MouseEventHandler; + isRequired?: boolean; } diff --git a/packages/canon/src/stories/Form.stories.tsx b/packages/canon/src/stories/Form.stories.tsx index 3bffca6554..97e28faaee 100644 --- a/packages/canon/src/stories/Form.stories.tsx +++ b/packages/canon/src/stories/Form.stories.tsx @@ -22,7 +22,7 @@ import { Button } from '../components/Button'; import { Select } from '../components/Select'; const meta = { - title: 'Examples/Form', + title: 'Forms/Form', } satisfies Meta; export default meta; From 35fd51dc33ebca8f37ef8cd752d5cf0dbfab5e94 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 16 Jun 2025 23:06:34 +0100 Subject: [PATCH 153/301] Add new FieldLabel component Signed-off-by: Charles de Dreuille --- .changeset/khaki-waves-lick.md | 5 + packages/canon/css/components.css | 1053 ++++++++--------- packages/canon/css/fieldlabel.css | 27 + packages/canon/css/styles.css | 1053 ++++++++--------- packages/canon/css/textfield.css | 149 +-- packages/canon/report.api.md | 40 +- .../FieldLabel/FieldLabel.stories.tsx | 65 + .../FieldLabel/FieldLabel.styles.css | 27 + .../src/components/FieldLabel/FieldLabel.tsx | 50 + .../{FormInput => FieldLabel}/index.ts | 4 +- .../{FormInput => FieldLabel}/types.ts | 20 +- .../FormInput/FormInput.stories.tsx | 87 -- .../components/FormInput/FormInput.styles.css | 91 -- .../src/components/FormInput/FormInput.tsx | 48 - .../components/TextField/TextField.styles.css | 28 - .../src/components/TextField/TextField.tsx | 27 +- .../canon/src/components/TextField/index.ts | 2 +- .../canon/src/components/TextField/types.ts | 25 +- packages/canon/src/css/components.css | 14 +- packages/canon/src/index.ts | 1 + packages/canon/src/stories/Form.stories.tsx | 195 --- 21 files changed, 1258 insertions(+), 1753 deletions(-) create mode 100644 .changeset/khaki-waves-lick.md create mode 100644 packages/canon/css/fieldlabel.css create mode 100644 packages/canon/src/components/FieldLabel/FieldLabel.stories.tsx create mode 100644 packages/canon/src/components/FieldLabel/FieldLabel.styles.css create mode 100644 packages/canon/src/components/FieldLabel/FieldLabel.tsx rename packages/canon/src/components/{FormInput => FieldLabel}/index.ts (88%) rename packages/canon/src/components/{FormInput => FieldLabel}/types.ts (61%) delete mode 100644 packages/canon/src/components/FormInput/FormInput.stories.tsx delete mode 100644 packages/canon/src/components/FormInput/FormInput.styles.css delete mode 100644 packages/canon/src/components/FormInput/FormInput.tsx delete mode 100644 packages/canon/src/stories/Form.stories.tsx diff --git a/.changeset/khaki-waves-lick.md b/.changeset/khaki-waves-lick.md new file mode 100644 index 0000000000..a299424170 --- /dev/null +++ b/.changeset/khaki-waves-lick.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': minor +--- + +Move TextField component to use react Aria under the hood. Introducing a new FieldLabel component to help build custom fields. diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index 1b60bf74ed..8d133abfb0 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -144,80 +144,6 @@ height: 1.25rem; } -.canon-CollapsiblePanel { - height: var(--collapsible-panel-height); - transition: all .15s ease-out; - display: flex; - overflow: hidden; - - &[data-starting-style], &[data-ending-style] { - height: 0; - } -} - -.canon-DataTableRoot { - gap: var(--canon-space-3); - flex-direction: column; - display: flex; -} - -.canon-DataTablePagination { - padding-top: var(--canon-space-5); - justify-content: space-between; - align-items: center; - display: flex; -} - -.canon-DataTablePagination--left { - justify-content: space-between; - align-items: center; - display: flex; -} - -.canon-DataTablePagination--right { - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - display: flex; -} - -.canon-DataTablePagination--select { - min-width: 10.5rem; -} - -.canon-Flex { - min-width: 0; - display: flex; -} - -.canon-Grid { - display: grid; -} - -.canon-Container { - max-width: 120rem; - padding-inline: var(--canon-space-4); - margin-inline: auto; - transition: padding .2s ease-in-out; -} - -@media (width >= 640px) { - .canon-Container { - padding-inline: var(--canon-space-8); - } -} - -@media (width >= 1024px) { - .canon-Container { - padding-inline: var(--canon-space-12); - } -} - -.canon-Icon { - width: 1rem; - height: 1rem; -} - .canon-CheckboxRoot { width: 1rem; height: 1rem; @@ -271,6 +197,474 @@ display: flex; } +.canon-CollapsiblePanel { + height: var(--collapsible-panel-height); + transition: all .15s ease-out; + display: flex; + overflow: hidden; + + &[data-starting-style], &[data-ending-style] { + height: 0; + } +} + +.canon-Container { + max-width: 120rem; + padding-inline: var(--canon-space-4); + margin-inline: auto; + transition: padding .2s ease-in-out; +} + +@media (width >= 640px) { + .canon-Container { + padding-inline: var(--canon-space-8); + } +} + +@media (width >= 1024px) { + .canon-Container { + padding-inline: var(--canon-space-12); + } +} + +.canon-DataTableRoot { + gap: var(--canon-space-3); + flex-direction: column; + display: flex; +} + +.canon-DataTablePagination { + padding-top: var(--canon-space-5); + justify-content: space-between; + align-items: center; + display: flex; +} + +.canon-DataTablePagination--left { + justify-content: space-between; + align-items: center; + display: flex; +} + +.canon-DataTablePagination--right { + justify-content: space-between; + align-items: center; + gap: var(--canon-space-2); + display: flex; +} + +.canon-DataTablePagination--select { + min-width: 10.5rem; +} + +.canon-TextFieldLabelWrapper { + margin-bottom: var(--canon-space-3); + gap: var(--canon-space-1); + flex-direction: column; + display: flex; +} + +.canon-TextFieldLabel { + color: var(--canon-fg-primary); + cursor: pointer; + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); + margin-right: auto; +} + +.canon-TextFieldSecondaryLabel { + color: var(--canon-fg-secondary); + font-weight: var(--canon-font-weight-regular); + margin-left: var(--canon-space-1); +} + +.canon-TextFieldDescription { + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); + color: var(--canon-fg-secondary); + margin: 0; +} + +.canon-Flex { + min-width: 0; + display: flex; +} + +.canon-Grid { + display: grid; +} + +.canon-Heading { + font-family: var(--canon-font-regular); + color: var(--canon-fg-primary); + margin: 0; + padding: 0; + line-height: 100%; +} + +.canon-Heading[data-variant="display"] { + font-size: var(--canon-font-size-10); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title1"] { + font-size: var(--canon-font-size-9); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title2"] { + font-size: var(--canon-font-size-8); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title3"] { + font-size: var(--canon-font-size-7); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title4"] { + font-size: var(--canon-font-size-6); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title5"] { + font-size: var(--canon-font-size-5); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-color="primary"] { + color: var(--canon-fg-primary); +} + +.canon-Heading[data-color="secondary"] { + color: var(--canon-fg-secondary); +} + +.canon-Heading[data-truncate] { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.canon-Icon { + width: 1rem; + height: 1rem; +} + +.canon-IconButton { + user-select: none; + font-family: var(--canon-font-regular); + font-weight: var(--canon-font-weight-bold); + cursor: pointer; + border-radius: var(--canon-radius-2); + justify-content: center; + align-items: center; + gap: var(--canon-space-1_5); + border: none; + padding: 0; + display: inline-flex; + + &:disabled { + cursor: not-allowed; + } +} + +.canon-IconButton[data-variant="primary"] { + background-color: var(--canon-bg-solid); + color: var(--canon-fg-solid); + transition: background-color .15s, box-shadow .15s; + + &:hover { + background-color: var(--canon-bg-solid-hover); + } + + &:active { + background-color: var(--canon-bg-solid-pressed); + } + + &:focus-visible { + outline: 2px solid var(--canon-ring); + outline-offset: 2px; + } + + &:disabled { + background-color: var(--canon-bg-solid-disabled); + color: var(--canon-fg-solid-disabled); + } +} + +.canon-IconButton[data-variant="secondary"] { + background-color: var(--canon-bg-surface-1); + box-shadow: inset 0 0 0 1px var(--canon-border); + color: var(--canon-fg-primary); + transition: box-shadow .15s; + + &:hover { + box-shadow: inset 0 0 0 1px var(--canon-border-hover); + } + + &:active { + box-shadow: inset 0 0 0 1px var(--canon-border-pressed); + } + + &:focus-visible { + box-shadow: inset 0 0 0 2px var(--canon-ring); + outline: none; + transition: none; + } + + &:disabled { + box-shadow: inset 0 0 0 1px var(--canon-border-disabled); + color: var(--canon-fg-disabled); + } +} + +.canon-IconButton[data-size="medium"] { + font-size: var(--canon-font-size-4); + width: 40px; + height: 40px; +} + +.canon-IconButton[data-size="small"] { + font-size: var(--canon-font-size-3); + width: 32px; + height: 32px; +} + +.canon-IconButtonIcon[data-size="small"], .canon-IconButtonIcon[data-size="small"] svg { + width: 1rem; + height: 1rem; +} + +.canon-IconButtonIcon[data-size="medium"], .canon-IconButtonIcon[data-size="medium"] svg { + width: 1.25rem; + height: 1.25rem; +} + +.canon-Link { + font-family: var(--canon-font-regular); + color: var(--canon-fg-link); + cursor: pointer; + margin: 0; + padding: 0; + text-decoration-line: none; + + &:hover { + color: var(--canon-fg-link-hover); + text-underline-offset: calc(.025em + 2px); + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-thickness: min(2px, max(1px, .05em)); + text-decoration-color: color-mix(in srgb, var(--canon-fg-link-hover) 30%, transparent); + } +} + +.canon-Link[data-variant="body"] { + font-size: var(--canon-font-size-3); + line-height: 140%; +} + +.canon-Link[data-variant="subtitle"] { + font-size: var(--canon-font-size-4); + line-height: 140%; +} + +.canon-Link[data-variant="caption"] { + font-size: var(--canon-font-size-2); + line-height: 140%; +} + +.canon-Link[data-variant="label"] { + font-size: var(--canon-font-size-1); + line-height: 140%; +} + +.canon-Link[data-weight="regular"] { + font-weight: var(--canon-font-weight-regular); +} + +.canon-Link[data-weight="bold"] { + font-weight: var(--canon-font-weight-bold); +} + +.canon-MenuPositioner { + outline: 0; +} + +.canon-MenuPopup { + background-color: var(--canon-bg-surface-1); + border: 1px solid var(--canon-border); + color: var(--canon-fg-primary); + transform-origin: var(--transform-origin); + max-width: min(var(--available-width), 340px); + max-height: min(var(--available-height), 500px); + padding-bottom: var(--canon-space-1); + border-radius: .375rem; + outline: none; + flex-direction: column; + transition: transform .15s, opacity .15s; + display: flex; + position: relative; + overflow: auto; + + &[data-starting-style], &[data-ending-style] { + opacity: 0; + transform: scale(.9); + } +} + +.canon-MenuItem { + user-select: none; + align-items: center; + gap: var(--canon-space-2); + height: 32px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + margin-inline: var(--canon-space-1); + padding-inline: var(--canon-space-2); + font-size: var(--canon-font-size-3); + cursor: pointer; + outline: 0; + flex-shrink: 0; + text-decoration: none; + display: flex; + + &:first-child { + margin-top: var(--canon-space-1); + } + + &[data-highlighted] { + background-color: var(--canon-gray-3); + } +} + +.canon-MenuSubmenuTrigger { + user-select: none; + justify-content: space-between; + align-items: center; + gap: var(--canon-space-2); + height: 32px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + margin-inline: var(--canon-space-1); + padding-inline: var(--canon-space-2); + font-size: var(--canon-font-size-3); + cursor: pointer; + outline: 0; + flex-shrink: 0; + text-decoration: none; + display: flex; + + & .canon-Icon { + color: var(--canon-fg-secondary); + } + + &:first-child { + margin-top: var(--canon-space-1); + } + + &[data-popup-open], &[data-highlighted] { + background-color: var(--canon-gray-3); + + & .canon-Icon { + color: var(--canon-fg-primary); + } + } +} + +.canon-MenuSeparator { + background-color: var(--color-gray-200); + height: 1px; + margin: .375rem 1rem; +} + +.canon-SubmenuComboboxSearch { + padding-inline: var(--canon-space-3); + border: none; + border-bottom: 1px solid var(--canon-border); + background-color: var(--canon-bg-surface-1); + height: 32px; + color: var(--canon-fg-primary); + line-height: 140%; + font-size: var(--canon-font-size-3); + z-index: 1; + outline: none; + position: sticky; + top: 0; + + &::placeholder { + color: var(--canon-fg-secondary); + } + + &:disabled { + opacity: .6; + cursor: not-allowed; + } +} + +.canon-SubmenuComboboxItems { + padding-top: var(--canon-space-2); + outline: none; + flex-direction: column; + display: flex; + overflow-y: auto; +} + +.canon-SubmenuComboboxNoResults { + padding-inline: var(--canon-space-3); + padding-top: var(--canon-space-2); + padding-bottom: var(--canon-space-4); + color: var(--canon-fg-secondary); + font-size: var(--canon-font-size-3); +} + +.canon-SubmenuComboboxItem { + user-select: none; + justify-content: space-between; + align-items: center; + gap: var(--canon-space-2); + height: 32px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + margin-inline: var(--canon-space-1); + padding-inline: var(--canon-space-2); + font-size: var(--canon-font-size-3); + cursor: pointer; + outline: 0; + flex-shrink: 0; + text-decoration: none; + display: flex; + + &[data-highlighted] { + background-color: var(--canon-gray-3); + } + + &[data-disabled] { + opacity: .5; + cursor: not-allowed; + } +} + +.canon-SubmenuComboboxItemCheckbox { + width: 16px; + height: 16px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + border: 1px solid var(--canon-border); + background: var(--canon-bg-surface-1); + flex-shrink: 0; + justify-content: center; + align-items: center; + display: flex; +} + +.canon-SubmenuComboboxItemLabel { + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + overflow: hidden; +} + .canon-TableRoot { caption-side: bottom; border-collapse: collapse; @@ -487,148 +881,6 @@ overflow: hidden; } -.canon-Heading { - font-family: var(--canon-font-regular); - color: var(--canon-fg-primary); - margin: 0; - padding: 0; - line-height: 100%; -} - -.canon-Heading[data-variant="display"] { - font-size: var(--canon-font-size-10); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title1"] { - font-size: var(--canon-font-size-9); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title2"] { - font-size: var(--canon-font-size-8); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title3"] { - font-size: var(--canon-font-size-7); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title4"] { - font-size: var(--canon-font-size-6); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title5"] { - font-size: var(--canon-font-size-5); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-color="primary"] { - color: var(--canon-fg-primary); -} - -.canon-Heading[data-color="secondary"] { - color: var(--canon-fg-secondary); -} - -.canon-Heading[data-truncate] { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -.canon-IconButton { - user-select: none; - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-bold); - cursor: pointer; - border-radius: var(--canon-radius-2); - justify-content: center; - align-items: center; - gap: var(--canon-space-1_5); - border: none; - padding: 0; - display: inline-flex; - - &:disabled { - cursor: not-allowed; - } -} - -.canon-IconButton[data-variant="primary"] { - background-color: var(--canon-bg-solid); - color: var(--canon-fg-solid); - transition: background-color .15s, box-shadow .15s; - - &:hover { - background-color: var(--canon-bg-solid-hover); - } - - &:active { - background-color: var(--canon-bg-solid-pressed); - } - - &:focus-visible { - outline: 2px solid var(--canon-ring); - outline-offset: 2px; - } - - &:disabled { - background-color: var(--canon-bg-solid-disabled); - color: var(--canon-fg-solid-disabled); - } -} - -.canon-IconButton[data-variant="secondary"] { - background-color: var(--canon-bg-surface-1); - box-shadow: inset 0 0 0 1px var(--canon-border); - color: var(--canon-fg-primary); - transition: box-shadow .15s; - - &:hover { - box-shadow: inset 0 0 0 1px var(--canon-border-hover); - } - - &:active { - box-shadow: inset 0 0 0 1px var(--canon-border-pressed); - } - - &:focus-visible { - box-shadow: inset 0 0 0 2px var(--canon-ring); - outline: none; - transition: none; - } - - &:disabled { - box-shadow: inset 0 0 0 1px var(--canon-border-disabled); - color: var(--canon-fg-disabled); - } -} - -.canon-IconButton[data-size="medium"] { - font-size: var(--canon-font-size-4); - width: 40px; - height: 40px; -} - -.canon-IconButton[data-size="small"] { - font-size: var(--canon-font-size-3); - width: 32px; - height: 32px; -} - -.canon-IconButtonIcon[data-size="small"], .canon-IconButtonIcon[data-size="small"] svg { - width: 1rem; - height: 1rem; -} - -.canon-IconButtonIcon[data-size="medium"], .canon-IconButtonIcon[data-size="medium"] svg { - width: 1.25rem; - height: 1.25rem; -} - .canon-TextField { font-family: var(--canon-font-regular); flex-direction: column; @@ -636,86 +888,26 @@ display: flex; } -.canon-TextFieldLabelWrapper { - margin-bottom: var(--canon-space-3); - gap: var(--canon-space-1); - flex-direction: column; - display: flex; -} - -.canon-TextFieldLabelWrapper[data-hidden] { - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; - width: 1px; - height: 1px; - margin: -1px; - padding: 0; - position: absolute; - overflow: hidden; -} - -.canon-TextFieldLabel { - color: var(--canon-fg-primary); - cursor: pointer; - margin-right: auto; -} - -.canon-TextFieldLabel[data-size="small"] { - font-weight: var(--canon-font-weight-regular); - font-size: var(--canon-font-size-2); -} - -.canon-TextFieldLabel[data-size="medium"] { - font-weight: var(--canon-font-weight-bold); - font-size: var(--canon-font-size-3); -} - -.canon-TextFieldLabel[data-disabled] { - cursor: default; -} - -.canon-TextFieldSecondaryLabel { - color: var(--canon-fg-secondary); - font-weight: var(--canon-font-weight-regular); - margin-left: var(--canon-space-1); -} - -.canon-TextFieldDescription { - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-secondary); - margin: 0; -} - -.canon-TextFieldDescription[data-size="small"] { - font-size: var(--canon-font-size-2); -} - -.canon-TextFieldDescription[data-size="medium"] { - font-size: var(--canon-font-size-3); -} - -.canon-TextFieldError { - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-danger); - padding-top: var(--canon-space-1_5); - margin: 0; -} - .canon-TextFieldInputWrapper { - padding: 0 var(--canon-space-3); - border-radius: var(--canon-radius-3); - border: 1px solid var(--canon-border); - background-color: var(--canon-bg-surface-1); - align-items: center; - display: flex; + position: relative; +} + +.canon-TextFieldInputWrapper[data-size="small"] { + height: 2rem; +} + +.canon-TextFieldInputWrapper[data-size="medium"] { + height: 2.5rem; } .canon-TextFieldIcon { + left: var(--canon-space-3); margin-right: var(--canon-space-1); color: var(--canon-fg-primary); flex-shrink: 0; + position: absolute; + top: 50%; + transform: translateY(-50%); } .canon-TextFieldIcon[data-size="small"], .canon-TextFieldIcon[data-size="small"] svg { @@ -729,6 +921,10 @@ } .canon-TextFieldInput { + padding: 0 var(--canon-space-3); + border-radius: var(--canon-radius-3); + border: 1px solid var(--canon-border); + background-color: var(--canon-bg-surface-1); font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); @@ -736,299 +932,48 @@ width: 100%; height: 100%; cursor: inherit; - background: none; - border: none; - padding: 0; + align-items: center; transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; -} - -.canon-TextFieldInput:not([data-filled]):has( + .canon-TextFieldClearButton) { - padding-right: 1.25rem; -} - -.canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration { - appearance: none; -} - -.canon-TextFieldClearButton { - margin-left: var(--canon-space-1); - vertical-align: middle; - color: var(--canon-fg-primary); - background: none; - border: none; - padding: 0; - display: none; -} - -.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { - display: inline-block; -} - -.canon-TextFieldClearButtonIcon { - display: block; + display: flex; } .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } +.canon-TextFieldInput[data-icon] { + padding-left: var(--canon-space-8); +} + .canon-TextFieldInput[data-focused] { outline-color: var(--canon-border-pressed); outline-width: 0; } -.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:hover) { +.canon-TextFieldInput[data-hovered] { border-color: var(--canon-border-hover); } -.canon-TextField[data-focused] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-focused] { border-color: var(--canon-border-pressed); + outline-width: 0; } -.canon-TextField[data-invalid] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-invalid] { border-color: var(--canon-fg-danger); } -.canon-TextField[data-disabled] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-disabled] { opacity: .5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } -.canon-TextField[data-disabled] .canon-TextFieldClearButton { - cursor: inherit; -} - -.canon-TextFieldInputWrapper[data-size="small"] { - height: 2rem; -} - -.canon-TextFieldInputWrapper[data-size="medium"] { - height: 2.5rem; -} - -.canon-MenuPositioner { - outline: 0; -} - -.canon-MenuPopup { - background-color: var(--canon-bg-surface-1); - border: 1px solid var(--canon-border); - color: var(--canon-fg-primary); - transform-origin: var(--transform-origin); - max-width: min(var(--available-width), 340px); - max-height: min(var(--available-height), 500px); - padding-bottom: var(--canon-space-1); - border-radius: .375rem; - outline: none; - flex-direction: column; - transition: transform .15s, opacity .15s; - display: flex; - position: relative; - overflow: auto; - - &[data-starting-style], &[data-ending-style] { - opacity: 0; - transform: scale(.9); - } -} - -.canon-MenuItem { - user-select: none; - align-items: center; - gap: var(--canon-space-2); - height: 32px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - margin-inline: var(--canon-space-1); - padding-inline: var(--canon-space-2); - font-size: var(--canon-font-size-3); - cursor: pointer; - outline: 0; - flex-shrink: 0; - text-decoration: none; - display: flex; - - &:first-child { - margin-top: var(--canon-space-1); - } - - &[data-highlighted] { - background-color: var(--canon-gray-3); - } -} - -.canon-MenuSubmenuTrigger { - user-select: none; - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - height: 32px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - margin-inline: var(--canon-space-1); - padding-inline: var(--canon-space-2); - font-size: var(--canon-font-size-3); - cursor: pointer; - outline: 0; - flex-shrink: 0; - text-decoration: none; - display: flex; - - & .canon-Icon { - color: var(--canon-fg-secondary); - } - - &:first-child { - margin-top: var(--canon-space-1); - } - - &[data-popup-open], &[data-highlighted] { - background-color: var(--canon-gray-3); - - & .canon-Icon { - color: var(--canon-fg-primary); - } - } -} - -.canon-MenuSeparator { - background-color: var(--color-gray-200); - height: 1px; - margin: .375rem 1rem; -} - -.canon-SubmenuComboboxSearch { - padding-inline: var(--canon-space-3); - border: none; - border-bottom: 1px solid var(--canon-border); - background-color: var(--canon-bg-surface-1); - height: 32px; - color: var(--canon-fg-primary); - line-height: 140%; - font-size: var(--canon-font-size-3); - z-index: 1; - outline: none; - position: sticky; - top: 0; - - &::placeholder { - color: var(--canon-fg-secondary); - } - - &:disabled { - opacity: .6; - cursor: not-allowed; - } -} - -.canon-SubmenuComboboxItems { - padding-top: var(--canon-space-2); - outline: none; - flex-direction: column; - display: flex; - overflow-y: auto; -} - -.canon-SubmenuComboboxNoResults { - padding-inline: var(--canon-space-3); - padding-top: var(--canon-space-2); - padding-bottom: var(--canon-space-4); - color: var(--canon-fg-secondary); - font-size: var(--canon-font-size-3); -} - -.canon-SubmenuComboboxItem { - user-select: none; - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - height: 32px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - margin-inline: var(--canon-space-1); - padding-inline: var(--canon-space-2); - font-size: var(--canon-font-size-3); - cursor: pointer; - outline: 0; - flex-shrink: 0; - text-decoration: none; - display: flex; - - &[data-highlighted] { - background-color: var(--canon-gray-3); - } - - &[data-disabled] { - opacity: .5; - cursor: not-allowed; - } -} - -.canon-SubmenuComboboxItemCheckbox { - width: 16px; - height: 16px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - border: 1px solid var(--canon-border); - background: var(--canon-bg-surface-1); - flex-shrink: 0; - justify-content: center; - align-items: center; - display: flex; -} - -.canon-SubmenuComboboxItemLabel { - text-overflow: ellipsis; - white-space: nowrap; - flex: 1; - overflow: hidden; -} - -.canon-Link { - font-family: var(--canon-font-regular); - color: var(--canon-fg-link); - cursor: pointer; - margin: 0; - padding: 0; - text-decoration-line: none; - - &:hover { - color: var(--canon-fg-link-hover); - text-underline-offset: calc(.025em + 2px); - text-decoration-line: underline; - text-decoration-style: solid; - text-decoration-thickness: min(2px, max(1px, .05em)); - text-decoration-color: color-mix(in srgb, var(--canon-fg-link-hover) 30%, transparent); - } -} - -.canon-Link[data-variant="body"] { - font-size: var(--canon-font-size-3); - line-height: 140%; -} - -.canon-Link[data-variant="subtitle"] { - font-size: var(--canon-font-size-4); - line-height: 140%; -} - -.canon-Link[data-variant="caption"] { +.canon-TextFieldError { + color: var(--canon-fg-danger); font-size: var(--canon-font-size-2); - line-height: 140%; -} - -.canon-Link[data-variant="label"] { - font-size: var(--canon-font-size-1); - line-height: 140%; -} - -.canon-Link[data-weight="regular"] { font-weight: var(--canon-font-weight-regular); -} - -.canon-Link[data-weight="bold"] { - font-weight: var(--canon-font-weight-bold); + margin-top: var(--canon-space-2); } .canon-TooltipPopup { diff --git a/packages/canon/css/fieldlabel.css b/packages/canon/css/fieldlabel.css new file mode 100644 index 0000000000..e8369ae92e --- /dev/null +++ b/packages/canon/css/fieldlabel.css @@ -0,0 +1,27 @@ +.canon-TextFieldLabelWrapper { + margin-bottom: var(--canon-space-3); + gap: var(--canon-space-1); + flex-direction: column; + display: flex; +} + +.canon-TextFieldLabel { + color: var(--canon-fg-primary); + cursor: pointer; + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); + margin-right: auto; +} + +.canon-TextFieldSecondaryLabel { + color: var(--canon-fg-secondary); + font-weight: var(--canon-font-weight-regular); + margin-left: var(--canon-space-1); +} + +.canon-TextFieldDescription { + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); + color: var(--canon-fg-secondary); + margin: 0; +} diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index 83329e817c..d6576ddb42 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -9368,80 +9368,6 @@ height: 1.25rem; } -.canon-CollapsiblePanel { - height: var(--collapsible-panel-height); - transition: all .15s ease-out; - display: flex; - overflow: hidden; - - &[data-starting-style], &[data-ending-style] { - height: 0; - } -} - -.canon-DataTableRoot { - gap: var(--canon-space-3); - flex-direction: column; - display: flex; -} - -.canon-DataTablePagination { - padding-top: var(--canon-space-5); - justify-content: space-between; - align-items: center; - display: flex; -} - -.canon-DataTablePagination--left { - justify-content: space-between; - align-items: center; - display: flex; -} - -.canon-DataTablePagination--right { - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - display: flex; -} - -.canon-DataTablePagination--select { - min-width: 10.5rem; -} - -.canon-Flex { - min-width: 0; - display: flex; -} - -.canon-Grid { - display: grid; -} - -.canon-Container { - max-width: 120rem; - padding-inline: var(--canon-space-4); - margin-inline: auto; - transition: padding .2s ease-in-out; -} - -@media (width >= 640px) { - .canon-Container { - padding-inline: var(--canon-space-8); - } -} - -@media (width >= 1024px) { - .canon-Container { - padding-inline: var(--canon-space-12); - } -} - -.canon-Icon { - width: 1rem; - height: 1rem; -} - .canon-CheckboxRoot { width: 1rem; height: 1rem; @@ -9495,6 +9421,474 @@ display: flex; } +.canon-CollapsiblePanel { + height: var(--collapsible-panel-height); + transition: all .15s ease-out; + display: flex; + overflow: hidden; + + &[data-starting-style], &[data-ending-style] { + height: 0; + } +} + +.canon-Container { + max-width: 120rem; + padding-inline: var(--canon-space-4); + margin-inline: auto; + transition: padding .2s ease-in-out; +} + +@media (width >= 640px) { + .canon-Container { + padding-inline: var(--canon-space-8); + } +} + +@media (width >= 1024px) { + .canon-Container { + padding-inline: var(--canon-space-12); + } +} + +.canon-DataTableRoot { + gap: var(--canon-space-3); + flex-direction: column; + display: flex; +} + +.canon-DataTablePagination { + padding-top: var(--canon-space-5); + justify-content: space-between; + align-items: center; + display: flex; +} + +.canon-DataTablePagination--left { + justify-content: space-between; + align-items: center; + display: flex; +} + +.canon-DataTablePagination--right { + justify-content: space-between; + align-items: center; + gap: var(--canon-space-2); + display: flex; +} + +.canon-DataTablePagination--select { + min-width: 10.5rem; +} + +.canon-TextFieldLabelWrapper { + margin-bottom: var(--canon-space-3); + gap: var(--canon-space-1); + flex-direction: column; + display: flex; +} + +.canon-TextFieldLabel { + color: var(--canon-fg-primary); + cursor: pointer; + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); + margin-right: auto; +} + +.canon-TextFieldSecondaryLabel { + color: var(--canon-fg-secondary); + font-weight: var(--canon-font-weight-regular); + margin-left: var(--canon-space-1); +} + +.canon-TextFieldDescription { + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); + color: var(--canon-fg-secondary); + margin: 0; +} + +.canon-Flex { + min-width: 0; + display: flex; +} + +.canon-Grid { + display: grid; +} + +.canon-Heading { + font-family: var(--canon-font-regular); + color: var(--canon-fg-primary); + margin: 0; + padding: 0; + line-height: 100%; +} + +.canon-Heading[data-variant="display"] { + font-size: var(--canon-font-size-10); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title1"] { + font-size: var(--canon-font-size-9); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title2"] { + font-size: var(--canon-font-size-8); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title3"] { + font-size: var(--canon-font-size-7); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title4"] { + font-size: var(--canon-font-size-6); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-variant="title5"] { + font-size: var(--canon-font-size-5); + font-weight: var(--canon-font-weight-bold); +} + +.canon-Heading[data-color="primary"] { + color: var(--canon-fg-primary); +} + +.canon-Heading[data-color="secondary"] { + color: var(--canon-fg-secondary); +} + +.canon-Heading[data-truncate] { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.canon-Icon { + width: 1rem; + height: 1rem; +} + +.canon-IconButton { + user-select: none; + font-family: var(--canon-font-regular); + font-weight: var(--canon-font-weight-bold); + cursor: pointer; + border-radius: var(--canon-radius-2); + justify-content: center; + align-items: center; + gap: var(--canon-space-1_5); + border: none; + padding: 0; + display: inline-flex; + + &:disabled { + cursor: not-allowed; + } +} + +.canon-IconButton[data-variant="primary"] { + background-color: var(--canon-bg-solid); + color: var(--canon-fg-solid); + transition: background-color .15s, box-shadow .15s; + + &:hover { + background-color: var(--canon-bg-solid-hover); + } + + &:active { + background-color: var(--canon-bg-solid-pressed); + } + + &:focus-visible { + outline: 2px solid var(--canon-ring); + outline-offset: 2px; + } + + &:disabled { + background-color: var(--canon-bg-solid-disabled); + color: var(--canon-fg-solid-disabled); + } +} + +.canon-IconButton[data-variant="secondary"] { + background-color: var(--canon-bg-surface-1); + box-shadow: inset 0 0 0 1px var(--canon-border); + color: var(--canon-fg-primary); + transition: box-shadow .15s; + + &:hover { + box-shadow: inset 0 0 0 1px var(--canon-border-hover); + } + + &:active { + box-shadow: inset 0 0 0 1px var(--canon-border-pressed); + } + + &:focus-visible { + box-shadow: inset 0 0 0 2px var(--canon-ring); + outline: none; + transition: none; + } + + &:disabled { + box-shadow: inset 0 0 0 1px var(--canon-border-disabled); + color: var(--canon-fg-disabled); + } +} + +.canon-IconButton[data-size="medium"] { + font-size: var(--canon-font-size-4); + width: 40px; + height: 40px; +} + +.canon-IconButton[data-size="small"] { + font-size: var(--canon-font-size-3); + width: 32px; + height: 32px; +} + +.canon-IconButtonIcon[data-size="small"], .canon-IconButtonIcon[data-size="small"] svg { + width: 1rem; + height: 1rem; +} + +.canon-IconButtonIcon[data-size="medium"], .canon-IconButtonIcon[data-size="medium"] svg { + width: 1.25rem; + height: 1.25rem; +} + +.canon-Link { + font-family: var(--canon-font-regular); + color: var(--canon-fg-link); + cursor: pointer; + margin: 0; + padding: 0; + text-decoration-line: none; + + &:hover { + color: var(--canon-fg-link-hover); + text-underline-offset: calc(.025em + 2px); + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-thickness: min(2px, max(1px, .05em)); + text-decoration-color: color-mix(in srgb, var(--canon-fg-link-hover) 30%, transparent); + } +} + +.canon-Link[data-variant="body"] { + font-size: var(--canon-font-size-3); + line-height: 140%; +} + +.canon-Link[data-variant="subtitle"] { + font-size: var(--canon-font-size-4); + line-height: 140%; +} + +.canon-Link[data-variant="caption"] { + font-size: var(--canon-font-size-2); + line-height: 140%; +} + +.canon-Link[data-variant="label"] { + font-size: var(--canon-font-size-1); + line-height: 140%; +} + +.canon-Link[data-weight="regular"] { + font-weight: var(--canon-font-weight-regular); +} + +.canon-Link[data-weight="bold"] { + font-weight: var(--canon-font-weight-bold); +} + +.canon-MenuPositioner { + outline: 0; +} + +.canon-MenuPopup { + background-color: var(--canon-bg-surface-1); + border: 1px solid var(--canon-border); + color: var(--canon-fg-primary); + transform-origin: var(--transform-origin); + max-width: min(var(--available-width), 340px); + max-height: min(var(--available-height), 500px); + padding-bottom: var(--canon-space-1); + border-radius: .375rem; + outline: none; + flex-direction: column; + transition: transform .15s, opacity .15s; + display: flex; + position: relative; + overflow: auto; + + &[data-starting-style], &[data-ending-style] { + opacity: 0; + transform: scale(.9); + } +} + +.canon-MenuItem { + user-select: none; + align-items: center; + gap: var(--canon-space-2); + height: 32px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + margin-inline: var(--canon-space-1); + padding-inline: var(--canon-space-2); + font-size: var(--canon-font-size-3); + cursor: pointer; + outline: 0; + flex-shrink: 0; + text-decoration: none; + display: flex; + + &:first-child { + margin-top: var(--canon-space-1); + } + + &[data-highlighted] { + background-color: var(--canon-gray-3); + } +} + +.canon-MenuSubmenuTrigger { + user-select: none; + justify-content: space-between; + align-items: center; + gap: var(--canon-space-2); + height: 32px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + margin-inline: var(--canon-space-1); + padding-inline: var(--canon-space-2); + font-size: var(--canon-font-size-3); + cursor: pointer; + outline: 0; + flex-shrink: 0; + text-decoration: none; + display: flex; + + & .canon-Icon { + color: var(--canon-fg-secondary); + } + + &:first-child { + margin-top: var(--canon-space-1); + } + + &[data-popup-open], &[data-highlighted] { + background-color: var(--canon-gray-3); + + & .canon-Icon { + color: var(--canon-fg-primary); + } + } +} + +.canon-MenuSeparator { + background-color: var(--color-gray-200); + height: 1px; + margin: .375rem 1rem; +} + +.canon-SubmenuComboboxSearch { + padding-inline: var(--canon-space-3); + border: none; + border-bottom: 1px solid var(--canon-border); + background-color: var(--canon-bg-surface-1); + height: 32px; + color: var(--canon-fg-primary); + line-height: 140%; + font-size: var(--canon-font-size-3); + z-index: 1; + outline: none; + position: sticky; + top: 0; + + &::placeholder { + color: var(--canon-fg-secondary); + } + + &:disabled { + opacity: .6; + cursor: not-allowed; + } +} + +.canon-SubmenuComboboxItems { + padding-top: var(--canon-space-2); + outline: none; + flex-direction: column; + display: flex; + overflow-y: auto; +} + +.canon-SubmenuComboboxNoResults { + padding-inline: var(--canon-space-3); + padding-top: var(--canon-space-2); + padding-bottom: var(--canon-space-4); + color: var(--canon-fg-secondary); + font-size: var(--canon-font-size-3); +} + +.canon-SubmenuComboboxItem { + user-select: none; + justify-content: space-between; + align-items: center; + gap: var(--canon-space-2); + height: 32px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + margin-inline: var(--canon-space-1); + padding-inline: var(--canon-space-2); + font-size: var(--canon-font-size-3); + cursor: pointer; + outline: 0; + flex-shrink: 0; + text-decoration: none; + display: flex; + + &[data-highlighted] { + background-color: var(--canon-gray-3); + } + + &[data-disabled] { + opacity: .5; + cursor: not-allowed; + } +} + +.canon-SubmenuComboboxItemCheckbox { + width: 16px; + height: 16px; + color: var(--canon-fg-primary); + border-radius: var(--canon-radius-2); + border: 1px solid var(--canon-border); + background: var(--canon-bg-surface-1); + flex-shrink: 0; + justify-content: center; + align-items: center; + display: flex; +} + +.canon-SubmenuComboboxItemLabel { + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + overflow: hidden; +} + .canon-TableRoot { caption-side: bottom; border-collapse: collapse; @@ -9711,148 +10105,6 @@ overflow: hidden; } -.canon-Heading { - font-family: var(--canon-font-regular); - color: var(--canon-fg-primary); - margin: 0; - padding: 0; - line-height: 100%; -} - -.canon-Heading[data-variant="display"] { - font-size: var(--canon-font-size-10); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title1"] { - font-size: var(--canon-font-size-9); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title2"] { - font-size: var(--canon-font-size-8); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title3"] { - font-size: var(--canon-font-size-7); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title4"] { - font-size: var(--canon-font-size-6); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title5"] { - font-size: var(--canon-font-size-5); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-color="primary"] { - color: var(--canon-fg-primary); -} - -.canon-Heading[data-color="secondary"] { - color: var(--canon-fg-secondary); -} - -.canon-Heading[data-truncate] { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -.canon-IconButton { - user-select: none; - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-bold); - cursor: pointer; - border-radius: var(--canon-radius-2); - justify-content: center; - align-items: center; - gap: var(--canon-space-1_5); - border: none; - padding: 0; - display: inline-flex; - - &:disabled { - cursor: not-allowed; - } -} - -.canon-IconButton[data-variant="primary"] { - background-color: var(--canon-bg-solid); - color: var(--canon-fg-solid); - transition: background-color .15s, box-shadow .15s; - - &:hover { - background-color: var(--canon-bg-solid-hover); - } - - &:active { - background-color: var(--canon-bg-solid-pressed); - } - - &:focus-visible { - outline: 2px solid var(--canon-ring); - outline-offset: 2px; - } - - &:disabled { - background-color: var(--canon-bg-solid-disabled); - color: var(--canon-fg-solid-disabled); - } -} - -.canon-IconButton[data-variant="secondary"] { - background-color: var(--canon-bg-surface-1); - box-shadow: inset 0 0 0 1px var(--canon-border); - color: var(--canon-fg-primary); - transition: box-shadow .15s; - - &:hover { - box-shadow: inset 0 0 0 1px var(--canon-border-hover); - } - - &:active { - box-shadow: inset 0 0 0 1px var(--canon-border-pressed); - } - - &:focus-visible { - box-shadow: inset 0 0 0 2px var(--canon-ring); - outline: none; - transition: none; - } - - &:disabled { - box-shadow: inset 0 0 0 1px var(--canon-border-disabled); - color: var(--canon-fg-disabled); - } -} - -.canon-IconButton[data-size="medium"] { - font-size: var(--canon-font-size-4); - width: 40px; - height: 40px; -} - -.canon-IconButton[data-size="small"] { - font-size: var(--canon-font-size-3); - width: 32px; - height: 32px; -} - -.canon-IconButtonIcon[data-size="small"], .canon-IconButtonIcon[data-size="small"] svg { - width: 1rem; - height: 1rem; -} - -.canon-IconButtonIcon[data-size="medium"], .canon-IconButtonIcon[data-size="medium"] svg { - width: 1.25rem; - height: 1.25rem; -} - .canon-TextField { font-family: var(--canon-font-regular); flex-direction: column; @@ -9860,86 +10112,26 @@ display: flex; } -.canon-TextFieldLabelWrapper { - margin-bottom: var(--canon-space-3); - gap: var(--canon-space-1); - flex-direction: column; - display: flex; -} - -.canon-TextFieldLabelWrapper[data-hidden] { - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; - width: 1px; - height: 1px; - margin: -1px; - padding: 0; - position: absolute; - overflow: hidden; -} - -.canon-TextFieldLabel { - color: var(--canon-fg-primary); - cursor: pointer; - margin-right: auto; -} - -.canon-TextFieldLabel[data-size="small"] { - font-weight: var(--canon-font-weight-regular); - font-size: var(--canon-font-size-2); -} - -.canon-TextFieldLabel[data-size="medium"] { - font-weight: var(--canon-font-weight-bold); - font-size: var(--canon-font-size-3); -} - -.canon-TextFieldLabel[data-disabled] { - cursor: default; -} - -.canon-TextFieldSecondaryLabel { - color: var(--canon-fg-secondary); - font-weight: var(--canon-font-weight-regular); - margin-left: var(--canon-space-1); -} - -.canon-TextFieldDescription { - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-secondary); - margin: 0; -} - -.canon-TextFieldDescription[data-size="small"] { - font-size: var(--canon-font-size-2); -} - -.canon-TextFieldDescription[data-size="medium"] { - font-size: var(--canon-font-size-3); -} - -.canon-TextFieldError { - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-danger); - padding-top: var(--canon-space-1_5); - margin: 0; -} - .canon-TextFieldInputWrapper { - padding: 0 var(--canon-space-3); - border-radius: var(--canon-radius-3); - border: 1px solid var(--canon-border); - background-color: var(--canon-bg-surface-1); - align-items: center; - display: flex; + position: relative; +} + +.canon-TextFieldInputWrapper[data-size="small"] { + height: 2rem; +} + +.canon-TextFieldInputWrapper[data-size="medium"] { + height: 2.5rem; } .canon-TextFieldIcon { + left: var(--canon-space-3); margin-right: var(--canon-space-1); color: var(--canon-fg-primary); flex-shrink: 0; + position: absolute; + top: 50%; + transform: translateY(-50%); } .canon-TextFieldIcon[data-size="small"], .canon-TextFieldIcon[data-size="small"] svg { @@ -9953,6 +10145,10 @@ } .canon-TextFieldInput { + padding: 0 var(--canon-space-3); + border-radius: var(--canon-radius-3); + border: 1px solid var(--canon-border); + background-color: var(--canon-bg-surface-1); font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); @@ -9960,299 +10156,48 @@ width: 100%; height: 100%; cursor: inherit; - background: none; - border: none; - padding: 0; + align-items: center; transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; -} - -.canon-TextFieldInput:not([data-filled]):has( + .canon-TextFieldClearButton) { - padding-right: 1.25rem; -} - -.canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration { - appearance: none; -} - -.canon-TextFieldClearButton { - margin-left: var(--canon-space-1); - vertical-align: middle; - color: var(--canon-fg-primary); - background: none; - border: none; - padding: 0; - display: none; -} - -.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { - display: inline-block; -} - -.canon-TextFieldClearButtonIcon { - display: block; + display: flex; } .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } +.canon-TextFieldInput[data-icon] { + padding-left: var(--canon-space-8); +} + .canon-TextFieldInput[data-focused] { outline-color: var(--canon-border-pressed); outline-width: 0; } -.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:hover) { +.canon-TextFieldInput[data-hovered] { border-color: var(--canon-border-hover); } -.canon-TextField[data-focused] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-focused] { border-color: var(--canon-border-pressed); + outline-width: 0; } -.canon-TextField[data-invalid] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-invalid] { border-color: var(--canon-fg-danger); } -.canon-TextField[data-disabled] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-disabled] { opacity: .5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } -.canon-TextField[data-disabled] .canon-TextFieldClearButton { - cursor: inherit; -} - -.canon-TextFieldInputWrapper[data-size="small"] { - height: 2rem; -} - -.canon-TextFieldInputWrapper[data-size="medium"] { - height: 2.5rem; -} - -.canon-MenuPositioner { - outline: 0; -} - -.canon-MenuPopup { - background-color: var(--canon-bg-surface-1); - border: 1px solid var(--canon-border); - color: var(--canon-fg-primary); - transform-origin: var(--transform-origin); - max-width: min(var(--available-width), 340px); - max-height: min(var(--available-height), 500px); - padding-bottom: var(--canon-space-1); - border-radius: .375rem; - outline: none; - flex-direction: column; - transition: transform .15s, opacity .15s; - display: flex; - position: relative; - overflow: auto; - - &[data-starting-style], &[data-ending-style] { - opacity: 0; - transform: scale(.9); - } -} - -.canon-MenuItem { - user-select: none; - align-items: center; - gap: var(--canon-space-2); - height: 32px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - margin-inline: var(--canon-space-1); - padding-inline: var(--canon-space-2); - font-size: var(--canon-font-size-3); - cursor: pointer; - outline: 0; - flex-shrink: 0; - text-decoration: none; - display: flex; - - &:first-child { - margin-top: var(--canon-space-1); - } - - &[data-highlighted] { - background-color: var(--canon-gray-3); - } -} - -.canon-MenuSubmenuTrigger { - user-select: none; - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - height: 32px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - margin-inline: var(--canon-space-1); - padding-inline: var(--canon-space-2); - font-size: var(--canon-font-size-3); - cursor: pointer; - outline: 0; - flex-shrink: 0; - text-decoration: none; - display: flex; - - & .canon-Icon { - color: var(--canon-fg-secondary); - } - - &:first-child { - margin-top: var(--canon-space-1); - } - - &[data-popup-open], &[data-highlighted] { - background-color: var(--canon-gray-3); - - & .canon-Icon { - color: var(--canon-fg-primary); - } - } -} - -.canon-MenuSeparator { - background-color: var(--color-gray-200); - height: 1px; - margin: .375rem 1rem; -} - -.canon-SubmenuComboboxSearch { - padding-inline: var(--canon-space-3); - border: none; - border-bottom: 1px solid var(--canon-border); - background-color: var(--canon-bg-surface-1); - height: 32px; - color: var(--canon-fg-primary); - line-height: 140%; - font-size: var(--canon-font-size-3); - z-index: 1; - outline: none; - position: sticky; - top: 0; - - &::placeholder { - color: var(--canon-fg-secondary); - } - - &:disabled { - opacity: .6; - cursor: not-allowed; - } -} - -.canon-SubmenuComboboxItems { - padding-top: var(--canon-space-2); - outline: none; - flex-direction: column; - display: flex; - overflow-y: auto; -} - -.canon-SubmenuComboboxNoResults { - padding-inline: var(--canon-space-3); - padding-top: var(--canon-space-2); - padding-bottom: var(--canon-space-4); - color: var(--canon-fg-secondary); - font-size: var(--canon-font-size-3); -} - -.canon-SubmenuComboboxItem { - user-select: none; - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - height: 32px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - margin-inline: var(--canon-space-1); - padding-inline: var(--canon-space-2); - font-size: var(--canon-font-size-3); - cursor: pointer; - outline: 0; - flex-shrink: 0; - text-decoration: none; - display: flex; - - &[data-highlighted] { - background-color: var(--canon-gray-3); - } - - &[data-disabled] { - opacity: .5; - cursor: not-allowed; - } -} - -.canon-SubmenuComboboxItemCheckbox { - width: 16px; - height: 16px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - border: 1px solid var(--canon-border); - background: var(--canon-bg-surface-1); - flex-shrink: 0; - justify-content: center; - align-items: center; - display: flex; -} - -.canon-SubmenuComboboxItemLabel { - text-overflow: ellipsis; - white-space: nowrap; - flex: 1; - overflow: hidden; -} - -.canon-Link { - font-family: var(--canon-font-regular); - color: var(--canon-fg-link); - cursor: pointer; - margin: 0; - padding: 0; - text-decoration-line: none; - - &:hover { - color: var(--canon-fg-link-hover); - text-underline-offset: calc(.025em + 2px); - text-decoration-line: underline; - text-decoration-style: solid; - text-decoration-thickness: min(2px, max(1px, .05em)); - text-decoration-color: color-mix(in srgb, var(--canon-fg-link-hover) 30%, transparent); - } -} - -.canon-Link[data-variant="body"] { - font-size: var(--canon-font-size-3); - line-height: 140%; -} - -.canon-Link[data-variant="subtitle"] { - font-size: var(--canon-font-size-4); - line-height: 140%; -} - -.canon-Link[data-variant="caption"] { +.canon-TextFieldError { + color: var(--canon-fg-danger); font-size: var(--canon-font-size-2); - line-height: 140%; -} - -.canon-Link[data-variant="label"] { - font-size: var(--canon-font-size-1); - line-height: 140%; -} - -.canon-Link[data-weight="regular"] { font-weight: var(--canon-font-weight-regular); -} - -.canon-Link[data-weight="bold"] { - font-weight: var(--canon-font-weight-bold); + margin-top: var(--canon-space-2); } .canon-TooltipPopup { diff --git a/packages/canon/css/textfield.css b/packages/canon/css/textfield.css index bdc5a23dbd..fdb3621016 100644 --- a/packages/canon/css/textfield.css +++ b/packages/canon/css/textfield.css @@ -5,86 +5,26 @@ display: flex; } -.canon-TextFieldLabelWrapper { - margin-bottom: var(--canon-space-3); - gap: var(--canon-space-1); - flex-direction: column; - display: flex; -} - -.canon-TextFieldLabelWrapper[data-hidden] { - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; - width: 1px; - height: 1px; - margin: -1px; - padding: 0; - position: absolute; - overflow: hidden; -} - -.canon-TextFieldLabel { - color: var(--canon-fg-primary); - cursor: pointer; - margin-right: auto; -} - -.canon-TextFieldLabel[data-size="small"] { - font-weight: var(--canon-font-weight-regular); - font-size: var(--canon-font-size-2); -} - -.canon-TextFieldLabel[data-size="medium"] { - font-weight: var(--canon-font-weight-bold); - font-size: var(--canon-font-size-3); -} - -.canon-TextFieldLabel[data-disabled] { - cursor: default; -} - -.canon-TextFieldSecondaryLabel { - color: var(--canon-fg-secondary); - font-weight: var(--canon-font-weight-regular); - margin-left: var(--canon-space-1); -} - -.canon-TextFieldDescription { - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-secondary); - margin: 0; -} - -.canon-TextFieldDescription[data-size="small"] { - font-size: var(--canon-font-size-2); -} - -.canon-TextFieldDescription[data-size="medium"] { - font-size: var(--canon-font-size-3); -} - -.canon-TextFieldError { - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-danger); - padding-top: var(--canon-space-1_5); - margin: 0; -} - .canon-TextFieldInputWrapper { - padding: 0 var(--canon-space-3); - border-radius: var(--canon-radius-3); - border: 1px solid var(--canon-border); - background-color: var(--canon-bg-surface-1); - align-items: center; - display: flex; + position: relative; +} + +.canon-TextFieldInputWrapper[data-size="small"] { + height: 2rem; +} + +.canon-TextFieldInputWrapper[data-size="medium"] { + height: 2.5rem; } .canon-TextFieldIcon { + left: var(--canon-space-3); margin-right: var(--canon-space-1); color: var(--canon-fg-primary); flex-shrink: 0; + position: absolute; + top: 50%; + transform: translateY(-50%); } .canon-TextFieldIcon[data-size="small"], .canon-TextFieldIcon[data-size="small"] svg { @@ -98,6 +38,10 @@ } .canon-TextFieldInput { + padding: 0 var(--canon-space-3); + border-radius: var(--canon-radius-3); + border: 1px solid var(--canon-border); + background-color: var(--canon-bg-surface-1); font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); @@ -105,73 +49,46 @@ width: 100%; height: 100%; cursor: inherit; - background: none; - border: none; - padding: 0; + align-items: center; transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; -} - -.canon-TextFieldInput:not([data-filled]):has( + .canon-TextFieldClearButton) { - padding-right: 1.25rem; -} - -.canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration { - appearance: none; -} - -.canon-TextFieldClearButton { - margin-left: var(--canon-space-1); - vertical-align: middle; - color: var(--canon-fg-primary); - background: none; - border: none; - padding: 0; - display: none; -} - -.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { - display: inline-block; -} - -.canon-TextFieldClearButtonIcon { - display: block; + display: flex; } .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } +.canon-TextFieldInput[data-icon] { + padding-left: var(--canon-space-8); +} + .canon-TextFieldInput[data-focused] { outline-color: var(--canon-border-pressed); outline-width: 0; } -.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:hover) { +.canon-TextFieldInput[data-hovered] { border-color: var(--canon-border-hover); } -.canon-TextField[data-focused] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-focused] { border-color: var(--canon-border-pressed); + outline-width: 0; } -.canon-TextField[data-invalid] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-invalid] { border-color: var(--canon-fg-danger); } -.canon-TextField[data-disabled] .canon-TextFieldInputWrapper { +.canon-TextFieldInput[data-disabled] { opacity: .5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } -.canon-TextField[data-disabled] .canon-TextFieldClearButton { - cursor: inherit; -} - -.canon-TextFieldInputWrapper[data-size="small"] { - height: 2rem; -} - -.canon-TextFieldInputWrapper[data-size="medium"] { - height: 2.5rem; +.canon-TextFieldError { + color: var(--canon-fg-danger); + font-size: var(--canon-font-size-2); + font-weight: var(--canon-font-weight-regular); + margin-top: var(--canon-space-2); } diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index bcc32ef651..21304fdaa7 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -16,7 +16,6 @@ import { ForwardRefExoticComponent } from 'react'; import { HTMLAttributes } from 'react'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { Menu as Menu_2 } from '@base-ui-components/react/menu'; -import type { MouseEventHandler } from 'react'; import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { RefAttributes } from 'react'; @@ -26,6 +25,7 @@ import type { SwitchProps as SwitchProps_2 } from 'react-aria-components'; import { Table as Table_2 } from '@tanstack/react-table'; import { Tabs as Tabs_2 } from '@base-ui-components/react/tabs'; import { TdHTMLAttributes } from 'react'; +import type { TextFieldProps } from 'react-aria-components'; import { ThHTMLAttributes } from 'react'; import { Tooltip as Tooltip_2 } from '@base-ui-components/react/tooltip'; import type { useRender } from '@base-ui-components/react/use-render'; @@ -354,6 +354,18 @@ export type EnumPropDef = { required?: boolean; }; +// @public (undocumented) +export const FieldLabel: ForwardRefExoticComponent< + FieldLabelProps & RefAttributes +>; + +// @public (undocumented) +export interface FieldLabelProps { + description?: string | null; + label?: string | null; + secondaryLabel?: string | null; +} + // @public (undocumented) export const Flex: ForwardRefExoticComponent< FlexProps & RefAttributes @@ -408,6 +420,14 @@ export interface FlexProps extends SpaceProps { // @public (undocumented) export type FlexWrap = 'wrap' | 'nowrap' | 'wrap-reverse'; +// @public (undocumented) +export interface FormInputProps + extends Omit, + FieldLabelProps { + icon?: ReactNode; + size?: 'small' | 'medium' | Partial>; +} + // @public (undocumented) export const gapPropDefs: { gap: { @@ -1276,25 +1296,9 @@ export { Text_2 as Text }; // @public (undocumented) export const TextField: ForwardRefExoticComponent< - TextFieldProps & RefAttributes + FormInputProps & RefAttributes >; -// @public (undocumented) -export interface TextFieldProps - extends Omit, 'size'> { - className?: string; - description?: string; - error?: string | null; - hideLabelAndDescription?: boolean; - icon?: ReactNode; - label?: string; - labelSize?: 'small' | 'medium'; - name: string; - onClear?: MouseEventHandler; - secondaryLabel?: string; - size?: 'small' | 'medium' | Partial>; -} - // @public (undocumented) export interface TextProps extends Omit, 'color'> { diff --git a/packages/canon/src/components/FieldLabel/FieldLabel.stories.tsx b/packages/canon/src/components/FieldLabel/FieldLabel.stories.tsx new file mode 100644 index 0000000000..a82639eca0 --- /dev/null +++ b/packages/canon/src/components/FieldLabel/FieldLabel.stories.tsx @@ -0,0 +1,65 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { FieldLabel } from './FieldLabel'; + +const meta = { + title: 'Forms/FieldLabel', + component: FieldLabel, + argTypes: { + label: { + control: 'text', + }, + secondaryLabel: { + control: 'text', + }, + description: { + control: 'text', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + label: 'Label', + }, +}; + +export const WithSecondaryLabel: Story = { + args: { + ...Default.args, + secondaryLabel: 'Secondary Label', + }, +}; + +export const WithDescription: Story = { + args: { + ...Default.args, + description: 'Description', + }, +}; + +export const WithAllFields: Story = { + args: { + ...Default.args, + secondaryLabel: 'Secondary Label', + description: 'Description', + }, +}; diff --git a/packages/canon/src/components/FieldLabel/FieldLabel.styles.css b/packages/canon/src/components/FieldLabel/FieldLabel.styles.css new file mode 100644 index 0000000000..5fee3d76a4 --- /dev/null +++ b/packages/canon/src/components/FieldLabel/FieldLabel.styles.css @@ -0,0 +1,27 @@ +.canon-TextFieldLabelWrapper { + display: flex; + flex-direction: column; + margin-bottom: var(--canon-space-3); + gap: var(--canon-space-1); +} + +.canon-TextFieldLabel { + color: var(--canon-fg-primary); + margin-right: auto; + cursor: pointer; + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); +} + +.canon-TextFieldSecondaryLabel { + color: var(--canon-fg-secondary); + font-weight: var(--canon-font-weight-regular); + margin-left: var(--canon-space-1); +} + +.canon-TextFieldDescription { + font-weight: var(--canon-font-weight-regular); + font-size: var(--canon-font-size-2); + color: var(--canon-fg-secondary); + margin: 0; +} diff --git a/packages/canon/src/components/FieldLabel/FieldLabel.tsx b/packages/canon/src/components/FieldLabel/FieldLabel.tsx new file mode 100644 index 0000000000..713f7fb78b --- /dev/null +++ b/packages/canon/src/components/FieldLabel/FieldLabel.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Label } from 'react-aria-components'; +import { forwardRef } from 'react'; +import type { FieldLabelProps } from './types'; + +/** @public */ +export const FieldLabel = forwardRef( + (props: FieldLabelProps, ref) => { + const { label, secondaryLabel, description } = props; + + if (!label) return null; + + return ( +
+ {label && ( + + )} + {description && ( +
{description}
+ )} +
+ ); + }, +); + +FieldLabel.displayName = 'FieldLabel'; diff --git a/packages/canon/src/components/FormInput/index.ts b/packages/canon/src/components/FieldLabel/index.ts similarity index 88% rename from packages/canon/src/components/FormInput/index.ts rename to packages/canon/src/components/FieldLabel/index.ts index 29df00ccaf..b9cebb1bf7 100644 --- a/packages/canon/src/components/FormInput/index.ts +++ b/packages/canon/src/components/FieldLabel/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -export * from './TextField'; -export type { TextFieldProps } from './types'; +export * from './FieldLabel'; +export * from './types'; diff --git a/packages/canon/src/components/FormInput/types.ts b/packages/canon/src/components/FieldLabel/types.ts similarity index 61% rename from packages/canon/src/components/FormInput/types.ts rename to packages/canon/src/components/FieldLabel/types.ts index 1f28436259..41c6f390d5 100644 --- a/packages/canon/src/components/FormInput/types.ts +++ b/packages/canon/src/components/FieldLabel/types.ts @@ -14,20 +14,20 @@ * limitations under the License. */ -import { InputProps } from 'react-aria-components'; -import { ReactNode } from 'react'; -import type { Breakpoint } from '../../types'; - /** @public */ -export interface FormInputProps extends Omit { +export interface FieldLabelProps { /** - * An icon to render before the input + * The label of the text field */ - icon?: ReactNode; + label?: string | null; /** - * The size of the text field - * @defaultValue 'medium' + * The secondary label of the text field */ - size?: 'small' | 'medium' | Partial>; + secondaryLabel?: string | null; + + /** + * The description of the text field + */ + description?: string | null; } diff --git a/packages/canon/src/components/FormInput/FormInput.stories.tsx b/packages/canon/src/components/FormInput/FormInput.stories.tsx deleted file mode 100644 index 8210bedb7c..0000000000 --- a/packages/canon/src/components/FormInput/FormInput.stories.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { Meta, StoryObj } from '@storybook/react'; -import { FormInput } from './FormInput'; -import { Icon } from '../Icon'; -import { Flex } from '../Flex'; - -const meta = { - title: 'Forms/FormInput', - component: FormInput, - argTypes: { - required: { - control: 'boolean', - }, - icon: { - control: 'object', - }, - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - name: 'url', - placeholder: 'Enter a URL', - style: { - maxWidth: '300px', - }, - }, -}; - -export const Sizes: Story = { - args: { - ...Default.args, - }, - render: args => ( - - } /> - } /> - - ), -}; - -export const Filled: Story = { - args: { - ...Default.args, - defaultValue: 'https://example.com', - }, -}; - -export const Disabled: Story = { - args: { - ...Default.args, - disabled: true, - }, -}; - -export const WithIcon: Story = { - args: { - ...Default.args, - placeholder: 'Search...', - icon: , - }, -}; - -export const DisabledWithIcon: Story = { - args: { - ...WithIcon.args, - disabled: true, - }, -}; diff --git a/packages/canon/src/components/FormInput/FormInput.styles.css b/packages/canon/src/components/FormInput/FormInput.styles.css deleted file mode 100644 index 4415247799..0000000000 --- a/packages/canon/src/components/FormInput/FormInput.styles.css +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -.canon-FormInput { - display: flex; - align-items: center; - padding: 0 var(--canon-space-3); - border-radius: var(--canon-radius-3); - border: 1px solid var(--canon-border); - background-color: var(--canon-bg-surface-1); -} - -.canon-FormInputIcon { - margin-right: var(--canon-space-1); - color: var(--canon-fg-primary); - flex-shrink: 0; -} - -.canon-FormInputIcon[data-size='small'], -.canon-FormInputIcon[data-size='small'] svg { - width: 1rem; - height: 1rem; -} - -.canon-FormInputIcon[data-size='medium'], -.canon-FormInputIcon[data-size='medium'] svg { - width: 1.25rem; - height: 1.25rem; -} - -.canon-Input { - border: none; - background: none; - font-size: var(--canon-font-size-3); - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-primary); - transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out; - width: 100%; - height: 100%; - cursor: inherit; - padding: 0; -} - -.canon-Input::placeholder { - color: var(--canon-fg-secondary); -} - -.canon-FormInput[data-size='small'] { - height: 2rem; -} - -.canon-FormInput[data-size='medium'] { - height: 2.5rem; -} - -.canon-Input[data-focused] { - outline-color: var(--canon-border-pressed); - outline-width: 0px; -} - -.canon-FormInput:has(> .canon-Input:hover) { - border-color: var(--canon-border-hover); -} - -.canon-FormInput[data-focused] { - border-color: var(--canon-border-pressed); -} - -.canon-FormInput[data-invalid] { - border-color: var(--canon-fg-danger); -} - -.canon-FormInput[data-disabled] { - opacity: 0.5; - cursor: not-allowed; - border: 1px solid var(--canon-border-disabled); -} diff --git a/packages/canon/src/components/FormInput/FormInput.tsx b/packages/canon/src/components/FormInput/FormInput.tsx deleted file mode 100644 index 9e9ca9855f..0000000000 --- a/packages/canon/src/components/FormInput/FormInput.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { forwardRef } from 'react'; -import { Input } from 'react-aria-components'; -import { useResponsiveValue } from '../../hooks/useResponsiveValue'; -import clsx from 'clsx'; - -import type { FormInputProps } from './types'; - -/** @public */ -export const FormInput = forwardRef( - (props: FormInputProps, ref) => { - const { className, icon, size = 'small', ...rest } = props; - - // Get the responsive value for the variant - const responsiveSize = useResponsiveValue(size); - - return ( -
- {icon && ( - - )} - -
- ); - }, -); - -FormInput.displayName = 'FormInput'; diff --git a/packages/canon/src/components/TextField/TextField.styles.css b/packages/canon/src/components/TextField/TextField.styles.css index aeb49cb8fc..9f2039b3af 100644 --- a/packages/canon/src/components/TextField/TextField.styles.css +++ b/packages/canon/src/components/TextField/TextField.styles.css @@ -21,34 +21,6 @@ width: 100%; } -.canon-TextFieldLabelWrapper { - display: flex; - flex-direction: column; - margin-bottom: var(--canon-space-3); - gap: var(--canon-space-1); -} - -.canon-TextFieldLabel { - color: var(--canon-fg-primary); - margin-right: auto; - cursor: pointer; - font-weight: var(--canon-font-weight-regular); - font-size: var(--canon-font-size-2); -} - -.canon-TextFieldSecondaryLabel { - color: var(--canon-fg-secondary); - font-weight: var(--canon-font-weight-regular); - margin-left: var(--canon-space-1); -} - -.canon-TextFieldDescription { - font-weight: var(--canon-font-weight-regular); - font-size: var(--canon-font-size-2); - color: var(--canon-fg-secondary); - margin: 0; -} - .canon-TextFieldInputWrapper { position: relative; } diff --git a/packages/canon/src/components/TextField/TextField.tsx b/packages/canon/src/components/TextField/TextField.tsx index 62bc5633cd..bab47c6434 100644 --- a/packages/canon/src/components/TextField/TextField.tsx +++ b/packages/canon/src/components/TextField/TextField.tsx @@ -18,11 +18,11 @@ import { forwardRef, useEffect } from 'react'; import { Input, TextField as AriaTextField, - Label, FieldError, } from 'react-aria-components'; import { useResponsiveValue } from '../../hooks/useResponsiveValue'; import clsx from 'clsx'; +import { FieldLabel } from '../FieldLabel'; import type { FormInputProps } from './types'; @@ -64,26 +64,11 @@ export const TextField = forwardRef( {...rest} ref={ref} > - {label && ( -
- {label && ( - - )} - {description && ( -
{description}
- )} -
- )} +
{icon && (
{ +export interface FormInputProps + extends Omit, + FieldLabelProps { /** * An icon to render before the input */ @@ -30,24 +33,4 @@ export interface FormInputProps extends Omit { * @defaultValue 'medium' */ size?: 'small' | 'medium' | Partial>; - - /** - * The label of the text field - */ - label?: string; - - /** - * The secondary label of the text field - */ - secondaryLabel?: string; - - /** - * The description of the text field - */ - description?: string; - - /** - * Whether the field is required - */ - isRequired?: boolean; } diff --git a/packages/canon/src/css/components.css b/packages/canon/src/css/components.css index 1f4c6bad99..c85c1bbdd9 100644 --- a/packages/canon/src/css/components.css +++ b/packages/canon/src/css/components.css @@ -17,15 +17,19 @@ @import '../components/Avatar/Avatar.styles.css'; @import '../components/Box/styles.css'; @import '../components/Button/styles.css'; +@import '../components/Checkbox/styles.css'; @import '../components/Collapsible/Collapsible.styles.css'; +@import '../components/Container/styles.css'; @import '../components/DataTable/Root/DataTableRoot.styles.css'; @import '../components/DataTable/Pagination/DataTablePagination.styles.css'; +@import '../components/FieldLabel/FieldLabel.styles.css'; @import '../components/Flex/styles.css'; -@import '../components/FormInput/FormInput.styles.css'; @import '../components/Grid/styles.css'; -@import '../components/Container/styles.css'; +@import '../components/Heading/styles.css'; @import '../components/Icon/styles.css'; -@import '../components/Checkbox/styles.css'; +@import '../components/IconButton/styles.css'; +@import '../components/Link/styles.css'; +@import '../components/Menu/Menu.styles.css'; @import '../components/Table/styles.css'; @import '../components/Table/TableCell/TableCell.styles.css'; @import '../components/Table/TableCellText/TableCellText.styles.css'; @@ -33,11 +37,7 @@ @import '../components/Table/TableCellProfile/TableCellProfile.styles.css'; @import '../components/Tabs/Tabs.styles.css'; @import '../components/Text/styles.css'; -@import '../components/Heading/styles.css'; -@import '../components/IconButton/styles.css'; @import '../components/TextField/TextField.styles.css'; -@import '../components/Menu/Menu.styles.css'; -@import '../components/Link/styles.css'; @import '../components/Tooltip/Tooltip.styles.css'; @import '../components/ScrollArea/ScrollArea.styles.css'; @import '../components/Select/Select.styles.css'; diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts index 310a7896c8..9b5a454254 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -36,6 +36,7 @@ export * from './components/Avatar'; export * from './components/Button'; export * from './components/Collapsible'; export * from './components/DataTable'; +export * from './components/FieldLabel'; export * from './components/Icon'; export * from './components/IconButton'; export * from './components/Checkbox'; diff --git a/packages/canon/src/stories/Form.stories.tsx b/packages/canon/src/stories/Form.stories.tsx deleted file mode 100644 index 97e28faaee..0000000000 --- a/packages/canon/src/stories/Form.stories.tsx +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { useState } from 'react'; -import type { Meta, StoryObj } from '@storybook/react'; -import { useForm, SubmitHandler, Controller } from 'react-hook-form'; -import { TextField } from '../components/TextField'; -import { Button } from '../components/Button'; -import { Select } from '../components/Select'; - -const meta = { - title: 'Forms/Form', -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -type Inputs = { - firstname: string; - lastname: string; - city: string; -}; - -export const Uncontrolled: Story = { - render: () => { - const { - register, - handleSubmit, - control, - formState: { errors }, - } = useForm(); - - const onSubmit: SubmitHandler = data => console.log(data); - - return ( -
- - - { - return ( - - ); - }} - /> - - - ); - }, -}; From 1f4f5a2a9e8ecbe3bb81e29fd8f924649f1a8f94 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 16 Jun 2025 23:10:05 +0100 Subject: [PATCH 154/301] Update class names Signed-off-by: Charles de Dreuille --- packages/canon/.storybook/main.ts | 5 +---- packages/canon/css/components.css | 8 ++++---- packages/canon/css/fieldlabel.css | 8 ++++---- packages/canon/css/styles.css | 8 ++++---- .../canon/src/components/FieldLabel/FieldLabel.styles.css | 8 ++++---- packages/canon/src/components/FieldLabel/FieldLabel.tsx | 8 ++++---- 6 files changed, 21 insertions(+), 24 deletions(-) diff --git a/packages/canon/.storybook/main.ts b/packages/canon/.storybook/main.ts index e58c0f182f..3020ade96e 100644 --- a/packages/canon/.storybook/main.ts +++ b/packages/canon/.storybook/main.ts @@ -9,10 +9,7 @@ function getAbsolutePath(value: string): string { return dirname(require.resolve(join(value, 'package.json'))); } const config: StorybookConfig = { - stories: [ - '../src/components/**/*.stories.@(js|jsx|mjs|ts|tsx)', - '../src/stories/**/*.stories.@(js|jsx|mjs|ts|tsx)', - ], + stories: ['../src/components/**/*.stories.@(js|jsx|mjs|ts|tsx)'], staticDirs: ['../static'], addons: [ getAbsolutePath('@storybook/addon-webpack5-compiler-swc'), diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index 8d133abfb0..b4ee5fb6e8 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -257,14 +257,14 @@ min-width: 10.5rem; } -.canon-TextFieldLabelWrapper { +.canon-FieldLabel { margin-bottom: var(--canon-space-3); gap: var(--canon-space-1); flex-direction: column; display: flex; } -.canon-TextFieldLabel { +.canon-FieldLabelLabel { color: var(--canon-fg-primary); cursor: pointer; font-weight: var(--canon-font-weight-regular); @@ -272,13 +272,13 @@ margin-right: auto; } -.canon-TextFieldSecondaryLabel { +.canon-FieldLabelSecondaryLabel { color: var(--canon-fg-secondary); font-weight: var(--canon-font-weight-regular); margin-left: var(--canon-space-1); } -.canon-TextFieldDescription { +.canon-FieldLabelDescription { font-weight: var(--canon-font-weight-regular); font-size: var(--canon-font-size-2); color: var(--canon-fg-secondary); diff --git a/packages/canon/css/fieldlabel.css b/packages/canon/css/fieldlabel.css index e8369ae92e..1b708ed580 100644 --- a/packages/canon/css/fieldlabel.css +++ b/packages/canon/css/fieldlabel.css @@ -1,11 +1,11 @@ -.canon-TextFieldLabelWrapper { +.canon-FieldLabel { margin-bottom: var(--canon-space-3); gap: var(--canon-space-1); flex-direction: column; display: flex; } -.canon-TextFieldLabel { +.canon-FieldLabelLabel { color: var(--canon-fg-primary); cursor: pointer; font-weight: var(--canon-font-weight-regular); @@ -13,13 +13,13 @@ margin-right: auto; } -.canon-TextFieldSecondaryLabel { +.canon-FieldLabelSecondaryLabel { color: var(--canon-fg-secondary); font-weight: var(--canon-font-weight-regular); margin-left: var(--canon-space-1); } -.canon-TextFieldDescription { +.canon-FieldLabelDescription { font-weight: var(--canon-font-weight-regular); font-size: var(--canon-font-size-2); color: var(--canon-fg-secondary); diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index d6576ddb42..f9d4f824a0 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -9481,14 +9481,14 @@ min-width: 10.5rem; } -.canon-TextFieldLabelWrapper { +.canon-FieldLabel { margin-bottom: var(--canon-space-3); gap: var(--canon-space-1); flex-direction: column; display: flex; } -.canon-TextFieldLabel { +.canon-FieldLabelLabel { color: var(--canon-fg-primary); cursor: pointer; font-weight: var(--canon-font-weight-regular); @@ -9496,13 +9496,13 @@ margin-right: auto; } -.canon-TextFieldSecondaryLabel { +.canon-FieldLabelSecondaryLabel { color: var(--canon-fg-secondary); font-weight: var(--canon-font-weight-regular); margin-left: var(--canon-space-1); } -.canon-TextFieldDescription { +.canon-FieldLabelDescription { font-weight: var(--canon-font-weight-regular); font-size: var(--canon-font-size-2); color: var(--canon-fg-secondary); diff --git a/packages/canon/src/components/FieldLabel/FieldLabel.styles.css b/packages/canon/src/components/FieldLabel/FieldLabel.styles.css index 5fee3d76a4..174e7f7326 100644 --- a/packages/canon/src/components/FieldLabel/FieldLabel.styles.css +++ b/packages/canon/src/components/FieldLabel/FieldLabel.styles.css @@ -1,11 +1,11 @@ -.canon-TextFieldLabelWrapper { +.canon-FieldLabel { display: flex; flex-direction: column; margin-bottom: var(--canon-space-3); gap: var(--canon-space-1); } -.canon-TextFieldLabel { +.canon-FieldLabelLabel { color: var(--canon-fg-primary); margin-right: auto; cursor: pointer; @@ -13,13 +13,13 @@ font-size: var(--canon-font-size-2); } -.canon-TextFieldSecondaryLabel { +.canon-FieldLabelSecondaryLabel { color: var(--canon-fg-secondary); font-weight: var(--canon-font-weight-regular); margin-left: var(--canon-space-1); } -.canon-TextFieldDescription { +.canon-FieldLabelDescription { font-weight: var(--canon-font-weight-regular); font-size: var(--canon-font-size-2); color: var(--canon-fg-secondary); diff --git a/packages/canon/src/components/FieldLabel/FieldLabel.tsx b/packages/canon/src/components/FieldLabel/FieldLabel.tsx index 713f7fb78b..03db922279 100644 --- a/packages/canon/src/components/FieldLabel/FieldLabel.tsx +++ b/packages/canon/src/components/FieldLabel/FieldLabel.tsx @@ -25,14 +25,14 @@ export const FieldLabel = forwardRef( if (!label) return null; return ( -
+
{label && ( -
); From f071baa58226cefcb745ad32b1ac69cdd1ed610b Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 16 Jun 2025 23:11:54 +0100 Subject: [PATCH 155/301] Cleanup CSS Signed-off-by: Charles de Dreuille --- packages/canon/css/components.css | 1 + packages/canon/css/styles.css | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index b4ee5fb6e8..d420031ada 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -583,6 +583,7 @@ border: none; border-bottom: 1px solid var(--canon-border); background-color: var(--canon-bg-surface-1); + width: 100%; height: 32px; color: var(--canon-fg-primary); line-height: 140%; diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index f9d4f824a0..b2d111b185 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -9807,6 +9807,7 @@ border: none; border-bottom: 1px solid var(--canon-border); background-color: var(--canon-bg-surface-1); + width: 100%; height: 32px; color: var(--canon-fg-primary); line-height: 140%; From 70416c788fe00af904165a3a1181b024ea515441 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 16 Jun 2025 23:41:50 +0100 Subject: [PATCH 156/301] Add more examples for custom fields Signed-off-by: Charles de Dreuille --- .../src/components/FieldLabel/FieldLabel.tsx | 6 +++--- .../canon/src/components/FieldLabel/types.ts | 9 ++++++++- .../TextField/TextField.stories.tsx | 19 +++++++++++++++++++ .../src/components/TextField/TextField.tsx | 4 +++- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/canon/src/components/FieldLabel/FieldLabel.tsx b/packages/canon/src/components/FieldLabel/FieldLabel.tsx index 03db922279..8fee7dffed 100644 --- a/packages/canon/src/components/FieldLabel/FieldLabel.tsx +++ b/packages/canon/src/components/FieldLabel/FieldLabel.tsx @@ -20,14 +20,14 @@ import type { FieldLabelProps } from './types'; /** @public */ export const FieldLabel = forwardRef( (props: FieldLabelProps, ref) => { - const { label, secondaryLabel, description } = props; + const { label, secondaryLabel, description, htmlFor, id, ...rest } = props; if (!label) return null; return ( -
+
{label && ( -
From 6bc0799e1f99efc4a019097fda8272572452376a Mon Sep 17 00:00:00 2001 From: Niall Thomson Date: Wed, 18 Jun 2025 12:59:16 -0600 Subject: [PATCH 205/301] docs: Fix MCP backend token subject Signed-off-by: Niall Thomson --- .changeset/rich-eagles-rule.md | 5 +++++ plugins/mcp-actions-backend/README.md | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/rich-eagles-rule.md diff --git a/.changeset/rich-eagles-rule.md b/.changeset/rich-eagles-rule.md new file mode 100644 index 0000000000..100ccafb77 --- /dev/null +++ b/.changeset/rich-eagles-rule.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-mcp-actions-backend': patch +--- + +Fixed the example in the README for generating a static token by adding a subject field diff --git a/plugins/mcp-actions-backend/README.md b/plugins/mcp-actions-backend/README.md index b14f252bce..c44bd7f0a6 100644 --- a/plugins/mcp-actions-backend/README.md +++ b/plugins/mcp-actions-backend/README.md @@ -89,6 +89,7 @@ backend: - type: static options: token: ${MCP_TOKEN} + subject: mcp-clients accessRestrictions: - plugin: mcp-actions - plugin: catalog From 7cb7bc0203827950261ce715bc1d55599fac6d58 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 18 Jun 2025 17:39:32 -0400 Subject: [PATCH 206/301] Improve changeset wording Co-authored-by: Vincenzo Scamporlino Signed-off-by: Chris --- .changeset/chubby-dots-kiss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/chubby-dots-kiss.md b/.changeset/chubby-dots-kiss.md index b23fe2fcef..7a28467725 100644 --- a/.changeset/chubby-dots-kiss.md +++ b/.changeset/chubby-dots-kiss.md @@ -2,4 +2,4 @@ '@backstage/plugin-techdocs': patch --- -Handle undefined defaultPath in TechDocs initial redirect. +Fixed an issue causing TechDocs to not properly handle initial redirect. From b4db4d190d3bebf0ad999a8051975fc8b213ad8f Mon Sep 17 00:00:00 2001 From: James Brooks Date: Thu, 19 Jun 2025 09:07:55 +0100 Subject: [PATCH 207/301] Improve story types Signed-off-by: James Brooks --- .../IconButton/IconButton.stories.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/canon/src/components/IconButton/IconButton.stories.tsx b/packages/canon/src/components/IconButton/IconButton.stories.tsx index 4065b61cb2..c32ddb878a 100644 --- a/packages/canon/src/components/IconButton/IconButton.stories.tsx +++ b/packages/canon/src/components/IconButton/IconButton.stories.tsx @@ -18,7 +18,6 @@ import type { Meta, StoryObj } from '@storybook/react'; import { IconButton } from './IconButton'; import { Flex } from '../Flex'; import { Text } from '../Text'; -import { IconButtonProps } from './types'; import { Icon } from '../Icon'; const meta = { @@ -105,7 +104,8 @@ export const Responsive: Story = { render: args => } />, }; -const variants: string[] = ['primary', 'secondary']; +const variants = ['primary', 'secondary'] as const; +const sizes = ['small', 'medium'] as const; export const Playground: Story = { render: args => ( @@ -113,27 +113,27 @@ export const Playground: Story = { {variants.map(variant => ( {variant} - {['small', 'medium'].map(size => ( + {sizes.map(size => ( ['variant']} - size={size as IconButtonProps['size']} + variant={variant} + size={size} icon={} /> } aria-label="Chevron right icon button" - variant={variant as IconButtonProps['variant']} - size={size as IconButtonProps['size']} + variant={variant} + size={size} /> } aria-label="Chevron right icon button" - variant={variant as IconButtonProps['variant']} - size={size as IconButtonProps['size']} + variant={variant} + size={size} /> ))} From aae98c9747662997d34fa9b9095ab8993ab67cfa Mon Sep 17 00:00:00 2001 From: James Brooks Date: Thu, 19 Jun 2025 09:10:45 +0100 Subject: [PATCH 208/301] Fix class name Signed-off-by: James Brooks --- packages/canon/src/components/IconButton/IconButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/canon/src/components/IconButton/IconButton.tsx b/packages/canon/src/components/IconButton/IconButton.tsx index dfa06cb612..73c95ee131 100644 --- a/packages/canon/src/components/IconButton/IconButton.tsx +++ b/packages/canon/src/components/IconButton/IconButton.tsx @@ -45,7 +45,7 @@ export const IconButton = forwardRef( return ( Date: Thu, 19 Jun 2025 09:12:33 +0100 Subject: [PATCH 209/301] Improve more story types Signed-off-by: James Brooks --- .../src/components/Button/Button.stories.tsx | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/packages/canon/src/components/Button/Button.stories.tsx b/packages/canon/src/components/Button/Button.stories.tsx index 7baff061b3..82709f1577 100644 --- a/packages/canon/src/components/Button/Button.stories.tsx +++ b/packages/canon/src/components/Button/Button.stories.tsx @@ -18,7 +18,6 @@ import type { Meta, StoryObj } from '@storybook/react'; import { Button } from './Button'; import { Flex } from '../Flex'; import { Text } from '../Text'; -import { ButtonProps } from './types'; import { Icon } from '../Icon'; const meta = { @@ -167,7 +166,8 @@ export const Responsive: Story = { }, }; -const variants: string[] = ['primary', 'secondary']; +const variants = ['primary', 'secondary'] as const; +const sizes = ['small', 'medium'] as const; export const Playground: Story = { args: { @@ -178,25 +178,22 @@ export const Playground: Story = { {variants.map(variant => ( {variant} - {['small', 'medium'].map(size => ( + {sizes.map(size => ( - @@ -204,30 +201,26 @@ export const Playground: Story = { iconStart={} iconEnd={} style={{ width: '200px' }} - variant={variant as ButtonProps['variant']} - size={size as ButtonProps['size']} + variant={variant} + size={size} > Button - + ), }; export const AsLink: Story = { args: { - children: 'Button', + as: 'a', + children: 'I am a link', href: 'https://canon.backstage.io', target: '_blank', }, From cf9ba6f0ab261b29c683144b737cdb23bb85e8f7 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Thu, 19 Jun 2025 11:29:50 +0200 Subject: [PATCH 211/301] chore: fixing cookiecutter input values Signed-off-by: benjdlambert --- .changeset/sixty-ties-matter.md | 5 +++++ .../src/actions/fetch/cookiecutter.ts | 11 ++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 .changeset/sixty-ties-matter.md diff --git a/.changeset/sixty-ties-matter.md b/.changeset/sixty-ties-matter.md new file mode 100644 index 0000000000..3be0bb08a8 --- /dev/null +++ b/.changeset/sixty-ties-matter.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +--- + +Fixing the typescript issue with using `z.unknown()` diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts index 3a4ee8c3d7..9a9df95dea 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts @@ -169,9 +169,14 @@ export function createFetchCookiecutterAction(options: { }) .optional(), values: z => - z.record(z.unknown(), { - description: 'Values to pass on to cookiecutter for templating', - }), + z + .object( + {}, + { + description: 'Values to pass on to cookiecutter for templating', + }, + ) + .passthrough(), copyWithoutRender: z => z .array(z.string(), { From 105722fe8ec8e40aa794763a1822a667910c4720 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Thu, 19 Jun 2025 11:36:19 +0200 Subject: [PATCH 212/301] chore: enter Signed-off-by: benjdlambert --- .changeset/pre.json | 207 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 .changeset/pre.json diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 0000000000..60eb6ca6ce --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,207 @@ +{ + "mode": "pre", + "tag": "next", + "initialVersions": { + "example-app": "0.2.110", + "@backstage/app-defaults": "1.6.3", + "example-app-next": "0.0.24", + "app-next-example-plugin": "0.0.24", + "example-backend": "0.0.39", + "@backstage/backend-app-api": "1.2.4", + "@backstage/backend-defaults": "0.11.0", + "@backstage/backend-dev-utils": "0.1.5", + "@backstage/backend-dynamic-feature-service": "0.7.1", + "@backstage/backend-openapi-utils": "0.5.4", + "@backstage/backend-plugin-api": "1.4.0", + "@backstage/backend-test-utils": "1.6.0", + "@backstage/canon": "0.5.0", + "@backstage/catalog-client": "1.10.1", + "@backstage/catalog-model": "1.7.4", + "@backstage/cli": "0.33.0", + "@backstage/cli-common": "0.1.15", + "@backstage/cli-node": "0.2.13", + "@backstage/codemods": "0.1.52", + "@backstage/config": "1.3.2", + "@backstage/config-loader": "1.10.1", + "@backstage/core-app-api": "1.17.1", + "@backstage/core-compat-api": "0.4.3", + "@backstage/core-components": "0.17.3", + "@backstage/core-plugin-api": "1.10.8", + "@backstage/create-app": "0.7.0", + "@backstage/dev-utils": "1.1.11", + "e2e-test": "0.2.29", + "@backstage/e2e-test-utils": "0.1.1", + "@backstage/errors": "1.2.7", + "@backstage/eslint-plugin": "0.1.11", + "@backstage/frontend-app-api": "0.11.3", + "@backstage/frontend-defaults": "0.2.3", + "@backstage/frontend-dynamic-feature-loader": "0.1.2", + "@internal/frontend": "0.0.10", + "@backstage/frontend-plugin-api": "0.10.3", + "@backstage/frontend-test-utils": "0.3.3", + "@backstage/integration": "1.17.0", + "@backstage/integration-aws-node": "0.1.16", + "@backstage/integration-react": "1.2.8", + "@internal/opaque": "0.0.1", + "@backstage/release-manifests": "0.0.13", + "@backstage/repo-tools": "0.14.0", + "@internal/scaffolder": "0.0.10", + "@techdocs/cli": "1.9.4", + "techdocs-cli-embedded-app": "0.2.109", + "@backstage/test-utils": "1.7.9", + "@backstage/theme": "0.6.6", + "@backstage/types": "1.2.1", + "@backstage/version-bridge": "1.0.11", + "yarn-plugin-backstage": "0.0.6", + "@backstage/plugin-api-docs": "0.12.8", + "@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.10", + "@backstage/plugin-app": "0.1.10", + "@backstage/plugin-app-backend": "0.5.3", + "@backstage/plugin-app-node": "0.1.34", + "@backstage/plugin-app-visualizer": "0.1.20", + "@backstage/plugin-auth-backend": "0.25.1", + "@backstage/plugin-auth-backend-module-atlassian-provider": "0.4.4", + "@backstage/plugin-auth-backend-module-auth0-provider": "0.2.4", + "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.4.4", + "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.2.9", + "@backstage/plugin-auth-backend-module-bitbucket-provider": "0.3.4", + "@backstage/plugin-auth-backend-module-bitbucket-server-provider": "0.2.4", + "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.4.4", + "@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.4.4", + "@backstage/plugin-auth-backend-module-github-provider": "0.3.4", + "@backstage/plugin-auth-backend-module-gitlab-provider": "0.3.4", + "@backstage/plugin-auth-backend-module-google-provider": "0.3.4", + "@backstage/plugin-auth-backend-module-guest-provider": "0.2.9", + "@backstage/plugin-auth-backend-module-microsoft-provider": "0.3.4", + "@backstage/plugin-auth-backend-module-oauth2-provider": "0.4.4", + "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.2.9", + "@backstage/plugin-auth-backend-module-oidc-provider": "0.4.4", + "@backstage/plugin-auth-backend-module-okta-provider": "0.2.4", + "@backstage/plugin-auth-backend-module-onelogin-provider": "0.3.4", + "@backstage/plugin-auth-backend-module-pinniped-provider": "0.3.4", + "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.5.4", + "@backstage/plugin-auth-node": "0.6.4", + "@backstage/plugin-auth-react": "0.1.16", + "@backstage/plugin-bitbucket-cloud-common": "0.3.0", + "@backstage/plugin-catalog": "1.31.0", + "@backstage/plugin-catalog-backend": "2.1.0", + "@backstage/plugin-catalog-backend-module-aws": "0.4.12", + "@backstage/plugin-catalog-backend-module-azure": "0.3.6", + "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.5.3", + "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.5.0", + "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.5.0", + "@backstage/plugin-catalog-backend-module-gcp": "0.3.9", + "@backstage/plugin-catalog-backend-module-gerrit": "0.3.3", + "@backstage/plugin-catalog-backend-module-gitea": "0.1.1", + "@backstage/plugin-catalog-backend-module-github": "0.10.0", + "@backstage/plugin-catalog-backend-module-github-org": "0.3.11", + "@backstage/plugin-catalog-backend-module-gitlab": "0.7.0", + "@backstage/plugin-catalog-backend-module-gitlab-org": "0.2.10", + "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.7.1", + "@backstage/plugin-catalog-backend-module-ldap": "0.11.6", + "@backstage/plugin-catalog-backend-module-logs": "0.1.11", + "@backstage/plugin-catalog-backend-module-msgraph": "0.7.1", + "@backstage/plugin-catalog-backend-module-openapi": "0.2.11", + "@backstage/plugin-catalog-backend-module-puppetdb": "0.2.11", + "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.2.9", + "@backstage/plugin-catalog-backend-module-unprocessed": "0.6.1", + "@backstage/plugin-catalog-common": "1.1.4", + "@backstage/plugin-catalog-graph": "0.4.20", + "@backstage/plugin-catalog-import": "0.13.1", + "@backstage/plugin-catalog-node": "1.17.1", + "@backstage/plugin-catalog-react": "1.19.0", + "@backstage/plugin-catalog-unprocessed-entities": "0.2.18", + "@backstage/plugin-catalog-unprocessed-entities-common": "0.0.8", + "@backstage/plugin-config-schema": "0.1.69", + "@backstage/plugin-devtools": "0.1.28", + "@backstage/plugin-devtools-backend": "0.5.6", + "@backstage/plugin-devtools-common": "0.1.16", + "@backstage/plugin-events-backend": "0.5.3", + "@backstage/plugin-events-backend-module-aws-sqs": "0.4.12", + "@backstage/plugin-events-backend-module-azure": "0.2.21", + "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.21", + "@backstage/plugin-events-backend-module-bitbucket-server": "0.1.2", + "@backstage/plugin-events-backend-module-gerrit": "0.2.21", + "@backstage/plugin-events-backend-module-github": "0.4.1", + "@backstage/plugin-events-backend-module-gitlab": "0.3.2", + "@backstage/plugin-events-backend-module-google-pubsub": "0.1.1", + "@backstage/plugin-events-backend-module-kafka": "0.1.0", + "@backstage/plugin-events-backend-test-utils": "0.1.45", + "@backstage/plugin-events-node": "0.4.12", + "@internal/plugin-todo-list": "1.0.40", + "@internal/plugin-todo-list-backend": "1.0.40", + "@internal/plugin-todo-list-common": "1.0.25", + "@backstage/plugin-gateway-backend": "1.0.2", + "@backstage/plugin-home": "0.8.9", + "@backstage/plugin-home-react": "0.1.27", + "@backstage/plugin-kubernetes": "0.12.8", + "@backstage/plugin-kubernetes-backend": "0.19.7", + "@backstage/plugin-kubernetes-cluster": "0.0.26", + "@backstage/plugin-kubernetes-common": "0.9.5", + "@backstage/plugin-kubernetes-node": "0.3.1", + "@backstage/plugin-kubernetes-react": "0.5.8", + "@backstage/plugin-mcp-actions-backend": "0.1.0", + "@backstage/plugin-notifications": "0.5.6", + "@backstage/plugin-notifications-backend": "0.5.7", + "@backstage/plugin-notifications-backend-module-email": "0.3.10", + "@backstage/plugin-notifications-backend-module-slack": "0.1.2", + "@backstage/plugin-notifications-common": "0.0.9", + "@backstage/plugin-notifications-node": "0.2.16", + "@backstage/plugin-org": "0.6.40", + "@backstage/plugin-org-react": "0.1.39", + "@backstage/plugin-permission-backend": "0.7.1", + "@backstage/plugin-permission-backend-module-allow-all-policy": "0.2.9", + "@backstage/plugin-permission-common": "0.9.0", + "@backstage/plugin-permission-node": "0.10.1", + "@backstage/plugin-permission-react": "0.4.35", + "@backstage/plugin-proxy-backend": "0.6.3", + "@backstage/plugin-proxy-node": "0.1.5", + "@backstage/plugin-scaffolder": "1.32.0", + "@backstage/plugin-scaffolder-backend": "2.0.0", + "@backstage/plugin-scaffolder-backend-module-azure": "0.2.10", + "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.3.11", + "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.2.10", + "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.2.10", + "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.3.10", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.3.11", + "@backstage/plugin-scaffolder-backend-module-gcp": "0.2.10", + "@backstage/plugin-scaffolder-backend-module-gerrit": "0.2.10", + "@backstage/plugin-scaffolder-backend-module-gitea": "0.2.10", + "@backstage/plugin-scaffolder-backend-module-github": "0.8.0", + "@backstage/plugin-scaffolder-backend-module-gitlab": "0.9.2", + "@backstage/plugin-scaffolder-backend-module-notifications": "0.1.11", + "@backstage/plugin-scaffolder-backend-module-rails": "0.5.10", + "@backstage/plugin-scaffolder-backend-module-sentry": "0.2.10", + "@backstage/plugin-scaffolder-backend-module-yeoman": "0.4.11", + "@backstage/plugin-scaffolder-common": "1.5.11", + "@backstage/plugin-scaffolder-node": "0.9.0", + "@backstage/plugin-scaffolder-node-test-utils": "0.3.0", + "@backstage/plugin-scaffolder-react": "1.17.0", + "@backstage/plugin-search": "1.4.27", + "@backstage/plugin-search-backend": "2.0.3", + "@backstage/plugin-search-backend-module-catalog": "0.3.5", + "@backstage/plugin-search-backend-module-elasticsearch": "1.7.3", + "@backstage/plugin-search-backend-module-explore": "0.3.3", + "@backstage/plugin-search-backend-module-pg": "0.5.45", + "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.3.10", + "@backstage/plugin-search-backend-module-techdocs": "0.4.3", + "@backstage/plugin-search-backend-node": "1.3.12", + "@backstage/plugin-search-common": "1.2.18", + "@backstage/plugin-search-react": "1.9.1", + "@backstage/plugin-signals": "0.0.20", + "@backstage/plugin-signals-backend": "0.3.5", + "@backstage/plugin-signals-node": "0.1.21", + "@backstage/plugin-signals-react": "0.0.14", + "@backstage/plugin-techdocs": "1.13.0", + "@backstage/plugin-techdocs-addons-test-utils": "1.0.49", + "@backstage/plugin-techdocs-backend": "2.0.3", + "@backstage/plugin-techdocs-common": "0.1.1", + "@backstage/plugin-techdocs-module-addons-contrib": "1.1.25", + "@backstage/plugin-techdocs-node": "1.13.4", + "@backstage/plugin-techdocs-react": "1.3.0", + "@backstage/plugin-user-settings": "0.8.23", + "@backstage/plugin-user-settings-backend": "0.3.3", + "@backstage/plugin-user-settings-common": "0.0.1" + }, + "changesets": [] +} From 4b0189d67252177de684548726c87c72b0d5186e Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Thu, 19 Jun 2025 12:44:27 +0200 Subject: [PATCH 213/301] chore: fixing types for cookiecutter Signed-off-by: benjdlambert --- plugins/scaffolder-backend-module-cookiecutter/report.api.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-cookiecutter/report.api.md b/plugins/scaffolder-backend-module-cookiecutter/report.api.md index 2dfba3f42f..a60a8c0396 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/report.api.md +++ b/plugins/scaffolder-backend-module-cookiecutter/report.api.md @@ -4,10 +4,12 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; +import { objectOutputType } from 'zod'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { Writable } from 'stream'; +import { ZodTypeAny } from 'zod'; // @public export interface ContainerRunner { @@ -47,7 +49,7 @@ export function createFetchCookiecutterAction(options: { }): TemplateAction< { url: string; - values: Record; + values: objectOutputType<{}, ZodTypeAny, 'passthrough'>; targetPath?: string | undefined; copyWithoutRender?: string[] | undefined; extensions?: string[] | undefined; From 5c805d338e9ef327e9df4873f1a1278879e20b02 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 19 Jun 2025 15:37:33 +0100 Subject: [PATCH 214/301] Removed support for the as prop Signed-off-by: Charles de Dreuille --- packages/canon/css/button.css | 8 +- packages/canon/css/buttonicon.css | 14 ++ packages/canon/css/components.css | 113 ++-------- packages/canon/css/styles.css | 113 ++-------- packages/canon/report.api.md | 107 ++++----- .../src/components/Button/Button.stories.tsx | 23 -- .../canon/src/components/Button/Button.tsx | 43 +--- .../canon/src/components/Button/styles.css | 10 +- packages/canon/src/components/Button/types.ts | 27 +-- .../ButtonIcon.stories.tsx} | 51 ++--- .../ButtonIcon.tsx} | 41 +--- .../{IconButton => ButtonIcon}/index.tsx | 2 +- .../src/components/ButtonIcon/styles.css | 30 +++ .../{IconButton => ButtonIcon}/types.ts | 29 +-- .../ButtonLink/ButtonLink.stories.tsx | 212 ++++++++++++++++++ .../src/components/ButtonLink/ButtonLink.tsx | 71 ++++++ .../canon/src/components/ButtonLink/index.ts | 18 ++ .../canon/src/components/ButtonLink/types.ts | 35 +++ .../Pagination/DataTablePagination.tsx | 6 +- .../src/components/IconButton/styles.css | 107 --------- packages/canon/src/css/components.css | 2 +- packages/canon/src/index.ts | 2 +- packages/canon/src/types.ts | 18 -- 23 files changed, 528 insertions(+), 554 deletions(-) create mode 100644 packages/canon/css/buttonicon.css rename packages/canon/src/components/{IconButton/IconButton.stories.tsx => ButtonIcon/ButtonIcon.stories.tsx} (69%) rename packages/canon/src/components/{IconButton/IconButton.tsx => ButtonIcon/ButtonIcon.tsx} (59%) rename packages/canon/src/components/{IconButton => ButtonIcon}/index.tsx (95%) create mode 100644 packages/canon/src/components/ButtonIcon/styles.css rename packages/canon/src/components/{IconButton => ButtonIcon}/types.ts (56%) create mode 100644 packages/canon/src/components/ButtonLink/ButtonLink.stories.tsx create mode 100644 packages/canon/src/components/ButtonLink/ButtonLink.tsx create mode 100644 packages/canon/src/components/ButtonLink/index.ts create mode 100644 packages/canon/src/components/ButtonLink/types.ts delete mode 100644 packages/canon/src/components/IconButton/styles.css diff --git a/packages/canon/css/button.css b/packages/canon/css/button.css index 7aaad3ec48..7f15f4a541 100644 --- a/packages/canon/css/button.css +++ b/packages/canon/css/button.css @@ -69,21 +69,21 @@ .canon-Button[data-size="medium"] { font-size: var(--canon-font-size-4); padding: 0 var(--canon-space-3); - height: 40px; + height: 2.5rem; } .canon-Button[data-size="small"] { font-size: var(--canon-font-size-3); padding: 0 var(--canon-space-2); - height: 32px; + height: 2rem; } -.canon-ButtonIcon[data-size="small"], .canon-ButtonIcon[data-size="small"] svg { +.canon-Button[data-size="small"] svg { width: 1rem; height: 1rem; } -.canon-ButtonIcon[data-size="medium"], .canon-ButtonIcon[data-size="medium"] svg { +.canon-Button[data-size="medium"] svg { width: 1.25rem; height: 1.25rem; } diff --git a/packages/canon/css/buttonicon.css b/packages/canon/css/buttonicon.css new file mode 100644 index 0000000000..1a791b481f --- /dev/null +++ b/packages/canon/css/buttonicon.css @@ -0,0 +1,14 @@ +.canon-ButtonIcon { + justify-content: center; + align-items: center; +} + +.canon-ButtonIcon[data-size="small"] { + width: 2rem; + padding: 0; +} + +.canon-ButtonIcon[data-size="medium"] { + width: 2.5rem; + padding: 0; +} diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index d420031ada..327f168696 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -125,25 +125,40 @@ .canon-Button[data-size="medium"] { font-size: var(--canon-font-size-4); padding: 0 var(--canon-space-3); - height: 40px; + height: 2.5rem; } .canon-Button[data-size="small"] { font-size: var(--canon-font-size-3); padding: 0 var(--canon-space-2); - height: 32px; + height: 2rem; } -.canon-ButtonIcon[data-size="small"], .canon-ButtonIcon[data-size="small"] svg { +.canon-Button[data-size="small"] svg { width: 1rem; height: 1rem; } -.canon-ButtonIcon[data-size="medium"], .canon-ButtonIcon[data-size="medium"] svg { +.canon-Button[data-size="medium"] svg { width: 1.25rem; height: 1.25rem; } +.canon-ButtonIcon { + justify-content: center; + align-items: center; +} + +.canon-ButtonIcon[data-size="small"] { + width: 2rem; + padding: 0; +} + +.canon-ButtonIcon[data-size="medium"] { + width: 2.5rem; + padding: 0; +} + .canon-CheckboxRoot { width: 1rem; height: 1rem; @@ -351,96 +366,6 @@ height: 1rem; } -.canon-IconButton { - user-select: none; - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-bold); - cursor: pointer; - border-radius: var(--canon-radius-2); - justify-content: center; - align-items: center; - gap: var(--canon-space-1_5); - border: none; - padding: 0; - display: inline-flex; - - &:disabled { - cursor: not-allowed; - } -} - -.canon-IconButton[data-variant="primary"] { - background-color: var(--canon-bg-solid); - color: var(--canon-fg-solid); - transition: background-color .15s, box-shadow .15s; - - &:hover { - background-color: var(--canon-bg-solid-hover); - } - - &:active { - background-color: var(--canon-bg-solid-pressed); - } - - &:focus-visible { - outline: 2px solid var(--canon-ring); - outline-offset: 2px; - } - - &:disabled { - background-color: var(--canon-bg-solid-disabled); - color: var(--canon-fg-solid-disabled); - } -} - -.canon-IconButton[data-variant="secondary"] { - background-color: var(--canon-bg-surface-1); - box-shadow: inset 0 0 0 1px var(--canon-border); - color: var(--canon-fg-primary); - transition: box-shadow .15s; - - &:hover { - box-shadow: inset 0 0 0 1px var(--canon-border-hover); - } - - &:active { - box-shadow: inset 0 0 0 1px var(--canon-border-pressed); - } - - &:focus-visible { - box-shadow: inset 0 0 0 2px var(--canon-ring); - outline: none; - transition: none; - } - - &:disabled { - box-shadow: inset 0 0 0 1px var(--canon-border-disabled); - color: var(--canon-fg-disabled); - } -} - -.canon-IconButton[data-size="medium"] { - font-size: var(--canon-font-size-4); - width: 40px; - height: 40px; -} - -.canon-IconButton[data-size="small"] { - font-size: var(--canon-font-size-3); - width: 32px; - height: 32px; -} - -.canon-IconButtonIcon[data-size="small"], .canon-IconButtonIcon[data-size="small"] svg { - width: 1rem; - height: 1rem; -} - -.canon-IconButtonIcon[data-size="medium"], .canon-IconButtonIcon[data-size="medium"] svg { - width: 1.25rem; - height: 1.25rem; -} - .canon-Link { font-family: var(--canon-font-regular); color: var(--canon-fg-link); diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index b2d111b185..9f32da5d66 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -9349,25 +9349,40 @@ .canon-Button[data-size="medium"] { font-size: var(--canon-font-size-4); padding: 0 var(--canon-space-3); - height: 40px; + height: 2.5rem; } .canon-Button[data-size="small"] { font-size: var(--canon-font-size-3); padding: 0 var(--canon-space-2); - height: 32px; + height: 2rem; } -.canon-ButtonIcon[data-size="small"], .canon-ButtonIcon[data-size="small"] svg { +.canon-Button[data-size="small"] svg { width: 1rem; height: 1rem; } -.canon-ButtonIcon[data-size="medium"], .canon-ButtonIcon[data-size="medium"] svg { +.canon-Button[data-size="medium"] svg { width: 1.25rem; height: 1.25rem; } +.canon-ButtonIcon { + justify-content: center; + align-items: center; +} + +.canon-ButtonIcon[data-size="small"] { + width: 2rem; + padding: 0; +} + +.canon-ButtonIcon[data-size="medium"] { + width: 2.5rem; + padding: 0; +} + .canon-CheckboxRoot { width: 1rem; height: 1rem; @@ -9575,96 +9590,6 @@ height: 1rem; } -.canon-IconButton { - user-select: none; - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-bold); - cursor: pointer; - border-radius: var(--canon-radius-2); - justify-content: center; - align-items: center; - gap: var(--canon-space-1_5); - border: none; - padding: 0; - display: inline-flex; - - &:disabled { - cursor: not-allowed; - } -} - -.canon-IconButton[data-variant="primary"] { - background-color: var(--canon-bg-solid); - color: var(--canon-fg-solid); - transition: background-color .15s, box-shadow .15s; - - &:hover { - background-color: var(--canon-bg-solid-hover); - } - - &:active { - background-color: var(--canon-bg-solid-pressed); - } - - &:focus-visible { - outline: 2px solid var(--canon-ring); - outline-offset: 2px; - } - - &:disabled { - background-color: var(--canon-bg-solid-disabled); - color: var(--canon-fg-solid-disabled); - } -} - -.canon-IconButton[data-variant="secondary"] { - background-color: var(--canon-bg-surface-1); - box-shadow: inset 0 0 0 1px var(--canon-border); - color: var(--canon-fg-primary); - transition: box-shadow .15s; - - &:hover { - box-shadow: inset 0 0 0 1px var(--canon-border-hover); - } - - &:active { - box-shadow: inset 0 0 0 1px var(--canon-border-pressed); - } - - &:focus-visible { - box-shadow: inset 0 0 0 2px var(--canon-ring); - outline: none; - transition: none; - } - - &:disabled { - box-shadow: inset 0 0 0 1px var(--canon-border-disabled); - color: var(--canon-fg-disabled); - } -} - -.canon-IconButton[data-size="medium"] { - font-size: var(--canon-font-size-4); - width: 40px; - height: 40px; -} - -.canon-IconButton[data-size="small"] { - font-size: var(--canon-font-size-3); - width: 32px; - height: 32px; -} - -.canon-IconButtonIcon[data-size="small"], .canon-IconButtonIcon[data-size="small"] svg { - width: 1rem; - height: 1rem; -} - -.canon-IconButtonIcon[data-size="medium"], .canon-IconButtonIcon[data-size="medium"] svg { - width: 1.25rem; - height: 1.25rem; -} - .canon-Link { font-family: var(--canon-font-regular); color: var(--canon-fg-link); diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index 96e9e31235..bd0b9cbee0 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -5,15 +5,14 @@ ```ts import { Avatar as Avatar_2 } from '@base-ui-components/react/avatar'; import { Breakpoint as Breakpoint_2 } from '@backstage/canon'; -import { Button as Button_2 } from 'react-aria-components'; +import { ButtonProps as ButtonProps_2 } from 'react-aria-components'; import { ChangeEvent } from 'react'; import { Collapsible as Collapsible_2 } from '@base-ui-components/react/collapsible'; import { ComponentProps } from 'react'; -import { ComponentPropsWithoutRef } from 'react'; -import { ComponentPropsWithRef } from 'react'; +import type { ComponentPropsWithRef } from 'react'; import { Context } from 'react'; import type { CSSProperties } from 'react'; -import { ElementType } from 'react'; +import type { ElementType } from 'react'; import { FC } from 'react'; import { FocusEvent as FocusEvent_2 } from 'react'; import { ForwardRefExoticComponent } from 'react'; @@ -44,11 +43,6 @@ export type ArbitraryStylingPropDef = { parseValue?: (value: string) => string | undefined; }; -// @public (undocumented) -export type As = { - as?: TAs; -}; - // @public (undocumented) export type AsProps = | 'div' @@ -158,32 +152,44 @@ export type Breakpoint = 'initial' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; export const breakpoints: Breakpoint[]; // @public (undocumented) -export const Button: { - ( - props: ButtonProps & { - ref?: ComponentPropsWithRef['ref']; - }, - ): ReactElement; - displayName: string; -}; +export const Button: ForwardRefExoticComponent< + ButtonProps & RefAttributes +>; + +// @public (undocumented) +export const ButtonIcon: ForwardRefExoticComponent< + ButtonIconProps & RefAttributes +>; // @public -export type ButtonProps = PolymorphicComponentProps< - TAs, - { - children?: ReactNode; - size?: - | 'small' - | 'medium' - | Partial>; - variant?: - | 'primary' - | 'secondary' - | Partial>; - iconStart?: ReactElement; - iconEnd?: ReactElement; - } ->; +export interface ButtonIconProps extends ButtonProps_2 { + // (undocumented) + icon?: ReactElement; + // (undocumented) + size?: 'small' | 'medium' | Partial>; + // (undocumented) + variant?: + | 'primary' + | 'secondary' + | Partial>; +} + +// @public +export interface ButtonProps extends ButtonProps_2 { + // (undocumented) + children?: ReactNode; + // (undocumented) + iconEnd?: ReactElement; + // (undocumented) + iconStart?: ReactElement; + // (undocumented) + size?: 'small' | 'medium' | Partial>; + // (undocumented) + variant?: + | 'primary' + | 'secondary' + | Partial>; +} // @public (undocumented) export const Checkbox: ForwardRefExoticComponent< @@ -692,33 +698,6 @@ export type HeightProps = GetPropDefTypes; // @public (undocumented) export const Icon: (props: IconProps) => JSX_2.Element | null; -// @public (undocumented) -export const IconButton: { - ( - props: IconButtonProps & { - ref?: ComponentPropsWithRef['ref']; - }, - ): ReactElement; - displayName: string; -}; - -// @public -export type IconButtonProps = - PolymorphicComponentProps< - TAs, - { - size?: - | 'small' - | 'medium' - | Partial>; - variant?: - | 'primary' - | 'secondary' - | Partial>; - icon?: ReactElement; - } - >; - // @public (undocumented) export const IconContext: Context; @@ -1014,14 +993,6 @@ export const paddingPropDefs: (spacingValues: string[]) => { // @public (undocumented) export type PaddingProps = GetPropDefTypes; -// @public -export type PolymorphicComponentProps< - TAs extends ElementType, - TProps = {}, -> = TProps & - As & - Omit, keyof (As & TProps)>; - // @public (undocumented) export const positionPropDefs: { position: { diff --git a/packages/canon/src/components/Button/Button.stories.tsx b/packages/canon/src/components/Button/Button.stories.tsx index f28ebccc28..e020ebb64d 100644 --- a/packages/canon/src/components/Button/Button.stories.tsx +++ b/packages/canon/src/components/Button/Button.stories.tsx @@ -130,29 +130,6 @@ export const Disabled: Story = { ), }; -export const AsLink: Story = { - args: { - as: 'a', - children: 'I am a link', - href: 'https://canon.backstage.io', - target: '_blank', - }, -}; - -export const AsComponent: Story = { - render: () => { - const Link = (props: { children: React.ReactNode; to: string }) => ( - - ); - - return ( - - ); - }, -}; - export const Responsive: Story = { args: { children: 'Button', diff --git a/packages/canon/src/components/Button/Button.tsx b/packages/canon/src/components/Button/Button.tsx index 905e81111b..9d256a5815 100644 --- a/packages/canon/src/components/Button/Button.tsx +++ b/packages/canon/src/components/Button/Button.tsx @@ -15,22 +15,15 @@ */ import clsx from 'clsx'; -import { - ComponentPropsWithRef, - ElementType, - forwardRef, - ReactElement, - Ref, -} from 'react'; +import { forwardRef, Ref } from 'react'; import { Button as RAButton } from 'react-aria-components'; import { useResponsiveValue } from '../../hooks/useResponsiveValue'; import type { ButtonProps } from './types'; /** @public */ export const Button = forwardRef( - (props: ButtonProps, ref: Ref) => { + (props: ButtonProps, ref: Ref) => { const { - as, size = 'small', variant = 'primary', iconStart, @@ -40,45 +33,23 @@ export const Button = forwardRef( ...rest } = props; - const Component = as || RAButton; const responsiveSize = useResponsiveValue(size); const responsiveVariant = useResponsiveValue(variant); return ( - - {iconStart && ( - - )} + {iconStart} {children} - {iconEnd && ( - - )} - + {iconEnd} + ); }, -) as { - ( - props: ButtonProps & { ref?: ComponentPropsWithRef['ref'] }, - ): ReactElement; - displayName: string; -}; +); Button.displayName = 'Button'; diff --git a/packages/canon/src/components/Button/styles.css b/packages/canon/src/components/Button/styles.css index a4afd85c9b..a2274fd907 100644 --- a/packages/canon/src/components/Button/styles.css +++ b/packages/canon/src/components/Button/styles.css @@ -85,23 +85,21 @@ .canon-Button[data-size='medium'] { font-size: var(--canon-font-size-4); padding: 0 var(--canon-space-3); - height: 40px; + height: 2.5rem; } .canon-Button[data-size='small'] { font-size: var(--canon-font-size-3); padding: 0 var(--canon-space-2); - height: 32px; + height: 2rem; } -.canon-ButtonIcon[data-size='small'], -.canon-ButtonIcon[data-size='small'] svg { +.canon-Button[data-size='small'] svg { width: 1rem; height: 1rem; } -.canon-ButtonIcon[data-size='medium'], -.canon-ButtonIcon[data-size='medium'] svg { +.canon-Button[data-size='medium'] svg { width: 1.25rem; height: 1.25rem; } diff --git a/packages/canon/src/components/Button/types.ts b/packages/canon/src/components/Button/types.ts index 327fa27bb6..ad89dbfe50 100644 --- a/packages/canon/src/components/Button/types.ts +++ b/packages/canon/src/components/Button/types.ts @@ -15,24 +15,21 @@ */ import { Breakpoint } from '@backstage/canon'; -import { ElementType, ReactElement, ReactNode } from 'react'; -import { PolymorphicComponentProps } from '../../types'; +import { ReactElement, ReactNode } from 'react'; +import { ButtonProps as RAButtonProps } from 'react-aria-components'; /** * Properties for {@link Button} * * @public */ -export type ButtonProps = PolymorphicComponentProps< - TAs, - { - children?: ReactNode; - size?: 'small' | 'medium' | Partial>; - variant?: - | 'primary' - | 'secondary' - | Partial>; - iconStart?: ReactElement; - iconEnd?: ReactElement; - } ->; +export interface ButtonProps extends RAButtonProps { + size?: 'small' | 'medium' | Partial>; + variant?: + | 'primary' + | 'secondary' + | Partial>; + iconStart?: ReactElement; + iconEnd?: ReactElement; + children?: ReactNode; +} diff --git a/packages/canon/src/components/IconButton/IconButton.stories.tsx b/packages/canon/src/components/ButtonIcon/ButtonIcon.stories.tsx similarity index 69% rename from packages/canon/src/components/IconButton/IconButton.stories.tsx rename to packages/canon/src/components/ButtonIcon/ButtonIcon.stories.tsx index c32ddb878a..c4c811eb92 100644 --- a/packages/canon/src/components/IconButton/IconButton.stories.tsx +++ b/packages/canon/src/components/ButtonIcon/ButtonIcon.stories.tsx @@ -15,14 +15,14 @@ */ import type { Meta, StoryObj } from '@storybook/react'; -import { IconButton } from './IconButton'; +import { ButtonIcon } from './ButtonIcon'; import { Flex } from '../Flex'; import { Text } from '../Text'; import { Icon } from '../Icon'; const meta = { - title: 'Components/IconButton', - component: IconButton, + title: 'Components/ButtonIcon', + component: ButtonIcon, argTypes: { size: { control: 'select', @@ -33,20 +33,20 @@ const meta = { options: ['primary', 'secondary'], }, }, -} satisfies Meta; +} satisfies Meta; export default meta; type Story = StoryObj; export const Default: Story = { - render: () => } />, + render: () => } />, }; export const Variants: Story = { render: () => ( - } variant="primary" /> - } variant="secondary" /> + } variant="primary" /> + } variant="secondary" /> ), }; @@ -54,8 +54,8 @@ export const Variants: Story = { export const Sizes: Story = { render: () => ( - } size="small" /> - } size="medium" /> + } size="small" /> + } size="medium" /> ), }; @@ -63,33 +63,12 @@ export const Sizes: Story = { export const Disabled: Story = { render: () => ( - } variant="primary" /> - } variant="secondary" /> + } variant="primary" /> + } variant="secondary" /> ), }; -export const AsLink: Story = { - render: () => ( - } - /> - ), -}; - -export const AsComponent: Story = { - render: () => { - const Link = (props: { children?: React.ReactNode; to: string }) => ( - - ); - - return } />; - }, -}; - export const Responsive: Story = { args: { variant: { @@ -101,7 +80,7 @@ export const Responsive: Story = { sm: 'medium', }, }, - render: args => } />, + render: args => } />, }; const variants = ['primary', 'secondary'] as const; @@ -115,20 +94,20 @@ export const Playground: Story = { {variant} {sizes.map(size => ( - } /> - } aria-label="Chevron right icon button" variant={variant} size={size} /> - } aria-label="Chevron right icon button" diff --git a/packages/canon/src/components/IconButton/IconButton.tsx b/packages/canon/src/components/ButtonIcon/ButtonIcon.tsx similarity index 59% rename from packages/canon/src/components/IconButton/IconButton.tsx rename to packages/canon/src/components/ButtonIcon/ButtonIcon.tsx index 73c95ee131..0fc61acaf7 100644 --- a/packages/canon/src/components/IconButton/IconButton.tsx +++ b/packages/canon/src/components/ButtonIcon/ButtonIcon.tsx @@ -15,22 +15,15 @@ */ import clsx from 'clsx'; -import { - ComponentPropsWithRef, - ElementType, - forwardRef, - ReactElement, - Ref, -} from 'react'; +import { forwardRef, Ref } from 'react'; import { Button as RAButton } from 'react-aria-components'; import { useResponsiveValue } from '../../hooks/useResponsiveValue'; -import type { IconButtonProps } from './types'; +import type { ButtonIconProps } from './types'; /** @public */ -export const IconButton = forwardRef( - (props: IconButtonProps, ref: Ref) => { +export const ButtonIcon = forwardRef( + (props: ButtonIconProps, ref: Ref) => { const { - as, size = 'small', variant = 'primary', icon, @@ -39,33 +32,23 @@ export const IconButton = forwardRef( ...rest } = props; - const Component = as || RAButton; const responsiveSize = useResponsiveValue(size); const responsiveVariant = useResponsiveValue(variant); + console.log(clsx('canon-Button', 'canon-ButtonIcon', className)); + return ( - - - + {icon} + ); }, -) as { - ( - props: IconButtonProps & { ref?: ComponentPropsWithRef['ref'] }, - ): ReactElement; - displayName: string; -}; +); -IconButton.displayName = 'IconButton'; +ButtonIcon.displayName = 'ButtonIcon'; diff --git a/packages/canon/src/components/IconButton/index.tsx b/packages/canon/src/components/ButtonIcon/index.tsx similarity index 95% rename from packages/canon/src/components/IconButton/index.tsx rename to packages/canon/src/components/ButtonIcon/index.tsx index 1d30c871a4..686fd844e8 100644 --- a/packages/canon/src/components/IconButton/index.tsx +++ b/packages/canon/src/components/ButtonIcon/index.tsx @@ -14,5 +14,5 @@ * limitations under the License. */ -export * from './IconButton'; +export * from './ButtonIcon'; export * from './types'; diff --git a/packages/canon/src/components/ButtonIcon/styles.css b/packages/canon/src/components/ButtonIcon/styles.css new file mode 100644 index 0000000000..27c96e696e --- /dev/null +++ b/packages/canon/src/components/ButtonIcon/styles.css @@ -0,0 +1,30 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.canon-ButtonIcon { + align-items: center; + justify-content: center; +} + +.canon-ButtonIcon[data-size='small'] { + padding: 0; + width: 2rem; +} + +.canon-ButtonIcon[data-size='medium'] { + padding: 0; + width: 2.5rem; +} diff --git a/packages/canon/src/components/IconButton/types.ts b/packages/canon/src/components/ButtonIcon/types.ts similarity index 56% rename from packages/canon/src/components/IconButton/types.ts rename to packages/canon/src/components/ButtonIcon/types.ts index 1d3deb94c2..7f8b845de4 100644 --- a/packages/canon/src/components/IconButton/types.ts +++ b/packages/canon/src/components/ButtonIcon/types.ts @@ -15,26 +15,19 @@ */ import { Breakpoint } from '@backstage/canon'; -import { ElementType, ReactElement } from 'react'; -import { PolymorphicComponentProps } from '../../types'; +import { ReactElement } from 'react'; +import { ButtonProps as RAButtonProps } from 'react-aria-components'; /** - * Properties for {@link IconButton} + * Properties for {@link ButtonIcon} * * @public */ -export type IconButtonProps = - PolymorphicComponentProps< - TAs, - { - size?: - | 'small' - | 'medium' - | Partial>; - variant?: - | 'primary' - | 'secondary' - | Partial>; - icon?: ReactElement; - } - >; +export interface ButtonIconProps extends RAButtonProps { + size?: 'small' | 'medium' | Partial>; + variant?: + | 'primary' + | 'secondary' + | Partial>; + icon?: ReactElement; +} diff --git a/packages/canon/src/components/ButtonLink/ButtonLink.stories.tsx b/packages/canon/src/components/ButtonLink/ButtonLink.stories.tsx new file mode 100644 index 0000000000..db2883c984 --- /dev/null +++ b/packages/canon/src/components/ButtonLink/ButtonLink.stories.tsx @@ -0,0 +1,212 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { ButtonLink } from './ButtonLink'; +import { Flex } from '../Flex'; +import { Text } from '../Text'; +import { Icon } from '../Icon'; + +const meta = { + title: 'Components/ButtonLink', + component: ButtonLink, + argTypes: { + size: { + control: 'select', + options: ['small', 'medium'], + }, + variant: { + control: 'select', + options: ['primary', 'secondary'], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + children: 'Button', + }, +}; + +export const Variants: Story = { + args: { + children: 'Button', + }, + parameters: { + argTypes: { + variant: { + control: false, + }, + }, + }, + render: () => ( + + } variant="primary"> + Button + + } variant="secondary"> + Button + + + ), +}; + +export const Sizes: Story = { + args: { + children: 'Button', + }, + render: () => ( + + }> + Small + + }> + Medium + + + ), +}; + +export const WithIcons: Story = { + args: { + children: 'Button', + }, + render: args => ( + + } /> + } /> + } + iconEnd={} + /> + + ), +}; + +export const FullWidth: Story = { + args: { + children: 'Button', + }, + render: args => ( + + } /> + } /> + } + iconEnd={} + /> + + ), +}; + +export const Disabled: Story = { + render: () => ( + + + Primary + + + Secondary + + + ), +}; + +export const Responsive: Story = { + args: { + children: 'Button', + variant: { + initial: 'primary', + sm: 'secondary', + }, + size: { + xs: 'small', + sm: 'medium', + }, + }, +}; + +const variants = ['primary', 'secondary'] as const; +const sizes = ['small', 'medium'] as const; + +export const Playground: Story = { + args: { + children: 'Button', + }, + render: () => ( + + {variants.map(variant => ( + + {variant} + {sizes.map(size => ( + + + Button + + } + variant={variant} + size={size} + > + Button + + } + variant={variant} + size={size} + > + Button + + } + iconEnd={} + style={{ width: '200px' }} + variant={variant} + size={size} + > + Button + + + Button + + } + variant={variant} + size={size} + isDisabled + > + Button + + } + variant={variant} + size={size} + isDisabled + > + Button + + + ))} + + ))} + + ), +}; diff --git a/packages/canon/src/components/ButtonLink/ButtonLink.tsx b/packages/canon/src/components/ButtonLink/ButtonLink.tsx new file mode 100644 index 0000000000..72eb78efe3 --- /dev/null +++ b/packages/canon/src/components/ButtonLink/ButtonLink.tsx @@ -0,0 +1,71 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import clsx from 'clsx'; +import { forwardRef, Ref } from 'react'; +import { Link as RALink } from 'react-aria-components'; +import { useResponsiveValue } from '../../hooks/useResponsiveValue'; +import type { ButtonLinkProps } from './types'; + +/** @public */ +export const ButtonLink = forwardRef( + (props: ButtonLinkProps, ref: Ref) => { + const { + size = 'small', + variant = 'primary', + iconStart, + iconEnd, + children, + className, + ...rest + } = props; + + const responsiveSize = useResponsiveValue(size); + const responsiveVariant = useResponsiveValue(variant); + + return ( + + {iconStart && ( + + )} + {children} + {iconEnd && ( + + )} + + ); + }, +); + +ButtonLink.displayName = 'ButtonLink'; diff --git a/packages/canon/src/components/ButtonLink/index.ts b/packages/canon/src/components/ButtonLink/index.ts new file mode 100644 index 0000000000..bbfc696c67 --- /dev/null +++ b/packages/canon/src/components/ButtonLink/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './ButtonLink'; +export * from './types'; diff --git a/packages/canon/src/components/ButtonLink/types.ts b/packages/canon/src/components/ButtonLink/types.ts new file mode 100644 index 0000000000..f6524fbe62 --- /dev/null +++ b/packages/canon/src/components/ButtonLink/types.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Breakpoint } from '@backstage/canon'; +import { ReactElement, ReactNode } from 'react'; +import { LinkProps as RALinkProps } from 'react-aria-components'; + +/** + * Properties for {@link ButtonLink} + * + * @public + */ +export interface ButtonLinkProps extends RALinkProps { + size?: 'small' | 'medium' | Partial>; + variant?: + | 'primary' + | 'secondary' + | Partial>; + iconStart?: ReactElement; + iconEnd?: ReactElement; + children?: ReactNode; +} diff --git a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx index 452e0c34fd..ee9dda7ab5 100644 --- a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx +++ b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx @@ -17,7 +17,7 @@ import { forwardRef } from 'react'; import { Text } from '../../Text'; import { DataTablePaginationProps } from './types'; -import { IconButton } from '../../IconButton'; +import { ButtonIcon } from '../../ButtonIcon'; import clsx from 'clsx'; import { Select } from '../../Select'; import { useDataTable } from '../Root/DataTableRoot'; @@ -71,14 +71,14 @@ const DataTablePagination = forwardRef(
{`${fromCount} - ${toCount} of ${rowCount}`} - table?.previousPage()} isDisabled={!table?.getCanPreviousPage()} icon={} /> - table?.nextPage()} diff --git a/packages/canon/src/components/IconButton/styles.css b/packages/canon/src/components/IconButton/styles.css deleted file mode 100644 index 066b0fbad5..0000000000 --- a/packages/canon/src/components/IconButton/styles.css +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -.canon-IconButton { - border: none; - display: inline-flex; - align-items: center; - justify-content: center; - user-select: none; - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-bold); - padding: 0; - cursor: pointer; - border-radius: var(--canon-radius-2); - gap: var(--canon-space-1_5); - - &:disabled { - cursor: not-allowed; - } -} - -.canon-IconButton[data-variant='primary'] { - background-color: var(--canon-bg-solid); - color: var(--canon-fg-solid); - transition: background-color 150ms ease, box-shadow 150ms ease; - - &:hover { - background-color: var(--canon-bg-solid-hover); - } - - &:active { - background-color: var(--canon-bg-solid-pressed); - } - - &:focus-visible { - outline: 2px solid var(--canon-ring); - outline-offset: 2px; - } - - &:disabled { - background-color: var(--canon-bg-solid-disabled); - color: var(--canon-fg-solid-disabled); - } -} - -.canon-IconButton[data-variant='secondary'] { - background-color: var(--canon-bg-surface-1); - box-shadow: inset 0 0 0 1px var(--canon-border); - color: var(--canon-fg-primary); - transition: box-shadow 150ms ease; - - &:hover { - box-shadow: inset 0 0 0 1px var(--canon-border-hover); - } - - &:active { - box-shadow: inset 0 0 0 1px var(--canon-border-pressed); - } - - &:focus-visible { - outline: none; - transition: none; - box-shadow: inset 0 0 0 2px var(--canon-ring); - } - - &:disabled { - box-shadow: inset 0 0 0 1px var(--canon-border-disabled); - color: var(--canon-fg-disabled); - } -} - -.canon-IconButton[data-size='medium'] { - font-size: var(--canon-font-size-4); - height: 40px; - width: 40px; -} - -.canon-IconButton[data-size='small'] { - font-size: var(--canon-font-size-3); - height: 32px; - width: 32px; -} - -.canon-IconButtonIcon[data-size='small'], -.canon-IconButtonIcon[data-size='small'] svg { - width: 1rem; - height: 1rem; -} - -.canon-IconButtonIcon[data-size='medium'], -.canon-IconButtonIcon[data-size='medium'] svg { - width: 1.25rem; - height: 1.25rem; -} diff --git a/packages/canon/src/css/components.css b/packages/canon/src/css/components.css index c85c1bbdd9..aa453f7314 100644 --- a/packages/canon/src/css/components.css +++ b/packages/canon/src/css/components.css @@ -17,6 +17,7 @@ @import '../components/Avatar/Avatar.styles.css'; @import '../components/Box/styles.css'; @import '../components/Button/styles.css'; +@import '../components/ButtonIcon/styles.css'; @import '../components/Checkbox/styles.css'; @import '../components/Collapsible/Collapsible.styles.css'; @import '../components/Container/styles.css'; @@ -27,7 +28,6 @@ @import '../components/Grid/styles.css'; @import '../components/Heading/styles.css'; @import '../components/Icon/styles.css'; -@import '../components/IconButton/styles.css'; @import '../components/Link/styles.css'; @import '../components/Menu/Menu.styles.css'; @import '../components/Table/styles.css'; diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts index 9b5a454254..69c302bdc5 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -38,7 +38,7 @@ export * from './components/Collapsible'; export * from './components/DataTable'; export * from './components/FieldLabel'; export * from './components/Icon'; -export * from './components/IconButton'; +export * from './components/ButtonIcon'; export * from './components/Checkbox'; export * from './components/Table'; export * from './components/Tabs'; diff --git a/packages/canon/src/types.ts b/packages/canon/src/types.ts index 7e542f3635..ee5d954576 100644 --- a/packages/canon/src/types.ts +++ b/packages/canon/src/types.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { ComponentPropsWithoutRef, ElementType } from 'react'; - /** @public */ export type AsProps = | 'div' @@ -133,19 +131,3 @@ export interface UtilityProps extends SpaceProps { justifyContent?: Responsive; rowSpan?: Responsive; } - -/** @public */ -export type As = { - as?: TAs; -}; - -/** - * This is the first reusable type utility we built - * @public - */ -export type PolymorphicComponentProps< - TAs extends ElementType, - TProps = {}, -> = TProps & - As & - Omit, keyof (As & TProps)>; From 65993b0a74ed110e4a5456645ae40e40b1f14803 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 19 Jun 2025 17:17:38 +0100 Subject: [PATCH 215/301] Improve docs Signed-off-by: Charles de Dreuille --- .../src/components/Sidebar/Sidebar.module.css | 4 - .../src/components/Sidebar/playground.tsx | 5 +- .../src/content/components/button-icon.mdx | 80 ++++++++++++++++ .../content/components/button-icon.props.ts | 59 ++++++++++++ .../src/content/components/button-link.mdx | 91 ++++++++++++++++++ .../content/components/button-link.props.ts | 73 +++++++++++++++ canon-docs/src/content/components/button.mdx | 19 +--- .../src/content/components/button.props.ts | 36 ++++---- .../src/content/components/icon-button.mdx | 92 ------------------- .../content/components/icon-button.props.ts | 57 ------------ canon-docs/src/snippets/stories-snippets.tsx | 36 +++++--- canon-docs/src/utils/data.ts | 15 ++- .../src/components/ButtonIcon/ButtonIcon.tsx | 2 - .../ButtonLink/ButtonLink.stories.tsx | 24 ++--- .../src/components/ButtonLink/ButtonLink.tsx | 20 +--- 15 files changed, 372 insertions(+), 241 deletions(-) create mode 100644 canon-docs/src/content/components/button-icon.mdx create mode 100644 canon-docs/src/content/components/button-icon.props.ts create mode 100644 canon-docs/src/content/components/button-link.mdx create mode 100644 canon-docs/src/content/components/button-link.props.ts delete mode 100644 canon-docs/src/content/components/icon-button.mdx delete mode 100644 canon-docs/src/content/components/icon-button.props.ts diff --git a/canon-docs/src/components/Sidebar/Sidebar.module.css b/canon-docs/src/components/Sidebar/Sidebar.module.css index e558621670..4eb49682df 100644 --- a/canon-docs/src/components/Sidebar/Sidebar.module.css +++ b/canon-docs/src/components/Sidebar/Sidebar.module.css @@ -55,10 +55,6 @@ gap: 2px; } -.playgroundSection { - position: absolute; -} - .sectionTitle { font-size: var(--canon-font-size-3); font-weight: var(--canon-font-weight-bold); diff --git a/canon-docs/src/components/Sidebar/playground.tsx b/canon-docs/src/components/Sidebar/playground.tsx index eb1c3213cc..2aaa4fe2c6 100644 --- a/canon-docs/src/components/Sidebar/playground.tsx +++ b/canon-docs/src/components/Sidebar/playground.tsx @@ -38,9 +38,7 @@ export const Playground = () => { return ( { visibility: isPlayground ? 'visible' : 'hidden', }} transition={{ duration: 0.2 }} + style={{ position: 'absolute' }} >
Components
{components.map(({ slug, title }) => ( diff --git a/canon-docs/src/content/components/button-icon.mdx b/canon-docs/src/content/components/button-icon.mdx new file mode 100644 index 0000000000..2281814a89 --- /dev/null +++ b/canon-docs/src/content/components/button-icon.mdx @@ -0,0 +1,80 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ButtonIconSnippet } from '@/snippets/stories-snippets'; +import { + buttonIconPropDefs, + buttonIconUsageSnippet, + buttonIconDefaultSnippet, + buttonIconVariantsSnippet, + buttonIconSizesSnippet, + buttonIconDisabledSnippet, + buttonIconResponsiveSnippet, + buttonIconAsLinkSnippet, +} from './button-icon.props'; +import { ComponentInfos } from '@/components/ComponentInfos'; + +# Icon Button + +A button component with a single icon that can be used to trigger actions. + +} + code={buttonIconDefaultSnippet} +/> + + + +## API reference + + + +## Examples + +### Variants + +Here's a view when buttons have different variants. + +} + code={buttonIconVariantsSnippet} +/> + +### Sizes + +Here's a view when buttons have different sizes. + +} + code={buttonIconSizesSnippet} +/> + +### Disabled + +Here's a view when buttons are disabled. + +} + code={buttonIconDisabledSnippet} +/> + +### Responsive + +Here's a view when buttons are responsive. + + diff --git a/canon-docs/src/content/components/button-icon.props.ts b/canon-docs/src/content/components/button-icon.props.ts new file mode 100644 index 0000000000..160690a29e --- /dev/null +++ b/canon-docs/src/content/components/button-icon.props.ts @@ -0,0 +1,59 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const buttonIconPropDefs: Record = { + variant: { + type: 'enum', + values: ['primary', 'secondary'], + default: 'primary', + responsive: true, + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'medium', + responsive: true, + }, + icon: { type: 'enum', values: ['ReactNode'], responsive: false }, + isDisabled: { type: 'boolean', default: 'false', responsive: false }, + type: { + type: 'enum', + values: ['button', 'submit', 'reset'], + default: 'button', + responsive: false, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const buttonIconUsageSnippet = `import { ButtonIcon } from '@backstage/canon'; + +`; + +export const buttonIconDefaultSnippet = ` + } variant="primary" /> + } variant="secondary" /> +`; + +export const buttonIconVariantsSnippet = ` + } variant="primary" /> + } variant="secondary" /> +`; + +export const buttonIconSizesSnippet = ` + } size="small" /> + } size="medium" /> +`; + +export const buttonIconDisabledSnippet = `} disabled />`; + +export const buttonIconResponsiveSnippet = `} variant={{ initial: 'primary', lg: 'secondary' }} />`; + +export const buttonIconAsLinkSnippet = `import { ButtonLink } from '@backstage/canon'; + + + Button +`; diff --git a/canon-docs/src/content/components/button-link.mdx b/canon-docs/src/content/components/button-link.mdx new file mode 100644 index 0000000000..3f24874220 --- /dev/null +++ b/canon-docs/src/content/components/button-link.mdx @@ -0,0 +1,91 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { CodeBlock } from '@/components/CodeBlock'; +import { ButtonLinkSnippet } from '@/snippets/stories-snippets'; +import { + buttonLinkPropDefs, + buttonLinkSnippetUsage, + buttonLinkVariantsSnippet, + buttonLinkSizesSnippet, + buttonLinkIconsSnippet, + buttonLinkDisabledSnippet, + buttonLinkResponsiveSnippet, +} from './button-link.props'; +import { ComponentInfos } from '@/components/ComponentInfos'; + +# ButtonLink + +A button component that can be used as a link. + +} + code={buttonLinkVariantsSnippet} +/> + + + +## API reference + + + +## Examples + +### Variants + +Here's a view when buttons have different variants. + +} + code={buttonLinkVariantsSnippet} +/> + +### Sizes + +Here's a view when buttons have different sizes. + +} + code={buttonLinkSizesSnippet} +/> + +### With Icons + +Here's a view when buttons have icons. + +} + code={buttonLinkIconsSnippet} +/> + +### Disabled + +Here's a view when buttons are disabled. + +} + code={buttonLinkDisabledSnippet} +/> + +### Responsive + +Here's a view when buttons are responsive. + + diff --git a/canon-docs/src/content/components/button-link.props.ts b/canon-docs/src/content/components/button-link.props.ts new file mode 100644 index 0000000000..94d696b736 --- /dev/null +++ b/canon-docs/src/content/components/button-link.props.ts @@ -0,0 +1,73 @@ +import { classNamePropDefs, stylePropDefs } from '../../utils/propDefs'; +import type { PropDef } from '../../utils/propDefs'; + +export const buttonLinkPropDefs: Record = { + variant: { + type: 'enum', + values: ['primary', 'secondary'], + default: 'primary', + responsive: true, + }, + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'medium', + responsive: true, + }, + iconStart: { type: 'enum', values: ['ReactNode'], responsive: false }, + iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false }, + isDisabled: { type: 'boolean', default: 'false', responsive: false }, + href: { type: 'string', responsive: false }, + hrefLang: { type: 'string', responsive: false }, + target: { + type: 'enum', + values: ['HTMLAttributeAnchorTarget'], + default: '_self', + responsive: false, + }, + rel: { type: 'string', responsive: false }, + children: { type: 'enum', values: ['ReactNode'], responsive: false }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const buttonLinkSnippetUsage = `import { ButtonLink } from '@backstage/canon'; + +`; + +export const buttonLinkVariantsSnippet = ` + + Button + + + Button + +`; + +export const buttonLinkSizesSnippet = ` + Small + Medium +`; + +export const buttonLinkIconsSnippet = ` + }>Button + }>Button + } + iconEnd={}> + Button + +`; + +export const buttonLinkDisabledSnippet = ` + + Primary + + + Secondary + +`; + +export const buttonLinkResponsiveSnippet = ` + Responsive Button +`; diff --git a/canon-docs/src/content/components/button.mdx b/canon-docs/src/content/components/button.mdx index 6690a90e23..89d3c3f612 100644 --- a/canon-docs/src/content/components/button.mdx +++ b/canon-docs/src/content/components/button.mdx @@ -1,14 +1,13 @@ import { PropsTable } from '@/components/PropsTable'; import { Snippet } from '@/components/Snippet'; import { CodeBlock } from '@/components/CodeBlock'; -import { ButtonSnippet } from '@/snippets/stories-snippets'; +import { ButtonSnippet, ButtonLinkSnippet } from '@/snippets/stories-snippets'; import { buttonPropDefs, buttonSnippetUsage, buttonVariantsSnippet, buttonSizesSnippet, buttonIconsSnippet, - buttonFullWidthSnippet, buttonDisabledSnippet, buttonResponsiveSnippet, buttonAsLinkSnippet, @@ -74,18 +73,6 @@ Here's a view when buttons have icons. code={buttonIconsSnippet} /> -### Full width - -Here's a view when buttons are full width. - -} - code={buttonFullWidthSnippet} -/> - ### Disabled Here's a view when buttons are disabled. @@ -106,12 +93,12 @@ Here's a view when buttons are responsive. ### As Link -You can use the `as` prop to make a button act as a link. +If you want to use a button as a link, please use the `ButtonLink` component. } + preview={} code={buttonAsLinkSnippet} /> diff --git a/canon-docs/src/content/components/button.props.ts b/canon-docs/src/content/components/button.props.ts index ef72a70596..b32cba4725 100644 --- a/canon-docs/src/content/components/button.props.ts +++ b/canon-docs/src/content/components/button.props.ts @@ -16,7 +16,14 @@ export const buttonPropDefs: Record = { }, iconStart: { type: 'enum', values: ['ReactNode'], responsive: false }, iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false }, + isDisabled: { type: 'boolean', default: 'false', responsive: false }, children: { type: 'enum', values: ['ReactNode'], responsive: false }, + type: { + type: 'enum', + values: ['button', 'submit', 'reset'], + default: 'button', + responsive: false, + }, ...classNamePropDefs, ...stylePropDefs, }; @@ -32,9 +39,6 @@ export const buttonVariantsSnippet = ` - `; export const buttonSizesSnippet = ` @@ -43,20 +47,16 @@ export const buttonSizesSnippet = ` `; export const buttonIconsSnippet = ` - - - -`; - -export const buttonFullWidthSnippet = ` - + + + `; export const buttonDisabledSnippet = ` - - `; @@ -65,12 +65,8 @@ export const buttonResponsiveSnippet = ` +export const buttonAsLinkSnippet = `import { ButtonLink } from '@backstage/canon'; -// Using a custom component -`; + + Button +`; diff --git a/canon-docs/src/content/components/icon-button.mdx b/canon-docs/src/content/components/icon-button.mdx deleted file mode 100644 index d8eb3beab2..0000000000 --- a/canon-docs/src/content/components/icon-button.mdx +++ /dev/null @@ -1,92 +0,0 @@ -import { PropsTable } from '@/components/PropsTable'; -import { Snippet } from '@/components/Snippet'; -import { CodeBlock } from '@/components/CodeBlock'; -import { IconButtonSnippet } from '@/snippets/stories-snippets'; -import { - iconButtonPropDefs, - iconButtonUsageSnippet, - iconButtonDefaultSnippet, - iconButtonVariantsSnippet, - iconButtonSizesSnippet, - iconButtonDisabledSnippet, - iconButtonResponsiveSnippet, - iconButtonAsLinkSnippet, -} from './icon-button.props'; -import { ComponentInfos } from '@/components/ComponentInfos'; - -# Icon Button - -A button component with a single icon that can be used to trigger actions. - -} - code={iconButtonDefaultSnippet} -/> - - - -## API reference - - - -## Examples - -### Variants - -Here's a view when buttons have different variants. - -} - code={iconButtonVariantsSnippet} -/> - -### Sizes - -Here's a view when buttons have different sizes. - -} - code={iconButtonSizesSnippet} -/> - -### Disabled - -Here's a view when buttons are disabled. - -} - code={iconButtonDisabledSnippet} -/> - -### Responsive - -Here's a view when buttons are responsive. - - - -### As Link - -You can use the `as` prop to make a button act as a link. - -} - code={iconButtonAsLinkSnippet} -/> diff --git a/canon-docs/src/content/components/icon-button.props.ts b/canon-docs/src/content/components/icon-button.props.ts deleted file mode 100644 index 9e3a2818fe..0000000000 --- a/canon-docs/src/content/components/icon-button.props.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { - classNamePropDefs, - stylePropDefs, - type PropDef, -} from '@/utils/propDefs'; - -export const iconButtonPropDefs: Record = { - variant: { - type: 'enum', - values: ['primary', 'secondary'], - default: 'primary', - responsive: true, - }, - size: { - type: 'enum', - values: ['small', 'medium'], - default: 'medium', - responsive: true, - }, - icon: { type: 'enum', values: ['ReactNode'], responsive: false }, - ...classNamePropDefs, - ...stylePropDefs, -}; - -export const iconButtonUsageSnippet = `import { IconButton } from '@backstage/canon'; - -`; - -export const iconButtonDefaultSnippet = ` - } variant="primary" /> - } variant="secondary" /> -`; - -export const iconButtonVariantsSnippet = ` - } variant="primary" /> - } variant="secondary" /> -`; - -export const iconButtonSizesSnippet = ` - } size="small" /> - } size="medium" /> -`; - -export const iconButtonDisabledSnippet = `} disabled />`; - -export const iconButtonResponsiveSnippet = `} variant={{ initial: 'primary', lg: 'secondary' }} />`; - -export const iconButtonAsLinkSnippet = `// Using the \`as\` prop -} -/> - -// Using a custom component -} />`; diff --git a/canon-docs/src/snippets/stories-snippets.tsx b/canon-docs/src/snippets/stories-snippets.tsx index 95738a6330..e5a7464b43 100644 --- a/canon-docs/src/snippets/stories-snippets.tsx +++ b/canon-docs/src/snippets/stories-snippets.tsx @@ -3,11 +3,12 @@ import { composeStories } from '@storybook/react'; import * as BoxStories from '../../../packages/canon/src/components/Box/Box.stories'; import * as ButtonStories from '../../../packages/canon/src/components/Button/Button.stories'; +import * as ButtonIconStories from '../../../packages/canon/src/components/ButtonIcon/ButtonIcon.stories'; +import * as ButtonLinkStories from '../../../packages/canon/src/components/ButtonLink/ButtonLink.stories'; import * as CheckboxStories from '../../../packages/canon/src/components/Checkbox/Checkbox.stories'; import * as ContainerStories from '../../../packages/canon/src/components/Container/Container.stories'; import * as GridStories from '../../../packages/canon/src/components/Grid/Grid.stories'; import * as HeadingStories from '../../../packages/canon/src/components/Heading/Heading.stories'; -import * as IconButtonStories from '../../../packages/canon/src/components/IconButton/IconButton.stories'; import * as IconStories from '../../../packages/canon/src/components/Icon/Icon.stories'; import * as TextFieldStories from '../../../packages/canon/src/components/TextField/TextField.stories'; import * as TextStories from '../../../packages/canon/src/components/Text/Text.stories'; @@ -38,6 +39,28 @@ export const ButtonSnippet = ({ return StoryComponent ? : null; }; +export const ButtonIconSnippet = ({ + story, +}: { + story: keyof typeof ButtonIconStories; +}) => { + const stories = composeStories(ButtonIconStories); + const StoryComponent = stories[story as keyof typeof stories]; + + return StoryComponent ? : null; +}; + +export const ButtonLinkSnippet = ({ + story, +}: { + story: keyof typeof ButtonLinkStories; +}) => { + const stories = composeStories(ButtonLinkStories); + const StoryComponent = stories[story as keyof typeof stories]; + + return StoryComponent ? : null; +}; + export const CheckboxSnippet = ({ story, }: { @@ -96,17 +119,6 @@ export const HeadingSnippet = ({ return StoryComponent ? : null; }; -export const IconButtonSnippet = ({ - story, -}: { - story: keyof typeof IconButtonStories; -}) => { - const stories = composeStories(IconButtonStories); - const StoryComponent = stories[story as keyof typeof stories]; - - return StoryComponent ? : null; -}; - export const IconSnippet = ({ story }: { story: keyof typeof IconStories }) => { const stories = composeStories(IconStories); const StoryComponent = stories[story as keyof typeof stories]; diff --git a/canon-docs/src/utils/data.ts b/canon-docs/src/utils/data.ts index 20ff9146a7..a741ad5160 100644 --- a/canon-docs/src/utils/data.ts +++ b/canon-docs/src/utils/data.ts @@ -76,6 +76,16 @@ export const components: Page[] = [ slug: 'button', status: 'alpha', }, + { + title: 'ButtonIcon', + slug: 'button-icon', + status: 'alpha', + }, + { + title: 'ButtonLink', + slug: 'button-link', + status: 'alpha', + }, { title: 'Checkbox', slug: 'checkbox', @@ -96,11 +106,6 @@ export const components: Page[] = [ slug: 'icon', status: 'alpha', }, - { - title: 'IconButton', - slug: 'icon-button', - status: 'alpha', - }, { title: 'Link', slug: 'link', diff --git a/packages/canon/src/components/ButtonIcon/ButtonIcon.tsx b/packages/canon/src/components/ButtonIcon/ButtonIcon.tsx index 0fc61acaf7..43c2f598ac 100644 --- a/packages/canon/src/components/ButtonIcon/ButtonIcon.tsx +++ b/packages/canon/src/components/ButtonIcon/ButtonIcon.tsx @@ -35,8 +35,6 @@ export const ButtonIcon = forwardRef( const responsiveSize = useResponsiveValue(size); const responsiveVariant = useResponsiveValue(variant); - console.log(clsx('canon-Button', 'canon-ButtonIcon', className)); - return ( ( - } variant="primary"> + } + variant="primary" + href="https://canon.backstage.io" + target="_blank" + > Button - } variant="secondary"> + } + variant="secondary" + href="https://canon.backstage.io" + target="_blank" + > Button diff --git a/packages/canon/src/components/ButtonLink/ButtonLink.tsx b/packages/canon/src/components/ButtonLink/ButtonLink.tsx index 72eb78efe3..cb68401160 100644 --- a/packages/canon/src/components/ButtonLink/ButtonLink.tsx +++ b/packages/canon/src/components/ButtonLink/ButtonLink.tsx @@ -44,25 +44,9 @@ export const ButtonLink = forwardRef( ref={ref} {...rest} > - {iconStart && ( - - )} + {iconStart} {children} - {iconEnd && ( - - )} + {iconEnd} ); }, From 71105b9bc4e93b04ba8fe4f1860e857fe3d89e33 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 19 Jun 2025 17:22:02 +0100 Subject: [PATCH 216/301] Fix some props Signed-off-by: Charles de Dreuille --- canon-docs/src/content/components/button-icon.mdx | 4 ++-- canon-docs/src/content/components/button-icon.props.ts | 2 +- canon-docs/src/content/components/button-link.mdx | 2 +- canon-docs/src/content/components/button-link.props.ts | 4 ++-- canon-docs/src/content/components/button.mdx | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/canon-docs/src/content/components/button-icon.mdx b/canon-docs/src/content/components/button-icon.mdx index 2281814a89..b020b19f33 100644 --- a/canon-docs/src/content/components/button-icon.mdx +++ b/canon-docs/src/content/components/button-icon.mdx @@ -14,7 +14,7 @@ import { } from './button-icon.props'; import { ComponentInfos } from '@/components/ComponentInfos'; -# Icon Button +# ButtonIcon A button component with a single icon that can be used to trigger actions. @@ -27,7 +27,7 @@ A button component with a single icon that can be used to trigger actions. diff --git a/canon-docs/src/content/components/button-icon.props.ts b/canon-docs/src/content/components/button-icon.props.ts index 160690a29e..a21bf3cd57 100644 --- a/canon-docs/src/content/components/button-icon.props.ts +++ b/canon-docs/src/content/components/button-icon.props.ts @@ -48,7 +48,7 @@ export const buttonIconSizesSnippet = ` } size="medium" /> `; -export const buttonIconDisabledSnippet = `} disabled />`; +export const buttonIconDisabledSnippet = `} isDisabled />`; export const buttonIconResponsiveSnippet = `} variant={{ initial: 'primary', lg: 'secondary' }} />`; diff --git a/canon-docs/src/content/components/button-link.mdx b/canon-docs/src/content/components/button-link.mdx index 3f24874220..f6da987941 100644 --- a/canon-docs/src/content/components/button-link.mdx +++ b/canon-docs/src/content/components/button-link.mdx @@ -26,7 +26,7 @@ A button component that can be used as a link. diff --git a/canon-docs/src/content/components/button-link.props.ts b/canon-docs/src/content/components/button-link.props.ts index 94d696b736..b92b3154e5 100644 --- a/canon-docs/src/content/components/button-link.props.ts +++ b/canon-docs/src/content/components/button-link.props.ts @@ -36,10 +36,10 @@ export const buttonLinkSnippetUsage = `import { ButtonLink } from '@backstage/ca `; export const buttonLinkVariantsSnippet = ` - + } variant="primary"> Button - + } variant="secondary"> Button `; diff --git a/canon-docs/src/content/components/button.mdx b/canon-docs/src/content/components/button.mdx index 89d3c3f612..c0053d0f5e 100644 --- a/canon-docs/src/content/components/button.mdx +++ b/canon-docs/src/content/components/button.mdx @@ -27,7 +27,7 @@ A button component that can be used to trigger actions. From afb728d8b7bce848fcd9103dc7c3e00de1ec5eed Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 19 Jun 2025 17:30:20 +0100 Subject: [PATCH 217/301] Update public-nails-melt.md Signed-off-by: Charles de Dreuille --- .changeset/public-nails-melt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/public-nails-melt.md b/.changeset/public-nails-melt.md index 169bf7fbfb..b4c77e764e 100644 --- a/.changeset/public-nails-melt.md +++ b/.changeset/public-nails-melt.md @@ -2,4 +2,4 @@ '@backstage/canon': minor --- -**Breaking** We are transforming the way we handle custom tags for both Button and IconButton. We are introducing a new as prop for both of these components and we are removing support for the render prop. +**Breaking** We are transforming our Button component to allow for beatter support for button links. Instead of using the render prop, we are introducing the ButtonLin component and renaming IconButton to ButtonIcon to make it consistent. We are also removing the render prop on all button components. From 072c25dd51bc7b835483ff8171a3272b29ee23cb Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 19 Jun 2025 17:37:49 +0100 Subject: [PATCH 218/301] Update public-nails-melt.md Signed-off-by: Charles de Dreuille --- .changeset/public-nails-melt.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.changeset/public-nails-melt.md b/.changeset/public-nails-melt.md index b4c77e764e..1529ee94d0 100644 --- a/.changeset/public-nails-melt.md +++ b/.changeset/public-nails-melt.md @@ -2,4 +2,12 @@ '@backstage/canon': minor --- -**Breaking** We are transforming our Button component to allow for beatter support for button links. Instead of using the render prop, we are introducing the ButtonLin component and renaming IconButton to ButtonIcon to make it consistent. We are also removing the render prop on all button components. +**BREAKING CHANGES** + +We’re updating our Button component to provide better support for button links. + +- We’re introducing a new `ButtonLink` component, which replaces the previous render prop pattern. +- To maintain naming consistency across components, `IconButton` is being renamed to `ButtonIcon`. +- Additionally, the render prop will be removed from all button-related components. + +These changes aim to simplify usage and improve clarity in our component API. From ba3d00f7d037704a575dc35555ae02eb9e6ce7f8 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 19 Jun 2025 18:11:41 +0100 Subject: [PATCH 219/301] Update styles.css Signed-off-by: Charles de Dreuille --- packages/canon/css/styles.css | 10487 +------------------------------- 1 file changed, 1 insertion(+), 10486 deletions(-) diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index 9f32da5d66..93b7234e90 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -1,10487 +1,2 @@ /*! modern-normalize v3.0.1 | MIT License | https://github.com/sindresorhus/modern-normalize */ -@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; - } -} - -.cu-p { - padding: var(--p); -} - -.cu-p-0\.5 { - padding: var(--canon-space-0_5); -} - -.cu-p-1 { - padding: var(--canon-space-1); -} - -.cu-p-1\.5 { - padding: var(--canon-space-1_5); -} - -.cu-p-2 { - padding: var(--canon-space-2); -} - -.cu-p-3 { - padding: var(--canon-space-3); -} - -.cu-p-4 { - padding: var(--canon-space-4); -} - -.cu-p-5 { - padding: var(--canon-space-5); -} - -.cu-p-6 { - padding: var(--canon-space-6); -} - -.cu-p-7 { - padding: var(--canon-space-7); -} - -.cu-p-8 { - padding: var(--canon-space-8); -} - -.cu-p-9 { - padding: var(--canon-space-9); -} - -.cu-p-10 { - padding: var(--canon-space-10); -} - -.cu-p-11 { - padding: var(--canon-space-11); -} - -.cu-p-12 { - padding: var(--canon-space-12); -} - -.cu-p-13 { - padding: var(--canon-space-13); -} - -.cu-p-14 { - padding: var(--canon-space-14); -} - -@media (width >= 640px) { - .xs\:cu-p { - padding: var(--p-xs); - } - - .xs\:cu-p-0\.5 { - padding: var(--canon-space-0_5); - } - - .xs\:cu-p-1 { - padding: var(--canon-space-1); - } - - .xs\:cu-p-1\.5 { - padding: var(--canon-space-1_5); - } - - .xs\:cu-p-2 { - padding: var(--canon-space-2); - } - - .xs\:cu-p-3 { - padding: var(--canon-space-3); - } - - .xs\:cu-p-4 { - padding: var(--canon-space-4); - } - - .xs\:cu-p-5 { - padding: var(--canon-space-5); - } - - .xs\:cu-p-6 { - padding: var(--canon-space-6); - } - - .xs\:cu-p-7 { - padding: var(--canon-space-7); - } - - .xs\:cu-p-8 { - padding: var(--canon-space-8); - } - - .xs\:cu-p-9 { - padding: var(--canon-space-9); - } - - .xs\:cu-p-10 { - padding: var(--canon-space-10); - } - - .xs\:cu-p-11 { - padding: var(--canon-space-11); - } - - .xs\:cu-p-12 { - padding: var(--canon-space-12); - } - - .xs\:cu-p-13 { - padding: var(--canon-space-13); - } - - .xs\:cu-p-14 { - padding: var(--canon-space-14); - } -} - -@media (width >= 768px) { - .sm\:cu-p { - padding: var(--p-sm); - } - - .sm\:cu-p-0\.5 { - padding: var(--canon-space-0_5); - } - - .sm\:cu-p-1 { - padding: var(--canon-space-1); - } - - .sm\:cu-p-1\.5 { - padding: var(--canon-space-1_5); - } - - .sm\:cu-p-2 { - padding: var(--canon-space-2); - } - - .sm\:cu-p-3 { - padding: var(--canon-space-3); - } - - .sm\:cu-p-4 { - padding: var(--canon-space-4); - } - - .sm\:cu-p-5 { - padding: var(--canon-space-5); - } - - .sm\:cu-p-6 { - padding: var(--canon-space-6); - } - - .sm\:cu-p-7 { - padding: var(--canon-space-7); - } - - .sm\:cu-p-8 { - padding: var(--canon-space-8); - } - - .sm\:cu-p-9 { - padding: var(--canon-space-9); - } - - .sm\:cu-p-10 { - padding: var(--canon-space-10); - } - - .sm\:cu-p-11 { - padding: var(--canon-space-11); - } - - .sm\:cu-p-12 { - padding: var(--canon-space-12); - } - - .sm\:cu-p-13 { - padding: var(--canon-space-13); - } - - .sm\:cu-p-14 { - padding: var(--canon-space-14); - } -} - -@media (width >= 1024px) { - .md\:cu-p { - padding: var(--p-md); - } - - .md\:cu-p-0\.5 { - padding: var(--canon-space-0_5); - } - - .md\:cu-p-1 { - padding: var(--canon-space-1); - } - - .md\:cu-p-1\.5 { - padding: var(--canon-space-1_5); - } - - .md\:cu-p-2 { - padding: var(--canon-space-2); - } - - .md\:cu-p-3 { - padding: var(--canon-space-3); - } - - .md\:cu-p-4 { - padding: var(--canon-space-4); - } - - .md\:cu-p-5 { - padding: var(--canon-space-5); - } - - .md\:cu-p-6 { - padding: var(--canon-space-6); - } - - .md\:cu-p-7 { - padding: var(--canon-space-7); - } - - .md\:cu-p-8 { - padding: var(--canon-space-8); - } - - .md\:cu-p-9 { - padding: var(--canon-space-9); - } - - .md\:cu-p-10 { - padding: var(--canon-space-10); - } - - .md\:cu-p-11 { - padding: var(--canon-space-11); - } - - .md\:cu-p-12 { - padding: var(--canon-space-12); - } - - .md\:cu-p-13 { - padding: var(--canon-space-13); - } - - .md\:cu-p-14 { - padding: var(--canon-space-14); - } -} - -@media (width >= 1280px) { - .lg\:cu-p { - padding: var(--p-lg); - } - - .lg\:cu-p-0\.5 { - padding: var(--canon-space-0_5); - } - - .lg\:cu-p-1 { - padding: var(--canon-space-1); - } - - .lg\:cu-p-1\.5 { - padding: var(--canon-space-1_5); - } - - .lg\:cu-p-2 { - padding: var(--canon-space-2); - } - - .lg\:cu-p-3 { - padding: var(--canon-space-3); - } - - .lg\:cu-p-4 { - padding: var(--canon-space-4); - } - - .lg\:cu-p-5 { - padding: var(--canon-space-5); - } - - .lg\:cu-p-6 { - padding: var(--canon-space-6); - } - - .lg\:cu-p-7 { - padding: var(--canon-space-7); - } - - .lg\:cu-p-8 { - padding: var(--canon-space-8); - } - - .lg\:cu-p-9 { - padding: var(--canon-space-9); - } - - .lg\:cu-p-10 { - padding: var(--canon-space-10); - } - - .lg\:cu-p-11 { - padding: var(--canon-space-11); - } - - .lg\:cu-p-12 { - padding: var(--canon-space-12); - } - - .lg\:cu-p-13 { - padding: var(--canon-space-13); - } - - .lg\:cu-p-14 { - padding: var(--canon-space-14); - } -} - -@media (width >= 1536px) { - .xl\:cu-p { - padding: var(--p-xl); - } - - .xl\:cu-p-0\.5 { - padding: var(--canon-space-0_5); - } - - .xl\:cu-p-1 { - padding: var(--canon-space-1); - } - - .xl\:cu-p-1\.5 { - padding: var(--canon-space-1_5); - } - - .xl\:cu-p-2 { - padding: var(--canon-space-2); - } - - .xl\:cu-p-3 { - padding: var(--canon-space-3); - } - - .xl\:cu-p-4 { - padding: var(--canon-space-4); - } - - .xl\:cu-p-5 { - padding: var(--canon-space-5); - } - - .xl\:cu-p-6 { - padding: var(--canon-space-6); - } - - .xl\:cu-p-7 { - padding: var(--canon-space-7); - } - - .xl\:cu-p-8 { - padding: var(--canon-space-8); - } - - .xl\:cu-p-9 { - padding: var(--canon-space-9); - } - - .xl\:cu-p-10 { - padding: var(--canon-space-10); - } - - .xl\:cu-p-11 { - padding: var(--canon-space-11); - } - - .xl\:cu-p-12 { - padding: var(--canon-space-12); - } - - .xl\:cu-p-13 { - padding: var(--canon-space-13); - } - - .xl\:cu-p-14 { - padding: var(--canon-space-14); - } -} - -.cu-pl { - padding-left: var(--pl); -} - -.cu-pl-0\.5 { - padding-left: var(--canon-space-0_5); -} - -.cu-pl-1 { - padding-left: var(--canon-space-1); -} - -.cu-pl-1\.5 { - padding-left: var(--canon-space-1_5); -} - -.cu-pl-2 { - padding-left: var(--canon-space-2); -} - -.cu-pl-3 { - padding-left: var(--canon-space-3); -} - -.cu-pl-4 { - padding-left: var(--canon-space-4); -} - -.cu-pl-5 { - padding-left: var(--canon-space-5); -} - -.cu-pl-6 { - padding-left: var(--canon-space-6); -} - -.cu-pl-7 { - padding-left: var(--canon-space-7); -} - -.cu-pl-8 { - padding-left: var(--canon-space-8); -} - -.cu-pl-9 { - padding-left: var(--canon-space-9); -} - -.cu-pl-10 { - padding-left: var(--canon-space-10); -} - -.cu-pl-11 { - padding-left: var(--canon-space-11); -} - -.cu-pl-12 { - padding-left: var(--canon-space-12); -} - -.cu-pl-13 { - padding-left: var(--canon-space-13); -} - -.cu-pl-14 { - padding-left: var(--canon-space-14); -} - -@media (width >= 640px) { - .xs\:cu-pl { - padding-left: var(--pl-xs); - } - - .xs\:cu-pl-0\.5 { - padding-left: var(--canon-space-0_5); - } - - .xs\:cu-pl-1 { - padding-left: var(--canon-space-1); - } - - .xs\:cu-pl-1\.5 { - padding-left: var(--canon-space-1_5); - } - - .xs\:cu-pl-2 { - padding-left: var(--canon-space-2); - } - - .xs\:cu-pl-3 { - padding-left: var(--canon-space-3); - } - - .xs\:cu-pl-4 { - padding-left: var(--canon-space-4); - } - - .xs\:cu-pl-5 { - padding-left: var(--canon-space-5); - } - - .xs\:cu-pl-6 { - padding-left: var(--canon-space-6); - } - - .xs\:cu-pl-7 { - padding-left: var(--canon-space-7); - } - - .xs\:cu-pl-8 { - padding-left: var(--canon-space-8); - } - - .xs\:cu-pl-9 { - padding-left: var(--canon-space-9); - } - - .xs\:cu-pl-10 { - padding-left: var(--canon-space-10); - } - - .xs\:cu-pl-11 { - padding-left: var(--canon-space-11); - } - - .xs\:cu-pl-12 { - padding-left: var(--canon-space-12); - } - - .xs\:cu-pl-13 { - padding-left: var(--canon-space-13); - } - - .xs\:cu-pl-14 { - padding-left: var(--canon-space-14); - } -} - -@media (width >= 768px) { - .sm\:cu-pl { - padding-left: var(--pl-sm); - } - - .sm\:cu-pl-0\.5 { - padding-left: var(--canon-space-0_5); - } - - .sm\:cu-pl-1 { - padding-left: var(--canon-space-1); - } - - .sm\:cu-pl-1\.5 { - padding-left: var(--canon-space-1_5); - } - - .sm\:cu-pl-2 { - padding-left: var(--canon-space-2); - } - - .sm\:cu-pl-3 { - padding-left: var(--canon-space-3); - } - - .sm\:cu-pl-4 { - padding-left: var(--canon-space-4); - } - - .sm\:cu-pl-5 { - padding-left: var(--canon-space-5); - } - - .sm\:cu-pl-6 { - padding-left: var(--canon-space-6); - } - - .sm\:cu-pl-7 { - padding-left: var(--canon-space-7); - } - - .sm\:cu-pl-8 { - padding-left: var(--canon-space-8); - } - - .sm\:cu-pl-9 { - padding-left: var(--canon-space-9); - } - - .sm\:cu-pl-10 { - padding-left: var(--canon-space-10); - } - - .sm\:cu-pl-11 { - padding-left: var(--canon-space-11); - } - - .sm\:cu-pl-12 { - padding-left: var(--canon-space-12); - } - - .sm\:cu-pl-13 { - padding-left: var(--canon-space-13); - } - - .sm\:cu-pl-14 { - padding-left: var(--canon-space-14); - } -} - -@media (width >= 1024px) { - .md\:cu-pl { - padding-left: var(--pl-md); - } - - .md\:cu-pl-0\.5 { - padding-left: var(--canon-space-0_5); - } - - .md\:cu-pl-1 { - padding-left: var(--canon-space-1); - } - - .md\:cu-pl-1\.5 { - padding-left: var(--canon-space-1_5); - } - - .md\:cu-pl-2 { - padding-left: var(--canon-space-2); - } - - .md\:cu-pl-3 { - padding-left: var(--canon-space-3); - } - - .md\:cu-pl-4 { - padding-left: var(--canon-space-4); - } - - .md\:cu-pl-5 { - padding-left: var(--canon-space-5); - } - - .md\:cu-pl-6 { - padding-left: var(--canon-space-6); - } - - .md\:cu-pl-7 { - padding-left: var(--canon-space-7); - } - - .md\:cu-pl-8 { - padding-left: var(--canon-space-8); - } - - .md\:cu-pl-9 { - padding-left: var(--canon-space-9); - } - - .md\:cu-pl-10 { - padding-left: var(--canon-space-10); - } - - .md\:cu-pl-11 { - padding-left: var(--canon-space-11); - } - - .md\:cu-pl-12 { - padding-left: var(--canon-space-12); - } - - .md\:cu-pl-13 { - padding-left: var(--canon-space-13); - } - - .md\:cu-pl-14 { - padding-left: var(--canon-space-14); - } -} - -@media (width >= 1280px) { - .lg\:cu-pl { - padding-left: var(--pl-lg); - } - - .lg\:cu-pl-0\.5 { - padding-left: var(--canon-space-0_5); - } - - .lg\:cu-pl-1 { - padding-left: var(--canon-space-1); - } - - .lg\:cu-pl-1\.5 { - padding-left: var(--canon-space-1_5); - } - - .lg\:cu-pl-2 { - padding-left: var(--canon-space-2); - } - - .lg\:cu-pl-3 { - padding-left: var(--canon-space-3); - } - - .lg\:cu-pl-4 { - padding-left: var(--canon-space-4); - } - - .lg\:cu-pl-5 { - padding-left: var(--canon-space-5); - } - - .lg\:cu-pl-6 { - padding-left: var(--canon-space-6); - } - - .lg\:cu-pl-7 { - padding-left: var(--canon-space-7); - } - - .lg\:cu-pl-8 { - padding-left: var(--canon-space-8); - } - - .lg\:cu-pl-9 { - padding-left: var(--canon-space-9); - } - - .lg\:cu-pl-10 { - padding-left: var(--canon-space-10); - } - - .lg\:cu-pl-11 { - padding-left: var(--canon-space-11); - } - - .lg\:cu-pl-12 { - padding-left: var(--canon-space-12); - } - - .lg\:cu-pl-13 { - padding-left: var(--canon-space-13); - } - - .lg\:cu-pl-14 { - padding-left: var(--canon-space-14); - } -} - -@media (width >= 1536px) { - .xl\:cu-pl { - padding-left: var(--pl-xl); - } - - .xl\:cu-pl-0\.5 { - padding-left: var(--canon-space-0_5); - } - - .xl\:cu-pl-1 { - padding-left: var(--canon-space-1); - } - - .xl\:cu-pl-1\.5 { - padding-left: var(--canon-space-1_5); - } - - .xl\:cu-pl-2 { - padding-left: var(--canon-space-2); - } - - .xl\:cu-pl-3 { - padding-left: var(--canon-space-3); - } - - .xl\:cu-pl-4 { - padding-left: var(--canon-space-4); - } - - .xl\:cu-pl-5 { - padding-left: var(--canon-space-5); - } - - .xl\:cu-pl-6 { - padding-left: var(--canon-space-6); - } - - .xl\:cu-pl-7 { - padding-left: var(--canon-space-7); - } - - .xl\:cu-pl-8 { - padding-left: var(--canon-space-8); - } - - .xl\:cu-pl-9 { - padding-left: var(--canon-space-9); - } - - .xl\:cu-pl-10 { - padding-left: var(--canon-space-10); - } - - .xl\:cu-pl-11 { - padding-left: var(--canon-space-11); - } - - .xl\:cu-pl-12 { - padding-left: var(--canon-space-12); - } - - .xl\:cu-pl-13 { - padding-left: var(--canon-space-13); - } - - .xl\:cu-pl-14 { - padding-left: var(--canon-space-14); - } -} - -.cu-pr { - padding-right: var(--pr); -} - -.cu-pr-0\.5 { - padding-right: var(--canon-space-0_5); -} - -.cu-pr-1 { - padding-right: var(--canon-space-1); -} - -.cu-pr-1\.5 { - padding-right: var(--canon-space-1_5); -} - -.cu-pr-2 { - padding-right: var(--canon-space-2); -} - -.cu-pr-3 { - padding-right: var(--canon-space-3); -} - -.cu-pr-4 { - padding-right: var(--canon-space-4); -} - -.cu-pr-5 { - padding-right: var(--canon-space-5); -} - -.cu-pr-6 { - padding-right: var(--canon-space-6); -} - -.cu-pr-7 { - padding-right: var(--canon-space-7); -} - -.cu-pr-8 { - padding-right: var(--canon-space-8); -} - -.cu-pr-9 { - padding-right: var(--canon-space-9); -} - -.cu-pr-10 { - padding-right: var(--canon-space-10); -} - -.cu-pr-11 { - padding-right: var(--canon-space-11); -} - -.cu-pr-12 { - padding-right: var(--canon-space-12); -} - -.cu-pr-13 { - padding-right: var(--canon-space-13); -} - -.cu-pr-14 { - padding-right: var(--canon-space-14); -} - -@media (width >= 640px) { - .xs\:cu-pr { - padding-right: var(--pr-xs); - } - - .xs\:cu-pr-0\.5 { - padding-right: var(--canon-space-0_5); - } - - .xs\:cu-pr-1 { - padding-right: var(--canon-space-1); - } - - .xs\:cu-pr-1\.5 { - padding-right: var(--canon-space-1_5); - } - - .xs\:cu-pr-2 { - padding-right: var(--canon-space-2); - } - - .xs\:cu-pr-3 { - padding-right: var(--canon-space-3); - } - - .xs\:cu-pr-4 { - padding-right: var(--canon-space-4); - } - - .xs\:cu-pr-5 { - padding-right: var(--canon-space-5); - } - - .xs\:cu-pr-6 { - padding-right: var(--canon-space-6); - } - - .xs\:cu-pr-7 { - padding-right: var(--canon-space-7); - } - - .xs\:cu-pr-8 { - padding-right: var(--canon-space-8); - } - - .xs\:cu-pr-9 { - padding-right: var(--canon-space-9); - } - - .xs\:cu-pr-10 { - padding-right: var(--canon-space-10); - } - - .xs\:cu-pr-11 { - padding-right: var(--canon-space-11); - } - - .xs\:cu-pr-12 { - padding-right: var(--canon-space-12); - } - - .xs\:cu-pr-13 { - padding-right: var(--canon-space-13); - } - - .xs\:cu-pr-14 { - padding-right: var(--canon-space-14); - } -} - -@media (width >= 768px) { - .sm\:cu-pr { - padding-right: var(--pr-sm); - } - - .sm\:cu-pr-0\.5 { - padding-right: var(--canon-space-0_5); - } - - .sm\:cu-pr-1 { - padding-right: var(--canon-space-1); - } - - .sm\:cu-pr-1\.5 { - padding-right: var(--canon-space-1_5); - } - - .sm\:cu-pr-2 { - padding-right: var(--canon-space-2); - } - - .sm\:cu-pr-3 { - padding-right: var(--canon-space-3); - } - - .sm\:cu-pr-4 { - padding-right: var(--canon-space-4); - } - - .sm\:cu-pr-5 { - padding-right: var(--canon-space-5); - } - - .sm\:cu-pr-6 { - padding-right: var(--canon-space-6); - } - - .sm\:cu-pr-7 { - padding-right: var(--canon-space-7); - } - - .sm\:cu-pr-8 { - padding-right: var(--canon-space-8); - } - - .sm\:cu-pr-9 { - padding-right: var(--canon-space-9); - } - - .sm\:cu-pr-10 { - padding-right: var(--canon-space-10); - } - - .sm\:cu-pr-11 { - padding-right: var(--canon-space-11); - } - - .sm\:cu-pr-12 { - padding-right: var(--canon-space-12); - } - - .sm\:cu-pr-13 { - padding-right: var(--canon-space-13); - } - - .sm\:cu-pr-14 { - padding-right: var(--canon-space-14); - } -} - -@media (width >= 1024px) { - .md\:cu-pr { - padding-right: var(--pr-md); - } - - .md\:cu-pr-0\.5 { - padding-right: var(--canon-space-0_5); - } - - .md\:cu-pr-1 { - padding-right: var(--canon-space-1); - } - - .md\:cu-pr-1\.5 { - padding-right: var(--canon-space-1_5); - } - - .md\:cu-pr-2 { - padding-right: var(--canon-space-2); - } - - .md\:cu-pr-3 { - padding-right: var(--canon-space-3); - } - - .md\:cu-pr-4 { - padding-right: var(--canon-space-4); - } - - .md\:cu-pr-5 { - padding-right: var(--canon-space-5); - } - - .md\:cu-pr-6 { - padding-right: var(--canon-space-6); - } - - .md\:cu-pr-7 { - padding-right: var(--canon-space-7); - } - - .md\:cu-pr-8 { - padding-right: var(--canon-space-8); - } - - .md\:cu-pr-9 { - padding-right: var(--canon-space-9); - } - - .md\:cu-pr-10 { - padding-right: var(--canon-space-10); - } - - .md\:cu-pr-11 { - padding-right: var(--canon-space-11); - } - - .md\:cu-pr-12 { - padding-right: var(--canon-space-12); - } - - .md\:cu-pr-13 { - padding-right: var(--canon-space-13); - } - - .md\:cu-pr-14 { - padding-right: var(--canon-space-14); - } -} - -@media (width >= 1280px) { - .lg\:cu-pr { - padding-right: var(--pr-lg); - } - - .lg\:cu-pr-0\.5 { - padding-right: var(--canon-space-0_5); - } - - .lg\:cu-pr-1 { - padding-right: var(--canon-space-1); - } - - .lg\:cu-pr-1\.5 { - padding-right: var(--canon-space-1_5); - } - - .lg\:cu-pr-2 { - padding-right: var(--canon-space-2); - } - - .lg\:cu-pr-3 { - padding-right: var(--canon-space-3); - } - - .lg\:cu-pr-4 { - padding-right: var(--canon-space-4); - } - - .lg\:cu-pr-5 { - padding-right: var(--canon-space-5); - } - - .lg\:cu-pr-6 { - padding-right: var(--canon-space-6); - } - - .lg\:cu-pr-7 { - padding-right: var(--canon-space-7); - } - - .lg\:cu-pr-8 { - padding-right: var(--canon-space-8); - } - - .lg\:cu-pr-9 { - padding-right: var(--canon-space-9); - } - - .lg\:cu-pr-10 { - padding-right: var(--canon-space-10); - } - - .lg\:cu-pr-11 { - padding-right: var(--canon-space-11); - } - - .lg\:cu-pr-12 { - padding-right: var(--canon-space-12); - } - - .lg\:cu-pr-13 { - padding-right: var(--canon-space-13); - } - - .lg\:cu-pr-14 { - padding-right: var(--canon-space-14); - } -} - -@media (width >= 1536px) { - .xl\:cu-pr { - padding-right: var(--pr-xl); - } - - .xl\:cu-pr-0\.5 { - padding-right: var(--canon-space-0_5); - } - - .xl\:cu-pr-1 { - padding-right: var(--canon-space-1); - } - - .xl\:cu-pr-1\.5 { - padding-right: var(--canon-space-1_5); - } - - .xl\:cu-pr-2 { - padding-right: var(--canon-space-2); - } - - .xl\:cu-pr-3 { - padding-right: var(--canon-space-3); - } - - .xl\:cu-pr-4 { - padding-right: var(--canon-space-4); - } - - .xl\:cu-pr-5 { - padding-right: var(--canon-space-5); - } - - .xl\:cu-pr-6 { - padding-right: var(--canon-space-6); - } - - .xl\:cu-pr-7 { - padding-right: var(--canon-space-7); - } - - .xl\:cu-pr-8 { - padding-right: var(--canon-space-8); - } - - .xl\:cu-pr-9 { - padding-right: var(--canon-space-9); - } - - .xl\:cu-pr-10 { - padding-right: var(--canon-space-10); - } - - .xl\:cu-pr-11 { - padding-right: var(--canon-space-11); - } - - .xl\:cu-pr-12 { - padding-right: var(--canon-space-12); - } - - .xl\:cu-pr-13 { - padding-right: var(--canon-space-13); - } - - .xl\:cu-pr-14 { - padding-right: var(--canon-space-14); - } -} - -.cu-pt { - padding-top: var(--pt); -} - -.cu-pt-0\.5 { - padding-top: var(--canon-space-0_5); -} - -.cu-pt-1 { - padding-top: var(--canon-space-1); -} - -.cu-pt-1\.5 { - padding-top: var(--canon-space-1_5); -} - -.cu-pt-2 { - padding-top: var(--canon-space-2); -} - -.cu-pt-3 { - padding-top: var(--canon-space-3); -} - -.cu-pt-4 { - padding-top: var(--canon-space-4); -} - -.cu-pt-5 { - padding-top: var(--canon-space-5); -} - -.cu-pt-6 { - padding-top: var(--canon-space-6); -} - -.cu-pt-7 { - padding-top: var(--canon-space-7); -} - -.cu-pt-8 { - padding-top: var(--canon-space-8); -} - -.cu-pt-9 { - padding-top: var(--canon-space-9); -} - -.cu-pt-10 { - padding-top: var(--canon-space-10); -} - -.cu-pt-11 { - padding-top: var(--canon-space-11); -} - -.cu-pt-12 { - padding-top: var(--canon-space-12); -} - -.cu-pt-13 { - padding-top: var(--canon-space-13); -} - -.cu-pt-14 { - padding-top: var(--canon-space-14); -} - -@media (width >= 640px) { - .xs\:cu-pt { - padding-top: var(--pt-xs); - } - - .xs\:cu-pt-0\.5 { - padding-top: var(--canon-space-0_5); - } - - .xs\:cu-pt-1 { - padding-top: var(--canon-space-1); - } - - .xs\:cu-pt-1\.5 { - padding-top: var(--canon-space-1_5); - } - - .xs\:cu-pt-2 { - padding-top: var(--canon-space-2); - } - - .xs\:cu-pt-3 { - padding-top: var(--canon-space-3); - } - - .xs\:cu-pt-4 { - padding-top: var(--canon-space-4); - } - - .xs\:cu-pt-5 { - padding-top: var(--canon-space-5); - } - - .xs\:cu-pt-6 { - padding-top: var(--canon-space-6); - } - - .xs\:cu-pt-7 { - padding-top: var(--canon-space-7); - } - - .xs\:cu-pt-8 { - padding-top: var(--canon-space-8); - } - - .xs\:cu-pt-9 { - padding-top: var(--canon-space-9); - } - - .xs\:cu-pt-10 { - padding-top: var(--canon-space-10); - } - - .xs\:cu-pt-11 { - padding-top: var(--canon-space-11); - } - - .xs\:cu-pt-12 { - padding-top: var(--canon-space-12); - } - - .xs\:cu-pt-13 { - padding-top: var(--canon-space-13); - } - - .xs\:cu-pt-14 { - padding-top: var(--canon-space-14); - } -} - -@media (width >= 768px) { - .sm\:cu-pt { - padding-top: var(--pt-sm); - } - - .sm\:cu-pt-0\.5 { - padding-top: var(--canon-space-0_5); - } - - .sm\:cu-pt-1 { - padding-top: var(--canon-space-1); - } - - .sm\:cu-pt-1\.5 { - padding-top: var(--canon-space-1_5); - } - - .sm\:cu-pt-2 { - padding-top: var(--canon-space-2); - } - - .sm\:cu-pt-3 { - padding-top: var(--canon-space-3); - } - - .sm\:cu-pt-4 { - padding-top: var(--canon-space-4); - } - - .sm\:cu-pt-5 { - padding-top: var(--canon-space-5); - } - - .sm\:cu-pt-6 { - padding-top: var(--canon-space-6); - } - - .sm\:cu-pt-7 { - padding-top: var(--canon-space-7); - } - - .sm\:cu-pt-8 { - padding-top: var(--canon-space-8); - } - - .sm\:cu-pt-9 { - padding-top: var(--canon-space-9); - } - - .sm\:cu-pt-10 { - padding-top: var(--canon-space-10); - } - - .sm\:cu-pt-11 { - padding-top: var(--canon-space-11); - } - - .sm\:cu-pt-12 { - padding-top: var(--canon-space-12); - } - - .sm\:cu-pt-13 { - padding-top: var(--canon-space-13); - } - - .sm\:cu-pt-14 { - padding-top: var(--canon-space-14); - } -} - -@media (width >= 1024px) { - .md\:cu-pt { - padding-top: var(--pt-md); - } - - .md\:cu-pt-0\.5 { - padding-top: var(--canon-space-0_5); - } - - .md\:cu-pt-1 { - padding-top: var(--canon-space-1); - } - - .md\:cu-pt-1\.5 { - padding-top: var(--canon-space-1_5); - } - - .md\:cu-pt-2 { - padding-top: var(--canon-space-2); - } - - .md\:cu-pt-3 { - padding-top: var(--canon-space-3); - } - - .md\:cu-pt-4 { - padding-top: var(--canon-space-4); - } - - .md\:cu-pt-5 { - padding-top: var(--canon-space-5); - } - - .md\:cu-pt-6 { - padding-top: var(--canon-space-6); - } - - .md\:cu-pt-7 { - padding-top: var(--canon-space-7); - } - - .md\:cu-pt-8 { - padding-top: var(--canon-space-8); - } - - .md\:cu-pt-9 { - padding-top: var(--canon-space-9); - } - - .md\:cu-pt-10 { - padding-top: var(--canon-space-10); - } - - .md\:cu-pt-11 { - padding-top: var(--canon-space-11); - } - - .md\:cu-pt-12 { - padding-top: var(--canon-space-12); - } - - .md\:cu-pt-13 { - padding-top: var(--canon-space-13); - } - - .md\:cu-pt-14 { - padding-top: var(--canon-space-14); - } -} - -@media (width >= 1280px) { - .lg\:cu-pt { - padding-top: var(--pt-lg); - } - - .lg\:cu-pt-0\.5 { - padding-top: var(--canon-space-0_5); - } - - .lg\:cu-pt-1 { - padding-top: var(--canon-space-1); - } - - .lg\:cu-pt-1\.5 { - padding-top: var(--canon-space-1_5); - } - - .lg\:cu-pt-2 { - padding-top: var(--canon-space-2); - } - - .lg\:cu-pt-3 { - padding-top: var(--canon-space-3); - } - - .lg\:cu-pt-4 { - padding-top: var(--canon-space-4); - } - - .lg\:cu-pt-5 { - padding-top: var(--canon-space-5); - } - - .lg\:cu-pt-6 { - padding-top: var(--canon-space-6); - } - - .lg\:cu-pt-7 { - padding-top: var(--canon-space-7); - } - - .lg\:cu-pt-8 { - padding-top: var(--canon-space-8); - } - - .lg\:cu-pt-9 { - padding-top: var(--canon-space-9); - } - - .lg\:cu-pt-10 { - padding-top: var(--canon-space-10); - } - - .lg\:cu-pt-11 { - padding-top: var(--canon-space-11); - } - - .lg\:cu-pt-12 { - padding-top: var(--canon-space-12); - } - - .lg\:cu-pt-13 { - padding-top: var(--canon-space-13); - } - - .lg\:cu-pt-14 { - padding-top: var(--canon-space-14); - } -} - -@media (width >= 1536px) { - .xl\:cu-pt { - padding-top: var(--pt-xl); - } - - .xl\:cu-pt-0\.5 { - padding-top: var(--canon-space-0_5); - } - - .xl\:cu-pt-1 { - padding-top: var(--canon-space-1); - } - - .xl\:cu-pt-1\.5 { - padding-top: var(--canon-space-1_5); - } - - .xl\:cu-pt-2 { - padding-top: var(--canon-space-2); - } - - .xl\:cu-pt-3 { - padding-top: var(--canon-space-3); - } - - .xl\:cu-pt-4 { - padding-top: var(--canon-space-4); - } - - .xl\:cu-pt-5 { - padding-top: var(--canon-space-5); - } - - .xl\:cu-pt-6 { - padding-top: var(--canon-space-6); - } - - .xl\:cu-pt-7 { - padding-top: var(--canon-space-7); - } - - .xl\:cu-pt-8 { - padding-top: var(--canon-space-8); - } - - .xl\:cu-pt-9 { - padding-top: var(--canon-space-9); - } - - .xl\:cu-pt-10 { - padding-top: var(--canon-space-10); - } - - .xl\:cu-pt-11 { - padding-top: var(--canon-space-11); - } - - .xl\:cu-pt-12 { - padding-top: var(--canon-space-12); - } - - .xl\:cu-pt-13 { - padding-top: var(--canon-space-13); - } - - .xl\:cu-pt-14 { - padding-top: var(--canon-space-14); - } -} - -.cu-pb { - padding-bottom: var(--pb); -} - -.cu-pb-0\.5 { - padding-bottom: var(--canon-space-0_5); -} - -.cu-pb-1 { - padding-bottom: var(--canon-space-1); -} - -.cu-pb-1\.5 { - padding-bottom: var(--canon-space-1_5); -} - -.cu-pb-2 { - padding-bottom: var(--canon-space-2); -} - -.cu-pb-3 { - padding-bottom: var(--canon-space-3); -} - -.cu-pb-4 { - padding-bottom: var(--canon-space-4); -} - -.cu-pb-5 { - padding-bottom: var(--canon-space-5); -} - -.cu-pb-6 { - padding-bottom: var(--canon-space-6); -} - -.cu-pb-7 { - padding-bottom: var(--canon-space-7); -} - -.cu-pb-8 { - padding-bottom: var(--canon-space-8); -} - -.cu-pb-9 { - padding-bottom: var(--canon-space-9); -} - -.cu-pb-10 { - padding-bottom: var(--canon-space-10); -} - -.cu-pb-11 { - padding-bottom: var(--canon-space-11); -} - -.cu-pb-12 { - padding-bottom: var(--canon-space-12); -} - -.cu-pb-13 { - padding-bottom: var(--canon-space-13); -} - -.cu-pb-14 { - padding-bottom: var(--canon-space-14); -} - -@media (width >= 640px) { - .xs\:cu-pb { - padding-bottom: var(--pb-xs); - } - - .xs\:cu-pb-0\.5 { - padding-bottom: var(--canon-space-0_5); - } - - .xs\:cu-pb-1 { - padding-bottom: var(--canon-space-1); - } - - .xs\:cu-pb-1\.5 { - padding-bottom: var(--canon-space-1_5); - } - - .xs\:cu-pb-2 { - padding-bottom: var(--canon-space-2); - } - - .xs\:cu-pb-3 { - padding-bottom: var(--canon-space-3); - } - - .xs\:cu-pb-4 { - padding-bottom: var(--canon-space-4); - } - - .xs\:cu-pb-5 { - padding-bottom: var(--canon-space-5); - } - - .xs\:cu-pb-6 { - padding-bottom: var(--canon-space-6); - } - - .xs\:cu-pb-7 { - padding-bottom: var(--canon-space-7); - } - - .xs\:cu-pb-8 { - padding-bottom: var(--canon-space-8); - } - - .xs\:cu-pb-9 { - padding-bottom: var(--canon-space-9); - } - - .xs\:cu-pb-10 { - padding-bottom: var(--canon-space-10); - } - - .xs\:cu-pb-11 { - padding-bottom: var(--canon-space-11); - } - - .xs\:cu-pb-12 { - padding-bottom: var(--canon-space-12); - } - - .xs\:cu-pb-13 { - padding-bottom: var(--canon-space-13); - } - - .xs\:cu-pb-14 { - padding-bottom: var(--canon-space-14); - } -} - -@media (width >= 768px) { - .sm\:cu-pb { - padding-bottom: var(--pb-sm); - } - - .sm\:cu-pb-0\.5 { - padding-bottom: var(--canon-space-0_5); - } - - .sm\:cu-pb-1 { - padding-bottom: var(--canon-space-1); - } - - .sm\:cu-pb-1\.5 { - padding-bottom: var(--canon-space-1_5); - } - - .sm\:cu-pb-2 { - padding-bottom: var(--canon-space-2); - } - - .sm\:cu-pb-3 { - padding-bottom: var(--canon-space-3); - } - - .sm\:cu-pb-4 { - padding-bottom: var(--canon-space-4); - } - - .sm\:cu-pb-5 { - padding-bottom: var(--canon-space-5); - } - - .sm\:cu-pb-6 { - padding-bottom: var(--canon-space-6); - } - - .sm\:cu-pb-7 { - padding-bottom: var(--canon-space-7); - } - - .sm\:cu-pb-8 { - padding-bottom: var(--canon-space-8); - } - - .sm\:cu-pb-9 { - padding-bottom: var(--canon-space-9); - } - - .sm\:cu-pb-10 { - padding-bottom: var(--canon-space-10); - } - - .sm\:cu-pb-11 { - padding-bottom: var(--canon-space-11); - } - - .sm\:cu-pb-12 { - padding-bottom: var(--canon-space-12); - } - - .sm\:cu-pb-13 { - padding-bottom: var(--canon-space-13); - } - - .sm\:cu-pb-14 { - padding-bottom: var(--canon-space-14); - } -} - -@media (width >= 1024px) { - .md\:cu-pb { - padding-bottom: var(--pb-md); - } - - .md\:cu-pb-0\.5 { - padding-bottom: var(--canon-space-0_5); - } - - .md\:cu-pb-1 { - padding-bottom: var(--canon-space-1); - } - - .md\:cu-pb-1\.5 { - padding-bottom: var(--canon-space-1_5); - } - - .md\:cu-pb-2 { - padding-bottom: var(--canon-space-2); - } - - .md\:cu-pb-3 { - padding-bottom: var(--canon-space-3); - } - - .md\:cu-pb-4 { - padding-bottom: var(--canon-space-4); - } - - .md\:cu-pb-5 { - padding-bottom: var(--canon-space-5); - } - - .md\:cu-pb-6 { - padding-bottom: var(--canon-space-6); - } - - .md\:cu-pb-7 { - padding-bottom: var(--canon-space-7); - } - - .md\:cu-pb-8 { - padding-bottom: var(--canon-space-8); - } - - .md\:cu-pb-9 { - padding-bottom: var(--canon-space-9); - } - - .md\:cu-pb-10 { - padding-bottom: var(--canon-space-10); - } - - .md\:cu-pb-11 { - padding-bottom: var(--canon-space-11); - } - - .md\:cu-pb-12 { - padding-bottom: var(--canon-space-12); - } - - .md\:cu-pb-13 { - padding-bottom: var(--canon-space-13); - } - - .md\:cu-pb-14 { - padding-bottom: var(--canon-space-14); - } -} - -@media (width >= 1280px) { - .lg\:cu-pb { - padding-bottom: var(--pb-lg); - } - - .lg\:cu-pb-0\.5 { - padding-bottom: var(--canon-space-0_5); - } - - .lg\:cu-pb-1 { - padding-bottom: var(--canon-space-1); - } - - .lg\:cu-pb-1\.5 { - padding-bottom: var(--canon-space-1_5); - } - - .lg\:cu-pb-2 { - padding-bottom: var(--canon-space-2); - } - - .lg\:cu-pb-3 { - padding-bottom: var(--canon-space-3); - } - - .lg\:cu-pb-4 { - padding-bottom: var(--canon-space-4); - } - - .lg\:cu-pb-5 { - padding-bottom: var(--canon-space-5); - } - - .lg\:cu-pb-6 { - padding-bottom: var(--canon-space-6); - } - - .lg\:cu-pb-7 { - padding-bottom: var(--canon-space-7); - } - - .lg\:cu-pb-8 { - padding-bottom: var(--canon-space-8); - } - - .lg\:cu-pb-9 { - padding-bottom: var(--canon-space-9); - } - - .lg\:cu-pb-10 { - padding-bottom: var(--canon-space-10); - } - - .lg\:cu-pb-11 { - padding-bottom: var(--canon-space-11); - } - - .lg\:cu-pb-12 { - padding-bottom: var(--canon-space-12); - } - - .lg\:cu-pb-13 { - padding-bottom: var(--canon-space-13); - } - - .lg\:cu-pb-14 { - padding-bottom: var(--canon-space-14); - } -} - -@media (width >= 1536px) { - .xl\:cu-pb { - padding-bottom: var(--pb-xl); - } - - .xl\:cu-pb-0\.5 { - padding-bottom: var(--canon-space-0_5); - } - - .xl\:cu-pb-1 { - padding-bottom: var(--canon-space-1); - } - - .xl\:cu-pb-1\.5 { - padding-bottom: var(--canon-space-1_5); - } - - .xl\:cu-pb-2 { - padding-bottom: var(--canon-space-2); - } - - .xl\:cu-pb-3 { - padding-bottom: var(--canon-space-3); - } - - .xl\:cu-pb-4 { - padding-bottom: var(--canon-space-4); - } - - .xl\:cu-pb-5 { - padding-bottom: var(--canon-space-5); - } - - .xl\:cu-pb-6 { - padding-bottom: var(--canon-space-6); - } - - .xl\:cu-pb-7 { - padding-bottom: var(--canon-space-7); - } - - .xl\:cu-pb-8 { - padding-bottom: var(--canon-space-8); - } - - .xl\:cu-pb-9 { - padding-bottom: var(--canon-space-9); - } - - .xl\:cu-pb-10 { - padding-bottom: var(--canon-space-10); - } - - .xl\:cu-pb-11 { - padding-bottom: var(--canon-space-11); - } - - .xl\:cu-pb-12 { - padding-bottom: var(--canon-space-12); - } - - .xl\:cu-pb-13 { - padding-bottom: var(--canon-space-13); - } - - .xl\:cu-pb-14 { - padding-bottom: var(--canon-space-14); - } -} - -.cu-py { - padding-top: var(--py); - padding-bottom: var(--py); -} - -.cu-py-0\.5 { - padding-top: var(--canon-space-0_5); - padding-bottom: var(--canon-space-0_5); -} - -.cu-py-1 { - padding-top: var(--canon-space-1); - padding-bottom: var(--canon-space-1); -} - -.cu-py-1\.5 { - padding-top: var(--canon-space-1_5); - padding-bottom: var(--canon-space-1_5); -} - -.cu-py-2 { - padding-top: var(--canon-space-2); - padding-bottom: var(--canon-space-2); -} - -.cu-py-3 { - padding-top: var(--canon-space-3); - padding-bottom: var(--canon-space-3); -} - -.cu-py-4 { - padding-top: var(--canon-space-4); - padding-bottom: var(--canon-space-4); -} - -.cu-py-5 { - padding-top: var(--canon-space-5); - padding-bottom: var(--canon-space-5); -} - -.cu-py-6 { - padding-top: var(--canon-space-6); - padding-bottom: var(--canon-space-6); -} - -.cu-py-7 { - padding-top: var(--canon-space-7); - padding-bottom: var(--canon-space-7); -} - -.cu-py-8 { - padding-top: var(--canon-space-8); - padding-bottom: var(--canon-space-8); -} - -.cu-py-9 { - padding-top: var(--canon-space-9); - padding-bottom: var(--canon-space-9); -} - -.cu-py-10 { - padding-top: var(--canon-space-10); - padding-bottom: var(--canon-space-10); -} - -.cu-py-11 { - padding-top: var(--canon-space-11); - padding-bottom: var(--canon-space-11); -} - -.cu-py-12 { - padding-top: var(--canon-space-12); - padding-bottom: var(--canon-space-12); -} - -.cu-py-13 { - padding-top: var(--canon-space-13); - padding-bottom: var(--canon-space-13); -} - -.cu-py-14 { - padding-top: var(--canon-space-14); - padding-bottom: var(--canon-space-14); -} - -@media (width >= 640px) { - .xs\:cu-py { - padding-top: var(--py-xs); - padding-bottom: var(--py-xs); - } - - .xs\:cu-py-0\.5 { - padding-top: var(--canon-space-0_5); - padding-bottom: var(--canon-space-0_5); - } - - .xs\:cu-py-1 { - padding-top: var(--canon-space-1); - padding-bottom: var(--canon-space-1); - } - - .xs\:cu-py-1\.5 { - padding-top: var(--canon-space-1_5); - padding-bottom: var(--canon-space-1_5); - } - - .xs\:cu-py-2 { - padding-top: var(--canon-space-2); - padding-bottom: var(--canon-space-2); - } - - .xs\:cu-py-3 { - padding-top: var(--canon-space-3); - padding-bottom: var(--canon-space-3); - } - - .xs\:cu-py-4 { - padding-top: var(--canon-space-4); - padding-bottom: var(--canon-space-4); - } - - .xs\:cu-py-5 { - padding-top: var(--canon-space-5); - padding-bottom: var(--canon-space-5); - } - - .xs\:cu-py-6 { - padding-top: var(--canon-space-6); - padding-bottom: var(--canon-space-6); - } - - .xs\:cu-py-7 { - padding-top: var(--canon-space-7); - padding-bottom: var(--canon-space-7); - } - - .xs\:cu-py-8 { - padding-top: var(--canon-space-8); - padding-bottom: var(--canon-space-8); - } - - .xs\:cu-py-9 { - padding-top: var(--canon-space-9); - padding-bottom: var(--canon-space-9); - } - - .xs\:cu-py-10 { - padding-top: var(--canon-space-10); - padding-bottom: var(--canon-space-10); - } - - .xs\:cu-py-11 { - padding-top: var(--canon-space-11); - padding-bottom: var(--canon-space-11); - } - - .xs\:cu-py-12 { - padding-top: var(--canon-space-12); - padding-bottom: var(--canon-space-12); - } - - .xs\:cu-py-13 { - padding-top: var(--canon-space-13); - padding-bottom: var(--canon-space-13); - } - - .xs\:cu-py-14 { - padding-top: var(--canon-space-14); - padding-bottom: var(--canon-space-14); - } -} - -@media (width >= 768px) { - .sm\:cu-py { - padding-top: var(--py-sm); - padding-bottom: var(--py-sm); - } - - .sm\:cu-py-0\.5 { - padding-top: var(--canon-space-0_5); - padding-bottom: var(--canon-space-0_5); - } - - .sm\:cu-py-1 { - padding-top: var(--canon-space-1); - padding-bottom: var(--canon-space-1); - } - - .sm\:cu-py-1\.5 { - padding-top: var(--canon-space-1_5); - padding-bottom: var(--canon-space-1_5); - } - - .sm\:cu-py-2 { - padding-top: var(--canon-space-2); - padding-bottom: var(--canon-space-2); - } - - .sm\:cu-py-3 { - padding-top: var(--canon-space-3); - padding-bottom: var(--canon-space-3); - } - - .sm\:cu-py-4 { - padding-top: var(--canon-space-4); - padding-bottom: var(--canon-space-4); - } - - .sm\:cu-py-5 { - padding-top: var(--canon-space-5); - padding-bottom: var(--canon-space-5); - } - - .sm\:cu-py-6 { - padding-top: var(--canon-space-6); - padding-bottom: var(--canon-space-6); - } - - .sm\:cu-py-7 { - padding-top: var(--canon-space-7); - padding-bottom: var(--canon-space-7); - } - - .sm\:cu-py-8 { - padding-top: var(--canon-space-8); - padding-bottom: var(--canon-space-8); - } - - .sm\:cu-py-9 { - padding-top: var(--canon-space-9); - padding-bottom: var(--canon-space-9); - } - - .sm\:cu-py-10 { - padding-top: var(--canon-space-10); - padding-bottom: var(--canon-space-10); - } - - .sm\:cu-py-11 { - padding-top: var(--canon-space-11); - padding-bottom: var(--canon-space-11); - } - - .sm\:cu-py-12 { - padding-top: var(--canon-space-12); - padding-bottom: var(--canon-space-12); - } - - .sm\:cu-py-13 { - padding-top: var(--canon-space-13); - padding-bottom: var(--canon-space-13); - } - - .sm\:cu-py-14 { - padding-top: var(--canon-space-14); - padding-bottom: var(--canon-space-14); - } -} - -@media (width >= 1024px) { - .md\:cu-py { - padding-top: var(--py-md); - padding-bottom: var(--py-md); - } - - .md\:cu-py-0\.5 { - padding-top: var(--canon-space-0_5); - padding-bottom: var(--canon-space-0_5); - } - - .md\:cu-py-1 { - padding-top: var(--canon-space-1); - padding-bottom: var(--canon-space-1); - } - - .md\:cu-py-1\.5 { - padding-top: var(--canon-space-1_5); - padding-bottom: var(--canon-space-1_5); - } - - .md\:cu-py-2 { - padding-top: var(--canon-space-2); - padding-bottom: var(--canon-space-2); - } - - .md\:cu-py-3 { - padding-top: var(--canon-space-3); - padding-bottom: var(--canon-space-3); - } - - .md\:cu-py-4 { - padding-top: var(--canon-space-4); - padding-bottom: var(--canon-space-4); - } - - .md\:cu-py-5 { - padding-top: var(--canon-space-5); - padding-bottom: var(--canon-space-5); - } - - .md\:cu-py-6 { - padding-top: var(--canon-space-6); - padding-bottom: var(--canon-space-6); - } - - .md\:cu-py-7 { - padding-top: var(--canon-space-7); - padding-bottom: var(--canon-space-7); - } - - .md\:cu-py-8 { - padding-top: var(--canon-space-8); - padding-bottom: var(--canon-space-8); - } - - .md\:cu-py-9 { - padding-top: var(--canon-space-9); - padding-bottom: var(--canon-space-9); - } - - .md\:cu-py-10 { - padding-top: var(--canon-space-10); - padding-bottom: var(--canon-space-10); - } - - .md\:cu-py-11 { - padding-top: var(--canon-space-11); - padding-bottom: var(--canon-space-11); - } - - .md\:cu-py-12 { - padding-top: var(--canon-space-12); - padding-bottom: var(--canon-space-12); - } - - .md\:cu-py-13 { - padding-top: var(--canon-space-13); - padding-bottom: var(--canon-space-13); - } - - .md\:cu-py-14 { - padding-top: var(--canon-space-14); - padding-bottom: var(--canon-space-14); - } -} - -@media (width >= 1280px) { - .lg\:cu-py { - padding-top: var(--py-lg); - padding-bottom: var(--py-lg); - } - - .lg\:cu-py-0\.5 { - padding-top: var(--canon-space-0_5); - padding-bottom: var(--canon-space-0_5); - } - - .lg\:cu-py-1 { - padding-top: var(--canon-space-1); - padding-bottom: var(--canon-space-1); - } - - .lg\:cu-py-1\.5 { - padding-top: var(--canon-space-1_5); - padding-bottom: var(--canon-space-1_5); - } - - .lg\:cu-py-2 { - padding-top: var(--canon-space-2); - padding-bottom: var(--canon-space-2); - } - - .lg\:cu-py-3 { - padding-top: var(--canon-space-3); - padding-bottom: var(--canon-space-3); - } - - .lg\:cu-py-4 { - padding-top: var(--canon-space-4); - padding-bottom: var(--canon-space-4); - } - - .lg\:cu-py-5 { - padding-top: var(--canon-space-5); - padding-bottom: var(--canon-space-5); - } - - .lg\:cu-py-6 { - padding-top: var(--canon-space-6); - padding-bottom: var(--canon-space-6); - } - - .lg\:cu-py-7 { - padding-top: var(--canon-space-7); - padding-bottom: var(--canon-space-7); - } - - .lg\:cu-py-8 { - padding-top: var(--canon-space-8); - padding-bottom: var(--canon-space-8); - } - - .lg\:cu-py-9 { - padding-top: var(--canon-space-9); - padding-bottom: var(--canon-space-9); - } - - .lg\:cu-py-10 { - padding-top: var(--canon-space-10); - padding-bottom: var(--canon-space-10); - } - - .lg\:cu-py-11 { - padding-top: var(--canon-space-11); - padding-bottom: var(--canon-space-11); - } - - .lg\:cu-py-12 { - padding-top: var(--canon-space-12); - padding-bottom: var(--canon-space-12); - } - - .lg\:cu-py-13 { - padding-top: var(--canon-space-13); - padding-bottom: var(--canon-space-13); - } - - .lg\:cu-py-14 { - padding-top: var(--canon-space-14); - padding-bottom: var(--canon-space-14); - } -} - -@media (width >= 1536px) { - .xl\:cu-py { - padding-top: var(--py-xl); - padding-bottom: var(--py-xl); - } - - .xl\:cu-py-0\.5 { - padding-top: var(--canon-space-0_5); - padding-bottom: var(--canon-space-0_5); - } - - .xl\:cu-py-1 { - padding-top: var(--canon-space-1); - padding-bottom: var(--canon-space-1); - } - - .xl\:cu-py-1\.5 { - padding-top: var(--canon-space-1_5); - padding-bottom: var(--canon-space-1_5); - } - - .xl\:cu-py-2 { - padding-top: var(--canon-space-2); - padding-bottom: var(--canon-space-2); - } - - .xl\:cu-py-3 { - padding-top: var(--canon-space-3); - padding-bottom: var(--canon-space-3); - } - - .xl\:cu-py-4 { - padding-top: var(--canon-space-4); - padding-bottom: var(--canon-space-4); - } - - .xl\:cu-py-5 { - padding-top: var(--canon-space-5); - padding-bottom: var(--canon-space-5); - } - - .xl\:cu-py-6 { - padding-top: var(--canon-space-6); - padding-bottom: var(--canon-space-6); - } - - .xl\:cu-py-7 { - padding-top: var(--canon-space-7); - padding-bottom: var(--canon-space-7); - } - - .xl\:cu-py-8 { - padding-top: var(--canon-space-8); - padding-bottom: var(--canon-space-8); - } - - .xl\:cu-py-9 { - padding-top: var(--canon-space-9); - padding-bottom: var(--canon-space-9); - } - - .xl\:cu-py-10 { - padding-top: var(--canon-space-10); - padding-bottom: var(--canon-space-10); - } - - .xl\:cu-py-11 { - padding-top: var(--canon-space-11); - padding-bottom: var(--canon-space-11); - } - - .xl\:cu-py-12 { - padding-top: var(--canon-space-12); - padding-bottom: var(--canon-space-12); - } - - .xl\:cu-py-13 { - padding-top: var(--canon-space-13); - padding-bottom: var(--canon-space-13); - } - - .xl\:cu-py-14 { - padding-top: var(--canon-space-14); - padding-bottom: var(--canon-space-14); - } -} - -.cu-px { - padding-left: var(--px); - padding-right: var(--px); -} - -.cu-px-0\.5 { - padding-left: var(--canon-space-0_5); - padding-right: var(--canon-space-0_5); -} - -.cu-px-1 { - padding-left: var(--canon-space-1); - padding-right: var(--canon-space-1); -} - -.cu-px-1\.5 { - padding-left: var(--canon-space-1_5); - padding-right: var(--canon-space-1_5); -} - -.cu-px-2 { - padding-left: var(--canon-space-2); - padding-right: var(--canon-space-2); -} - -.cu-px-3 { - padding-left: var(--canon-space-3); - padding-right: var(--canon-space-3); -} - -.cu-px-4 { - padding-left: var(--canon-space-4); - padding-right: var(--canon-space-4); -} - -.cu-px-5 { - padding-left: var(--canon-space-5); - padding-right: var(--canon-space-5); -} - -.cu-px-6 { - padding-left: var(--canon-space-6); - padding-right: var(--canon-space-6); -} - -.cu-px-7 { - padding-left: var(--canon-space-7); - padding-right: var(--canon-space-7); -} - -.cu-px-8 { - padding-left: var(--canon-space-8); - padding-right: var(--canon-space-8); -} - -.cu-px-9 { - padding-left: var(--canon-space-9); - padding-right: var(--canon-space-9); -} - -.cu-px-10 { - padding-left: var(--canon-space-10); - padding-right: var(--canon-space-10); -} - -.cu-px-11 { - padding-left: var(--canon-space-11); - padding-right: var(--canon-space-11); -} - -.cu-px-12 { - padding-left: var(--canon-space-12); - padding-right: var(--canon-space-12); -} - -.cu-px-13 { - padding-left: var(--canon-space-13); - padding-right: var(--canon-space-13); -} - -.cu-px-14 { - padding-left: var(--canon-space-14); - padding-right: var(--canon-space-14); -} - -@media (width >= 640px) { - .xs\:cu-px { - padding-left: var(--px-xs); - padding-right: var(--px-xs); - } - - .xs\:cu-px-0\.5 { - padding-left: var(--canon-space-0_5); - padding-right: var(--canon-space-0_5); - } - - .xs\:cu-px-1 { - padding-left: var(--canon-space-1); - padding-right: var(--canon-space-1); - } - - .xs\:cu-px-1\.5 { - padding-left: var(--canon-space-1_5); - padding-right: var(--canon-space-1_5); - } - - .xs\:cu-px-2 { - padding-left: var(--canon-space-2); - padding-right: var(--canon-space-2); - } - - .xs\:cu-px-3 { - padding-left: var(--canon-space-3); - padding-right: var(--canon-space-3); - } - - .xs\:cu-px-4 { - padding-left: var(--canon-space-4); - padding-right: var(--canon-space-4); - } - - .xs\:cu-px-5 { - padding-left: var(--canon-space-5); - padding-right: var(--canon-space-5); - } - - .xs\:cu-px-6 { - padding-left: var(--canon-space-6); - padding-right: var(--canon-space-6); - } - - .xs\:cu-px-7 { - padding-left: var(--canon-space-7); - padding-right: var(--canon-space-7); - } - - .xs\:cu-px-8 { - padding-left: var(--canon-space-8); - padding-right: var(--canon-space-8); - } - - .xs\:cu-px-9 { - padding-left: var(--canon-space-9); - padding-right: var(--canon-space-9); - } - - .xs\:cu-px-10 { - padding-left: var(--canon-space-10); - padding-right: var(--canon-space-10); - } - - .xs\:cu-px-11 { - padding-left: var(--canon-space-11); - padding-right: var(--canon-space-11); - } - - .xs\:cu-px-12 { - padding-left: var(--canon-space-12); - padding-right: var(--canon-space-12); - } - - .xs\:cu-px-13 { - padding-left: var(--canon-space-13); - padding-right: var(--canon-space-13); - } - - .xs\:cu-px-14 { - padding-left: var(--canon-space-14); - padding-right: var(--canon-space-14); - } -} - -@media (width >= 768px) { - .sm\:cu-px { - padding-left: var(--px-sm); - padding-right: var(--px-sm); - } - - .sm\:cu-px-0\.5 { - padding-left: var(--canon-space-0_5); - padding-right: var(--canon-space-0_5); - } - - .sm\:cu-px-1 { - padding-left: var(--canon-space-1); - padding-right: var(--canon-space-1); - } - - .sm\:cu-px-1\.5 { - padding-left: var(--canon-space-1_5); - padding-right: var(--canon-space-1_5); - } - - .sm\:cu-px-2 { - padding-left: var(--canon-space-2); - padding-right: var(--canon-space-2); - } - - .sm\:cu-px-3 { - padding-left: var(--canon-space-3); - padding-right: var(--canon-space-3); - } - - .sm\:cu-px-4 { - padding-left: var(--canon-space-4); - padding-right: var(--canon-space-4); - } - - .sm\:cu-px-5 { - padding-left: var(--canon-space-5); - padding-right: var(--canon-space-5); - } - - .sm\:cu-px-6 { - padding-left: var(--canon-space-6); - padding-right: var(--canon-space-6); - } - - .sm\:cu-px-7 { - padding-left: var(--canon-space-7); - padding-right: var(--canon-space-7); - } - - .sm\:cu-px-8 { - padding-left: var(--canon-space-8); - padding-right: var(--canon-space-8); - } - - .sm\:cu-px-9 { - padding-left: var(--canon-space-9); - padding-right: var(--canon-space-9); - } - - .sm\:cu-px-10 { - padding-left: var(--canon-space-10); - padding-right: var(--canon-space-10); - } - - .sm\:cu-px-11 { - padding-left: var(--canon-space-11); - padding-right: var(--canon-space-11); - } - - .sm\:cu-px-12 { - padding-left: var(--canon-space-12); - padding-right: var(--canon-space-12); - } - - .sm\:cu-px-13 { - padding-left: var(--canon-space-13); - padding-right: var(--canon-space-13); - } - - .sm\:cu-px-14 { - padding-left: var(--canon-space-14); - padding-right: var(--canon-space-14); - } -} - -@media (width >= 1024px) { - .md\:cu-px { - padding-left: var(--px-md); - padding-right: var(--px-md); - } - - .md\:cu-px-0\.5 { - padding-left: var(--canon-space-0_5); - padding-right: var(--canon-space-0_5); - } - - .md\:cu-px-1 { - padding-left: var(--canon-space-1); - padding-right: var(--canon-space-1); - } - - .md\:cu-px-1\.5 { - padding-left: var(--canon-space-1_5); - padding-right: var(--canon-space-1_5); - } - - .md\:cu-px-2 { - padding-left: var(--canon-space-2); - padding-right: var(--canon-space-2); - } - - .md\:cu-px-3 { - padding-left: var(--canon-space-3); - padding-right: var(--canon-space-3); - } - - .md\:cu-px-4 { - padding-left: var(--canon-space-4); - padding-right: var(--canon-space-4); - } - - .md\:cu-px-5 { - padding-left: var(--canon-space-5); - padding-right: var(--canon-space-5); - } - - .md\:cu-px-6 { - padding-left: var(--canon-space-6); - padding-right: var(--canon-space-6); - } - - .md\:cu-px-7 { - padding-left: var(--canon-space-7); - padding-right: var(--canon-space-7); - } - - .md\:cu-px-8 { - padding-left: var(--canon-space-8); - padding-right: var(--canon-space-8); - } - - .md\:cu-px-9 { - padding-left: var(--canon-space-9); - padding-right: var(--canon-space-9); - } - - .md\:cu-px-10 { - padding-left: var(--canon-space-10); - padding-right: var(--canon-space-10); - } - - .md\:cu-px-11 { - padding-left: var(--canon-space-11); - padding-right: var(--canon-space-11); - } - - .md\:cu-px-12 { - padding-left: var(--canon-space-12); - padding-right: var(--canon-space-12); - } - - .md\:cu-px-13 { - padding-left: var(--canon-space-13); - padding-right: var(--canon-space-13); - } - - .md\:cu-px-14 { - padding-left: var(--canon-space-14); - padding-right: var(--canon-space-14); - } -} - -@media (width >= 1280px) { - .lg\:cu-px { - padding-left: var(--px-lg); - padding-right: var(--px-lg); - } - - .lg\:cu-px-0\.5 { - padding-left: var(--canon-space-0_5); - padding-right: var(--canon-space-0_5); - } - - .lg\:cu-px-1 { - padding-left: var(--canon-space-1); - padding-right: var(--canon-space-1); - } - - .lg\:cu-px-1\.5 { - padding-left: var(--canon-space-1_5); - padding-right: var(--canon-space-1_5); - } - - .lg\:cu-px-2 { - padding-left: var(--canon-space-2); - padding-right: var(--canon-space-2); - } - - .lg\:cu-px-3 { - padding-left: var(--canon-space-3); - padding-right: var(--canon-space-3); - } - - .lg\:cu-px-4 { - padding-left: var(--canon-space-4); - padding-right: var(--canon-space-4); - } - - .lg\:cu-px-5 { - padding-left: var(--canon-space-5); - padding-right: var(--canon-space-5); - } - - .lg\:cu-px-6 { - padding-left: var(--canon-space-6); - padding-right: var(--canon-space-6); - } - - .lg\:cu-px-7 { - padding-left: var(--canon-space-7); - padding-right: var(--canon-space-7); - } - - .lg\:cu-px-8 { - padding-left: var(--canon-space-8); - padding-right: var(--canon-space-8); - } - - .lg\:cu-px-9 { - padding-left: var(--canon-space-9); - padding-right: var(--canon-space-9); - } - - .lg\:cu-px-10 { - padding-left: var(--canon-space-10); - padding-right: var(--canon-space-10); - } - - .lg\:cu-px-11 { - padding-left: var(--canon-space-11); - padding-right: var(--canon-space-11); - } - - .lg\:cu-px-12 { - padding-left: var(--canon-space-12); - padding-right: var(--canon-space-12); - } - - .lg\:cu-px-13 { - padding-left: var(--canon-space-13); - padding-right: var(--canon-space-13); - } - - .lg\:cu-px-14 { - padding-left: var(--canon-space-14); - padding-right: var(--canon-space-14); - } -} - -@media (width >= 1536px) { - .xl\:cu-px { - padding-left: var(--px-xl); - padding-right: var(--px-xl); - } - - .xl\:cu-px-0\.5 { - padding-left: var(--canon-space-0_5); - padding-right: var(--canon-space-0_5); - } - - .xl\:cu-px-1 { - padding-left: var(--canon-space-1); - padding-right: var(--canon-space-1); - } - - .xl\:cu-px-1\.5 { - padding-left: var(--canon-space-1_5); - padding-right: var(--canon-space-1_5); - } - - .xl\:cu-px-2 { - padding-left: var(--canon-space-2); - padding-right: var(--canon-space-2); - } - - .xl\:cu-px-3 { - padding-left: var(--canon-space-3); - padding-right: var(--canon-space-3); - } - - .xl\:cu-px-4 { - padding-left: var(--canon-space-4); - padding-right: var(--canon-space-4); - } - - .xl\:cu-px-5 { - padding-left: var(--canon-space-5); - padding-right: var(--canon-space-5); - } - - .xl\:cu-px-6 { - padding-left: var(--canon-space-6); - padding-right: var(--canon-space-6); - } - - .xl\:cu-px-7 { - padding-left: var(--canon-space-7); - padding-right: var(--canon-space-7); - } - - .xl\:cu-px-8 { - padding-left: var(--canon-space-8); - padding-right: var(--canon-space-8); - } - - .xl\:cu-px-9 { - padding-left: var(--canon-space-9); - padding-right: var(--canon-space-9); - } - - .xl\:cu-px-10 { - padding-left: var(--canon-space-10); - padding-right: var(--canon-space-10); - } - - .xl\:cu-px-11 { - padding-left: var(--canon-space-11); - padding-right: var(--canon-space-11); - } - - .xl\:cu-px-12 { - padding-left: var(--canon-space-12); - padding-right: var(--canon-space-12); - } - - .xl\:cu-px-13 { - padding-left: var(--canon-space-13); - padding-right: var(--canon-space-13); - } - - .xl\:cu-px-14 { - padding-left: var(--canon-space-14); - padding-right: var(--canon-space-14); - } -} - -.cu-m { - margin: var(--m); -} - -.cu-m-0\.5 { - margin: var(--canon-space-0_5); -} - -.cu-m-1 { - margin: var(--canon-space-1); -} - -.cu-m-1\.5 { - margin: var(--canon-space-1_5); -} - -.cu-m-2 { - margin: var(--canon-space-2); -} - -.cu-m-3 { - margin: var(--canon-space-3); -} - -.cu-m-4 { - margin: var(--canon-space-4); -} - -.cu-m-5 { - margin: var(--canon-space-5); -} - -.cu-m-6 { - margin: var(--canon-space-6); -} - -.cu-m-7 { - margin: var(--canon-space-7); -} - -.cu-m-8 { - margin: var(--canon-space-8); -} - -.cu-m-9 { - margin: var(--canon-space-9); -} - -.cu-m-10 { - margin: var(--canon-space-10); -} - -.cu-m-11 { - margin: var(--canon-space-11); -} - -.cu-m-12 { - margin: var(--canon-space-12); -} - -.cu-m-13 { - margin: var(--canon-space-13); -} - -.cu-m-14 { - margin: var(--canon-space-14); -} - -@media (width >= 640px) { - .xs\:cu-m { - margin: var(--p-xs); - } - - .xs\:cu-m-0\.5 { - margin: var(--canon-space-0_5); - } - - .xs\:cu-m-1 { - margin: var(--canon-space-1); - } - - .xs\:cu-m-1\.5 { - margin: var(--canon-space-1_5); - } - - .xs\:cu-m-2 { - margin: var(--canon-space-2); - } - - .xs\:cu-m-3 { - margin: var(--canon-space-3); - } - - .xs\:cu-m-4 { - margin: var(--canon-space-4); - } - - .xs\:cu-m-5 { - margin: var(--canon-space-5); - } - - .xs\:cu-m-6 { - margin: var(--canon-space-6); - } - - .xs\:cu-m-7 { - margin: var(--canon-space-7); - } - - .xs\:cu-m-8 { - margin: var(--canon-space-8); - } - - .xs\:cu-m-9 { - margin: var(--canon-space-9); - } - - .xs\:cu-m-10 { - margin: var(--canon-space-10); - } - - .xs\:cu-m-11 { - margin: var(--canon-space-11); - } - - .xs\:cu-m-12 { - margin: var(--canon-space-12); - } - - .xs\:cu-m-13 { - margin: var(--canon-space-13); - } - - .xs\:cu-m-14 { - margin: var(--canon-space-14); - } -} - -@media (width >= 768px) { - .sm\:cu-m { - margin: var(--p-sm); - } - - .sm\:cu-m-0\.5 { - margin: var(--canon-space-0_5); - } - - .sm\:cu-m-1 { - margin: var(--canon-space-1); - } - - .sm\:cu-m-1\.5 { - margin: var(--canon-space-1_5); - } - - .sm\:cu-m-2 { - margin: var(--canon-space-2); - } - - .sm\:cu-m-3 { - margin: var(--canon-space-3); - } - - .sm\:cu-m-4 { - margin: var(--canon-space-4); - } - - .sm\:cu-m-5 { - margin: var(--canon-space-5); - } - - .sm\:cu-m-6 { - margin: var(--canon-space-6); - } - - .sm\:cu-m-7 { - margin: var(--canon-space-7); - } - - .sm\:cu-m-8 { - margin: var(--canon-space-8); - } - - .sm\:cu-m-9 { - margin: var(--canon-space-9); - } - - .sm\:cu-m-10 { - margin: var(--canon-space-10); - } - - .sm\:cu-m-11 { - margin: var(--canon-space-11); - } - - .sm\:cu-m-12 { - margin: var(--canon-space-12); - } - - .sm\:cu-m-13 { - margin: var(--canon-space-13); - } - - .sm\:cu-m-14 { - margin: var(--canon-space-14); - } -} - -@media (width >= 1024px) { - .md\:cu-m { - margin: var(--p-md); - } - - .md\:cu-m-0\.5 { - margin: var(--canon-space-0_5); - } - - .md\:cu-m-1 { - margin: var(--canon-space-1); - } - - .md\:cu-m-1\.5 { - margin: var(--canon-space-1_5); - } - - .md\:cu-m-2 { - margin: var(--canon-space-2); - } - - .md\:cu-m-3 { - margin: var(--canon-space-3); - } - - .md\:cu-m-4 { - margin: var(--canon-space-4); - } - - .md\:cu-m-5 { - margin: var(--canon-space-5); - } - - .md\:cu-m-6 { - margin: var(--canon-space-6); - } - - .md\:cu-m-7 { - margin: var(--canon-space-7); - } - - .md\:cu-m-8 { - margin: var(--canon-space-8); - } - - .md\:cu-m-9 { - margin: var(--canon-space-9); - } - - .md\:cu-m-10 { - margin: var(--canon-space-10); - } - - .md\:cu-m-11 { - margin: var(--canon-space-11); - } - - .md\:cu-m-12 { - margin: var(--canon-space-12); - } - - .md\:cu-m-13 { - margin: var(--canon-space-13); - } - - .md\:cu-m-14 { - margin: var(--canon-space-14); - } -} - -@media (width >= 1280px) { - .lg\:cu-m { - margin: var(--p-lg); - } - - .lg\:cu-m-0\.5 { - margin: var(--canon-space-0_5); - } - - .lg\:cu-m-1 { - margin: var(--canon-space-1); - } - - .lg\:cu-m-1\.5 { - margin: var(--canon-space-1_5); - } - - .lg\:cu-m-2 { - margin: var(--canon-space-2); - } - - .lg\:cu-m-3 { - margin: var(--canon-space-3); - } - - .lg\:cu-m-4 { - margin: var(--canon-space-4); - } - - .lg\:cu-m-5 { - margin: var(--canon-space-5); - } - - .lg\:cu-m-6 { - margin: var(--canon-space-6); - } - - .lg\:cu-m-7 { - margin: var(--canon-space-7); - } - - .lg\:cu-m-8 { - margin: var(--canon-space-8); - } - - .lg\:cu-m-9 { - margin: var(--canon-space-9); - } - - .lg\:cu-m-10 { - margin: var(--canon-space-10); - } - - .lg\:cu-m-11 { - margin: var(--canon-space-11); - } - - .lg\:cu-m-12 { - margin: var(--canon-space-12); - } - - .lg\:cu-m-13 { - margin: var(--canon-space-13); - } - - .lg\:cu-m-14 { - margin: var(--canon-space-14); - } -} - -@media (width >= 1536px) { - .xl\:cu-m { - margin: var(--p-xl); - } - - .xl\:cu-m-0\.5 { - margin: var(--canon-space-0_5); - } - - .xl\:cu-m-1 { - margin: var(--canon-space-1); - } - - .xl\:cu-m-1\.5 { - margin: var(--canon-space-1_5); - } - - .xl\:cu-m-2 { - margin: var(--canon-space-2); - } - - .xl\:cu-m-3 { - margin: var(--canon-space-3); - } - - .xl\:cu-m-4 { - margin: var(--canon-space-4); - } - - .xl\:cu-m-5 { - margin: var(--canon-space-5); - } - - .xl\:cu-m-6 { - margin: var(--canon-space-6); - } - - .xl\:cu-m-7 { - margin: var(--canon-space-7); - } - - .xl\:cu-m-8 { - margin: var(--canon-space-8); - } - - .xl\:cu-m-9 { - margin: var(--canon-space-9); - } - - .xl\:cu-m-10 { - margin: var(--canon-space-10); - } - - .xl\:cu-m-11 { - margin: var(--canon-space-11); - } - - .xl\:cu-m-12 { - margin: var(--canon-space-12); - } - - .xl\:cu-m-13 { - margin: var(--canon-space-13); - } - - .xl\:cu-m-14 { - margin: var(--canon-space-14); - } -} - -.cu-ml { - margin-left: var(--ml); -} - -.cu-ml-0\.5 { - margin-left: var(--canon-space-0_5); -} - -.cu-ml-1 { - margin-left: var(--canon-space-1); -} - -.cu-ml-1\.5 { - margin-left: var(--canon-space-1_5); -} - -.cu-ml-2 { - margin-left: var(--canon-space-2); -} - -.cu-ml-3 { - margin-left: var(--canon-space-3); -} - -.cu-ml-4 { - margin-left: var(--canon-space-4); -} - -.cu-ml-5 { - margin-left: var(--canon-space-5); -} - -.cu-ml-6 { - margin-left: var(--canon-space-6); -} - -.cu-ml-7 { - margin-left: var(--canon-space-7); -} - -.cu-ml-8 { - margin-left: var(--canon-space-8); -} - -.cu-ml-9 { - margin-left: var(--canon-space-9); -} - -.cu-ml-10 { - margin-left: var(--canon-space-10); -} - -.cu-ml-11 { - margin-left: var(--canon-space-11); -} - -.cu-ml-12 { - margin-left: var(--canon-space-12); -} - -.cu-ml-13 { - margin-left: var(--canon-space-13); -} - -.cu-ml-14 { - margin-left: var(--canon-space-14); -} - -@media (width >= 640px) { - .xs\:cu-ml { - margin-left: var(--pl-xs); - } - - .xs\:cu-ml-0\.5 { - margin-left: var(--canon-space-0_5); - } - - .xs\:cu-ml-1 { - margin-left: var(--canon-space-1); - } - - .xs\:cu-ml-1\.5 { - margin-left: var(--canon-space-1_5); - } - - .xs\:cu-ml-2 { - margin-left: var(--canon-space-2); - } - - .xs\:cu-ml-3 { - margin-left: var(--canon-space-3); - } - - .xs\:cu-ml-4 { - margin-left: var(--canon-space-4); - } - - .xs\:cu-ml-5 { - margin-left: var(--canon-space-5); - } - - .xs\:cu-ml-6 { - margin-left: var(--canon-space-6); - } - - .xs\:cu-ml-7 { - margin-left: var(--canon-space-7); - } - - .xs\:cu-ml-8 { - margin-left: var(--canon-space-8); - } - - .xs\:cu-ml-9 { - margin-left: var(--canon-space-9); - } - - .xs\:cu-ml-10 { - margin-left: var(--canon-space-10); - } - - .xs\:cu-ml-11 { - margin-left: var(--canon-space-11); - } - - .xs\:cu-ml-12 { - margin-left: var(--canon-space-12); - } - - .xs\:cu-ml-13 { - margin-left: var(--canon-space-13); - } - - .xs\:cu-ml-14 { - margin-left: var(--canon-space-14); - } -} - -@media (width >= 768px) { - .sm\:cu-ml { - margin-left: var(--pl-sm); - } - - .sm\:cu-ml-0\.5 { - margin-left: var(--canon-space-0_5); - } - - .sm\:cu-ml-1 { - margin-left: var(--canon-space-1); - } - - .sm\:cu-ml-1\.5 { - margin-left: var(--canon-space-1_5); - } - - .sm\:cu-ml-2 { - margin-left: var(--canon-space-2); - } - - .sm\:cu-ml-3 { - margin-left: var(--canon-space-3); - } - - .sm\:cu-ml-4 { - margin-left: var(--canon-space-4); - } - - .sm\:cu-ml-5 { - margin-left: var(--canon-space-5); - } - - .sm\:cu-ml-6 { - margin-left: var(--canon-space-6); - } - - .sm\:cu-ml-7 { - margin-left: var(--canon-space-7); - } - - .sm\:cu-ml-8 { - margin-left: var(--canon-space-8); - } - - .sm\:cu-ml-9 { - margin-left: var(--canon-space-9); - } - - .sm\:cu-ml-10 { - margin-left: var(--canon-space-10); - } - - .sm\:cu-ml-11 { - margin-left: var(--canon-space-11); - } - - .sm\:cu-ml-12 { - margin-left: var(--canon-space-12); - } - - .sm\:cu-ml-13 { - margin-left: var(--canon-space-13); - } - - .sm\:cu-ml-14 { - margin-left: var(--canon-space-14); - } -} - -@media (width >= 1024px) { - .md\:cu-ml { - margin-left: var(--pl-md); - } - - .md\:cu-ml-0\.5 { - margin-left: var(--canon-space-0_5); - } - - .md\:cu-ml-1 { - margin-left: var(--canon-space-1); - } - - .md\:cu-ml-1\.5 { - margin-left: var(--canon-space-1_5); - } - - .md\:cu-ml-2 { - margin-left: var(--canon-space-2); - } - - .md\:cu-ml-3 { - margin-left: var(--canon-space-3); - } - - .md\:cu-ml-4 { - margin-left: var(--canon-space-4); - } - - .md\:cu-ml-5 { - margin-left: var(--canon-space-5); - } - - .md\:cu-ml-6 { - margin-left: var(--canon-space-6); - } - - .md\:cu-ml-7 { - margin-left: var(--canon-space-7); - } - - .md\:cu-ml-8 { - margin-left: var(--canon-space-8); - } - - .md\:cu-ml-9 { - margin-left: var(--canon-space-9); - } - - .md\:cu-ml-10 { - margin-left: var(--canon-space-10); - } - - .md\:cu-ml-11 { - margin-left: var(--canon-space-11); - } - - .md\:cu-ml-12 { - margin-left: var(--canon-space-12); - } - - .md\:cu-ml-13 { - margin-left: var(--canon-space-13); - } - - .md\:cu-ml-14 { - margin-left: var(--canon-space-14); - } -} - -@media (width >= 1280px) { - .lg\:cu-ml { - margin-left: var(--pl-lg); - } - - .lg\:cu-ml-0\.5 { - margin-left: var(--canon-space-0_5); - } - - .lg\:cu-ml-1 { - margin-left: var(--canon-space-1); - } - - .lg\:cu-ml-1\.5 { - margin-left: var(--canon-space-1_5); - } - - .lg\:cu-ml-2 { - margin-left: var(--canon-space-2); - } - - .lg\:cu-ml-3 { - margin-left: var(--canon-space-3); - } - - .lg\:cu-ml-4 { - margin-left: var(--canon-space-4); - } - - .lg\:cu-ml-5 { - margin-left: var(--canon-space-5); - } - - .lg\:cu-ml-6 { - margin-left: var(--canon-space-6); - } - - .lg\:cu-ml-7 { - margin-left: var(--canon-space-7); - } - - .lg\:cu-ml-8 { - margin-left: var(--canon-space-8); - } - - .lg\:cu-ml-9 { - margin-left: var(--canon-space-9); - } - - .lg\:cu-ml-10 { - margin-left: var(--canon-space-10); - } - - .lg\:cu-ml-11 { - margin-left: var(--canon-space-11); - } - - .lg\:cu-ml-12 { - margin-left: var(--canon-space-12); - } - - .lg\:cu-ml-13 { - margin-left: var(--canon-space-13); - } - - .lg\:cu-ml-14 { - margin-left: var(--canon-space-14); - } -} - -@media (width >= 1536px) { - .xl\:cu-ml { - margin-left: var(--pl-xl); - } - - .xl\:cu-ml-0\.5 { - margin-left: var(--canon-space-0_5); - } - - .xl\:cu-ml-1 { - margin-left: var(--canon-space-1); - } - - .xl\:cu-ml-1\.5 { - margin-left: var(--canon-space-1_5); - } - - .xl\:cu-ml-2 { - margin-left: var(--canon-space-2); - } - - .xl\:cu-ml-3 { - margin-left: var(--canon-space-3); - } - - .xl\:cu-ml-4 { - margin-left: var(--canon-space-4); - } - - .xl\:cu-ml-5 { - margin-left: var(--canon-space-5); - } - - .xl\:cu-ml-6 { - margin-left: var(--canon-space-6); - } - - .xl\:cu-ml-7 { - margin-left: var(--canon-space-7); - } - - .xl\:cu-ml-8 { - margin-left: var(--canon-space-8); - } - - .xl\:cu-ml-9 { - margin-left: var(--canon-space-9); - } - - .xl\:cu-ml-10 { - margin-left: var(--canon-space-10); - } - - .xl\:cu-ml-11 { - margin-left: var(--canon-space-11); - } - - .xl\:cu-ml-12 { - margin-left: var(--canon-space-12); - } - - .xl\:cu-ml-13 { - margin-left: var(--canon-space-13); - } - - .xl\:cu-ml-14 { - margin-left: var(--canon-space-14); - } -} - -.cu-mr { - margin-right: var(--mr); -} - -.cu-mr-0\.5 { - margin-right: var(--canon-space-0_5); -} - -.cu-mr-1 { - margin-right: var(--canon-space-1); -} - -.cu-mr-1\.5 { - margin-right: var(--canon-space-1_5); -} - -.cu-mr-2 { - margin-right: var(--canon-space-2); -} - -.cu-mr-3 { - margin-right: var(--canon-space-3); -} - -.cu-mr-4 { - margin-right: var(--canon-space-4); -} - -.cu-mr-5 { - margin-right: var(--canon-space-5); -} - -.cu-mr-6 { - margin-right: var(--canon-space-6); -} - -.cu-mr-7 { - margin-right: var(--canon-space-7); -} - -.cu-mr-8 { - margin-right: var(--canon-space-8); -} - -.cu-mr-9 { - margin-right: var(--canon-space-9); -} - -.cu-mr-10 { - margin-right: var(--canon-space-10); -} - -.cu-mr-11 { - margin-right: var(--canon-space-11); -} - -.cu-mr-12 { - margin-right: var(--canon-space-12); -} - -.cu-mr-13 { - margin-right: var(--canon-space-13); -} - -.cu-mr-14 { - margin-right: var(--canon-space-14); -} - -@media (width >= 640px) { - .xs\:cu-mr { - margin-right: var(--pr-xs); - } - - .xs\:cu-mr-0\.5 { - margin-right: var(--canon-space-0_5); - } - - .xs\:cu-mr-1 { - margin-right: var(--canon-space-1); - } - - .xs\:cu-mr-1\.5 { - margin-right: var(--canon-space-1_5); - } - - .xs\:cu-mr-2 { - margin-right: var(--canon-space-2); - } - - .xs\:cu-mr-3 { - margin-right: var(--canon-space-3); - } - - .xs\:cu-mr-4 { - margin-right: var(--canon-space-4); - } - - .xs\:cu-mr-5 { - margin-right: var(--canon-space-5); - } - - .xs\:cu-mr-6 { - margin-right: var(--canon-space-6); - } - - .xs\:cu-mr-7 { - margin-right: var(--canon-space-7); - } - - .xs\:cu-mr-8 { - margin-right: var(--canon-space-8); - } - - .xs\:cu-mr-9 { - margin-right: var(--canon-space-9); - } - - .xs\:cu-mr-10 { - margin-right: var(--canon-space-10); - } - - .xs\:cu-mr-11 { - margin-right: var(--canon-space-11); - } - - .xs\:cu-mr-12 { - margin-right: var(--canon-space-12); - } - - .xs\:cu-mr-13 { - margin-right: var(--canon-space-13); - } - - .xs\:cu-mr-14 { - margin-right: var(--canon-space-14); - } -} - -@media (width >= 768px) { - .sm\:cu-mr { - margin-right: var(--pr-sm); - } - - .sm\:cu-mr-0\.5 { - margin-right: var(--canon-space-0_5); - } - - .sm\:cu-mr-1 { - margin-right: var(--canon-space-1); - } - - .sm\:cu-mr-1\.5 { - margin-right: var(--canon-space-1_5); - } - - .sm\:cu-mr-2 { - margin-right: var(--canon-space-2); - } - - .sm\:cu-mr-3 { - margin-right: var(--canon-space-3); - } - - .sm\:cu-mr-4 { - margin-right: var(--canon-space-4); - } - - .sm\:cu-mr-5 { - margin-right: var(--canon-space-5); - } - - .sm\:cu-mr-6 { - margin-right: var(--canon-space-6); - } - - .sm\:cu-mr-7 { - margin-right: var(--canon-space-7); - } - - .sm\:cu-mr-8 { - margin-right: var(--canon-space-8); - } - - .sm\:cu-mr-9 { - margin-right: var(--canon-space-9); - } - - .sm\:cu-mr-10 { - margin-right: var(--canon-space-10); - } - - .sm\:cu-mr-11 { - margin-right: var(--canon-space-11); - } - - .sm\:cu-mr-12 { - margin-right: var(--canon-space-12); - } - - .sm\:cu-mr-13 { - margin-right: var(--canon-space-13); - } - - .sm\:cu-mr-14 { - margin-right: var(--canon-space-14); - } -} - -@media (width >= 1024px) { - .md\:cu-mr { - margin-right: var(--pr-md); - } - - .md\:cu-mr-0\.5 { - margin-right: var(--canon-space-0_5); - } - - .md\:cu-mr-1 { - margin-right: var(--canon-space-1); - } - - .md\:cu-mr-1\.5 { - margin-right: var(--canon-space-1_5); - } - - .md\:cu-mr-2 { - margin-right: var(--canon-space-2); - } - - .md\:cu-mr-3 { - margin-right: var(--canon-space-3); - } - - .md\:cu-mr-4 { - margin-right: var(--canon-space-4); - } - - .md\:cu-mr-5 { - margin-right: var(--canon-space-5); - } - - .md\:cu-mr-6 { - margin-right: var(--canon-space-6); - } - - .md\:cu-mr-7 { - margin-right: var(--canon-space-7); - } - - .md\:cu-mr-8 { - margin-right: var(--canon-space-8); - } - - .md\:cu-mr-9 { - margin-right: var(--canon-space-9); - } - - .md\:cu-mr-10 { - margin-right: var(--canon-space-10); - } - - .md\:cu-mr-11 { - margin-right: var(--canon-space-11); - } - - .md\:cu-mr-12 { - margin-right: var(--canon-space-12); - } - - .md\:cu-mr-13 { - margin-right: var(--canon-space-13); - } - - .md\:cu-mr-14 { - margin-right: var(--canon-space-14); - } -} - -@media (width >= 1280px) { - .lg\:cu-mr { - margin-right: var(--pr-lg); - } - - .lg\:cu-mr-0\.5 { - margin-right: var(--canon-space-0_5); - } - - .lg\:cu-mr-1 { - margin-right: var(--canon-space-1); - } - - .lg\:cu-mr-1\.5 { - margin-right: var(--canon-space-1_5); - } - - .lg\:cu-mr-2 { - margin-right: var(--canon-space-2); - } - - .lg\:cu-mr-3 { - margin-right: var(--canon-space-3); - } - - .lg\:cu-mr-4 { - margin-right: var(--canon-space-4); - } - - .lg\:cu-mr-5 { - margin-right: var(--canon-space-5); - } - - .lg\:cu-mr-6 { - margin-right: var(--canon-space-6); - } - - .lg\:cu-mr-7 { - margin-right: var(--canon-space-7); - } - - .lg\:cu-mr-8 { - margin-right: var(--canon-space-8); - } - - .lg\:cu-mr-9 { - margin-right: var(--canon-space-9); - } - - .lg\:cu-mr-10 { - margin-right: var(--canon-space-10); - } - - .lg\:cu-mr-11 { - margin-right: var(--canon-space-11); - } - - .lg\:cu-mr-12 { - margin-right: var(--canon-space-12); - } - - .lg\:cu-mr-13 { - margin-right: var(--canon-space-13); - } - - .lg\:cu-mr-14 { - margin-right: var(--canon-space-14); - } -} - -@media (width >= 1536px) { - .xl\:cu-mr { - margin-right: var(--pr-xl); - } - - .xl\:cu-mr-0\.5 { - margin-right: var(--canon-space-0_5); - } - - .xl\:cu-mr-1 { - margin-right: var(--canon-space-1); - } - - .xl\:cu-mr-1\.5 { - margin-right: var(--canon-space-1_5); - } - - .xl\:cu-mr-2 { - margin-right: var(--canon-space-2); - } - - .xl\:cu-mr-3 { - margin-right: var(--canon-space-3); - } - - .xl\:cu-mr-4 { - margin-right: var(--canon-space-4); - } - - .xl\:cu-mr-5 { - margin-right: var(--canon-space-5); - } - - .xl\:cu-mr-6 { - margin-right: var(--canon-space-6); - } - - .xl\:cu-mr-7 { - margin-right: var(--canon-space-7); - } - - .xl\:cu-mr-8 { - margin-right: var(--canon-space-8); - } - - .xl\:cu-mr-9 { - margin-right: var(--canon-space-9); - } - - .xl\:cu-mr-10 { - margin-right: var(--canon-space-10); - } - - .xl\:cu-mr-11 { - margin-right: var(--canon-space-11); - } - - .xl\:cu-mr-12 { - margin-right: var(--canon-space-12); - } - - .xl\:cu-mr-13 { - margin-right: var(--canon-space-13); - } - - .xl\:cu-mr-14 { - margin-right: var(--canon-space-14); - } -} - -.cu-mt { - margin-top: var(--mt); -} - -.cu-mt-0\.5 { - margin-top: var(--canon-space-0_5); -} - -.cu-mt-1 { - margin-top: var(--canon-space-1); -} - -.cu-mt-1\.5 { - margin-top: var(--canon-space-1_5); -} - -.cu-mt-2 { - margin-top: var(--canon-space-2); -} - -.cu-mt-3 { - margin-top: var(--canon-space-3); -} - -.cu-mt-4 { - margin-top: var(--canon-space-4); -} - -.cu-mt-5 { - margin-top: var(--canon-space-5); -} - -.cu-mt-6 { - margin-top: var(--canon-space-6); -} - -.cu-mt-7 { - margin-top: var(--canon-space-7); -} - -.cu-mt-8 { - margin-top: var(--canon-space-8); -} - -.cu-mt-9 { - margin-top: var(--canon-space-9); -} - -.cu-mt-10 { - margin-top: var(--canon-space-10); -} - -.cu-mt-11 { - margin-top: var(--canon-space-11); -} - -.cu-mt-12 { - margin-top: var(--canon-space-12); -} - -.cu-mt-13 { - margin-top: var(--canon-space-13); -} - -.cu-mt-14 { - margin-top: var(--canon-space-14); -} - -@media (width >= 640px) { - .xs\:cu-mt { - margin-top: var(--pt-xs); - } - - .xs\:cu-mt-0\.5 { - margin-top: var(--canon-space-0_5); - } - - .xs\:cu-mt-1 { - margin-top: var(--canon-space-1); - } - - .xs\:cu-mt-1\.5 { - margin-top: var(--canon-space-1_5); - } - - .xs\:cu-mt-2 { - margin-top: var(--canon-space-2); - } - - .xs\:cu-mt-3 { - margin-top: var(--canon-space-3); - } - - .xs\:cu-mt-4 { - margin-top: var(--canon-space-4); - } - - .xs\:cu-mt-5 { - margin-top: var(--canon-space-5); - } - - .xs\:cu-mt-6 { - margin-top: var(--canon-space-6); - } - - .xs\:cu-mt-7 { - margin-top: var(--canon-space-7); - } - - .xs\:cu-mt-8 { - margin-top: var(--canon-space-8); - } - - .xs\:cu-mt-9 { - margin-top: var(--canon-space-9); - } - - .xs\:cu-mt-10 { - margin-top: var(--canon-space-10); - } - - .xs\:cu-mt-11 { - margin-top: var(--canon-space-11); - } - - .xs\:cu-mt-12 { - margin-top: var(--canon-space-12); - } - - .xs\:cu-mt-13 { - margin-top: var(--canon-space-13); - } - - .xs\:cu-mt-14 { - margin-top: var(--canon-space-14); - } -} - -@media (width >= 768px) { - .sm\:cu-mt { - margin-top: var(--pt-sm); - } - - .sm\:cu-mt-0\.5 { - margin-top: var(--canon-space-0_5); - } - - .sm\:cu-mt-1 { - margin-top: var(--canon-space-1); - } - - .sm\:cu-mt-1\.5 { - margin-top: var(--canon-space-1_5); - } - - .sm\:cu-mt-2 { - margin-top: var(--canon-space-2); - } - - .sm\:cu-mt-3 { - margin-top: var(--canon-space-3); - } - - .sm\:cu-mt-4 { - margin-top: var(--canon-space-4); - } - - .sm\:cu-mt-5 { - margin-top: var(--canon-space-5); - } - - .sm\:cu-mt-6 { - margin-top: var(--canon-space-6); - } - - .sm\:cu-mt-7 { - margin-top: var(--canon-space-7); - } - - .sm\:cu-mt-8 { - margin-top: var(--canon-space-8); - } - - .sm\:cu-mt-9 { - margin-top: var(--canon-space-9); - } - - .sm\:cu-mt-10 { - margin-top: var(--canon-space-10); - } - - .sm\:cu-mt-11 { - margin-top: var(--canon-space-11); - } - - .sm\:cu-mt-12 { - margin-top: var(--canon-space-12); - } - - .sm\:cu-mt-13 { - margin-top: var(--canon-space-13); - } - - .sm\:cu-mt-14 { - margin-top: var(--canon-space-14); - } -} - -@media (width >= 1024px) { - .md\:cu-mt { - margin-top: var(--pt-md); - } - - .md\:cu-mt-0\.5 { - margin-top: var(--canon-space-0_5); - } - - .md\:cu-mt-1 { - margin-top: var(--canon-space-1); - } - - .md\:cu-mt-1\.5 { - margin-top: var(--canon-space-1_5); - } - - .md\:cu-mt-2 { - margin-top: var(--canon-space-2); - } - - .md\:cu-mt-3 { - margin-top: var(--canon-space-3); - } - - .md\:cu-mt-4 { - margin-top: var(--canon-space-4); - } - - .md\:cu-mt-5 { - margin-top: var(--canon-space-5); - } - - .md\:cu-mt-6 { - margin-top: var(--canon-space-6); - } - - .md\:cu-mt-7 { - margin-top: var(--canon-space-7); - } - - .md\:cu-mt-8 { - margin-top: var(--canon-space-8); - } - - .md\:cu-mt-9 { - margin-top: var(--canon-space-9); - } - - .md\:cu-mt-10 { - margin-top: var(--canon-space-10); - } - - .md\:cu-mt-11 { - margin-top: var(--canon-space-11); - } - - .md\:cu-mt-12 { - margin-top: var(--canon-space-12); - } - - .md\:cu-mt-13 { - margin-top: var(--canon-space-13); - } - - .md\:cu-mt-14 { - margin-top: var(--canon-space-14); - } -} - -@media (width >= 1280px) { - .lg\:cu-mt { - margin-top: var(--pt-lg); - } - - .lg\:cu-mt-0\.5 { - margin-top: var(--canon-space-0_5); - } - - .lg\:cu-mt-1 { - margin-top: var(--canon-space-1); - } - - .lg\:cu-mt-1\.5 { - margin-top: var(--canon-space-1_5); - } - - .lg\:cu-mt-2 { - margin-top: var(--canon-space-2); - } - - .lg\:cu-mt-3 { - margin-top: var(--canon-space-3); - } - - .lg\:cu-mt-4 { - margin-top: var(--canon-space-4); - } - - .lg\:cu-mt-5 { - margin-top: var(--canon-space-5); - } - - .lg\:cu-mt-6 { - margin-top: var(--canon-space-6); - } - - .lg\:cu-mt-7 { - margin-top: var(--canon-space-7); - } - - .lg\:cu-mt-8 { - margin-top: var(--canon-space-8); - } - - .lg\:cu-mt-9 { - margin-top: var(--canon-space-9); - } - - .lg\:cu-mt-10 { - margin-top: var(--canon-space-10); - } - - .lg\:cu-mt-11 { - margin-top: var(--canon-space-11); - } - - .lg\:cu-mt-12 { - margin-top: var(--canon-space-12); - } - - .lg\:cu-mt-13 { - margin-top: var(--canon-space-13); - } - - .lg\:cu-mt-14 { - margin-top: var(--canon-space-14); - } -} - -@media (width >= 1536px) { - .xl\:cu-mt { - margin-top: var(--pt-xl); - } - - .xl\:cu-mt-0\.5 { - margin-top: var(--canon-space-0_5); - } - - .xl\:cu-mt-1 { - margin-top: var(--canon-space-1); - } - - .xl\:cu-mt-1\.5 { - margin-top: var(--canon-space-1_5); - } - - .xl\:cu-mt-2 { - margin-top: var(--canon-space-2); - } - - .xl\:cu-mt-3 { - margin-top: var(--canon-space-3); - } - - .xl\:cu-mt-4 { - margin-top: var(--canon-space-4); - } - - .xl\:cu-mt-5 { - margin-top: var(--canon-space-5); - } - - .xl\:cu-mt-6 { - margin-top: var(--canon-space-6); - } - - .xl\:cu-mt-7 { - margin-top: var(--canon-space-7); - } - - .xl\:cu-mt-8 { - margin-top: var(--canon-space-8); - } - - .xl\:cu-mt-9 { - margin-top: var(--canon-space-9); - } - - .xl\:cu-mt-10 { - margin-top: var(--canon-space-10); - } - - .xl\:cu-mt-11 { - margin-top: var(--canon-space-11); - } - - .xl\:cu-mt-12 { - margin-top: var(--canon-space-12); - } - - .xl\:cu-mt-13 { - margin-top: var(--canon-space-13); - } - - .xl\:cu-mt-14 { - margin-top: var(--canon-space-14); - } -} - -.cu-mb { - margin-bottom: var(--mb); -} - -.cu-mb-0\.5 { - margin-bottom: var(--canon-space-0_5); -} - -.cu-mb-1 { - margin-bottom: var(--canon-space-1); -} - -.cu-mb-1\.5 { - margin-bottom: var(--canon-space-1_5); -} - -.cu-mb-2 { - margin-bottom: var(--canon-space-2); -} - -.cu-mb-3 { - margin-bottom: var(--canon-space-3); -} - -.cu-mb-4 { - margin-bottom: var(--canon-space-4); -} - -.cu-mb-5 { - margin-bottom: var(--canon-space-5); -} - -.cu-mb-6 { - margin-bottom: var(--canon-space-6); -} - -.cu-mb-7 { - margin-bottom: var(--canon-space-7); -} - -.cu-mb-8 { - margin-bottom: var(--canon-space-8); -} - -.cu-mb-9 { - margin-bottom: var(--canon-space-9); -} - -.cu-mb-10 { - margin-bottom: var(--canon-space-10); -} - -.cu-mb-11 { - margin-bottom: var(--canon-space-11); -} - -.cu-mb-12 { - margin-bottom: var(--canon-space-12); -} - -.cu-mb-13 { - margin-bottom: var(--canon-space-13); -} - -.cu-mb-14 { - margin-bottom: var(--canon-space-14); -} - -@media (width >= 640px) { - .xs\:cu-mb { - margin-bottom: var(--pb-xs); - } - - .xs\:cu-mb-0\.5 { - margin-bottom: var(--canon-space-0_5); - } - - .xs\:cu-mb-1 { - margin-bottom: var(--canon-space-1); - } - - .xs\:cu-mb-1\.5 { - margin-bottom: var(--canon-space-1_5); - } - - .xs\:cu-mb-2 { - margin-bottom: var(--canon-space-2); - } - - .xs\:cu-mb-3 { - margin-bottom: var(--canon-space-3); - } - - .xs\:cu-mb-4 { - margin-bottom: var(--canon-space-4); - } - - .xs\:cu-mb-5 { - margin-bottom: var(--canon-space-5); - } - - .xs\:cu-mb-6 { - margin-bottom: var(--canon-space-6); - } - - .xs\:cu-mb-7 { - margin-bottom: var(--canon-space-7); - } - - .xs\:cu-mb-8 { - margin-bottom: var(--canon-space-8); - } - - .xs\:cu-mb-9 { - margin-bottom: var(--canon-space-9); - } - - .xs\:cu-mb-10 { - margin-bottom: var(--canon-space-10); - } - - .xs\:cu-mb-11 { - margin-bottom: var(--canon-space-11); - } - - .xs\:cu-mb-12 { - margin-bottom: var(--canon-space-12); - } - - .xs\:cu-mb-13 { - margin-bottom: var(--canon-space-13); - } - - .xs\:cu-mb-14 { - margin-bottom: var(--canon-space-14); - } -} - -@media (width >= 768px) { - .sm\:cu-mb { - margin-bottom: var(--pb-sm); - } - - .sm\:cu-mb-0\.5 { - margin-bottom: var(--canon-space-0_5); - } - - .sm\:cu-mb-1 { - margin-bottom: var(--canon-space-1); - } - - .sm\:cu-mb-1\.5 { - margin-bottom: var(--canon-space-1_5); - } - - .sm\:cu-mb-2 { - margin-bottom: var(--canon-space-2); - } - - .sm\:cu-mb-3 { - margin-bottom: var(--canon-space-3); - } - - .sm\:cu-mb-4 { - margin-bottom: var(--canon-space-4); - } - - .sm\:cu-mb-5 { - margin-bottom: var(--canon-space-5); - } - - .sm\:cu-mb-6 { - margin-bottom: var(--canon-space-6); - } - - .sm\:cu-mb-7 { - margin-bottom: var(--canon-space-7); - } - - .sm\:cu-mb-8 { - margin-bottom: var(--canon-space-8); - } - - .sm\:cu-mb-9 { - margin-bottom: var(--canon-space-9); - } - - .sm\:cu-mb-10 { - margin-bottom: var(--canon-space-10); - } - - .sm\:cu-mb-11 { - margin-bottom: var(--canon-space-11); - } - - .sm\:cu-mb-12 { - margin-bottom: var(--canon-space-12); - } - - .sm\:cu-mb-13 { - margin-bottom: var(--canon-space-13); - } - - .sm\:cu-mb-14 { - margin-bottom: var(--canon-space-14); - } -} - -@media (width >= 1024px) { - .md\:cu-mb { - margin-bottom: var(--pb-md); - } - - .md\:cu-mb-0\.5 { - margin-bottom: var(--canon-space-0_5); - } - - .md\:cu-mb-1 { - margin-bottom: var(--canon-space-1); - } - - .md\:cu-mb-1\.5 { - margin-bottom: var(--canon-space-1_5); - } - - .md\:cu-mb-2 { - margin-bottom: var(--canon-space-2); - } - - .md\:cu-mb-3 { - margin-bottom: var(--canon-space-3); - } - - .md\:cu-mb-4 { - margin-bottom: var(--canon-space-4); - } - - .md\:cu-mb-5 { - margin-bottom: var(--canon-space-5); - } - - .md\:cu-mb-6 { - margin-bottom: var(--canon-space-6); - } - - .md\:cu-mb-7 { - margin-bottom: var(--canon-space-7); - } - - .md\:cu-mb-8 { - margin-bottom: var(--canon-space-8); - } - - .md\:cu-mb-9 { - margin-bottom: var(--canon-space-9); - } - - .md\:cu-mb-10 { - margin-bottom: var(--canon-space-10); - } - - .md\:cu-mb-11 { - margin-bottom: var(--canon-space-11); - } - - .md\:cu-mb-12 { - margin-bottom: var(--canon-space-12); - } - - .md\:cu-mb-13 { - margin-bottom: var(--canon-space-13); - } - - .md\:cu-mb-14 { - margin-bottom: var(--canon-space-14); - } -} - -@media (width >= 1280px) { - .lg\:cu-mb { - margin-bottom: var(--pb-lg); - } - - .lg\:cu-mb-0\.5 { - margin-bottom: var(--canon-space-0_5); - } - - .lg\:cu-mb-1 { - margin-bottom: var(--canon-space-1); - } - - .lg\:cu-mb-1\.5 { - margin-bottom: var(--canon-space-1_5); - } - - .lg\:cu-mb-2 { - margin-bottom: var(--canon-space-2); - } - - .lg\:cu-mb-3 { - margin-bottom: var(--canon-space-3); - } - - .lg\:cu-mb-4 { - margin-bottom: var(--canon-space-4); - } - - .lg\:cu-mb-5 { - margin-bottom: var(--canon-space-5); - } - - .lg\:cu-mb-6 { - margin-bottom: var(--canon-space-6); - } - - .lg\:cu-mb-7 { - margin-bottom: var(--canon-space-7); - } - - .lg\:cu-mb-8 { - margin-bottom: var(--canon-space-8); - } - - .lg\:cu-mb-9 { - margin-bottom: var(--canon-space-9); - } - - .lg\:cu-mb-10 { - margin-bottom: var(--canon-space-10); - } - - .lg\:cu-mb-11 { - margin-bottom: var(--canon-space-11); - } - - .lg\:cu-mb-12 { - margin-bottom: var(--canon-space-12); - } - - .lg\:cu-mb-13 { - margin-bottom: var(--canon-space-13); - } - - .lg\:cu-mb-14 { - margin-bottom: var(--canon-space-14); - } -} - -@media (width >= 1536px) { - .xl\:cu-mb { - margin-bottom: var(--pb-xl); - } - - .xl\:cu-mb-0\.5 { - margin-bottom: var(--canon-space-0_5); - } - - .xl\:cu-mb-1 { - margin-bottom: var(--canon-space-1); - } - - .xl\:cu-mb-1\.5 { - margin-bottom: var(--canon-space-1_5); - } - - .xl\:cu-mb-2 { - margin-bottom: var(--canon-space-2); - } - - .xl\:cu-mb-3 { - margin-bottom: var(--canon-space-3); - } - - .xl\:cu-mb-4 { - margin-bottom: var(--canon-space-4); - } - - .xl\:cu-mb-5 { - margin-bottom: var(--canon-space-5); - } - - .xl\:cu-mb-6 { - margin-bottom: var(--canon-space-6); - } - - .xl\:cu-mb-7 { - margin-bottom: var(--canon-space-7); - } - - .xl\:cu-mb-8 { - margin-bottom: var(--canon-space-8); - } - - .xl\:cu-mb-9 { - margin-bottom: var(--canon-space-9); - } - - .xl\:cu-mb-10 { - margin-bottom: var(--canon-space-10); - } - - .xl\:cu-mb-11 { - margin-bottom: var(--canon-space-11); - } - - .xl\:cu-mb-12 { - margin-bottom: var(--canon-space-12); - } - - .xl\:cu-mb-13 { - margin-bottom: var(--canon-space-13); - } - - .xl\:cu-mb-14 { - margin-bottom: var(--canon-space-14); - } -} - -.cu-my { - margin-top: var(--my); - margin-bottom: var(--my); -} - -.cu-my-0\.5 { - margin-top: var(--canon-space-0_5); - margin-bottom: var(--canon-space-0_5); -} - -.cu-my-1 { - margin-top: var(--canon-space-1); - margin-bottom: var(--canon-space-1); -} - -.cu-my-1\.5 { - margin-top: var(--canon-space-1_5); - margin-bottom: var(--canon-space-1_5); -} - -.cu-my-2 { - margin-top: var(--canon-space-2); - margin-bottom: var(--canon-space-2); -} - -.cu-my-3 { - margin-top: var(--canon-space-3); - margin-bottom: var(--canon-space-3); -} - -.cu-my-4 { - margin-top: var(--canon-space-4); - margin-bottom: var(--canon-space-4); -} - -.cu-my-5 { - margin-top: var(--canon-space-5); - margin-bottom: var(--canon-space-5); -} - -.cu-my-6 { - margin-top: var(--canon-space-6); - margin-bottom: var(--canon-space-6); -} - -.cu-my-7 { - margin-top: var(--canon-space-7); - margin-bottom: var(--canon-space-7); -} - -.cu-my-8 { - margin-top: var(--canon-space-8); - margin-bottom: var(--canon-space-8); -} - -.cu-my-9 { - margin-top: var(--canon-space-9); - margin-bottom: var(--canon-space-9); -} - -.cu-my-10 { - margin-top: var(--canon-space-10); - margin-bottom: var(--canon-space-10); -} - -.cu-my-11 { - margin-top: var(--canon-space-11); - margin-bottom: var(--canon-space-11); -} - -.cu-my-12 { - margin-top: var(--canon-space-12); - margin-bottom: var(--canon-space-12); -} - -.cu-my-13 { - margin-top: var(--canon-space-13); - margin-bottom: var(--canon-space-13); -} - -.cu-my-14 { - margin-top: var(--canon-space-14); - margin-bottom: var(--canon-space-14); -} - -@media (width >= 640px) { - .xs\:cu-my { - margin-top: var(--py-xs); - margin-bottom: var(--py-xs); - } - - .xs\:cu-my-0\.5 { - margin-top: var(--canon-space-0_5); - margin-bottom: var(--canon-space-0_5); - } - - .xs\:cu-my-1 { - margin-top: var(--canon-space-1); - margin-bottom: var(--canon-space-1); - } - - .xs\:cu-my-1\.5 { - margin-top: var(--canon-space-1_5); - margin-bottom: var(--canon-space-1_5); - } - - .xs\:cu-my-2 { - margin-top: var(--canon-space-2); - margin-bottom: var(--canon-space-2); - } - - .xs\:cu-my-3 { - margin-top: var(--canon-space-3); - margin-bottom: var(--canon-space-3); - } - - .xs\:cu-my-4 { - margin-top: var(--canon-space-4); - margin-bottom: var(--canon-space-4); - } - - .xs\:cu-my-5 { - margin-top: var(--canon-space-5); - margin-bottom: var(--canon-space-5); - } - - .xs\:cu-my-6 { - margin-top: var(--canon-space-6); - margin-bottom: var(--canon-space-6); - } - - .xs\:cu-my-7 { - margin-top: var(--canon-space-7); - margin-bottom: var(--canon-space-7); - } - - .xs\:cu-my-8 { - margin-top: var(--canon-space-8); - margin-bottom: var(--canon-space-8); - } - - .xs\:cu-my-9 { - margin-top: var(--canon-space-9); - margin-bottom: var(--canon-space-9); - } - - .xs\:cu-my-10 { - margin-top: var(--canon-space-10); - margin-bottom: var(--canon-space-10); - } - - .xs\:cu-my-11 { - margin-top: var(--canon-space-11); - margin-bottom: var(--canon-space-11); - } - - .xs\:cu-my-12 { - margin-top: var(--canon-space-12); - margin-bottom: var(--canon-space-12); - } - - .xs\:cu-my-13 { - margin-top: var(--canon-space-13); - margin-bottom: var(--canon-space-13); - } - - .xs\:cu-my-14 { - margin-top: var(--canon-space-14); - margin-bottom: var(--canon-space-14); - } -} - -@media (width >= 768px) { - .sm\:cu-my { - margin-top: var(--py-sm); - margin-bottom: var(--py-sm); - } - - .sm\:cu-my-0\.5 { - margin-top: var(--canon-space-0_5); - margin-bottom: var(--canon-space-0_5); - } - - .sm\:cu-my-1 { - margin-top: var(--canon-space-1); - margin-bottom: var(--canon-space-1); - } - - .sm\:cu-my-1\.5 { - margin-top: var(--canon-space-1_5); - margin-bottom: var(--canon-space-1_5); - } - - .sm\:cu-my-2 { - margin-top: var(--canon-space-2); - margin-bottom: var(--canon-space-2); - } - - .sm\:cu-my-3 { - margin-top: var(--canon-space-3); - margin-bottom: var(--canon-space-3); - } - - .sm\:cu-my-4 { - margin-top: var(--canon-space-4); - margin-bottom: var(--canon-space-4); - } - - .sm\:cu-my-5 { - margin-top: var(--canon-space-5); - margin-bottom: var(--canon-space-5); - } - - .sm\:cu-my-6 { - margin-top: var(--canon-space-6); - margin-bottom: var(--canon-space-6); - } - - .sm\:cu-my-7 { - margin-top: var(--canon-space-7); - margin-bottom: var(--canon-space-7); - } - - .sm\:cu-my-8 { - margin-top: var(--canon-space-8); - margin-bottom: var(--canon-space-8); - } - - .sm\:cu-my-9 { - margin-top: var(--canon-space-9); - margin-bottom: var(--canon-space-9); - } - - .sm\:cu-my-10 { - margin-top: var(--canon-space-10); - margin-bottom: var(--canon-space-10); - } - - .sm\:cu-my-11 { - margin-top: var(--canon-space-11); - margin-bottom: var(--canon-space-11); - } - - .sm\:cu-my-12 { - margin-top: var(--canon-space-12); - margin-bottom: var(--canon-space-12); - } - - .sm\:cu-my-13 { - margin-top: var(--canon-space-13); - margin-bottom: var(--canon-space-13); - } - - .sm\:cu-my-14 { - margin-top: var(--canon-space-14); - margin-bottom: var(--canon-space-14); - } -} - -@media (width >= 1024px) { - .md\:cu-my { - margin-top: var(--py-md); - margin-bottom: var(--py-md); - } - - .md\:cu-my-0\.5 { - margin-top: var(--canon-space-0_5); - margin-bottom: var(--canon-space-0_5); - } - - .md\:cu-my-1 { - margin-top: var(--canon-space-1); - margin-bottom: var(--canon-space-1); - } - - .md\:cu-my-1\.5 { - margin-top: var(--canon-space-1_5); - margin-bottom: var(--canon-space-1_5); - } - - .md\:cu-my-2 { - margin-top: var(--canon-space-2); - margin-bottom: var(--canon-space-2); - } - - .md\:cu-my-3 { - margin-top: var(--canon-space-3); - margin-bottom: var(--canon-space-3); - } - - .md\:cu-my-4 { - margin-top: var(--canon-space-4); - margin-bottom: var(--canon-space-4); - } - - .md\:cu-my-5 { - margin-top: var(--canon-space-5); - margin-bottom: var(--canon-space-5); - } - - .md\:cu-my-6 { - margin-top: var(--canon-space-6); - margin-bottom: var(--canon-space-6); - } - - .md\:cu-my-7 { - margin-top: var(--canon-space-7); - margin-bottom: var(--canon-space-7); - } - - .md\:cu-my-8 { - margin-top: var(--canon-space-8); - margin-bottom: var(--canon-space-8); - } - - .md\:cu-my-9 { - margin-top: var(--canon-space-9); - margin-bottom: var(--canon-space-9); - } - - .md\:cu-my-10 { - margin-top: var(--canon-space-10); - margin-bottom: var(--canon-space-10); - } - - .md\:cu-my-11 { - margin-top: var(--canon-space-11); - margin-bottom: var(--canon-space-11); - } - - .md\:cu-my-12 { - margin-top: var(--canon-space-12); - margin-bottom: var(--canon-space-12); - } - - .md\:cu-my-13 { - margin-top: var(--canon-space-13); - margin-bottom: var(--canon-space-13); - } - - .md\:cu-my-14 { - margin-top: var(--canon-space-14); - margin-bottom: var(--canon-space-14); - } -} - -@media (width >= 1280px) { - .lg\:cu-my { - margin-top: var(--py-lg); - margin-bottom: var(--py-lg); - } - - .lg\:cu-my-0\.5 { - margin-top: var(--canon-space-0_5); - margin-bottom: var(--canon-space-0_5); - } - - .lg\:cu-my-1 { - margin-top: var(--canon-space-1); - margin-bottom: var(--canon-space-1); - } - - .lg\:cu-my-1\.5 { - margin-top: var(--canon-space-1_5); - margin-bottom: var(--canon-space-1_5); - } - - .lg\:cu-my-2 { - margin-top: var(--canon-space-2); - margin-bottom: var(--canon-space-2); - } - - .lg\:cu-my-3 { - margin-top: var(--canon-space-3); - margin-bottom: var(--canon-space-3); - } - - .lg\:cu-my-4 { - margin-top: var(--canon-space-4); - margin-bottom: var(--canon-space-4); - } - - .lg\:cu-my-5 { - margin-top: var(--canon-space-5); - margin-bottom: var(--canon-space-5); - } - - .lg\:cu-my-6 { - margin-top: var(--canon-space-6); - margin-bottom: var(--canon-space-6); - } - - .lg\:cu-my-7 { - margin-top: var(--canon-space-7); - margin-bottom: var(--canon-space-7); - } - - .lg\:cu-my-8 { - margin-top: var(--canon-space-8); - margin-bottom: var(--canon-space-8); - } - - .lg\:cu-my-9 { - margin-top: var(--canon-space-9); - margin-bottom: var(--canon-space-9); - } - - .lg\:cu-my-10 { - margin-top: var(--canon-space-10); - margin-bottom: var(--canon-space-10); - } - - .lg\:cu-my-11 { - margin-top: var(--canon-space-11); - margin-bottom: var(--canon-space-11); - } - - .lg\:cu-my-12 { - margin-top: var(--canon-space-12); - margin-bottom: var(--canon-space-12); - } - - .lg\:cu-my-13 { - margin-top: var(--canon-space-13); - margin-bottom: var(--canon-space-13); - } - - .lg\:cu-my-14 { - margin-top: var(--canon-space-14); - margin-bottom: var(--canon-space-14); - } -} - -@media (width >= 1536px) { - .xl\:cu-my { - margin-top: var(--py-xl); - margin-bottom: var(--py-xl); - } - - .xl\:cu-my-0\.5 { - margin-top: var(--canon-space-0_5); - margin-bottom: var(--canon-space-0_5); - } - - .xl\:cu-my-1 { - margin-top: var(--canon-space-1); - margin-bottom: var(--canon-space-1); - } - - .xl\:cu-my-1\.5 { - margin-top: var(--canon-space-1_5); - margin-bottom: var(--canon-space-1_5); - } - - .xl\:cu-my-2 { - margin-top: var(--canon-space-2); - margin-bottom: var(--canon-space-2); - } - - .xl\:cu-my-3 { - margin-top: var(--canon-space-3); - margin-bottom: var(--canon-space-3); - } - - .xl\:cu-my-4 { - margin-top: var(--canon-space-4); - margin-bottom: var(--canon-space-4); - } - - .xl\:cu-my-5 { - margin-top: var(--canon-space-5); - margin-bottom: var(--canon-space-5); - } - - .xl\:cu-my-6 { - margin-top: var(--canon-space-6); - margin-bottom: var(--canon-space-6); - } - - .xl\:cu-my-7 { - margin-top: var(--canon-space-7); - margin-bottom: var(--canon-space-7); - } - - .xl\:cu-my-8 { - margin-top: var(--canon-space-8); - margin-bottom: var(--canon-space-8); - } - - .xl\:cu-my-9 { - margin-top: var(--canon-space-9); - margin-bottom: var(--canon-space-9); - } - - .xl\:cu-my-10 { - margin-top: var(--canon-space-10); - margin-bottom: var(--canon-space-10); - } - - .xl\:cu-my-11 { - margin-top: var(--canon-space-11); - margin-bottom: var(--canon-space-11); - } - - .xl\:cu-my-12 { - margin-top: var(--canon-space-12); - margin-bottom: var(--canon-space-12); - } - - .xl\:cu-my-13 { - margin-top: var(--canon-space-13); - margin-bottom: var(--canon-space-13); - } - - .xl\:cu-my-14 { - margin-top: var(--canon-space-14); - margin-bottom: var(--canon-space-14); - } -} - -.cu-mx { - margin-left: var(--mx); - margin-right: var(--mx); -} - -.cu-mx-0\.5 { - margin-left: var(--canon-space-0_5); - margin-right: var(--canon-space-0_5); -} - -.cu-mx-1 { - margin-left: var(--canon-space-1); - margin-right: var(--canon-space-1); -} - -.cu-mx-1\.5 { - margin-left: var(--canon-space-1_5); - margin-right: var(--canon-space-1_5); -} - -.cu-mx-2 { - margin-left: var(--canon-space-2); - margin-right: var(--canon-space-2); -} - -.cu-mx-3 { - margin-left: var(--canon-space-3); - margin-right: var(--canon-space-3); -} - -.cu-mx-4 { - margin-left: var(--canon-space-4); - margin-right: var(--canon-space-4); -} - -.cu-mx-5 { - margin-left: var(--canon-space-5); - margin-right: var(--canon-space-5); -} - -.cu-mx-6 { - margin-left: var(--canon-space-6); - margin-right: var(--canon-space-6); -} - -.cu-mx-7 { - margin-left: var(--canon-space-7); - margin-right: var(--canon-space-7); -} - -.cu-mx-8 { - margin-left: var(--canon-space-8); - margin-right: var(--canon-space-8); -} - -.cu-mx-9 { - margin-left: var(--canon-space-9); - margin-right: var(--canon-space-9); -} - -.cu-mx-10 { - margin-left: var(--canon-space-10); - margin-right: var(--canon-space-10); -} - -.cu-mx-11 { - margin-left: var(--canon-space-11); - margin-right: var(--canon-space-11); -} - -.cu-mx-12 { - margin-left: var(--canon-space-12); - margin-right: var(--canon-space-12); -} - -.cu-mx-13 { - margin-left: var(--canon-space-13); - margin-right: var(--canon-space-13); -} - -.cu-mx-14 { - margin-left: var(--canon-space-14); - margin-right: var(--canon-space-14); -} - -@media (width >= 640px) { - .xs\:cu-mx { - margin-left: var(--px-xs); - margin-right: var(--px-xs); - } - - .xs\:cu-mx-0\.5 { - margin-left: var(--canon-space-0_5); - margin-right: var(--canon-space-0_5); - } - - .xs\:cu-mx-1 { - margin-left: var(--canon-space-1); - margin-right: var(--canon-space-1); - } - - .xs\:cu-mx-1\.5 { - margin-left: var(--canon-space-1_5); - margin-right: var(--canon-space-1_5); - } - - .xs\:cu-mx-2 { - margin-left: var(--canon-space-2); - margin-right: var(--canon-space-2); - } - - .xs\:cu-mx-3 { - margin-left: var(--canon-space-3); - margin-right: var(--canon-space-3); - } - - .xs\:cu-mx-4 { - margin-left: var(--canon-space-4); - margin-right: var(--canon-space-4); - } - - .xs\:cu-mx-5 { - margin-left: var(--canon-space-5); - margin-right: var(--canon-space-5); - } - - .xs\:cu-mx-6 { - margin-left: var(--canon-space-6); - margin-right: var(--canon-space-6); - } - - .xs\:cu-mx-7 { - margin-left: var(--canon-space-7); - margin-right: var(--canon-space-7); - } - - .xs\:cu-mx-8 { - margin-left: var(--canon-space-8); - margin-right: var(--canon-space-8); - } - - .xs\:cu-mx-9 { - margin-left: var(--canon-space-9); - margin-right: var(--canon-space-9); - } - - .xs\:cu-mx-10 { - margin-left: var(--canon-space-10); - margin-right: var(--canon-space-10); - } - - .xs\:cu-mx-11 { - margin-left: var(--canon-space-11); - margin-right: var(--canon-space-11); - } - - .xs\:cu-mx-12 { - margin-left: var(--canon-space-12); - margin-right: var(--canon-space-12); - } - - .xs\:cu-mx-13 { - margin-left: var(--canon-space-13); - margin-right: var(--canon-space-13); - } - - .xs\:cu-mx-14 { - margin-left: var(--canon-space-14); - margin-right: var(--canon-space-14); - } -} - -@media (width >= 768px) { - .sm\:cu-mx { - margin-left: var(--px-sm); - margin-right: var(--px-sm); - } - - .sm\:cu-mx-0\.5 { - margin-left: var(--canon-space-0_5); - margin-right: var(--canon-space-0_5); - } - - .sm\:cu-mx-1 { - margin-left: var(--canon-space-1); - margin-right: var(--canon-space-1); - } - - .sm\:cu-mx-1\.5 { - margin-left: var(--canon-space-1_5); - margin-right: var(--canon-space-1_5); - } - - .sm\:cu-mx-2 { - margin-left: var(--canon-space-2); - margin-right: var(--canon-space-2); - } - - .sm\:cu-mx-3 { - margin-left: var(--canon-space-3); - margin-right: var(--canon-space-3); - } - - .sm\:cu-mx-4 { - margin-left: var(--canon-space-4); - margin-right: var(--canon-space-4); - } - - .sm\:cu-mx-5 { - margin-left: var(--canon-space-5); - margin-right: var(--canon-space-5); - } - - .sm\:cu-mx-6 { - margin-left: var(--canon-space-6); - margin-right: var(--canon-space-6); - } - - .sm\:cu-mx-7 { - margin-left: var(--canon-space-7); - margin-right: var(--canon-space-7); - } - - .sm\:cu-mx-8 { - margin-left: var(--canon-space-8); - margin-right: var(--canon-space-8); - } - - .sm\:cu-mx-9 { - margin-left: var(--canon-space-9); - margin-right: var(--canon-space-9); - } - - .sm\:cu-mx-10 { - margin-left: var(--canon-space-10); - margin-right: var(--canon-space-10); - } - - .sm\:cu-mx-11 { - margin-left: var(--canon-space-11); - margin-right: var(--canon-space-11); - } - - .sm\:cu-mx-12 { - margin-left: var(--canon-space-12); - margin-right: var(--canon-space-12); - } - - .sm\:cu-mx-13 { - margin-left: var(--canon-space-13); - margin-right: var(--canon-space-13); - } - - .sm\:cu-mx-14 { - margin-left: var(--canon-space-14); - margin-right: var(--canon-space-14); - } -} - -@media (width >= 1024px) { - .md\:cu-mx { - margin-left: var(--px-md); - margin-right: var(--px-md); - } - - .md\:cu-mx-0\.5 { - margin-left: var(--canon-space-0_5); - margin-right: var(--canon-space-0_5); - } - - .md\:cu-mx-1 { - margin-left: var(--canon-space-1); - margin-right: var(--canon-space-1); - } - - .md\:cu-mx-1\.5 { - margin-left: var(--canon-space-1_5); - margin-right: var(--canon-space-1_5); - } - - .md\:cu-mx-2 { - margin-left: var(--canon-space-2); - margin-right: var(--canon-space-2); - } - - .md\:cu-mx-3 { - margin-left: var(--canon-space-3); - margin-right: var(--canon-space-3); - } - - .md\:cu-mx-4 { - margin-left: var(--canon-space-4); - margin-right: var(--canon-space-4); - } - - .md\:cu-mx-5 { - margin-left: var(--canon-space-5); - margin-right: var(--canon-space-5); - } - - .md\:cu-mx-6 { - margin-left: var(--canon-space-6); - margin-right: var(--canon-space-6); - } - - .md\:cu-mx-7 { - margin-left: var(--canon-space-7); - margin-right: var(--canon-space-7); - } - - .md\:cu-mx-8 { - margin-left: var(--canon-space-8); - margin-right: var(--canon-space-8); - } - - .md\:cu-mx-9 { - margin-left: var(--canon-space-9); - margin-right: var(--canon-space-9); - } - - .md\:cu-mx-10 { - margin-left: var(--canon-space-10); - margin-right: var(--canon-space-10); - } - - .md\:cu-mx-11 { - margin-left: var(--canon-space-11); - margin-right: var(--canon-space-11); - } - - .md\:cu-mx-12 { - margin-left: var(--canon-space-12); - margin-right: var(--canon-space-12); - } - - .md\:cu-mx-13 { - margin-left: var(--canon-space-13); - margin-right: var(--canon-space-13); - } - - .md\:cu-mx-14 { - margin-left: var(--canon-space-14); - margin-right: var(--canon-space-14); - } -} - -@media (width >= 1280px) { - .lg\:cu-mx { - margin-left: var(--px-lg); - margin-right: var(--px-lg); - } - - .lg\:cu-mx-0\.5 { - margin-left: var(--canon-space-0_5); - margin-right: var(--canon-space-0_5); - } - - .lg\:cu-mx-1 { - margin-left: var(--canon-space-1); - margin-right: var(--canon-space-1); - } - - .lg\:cu-mx-1\.5 { - margin-left: var(--canon-space-1_5); - margin-right: var(--canon-space-1_5); - } - - .lg\:cu-mx-2 { - margin-left: var(--canon-space-2); - margin-right: var(--canon-space-2); - } - - .lg\:cu-mx-3 { - margin-left: var(--canon-space-3); - margin-right: var(--canon-space-3); - } - - .lg\:cu-mx-4 { - margin-left: var(--canon-space-4); - margin-right: var(--canon-space-4); - } - - .lg\:cu-mx-5 { - margin-left: var(--canon-space-5); - margin-right: var(--canon-space-5); - } - - .lg\:cu-mx-6 { - margin-left: var(--canon-space-6); - margin-right: var(--canon-space-6); - } - - .lg\:cu-mx-7 { - margin-left: var(--canon-space-7); - margin-right: var(--canon-space-7); - } - - .lg\:cu-mx-8 { - margin-left: var(--canon-space-8); - margin-right: var(--canon-space-8); - } - - .lg\:cu-mx-9 { - margin-left: var(--canon-space-9); - margin-right: var(--canon-space-9); - } - - .lg\:cu-mx-10 { - margin-left: var(--canon-space-10); - margin-right: var(--canon-space-10); - } - - .lg\:cu-mx-11 { - margin-left: var(--canon-space-11); - margin-right: var(--canon-space-11); - } - - .lg\:cu-mx-12 { - margin-left: var(--canon-space-12); - margin-right: var(--canon-space-12); - } - - .lg\:cu-mx-13 { - margin-left: var(--canon-space-13); - margin-right: var(--canon-space-13); - } - - .lg\:cu-mx-14 { - margin-left: var(--canon-space-14); - margin-right: var(--canon-space-14); - } -} - -@media (width >= 1536px) { - .xl\:cu-mx { - margin-left: var(--px-xl); - margin-right: var(--px-xl); - } - - .xl\:cu-mx-0\.5 { - margin-left: var(--canon-space-0_5); - margin-right: var(--canon-space-0_5); - } - - .xl\:cu-mx-1 { - margin-left: var(--canon-space-1); - margin-right: var(--canon-space-1); - } - - .xl\:cu-mx-1\.5 { - margin-left: var(--canon-space-1_5); - margin-right: var(--canon-space-1_5); - } - - .xl\:cu-mx-2 { - margin-left: var(--canon-space-2); - margin-right: var(--canon-space-2); - } - - .xl\:cu-mx-3 { - margin-left: var(--canon-space-3); - margin-right: var(--canon-space-3); - } - - .xl\:cu-mx-4 { - margin-left: var(--canon-space-4); - margin-right: var(--canon-space-4); - } - - .xl\:cu-mx-5 { - margin-left: var(--canon-space-5); - margin-right: var(--canon-space-5); - } - - .xl\:cu-mx-6 { - margin-left: var(--canon-space-6); - margin-right: var(--canon-space-6); - } - - .xl\:cu-mx-7 { - margin-left: var(--canon-space-7); - margin-right: var(--canon-space-7); - } - - .xl\:cu-mx-8 { - margin-left: var(--canon-space-8); - margin-right: var(--canon-space-8); - } - - .xl\:cu-mx-9 { - margin-left: var(--canon-space-9); - margin-right: var(--canon-space-9); - } - - .xl\:cu-mx-10 { - margin-left: var(--canon-space-10); - margin-right: var(--canon-space-10); - } - - .xl\:cu-mx-11 { - margin-left: var(--canon-space-11); - margin-right: var(--canon-space-11); - } - - .xl\:cu-mx-12 { - margin-left: var(--canon-space-12); - margin-right: var(--canon-space-12); - } - - .xl\:cu-mx-13 { - margin-left: var(--canon-space-13); - margin-right: var(--canon-space-13); - } - - .xl\:cu-mx-14 { - margin-left: var(--canon-space-14); - margin-right: var(--canon-space-14); - } -} - -.cu-display-none { - display: none; -} - -.cu-display-inline { - display: inline; -} - -.cu-display-inline-block { - display: inline-block; -} - -.cu-display-block { - display: block; -} - -@media (width >= 640px) { - .xs\:cu-display-none { - display: none; - } - - .xs\:cu-display-inline { - display: inline; - } - - .xs\:cu-display-inline-block { - display: inline-block; - } - - .xs\:cu-display-block { - display: block; - } -} - -@media (width >= 768px) { - .sm\:cu-display-none { - display: none; - } - - .sm\:cu-display-inline { - display: inline; - } - - .sm\:cu-display-inline-block { - display: inline-block; - } - - .sm\:cu-display-block { - display: block; - } -} - -@media (width >= 1024px) { - .md\:cu-display-none { - display: none; - } - - .md\:cu-display-inline { - display: inline; - } - - .md\:cu-display-inline-block { - display: inline-block; - } - - .md\:cu-display-block { - display: block; - } -} - -@media (width >= 1280px) { - .lg\:cu-display-none { - display: none; - } - - .lg\:cu-display-inline { - display: inline; - } - - .lg\:cu-display-inline-block { - display: inline-block; - } - - .lg\:cu-display-block { - display: block; - } -} - -@media (width >= 1536px) { - .xl\:cu-display-none { - display: none; - } - - .xl\:cu-display-inline { - display: inline; - } - - .xl\:cu-display-inline-block { - display: inline-block; - } - - .xl\:cu-display-block { - display: block; - } -} - -.cu-w { - width: var(--width); -} - -.cu-min-w { - min-width: var(--min-width); -} - -.cu-max-w { - max-width: var(--max-width); -} - -@media (width >= 640px) { - .xs\:cu-w { - width: var(--width); - } - - .xs\:cu-min-w { - min-width: var(--min-width); - } - - .xs\:cu-max-w { - max-width: var(--max-width); - } -} - -@media (width >= 768px) { - .sm\:cu-w { - width: var(--width); - } - - .sm\:cu-min-w { - min-width: var(--min-width); - } - - .sm\:cu-max-w { - max-width: var(--max-width); - } -} - -@media (width >= 1024px) { - .md\:cu-w { - width: var(--width); - } - - .md\:cu-min-w { - min-width: var(--min-width); - } - - .md\:cu-max-w { - max-width: var(--max-width); - } -} - -@media (width >= 1280px) { - .lg\:cu-w { - width: var(--width); - } - - .lg\:cu-min-w { - min-width: var(--min-width); - } - - .lg\:cu-max-w { - max-width: var(--max-width); - } -} - -@media (width >= 1536px) { - .xl\:cu-w { - width: var(--width); - } - - .xl\:cu-min-w { - min-width: var(--min-width); - } - - .xl\:cu-max-w { - max-width: var(--max-width); - } -} - -.cu-h { - height: var(--height); -} - -.cu-min-h { - min-height: var(--min-height); -} - -.cu-max-h { - max-height: var(--max-height); -} - -@media (width >= 640px) { - .xs\:cu-h { - height: var(--height); - } - - .xs\:cu-min-h { - min-height: var(--min-height); - } - - .xs\:cu-max-h { - max-height: var(--max-height); - } -} - -@media (width >= 768px) { - .sm\:cu-h { - height: var(--height); - } - - .sm\:cu-min-h { - min-height: var(--min-height); - } - - .sm\:cu-max-h { - max-height: var(--max-height); - } -} - -@media (width >= 1024px) { - .md\:cu-h { - height: var(--height); - } - - .md\:cu-min-h { - min-height: var(--min-height); - } - - .md\:cu-max-h { - max-height: var(--max-height); - } -} - -@media (width >= 1280px) { - .lg\:cu-h { - height: var(--height); - } - - .lg\:cu-min-h { - min-height: var(--min-height); - } - - .lg\:cu-max-h { - max-height: var(--max-height); - } -} - -@media (width >= 1536px) { - .xl\:cu-h { - height: var(--height); - } - - .xl\:cu-min-h { - min-height: var(--min-height); - } - - .xl\:cu-max-h { - max-height: var(--max-height); - } -} - -.cu-position-absolute { - position: absolute; -} - -.cu-position-fixed { - position: fixed; -} - -.cu-position-sticky { - position: sticky; -} - -.cu-position-relative { - position: relative; -} - -.cu-position-static { - position: static; -} - -@media (width >= 640px) { - .xs\:cu-position-absolute { - position: absolute; - } - - .xs\:cu-position-fixed { - position: fixed; - } - - .xs\:cu-position-sticky { - position: sticky; - } - - .xs\:cu-position-relative { - position: relative; - } - - .xs\:cu-position-static { - position: static; - } -} - -@media (width >= 768px) { - .sm\:cu-position-absolute { - position: absolute; - } - - .sm\:cu-position-fixed { - position: fixed; - } - - .sm\:cu-position-sticky { - position: sticky; - } - - .sm\:cu-position-relative { - position: relative; - } - - .sm\:cu-position-static { - position: static; - } -} - -@media (width >= 1024px) { - .md\:cu-position-absolute { - position: absolute; - } - - .md\:cu-position-fixed { - position: fixed; - } - - .md\:cu-position-sticky { - position: sticky; - } - - .md\:cu-position-relative { - position: relative; - } - - .md\:cu-position-static { - position: static; - } -} - -@media (width >= 1280px) { - .lg\:cu-position-absolute { - position: absolute; - } - - .lg\:cu-position-fixed { - position: fixed; - } - - .lg\:cu-position-sticky { - position: sticky; - } - - .lg\:cu-position-relative { - position: relative; - } - - .lg\:cu-position-static { - position: static; - } -} - -@media (width >= 1536px) { - .xl\:cu-position-absolute { - position: absolute; - } - - .xl\:cu-position-fixed { - position: fixed; - } - - .xl\:cu-position-sticky { - position: sticky; - } - - .xl\:cu-position-relative { - position: relative; - } - - .xl\:cu-position-static { - position: static; - } -} - -.cu-columns-1 { - grid-template-columns: repeat(1, minmax(0, 1fr)); -} - -.cu-columns-2 { - grid-template-columns: repeat(2, minmax(0, 1fr)); -} - -.cu-columns-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); -} - -.cu-columns-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)); -} - -.cu-columns-5 { - grid-template-columns: repeat(5, minmax(0, 1fr)); -} - -.cu-columns-6 { - grid-template-columns: repeat(6, minmax(0, 1fr)); -} - -.cu-columns-7 { - grid-template-columns: repeat(7, minmax(0, 1fr)); -} - -.cu-columns-8 { - grid-template-columns: repeat(8, minmax(0, 1fr)); -} - -.cu-columns-9 { - grid-template-columns: repeat(9, minmax(0, 1fr)); -} - -.cu-columns-10 { - grid-template-columns: repeat(10, minmax(0, 1fr)); -} - -.cu-columns-11 { - grid-template-columns: repeat(11, minmax(0, 1fr)); -} - -.cu-columns-12 { - grid-template-columns: repeat(12, minmax(0, 1fr)); -} - -.cu-columns-auto { - grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); -} - -.cu-col-span-1 { - grid-column: span 1 / span 1; -} - -.cu-col-span-2 { - grid-column: span 2 / span 2; -} - -.cu-col-span-3 { - grid-column: span 3 / span 3; -} - -.cu-col-span-4 { - grid-column: span 4 / span 4; -} - -.cu-col-span-5 { - grid-column: span 5 / span 5; -} - -.cu-col-span-6 { - grid-column: span 6 / span 6; -} - -.cu-col-span-7 { - grid-column: span 7 / span 7; -} - -.cu-col-span-8 { - grid-column: span 8 / span 8; -} - -.cu-col-span-9 { - grid-column: span 9 / span 9; -} - -.cu-col-span-10 { - grid-column: span 10 / span 10; -} - -.cu-col-span-11 { - grid-column: span 11 / span 11; -} - -.cu-col-span-12 { - grid-column: span 12 / span 12; -} - -.cu-col-span-auto { - grid-column: span auto / span auto; -} - -.cu-col-start-1 { - grid-column-start: 1; -} - -.cu-col-start-2 { - grid-column-start: 2; -} - -.cu-col-start-3 { - grid-column-start: 3; -} - -.cu-col-start-4 { - grid-column-start: 4; -} - -.cu-col-start-5 { - grid-column-start: 5; -} - -.cu-col-start-6 { - grid-column-start: 6; -} - -.cu-col-start-7 { - grid-column-start: 7; -} - -.cu-col-start-8 { - grid-column-start: 8; -} - -.cu-col-start-9 { - grid-column-start: 9; -} - -.cu-col-start-10 { - grid-column-start: 10; -} - -.cu-col-start-11 { - grid-column-start: 11; -} - -.cu-col-start-12 { - grid-column-start: 12; -} - -.cu-col-start-13 { - grid-column-start: 13; -} - -.cu-col-start-auto { - grid-column-start: auto; -} - -.cu-col-end-1 { - grid-column-end: 1; -} - -.cu-col-end-2 { - grid-column-end: 2; -} - -.cu-col-end-3 { - grid-column-end: 3; -} - -.cu-col-end-4 { - grid-column-end: 4; -} - -.cu-col-end-5 { - grid-column-end: 5; -} - -.cu-col-end-6 { - grid-column-end: 6; -} - -.cu-col-end-7 { - grid-column-end: 7; -} - -.cu-col-end-8 { - grid-column-end: 8; -} - -.cu-col-end-9 { - grid-column-end: 9; -} - -.cu-col-end-10 { - grid-column-end: 10; -} - -.cu-col-end-11 { - grid-column-end: 11; -} - -.cu-col-end-12 { - grid-column-end: 12; -} - -.cu-col-end-13 { - grid-column-end: 13; -} - -.cu-col-end-auto { - grid-column-end: auto; -} - -.cu-row-span-1 { - grid-row: span 1 / span 1; -} - -.cu-row-span-2 { - grid-row: span 2 / span 2; -} - -.cu-row-span-3 { - grid-row: span 3 / span 3; -} - -.cu-row-span-4 { - grid-row: span 4 / span 4; -} - -.cu-row-span-5 { - grid-row: span 5 / span 5; -} - -.cu-row-span-6 { - grid-row: span 6 / span 6; -} - -.cu-row-span-7 { - grid-row: span 7 / span 7; -} - -.cu-row-span-8 { - grid-row: span 8 / span 8; -} - -.cu-row-span-9 { - grid-row: span 9 / span 9; -} - -.cu-row-span-10 { - grid-row: span 10 / span 10; -} - -.cu-row-span-11 { - grid-row: span 11 / span 11; -} - -.cu-row-span-12 { - grid-row: span 12 / span 12; -} - -.cu-row-span-auto { - grid-row: span auto / span auto; -} - -@media (width >= 640px) { - .xs\:cu-columns-1 { - grid-template-columns: repeat(1, minmax(0, 1fr)); - } - - .xs\:cu-columns-2 { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .xs\:cu-columns-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - - .xs\:cu-columns-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)); - } - - .xs\:cu-columns-5 { - grid-template-columns: repeat(5, minmax(0, 1fr)); - } - - .xs\:cu-columns-6 { - grid-template-columns: repeat(6, minmax(0, 1fr)); - } - - .xs\:cu-columns-7 { - grid-template-columns: repeat(7, minmax(0, 1fr)); - } - - .xs\:cu-columns-8 { - grid-template-columns: repeat(8, minmax(0, 1fr)); - } - - .xs\:cu-columns-9 { - grid-template-columns: repeat(9, minmax(0, 1fr)); - } - - .xs\:cu-columns-10 { - grid-template-columns: repeat(10, minmax(0, 1fr)); - } - - .xs\:cu-columns-11 { - grid-template-columns: repeat(11, minmax(0, 1fr)); - } - - .xs\:cu-columns-12 { - grid-template-columns: repeat(12, minmax(0, 1fr)); - } - - .xs\:cu-columns-auto { - grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); - } - - .xs\:cu-col-span-1 { - grid-column: span 1 / span 1; - } - - .xs\:cu-col-span-2 { - grid-column: span 2 / span 2; - } - - .xs\:cu-col-span-3 { - grid-column: span 3 / span 3; - } - - .xs\:cu-col-span-4 { - grid-column: span 4 / span 4; - } - - .xs\:cu-col-span-5 { - grid-column: span 5 / span 5; - } - - .xs\:cu-col-span-6 { - grid-column: span 6 / span 6; - } - - .xs\:cu-col-span-7 { - grid-column: span 7 / span 7; - } - - .xs\:cu-col-span-8 { - grid-column: span 8 / span 8; - } - - .xs\:cu-col-span-9 { - grid-column: span 9 / span 9; - } - - .xs\:cu-col-span-10 { - grid-column: span 10 / span 10; - } - - .xs\:cu-col-span-11 { - grid-column: span 11 / span 11; - } - - .xs\:cu-col-span-12 { - grid-column: span 12 / span 12; - } - - .xs\:cu-col-span-auto { - grid-column: span auto / span auto; - } - - .xs\:cu-col-start-1 { - grid-column-start: 1; - } - - .xs\:cu-col-start-2 { - grid-column-start: 2; - } - - .xs\:cu-col-start-3 { - grid-column-start: 3; - } - - .xs\:cu-col-start-4 { - grid-column-start: 4; - } - - .xs\:cu-col-start-5 { - grid-column-start: 5; - } - - .xs\:cu-col-start-6 { - grid-column-start: 6; - } - - .xs\:cu-col-start-7 { - grid-column-start: 7; - } - - .xs\:cu-col-start-8 { - grid-column-start: 8; - } - - .xs\:cu-col-start-9 { - grid-column-start: 9; - } - - .xs\:cu-col-start-10 { - grid-column-start: 10; - } - - .xs\:cu-col-start-11 { - grid-column-start: 11; - } - - .xs\:cu-col-start-12 { - grid-column-start: 12; - } - - .xs\:cu-col-start-13 { - grid-column-start: 13; - } - - .xs\:cu-col-start-auto { - grid-column-start: auto; - } - - .xs\:cu-col-end-1 { - grid-column-end: 1; - } - - .xs\:cu-col-end-2 { - grid-column-end: 2; - } - - .xs\:cu-col-end-3 { - grid-column-end: 3; - } - - .xs\:cu-col-end-4 { - grid-column-end: 4; - } - - .xs\:cu-col-end-5 { - grid-column-end: 5; - } - - .xs\:cu-col-end-6 { - grid-column-end: 6; - } - - .xs\:cu-col-end-7 { - grid-column-end: 7; - } - - .xs\:cu-col-end-8 { - grid-column-end: 8; - } - - .xs\:cu-col-end-9 { - grid-column-end: 9; - } - - .xs\:cu-col-end-10 { - grid-column-end: 10; - } - - .xs\:cu-col-end-11 { - grid-column-end: 11; - } - - .xs\:cu-col-end-12 { - grid-column-end: 12; - } - - .xs\:cu-col-end-13 { - grid-column-end: 13; - } - - .xs\:cu-col-end-auto { - grid-column-end: auto; - } - - .xs\:cu-row-span-1 { - grid-row: span 1 / span 1; - } - - .xs\:cu-row-span-2 { - grid-row: span 2 / span 2; - } - - .xs\:cu-row-span-3 { - grid-row: span 3 / span 3; - } - - .xs\:cu-row-span-4 { - grid-row: span 4 / span 4; - } - - .xs\:cu-row-span-5 { - grid-row: span 5 / span 5; - } - - .xs\:cu-row-span-6 { - grid-row: span 6 / span 6; - } - - .xs\:cu-row-span-7 { - grid-row: span 7 / span 7; - } - - .xs\:cu-row-span-8 { - grid-row: span 8 / span 8; - } - - .xs\:cu-row-span-9 { - grid-row: span 9 / span 9; - } - - .xs\:cu-row-span-10 { - grid-row: span 10 / span 10; - } - - .xs\:cu-row-span-11 { - grid-row: span 11 / span 11; - } - - .xs\:cu-row-span-12 { - grid-row: span 12 / span 12; - } - - .xs\:cu-row-span-auto { - grid-row: span auto / span auto; - } -} - -@media (width >= 768px) { - .sm\:cu-columns-1 { - grid-template-columns: repeat(1, minmax(0, 1fr)); - } - - .sm\:cu-columns-2 { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .sm\:cu-columns-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - - .sm\:cu-columns-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)); - } - - .sm\:cu-columns-5 { - grid-template-columns: repeat(5, minmax(0, 1fr)); - } - - .sm\:cu-columns-6 { - grid-template-columns: repeat(6, minmax(0, 1fr)); - } - - .sm\:cu-columns-7 { - grid-template-columns: repeat(7, minmax(0, 1fr)); - } - - .sm\:cu-columns-8 { - grid-template-columns: repeat(8, minmax(0, 1fr)); - } - - .sm\:cu-columns-9 { - grid-template-columns: repeat(9, minmax(0, 1fr)); - } - - .sm\:cu-columns-10 { - grid-template-columns: repeat(10, minmax(0, 1fr)); - } - - .sm\:cu-columns-11 { - grid-template-columns: repeat(11, minmax(0, 1fr)); - } - - .sm\:cu-columns-12 { - grid-template-columns: repeat(12, minmax(0, 1fr)); - } - - .sm\:cu-columns-auto { - grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); - } - - .sm\:cu-col-span-1 { - grid-column: span 1 / span 1; - } - - .sm\:cu-col-span-2 { - grid-column: span 2 / span 2; - } - - .sm\:cu-col-span-3 { - grid-column: span 3 / span 3; - } - - .sm\:cu-col-span-4 { - grid-column: span 4 / span 4; - } - - .sm\:cu-col-span-5 { - grid-column: span 5 / span 5; - } - - .sm\:cu-col-span-6 { - grid-column: span 6 / span 6; - } - - .sm\:cu-col-span-7 { - grid-column: span 7 / span 7; - } - - .sm\:cu-col-span-8 { - grid-column: span 8 / span 8; - } - - .sm\:cu-col-span-9 { - grid-column: span 9 / span 9; - } - - .sm\:cu-col-span-10 { - grid-column: span 10 / span 10; - } - - .sm\:cu-col-span-11 { - grid-column: span 11 / span 11; - } - - .sm\:cu-col-span-12 { - grid-column: span 12 / span 12; - } - - .sm\:cu-col-span-auto { - grid-column: span auto / span auto; - } - - .sm\:cu-col-start-1 { - grid-column-start: 1; - } - - .sm\:cu-col-start-2 { - grid-column-start: 2; - } - - .sm\:cu-col-start-3 { - grid-column-start: 3; - } - - .sm\:cu-col-start-4 { - grid-column-start: 4; - } - - .sm\:cu-col-start-5 { - grid-column-start: 5; - } - - .sm\:cu-col-start-6 { - grid-column-start: 6; - } - - .sm\:cu-col-start-7 { - grid-column-start: 7; - } - - .sm\:cu-col-start-8 { - grid-column-start: 8; - } - - .sm\:cu-col-start-9 { - grid-column-start: 9; - } - - .sm\:cu-col-start-10 { - grid-column-start: 10; - } - - .sm\:cu-col-start-11 { - grid-column-start: 11; - } - - .sm\:cu-col-start-12 { - grid-column-start: 12; - } - - .sm\:cu-col-start-13 { - grid-column-start: 13; - } - - .sm\:cu-col-start-auto { - grid-column-start: auto; - } - - .sm\:cu-col-end-1 { - grid-column-end: 1; - } - - .sm\:cu-col-end-2 { - grid-column-end: 2; - } - - .sm\:cu-col-end-3 { - grid-column-end: 3; - } - - .sm\:cu-col-end-4 { - grid-column-end: 4; - } - - .sm\:cu-col-end-5 { - grid-column-end: 5; - } - - .sm\:cu-col-end-6 { - grid-column-end: 6; - } - - .sm\:cu-col-end-7 { - grid-column-end: 7; - } - - .sm\:cu-col-end-8 { - grid-column-end: 8; - } - - .sm\:cu-col-end-9 { - grid-column-end: 9; - } - - .sm\:cu-col-end-10 { - grid-column-end: 10; - } - - .sm\:cu-col-end-11 { - grid-column-end: 11; - } - - .sm\:cu-col-end-12 { - grid-column-end: 12; - } - - .sm\:cu-col-end-13 { - grid-column-end: 13; - } - - .sm\:cu-col-end-auto { - grid-column-end: auto; - } - - .sm\:cu-row-span-1 { - grid-row: span 1 / span 1; - } - - .sm\:cu-row-span-2 { - grid-row: span 2 / span 2; - } - - .sm\:cu-row-span-3 { - grid-row: span 3 / span 3; - } - - .sm\:cu-row-span-4 { - grid-row: span 4 / span 4; - } - - .sm\:cu-row-span-5 { - grid-row: span 5 / span 5; - } - - .sm\:cu-row-span-6 { - grid-row: span 6 / span 6; - } - - .sm\:cu-row-span-7 { - grid-row: span 7 / span 7; - } - - .sm\:cu-row-span-8 { - grid-row: span 8 / span 8; - } - - .sm\:cu-row-span-9 { - grid-row: span 9 / span 9; - } - - .sm\:cu-row-span-10 { - grid-row: span 10 / span 10; - } - - .sm\:cu-row-span-11 { - grid-row: span 11 / span 11; - } - - .sm\:cu-row-span-12 { - grid-row: span 12 / span 12; - } - - .sm\:cu-row-span-auto { - grid-row: span auto / span auto; - } -} - -@media (width >= 1024px) { - .md\:cu-columns-1 { - grid-template-columns: repeat(1, minmax(0, 1fr)); - } - - .md\:cu-columns-2 { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .md\:cu-columns-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - - .md\:cu-columns-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)); - } - - .md\:cu-columns-5 { - grid-template-columns: repeat(5, minmax(0, 1fr)); - } - - .md\:cu-columns-6 { - grid-template-columns: repeat(6, minmax(0, 1fr)); - } - - .md\:cu-columns-7 { - grid-template-columns: repeat(7, minmax(0, 1fr)); - } - - .md\:cu-columns-8 { - grid-template-columns: repeat(8, minmax(0, 1fr)); - } - - .md\:cu-columns-9 { - grid-template-columns: repeat(9, minmax(0, 1fr)); - } - - .md\:cu-columns-10 { - grid-template-columns: repeat(10, minmax(0, 1fr)); - } - - .md\:cu-columns-11 { - grid-template-columns: repeat(11, minmax(0, 1fr)); - } - - .md\:cu-columns-12 { - grid-template-columns: repeat(12, minmax(0, 1fr)); - } - - .md\:cu-columns-auto { - grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); - } - - .md\:cu-col-span-1 { - grid-column: span 1 / span 1; - } - - .md\:cu-col-span-2 { - grid-column: span 2 / span 2; - } - - .md\:cu-col-span-3 { - grid-column: span 3 / span 3; - } - - .md\:cu-col-span-4 { - grid-column: span 4 / span 4; - } - - .md\:cu-col-span-5 { - grid-column: span 5 / span 5; - } - - .md\:cu-col-span-6 { - grid-column: span 6 / span 6; - } - - .md\:cu-col-span-7 { - grid-column: span 7 / span 7; - } - - .md\:cu-col-span-8 { - grid-column: span 8 / span 8; - } - - .md\:cu-col-span-9 { - grid-column: span 9 / span 9; - } - - .md\:cu-col-span-10 { - grid-column: span 10 / span 10; - } - - .md\:cu-col-span-11 { - grid-column: span 11 / span 11; - } - - .md\:cu-col-span-12 { - grid-column: span 12 / span 12; - } - - .md\:cu-col-span-auto { - grid-column: span auto / span auto; - } - - .md\:cu-col-start-1 { - grid-column-start: 1; - } - - .md\:cu-col-start-2 { - grid-column-start: 2; - } - - .md\:cu-col-start-3 { - grid-column-start: 3; - } - - .md\:cu-col-start-4 { - grid-column-start: 4; - } - - .md\:cu-col-start-5 { - grid-column-start: 5; - } - - .md\:cu-col-start-6 { - grid-column-start: 6; - } - - .md\:cu-col-start-7 { - grid-column-start: 7; - } - - .md\:cu-col-start-8 { - grid-column-start: 8; - } - - .md\:cu-col-start-9 { - grid-column-start: 9; - } - - .md\:cu-col-start-10 { - grid-column-start: 10; - } - - .md\:cu-col-start-11 { - grid-column-start: 11; - } - - .md\:cu-col-start-12 { - grid-column-start: 12; - } - - .md\:cu-col-start-13 { - grid-column-start: 13; - } - - .md\:cu-col-start-auto { - grid-column-start: auto; - } - - .md\:cu-col-end-1 { - grid-column-end: 1; - } - - .md\:cu-col-end-2 { - grid-column-end: 2; - } - - .md\:cu-col-end-3 { - grid-column-end: 3; - } - - .md\:cu-col-end-4 { - grid-column-end: 4; - } - - .md\:cu-col-end-5 { - grid-column-end: 5; - } - - .md\:cu-col-end-6 { - grid-column-end: 6; - } - - .md\:cu-col-end-7 { - grid-column-end: 7; - } - - .md\:cu-col-end-8 { - grid-column-end: 8; - } - - .md\:cu-col-end-9 { - grid-column-end: 9; - } - - .md\:cu-col-end-10 { - grid-column-end: 10; - } - - .md\:cu-col-end-11 { - grid-column-end: 11; - } - - .md\:cu-col-end-12 { - grid-column-end: 12; - } - - .md\:cu-col-end-13 { - grid-column-end: 13; - } - - .md\:cu-col-end-auto { - grid-column-end: auto; - } - - .md\:cu-row-span-1 { - grid-row: span 1 / span 1; - } - - .md\:cu-row-span-2 { - grid-row: span 2 / span 2; - } - - .md\:cu-row-span-3 { - grid-row: span 3 / span 3; - } - - .md\:cu-row-span-4 { - grid-row: span 4 / span 4; - } - - .md\:cu-row-span-5 { - grid-row: span 5 / span 5; - } - - .md\:cu-row-span-6 { - grid-row: span 6 / span 6; - } - - .md\:cu-row-span-7 { - grid-row: span 7 / span 7; - } - - .md\:cu-row-span-8 { - grid-row: span 8 / span 8; - } - - .md\:cu-row-span-9 { - grid-row: span 9 / span 9; - } - - .md\:cu-row-span-10 { - grid-row: span 10 / span 10; - } - - .md\:cu-row-span-11 { - grid-row: span 11 / span 11; - } - - .md\:cu-row-span-12 { - grid-row: span 12 / span 12; - } - - .md\:cu-row-span-auto { - grid-row: span auto / span auto; - } -} - -@media (width >= 1280px) { - .lg\:cu-columns-1 { - grid-template-columns: repeat(1, minmax(0, 1fr)); - } - - .lg\:cu-columns-2 { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .lg\:cu-columns-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - - .lg\:cu-columns-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)); - } - - .lg\:cu-columns-5 { - grid-template-columns: repeat(5, minmax(0, 1fr)); - } - - .lg\:cu-columns-6 { - grid-template-columns: repeat(6, minmax(0, 1fr)); - } - - .lg\:cu-columns-7 { - grid-template-columns: repeat(7, minmax(0, 1fr)); - } - - .lg\:cu-columns-8 { - grid-template-columns: repeat(8, minmax(0, 1fr)); - } - - .lg\:cu-columns-9 { - grid-template-columns: repeat(9, minmax(0, 1fr)); - } - - .lg\:cu-columns-10 { - grid-template-columns: repeat(10, minmax(0, 1fr)); - } - - .lg\:cu-columns-11 { - grid-template-columns: repeat(11, minmax(0, 1fr)); - } - - .lg\:cu-columns-12 { - grid-template-columns: repeat(12, minmax(0, 1fr)); - } - - .lg\:cu-columns-auto { - grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); - } - - .lg\:cu-col-span-1 { - grid-column: span 1 / span 1; - } - - .lg\:cu-col-span-2 { - grid-column: span 2 / span 2; - } - - .lg\:cu-col-span-3 { - grid-column: span 3 / span 3; - } - - .lg\:cu-col-span-4 { - grid-column: span 4 / span 4; - } - - .lg\:cu-col-span-5 { - grid-column: span 5 / span 5; - } - - .lg\:cu-col-span-6 { - grid-column: span 6 / span 6; - } - - .lg\:cu-col-span-7 { - grid-column: span 7 / span 7; - } - - .lg\:cu-col-span-8 { - grid-column: span 8 / span 8; - } - - .lg\:cu-col-span-9 { - grid-column: span 9 / span 9; - } - - .lg\:cu-col-span-10 { - grid-column: span 10 / span 10; - } - - .lg\:cu-col-span-11 { - grid-column: span 11 / span 11; - } - - .lg\:cu-col-span-12 { - grid-column: span 12 / span 12; - } - - .lg\:cu-col-span-auto { - grid-column: span auto / span auto; - } - - .lg\:cu-col-start-1 { - grid-column-start: 1; - } - - .lg\:cu-col-start-2 { - grid-column-start: 2; - } - - .lg\:cu-col-start-3 { - grid-column-start: 3; - } - - .lg\:cu-col-start-4 { - grid-column-start: 4; - } - - .lg\:cu-col-start-5 { - grid-column-start: 5; - } - - .lg\:cu-col-start-6 { - grid-column-start: 6; - } - - .lg\:cu-col-start-7 { - grid-column-start: 7; - } - - .lg\:cu-col-start-8 { - grid-column-start: 8; - } - - .lg\:cu-col-start-9 { - grid-column-start: 9; - } - - .lg\:cu-col-start-10 { - grid-column-start: 10; - } - - .lg\:cu-col-start-11 { - grid-column-start: 11; - } - - .lg\:cu-col-start-12 { - grid-column-start: 12; - } - - .lg\:cu-col-start-13 { - grid-column-start: 13; - } - - .lg\:cu-col-start-auto { - grid-column-start: auto; - } - - .lg\:cu-col-end-1 { - grid-column-end: 1; - } - - .lg\:cu-col-end-2 { - grid-column-end: 2; - } - - .lg\:cu-col-end-3 { - grid-column-end: 3; - } - - .lg\:cu-col-end-4 { - grid-column-end: 4; - } - - .lg\:cu-col-end-5 { - grid-column-end: 5; - } - - .lg\:cu-col-end-6 { - grid-column-end: 6; - } - - .lg\:cu-col-end-7 { - grid-column-end: 7; - } - - .lg\:cu-col-end-8 { - grid-column-end: 8; - } - - .lg\:cu-col-end-9 { - grid-column-end: 9; - } - - .lg\:cu-col-end-10 { - grid-column-end: 10; - } - - .lg\:cu-col-end-11 { - grid-column-end: 11; - } - - .lg\:cu-col-end-12 { - grid-column-end: 12; - } - - .lg\:cu-col-end-13 { - grid-column-end: 13; - } - - .lg\:cu-col-end-auto { - grid-column-end: auto; - } - - .lg\:cu-row-span-1 { - grid-row: span 1 / span 1; - } - - .lg\:cu-row-span-2 { - grid-row: span 2 / span 2; - } - - .lg\:cu-row-span-3 { - grid-row: span 3 / span 3; - } - - .lg\:cu-row-span-4 { - grid-row: span 4 / span 4; - } - - .lg\:cu-row-span-5 { - grid-row: span 5 / span 5; - } - - .lg\:cu-row-span-6 { - grid-row: span 6 / span 6; - } - - .lg\:cu-row-span-7 { - grid-row: span 7 / span 7; - } - - .lg\:cu-row-span-8 { - grid-row: span 8 / span 8; - } - - .lg\:cu-row-span-9 { - grid-row: span 9 / span 9; - } - - .lg\:cu-row-span-10 { - grid-row: span 10 / span 10; - } - - .lg\:cu-row-span-11 { - grid-row: span 11 / span 11; - } - - .lg\:cu-row-span-12 { - grid-row: span 12 / span 12; - } - - .lg\:cu-row-span-auto { - grid-row: span auto / span auto; - } -} - -@media (width >= 1536px) { - .xl\:cu-columns-1 { - grid-template-columns: repeat(1, minmax(0, 1fr)); - } - - .xl\:cu-columns-2 { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .xl\:cu-columns-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - - .xl\:cu-columns-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)); - } - - .xl\:cu-columns-5 { - grid-template-columns: repeat(5, minmax(0, 1fr)); - } - - .xl\:cu-columns-6 { - grid-template-columns: repeat(6, minmax(0, 1fr)); - } - - .xl\:cu-columns-7 { - grid-template-columns: repeat(7, minmax(0, 1fr)); - } - - .xl\:cu-columns-8 { - grid-template-columns: repeat(8, minmax(0, 1fr)); - } - - .xl\:cu-columns-9 { - grid-template-columns: repeat(9, minmax(0, 1fr)); - } - - .xl\:cu-columns-10 { - grid-template-columns: repeat(10, minmax(0, 1fr)); - } - - .xl\:cu-columns-11 { - grid-template-columns: repeat(11, minmax(0, 1fr)); - } - - .xl\:cu-columns-12 { - grid-template-columns: repeat(12, minmax(0, 1fr)); - } - - .xl\:cu-columns-auto { - grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); - } - - .xl\:cu-col-span-1 { - grid-column: span 1 / span 1; - } - - .xl\:cu-col-span-2 { - grid-column: span 2 / span 2; - } - - .xl\:cu-col-span-3 { - grid-column: span 3 / span 3; - } - - .xl\:cu-col-span-4 { - grid-column: span 4 / span 4; - } - - .xl\:cu-col-span-5 { - grid-column: span 5 / span 5; - } - - .xl\:cu-col-span-6 { - grid-column: span 6 / span 6; - } - - .xl\:cu-col-span-7 { - grid-column: span 7 / span 7; - } - - .xl\:cu-col-span-8 { - grid-column: span 8 / span 8; - } - - .xl\:cu-col-span-9 { - grid-column: span 9 / span 9; - } - - .xl\:cu-col-span-10 { - grid-column: span 10 / span 10; - } - - .xl\:cu-col-span-11 { - grid-column: span 11 / span 11; - } - - .xl\:cu-col-span-12 { - grid-column: span 12 / span 12; - } - - .xl\:cu-col-span-auto { - grid-column: span auto / span auto; - } - - .xl\:cu-col-start-1 { - grid-column-start: 1; - } - - .xl\:cu-col-start-2 { - grid-column-start: 2; - } - - .xl\:cu-col-start-3 { - grid-column-start: 3; - } - - .xl\:cu-col-start-4 { - grid-column-start: 4; - } - - .xl\:cu-col-start-5 { - grid-column-start: 5; - } - - .xl\:cu-col-start-6 { - grid-column-start: 6; - } - - .xl\:cu-col-start-7 { - grid-column-start: 7; - } - - .xl\:cu-col-start-8 { - grid-column-start: 8; - } - - .xl\:cu-col-start-9 { - grid-column-start: 9; - } - - .xl\:cu-col-start-10 { - grid-column-start: 10; - } - - .xl\:cu-col-start-11 { - grid-column-start: 11; - } - - .xl\:cu-col-start-12 { - grid-column-start: 12; - } - - .xl\:cu-col-start-13 { - grid-column-start: 13; - } - - .xl\:cu-col-start-auto { - grid-column-start: auto; - } - - .xl\:cu-col-end-1 { - grid-column-end: 1; - } - - .xl\:cu-col-end-2 { - grid-column-end: 2; - } - - .xl\:cu-col-end-3 { - grid-column-end: 3; - } - - .xl\:cu-col-end-4 { - grid-column-end: 4; - } - - .xl\:cu-col-end-5 { - grid-column-end: 5; - } - - .xl\:cu-col-end-6 { - grid-column-end: 6; - } - - .xl\:cu-col-end-7 { - grid-column-end: 7; - } - - .xl\:cu-col-end-8 { - grid-column-end: 8; - } - - .xl\:cu-col-end-9 { - grid-column-end: 9; - } - - .xl\:cu-col-end-10 { - grid-column-end: 10; - } - - .xl\:cu-col-end-11 { - grid-column-end: 11; - } - - .xl\:cu-col-end-12 { - grid-column-end: 12; - } - - .xl\:cu-col-end-13 { - grid-column-end: 13; - } - - .xl\:cu-col-end-auto { - grid-column-end: auto; - } - - .xl\:cu-row-span-1 { - grid-row: span 1 / span 1; - } - - .xl\:cu-row-span-2 { - grid-row: span 2 / span 2; - } - - .xl\:cu-row-span-3 { - grid-row: span 3 / span 3; - } - - .xl\:cu-row-span-4 { - grid-row: span 4 / span 4; - } - - .xl\:cu-row-span-5 { - grid-row: span 5 / span 5; - } - - .xl\:cu-row-span-6 { - grid-row: span 6 / span 6; - } - - .xl\:cu-row-span-7 { - grid-row: span 7 / span 7; - } - - .xl\:cu-row-span-8 { - grid-row: span 8 / span 8; - } - - .xl\:cu-row-span-9 { - grid-row: span 9 / span 9; - } - - .xl\:cu-row-span-10 { - grid-row: span 10 / span 10; - } - - .xl\:cu-row-span-11 { - grid-row: span 11 / span 11; - } - - .xl\:cu-row-span-12 { - grid-row: span 12 / span 12; - } - - .xl\:cu-row-span-auto { - grid-row: span auto / span auto; - } -} - -.cu-gap { - gap: var(--gap); -} - -.cu-gap-0\.5 { - gap: var(--canon-space-0_5); -} - -.cu-gap-1 { - gap: var(--canon-space-1); -} - -.cu-gap-1\.5 { - gap: var(--canon-space-1_5); -} - -.cu-gap-2 { - gap: var(--canon-space-2); -} - -.cu-gap-3 { - gap: var(--canon-space-3); -} - -.cu-gap-4 { - gap: var(--canon-space-4); -} - -.cu-gap-5 { - gap: var(--canon-space-5); -} - -.cu-gap-6 { - gap: var(--canon-space-6); -} - -.cu-gap-7 { - gap: var(--canon-space-7); -} - -.cu-gap-8 { - gap: var(--canon-space-8); -} - -.cu-gap-9 { - gap: var(--canon-space-9); -} - -.cu-gap-10 { - gap: var(--canon-space-10); -} - -.cu-gap-11 { - gap: var(--canon-space-11); -} - -.cu-gap-12 { - gap: var(--canon-space-12); -} - -.cu-gap-13 { - gap: var(--canon-space-13); -} - -.cu-gap-14 { - gap: var(--canon-space-14); -} - -@media (width >= 640px) { - .xs\:cu-gap { - gap: var(--gap-xs); - } - - .xs\:cu-gap-0\.5 { - gap: var(--canon-space-0_5); - } - - .xs\:cu-gap-1 { - gap: var(--canon-space-1); - } - - .xs\:cu-gap-1\.5 { - gap: var(--canon-space-1_5); - } - - .xs\:cu-gap-2 { - gap: var(--canon-space-2); - } - - .xs\:cu-gap-3 { - gap: var(--canon-space-3); - } - - .xs\:cu-gap-4 { - gap: var(--canon-space-4); - } - - .xs\:cu-gap-5 { - gap: var(--canon-space-5); - } - - .xs\:cu-gap-6 { - gap: var(--canon-space-6); - } - - .xs\:cu-gap-7 { - gap: var(--canon-space-7); - } - - .xs\:cu-gap-8 { - gap: var(--canon-space-8); - } - - .xs\:cu-gap-9 { - gap: var(--canon-space-9); - } - - .xs\:cu-gap-10 { - gap: var(--canon-space-10); - } - - .xs\:cu-gap-11 { - gap: var(--canon-space-11); - } - - .xs\:cu-gap-12 { - gap: var(--canon-space-12); - } - - .xs\:cu-gap-13 { - gap: var(--canon-space-13); - } - - .xs\:cu-gap-14 { - gap: var(--canon-space-14); - } -} - -@media (width >= 768px) { - .sm\:cu-gap { - gap: var(--gap-sm); - } - - .sm\:cu-gap-0\.5 { - gap: var(--canon-space-0_5); - } - - .sm\:cu-gap-1 { - gap: var(--canon-space-1); - } - - .sm\:cu-gap-1\.5 { - gap: var(--canon-space-1_5); - } - - .sm\:cu-gap-2 { - gap: var(--canon-space-2); - } - - .sm\:cu-gap-3 { - gap: var(--canon-space-3); - } - - .sm\:cu-gap-4 { - gap: var(--canon-space-4); - } - - .sm\:cu-gap-5 { - gap: var(--canon-space-5); - } - - .sm\:cu-gap-6 { - gap: var(--canon-space-6); - } - - .sm\:cu-gap-7 { - gap: var(--canon-space-7); - } - - .sm\:cu-gap-8 { - gap: var(--canon-space-8); - } - - .sm\:cu-gap-9 { - gap: var(--canon-space-9); - } - - .sm\:cu-gap-10 { - gap: var(--canon-space-10); - } - - .sm\:cu-gap-11 { - gap: var(--canon-space-11); - } - - .sm\:cu-gap-12 { - gap: var(--canon-space-12); - } - - .sm\:cu-gap-13 { - gap: var(--canon-space-13); - } - - .sm\:cu-gap-14 { - gap: var(--canon-space-14); - } -} - -@media (width >= 1024px) { - .md\:cu-gap { - gap: var(--gap-md); - } - - .md\:cu-gap-0\.5 { - gap: var(--canon-space-0_5); - } - - .md\:cu-gap-1 { - gap: var(--canon-space-1); - } - - .md\:cu-gap-1\.5 { - gap: var(--canon-space-1_5); - } - - .md\:cu-gap-2 { - gap: var(--canon-space-2); - } - - .md\:cu-gap-3 { - gap: var(--canon-space-3); - } - - .md\:cu-gap-4 { - gap: var(--canon-space-4); - } - - .md\:cu-gap-5 { - gap: var(--canon-space-5); - } - - .md\:cu-gap-6 { - gap: var(--canon-space-6); - } - - .md\:cu-gap-7 { - gap: var(--canon-space-7); - } - - .md\:cu-gap-8 { - gap: var(--canon-space-8); - } - - .md\:cu-gap-9 { - gap: var(--canon-space-9); - } - - .md\:cu-gap-10 { - gap: var(--canon-space-10); - } - - .md\:cu-gap-11 { - gap: var(--canon-space-11); - } - - .md\:cu-gap-12 { - gap: var(--canon-space-12); - } - - .md\:cu-gap-13 { - gap: var(--canon-space-13); - } - - .md\:cu-gap-14 { - gap: var(--canon-space-14); - } -} - -@media (width >= 1280px) { - .lg\:cu-gap { - gap: var(--gap-lg); - } - - .lg\:cu-gap-0\.5 { - gap: var(--canon-space-0_5); - } - - .lg\:cu-gap-1 { - gap: var(--canon-space-1); - } - - .lg\:cu-gap-1\.5 { - gap: var(--canon-space-1_5); - } - - .lg\:cu-gap-2 { - gap: var(--canon-space-2); - } - - .lg\:cu-gap-3 { - gap: var(--canon-space-3); - } - - .lg\:cu-gap-4 { - gap: var(--canon-space-4); - } - - .lg\:cu-gap-5 { - gap: var(--canon-space-5); - } - - .lg\:cu-gap-6 { - gap: var(--canon-space-6); - } - - .lg\:cu-gap-7 { - gap: var(--canon-space-7); - } - - .lg\:cu-gap-8 { - gap: var(--canon-space-8); - } - - .lg\:cu-gap-9 { - gap: var(--canon-space-9); - } - - .lg\:cu-gap-10 { - gap: var(--canon-space-10); - } - - .lg\:cu-gap-11 { - gap: var(--canon-space-11); - } - - .lg\:cu-gap-12 { - gap: var(--canon-space-12); - } - - .lg\:cu-gap-13 { - gap: var(--canon-space-13); - } - - .lg\:cu-gap-14 { - gap: var(--canon-space-14); - } -} - -@media (width >= 1536px) { - .xl\:cu-gap { - gap: var(--gap-xl); - } - - .xl\:cu-gap-0\.5 { - gap: var(--canon-space-0_5); - } - - .xl\:cu-gap-1 { - gap: var(--canon-space-1); - } - - .xl\:cu-gap-1\.5 { - gap: var(--canon-space-1_5); - } - - .xl\:cu-gap-2 { - gap: var(--canon-space-2); - } - - .xl\:cu-gap-3 { - gap: var(--canon-space-3); - } - - .xl\:cu-gap-4 { - gap: var(--canon-space-4); - } - - .xl\:cu-gap-5 { - gap: var(--canon-space-5); - } - - .xl\:cu-gap-6 { - gap: var(--canon-space-6); - } - - .xl\:cu-gap-7 { - gap: var(--canon-space-7); - } - - .xl\:cu-gap-8 { - gap: var(--canon-space-8); - } - - .xl\:cu-gap-9 { - gap: var(--canon-space-9); - } - - .xl\:cu-gap-10 { - gap: var(--canon-space-10); - } - - .xl\:cu-gap-11 { - gap: var(--canon-space-11); - } - - .xl\:cu-gap-12 { - gap: var(--canon-space-12); - } - - .xl\:cu-gap-13 { - gap: var(--canon-space-13); - } - - .xl\:cu-gap-14 { - gap: var(--canon-space-14); - } -} - -.cu-align-start { - align-items: start; -} - -.cu-align-center { - align-items: center; -} - -.cu-align-end { - align-items: end; -} - -.cu-align-stretch { - align-items: stretch; -} - -.cu-jc-start { - justify-content: start; -} - -.cu-jc-center { - justify-content: center; -} - -.cu-jc-end { - justify-content: end; -} - -.cu-jc-between { - justify-content: space-between; -} - -.cu-fd-row { - flex-direction: row; -} - -.cu-fd-column { - flex-direction: column; -} - -.cu-fd-row-reverse { - flex-direction: row-reverse; -} - -.cu-fd-column-reverse { - flex-direction: column-reverse; -} - -@media (width >= 640px) { - .xs\:cu-align-start { - align-items: start; - } - - .xs\:cu-align-center { - align-items: center; - } - - .xs\:cu-align-end { - align-items: end; - } - - .xs\:cu-align-stretch { - align-items: stretch; - } - - .xs\:cu-jc-start { - justify-content: start; - } - - .xs\:cu-jc-center { - justify-content: center; - } - - .xs\:cu-jc-end { - justify-content: end; - } - - .xs\:cu-jc-between { - justify-content: space-between; - } - - .xs\:cu-fd-row { - flex-direction: row; - } - - .xs\:cu-fd-column { - flex-direction: column; - } - - .xs\:cu-fd-row-reverse { - flex-direction: row-reverse; - } - - .xs\:cu-fd-column-reverse { - flex-direction: column-reverse; - } -} - -@media (width >= 768px) { - .sm\:cu-align-start { - align-items: start; - } - - .sm\:cu-align-center { - align-items: center; - } - - .sm\:cu-align-end { - align-items: end; - } - - .sm\:cu-align-stretch { - align-items: stretch; - } - - .sm\:cu-jc-start { - justify-content: start; - } - - .sm\:cu-jc-center { - justify-content: center; - } - - .sm\:cu-jc-end { - justify-content: end; - } - - .sm\:cu-jc-between { - justify-content: space-between; - } - - .sm\:cu-fd-row { - flex-direction: row; - } - - .sm\:cu-fd-column { - flex-direction: column; - } - - .sm\:cu-fd-row-reverse { - flex-direction: row-reverse; - } - - .sm\:cu-fd-column-reverse { - flex-direction: column-reverse; - } -} - -@media (width >= 1024px) { - .md\:cu-align-start { - align-items: start; - } - - .md\:cu-align-center { - align-items: center; - } - - .md\:cu-align-end { - align-items: end; - } - - .md\:cu-align-stretch { - align-items: stretch; - } - - .md\:cu-jc-start { - justify-content: start; - } - - .md\:cu-jc-center { - justify-content: center; - } - - .md\:cu-jc-end { - justify-content: end; - } - - .md\:cu-jc-between { - justify-content: space-between; - } - - .md\:cu-fd-row { - flex-direction: row; - } - - .md\:cu-fd-column { - flex-direction: column; - } - - .md\:cu-fd-row-reverse { - flex-direction: row-reverse; - } - - .md\:cu-fd-column-reverse { - flex-direction: column-reverse; - } -} - -@media (width >= 1280px) { - .lg\:cu-align-start { - align-items: start; - } - - .lg\:cu-align-center { - align-items: center; - } - - .lg\:cu-align-end { - align-items: end; - } - - .lg\:cu-align-stretch { - align-items: stretch; - } - - .lg\:cu-jc-start { - justify-content: start; - } - - .lg\:cu-jc-center { - justify-content: center; - } - - .lg\:cu-jc-end { - justify-content: end; - } - - .lg\:cu-jc-between { - justify-content: space-between; - } - - .lg\:cu-fd-row { - flex-direction: row; - } - - .lg\:cu-fd-column { - flex-direction: column; - } - - .lg\:cu-fd-row-reverse { - flex-direction: row-reverse; - } - - .lg\:cu-fd-column-reverse { - flex-direction: column-reverse; - } -} - -@media (width >= 1536px) { - .xl\:cu-align-start { - align-items: start; - } - - .xl\:cu-align-center { - align-items: center; - } - - .xl\:cu-align-end { - align-items: end; - } - - .xl\:cu-align-stretch { - align-items: stretch; - } - - .xl\:cu-jc-start { - justify-content: start; - } - - .xl\:cu-jc-center { - justify-content: center; - } - - .xl\:cu-jc-end { - justify-content: end; - } - - .xl\:cu-jc-between { - justify-content: space-between; - } - - .xl\:cu-fd-row { - flex-direction: row; - } - - .xl\:cu-fd-column { - flex-direction: column; - } - - .xl\:cu-fd-row-reverse { - flex-direction: row-reverse; - } - - .xl\:cu-fd-column-reverse { - flex-direction: column-reverse; - } -} - -:where(a) { - color: inherit; - text-decoration: none; -} - -:root { - --canon-font-regular: system-ui; - --canon-font-monospace: ui-monospace, "Menlo", "Monaco", "Consolas", "Liberation Mono", "Courier New", monospace; - --canon-font-weight-regular: 400; - --canon-font-weight-bold: 600; - --canon-font-size-1: .625rem; - --canon-font-size-2: .75rem; - --canon-font-size-3: .875rem; - --canon-font-size-4: 1rem; - --canon-font-size-5: 1.25rem; - --canon-font-size-6: 1.5rem; - --canon-font-size-7: 2rem; - --canon-font-size-8: 3rem; - --canon-font-size-9: 4rem; - --canon-font-size-10: 5.75rem; - --canon-space: .25rem; - --canon-space-0_5: calc(var(--canon-space) * .5); - --canon-space-1: var(--canon-space); - --canon-space-1_5: calc(var(--canon-space) * 1.5); - --canon-space-2: calc(var(--canon-space) * 2); - --canon-space-3: calc(var(--canon-space) * 3); - --canon-space-4: calc(var(--canon-space) * 4); - --canon-space-5: calc(var(--canon-space) * 5); - --canon-space-6: calc(var(--canon-space) * 6); - --canon-space-7: calc(var(--canon-space) * 7); - --canon-space-8: calc(var(--canon-space) * 8); - --canon-space-9: calc(var(--canon-space) * 9); - --canon-space-10: calc(var(--canon-space) * 10); - --canon-space-11: calc(var(--canon-space) * 11); - --canon-space-12: calc(var(--canon-space) * 12); - --canon-space-13: calc(var(--canon-space) * 13); - --canon-space-14: calc(var(--canon-space) * 14); - --canon-radius-1: calc(.125rem); - --canon-radius-2: calc(.25rem); - --canon-radius-3: calc(.5rem); - --canon-radius-4: calc(.75rem); - --canon-radius-5: calc(1rem); - --canon-radius-6: calc(1.25rem); - --canon-radius-full: 9999px; - --canon-black: #000; - --canon-white: #fff; - --canon-gray-1: #f8f8f8; - --canon-gray-2: #ececec; - --canon-gray-3: #d9d9d9; - --canon-gray-4: #c1c1c1; - --canon-gray-5: #9e9e9e; - --canon-gray-6: #8c8c8c; - --canon-gray-7: #757575; - --canon-gray-8: #595959; - --canon-bg: var(--canon-gray-1); - --canon-bg-surface-1: var(--canon-white); - --canon-bg-surface-2: var(--canon-gray-2); - --canon-bg-solid: #1f5493; - --canon-bg-solid-hover: #163a66; - --canon-bg-solid-pressed: #0f2b4e; - --canon-bg-solid-disabled: #ebebeb; - --canon-bg-tint: transparent; - --canon-bg-tint-hover: #1f549366; - --canon-bg-tint-pressed: #1f549399; - --canon-bg-tint-disabled: #ebebeb; - --canon-bg-danger: #feebe7; - --canon-bg-warning: #fff2b2; - --canon-bg-success: #e6f6eb; - --canon-fg-primary: var(--canon-black); - --canon-fg-secondary: var(--canon-gray-7); - --canon-fg-link: #1f5493; - --canon-fg-link-hover: #1f2d5c; - --canon-fg-disabled: #9e9e9e; - --canon-fg-solid: var(--canon-white); - --canon-fg-solid-disabled: #9c9c9c; - --canon-fg-tint: #1f5493; - --canon-fg-tint-disabled: var(--canon-gray-5); - --canon-fg-danger: #e22b2b; - --canon-fg-warning: #e36d05; - --canon-fg-success: #1db954; - --canon-border: #0000001a; - --canon-border-hover: #0003; - --canon-border-pressed: #0006; - --canon-border-disabled: #0000001a; - --canon-border-danger: #f87a7a; - --canon-border-warning: #e36d05; - --canon-border-success: #53db83; - --canon-ring: #1f5493; - --canon-scrollbar: #a0a0a03b; - --canon-scrollbar-thumb: #a0a0a0; -} - -[data-theme="dark"] { - --canon-gray-1: #191919; - --canon-gray-2: #242424; - --canon-gray-3: #373737; - --canon-gray-4: #464646; - --canon-gray-5: #575757; - --canon-gray-6: #7b7b7b; - --canon-gray-7: #9e9e9e; - --canon-gray-8: #b4b4b4; - --canon-bg: var(--canon-black); - --canon-bg-surface-1: var(--canon-gray-1); - --canon-bg-surface-2: var(--canon-gray-2); - --canon-bg-solid: #9cc9ff; - --canon-bg-solid-hover: #83b9fd; - --canon-bg-solid-pressed: #83b9fd; - --canon-bg-solid-disabled: #222; - --canon-bg-tint: transparent; - --canon-bg-tint-hover: #9cc9ff1f; - --canon-bg-tint-pressed: #9cc9ff29; - --canon-bg-tint-disabled: transparent; - --canon-bg-danger: #3b1219; - --canon-bg-warning: #302008; - --canon-bg-success: #132d21; - --canon-fg-primary: var(--canon-white); - --canon-fg-secondary: var(--canon-gray-7); - --canon-fg-link: #9cc9ff; - --canon-fg-link-hover: #7eb5f7; - --canon-fg-disabled: var(--canon-gray-7); - --canon-fg-solid: #101821; - --canon-fg-solid-disabled: var(--canon-gray-5); - --canon-fg-tint: #9cc9ff; - --canon-fg-tint-disabled: var(--canon-gray-5); - --canon-fg-danger: #e22b2b; - --canon-fg-warning: #e36d05; - --canon-fg-success: #1db954; - --canon-border: #fff3; - --canon-border-hover: #fff6; - --canon-border-pressed: #ffffff80; - --canon-border-disabled: #fff3; - --canon-border-danger: #f87a7a; - --canon-border-warning: #e36d05; - --canon-border-success: #53db83; - --canon-ring: #1f5493; - --canon-scrollbar: #3636363a; - --canon-scrollbar-thumb: #575757; -} - -.canon-AvatarRoot { - vertical-align: middle; - user-select: none; - color: var(--canon-fg-primary); - background-color: var(--canon-bg-surface-2); - border-radius: 100%; - justify-content: center; - align-items: center; - width: 2rem; - height: 2rem; - font-size: 1rem; - font-weight: 500; - line-height: 1; - display: inline-flex; - overflow: hidden; -} - -.canon-AvatarRoot[data-size="small"] { - width: 1.5rem; - height: 1.5rem; -} - -.canon-AvatarRoot[data-size="medium"] { - width: 2rem; - height: 2rem; -} - -.canon-AvatarRoot[data-size="large"] { - width: 3rem; - height: 3rem; -} - -.canon-AvatarImage { - object-fit: cover; - width: 100%; - height: 100%; -} - -.canon-AvatarFallback { - width: 100%; - height: 100%; - font-size: var(--canon-font-size-3); - font-weight: var(--canon-font-weight-regular); - box-shadow: inset 0 0 0 1px var(--canon-border); - border-radius: var(--canon-radius-full); - justify-content: center; - align-items: center; - display: flex; -} - -.canon-Box { - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-primary); -} - -.canon-Button { - user-select: none; - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-bold); - cursor: pointer; - border-radius: var(--canon-radius-2); - justify-content: center; - align-items: center; - gap: var(--canon-space-1_5); - border: none; - padding: 0; - display: inline-flex; - - &:disabled { - cursor: not-allowed; - } -} - -.canon-Button[data-variant="primary"] { - background-color: var(--canon-bg-solid); - color: var(--canon-fg-solid); - transition: background-color .15s, box-shadow .15s; - - &:hover { - background-color: var(--canon-bg-solid-hover); - } - - &:active { - background-color: var(--canon-bg-solid-pressed); - } - - &:focus-visible { - outline: 2px solid var(--canon-ring); - outline-offset: 2px; - } - - &:disabled { - background-color: var(--canon-bg-solid-disabled); - color: var(--canon-fg-solid-disabled); - } -} - -.canon-Button[data-variant="secondary"] { - background-color: var(--canon-bg-surface-1); - box-shadow: inset 0 0 0 1px var(--canon-border); - color: var(--canon-fg-primary); - transition: box-shadow .15s; - - &:hover { - box-shadow: inset 0 0 0 1px var(--canon-border-hover); - } - - &:active { - box-shadow: inset 0 0 0 1px var(--canon-border-pressed); - } - - &:focus-visible { - box-shadow: inset 0 0 0 2px var(--canon-ring); - outline: none; - transition: none; - } - - &:disabled { - box-shadow: inset 0 0 0 1px var(--canon-border-disabled); - color: var(--canon-fg-disabled); - } -} - -.canon-Button[data-size="medium"] { - font-size: var(--canon-font-size-4); - padding: 0 var(--canon-space-3); - height: 2.5rem; -} - -.canon-Button[data-size="small"] { - font-size: var(--canon-font-size-3); - padding: 0 var(--canon-space-2); - height: 2rem; -} - -.canon-Button[data-size="small"] svg { - width: 1rem; - height: 1rem; -} - -.canon-Button[data-size="medium"] svg { - width: 1.25rem; - height: 1.25rem; -} - -.canon-ButtonIcon { - justify-content: center; - align-items: center; -} - -.canon-ButtonIcon[data-size="small"] { - width: 2rem; - padding: 0; -} - -.canon-ButtonIcon[data-size="medium"] { - width: 2.5rem; - padding: 0; -} - -.canon-CheckboxRoot { - width: 1rem; - height: 1rem; - box-shadow: inset 0 0 0 1px var(--canon-border); - cursor: pointer; - background-color: var(--canon-bg-surface-1); - border: none; - border-radius: 2px; - flex-shrink: 0; - justify-content: center; - align-items: center; - padding: 0; - transition: background-color .2s ease-in-out; - display: flex; -} - -.canon-CheckboxRoot:focus-visible { - outline: 2px solid var(--canon-ring); - outline-offset: 2px; - transition: none; -} - -.canon-CheckboxRoot[data-checked] { - background-color: var(--canon-bg-solid); - box-shadow: none; - color: var(--canon-fg-solid); -} - -.canon-CheckboxLabel { - align-items: center; - gap: var(--canon-space-2); - font-size: var(--canon-font-size-3); - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-primary); - user-select: none; - flex-direction: row; - display: flex; - - &:hover { - & .canon-CheckboxRoot:not([data-checked]) { - box-shadow: inset 0 0 0 1px var(--canon-border-hover); - } - } -} - -.canon-CheckboxIndicator { - color: var(--canon-fg-solid); - justify-content: center; - align-items: center; - display: flex; -} - -.canon-CollapsiblePanel { - height: var(--collapsible-panel-height); - transition: all .15s ease-out; - display: flex; - overflow: hidden; - - &[data-starting-style], &[data-ending-style] { - height: 0; - } -} - -.canon-Container { - max-width: 120rem; - padding-inline: var(--canon-space-4); - margin-inline: auto; - transition: padding .2s ease-in-out; -} - -@media (width >= 640px) { - .canon-Container { - padding-inline: var(--canon-space-8); - } -} - -@media (width >= 1024px) { - .canon-Container { - padding-inline: var(--canon-space-12); - } -} - -.canon-DataTableRoot { - gap: var(--canon-space-3); - flex-direction: column; - display: flex; -} - -.canon-DataTablePagination { - padding-top: var(--canon-space-5); - justify-content: space-between; - align-items: center; - display: flex; -} - -.canon-DataTablePagination--left { - justify-content: space-between; - align-items: center; - display: flex; -} - -.canon-DataTablePagination--right { - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - display: flex; -} - -.canon-DataTablePagination--select { - min-width: 10.5rem; -} - -.canon-FieldLabel { - margin-bottom: var(--canon-space-3); - gap: var(--canon-space-1); - flex-direction: column; - display: flex; -} - -.canon-FieldLabelLabel { - color: var(--canon-fg-primary); - cursor: pointer; - font-weight: var(--canon-font-weight-regular); - font-size: var(--canon-font-size-2); - margin-right: auto; -} - -.canon-FieldLabelSecondaryLabel { - color: var(--canon-fg-secondary); - font-weight: var(--canon-font-weight-regular); - margin-left: var(--canon-space-1); -} - -.canon-FieldLabelDescription { - font-weight: var(--canon-font-weight-regular); - font-size: var(--canon-font-size-2); - color: var(--canon-fg-secondary); - margin: 0; -} - -.canon-Flex { - min-width: 0; - display: flex; -} - -.canon-Grid { - display: grid; -} - -.canon-Heading { - font-family: var(--canon-font-regular); - color: var(--canon-fg-primary); - margin: 0; - padding: 0; - line-height: 100%; -} - -.canon-Heading[data-variant="display"] { - font-size: var(--canon-font-size-10); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title1"] { - font-size: var(--canon-font-size-9); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title2"] { - font-size: var(--canon-font-size-8); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title3"] { - font-size: var(--canon-font-size-7); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title4"] { - font-size: var(--canon-font-size-6); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-variant="title5"] { - font-size: var(--canon-font-size-5); - font-weight: var(--canon-font-weight-bold); -} - -.canon-Heading[data-color="primary"] { - color: var(--canon-fg-primary); -} - -.canon-Heading[data-color="secondary"] { - color: var(--canon-fg-secondary); -} - -.canon-Heading[data-truncate] { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -.canon-Icon { - width: 1rem; - height: 1rem; -} - -.canon-Link { - font-family: var(--canon-font-regular); - color: var(--canon-fg-link); - cursor: pointer; - margin: 0; - padding: 0; - text-decoration-line: none; - - &:hover { - color: var(--canon-fg-link-hover); - text-underline-offset: calc(.025em + 2px); - text-decoration-line: underline; - text-decoration-style: solid; - text-decoration-thickness: min(2px, max(1px, .05em)); - text-decoration-color: color-mix(in srgb, var(--canon-fg-link-hover) 30%, transparent); - } -} - -.canon-Link[data-variant="body"] { - font-size: var(--canon-font-size-3); - line-height: 140%; -} - -.canon-Link[data-variant="subtitle"] { - font-size: var(--canon-font-size-4); - line-height: 140%; -} - -.canon-Link[data-variant="caption"] { - font-size: var(--canon-font-size-2); - line-height: 140%; -} - -.canon-Link[data-variant="label"] { - font-size: var(--canon-font-size-1); - line-height: 140%; -} - -.canon-Link[data-weight="regular"] { - font-weight: var(--canon-font-weight-regular); -} - -.canon-Link[data-weight="bold"] { - font-weight: var(--canon-font-weight-bold); -} - -.canon-MenuPositioner { - outline: 0; -} - -.canon-MenuPopup { - background-color: var(--canon-bg-surface-1); - border: 1px solid var(--canon-border); - color: var(--canon-fg-primary); - transform-origin: var(--transform-origin); - max-width: min(var(--available-width), 340px); - max-height: min(var(--available-height), 500px); - padding-bottom: var(--canon-space-1); - border-radius: .375rem; - outline: none; - flex-direction: column; - transition: transform .15s, opacity .15s; - display: flex; - position: relative; - overflow: auto; - - &[data-starting-style], &[data-ending-style] { - opacity: 0; - transform: scale(.9); - } -} - -.canon-MenuItem { - user-select: none; - align-items: center; - gap: var(--canon-space-2); - height: 32px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - margin-inline: var(--canon-space-1); - padding-inline: var(--canon-space-2); - font-size: var(--canon-font-size-3); - cursor: pointer; - outline: 0; - flex-shrink: 0; - text-decoration: none; - display: flex; - - &:first-child { - margin-top: var(--canon-space-1); - } - - &[data-highlighted] { - background-color: var(--canon-gray-3); - } -} - -.canon-MenuSubmenuTrigger { - user-select: none; - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - height: 32px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - margin-inline: var(--canon-space-1); - padding-inline: var(--canon-space-2); - font-size: var(--canon-font-size-3); - cursor: pointer; - outline: 0; - flex-shrink: 0; - text-decoration: none; - display: flex; - - & .canon-Icon { - color: var(--canon-fg-secondary); - } - - &:first-child { - margin-top: var(--canon-space-1); - } - - &[data-popup-open], &[data-highlighted] { - background-color: var(--canon-gray-3); - - & .canon-Icon { - color: var(--canon-fg-primary); - } - } -} - -.canon-MenuSeparator { - background-color: var(--color-gray-200); - height: 1px; - margin: .375rem 1rem; -} - -.canon-SubmenuComboboxSearch { - padding-inline: var(--canon-space-3); - border: none; - border-bottom: 1px solid var(--canon-border); - background-color: var(--canon-bg-surface-1); - width: 100%; - height: 32px; - color: var(--canon-fg-primary); - line-height: 140%; - font-size: var(--canon-font-size-3); - z-index: 1; - outline: none; - position: sticky; - top: 0; - - &::placeholder { - color: var(--canon-fg-secondary); - } - - &:disabled { - opacity: .6; - cursor: not-allowed; - } -} - -.canon-SubmenuComboboxItems { - padding-top: var(--canon-space-2); - outline: none; - flex-direction: column; - display: flex; - overflow-y: auto; -} - -.canon-SubmenuComboboxNoResults { - padding-inline: var(--canon-space-3); - padding-top: var(--canon-space-2); - padding-bottom: var(--canon-space-4); - color: var(--canon-fg-secondary); - font-size: var(--canon-font-size-3); -} - -.canon-SubmenuComboboxItem { - user-select: none; - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - height: 32px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - margin-inline: var(--canon-space-1); - padding-inline: var(--canon-space-2); - font-size: var(--canon-font-size-3); - cursor: pointer; - outline: 0; - flex-shrink: 0; - text-decoration: none; - display: flex; - - &[data-highlighted] { - background-color: var(--canon-gray-3); - } - - &[data-disabled] { - opacity: .5; - cursor: not-allowed; - } -} - -.canon-SubmenuComboboxItemCheckbox { - width: 16px; - height: 16px; - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-2); - border: 1px solid var(--canon-border); - background: var(--canon-bg-surface-1); - flex-shrink: 0; - justify-content: center; - align-items: center; - display: flex; -} - -.canon-SubmenuComboboxItemLabel { - text-overflow: ellipsis; - white-space: nowrap; - flex: 1; - overflow: hidden; -} - -.canon-TableRoot { - caption-side: bottom; - border-collapse: collapse; - width: 100%; -} - -.canon-TableHead { - text-align: left; - padding: var(--canon-space-3); - font-size: var(--canon-font-size-3); - color: var(--canon-fg-primary); -} - -.canon-TableBody { - color: var(--canon-fg-primary); -} - -.canon-TableRow { - border-bottom: 1px solid var(--canon-border); - transition: color .2s ease-in-out; -} - -.canon-TableBody .canon-TableRow:hover { - background-color: var(--canon-gray-2); -} - -.canon-TableCell { - padding: var(--canon-space-3); - font-size: var(--canon-font-size-3); -} - -.canon-TableCellText, .canon-TableCellLink { - gap: var(--canon-space-0_5); - flex-direction: column; - display: flex; -} - -.canon-TableCellProfile { - gap: var(--canon-space-2); - flex-direction: row; - align-items: center; - display: flex; -} - -.canon-TableCellProfileAvatar { - vertical-align: middle; - user-select: none; - color: var(--canon-fg-primary); - background-color: var(--canon-bg-surface-2); - border-radius: 100%; - justify-content: center; - align-items: center; - width: 1.25rem; - height: 1.25rem; - font-size: 1rem; - font-weight: 500; - line-height: 1; - display: inline-flex; - overflow: hidden; -} - -.canon-TableCellProfileAvatarImage { - object-fit: cover; - width: 100%; - height: 100%; -} - -.canon-TableCellProfileAvatarFallback { - width: 100%; - height: 100%; - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - box-shadow: inset 0 0 0 1px var(--canon-border); - border-radius: var(--canon-radius-full); - justify-content: center; - align-items: center; - display: flex; -} - -.canon-TabsRoot { - border: 1px solid var(--color-gray-200); - border-radius: .375rem; -} - -.canon-TabsList { - z-index: 0; - display: flex; - position: relative; -} - -.canon-TabsTab { - appearance: none; - color: var(--canon-fg-secondary); - user-select: none; - padding-inline: var(--canon-space-3); - height: 2rem; - font-family: inherit; - font-size: .875rem; - font-weight: 500; - line-height: 1.25rem; - font-size: var(--canon-font-size-2); - cursor: pointer; - background: none; - border: 0; - outline: 0; - justify-content: center; - align-items: center; - margin: 0; - padding-block: 0; - transition: color .2s ease-in-out; - display: flex; - - &[data-selected] { - color: var(--canon-fg-primary); - } - - @media (hover: hover) { - &:hover { - color: var(--canon-fg-primary); - } - } - - &:focus-visible { - position: relative; - - &:before { - content: ""; - outline: 1px solid var(--canon-ring); - outline-offset: -1px; - border-radius: .25rem; - position: absolute; - inset: .25rem 0; - } - } -} - -.canon-TabsIndicator { - z-index: -1; - translate: var(--active-tab-left) -50%; - width: var(--active-tab-width); - background-color: var(--canon-bg-solid); - height: 1px; - transition-property: translate, width; - transition-duration: .2s; - transition-timing-function: ease-in-out; - position: absolute; - bottom: 0; - left: 0; -} - -.canon-TabsPanel { - &[hidden] { - display: none; - } -} - -.canon-Text { - font-family: var(--canon-font-regular); - margin: 0; - padding: 0; -} - -.canon-Text[data-variant="body"] { - font-size: var(--canon-font-size-3); - line-height: 140%; -} - -.canon-Text[data-variant="subtitle"] { - font-size: var(--canon-font-size-4); - line-height: 140%; -} - -.canon-Text[data-variant="caption"] { - font-size: var(--canon-font-size-2); - line-height: 140%; -} - -.canon-Text[data-variant="label"] { - font-size: var(--canon-font-size-1); - line-height: 140%; -} - -.canon-Text[data-weight="regular"] { - font-weight: var(--canon-font-weight-regular); -} - -.canon-Text[data-weight="bold"] { - font-weight: var(--canon-font-weight-bold); -} - -.canon-Text[data-color="primary"] { - color: var(--canon-fg-primary); -} - -.canon-Text[data-color="secondary"] { - color: var(--canon-fg-secondary); -} - -.canon-Text[data-color="danger"] { - color: var(--canon-fg-danger); -} - -.canon-Text[data-color="warning"] { - color: var(--canon-fg-warning); -} - -.canon-Text[data-color="success"] { - color: var(--canon-fg-success); -} - -.canon-Text[data-truncate] { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -.canon-TextField { - font-family: var(--canon-font-regular); - flex-direction: column; - width: 100%; - display: flex; -} - -.canon-TextFieldInputWrapper { - position: relative; -} - -.canon-TextFieldInputWrapper[data-size="small"] { - height: 2rem; -} - -.canon-TextFieldInputWrapper[data-size="medium"] { - height: 2.5rem; -} - -.canon-TextFieldIcon { - left: var(--canon-space-3); - margin-right: var(--canon-space-1); - color: var(--canon-fg-primary); - flex-shrink: 0; - position: absolute; - top: 50%; - transform: translateY(-50%); -} - -.canon-TextFieldIcon[data-size="small"], .canon-TextFieldIcon[data-size="small"] svg { - width: 1rem; - height: 1rem; -} - -.canon-TextFieldIcon[data-size="medium"], .canon-TextFieldIcon[data-size="medium"] svg { - width: 1.25rem; - height: 1.25rem; -} - -.canon-TextFieldInput { - padding: 0 var(--canon-space-3); - border-radius: var(--canon-radius-3); - border: 1px solid var(--canon-border); - background-color: var(--canon-bg-surface-1); - font-size: var(--canon-font-size-3); - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-primary); - width: 100%; - height: 100%; - cursor: inherit; - align-items: center; - transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; - display: flex; -} - -.canon-TextFieldInput::placeholder { - color: var(--canon-fg-secondary); -} - -.canon-TextFieldInput[data-icon] { - padding-left: var(--canon-space-8); -} - -.canon-TextFieldInput[data-focused] { - outline-color: var(--canon-border-pressed); - outline-width: 0; -} - -.canon-TextFieldInput[data-hovered] { - border-color: var(--canon-border-hover); -} - -.canon-TextFieldInput[data-focused] { - border-color: var(--canon-border-pressed); - outline-width: 0; -} - -.canon-TextFieldInput[data-invalid] { - border-color: var(--canon-fg-danger); -} - -.canon-TextFieldInput[data-disabled] { - opacity: .5; - cursor: not-allowed; - border: 1px solid var(--canon-border-disabled); -} - -.canon-TextFieldError { - color: var(--canon-fg-danger); - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - margin-top: var(--canon-space-2); -} - -.canon-TooltipPopup { - box-sizing: border-box; - transform-origin: var(--transform-origin); - background-color: canvas; - background-color: var(--canon-bg-surface-1); - color: var(--canon-fg-primary); - outline: 1px solid var(--canon-border); - box-shadow: 0 10px 15px -3px var(--canon-border), 0 4px 6px -4px var(--canon-border); - border-radius: .375rem; - flex-direction: column; - padding: .25rem .5rem; - font-size: .875rem; - line-height: 1.25rem; - transition: transform .15s, opacity .15s; - display: flex; - - &[data-starting-style], &[data-ending-style] { - opacity: 0; - transform: scale(.9); - } - - &[data-instant] { - transition-duration: 0s; - } -} - -.canon-TooltipArrow { - display: flex; - - &[data-side="top"] { - bottom: -8px; - rotate: 180deg; - } - - &[data-side="bottom"] { - top: -8px; - rotate: none; - } - - &[data-side="left"] { - right: -13px; - rotate: 90deg; - } - - &[data-side="right"] { - left: -13px; - rotate: -90deg; - } -} - -.canon-TooltipArrow-fill { - fill: var(--canon-bg-surface-1); -} - -.canon-TooltipArrow-outer-stroke { - @media (prefers-color-scheme: light) { - & { - fill: var(--canon-border); - } - } -} - -.canon-ScrollAreaRoot { - box-sizing: border-box; - width: 24rem; - max-width: calc(100vw - 8rem); - height: 8.5rem; -} - -.canon-ScrollAreaViewport { - overscroll-behavior: contain; - height: 100%; -} - -.canon-ScrollAreaContent { - padding-block: .75rem; - flex-direction: column; - gap: 1rem; - padding-left: 1rem; - padding-right: 1.5rem; - display: flex; -} - -.canon-ScrollAreaScrollbar { - background-color: var(--canon-scrollbar); - opacity: 0; - border-radius: .375rem; - justify-content: center; - width: .25rem; - margin: .5rem; - transition: opacity .15s .3s; - display: flex; - - &[data-hovering], &[data-scrolling] { - opacity: 1; - transition-duration: 75ms; - transition-delay: 0s; - } - - &:before { - content: ""; - width: 1.25rem; - height: 100%; - position: absolute; - } -} - -.canon-ScrollAreaThumb { - border-radius: inherit; - background-color: var(--canon-scrollbar-thumb); - width: 100%; -} - -.canon-Select { - font-family: var(--canon-font-regular); - flex-direction: column; - width: 100%; - display: flex; -} - -.canon-SelectLabel { - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-primary); - margin-bottom: var(--canon-space-1_5); - cursor: pointer; -} - -.canon-SelectLabel[data-disabled] { - cursor: default; -} - -.canon-SelectDescription { - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-secondary); - padding-top: var(--canon-space-1_5); - margin: 0; -} - -.canon-SelectError { - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-danger); - padding-top: var(--canon-space-1_5); - margin: 0; -} - -.canon-SelectTrigger { - box-sizing: border-box; - border-radius: var(--canon-radius-3); - border: 1px solid var(--canon-border); - padding: 0 var(--canon-space-4); - background-color: var(--canon-bg-surface-1); - font-size: var(--canon-font-size-3); - font-family: var(--canon-font-regular); - font-weight: var(--canon-font-weight-regular); - color: var(--canon-fg-primary); - cursor: pointer; - justify-content: space-between; - align-items: center; - gap: var(--canon-space-2); - width: 100%; - transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; - display: flex; -} - -.canon-SelectTrigger::placeholder { - color: var(--canon-fg-secondary); -} - -.canon-SelectTrigger:hover { - border-color: var(--canon-border-hover); -} - -.canon-SelectTrigger:focus-visible { - border-color: var(--canon-border-pressed); - outline: 0; -} - -.canon-SelectTrigger[data-invalid] { - border-color: var(--canon-fg-danger); -} - -.canon-SelectTrigger[data-invalid]:hover, .canon-SelectTrigger[data-invalid]:focus-visible { - border-width: 2px; -} - -.canon-SelectTrigger[data-disabled] { - cursor: not-allowed; - border-color: var(--canon-border-disabled); - color: var(--canon-fg-disabled); -} - -.canon-SelectTrigger[data-size="small"] { - height: 2rem; -} - -.canon-SelectTrigger[data-size="medium"] { - height: 3rem; -} - -.canon-SelectIcon { - margin-left: var(--canon-space-5); - transition: transform .2s; -} - -.canon-SelectTrigger[data-popup-open] .canon-SelectIcon { - transform: rotate(180deg); -} - -.canon-SelectPopup { - box-sizing: border-box; - max-height: var(--available-height); - background-color: var(--canon-bg-surface-1); - border: 1px solid var(--canon-border); - border-radius: var(--canon-radius-3); - padding-block: var(--canon-space-1); - z-index: 1; - transform-origin: var(--transform-origin); - outline: 0; - transition: transform .15s, opacity .15s; - overflow-y: auto; - box-shadow: 0 4px 12px #0003; -} - -.canon-SelectPopup[data-starting-style], .canon-SelectPopup[data-ending-style] { - opacity: 0; - transform: scale(.9); -} - -.canon-SelectItem { - width: var(--anchor-width); - padding-block: var(--canon-space-2); - padding-inline: var(--canon-space-4); - color: var(--canon-fg-primary); - border-radius: var(--canon-radius-3); - cursor: pointer; - user-select: none; - font-size: var(--canon-font-size-3); - align-items: center; - gap: var(--canon-space-2); - outline: none; - grid-template-columns: 1rem 1fr; - grid-template-areas: "icon text"; - display: grid; - position: relative; -} - -.canon-SelectItem[data-highlighted] { - z-index: 0; - color: var(--canon-fg-primary); - position: relative; -} - -.canon-SelectItem[data-highlighted]:before { - content: ""; - z-index: -1; - background-color: var(--canon-bg-tint-hover); - border-radius: .25rem; - position: absolute; - inset-block: 0; - inset-inline: .25rem; -} - -.canon-SelectItem[data-disabled] { - cursor: not-allowed; - color: var(--canon-fg-disabled); -} - -.canon-SelectItemIndicator { - grid-area: icon; - justify-content: center; - align-items: center; - display: flex; -} - -.canon-SelectItemText { - flex: 1; - grid-area: text; -} - -.canon-SelectRequired { - color: var(--canon-fg-secondary); - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - margin-left: var(--canon-space-1); -} - -.canon-SelectIcon { - justify-content: center; - align-items: center; - display: flex; -} - -.canon-SelectValue { - text-overflow: ellipsis; - white-space: nowrap; - width: 100%; - overflow: hidden; -} - -.canon-Switch { - align-items: center; - gap: var(--canon-space-3); - font-size: var(--canon-font-size-3); - color: var(--canon-fg-primary); - cursor: pointer; - display: flex; - position: relative; - - &[data-pressed] .canon-SwitchIndicator { - &:before { - background: var(--canon-fg-solid); - } - } - - &[data-selected] { - & .canon-SwitchIndicator { - background: var(--canon-bg-solid); - - &:before { - background: var(--canon-fg-solid); - transform: translateX(100%); - } - } - - &[data-pressed] { - & .indicator { - background: var(--canon-gray-3); - } - } - } - - &[data-focus-visible] .canon-SwitchIndicator { - outline-offset: 2px; - outline: 2px solid; - } -} - -.canon-SwitchIndicator { - background: var(--canon-gray-3); - border: 2px; - border-radius: 1.143rem; - width: 2rem; - height: 1.143rem; - transition: all .2s; - - &:before { - content: ""; - background: var(--canon-fg-solid); - border-radius: 16px; - width: .857rem; - height: .857rem; - margin: .143rem; - transition: all .2s; - display: block; - } -} +@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}}.cu-p{padding:var(--p)}.cu-p-0\.5{padding:var(--canon-space-0_5)}.cu-p-1{padding:var(--canon-space-1)}.cu-p-1\.5{padding:var(--canon-space-1_5)}.cu-p-2{padding:var(--canon-space-2)}.cu-p-3{padding:var(--canon-space-3)}.cu-p-4{padding:var(--canon-space-4)}.cu-p-5{padding:var(--canon-space-5)}.cu-p-6{padding:var(--canon-space-6)}.cu-p-7{padding:var(--canon-space-7)}.cu-p-8{padding:var(--canon-space-8)}.cu-p-9{padding:var(--canon-space-9)}.cu-p-10{padding:var(--canon-space-10)}.cu-p-11{padding:var(--canon-space-11)}.cu-p-12{padding:var(--canon-space-12)}.cu-p-13{padding:var(--canon-space-13)}.cu-p-14{padding:var(--canon-space-14)}@media (width>=640px){.xs\:cu-p{padding:var(--p-xs)}.xs\:cu-p-0\.5{padding:var(--canon-space-0_5)}.xs\:cu-p-1{padding:var(--canon-space-1)}.xs\:cu-p-1\.5{padding:var(--canon-space-1_5)}.xs\:cu-p-2{padding:var(--canon-space-2)}.xs\:cu-p-3{padding:var(--canon-space-3)}.xs\:cu-p-4{padding:var(--canon-space-4)}.xs\:cu-p-5{padding:var(--canon-space-5)}.xs\:cu-p-6{padding:var(--canon-space-6)}.xs\:cu-p-7{padding:var(--canon-space-7)}.xs\:cu-p-8{padding:var(--canon-space-8)}.xs\:cu-p-9{padding:var(--canon-space-9)}.xs\:cu-p-10{padding:var(--canon-space-10)}.xs\:cu-p-11{padding:var(--canon-space-11)}.xs\:cu-p-12{padding:var(--canon-space-12)}.xs\:cu-p-13{padding:var(--canon-space-13)}.xs\:cu-p-14{padding:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-p{padding:var(--p-sm)}.sm\:cu-p-0\.5{padding:var(--canon-space-0_5)}.sm\:cu-p-1{padding:var(--canon-space-1)}.sm\:cu-p-1\.5{padding:var(--canon-space-1_5)}.sm\:cu-p-2{padding:var(--canon-space-2)}.sm\:cu-p-3{padding:var(--canon-space-3)}.sm\:cu-p-4{padding:var(--canon-space-4)}.sm\:cu-p-5{padding:var(--canon-space-5)}.sm\:cu-p-6{padding:var(--canon-space-6)}.sm\:cu-p-7{padding:var(--canon-space-7)}.sm\:cu-p-8{padding:var(--canon-space-8)}.sm\:cu-p-9{padding:var(--canon-space-9)}.sm\:cu-p-10{padding:var(--canon-space-10)}.sm\:cu-p-11{padding:var(--canon-space-11)}.sm\:cu-p-12{padding:var(--canon-space-12)}.sm\:cu-p-13{padding:var(--canon-space-13)}.sm\:cu-p-14{padding:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-p{padding:var(--p-md)}.md\:cu-p-0\.5{padding:var(--canon-space-0_5)}.md\:cu-p-1{padding:var(--canon-space-1)}.md\:cu-p-1\.5{padding:var(--canon-space-1_5)}.md\:cu-p-2{padding:var(--canon-space-2)}.md\:cu-p-3{padding:var(--canon-space-3)}.md\:cu-p-4{padding:var(--canon-space-4)}.md\:cu-p-5{padding:var(--canon-space-5)}.md\:cu-p-6{padding:var(--canon-space-6)}.md\:cu-p-7{padding:var(--canon-space-7)}.md\:cu-p-8{padding:var(--canon-space-8)}.md\:cu-p-9{padding:var(--canon-space-9)}.md\:cu-p-10{padding:var(--canon-space-10)}.md\:cu-p-11{padding:var(--canon-space-11)}.md\:cu-p-12{padding:var(--canon-space-12)}.md\:cu-p-13{padding:var(--canon-space-13)}.md\:cu-p-14{padding:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-p{padding:var(--p-lg)}.lg\:cu-p-0\.5{padding:var(--canon-space-0_5)}.lg\:cu-p-1{padding:var(--canon-space-1)}.lg\:cu-p-1\.5{padding:var(--canon-space-1_5)}.lg\:cu-p-2{padding:var(--canon-space-2)}.lg\:cu-p-3{padding:var(--canon-space-3)}.lg\:cu-p-4{padding:var(--canon-space-4)}.lg\:cu-p-5{padding:var(--canon-space-5)}.lg\:cu-p-6{padding:var(--canon-space-6)}.lg\:cu-p-7{padding:var(--canon-space-7)}.lg\:cu-p-8{padding:var(--canon-space-8)}.lg\:cu-p-9{padding:var(--canon-space-9)}.lg\:cu-p-10{padding:var(--canon-space-10)}.lg\:cu-p-11{padding:var(--canon-space-11)}.lg\:cu-p-12{padding:var(--canon-space-12)}.lg\:cu-p-13{padding:var(--canon-space-13)}.lg\:cu-p-14{padding:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-p{padding:var(--p-xl)}.xl\:cu-p-0\.5{padding:var(--canon-space-0_5)}.xl\:cu-p-1{padding:var(--canon-space-1)}.xl\:cu-p-1\.5{padding:var(--canon-space-1_5)}.xl\:cu-p-2{padding:var(--canon-space-2)}.xl\:cu-p-3{padding:var(--canon-space-3)}.xl\:cu-p-4{padding:var(--canon-space-4)}.xl\:cu-p-5{padding:var(--canon-space-5)}.xl\:cu-p-6{padding:var(--canon-space-6)}.xl\:cu-p-7{padding:var(--canon-space-7)}.xl\:cu-p-8{padding:var(--canon-space-8)}.xl\:cu-p-9{padding:var(--canon-space-9)}.xl\:cu-p-10{padding:var(--canon-space-10)}.xl\:cu-p-11{padding:var(--canon-space-11)}.xl\:cu-p-12{padding:var(--canon-space-12)}.xl\:cu-p-13{padding:var(--canon-space-13)}.xl\:cu-p-14{padding:var(--canon-space-14)}}.cu-pl{padding-left:var(--pl)}.cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.cu-pl-1{padding-left:var(--canon-space-1)}.cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.cu-pl-2{padding-left:var(--canon-space-2)}.cu-pl-3{padding-left:var(--canon-space-3)}.cu-pl-4{padding-left:var(--canon-space-4)}.cu-pl-5{padding-left:var(--canon-space-5)}.cu-pl-6{padding-left:var(--canon-space-6)}.cu-pl-7{padding-left:var(--canon-space-7)}.cu-pl-8{padding-left:var(--canon-space-8)}.cu-pl-9{padding-left:var(--canon-space-9)}.cu-pl-10{padding-left:var(--canon-space-10)}.cu-pl-11{padding-left:var(--canon-space-11)}.cu-pl-12{padding-left:var(--canon-space-12)}.cu-pl-13{padding-left:var(--canon-space-13)}.cu-pl-14{padding-left:var(--canon-space-14)}@media (width>=640px){.xs\:cu-pl{padding-left:var(--pl-xs)}.xs\:cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.xs\:cu-pl-1{padding-left:var(--canon-space-1)}.xs\:cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.xs\:cu-pl-2{padding-left:var(--canon-space-2)}.xs\:cu-pl-3{padding-left:var(--canon-space-3)}.xs\:cu-pl-4{padding-left:var(--canon-space-4)}.xs\:cu-pl-5{padding-left:var(--canon-space-5)}.xs\:cu-pl-6{padding-left:var(--canon-space-6)}.xs\:cu-pl-7{padding-left:var(--canon-space-7)}.xs\:cu-pl-8{padding-left:var(--canon-space-8)}.xs\:cu-pl-9{padding-left:var(--canon-space-9)}.xs\:cu-pl-10{padding-left:var(--canon-space-10)}.xs\:cu-pl-11{padding-left:var(--canon-space-11)}.xs\:cu-pl-12{padding-left:var(--canon-space-12)}.xs\:cu-pl-13{padding-left:var(--canon-space-13)}.xs\:cu-pl-14{padding-left:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-pl{padding-left:var(--pl-sm)}.sm\:cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.sm\:cu-pl-1{padding-left:var(--canon-space-1)}.sm\:cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.sm\:cu-pl-2{padding-left:var(--canon-space-2)}.sm\:cu-pl-3{padding-left:var(--canon-space-3)}.sm\:cu-pl-4{padding-left:var(--canon-space-4)}.sm\:cu-pl-5{padding-left:var(--canon-space-5)}.sm\:cu-pl-6{padding-left:var(--canon-space-6)}.sm\:cu-pl-7{padding-left:var(--canon-space-7)}.sm\:cu-pl-8{padding-left:var(--canon-space-8)}.sm\:cu-pl-9{padding-left:var(--canon-space-9)}.sm\:cu-pl-10{padding-left:var(--canon-space-10)}.sm\:cu-pl-11{padding-left:var(--canon-space-11)}.sm\:cu-pl-12{padding-left:var(--canon-space-12)}.sm\:cu-pl-13{padding-left:var(--canon-space-13)}.sm\:cu-pl-14{padding-left:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-pl{padding-left:var(--pl-md)}.md\:cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.md\:cu-pl-1{padding-left:var(--canon-space-1)}.md\:cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.md\:cu-pl-2{padding-left:var(--canon-space-2)}.md\:cu-pl-3{padding-left:var(--canon-space-3)}.md\:cu-pl-4{padding-left:var(--canon-space-4)}.md\:cu-pl-5{padding-left:var(--canon-space-5)}.md\:cu-pl-6{padding-left:var(--canon-space-6)}.md\:cu-pl-7{padding-left:var(--canon-space-7)}.md\:cu-pl-8{padding-left:var(--canon-space-8)}.md\:cu-pl-9{padding-left:var(--canon-space-9)}.md\:cu-pl-10{padding-left:var(--canon-space-10)}.md\:cu-pl-11{padding-left:var(--canon-space-11)}.md\:cu-pl-12{padding-left:var(--canon-space-12)}.md\:cu-pl-13{padding-left:var(--canon-space-13)}.md\:cu-pl-14{padding-left:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-pl{padding-left:var(--pl-lg)}.lg\:cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.lg\:cu-pl-1{padding-left:var(--canon-space-1)}.lg\:cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.lg\:cu-pl-2{padding-left:var(--canon-space-2)}.lg\:cu-pl-3{padding-left:var(--canon-space-3)}.lg\:cu-pl-4{padding-left:var(--canon-space-4)}.lg\:cu-pl-5{padding-left:var(--canon-space-5)}.lg\:cu-pl-6{padding-left:var(--canon-space-6)}.lg\:cu-pl-7{padding-left:var(--canon-space-7)}.lg\:cu-pl-8{padding-left:var(--canon-space-8)}.lg\:cu-pl-9{padding-left:var(--canon-space-9)}.lg\:cu-pl-10{padding-left:var(--canon-space-10)}.lg\:cu-pl-11{padding-left:var(--canon-space-11)}.lg\:cu-pl-12{padding-left:var(--canon-space-12)}.lg\:cu-pl-13{padding-left:var(--canon-space-13)}.lg\:cu-pl-14{padding-left:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-pl{padding-left:var(--pl-xl)}.xl\:cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.xl\:cu-pl-1{padding-left:var(--canon-space-1)}.xl\:cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.xl\:cu-pl-2{padding-left:var(--canon-space-2)}.xl\:cu-pl-3{padding-left:var(--canon-space-3)}.xl\:cu-pl-4{padding-left:var(--canon-space-4)}.xl\:cu-pl-5{padding-left:var(--canon-space-5)}.xl\:cu-pl-6{padding-left:var(--canon-space-6)}.xl\:cu-pl-7{padding-left:var(--canon-space-7)}.xl\:cu-pl-8{padding-left:var(--canon-space-8)}.xl\:cu-pl-9{padding-left:var(--canon-space-9)}.xl\:cu-pl-10{padding-left:var(--canon-space-10)}.xl\:cu-pl-11{padding-left:var(--canon-space-11)}.xl\:cu-pl-12{padding-left:var(--canon-space-12)}.xl\:cu-pl-13{padding-left:var(--canon-space-13)}.xl\:cu-pl-14{padding-left:var(--canon-space-14)}}.cu-pr{padding-right:var(--pr)}.cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.cu-pr-1{padding-right:var(--canon-space-1)}.cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.cu-pr-2{padding-right:var(--canon-space-2)}.cu-pr-3{padding-right:var(--canon-space-3)}.cu-pr-4{padding-right:var(--canon-space-4)}.cu-pr-5{padding-right:var(--canon-space-5)}.cu-pr-6{padding-right:var(--canon-space-6)}.cu-pr-7{padding-right:var(--canon-space-7)}.cu-pr-8{padding-right:var(--canon-space-8)}.cu-pr-9{padding-right:var(--canon-space-9)}.cu-pr-10{padding-right:var(--canon-space-10)}.cu-pr-11{padding-right:var(--canon-space-11)}.cu-pr-12{padding-right:var(--canon-space-12)}.cu-pr-13{padding-right:var(--canon-space-13)}.cu-pr-14{padding-right:var(--canon-space-14)}@media (width>=640px){.xs\:cu-pr{padding-right:var(--pr-xs)}.xs\:cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.xs\:cu-pr-1{padding-right:var(--canon-space-1)}.xs\:cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.xs\:cu-pr-2{padding-right:var(--canon-space-2)}.xs\:cu-pr-3{padding-right:var(--canon-space-3)}.xs\:cu-pr-4{padding-right:var(--canon-space-4)}.xs\:cu-pr-5{padding-right:var(--canon-space-5)}.xs\:cu-pr-6{padding-right:var(--canon-space-6)}.xs\:cu-pr-7{padding-right:var(--canon-space-7)}.xs\:cu-pr-8{padding-right:var(--canon-space-8)}.xs\:cu-pr-9{padding-right:var(--canon-space-9)}.xs\:cu-pr-10{padding-right:var(--canon-space-10)}.xs\:cu-pr-11{padding-right:var(--canon-space-11)}.xs\:cu-pr-12{padding-right:var(--canon-space-12)}.xs\:cu-pr-13{padding-right:var(--canon-space-13)}.xs\:cu-pr-14{padding-right:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-pr{padding-right:var(--pr-sm)}.sm\:cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.sm\:cu-pr-1{padding-right:var(--canon-space-1)}.sm\:cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.sm\:cu-pr-2{padding-right:var(--canon-space-2)}.sm\:cu-pr-3{padding-right:var(--canon-space-3)}.sm\:cu-pr-4{padding-right:var(--canon-space-4)}.sm\:cu-pr-5{padding-right:var(--canon-space-5)}.sm\:cu-pr-6{padding-right:var(--canon-space-6)}.sm\:cu-pr-7{padding-right:var(--canon-space-7)}.sm\:cu-pr-8{padding-right:var(--canon-space-8)}.sm\:cu-pr-9{padding-right:var(--canon-space-9)}.sm\:cu-pr-10{padding-right:var(--canon-space-10)}.sm\:cu-pr-11{padding-right:var(--canon-space-11)}.sm\:cu-pr-12{padding-right:var(--canon-space-12)}.sm\:cu-pr-13{padding-right:var(--canon-space-13)}.sm\:cu-pr-14{padding-right:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-pr{padding-right:var(--pr-md)}.md\:cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.md\:cu-pr-1{padding-right:var(--canon-space-1)}.md\:cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.md\:cu-pr-2{padding-right:var(--canon-space-2)}.md\:cu-pr-3{padding-right:var(--canon-space-3)}.md\:cu-pr-4{padding-right:var(--canon-space-4)}.md\:cu-pr-5{padding-right:var(--canon-space-5)}.md\:cu-pr-6{padding-right:var(--canon-space-6)}.md\:cu-pr-7{padding-right:var(--canon-space-7)}.md\:cu-pr-8{padding-right:var(--canon-space-8)}.md\:cu-pr-9{padding-right:var(--canon-space-9)}.md\:cu-pr-10{padding-right:var(--canon-space-10)}.md\:cu-pr-11{padding-right:var(--canon-space-11)}.md\:cu-pr-12{padding-right:var(--canon-space-12)}.md\:cu-pr-13{padding-right:var(--canon-space-13)}.md\:cu-pr-14{padding-right:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-pr{padding-right:var(--pr-lg)}.lg\:cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.lg\:cu-pr-1{padding-right:var(--canon-space-1)}.lg\:cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.lg\:cu-pr-2{padding-right:var(--canon-space-2)}.lg\:cu-pr-3{padding-right:var(--canon-space-3)}.lg\:cu-pr-4{padding-right:var(--canon-space-4)}.lg\:cu-pr-5{padding-right:var(--canon-space-5)}.lg\:cu-pr-6{padding-right:var(--canon-space-6)}.lg\:cu-pr-7{padding-right:var(--canon-space-7)}.lg\:cu-pr-8{padding-right:var(--canon-space-8)}.lg\:cu-pr-9{padding-right:var(--canon-space-9)}.lg\:cu-pr-10{padding-right:var(--canon-space-10)}.lg\:cu-pr-11{padding-right:var(--canon-space-11)}.lg\:cu-pr-12{padding-right:var(--canon-space-12)}.lg\:cu-pr-13{padding-right:var(--canon-space-13)}.lg\:cu-pr-14{padding-right:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-pr{padding-right:var(--pr-xl)}.xl\:cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.xl\:cu-pr-1{padding-right:var(--canon-space-1)}.xl\:cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.xl\:cu-pr-2{padding-right:var(--canon-space-2)}.xl\:cu-pr-3{padding-right:var(--canon-space-3)}.xl\:cu-pr-4{padding-right:var(--canon-space-4)}.xl\:cu-pr-5{padding-right:var(--canon-space-5)}.xl\:cu-pr-6{padding-right:var(--canon-space-6)}.xl\:cu-pr-7{padding-right:var(--canon-space-7)}.xl\:cu-pr-8{padding-right:var(--canon-space-8)}.xl\:cu-pr-9{padding-right:var(--canon-space-9)}.xl\:cu-pr-10{padding-right:var(--canon-space-10)}.xl\:cu-pr-11{padding-right:var(--canon-space-11)}.xl\:cu-pr-12{padding-right:var(--canon-space-12)}.xl\:cu-pr-13{padding-right:var(--canon-space-13)}.xl\:cu-pr-14{padding-right:var(--canon-space-14)}}.cu-pt{padding-top:var(--pt)}.cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.cu-pt-1{padding-top:var(--canon-space-1)}.cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.cu-pt-2{padding-top:var(--canon-space-2)}.cu-pt-3{padding-top:var(--canon-space-3)}.cu-pt-4{padding-top:var(--canon-space-4)}.cu-pt-5{padding-top:var(--canon-space-5)}.cu-pt-6{padding-top:var(--canon-space-6)}.cu-pt-7{padding-top:var(--canon-space-7)}.cu-pt-8{padding-top:var(--canon-space-8)}.cu-pt-9{padding-top:var(--canon-space-9)}.cu-pt-10{padding-top:var(--canon-space-10)}.cu-pt-11{padding-top:var(--canon-space-11)}.cu-pt-12{padding-top:var(--canon-space-12)}.cu-pt-13{padding-top:var(--canon-space-13)}.cu-pt-14{padding-top:var(--canon-space-14)}@media (width>=640px){.xs\:cu-pt{padding-top:var(--pt-xs)}.xs\:cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.xs\:cu-pt-1{padding-top:var(--canon-space-1)}.xs\:cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.xs\:cu-pt-2{padding-top:var(--canon-space-2)}.xs\:cu-pt-3{padding-top:var(--canon-space-3)}.xs\:cu-pt-4{padding-top:var(--canon-space-4)}.xs\:cu-pt-5{padding-top:var(--canon-space-5)}.xs\:cu-pt-6{padding-top:var(--canon-space-6)}.xs\:cu-pt-7{padding-top:var(--canon-space-7)}.xs\:cu-pt-8{padding-top:var(--canon-space-8)}.xs\:cu-pt-9{padding-top:var(--canon-space-9)}.xs\:cu-pt-10{padding-top:var(--canon-space-10)}.xs\:cu-pt-11{padding-top:var(--canon-space-11)}.xs\:cu-pt-12{padding-top:var(--canon-space-12)}.xs\:cu-pt-13{padding-top:var(--canon-space-13)}.xs\:cu-pt-14{padding-top:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-pt{padding-top:var(--pt-sm)}.sm\:cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.sm\:cu-pt-1{padding-top:var(--canon-space-1)}.sm\:cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.sm\:cu-pt-2{padding-top:var(--canon-space-2)}.sm\:cu-pt-3{padding-top:var(--canon-space-3)}.sm\:cu-pt-4{padding-top:var(--canon-space-4)}.sm\:cu-pt-5{padding-top:var(--canon-space-5)}.sm\:cu-pt-6{padding-top:var(--canon-space-6)}.sm\:cu-pt-7{padding-top:var(--canon-space-7)}.sm\:cu-pt-8{padding-top:var(--canon-space-8)}.sm\:cu-pt-9{padding-top:var(--canon-space-9)}.sm\:cu-pt-10{padding-top:var(--canon-space-10)}.sm\:cu-pt-11{padding-top:var(--canon-space-11)}.sm\:cu-pt-12{padding-top:var(--canon-space-12)}.sm\:cu-pt-13{padding-top:var(--canon-space-13)}.sm\:cu-pt-14{padding-top:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-pt{padding-top:var(--pt-md)}.md\:cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.md\:cu-pt-1{padding-top:var(--canon-space-1)}.md\:cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.md\:cu-pt-2{padding-top:var(--canon-space-2)}.md\:cu-pt-3{padding-top:var(--canon-space-3)}.md\:cu-pt-4{padding-top:var(--canon-space-4)}.md\:cu-pt-5{padding-top:var(--canon-space-5)}.md\:cu-pt-6{padding-top:var(--canon-space-6)}.md\:cu-pt-7{padding-top:var(--canon-space-7)}.md\:cu-pt-8{padding-top:var(--canon-space-8)}.md\:cu-pt-9{padding-top:var(--canon-space-9)}.md\:cu-pt-10{padding-top:var(--canon-space-10)}.md\:cu-pt-11{padding-top:var(--canon-space-11)}.md\:cu-pt-12{padding-top:var(--canon-space-12)}.md\:cu-pt-13{padding-top:var(--canon-space-13)}.md\:cu-pt-14{padding-top:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-pt{padding-top:var(--pt-lg)}.lg\:cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.lg\:cu-pt-1{padding-top:var(--canon-space-1)}.lg\:cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.lg\:cu-pt-2{padding-top:var(--canon-space-2)}.lg\:cu-pt-3{padding-top:var(--canon-space-3)}.lg\:cu-pt-4{padding-top:var(--canon-space-4)}.lg\:cu-pt-5{padding-top:var(--canon-space-5)}.lg\:cu-pt-6{padding-top:var(--canon-space-6)}.lg\:cu-pt-7{padding-top:var(--canon-space-7)}.lg\:cu-pt-8{padding-top:var(--canon-space-8)}.lg\:cu-pt-9{padding-top:var(--canon-space-9)}.lg\:cu-pt-10{padding-top:var(--canon-space-10)}.lg\:cu-pt-11{padding-top:var(--canon-space-11)}.lg\:cu-pt-12{padding-top:var(--canon-space-12)}.lg\:cu-pt-13{padding-top:var(--canon-space-13)}.lg\:cu-pt-14{padding-top:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-pt{padding-top:var(--pt-xl)}.xl\:cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.xl\:cu-pt-1{padding-top:var(--canon-space-1)}.xl\:cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.xl\:cu-pt-2{padding-top:var(--canon-space-2)}.xl\:cu-pt-3{padding-top:var(--canon-space-3)}.xl\:cu-pt-4{padding-top:var(--canon-space-4)}.xl\:cu-pt-5{padding-top:var(--canon-space-5)}.xl\:cu-pt-6{padding-top:var(--canon-space-6)}.xl\:cu-pt-7{padding-top:var(--canon-space-7)}.xl\:cu-pt-8{padding-top:var(--canon-space-8)}.xl\:cu-pt-9{padding-top:var(--canon-space-9)}.xl\:cu-pt-10{padding-top:var(--canon-space-10)}.xl\:cu-pt-11{padding-top:var(--canon-space-11)}.xl\:cu-pt-12{padding-top:var(--canon-space-12)}.xl\:cu-pt-13{padding-top:var(--canon-space-13)}.xl\:cu-pt-14{padding-top:var(--canon-space-14)}}.cu-pb{padding-bottom:var(--pb)}.cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.cu-pb-1{padding-bottom:var(--canon-space-1)}.cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.cu-pb-2{padding-bottom:var(--canon-space-2)}.cu-pb-3{padding-bottom:var(--canon-space-3)}.cu-pb-4{padding-bottom:var(--canon-space-4)}.cu-pb-5{padding-bottom:var(--canon-space-5)}.cu-pb-6{padding-bottom:var(--canon-space-6)}.cu-pb-7{padding-bottom:var(--canon-space-7)}.cu-pb-8{padding-bottom:var(--canon-space-8)}.cu-pb-9{padding-bottom:var(--canon-space-9)}.cu-pb-10{padding-bottom:var(--canon-space-10)}.cu-pb-11{padding-bottom:var(--canon-space-11)}.cu-pb-12{padding-bottom:var(--canon-space-12)}.cu-pb-13{padding-bottom:var(--canon-space-13)}.cu-pb-14{padding-bottom:var(--canon-space-14)}@media (width>=640px){.xs\:cu-pb{padding-bottom:var(--pb-xs)}.xs\:cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.xs\:cu-pb-1{padding-bottom:var(--canon-space-1)}.xs\:cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.xs\:cu-pb-2{padding-bottom:var(--canon-space-2)}.xs\:cu-pb-3{padding-bottom:var(--canon-space-3)}.xs\:cu-pb-4{padding-bottom:var(--canon-space-4)}.xs\:cu-pb-5{padding-bottom:var(--canon-space-5)}.xs\:cu-pb-6{padding-bottom:var(--canon-space-6)}.xs\:cu-pb-7{padding-bottom:var(--canon-space-7)}.xs\:cu-pb-8{padding-bottom:var(--canon-space-8)}.xs\:cu-pb-9{padding-bottom:var(--canon-space-9)}.xs\:cu-pb-10{padding-bottom:var(--canon-space-10)}.xs\:cu-pb-11{padding-bottom:var(--canon-space-11)}.xs\:cu-pb-12{padding-bottom:var(--canon-space-12)}.xs\:cu-pb-13{padding-bottom:var(--canon-space-13)}.xs\:cu-pb-14{padding-bottom:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-pb{padding-bottom:var(--pb-sm)}.sm\:cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.sm\:cu-pb-1{padding-bottom:var(--canon-space-1)}.sm\:cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.sm\:cu-pb-2{padding-bottom:var(--canon-space-2)}.sm\:cu-pb-3{padding-bottom:var(--canon-space-3)}.sm\:cu-pb-4{padding-bottom:var(--canon-space-4)}.sm\:cu-pb-5{padding-bottom:var(--canon-space-5)}.sm\:cu-pb-6{padding-bottom:var(--canon-space-6)}.sm\:cu-pb-7{padding-bottom:var(--canon-space-7)}.sm\:cu-pb-8{padding-bottom:var(--canon-space-8)}.sm\:cu-pb-9{padding-bottom:var(--canon-space-9)}.sm\:cu-pb-10{padding-bottom:var(--canon-space-10)}.sm\:cu-pb-11{padding-bottom:var(--canon-space-11)}.sm\:cu-pb-12{padding-bottom:var(--canon-space-12)}.sm\:cu-pb-13{padding-bottom:var(--canon-space-13)}.sm\:cu-pb-14{padding-bottom:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-pb{padding-bottom:var(--pb-md)}.md\:cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.md\:cu-pb-1{padding-bottom:var(--canon-space-1)}.md\:cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.md\:cu-pb-2{padding-bottom:var(--canon-space-2)}.md\:cu-pb-3{padding-bottom:var(--canon-space-3)}.md\:cu-pb-4{padding-bottom:var(--canon-space-4)}.md\:cu-pb-5{padding-bottom:var(--canon-space-5)}.md\:cu-pb-6{padding-bottom:var(--canon-space-6)}.md\:cu-pb-7{padding-bottom:var(--canon-space-7)}.md\:cu-pb-8{padding-bottom:var(--canon-space-8)}.md\:cu-pb-9{padding-bottom:var(--canon-space-9)}.md\:cu-pb-10{padding-bottom:var(--canon-space-10)}.md\:cu-pb-11{padding-bottom:var(--canon-space-11)}.md\:cu-pb-12{padding-bottom:var(--canon-space-12)}.md\:cu-pb-13{padding-bottom:var(--canon-space-13)}.md\:cu-pb-14{padding-bottom:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-pb{padding-bottom:var(--pb-lg)}.lg\:cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.lg\:cu-pb-1{padding-bottom:var(--canon-space-1)}.lg\:cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.lg\:cu-pb-2{padding-bottom:var(--canon-space-2)}.lg\:cu-pb-3{padding-bottom:var(--canon-space-3)}.lg\:cu-pb-4{padding-bottom:var(--canon-space-4)}.lg\:cu-pb-5{padding-bottom:var(--canon-space-5)}.lg\:cu-pb-6{padding-bottom:var(--canon-space-6)}.lg\:cu-pb-7{padding-bottom:var(--canon-space-7)}.lg\:cu-pb-8{padding-bottom:var(--canon-space-8)}.lg\:cu-pb-9{padding-bottom:var(--canon-space-9)}.lg\:cu-pb-10{padding-bottom:var(--canon-space-10)}.lg\:cu-pb-11{padding-bottom:var(--canon-space-11)}.lg\:cu-pb-12{padding-bottom:var(--canon-space-12)}.lg\:cu-pb-13{padding-bottom:var(--canon-space-13)}.lg\:cu-pb-14{padding-bottom:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-pb{padding-bottom:var(--pb-xl)}.xl\:cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.xl\:cu-pb-1{padding-bottom:var(--canon-space-1)}.xl\:cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.xl\:cu-pb-2{padding-bottom:var(--canon-space-2)}.xl\:cu-pb-3{padding-bottom:var(--canon-space-3)}.xl\:cu-pb-4{padding-bottom:var(--canon-space-4)}.xl\:cu-pb-5{padding-bottom:var(--canon-space-5)}.xl\:cu-pb-6{padding-bottom:var(--canon-space-6)}.xl\:cu-pb-7{padding-bottom:var(--canon-space-7)}.xl\:cu-pb-8{padding-bottom:var(--canon-space-8)}.xl\:cu-pb-9{padding-bottom:var(--canon-space-9)}.xl\:cu-pb-10{padding-bottom:var(--canon-space-10)}.xl\:cu-pb-11{padding-bottom:var(--canon-space-11)}.xl\:cu-pb-12{padding-bottom:var(--canon-space-12)}.xl\:cu-pb-13{padding-bottom:var(--canon-space-13)}.xl\:cu-pb-14{padding-bottom:var(--canon-space-14)}}.cu-py{padding-top:var(--py);padding-bottom:var(--py)}.cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}@media (width>=640px){.xs\:cu-py{padding-top:var(--py-xs);padding-bottom:var(--py-xs)}.xs\:cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.xs\:cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.xs\:cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.xs\:cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.xs\:cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.xs\:cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.xs\:cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.xs\:cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.xs\:cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.xs\:cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.xs\:cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.xs\:cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.xs\:cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.xs\:cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.xs\:cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.xs\:cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-py{padding-top:var(--py-sm);padding-bottom:var(--py-sm)}.sm\:cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.sm\:cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.sm\:cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.sm\:cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.sm\:cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.sm\:cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.sm\:cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.sm\:cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.sm\:cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.sm\:cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.sm\:cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.sm\:cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.sm\:cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.sm\:cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.sm\:cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.sm\:cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-py{padding-top:var(--py-md);padding-bottom:var(--py-md)}.md\:cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.md\:cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.md\:cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.md\:cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.md\:cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.md\:cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.md\:cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.md\:cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.md\:cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.md\:cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.md\:cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.md\:cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.md\:cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.md\:cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.md\:cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.md\:cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-py{padding-top:var(--py-lg);padding-bottom:var(--py-lg)}.lg\:cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.lg\:cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.lg\:cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.lg\:cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.lg\:cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.lg\:cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.lg\:cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.lg\:cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.lg\:cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.lg\:cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.lg\:cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.lg\:cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.lg\:cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.lg\:cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.lg\:cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.lg\:cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-py{padding-top:var(--py-xl);padding-bottom:var(--py-xl)}.xl\:cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.xl\:cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.xl\:cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.xl\:cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.xl\:cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.xl\:cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.xl\:cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.xl\:cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.xl\:cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.xl\:cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.xl\:cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.xl\:cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.xl\:cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.xl\:cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.xl\:cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.xl\:cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}}.cu-px{padding-left:var(--px);padding-right:var(--px)}.cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}@media (width>=640px){.xs\:cu-px{padding-left:var(--px-xs);padding-right:var(--px-xs)}.xs\:cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.xs\:cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.xs\:cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.xs\:cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.xs\:cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.xs\:cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.xs\:cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.xs\:cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.xs\:cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.xs\:cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.xs\:cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.xs\:cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.xs\:cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.xs\:cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.xs\:cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.xs\:cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-px{padding-left:var(--px-sm);padding-right:var(--px-sm)}.sm\:cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.sm\:cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.sm\:cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.sm\:cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.sm\:cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.sm\:cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.sm\:cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.sm\:cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.sm\:cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.sm\:cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.sm\:cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.sm\:cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.sm\:cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.sm\:cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.sm\:cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.sm\:cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-px{padding-left:var(--px-md);padding-right:var(--px-md)}.md\:cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.md\:cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.md\:cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.md\:cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.md\:cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.md\:cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.md\:cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.md\:cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.md\:cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.md\:cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.md\:cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.md\:cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.md\:cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.md\:cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.md\:cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.md\:cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-px{padding-left:var(--px-lg);padding-right:var(--px-lg)}.lg\:cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.lg\:cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.lg\:cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.lg\:cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.lg\:cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.lg\:cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.lg\:cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.lg\:cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.lg\:cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.lg\:cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.lg\:cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.lg\:cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.lg\:cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.lg\:cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.lg\:cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.lg\:cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-px{padding-left:var(--px-xl);padding-right:var(--px-xl)}.xl\:cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.xl\:cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.xl\:cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.xl\:cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.xl\:cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.xl\:cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.xl\:cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.xl\:cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.xl\:cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.xl\:cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.xl\:cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.xl\:cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.xl\:cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.xl\:cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.xl\:cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.xl\:cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}}.cu-m{margin:var(--m)}.cu-m-0\.5{margin:var(--canon-space-0_5)}.cu-m-1{margin:var(--canon-space-1)}.cu-m-1\.5{margin:var(--canon-space-1_5)}.cu-m-2{margin:var(--canon-space-2)}.cu-m-3{margin:var(--canon-space-3)}.cu-m-4{margin:var(--canon-space-4)}.cu-m-5{margin:var(--canon-space-5)}.cu-m-6{margin:var(--canon-space-6)}.cu-m-7{margin:var(--canon-space-7)}.cu-m-8{margin:var(--canon-space-8)}.cu-m-9{margin:var(--canon-space-9)}.cu-m-10{margin:var(--canon-space-10)}.cu-m-11{margin:var(--canon-space-11)}.cu-m-12{margin:var(--canon-space-12)}.cu-m-13{margin:var(--canon-space-13)}.cu-m-14{margin:var(--canon-space-14)}@media (width>=640px){.xs\:cu-m{margin:var(--p-xs)}.xs\:cu-m-0\.5{margin:var(--canon-space-0_5)}.xs\:cu-m-1{margin:var(--canon-space-1)}.xs\:cu-m-1\.5{margin:var(--canon-space-1_5)}.xs\:cu-m-2{margin:var(--canon-space-2)}.xs\:cu-m-3{margin:var(--canon-space-3)}.xs\:cu-m-4{margin:var(--canon-space-4)}.xs\:cu-m-5{margin:var(--canon-space-5)}.xs\:cu-m-6{margin:var(--canon-space-6)}.xs\:cu-m-7{margin:var(--canon-space-7)}.xs\:cu-m-8{margin:var(--canon-space-8)}.xs\:cu-m-9{margin:var(--canon-space-9)}.xs\:cu-m-10{margin:var(--canon-space-10)}.xs\:cu-m-11{margin:var(--canon-space-11)}.xs\:cu-m-12{margin:var(--canon-space-12)}.xs\:cu-m-13{margin:var(--canon-space-13)}.xs\:cu-m-14{margin:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-m{margin:var(--p-sm)}.sm\:cu-m-0\.5{margin:var(--canon-space-0_5)}.sm\:cu-m-1{margin:var(--canon-space-1)}.sm\:cu-m-1\.5{margin:var(--canon-space-1_5)}.sm\:cu-m-2{margin:var(--canon-space-2)}.sm\:cu-m-3{margin:var(--canon-space-3)}.sm\:cu-m-4{margin:var(--canon-space-4)}.sm\:cu-m-5{margin:var(--canon-space-5)}.sm\:cu-m-6{margin:var(--canon-space-6)}.sm\:cu-m-7{margin:var(--canon-space-7)}.sm\:cu-m-8{margin:var(--canon-space-8)}.sm\:cu-m-9{margin:var(--canon-space-9)}.sm\:cu-m-10{margin:var(--canon-space-10)}.sm\:cu-m-11{margin:var(--canon-space-11)}.sm\:cu-m-12{margin:var(--canon-space-12)}.sm\:cu-m-13{margin:var(--canon-space-13)}.sm\:cu-m-14{margin:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-m{margin:var(--p-md)}.md\:cu-m-0\.5{margin:var(--canon-space-0_5)}.md\:cu-m-1{margin:var(--canon-space-1)}.md\:cu-m-1\.5{margin:var(--canon-space-1_5)}.md\:cu-m-2{margin:var(--canon-space-2)}.md\:cu-m-3{margin:var(--canon-space-3)}.md\:cu-m-4{margin:var(--canon-space-4)}.md\:cu-m-5{margin:var(--canon-space-5)}.md\:cu-m-6{margin:var(--canon-space-6)}.md\:cu-m-7{margin:var(--canon-space-7)}.md\:cu-m-8{margin:var(--canon-space-8)}.md\:cu-m-9{margin:var(--canon-space-9)}.md\:cu-m-10{margin:var(--canon-space-10)}.md\:cu-m-11{margin:var(--canon-space-11)}.md\:cu-m-12{margin:var(--canon-space-12)}.md\:cu-m-13{margin:var(--canon-space-13)}.md\:cu-m-14{margin:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-m{margin:var(--p-lg)}.lg\:cu-m-0\.5{margin:var(--canon-space-0_5)}.lg\:cu-m-1{margin:var(--canon-space-1)}.lg\:cu-m-1\.5{margin:var(--canon-space-1_5)}.lg\:cu-m-2{margin:var(--canon-space-2)}.lg\:cu-m-3{margin:var(--canon-space-3)}.lg\:cu-m-4{margin:var(--canon-space-4)}.lg\:cu-m-5{margin:var(--canon-space-5)}.lg\:cu-m-6{margin:var(--canon-space-6)}.lg\:cu-m-7{margin:var(--canon-space-7)}.lg\:cu-m-8{margin:var(--canon-space-8)}.lg\:cu-m-9{margin:var(--canon-space-9)}.lg\:cu-m-10{margin:var(--canon-space-10)}.lg\:cu-m-11{margin:var(--canon-space-11)}.lg\:cu-m-12{margin:var(--canon-space-12)}.lg\:cu-m-13{margin:var(--canon-space-13)}.lg\:cu-m-14{margin:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-m{margin:var(--p-xl)}.xl\:cu-m-0\.5{margin:var(--canon-space-0_5)}.xl\:cu-m-1{margin:var(--canon-space-1)}.xl\:cu-m-1\.5{margin:var(--canon-space-1_5)}.xl\:cu-m-2{margin:var(--canon-space-2)}.xl\:cu-m-3{margin:var(--canon-space-3)}.xl\:cu-m-4{margin:var(--canon-space-4)}.xl\:cu-m-5{margin:var(--canon-space-5)}.xl\:cu-m-6{margin:var(--canon-space-6)}.xl\:cu-m-7{margin:var(--canon-space-7)}.xl\:cu-m-8{margin:var(--canon-space-8)}.xl\:cu-m-9{margin:var(--canon-space-9)}.xl\:cu-m-10{margin:var(--canon-space-10)}.xl\:cu-m-11{margin:var(--canon-space-11)}.xl\:cu-m-12{margin:var(--canon-space-12)}.xl\:cu-m-13{margin:var(--canon-space-13)}.xl\:cu-m-14{margin:var(--canon-space-14)}}.cu-ml{margin-left:var(--ml)}.cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.cu-ml-1{margin-left:var(--canon-space-1)}.cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.cu-ml-2{margin-left:var(--canon-space-2)}.cu-ml-3{margin-left:var(--canon-space-3)}.cu-ml-4{margin-left:var(--canon-space-4)}.cu-ml-5{margin-left:var(--canon-space-5)}.cu-ml-6{margin-left:var(--canon-space-6)}.cu-ml-7{margin-left:var(--canon-space-7)}.cu-ml-8{margin-left:var(--canon-space-8)}.cu-ml-9{margin-left:var(--canon-space-9)}.cu-ml-10{margin-left:var(--canon-space-10)}.cu-ml-11{margin-left:var(--canon-space-11)}.cu-ml-12{margin-left:var(--canon-space-12)}.cu-ml-13{margin-left:var(--canon-space-13)}.cu-ml-14{margin-left:var(--canon-space-14)}@media (width>=640px){.xs\:cu-ml{margin-left:var(--pl-xs)}.xs\:cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.xs\:cu-ml-1{margin-left:var(--canon-space-1)}.xs\:cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.xs\:cu-ml-2{margin-left:var(--canon-space-2)}.xs\:cu-ml-3{margin-left:var(--canon-space-3)}.xs\:cu-ml-4{margin-left:var(--canon-space-4)}.xs\:cu-ml-5{margin-left:var(--canon-space-5)}.xs\:cu-ml-6{margin-left:var(--canon-space-6)}.xs\:cu-ml-7{margin-left:var(--canon-space-7)}.xs\:cu-ml-8{margin-left:var(--canon-space-8)}.xs\:cu-ml-9{margin-left:var(--canon-space-9)}.xs\:cu-ml-10{margin-left:var(--canon-space-10)}.xs\:cu-ml-11{margin-left:var(--canon-space-11)}.xs\:cu-ml-12{margin-left:var(--canon-space-12)}.xs\:cu-ml-13{margin-left:var(--canon-space-13)}.xs\:cu-ml-14{margin-left:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-ml{margin-left:var(--pl-sm)}.sm\:cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.sm\:cu-ml-1{margin-left:var(--canon-space-1)}.sm\:cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.sm\:cu-ml-2{margin-left:var(--canon-space-2)}.sm\:cu-ml-3{margin-left:var(--canon-space-3)}.sm\:cu-ml-4{margin-left:var(--canon-space-4)}.sm\:cu-ml-5{margin-left:var(--canon-space-5)}.sm\:cu-ml-6{margin-left:var(--canon-space-6)}.sm\:cu-ml-7{margin-left:var(--canon-space-7)}.sm\:cu-ml-8{margin-left:var(--canon-space-8)}.sm\:cu-ml-9{margin-left:var(--canon-space-9)}.sm\:cu-ml-10{margin-left:var(--canon-space-10)}.sm\:cu-ml-11{margin-left:var(--canon-space-11)}.sm\:cu-ml-12{margin-left:var(--canon-space-12)}.sm\:cu-ml-13{margin-left:var(--canon-space-13)}.sm\:cu-ml-14{margin-left:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-ml{margin-left:var(--pl-md)}.md\:cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.md\:cu-ml-1{margin-left:var(--canon-space-1)}.md\:cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.md\:cu-ml-2{margin-left:var(--canon-space-2)}.md\:cu-ml-3{margin-left:var(--canon-space-3)}.md\:cu-ml-4{margin-left:var(--canon-space-4)}.md\:cu-ml-5{margin-left:var(--canon-space-5)}.md\:cu-ml-6{margin-left:var(--canon-space-6)}.md\:cu-ml-7{margin-left:var(--canon-space-7)}.md\:cu-ml-8{margin-left:var(--canon-space-8)}.md\:cu-ml-9{margin-left:var(--canon-space-9)}.md\:cu-ml-10{margin-left:var(--canon-space-10)}.md\:cu-ml-11{margin-left:var(--canon-space-11)}.md\:cu-ml-12{margin-left:var(--canon-space-12)}.md\:cu-ml-13{margin-left:var(--canon-space-13)}.md\:cu-ml-14{margin-left:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-ml{margin-left:var(--pl-lg)}.lg\:cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.lg\:cu-ml-1{margin-left:var(--canon-space-1)}.lg\:cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.lg\:cu-ml-2{margin-left:var(--canon-space-2)}.lg\:cu-ml-3{margin-left:var(--canon-space-3)}.lg\:cu-ml-4{margin-left:var(--canon-space-4)}.lg\:cu-ml-5{margin-left:var(--canon-space-5)}.lg\:cu-ml-6{margin-left:var(--canon-space-6)}.lg\:cu-ml-7{margin-left:var(--canon-space-7)}.lg\:cu-ml-8{margin-left:var(--canon-space-8)}.lg\:cu-ml-9{margin-left:var(--canon-space-9)}.lg\:cu-ml-10{margin-left:var(--canon-space-10)}.lg\:cu-ml-11{margin-left:var(--canon-space-11)}.lg\:cu-ml-12{margin-left:var(--canon-space-12)}.lg\:cu-ml-13{margin-left:var(--canon-space-13)}.lg\:cu-ml-14{margin-left:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-ml{margin-left:var(--pl-xl)}.xl\:cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.xl\:cu-ml-1{margin-left:var(--canon-space-1)}.xl\:cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.xl\:cu-ml-2{margin-left:var(--canon-space-2)}.xl\:cu-ml-3{margin-left:var(--canon-space-3)}.xl\:cu-ml-4{margin-left:var(--canon-space-4)}.xl\:cu-ml-5{margin-left:var(--canon-space-5)}.xl\:cu-ml-6{margin-left:var(--canon-space-6)}.xl\:cu-ml-7{margin-left:var(--canon-space-7)}.xl\:cu-ml-8{margin-left:var(--canon-space-8)}.xl\:cu-ml-9{margin-left:var(--canon-space-9)}.xl\:cu-ml-10{margin-left:var(--canon-space-10)}.xl\:cu-ml-11{margin-left:var(--canon-space-11)}.xl\:cu-ml-12{margin-left:var(--canon-space-12)}.xl\:cu-ml-13{margin-left:var(--canon-space-13)}.xl\:cu-ml-14{margin-left:var(--canon-space-14)}}.cu-mr{margin-right:var(--mr)}.cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.cu-mr-1{margin-right:var(--canon-space-1)}.cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.cu-mr-2{margin-right:var(--canon-space-2)}.cu-mr-3{margin-right:var(--canon-space-3)}.cu-mr-4{margin-right:var(--canon-space-4)}.cu-mr-5{margin-right:var(--canon-space-5)}.cu-mr-6{margin-right:var(--canon-space-6)}.cu-mr-7{margin-right:var(--canon-space-7)}.cu-mr-8{margin-right:var(--canon-space-8)}.cu-mr-9{margin-right:var(--canon-space-9)}.cu-mr-10{margin-right:var(--canon-space-10)}.cu-mr-11{margin-right:var(--canon-space-11)}.cu-mr-12{margin-right:var(--canon-space-12)}.cu-mr-13{margin-right:var(--canon-space-13)}.cu-mr-14{margin-right:var(--canon-space-14)}@media (width>=640px){.xs\:cu-mr{margin-right:var(--pr-xs)}.xs\:cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.xs\:cu-mr-1{margin-right:var(--canon-space-1)}.xs\:cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.xs\:cu-mr-2{margin-right:var(--canon-space-2)}.xs\:cu-mr-3{margin-right:var(--canon-space-3)}.xs\:cu-mr-4{margin-right:var(--canon-space-4)}.xs\:cu-mr-5{margin-right:var(--canon-space-5)}.xs\:cu-mr-6{margin-right:var(--canon-space-6)}.xs\:cu-mr-7{margin-right:var(--canon-space-7)}.xs\:cu-mr-8{margin-right:var(--canon-space-8)}.xs\:cu-mr-9{margin-right:var(--canon-space-9)}.xs\:cu-mr-10{margin-right:var(--canon-space-10)}.xs\:cu-mr-11{margin-right:var(--canon-space-11)}.xs\:cu-mr-12{margin-right:var(--canon-space-12)}.xs\:cu-mr-13{margin-right:var(--canon-space-13)}.xs\:cu-mr-14{margin-right:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-mr{margin-right:var(--pr-sm)}.sm\:cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.sm\:cu-mr-1{margin-right:var(--canon-space-1)}.sm\:cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.sm\:cu-mr-2{margin-right:var(--canon-space-2)}.sm\:cu-mr-3{margin-right:var(--canon-space-3)}.sm\:cu-mr-4{margin-right:var(--canon-space-4)}.sm\:cu-mr-5{margin-right:var(--canon-space-5)}.sm\:cu-mr-6{margin-right:var(--canon-space-6)}.sm\:cu-mr-7{margin-right:var(--canon-space-7)}.sm\:cu-mr-8{margin-right:var(--canon-space-8)}.sm\:cu-mr-9{margin-right:var(--canon-space-9)}.sm\:cu-mr-10{margin-right:var(--canon-space-10)}.sm\:cu-mr-11{margin-right:var(--canon-space-11)}.sm\:cu-mr-12{margin-right:var(--canon-space-12)}.sm\:cu-mr-13{margin-right:var(--canon-space-13)}.sm\:cu-mr-14{margin-right:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-mr{margin-right:var(--pr-md)}.md\:cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.md\:cu-mr-1{margin-right:var(--canon-space-1)}.md\:cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.md\:cu-mr-2{margin-right:var(--canon-space-2)}.md\:cu-mr-3{margin-right:var(--canon-space-3)}.md\:cu-mr-4{margin-right:var(--canon-space-4)}.md\:cu-mr-5{margin-right:var(--canon-space-5)}.md\:cu-mr-6{margin-right:var(--canon-space-6)}.md\:cu-mr-7{margin-right:var(--canon-space-7)}.md\:cu-mr-8{margin-right:var(--canon-space-8)}.md\:cu-mr-9{margin-right:var(--canon-space-9)}.md\:cu-mr-10{margin-right:var(--canon-space-10)}.md\:cu-mr-11{margin-right:var(--canon-space-11)}.md\:cu-mr-12{margin-right:var(--canon-space-12)}.md\:cu-mr-13{margin-right:var(--canon-space-13)}.md\:cu-mr-14{margin-right:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-mr{margin-right:var(--pr-lg)}.lg\:cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.lg\:cu-mr-1{margin-right:var(--canon-space-1)}.lg\:cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.lg\:cu-mr-2{margin-right:var(--canon-space-2)}.lg\:cu-mr-3{margin-right:var(--canon-space-3)}.lg\:cu-mr-4{margin-right:var(--canon-space-4)}.lg\:cu-mr-5{margin-right:var(--canon-space-5)}.lg\:cu-mr-6{margin-right:var(--canon-space-6)}.lg\:cu-mr-7{margin-right:var(--canon-space-7)}.lg\:cu-mr-8{margin-right:var(--canon-space-8)}.lg\:cu-mr-9{margin-right:var(--canon-space-9)}.lg\:cu-mr-10{margin-right:var(--canon-space-10)}.lg\:cu-mr-11{margin-right:var(--canon-space-11)}.lg\:cu-mr-12{margin-right:var(--canon-space-12)}.lg\:cu-mr-13{margin-right:var(--canon-space-13)}.lg\:cu-mr-14{margin-right:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-mr{margin-right:var(--pr-xl)}.xl\:cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.xl\:cu-mr-1{margin-right:var(--canon-space-1)}.xl\:cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.xl\:cu-mr-2{margin-right:var(--canon-space-2)}.xl\:cu-mr-3{margin-right:var(--canon-space-3)}.xl\:cu-mr-4{margin-right:var(--canon-space-4)}.xl\:cu-mr-5{margin-right:var(--canon-space-5)}.xl\:cu-mr-6{margin-right:var(--canon-space-6)}.xl\:cu-mr-7{margin-right:var(--canon-space-7)}.xl\:cu-mr-8{margin-right:var(--canon-space-8)}.xl\:cu-mr-9{margin-right:var(--canon-space-9)}.xl\:cu-mr-10{margin-right:var(--canon-space-10)}.xl\:cu-mr-11{margin-right:var(--canon-space-11)}.xl\:cu-mr-12{margin-right:var(--canon-space-12)}.xl\:cu-mr-13{margin-right:var(--canon-space-13)}.xl\:cu-mr-14{margin-right:var(--canon-space-14)}}.cu-mt{margin-top:var(--mt)}.cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.cu-mt-1{margin-top:var(--canon-space-1)}.cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.cu-mt-2{margin-top:var(--canon-space-2)}.cu-mt-3{margin-top:var(--canon-space-3)}.cu-mt-4{margin-top:var(--canon-space-4)}.cu-mt-5{margin-top:var(--canon-space-5)}.cu-mt-6{margin-top:var(--canon-space-6)}.cu-mt-7{margin-top:var(--canon-space-7)}.cu-mt-8{margin-top:var(--canon-space-8)}.cu-mt-9{margin-top:var(--canon-space-9)}.cu-mt-10{margin-top:var(--canon-space-10)}.cu-mt-11{margin-top:var(--canon-space-11)}.cu-mt-12{margin-top:var(--canon-space-12)}.cu-mt-13{margin-top:var(--canon-space-13)}.cu-mt-14{margin-top:var(--canon-space-14)}@media (width>=640px){.xs\:cu-mt{margin-top:var(--pt-xs)}.xs\:cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.xs\:cu-mt-1{margin-top:var(--canon-space-1)}.xs\:cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.xs\:cu-mt-2{margin-top:var(--canon-space-2)}.xs\:cu-mt-3{margin-top:var(--canon-space-3)}.xs\:cu-mt-4{margin-top:var(--canon-space-4)}.xs\:cu-mt-5{margin-top:var(--canon-space-5)}.xs\:cu-mt-6{margin-top:var(--canon-space-6)}.xs\:cu-mt-7{margin-top:var(--canon-space-7)}.xs\:cu-mt-8{margin-top:var(--canon-space-8)}.xs\:cu-mt-9{margin-top:var(--canon-space-9)}.xs\:cu-mt-10{margin-top:var(--canon-space-10)}.xs\:cu-mt-11{margin-top:var(--canon-space-11)}.xs\:cu-mt-12{margin-top:var(--canon-space-12)}.xs\:cu-mt-13{margin-top:var(--canon-space-13)}.xs\:cu-mt-14{margin-top:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-mt{margin-top:var(--pt-sm)}.sm\:cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.sm\:cu-mt-1{margin-top:var(--canon-space-1)}.sm\:cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.sm\:cu-mt-2{margin-top:var(--canon-space-2)}.sm\:cu-mt-3{margin-top:var(--canon-space-3)}.sm\:cu-mt-4{margin-top:var(--canon-space-4)}.sm\:cu-mt-5{margin-top:var(--canon-space-5)}.sm\:cu-mt-6{margin-top:var(--canon-space-6)}.sm\:cu-mt-7{margin-top:var(--canon-space-7)}.sm\:cu-mt-8{margin-top:var(--canon-space-8)}.sm\:cu-mt-9{margin-top:var(--canon-space-9)}.sm\:cu-mt-10{margin-top:var(--canon-space-10)}.sm\:cu-mt-11{margin-top:var(--canon-space-11)}.sm\:cu-mt-12{margin-top:var(--canon-space-12)}.sm\:cu-mt-13{margin-top:var(--canon-space-13)}.sm\:cu-mt-14{margin-top:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-mt{margin-top:var(--pt-md)}.md\:cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.md\:cu-mt-1{margin-top:var(--canon-space-1)}.md\:cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.md\:cu-mt-2{margin-top:var(--canon-space-2)}.md\:cu-mt-3{margin-top:var(--canon-space-3)}.md\:cu-mt-4{margin-top:var(--canon-space-4)}.md\:cu-mt-5{margin-top:var(--canon-space-5)}.md\:cu-mt-6{margin-top:var(--canon-space-6)}.md\:cu-mt-7{margin-top:var(--canon-space-7)}.md\:cu-mt-8{margin-top:var(--canon-space-8)}.md\:cu-mt-9{margin-top:var(--canon-space-9)}.md\:cu-mt-10{margin-top:var(--canon-space-10)}.md\:cu-mt-11{margin-top:var(--canon-space-11)}.md\:cu-mt-12{margin-top:var(--canon-space-12)}.md\:cu-mt-13{margin-top:var(--canon-space-13)}.md\:cu-mt-14{margin-top:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-mt{margin-top:var(--pt-lg)}.lg\:cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.lg\:cu-mt-1{margin-top:var(--canon-space-1)}.lg\:cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.lg\:cu-mt-2{margin-top:var(--canon-space-2)}.lg\:cu-mt-3{margin-top:var(--canon-space-3)}.lg\:cu-mt-4{margin-top:var(--canon-space-4)}.lg\:cu-mt-5{margin-top:var(--canon-space-5)}.lg\:cu-mt-6{margin-top:var(--canon-space-6)}.lg\:cu-mt-7{margin-top:var(--canon-space-7)}.lg\:cu-mt-8{margin-top:var(--canon-space-8)}.lg\:cu-mt-9{margin-top:var(--canon-space-9)}.lg\:cu-mt-10{margin-top:var(--canon-space-10)}.lg\:cu-mt-11{margin-top:var(--canon-space-11)}.lg\:cu-mt-12{margin-top:var(--canon-space-12)}.lg\:cu-mt-13{margin-top:var(--canon-space-13)}.lg\:cu-mt-14{margin-top:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-mt{margin-top:var(--pt-xl)}.xl\:cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.xl\:cu-mt-1{margin-top:var(--canon-space-1)}.xl\:cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.xl\:cu-mt-2{margin-top:var(--canon-space-2)}.xl\:cu-mt-3{margin-top:var(--canon-space-3)}.xl\:cu-mt-4{margin-top:var(--canon-space-4)}.xl\:cu-mt-5{margin-top:var(--canon-space-5)}.xl\:cu-mt-6{margin-top:var(--canon-space-6)}.xl\:cu-mt-7{margin-top:var(--canon-space-7)}.xl\:cu-mt-8{margin-top:var(--canon-space-8)}.xl\:cu-mt-9{margin-top:var(--canon-space-9)}.xl\:cu-mt-10{margin-top:var(--canon-space-10)}.xl\:cu-mt-11{margin-top:var(--canon-space-11)}.xl\:cu-mt-12{margin-top:var(--canon-space-12)}.xl\:cu-mt-13{margin-top:var(--canon-space-13)}.xl\:cu-mt-14{margin-top:var(--canon-space-14)}}.cu-mb{margin-bottom:var(--mb)}.cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.cu-mb-1{margin-bottom:var(--canon-space-1)}.cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.cu-mb-2{margin-bottom:var(--canon-space-2)}.cu-mb-3{margin-bottom:var(--canon-space-3)}.cu-mb-4{margin-bottom:var(--canon-space-4)}.cu-mb-5{margin-bottom:var(--canon-space-5)}.cu-mb-6{margin-bottom:var(--canon-space-6)}.cu-mb-7{margin-bottom:var(--canon-space-7)}.cu-mb-8{margin-bottom:var(--canon-space-8)}.cu-mb-9{margin-bottom:var(--canon-space-9)}.cu-mb-10{margin-bottom:var(--canon-space-10)}.cu-mb-11{margin-bottom:var(--canon-space-11)}.cu-mb-12{margin-bottom:var(--canon-space-12)}.cu-mb-13{margin-bottom:var(--canon-space-13)}.cu-mb-14{margin-bottom:var(--canon-space-14)}@media (width>=640px){.xs\:cu-mb{margin-bottom:var(--pb-xs)}.xs\:cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.xs\:cu-mb-1{margin-bottom:var(--canon-space-1)}.xs\:cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.xs\:cu-mb-2{margin-bottom:var(--canon-space-2)}.xs\:cu-mb-3{margin-bottom:var(--canon-space-3)}.xs\:cu-mb-4{margin-bottom:var(--canon-space-4)}.xs\:cu-mb-5{margin-bottom:var(--canon-space-5)}.xs\:cu-mb-6{margin-bottom:var(--canon-space-6)}.xs\:cu-mb-7{margin-bottom:var(--canon-space-7)}.xs\:cu-mb-8{margin-bottom:var(--canon-space-8)}.xs\:cu-mb-9{margin-bottom:var(--canon-space-9)}.xs\:cu-mb-10{margin-bottom:var(--canon-space-10)}.xs\:cu-mb-11{margin-bottom:var(--canon-space-11)}.xs\:cu-mb-12{margin-bottom:var(--canon-space-12)}.xs\:cu-mb-13{margin-bottom:var(--canon-space-13)}.xs\:cu-mb-14{margin-bottom:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-mb{margin-bottom:var(--pb-sm)}.sm\:cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.sm\:cu-mb-1{margin-bottom:var(--canon-space-1)}.sm\:cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.sm\:cu-mb-2{margin-bottom:var(--canon-space-2)}.sm\:cu-mb-3{margin-bottom:var(--canon-space-3)}.sm\:cu-mb-4{margin-bottom:var(--canon-space-4)}.sm\:cu-mb-5{margin-bottom:var(--canon-space-5)}.sm\:cu-mb-6{margin-bottom:var(--canon-space-6)}.sm\:cu-mb-7{margin-bottom:var(--canon-space-7)}.sm\:cu-mb-8{margin-bottom:var(--canon-space-8)}.sm\:cu-mb-9{margin-bottom:var(--canon-space-9)}.sm\:cu-mb-10{margin-bottom:var(--canon-space-10)}.sm\:cu-mb-11{margin-bottom:var(--canon-space-11)}.sm\:cu-mb-12{margin-bottom:var(--canon-space-12)}.sm\:cu-mb-13{margin-bottom:var(--canon-space-13)}.sm\:cu-mb-14{margin-bottom:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-mb{margin-bottom:var(--pb-md)}.md\:cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.md\:cu-mb-1{margin-bottom:var(--canon-space-1)}.md\:cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.md\:cu-mb-2{margin-bottom:var(--canon-space-2)}.md\:cu-mb-3{margin-bottom:var(--canon-space-3)}.md\:cu-mb-4{margin-bottom:var(--canon-space-4)}.md\:cu-mb-5{margin-bottom:var(--canon-space-5)}.md\:cu-mb-6{margin-bottom:var(--canon-space-6)}.md\:cu-mb-7{margin-bottom:var(--canon-space-7)}.md\:cu-mb-8{margin-bottom:var(--canon-space-8)}.md\:cu-mb-9{margin-bottom:var(--canon-space-9)}.md\:cu-mb-10{margin-bottom:var(--canon-space-10)}.md\:cu-mb-11{margin-bottom:var(--canon-space-11)}.md\:cu-mb-12{margin-bottom:var(--canon-space-12)}.md\:cu-mb-13{margin-bottom:var(--canon-space-13)}.md\:cu-mb-14{margin-bottom:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-mb{margin-bottom:var(--pb-lg)}.lg\:cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.lg\:cu-mb-1{margin-bottom:var(--canon-space-1)}.lg\:cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.lg\:cu-mb-2{margin-bottom:var(--canon-space-2)}.lg\:cu-mb-3{margin-bottom:var(--canon-space-3)}.lg\:cu-mb-4{margin-bottom:var(--canon-space-4)}.lg\:cu-mb-5{margin-bottom:var(--canon-space-5)}.lg\:cu-mb-6{margin-bottom:var(--canon-space-6)}.lg\:cu-mb-7{margin-bottom:var(--canon-space-7)}.lg\:cu-mb-8{margin-bottom:var(--canon-space-8)}.lg\:cu-mb-9{margin-bottom:var(--canon-space-9)}.lg\:cu-mb-10{margin-bottom:var(--canon-space-10)}.lg\:cu-mb-11{margin-bottom:var(--canon-space-11)}.lg\:cu-mb-12{margin-bottom:var(--canon-space-12)}.lg\:cu-mb-13{margin-bottom:var(--canon-space-13)}.lg\:cu-mb-14{margin-bottom:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-mb{margin-bottom:var(--pb-xl)}.xl\:cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.xl\:cu-mb-1{margin-bottom:var(--canon-space-1)}.xl\:cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.xl\:cu-mb-2{margin-bottom:var(--canon-space-2)}.xl\:cu-mb-3{margin-bottom:var(--canon-space-3)}.xl\:cu-mb-4{margin-bottom:var(--canon-space-4)}.xl\:cu-mb-5{margin-bottom:var(--canon-space-5)}.xl\:cu-mb-6{margin-bottom:var(--canon-space-6)}.xl\:cu-mb-7{margin-bottom:var(--canon-space-7)}.xl\:cu-mb-8{margin-bottom:var(--canon-space-8)}.xl\:cu-mb-9{margin-bottom:var(--canon-space-9)}.xl\:cu-mb-10{margin-bottom:var(--canon-space-10)}.xl\:cu-mb-11{margin-bottom:var(--canon-space-11)}.xl\:cu-mb-12{margin-bottom:var(--canon-space-12)}.xl\:cu-mb-13{margin-bottom:var(--canon-space-13)}.xl\:cu-mb-14{margin-bottom:var(--canon-space-14)}}.cu-my{margin-top:var(--my);margin-bottom:var(--my)}.cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}@media (width>=640px){.xs\:cu-my{margin-top:var(--py-xs);margin-bottom:var(--py-xs)}.xs\:cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.xs\:cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.xs\:cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.xs\:cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.xs\:cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.xs\:cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.xs\:cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.xs\:cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.xs\:cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.xs\:cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.xs\:cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.xs\:cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.xs\:cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.xs\:cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.xs\:cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.xs\:cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-my{margin-top:var(--py-sm);margin-bottom:var(--py-sm)}.sm\:cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.sm\:cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.sm\:cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.sm\:cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.sm\:cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.sm\:cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.sm\:cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.sm\:cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.sm\:cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.sm\:cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.sm\:cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.sm\:cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.sm\:cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.sm\:cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.sm\:cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.sm\:cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-my{margin-top:var(--py-md);margin-bottom:var(--py-md)}.md\:cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.md\:cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.md\:cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.md\:cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.md\:cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.md\:cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.md\:cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.md\:cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.md\:cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.md\:cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.md\:cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.md\:cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.md\:cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.md\:cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.md\:cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.md\:cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-my{margin-top:var(--py-lg);margin-bottom:var(--py-lg)}.lg\:cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.lg\:cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.lg\:cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.lg\:cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.lg\:cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.lg\:cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.lg\:cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.lg\:cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.lg\:cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.lg\:cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.lg\:cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.lg\:cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.lg\:cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.lg\:cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.lg\:cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.lg\:cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-my{margin-top:var(--py-xl);margin-bottom:var(--py-xl)}.xl\:cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.xl\:cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.xl\:cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.xl\:cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.xl\:cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.xl\:cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.xl\:cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.xl\:cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.xl\:cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.xl\:cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.xl\:cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.xl\:cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.xl\:cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.xl\:cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.xl\:cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.xl\:cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}}.cu-mx{margin-left:var(--mx);margin-right:var(--mx)}.cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}@media (width>=640px){.xs\:cu-mx{margin-left:var(--px-xs);margin-right:var(--px-xs)}.xs\:cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.xs\:cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.xs\:cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.xs\:cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.xs\:cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.xs\:cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.xs\:cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.xs\:cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.xs\:cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.xs\:cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.xs\:cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.xs\:cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.xs\:cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.xs\:cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.xs\:cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.xs\:cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-mx{margin-left:var(--px-sm);margin-right:var(--px-sm)}.sm\:cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.sm\:cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.sm\:cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.sm\:cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.sm\:cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.sm\:cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.sm\:cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.sm\:cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.sm\:cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.sm\:cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.sm\:cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.sm\:cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.sm\:cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.sm\:cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.sm\:cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.sm\:cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-mx{margin-left:var(--px-md);margin-right:var(--px-md)}.md\:cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.md\:cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.md\:cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.md\:cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.md\:cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.md\:cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.md\:cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.md\:cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.md\:cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.md\:cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.md\:cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.md\:cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.md\:cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.md\:cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.md\:cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.md\:cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-mx{margin-left:var(--px-lg);margin-right:var(--px-lg)}.lg\:cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.lg\:cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.lg\:cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.lg\:cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.lg\:cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.lg\:cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.lg\:cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.lg\:cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.lg\:cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.lg\:cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.lg\:cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.lg\:cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.lg\:cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.lg\:cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.lg\:cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.lg\:cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-mx{margin-left:var(--px-xl);margin-right:var(--px-xl)}.xl\:cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.xl\:cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.xl\:cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.xl\:cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.xl\:cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.xl\:cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.xl\:cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.xl\:cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.xl\:cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.xl\:cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.xl\:cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.xl\:cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.xl\:cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.xl\:cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.xl\:cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.xl\:cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}}.cu-display-none{display:none}.cu-display-inline{display:inline}.cu-display-inline-block{display:inline-block}.cu-display-block{display:block}@media (width>=640px){.xs\:cu-display-none{display:none}.xs\:cu-display-inline{display:inline}.xs\:cu-display-inline-block{display:inline-block}.xs\:cu-display-block{display:block}}@media (width>=768px){.sm\:cu-display-none{display:none}.sm\:cu-display-inline{display:inline}.sm\:cu-display-inline-block{display:inline-block}.sm\:cu-display-block{display:block}}@media (width>=1024px){.md\:cu-display-none{display:none}.md\:cu-display-inline{display:inline}.md\:cu-display-inline-block{display:inline-block}.md\:cu-display-block{display:block}}@media (width>=1280px){.lg\:cu-display-none{display:none}.lg\:cu-display-inline{display:inline}.lg\:cu-display-inline-block{display:inline-block}.lg\:cu-display-block{display:block}}@media (width>=1536px){.xl\:cu-display-none{display:none}.xl\:cu-display-inline{display:inline}.xl\:cu-display-inline-block{display:inline-block}.xl\:cu-display-block{display:block}}.cu-w{width:var(--width)}.cu-min-w{min-width:var(--min-width)}.cu-max-w{max-width:var(--max-width)}@media (width>=640px){.xs\:cu-w{width:var(--width)}.xs\:cu-min-w{min-width:var(--min-width)}.xs\:cu-max-w{max-width:var(--max-width)}}@media (width>=768px){.sm\:cu-w{width:var(--width)}.sm\:cu-min-w{min-width:var(--min-width)}.sm\:cu-max-w{max-width:var(--max-width)}}@media (width>=1024px){.md\:cu-w{width:var(--width)}.md\:cu-min-w{min-width:var(--min-width)}.md\:cu-max-w{max-width:var(--max-width)}}@media (width>=1280px){.lg\:cu-w{width:var(--width)}.lg\:cu-min-w{min-width:var(--min-width)}.lg\:cu-max-w{max-width:var(--max-width)}}@media (width>=1536px){.xl\:cu-w{width:var(--width)}.xl\:cu-min-w{min-width:var(--min-width)}.xl\:cu-max-w{max-width:var(--max-width)}}.cu-h{height:var(--height)}.cu-min-h{min-height:var(--min-height)}.cu-max-h{max-height:var(--max-height)}@media (width>=640px){.xs\:cu-h{height:var(--height)}.xs\:cu-min-h{min-height:var(--min-height)}.xs\:cu-max-h{max-height:var(--max-height)}}@media (width>=768px){.sm\:cu-h{height:var(--height)}.sm\:cu-min-h{min-height:var(--min-height)}.sm\:cu-max-h{max-height:var(--max-height)}}@media (width>=1024px){.md\:cu-h{height:var(--height)}.md\:cu-min-h{min-height:var(--min-height)}.md\:cu-max-h{max-height:var(--max-height)}}@media (width>=1280px){.lg\:cu-h{height:var(--height)}.lg\:cu-min-h{min-height:var(--min-height)}.lg\:cu-max-h{max-height:var(--max-height)}}@media (width>=1536px){.xl\:cu-h{height:var(--height)}.xl\:cu-min-h{min-height:var(--min-height)}.xl\:cu-max-h{max-height:var(--max-height)}}.cu-position-absolute{position:absolute}.cu-position-fixed{position:fixed}.cu-position-sticky{position:sticky}.cu-position-relative{position:relative}.cu-position-static{position:static}@media (width>=640px){.xs\:cu-position-absolute{position:absolute}.xs\:cu-position-fixed{position:fixed}.xs\:cu-position-sticky{position:sticky}.xs\:cu-position-relative{position:relative}.xs\:cu-position-static{position:static}}@media (width>=768px){.sm\:cu-position-absolute{position:absolute}.sm\:cu-position-fixed{position:fixed}.sm\:cu-position-sticky{position:sticky}.sm\:cu-position-relative{position:relative}.sm\:cu-position-static{position:static}}@media (width>=1024px){.md\:cu-position-absolute{position:absolute}.md\:cu-position-fixed{position:fixed}.md\:cu-position-sticky{position:sticky}.md\:cu-position-relative{position:relative}.md\:cu-position-static{position:static}}@media (width>=1280px){.lg\:cu-position-absolute{position:absolute}.lg\:cu-position-fixed{position:fixed}.lg\:cu-position-sticky{position:sticky}.lg\:cu-position-relative{position:relative}.lg\:cu-position-static{position:static}}@media (width>=1536px){.xl\:cu-position-absolute{position:absolute}.xl\:cu-position-fixed{position:fixed}.xl\:cu-position-sticky{position:sticky}.xl\:cu-position-relative{position:relative}.xl\:cu-position-static{position:static}}.cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.cu-col-span-1{grid-column:span 1/span 1}.cu-col-span-2{grid-column:span 2/span 2}.cu-col-span-3{grid-column:span 3/span 3}.cu-col-span-4{grid-column:span 4/span 4}.cu-col-span-5{grid-column:span 5/span 5}.cu-col-span-6{grid-column:span 6/span 6}.cu-col-span-7{grid-column:span 7/span 7}.cu-col-span-8{grid-column:span 8/span 8}.cu-col-span-9{grid-column:span 9/span 9}.cu-col-span-10{grid-column:span 10/span 10}.cu-col-span-11{grid-column:span 11/span 11}.cu-col-span-12{grid-column:span 12/span 12}.cu-col-span-auto{grid-column:span auto/span auto}.cu-col-start-1{grid-column-start:1}.cu-col-start-2{grid-column-start:2}.cu-col-start-3{grid-column-start:3}.cu-col-start-4{grid-column-start:4}.cu-col-start-5{grid-column-start:5}.cu-col-start-6{grid-column-start:6}.cu-col-start-7{grid-column-start:7}.cu-col-start-8{grid-column-start:8}.cu-col-start-9{grid-column-start:9}.cu-col-start-10{grid-column-start:10}.cu-col-start-11{grid-column-start:11}.cu-col-start-12{grid-column-start:12}.cu-col-start-13{grid-column-start:13}.cu-col-start-auto{grid-column-start:auto}.cu-col-end-1{grid-column-end:1}.cu-col-end-2{grid-column-end:2}.cu-col-end-3{grid-column-end:3}.cu-col-end-4{grid-column-end:4}.cu-col-end-5{grid-column-end:5}.cu-col-end-6{grid-column-end:6}.cu-col-end-7{grid-column-end:7}.cu-col-end-8{grid-column-end:8}.cu-col-end-9{grid-column-end:9}.cu-col-end-10{grid-column-end:10}.cu-col-end-11{grid-column-end:11}.cu-col-end-12{grid-column-end:12}.cu-col-end-13{grid-column-end:13}.cu-col-end-auto{grid-column-end:auto}.cu-row-span-1{grid-row:span 1/span 1}.cu-row-span-2{grid-row:span 2/span 2}.cu-row-span-3{grid-row:span 3/span 3}.cu-row-span-4{grid-row:span 4/span 4}.cu-row-span-5{grid-row:span 5/span 5}.cu-row-span-6{grid-row:span 6/span 6}.cu-row-span-7{grid-row:span 7/span 7}.cu-row-span-8{grid-row:span 8/span 8}.cu-row-span-9{grid-row:span 9/span 9}.cu-row-span-10{grid-row:span 10/span 10}.cu-row-span-11{grid-row:span 11/span 11}.cu-row-span-12{grid-row:span 12/span 12}.cu-row-span-auto{grid-row:span auto/span auto}@media (width>=640px){.xs\:cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xs\:cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xs\:cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xs\:cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xs\:cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xs\:cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xs\:cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xs\:cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xs\:cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xs\:cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xs\:cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xs\:cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xs\:cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xs\:cu-col-span-1{grid-column:span 1/span 1}.xs\:cu-col-span-2{grid-column:span 2/span 2}.xs\:cu-col-span-3{grid-column:span 3/span 3}.xs\:cu-col-span-4{grid-column:span 4/span 4}.xs\:cu-col-span-5{grid-column:span 5/span 5}.xs\:cu-col-span-6{grid-column:span 6/span 6}.xs\:cu-col-span-7{grid-column:span 7/span 7}.xs\:cu-col-span-8{grid-column:span 8/span 8}.xs\:cu-col-span-9{grid-column:span 9/span 9}.xs\:cu-col-span-10{grid-column:span 10/span 10}.xs\:cu-col-span-11{grid-column:span 11/span 11}.xs\:cu-col-span-12{grid-column:span 12/span 12}.xs\:cu-col-span-auto{grid-column:span auto/span auto}.xs\:cu-col-start-1{grid-column-start:1}.xs\:cu-col-start-2{grid-column-start:2}.xs\:cu-col-start-3{grid-column-start:3}.xs\:cu-col-start-4{grid-column-start:4}.xs\:cu-col-start-5{grid-column-start:5}.xs\:cu-col-start-6{grid-column-start:6}.xs\:cu-col-start-7{grid-column-start:7}.xs\:cu-col-start-8{grid-column-start:8}.xs\:cu-col-start-9{grid-column-start:9}.xs\:cu-col-start-10{grid-column-start:10}.xs\:cu-col-start-11{grid-column-start:11}.xs\:cu-col-start-12{grid-column-start:12}.xs\:cu-col-start-13{grid-column-start:13}.xs\:cu-col-start-auto{grid-column-start:auto}.xs\:cu-col-end-1{grid-column-end:1}.xs\:cu-col-end-2{grid-column-end:2}.xs\:cu-col-end-3{grid-column-end:3}.xs\:cu-col-end-4{grid-column-end:4}.xs\:cu-col-end-5{grid-column-end:5}.xs\:cu-col-end-6{grid-column-end:6}.xs\:cu-col-end-7{grid-column-end:7}.xs\:cu-col-end-8{grid-column-end:8}.xs\:cu-col-end-9{grid-column-end:9}.xs\:cu-col-end-10{grid-column-end:10}.xs\:cu-col-end-11{grid-column-end:11}.xs\:cu-col-end-12{grid-column-end:12}.xs\:cu-col-end-13{grid-column-end:13}.xs\:cu-col-end-auto{grid-column-end:auto}.xs\:cu-row-span-1{grid-row:span 1/span 1}.xs\:cu-row-span-2{grid-row:span 2/span 2}.xs\:cu-row-span-3{grid-row:span 3/span 3}.xs\:cu-row-span-4{grid-row:span 4/span 4}.xs\:cu-row-span-5{grid-row:span 5/span 5}.xs\:cu-row-span-6{grid-row:span 6/span 6}.xs\:cu-row-span-7{grid-row:span 7/span 7}.xs\:cu-row-span-8{grid-row:span 8/span 8}.xs\:cu-row-span-9{grid-row:span 9/span 9}.xs\:cu-row-span-10{grid-row:span 10/span 10}.xs\:cu-row-span-11{grid-row:span 11/span 11}.xs\:cu-row-span-12{grid-row:span 12/span 12}.xs\:cu-row-span-auto{grid-row:span auto/span auto}}@media (width>=768px){.sm\:cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.sm\:cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.sm\:cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.sm\:cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.sm\:cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:cu-col-span-1{grid-column:span 1/span 1}.sm\:cu-col-span-2{grid-column:span 2/span 2}.sm\:cu-col-span-3{grid-column:span 3/span 3}.sm\:cu-col-span-4{grid-column:span 4/span 4}.sm\:cu-col-span-5{grid-column:span 5/span 5}.sm\:cu-col-span-6{grid-column:span 6/span 6}.sm\:cu-col-span-7{grid-column:span 7/span 7}.sm\:cu-col-span-8{grid-column:span 8/span 8}.sm\:cu-col-span-9{grid-column:span 9/span 9}.sm\:cu-col-span-10{grid-column:span 10/span 10}.sm\:cu-col-span-11{grid-column:span 11/span 11}.sm\:cu-col-span-12{grid-column:span 12/span 12}.sm\:cu-col-span-auto{grid-column:span auto/span auto}.sm\:cu-col-start-1{grid-column-start:1}.sm\:cu-col-start-2{grid-column-start:2}.sm\:cu-col-start-3{grid-column-start:3}.sm\:cu-col-start-4{grid-column-start:4}.sm\:cu-col-start-5{grid-column-start:5}.sm\:cu-col-start-6{grid-column-start:6}.sm\:cu-col-start-7{grid-column-start:7}.sm\:cu-col-start-8{grid-column-start:8}.sm\:cu-col-start-9{grid-column-start:9}.sm\:cu-col-start-10{grid-column-start:10}.sm\:cu-col-start-11{grid-column-start:11}.sm\:cu-col-start-12{grid-column-start:12}.sm\:cu-col-start-13{grid-column-start:13}.sm\:cu-col-start-auto{grid-column-start:auto}.sm\:cu-col-end-1{grid-column-end:1}.sm\:cu-col-end-2{grid-column-end:2}.sm\:cu-col-end-3{grid-column-end:3}.sm\:cu-col-end-4{grid-column-end:4}.sm\:cu-col-end-5{grid-column-end:5}.sm\:cu-col-end-6{grid-column-end:6}.sm\:cu-col-end-7{grid-column-end:7}.sm\:cu-col-end-8{grid-column-end:8}.sm\:cu-col-end-9{grid-column-end:9}.sm\:cu-col-end-10{grid-column-end:10}.sm\:cu-col-end-11{grid-column-end:11}.sm\:cu-col-end-12{grid-column-end:12}.sm\:cu-col-end-13{grid-column-end:13}.sm\:cu-col-end-auto{grid-column-end:auto}.sm\:cu-row-span-1{grid-row:span 1/span 1}.sm\:cu-row-span-2{grid-row:span 2/span 2}.sm\:cu-row-span-3{grid-row:span 3/span 3}.sm\:cu-row-span-4{grid-row:span 4/span 4}.sm\:cu-row-span-5{grid-row:span 5/span 5}.sm\:cu-row-span-6{grid-row:span 6/span 6}.sm\:cu-row-span-7{grid-row:span 7/span 7}.sm\:cu-row-span-8{grid-row:span 8/span 8}.sm\:cu-row-span-9{grid-row:span 9/span 9}.sm\:cu-row-span-10{grid-row:span 10/span 10}.sm\:cu-row-span-11{grid-row:span 11/span 11}.sm\:cu-row-span-12{grid-row:span 12/span 12}.sm\:cu-row-span-auto{grid-row:span auto/span auto}}@media (width>=1024px){.md\:cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.md\:cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.md\:cu-col-span-1{grid-column:span 1/span 1}.md\:cu-col-span-2{grid-column:span 2/span 2}.md\:cu-col-span-3{grid-column:span 3/span 3}.md\:cu-col-span-4{grid-column:span 4/span 4}.md\:cu-col-span-5{grid-column:span 5/span 5}.md\:cu-col-span-6{grid-column:span 6/span 6}.md\:cu-col-span-7{grid-column:span 7/span 7}.md\:cu-col-span-8{grid-column:span 8/span 8}.md\:cu-col-span-9{grid-column:span 9/span 9}.md\:cu-col-span-10{grid-column:span 10/span 10}.md\:cu-col-span-11{grid-column:span 11/span 11}.md\:cu-col-span-12{grid-column:span 12/span 12}.md\:cu-col-span-auto{grid-column:span auto/span auto}.md\:cu-col-start-1{grid-column-start:1}.md\:cu-col-start-2{grid-column-start:2}.md\:cu-col-start-3{grid-column-start:3}.md\:cu-col-start-4{grid-column-start:4}.md\:cu-col-start-5{grid-column-start:5}.md\:cu-col-start-6{grid-column-start:6}.md\:cu-col-start-7{grid-column-start:7}.md\:cu-col-start-8{grid-column-start:8}.md\:cu-col-start-9{grid-column-start:9}.md\:cu-col-start-10{grid-column-start:10}.md\:cu-col-start-11{grid-column-start:11}.md\:cu-col-start-12{grid-column-start:12}.md\:cu-col-start-13{grid-column-start:13}.md\:cu-col-start-auto{grid-column-start:auto}.md\:cu-col-end-1{grid-column-end:1}.md\:cu-col-end-2{grid-column-end:2}.md\:cu-col-end-3{grid-column-end:3}.md\:cu-col-end-4{grid-column-end:4}.md\:cu-col-end-5{grid-column-end:5}.md\:cu-col-end-6{grid-column-end:6}.md\:cu-col-end-7{grid-column-end:7}.md\:cu-col-end-8{grid-column-end:8}.md\:cu-col-end-9{grid-column-end:9}.md\:cu-col-end-10{grid-column-end:10}.md\:cu-col-end-11{grid-column-end:11}.md\:cu-col-end-12{grid-column-end:12}.md\:cu-col-end-13{grid-column-end:13}.md\:cu-col-end-auto{grid-column-end:auto}.md\:cu-row-span-1{grid-row:span 1/span 1}.md\:cu-row-span-2{grid-row:span 2/span 2}.md\:cu-row-span-3{grid-row:span 3/span 3}.md\:cu-row-span-4{grid-row:span 4/span 4}.md\:cu-row-span-5{grid-row:span 5/span 5}.md\:cu-row-span-6{grid-row:span 6/span 6}.md\:cu-row-span-7{grid-row:span 7/span 7}.md\:cu-row-span-8{grid-row:span 8/span 8}.md\:cu-row-span-9{grid-row:span 9/span 9}.md\:cu-row-span-10{grid-row:span 10/span 10}.md\:cu-row-span-11{grid-row:span 11/span 11}.md\:cu-row-span-12{grid-row:span 12/span 12}.md\:cu-row-span-auto{grid-row:span auto/span auto}}@media (width>=1280px){.lg\:cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.lg\:cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.lg\:cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.lg\:cu-col-span-1{grid-column:span 1/span 1}.lg\:cu-col-span-2{grid-column:span 2/span 2}.lg\:cu-col-span-3{grid-column:span 3/span 3}.lg\:cu-col-span-4{grid-column:span 4/span 4}.lg\:cu-col-span-5{grid-column:span 5/span 5}.lg\:cu-col-span-6{grid-column:span 6/span 6}.lg\:cu-col-span-7{grid-column:span 7/span 7}.lg\:cu-col-span-8{grid-column:span 8/span 8}.lg\:cu-col-span-9{grid-column:span 9/span 9}.lg\:cu-col-span-10{grid-column:span 10/span 10}.lg\:cu-col-span-11{grid-column:span 11/span 11}.lg\:cu-col-span-12{grid-column:span 12/span 12}.lg\:cu-col-span-auto{grid-column:span auto/span auto}.lg\:cu-col-start-1{grid-column-start:1}.lg\:cu-col-start-2{grid-column-start:2}.lg\:cu-col-start-3{grid-column-start:3}.lg\:cu-col-start-4{grid-column-start:4}.lg\:cu-col-start-5{grid-column-start:5}.lg\:cu-col-start-6{grid-column-start:6}.lg\:cu-col-start-7{grid-column-start:7}.lg\:cu-col-start-8{grid-column-start:8}.lg\:cu-col-start-9{grid-column-start:9}.lg\:cu-col-start-10{grid-column-start:10}.lg\:cu-col-start-11{grid-column-start:11}.lg\:cu-col-start-12{grid-column-start:12}.lg\:cu-col-start-13{grid-column-start:13}.lg\:cu-col-start-auto{grid-column-start:auto}.lg\:cu-col-end-1{grid-column-end:1}.lg\:cu-col-end-2{grid-column-end:2}.lg\:cu-col-end-3{grid-column-end:3}.lg\:cu-col-end-4{grid-column-end:4}.lg\:cu-col-end-5{grid-column-end:5}.lg\:cu-col-end-6{grid-column-end:6}.lg\:cu-col-end-7{grid-column-end:7}.lg\:cu-col-end-8{grid-column-end:8}.lg\:cu-col-end-9{grid-column-end:9}.lg\:cu-col-end-10{grid-column-end:10}.lg\:cu-col-end-11{grid-column-end:11}.lg\:cu-col-end-12{grid-column-end:12}.lg\:cu-col-end-13{grid-column-end:13}.lg\:cu-col-end-auto{grid-column-end:auto}.lg\:cu-row-span-1{grid-row:span 1/span 1}.lg\:cu-row-span-2{grid-row:span 2/span 2}.lg\:cu-row-span-3{grid-row:span 3/span 3}.lg\:cu-row-span-4{grid-row:span 4/span 4}.lg\:cu-row-span-5{grid-row:span 5/span 5}.lg\:cu-row-span-6{grid-row:span 6/span 6}.lg\:cu-row-span-7{grid-row:span 7/span 7}.lg\:cu-row-span-8{grid-row:span 8/span 8}.lg\:cu-row-span-9{grid-row:span 9/span 9}.lg\:cu-row-span-10{grid-row:span 10/span 10}.lg\:cu-row-span-11{grid-row:span 11/span 11}.lg\:cu-row-span-12{grid-row:span 12/span 12}.lg\:cu-row-span-auto{grid-row:span auto/span auto}}@media (width>=1536px){.xl\:cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xl\:cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xl\:cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xl\:cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xl\:cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xl\:cu-col-span-1{grid-column:span 1/span 1}.xl\:cu-col-span-2{grid-column:span 2/span 2}.xl\:cu-col-span-3{grid-column:span 3/span 3}.xl\:cu-col-span-4{grid-column:span 4/span 4}.xl\:cu-col-span-5{grid-column:span 5/span 5}.xl\:cu-col-span-6{grid-column:span 6/span 6}.xl\:cu-col-span-7{grid-column:span 7/span 7}.xl\:cu-col-span-8{grid-column:span 8/span 8}.xl\:cu-col-span-9{grid-column:span 9/span 9}.xl\:cu-col-span-10{grid-column:span 10/span 10}.xl\:cu-col-span-11{grid-column:span 11/span 11}.xl\:cu-col-span-12{grid-column:span 12/span 12}.xl\:cu-col-span-auto{grid-column:span auto/span auto}.xl\:cu-col-start-1{grid-column-start:1}.xl\:cu-col-start-2{grid-column-start:2}.xl\:cu-col-start-3{grid-column-start:3}.xl\:cu-col-start-4{grid-column-start:4}.xl\:cu-col-start-5{grid-column-start:5}.xl\:cu-col-start-6{grid-column-start:6}.xl\:cu-col-start-7{grid-column-start:7}.xl\:cu-col-start-8{grid-column-start:8}.xl\:cu-col-start-9{grid-column-start:9}.xl\:cu-col-start-10{grid-column-start:10}.xl\:cu-col-start-11{grid-column-start:11}.xl\:cu-col-start-12{grid-column-start:12}.xl\:cu-col-start-13{grid-column-start:13}.xl\:cu-col-start-auto{grid-column-start:auto}.xl\:cu-col-end-1{grid-column-end:1}.xl\:cu-col-end-2{grid-column-end:2}.xl\:cu-col-end-3{grid-column-end:3}.xl\:cu-col-end-4{grid-column-end:4}.xl\:cu-col-end-5{grid-column-end:5}.xl\:cu-col-end-6{grid-column-end:6}.xl\:cu-col-end-7{grid-column-end:7}.xl\:cu-col-end-8{grid-column-end:8}.xl\:cu-col-end-9{grid-column-end:9}.xl\:cu-col-end-10{grid-column-end:10}.xl\:cu-col-end-11{grid-column-end:11}.xl\:cu-col-end-12{grid-column-end:12}.xl\:cu-col-end-13{grid-column-end:13}.xl\:cu-col-end-auto{grid-column-end:auto}.xl\:cu-row-span-1{grid-row:span 1/span 1}.xl\:cu-row-span-2{grid-row:span 2/span 2}.xl\:cu-row-span-3{grid-row:span 3/span 3}.xl\:cu-row-span-4{grid-row:span 4/span 4}.xl\:cu-row-span-5{grid-row:span 5/span 5}.xl\:cu-row-span-6{grid-row:span 6/span 6}.xl\:cu-row-span-7{grid-row:span 7/span 7}.xl\:cu-row-span-8{grid-row:span 8/span 8}.xl\:cu-row-span-9{grid-row:span 9/span 9}.xl\:cu-row-span-10{grid-row:span 10/span 10}.xl\:cu-row-span-11{grid-row:span 11/span 11}.xl\:cu-row-span-12{grid-row:span 12/span 12}.xl\:cu-row-span-auto{grid-row:span auto/span auto}}.cu-gap{gap:var(--gap)}.cu-gap-0\.5{gap:var(--canon-space-0_5)}.cu-gap-1{gap:var(--canon-space-1)}.cu-gap-1\.5{gap:var(--canon-space-1_5)}.cu-gap-2{gap:var(--canon-space-2)}.cu-gap-3{gap:var(--canon-space-3)}.cu-gap-4{gap:var(--canon-space-4)}.cu-gap-5{gap:var(--canon-space-5)}.cu-gap-6{gap:var(--canon-space-6)}.cu-gap-7{gap:var(--canon-space-7)}.cu-gap-8{gap:var(--canon-space-8)}.cu-gap-9{gap:var(--canon-space-9)}.cu-gap-10{gap:var(--canon-space-10)}.cu-gap-11{gap:var(--canon-space-11)}.cu-gap-12{gap:var(--canon-space-12)}.cu-gap-13{gap:var(--canon-space-13)}.cu-gap-14{gap:var(--canon-space-14)}@media (width>=640px){.xs\:cu-gap{gap:var(--gap-xs)}.xs\:cu-gap-0\.5{gap:var(--canon-space-0_5)}.xs\:cu-gap-1{gap:var(--canon-space-1)}.xs\:cu-gap-1\.5{gap:var(--canon-space-1_5)}.xs\:cu-gap-2{gap:var(--canon-space-2)}.xs\:cu-gap-3{gap:var(--canon-space-3)}.xs\:cu-gap-4{gap:var(--canon-space-4)}.xs\:cu-gap-5{gap:var(--canon-space-5)}.xs\:cu-gap-6{gap:var(--canon-space-6)}.xs\:cu-gap-7{gap:var(--canon-space-7)}.xs\:cu-gap-8{gap:var(--canon-space-8)}.xs\:cu-gap-9{gap:var(--canon-space-9)}.xs\:cu-gap-10{gap:var(--canon-space-10)}.xs\:cu-gap-11{gap:var(--canon-space-11)}.xs\:cu-gap-12{gap:var(--canon-space-12)}.xs\:cu-gap-13{gap:var(--canon-space-13)}.xs\:cu-gap-14{gap:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-gap{gap:var(--gap-sm)}.sm\:cu-gap-0\.5{gap:var(--canon-space-0_5)}.sm\:cu-gap-1{gap:var(--canon-space-1)}.sm\:cu-gap-1\.5{gap:var(--canon-space-1_5)}.sm\:cu-gap-2{gap:var(--canon-space-2)}.sm\:cu-gap-3{gap:var(--canon-space-3)}.sm\:cu-gap-4{gap:var(--canon-space-4)}.sm\:cu-gap-5{gap:var(--canon-space-5)}.sm\:cu-gap-6{gap:var(--canon-space-6)}.sm\:cu-gap-7{gap:var(--canon-space-7)}.sm\:cu-gap-8{gap:var(--canon-space-8)}.sm\:cu-gap-9{gap:var(--canon-space-9)}.sm\:cu-gap-10{gap:var(--canon-space-10)}.sm\:cu-gap-11{gap:var(--canon-space-11)}.sm\:cu-gap-12{gap:var(--canon-space-12)}.sm\:cu-gap-13{gap:var(--canon-space-13)}.sm\:cu-gap-14{gap:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-gap{gap:var(--gap-md)}.md\:cu-gap-0\.5{gap:var(--canon-space-0_5)}.md\:cu-gap-1{gap:var(--canon-space-1)}.md\:cu-gap-1\.5{gap:var(--canon-space-1_5)}.md\:cu-gap-2{gap:var(--canon-space-2)}.md\:cu-gap-3{gap:var(--canon-space-3)}.md\:cu-gap-4{gap:var(--canon-space-4)}.md\:cu-gap-5{gap:var(--canon-space-5)}.md\:cu-gap-6{gap:var(--canon-space-6)}.md\:cu-gap-7{gap:var(--canon-space-7)}.md\:cu-gap-8{gap:var(--canon-space-8)}.md\:cu-gap-9{gap:var(--canon-space-9)}.md\:cu-gap-10{gap:var(--canon-space-10)}.md\:cu-gap-11{gap:var(--canon-space-11)}.md\:cu-gap-12{gap:var(--canon-space-12)}.md\:cu-gap-13{gap:var(--canon-space-13)}.md\:cu-gap-14{gap:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-gap{gap:var(--gap-lg)}.lg\:cu-gap-0\.5{gap:var(--canon-space-0_5)}.lg\:cu-gap-1{gap:var(--canon-space-1)}.lg\:cu-gap-1\.5{gap:var(--canon-space-1_5)}.lg\:cu-gap-2{gap:var(--canon-space-2)}.lg\:cu-gap-3{gap:var(--canon-space-3)}.lg\:cu-gap-4{gap:var(--canon-space-4)}.lg\:cu-gap-5{gap:var(--canon-space-5)}.lg\:cu-gap-6{gap:var(--canon-space-6)}.lg\:cu-gap-7{gap:var(--canon-space-7)}.lg\:cu-gap-8{gap:var(--canon-space-8)}.lg\:cu-gap-9{gap:var(--canon-space-9)}.lg\:cu-gap-10{gap:var(--canon-space-10)}.lg\:cu-gap-11{gap:var(--canon-space-11)}.lg\:cu-gap-12{gap:var(--canon-space-12)}.lg\:cu-gap-13{gap:var(--canon-space-13)}.lg\:cu-gap-14{gap:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-gap{gap:var(--gap-xl)}.xl\:cu-gap-0\.5{gap:var(--canon-space-0_5)}.xl\:cu-gap-1{gap:var(--canon-space-1)}.xl\:cu-gap-1\.5{gap:var(--canon-space-1_5)}.xl\:cu-gap-2{gap:var(--canon-space-2)}.xl\:cu-gap-3{gap:var(--canon-space-3)}.xl\:cu-gap-4{gap:var(--canon-space-4)}.xl\:cu-gap-5{gap:var(--canon-space-5)}.xl\:cu-gap-6{gap:var(--canon-space-6)}.xl\:cu-gap-7{gap:var(--canon-space-7)}.xl\:cu-gap-8{gap:var(--canon-space-8)}.xl\:cu-gap-9{gap:var(--canon-space-9)}.xl\:cu-gap-10{gap:var(--canon-space-10)}.xl\:cu-gap-11{gap:var(--canon-space-11)}.xl\:cu-gap-12{gap:var(--canon-space-12)}.xl\:cu-gap-13{gap:var(--canon-space-13)}.xl\:cu-gap-14{gap:var(--canon-space-14)}}.cu-align-start{align-items:start}.cu-align-center{align-items:center}.cu-align-end{align-items:end}.cu-align-stretch{align-items:stretch}.cu-jc-start{justify-content:start}.cu-jc-center{justify-content:center}.cu-jc-end{justify-content:end}.cu-jc-between{justify-content:space-between}.cu-fd-row{flex-direction:row}.cu-fd-column{flex-direction:column}.cu-fd-row-reverse{flex-direction:row-reverse}.cu-fd-column-reverse{flex-direction:column-reverse}@media (width>=640px){.xs\:cu-align-start{align-items:start}.xs\:cu-align-center{align-items:center}.xs\:cu-align-end{align-items:end}.xs\:cu-align-stretch{align-items:stretch}.xs\:cu-jc-start{justify-content:start}.xs\:cu-jc-center{justify-content:center}.xs\:cu-jc-end{justify-content:end}.xs\:cu-jc-between{justify-content:space-between}.xs\:cu-fd-row{flex-direction:row}.xs\:cu-fd-column{flex-direction:column}.xs\:cu-fd-row-reverse{flex-direction:row-reverse}.xs\:cu-fd-column-reverse{flex-direction:column-reverse}}@media (width>=768px){.sm\:cu-align-start{align-items:start}.sm\:cu-align-center{align-items:center}.sm\:cu-align-end{align-items:end}.sm\:cu-align-stretch{align-items:stretch}.sm\:cu-jc-start{justify-content:start}.sm\:cu-jc-center{justify-content:center}.sm\:cu-jc-end{justify-content:end}.sm\:cu-jc-between{justify-content:space-between}.sm\:cu-fd-row{flex-direction:row}.sm\:cu-fd-column{flex-direction:column}.sm\:cu-fd-row-reverse{flex-direction:row-reverse}.sm\:cu-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1024px){.md\:cu-align-start{align-items:start}.md\:cu-align-center{align-items:center}.md\:cu-align-end{align-items:end}.md\:cu-align-stretch{align-items:stretch}.md\:cu-jc-start{justify-content:start}.md\:cu-jc-center{justify-content:center}.md\:cu-jc-end{justify-content:end}.md\:cu-jc-between{justify-content:space-between}.md\:cu-fd-row{flex-direction:row}.md\:cu-fd-column{flex-direction:column}.md\:cu-fd-row-reverse{flex-direction:row-reverse}.md\:cu-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1280px){.lg\:cu-align-start{align-items:start}.lg\:cu-align-center{align-items:center}.lg\:cu-align-end{align-items:end}.lg\:cu-align-stretch{align-items:stretch}.lg\:cu-jc-start{justify-content:start}.lg\:cu-jc-center{justify-content:center}.lg\:cu-jc-end{justify-content:end}.lg\:cu-jc-between{justify-content:space-between}.lg\:cu-fd-row{flex-direction:row}.lg\:cu-fd-column{flex-direction:column}.lg\:cu-fd-row-reverse{flex-direction:row-reverse}.lg\:cu-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1536px){.xl\:cu-align-start{align-items:start}.xl\:cu-align-center{align-items:center}.xl\:cu-align-end{align-items:end}.xl\:cu-align-stretch{align-items:stretch}.xl\:cu-jc-start{justify-content:start}.xl\:cu-jc-center{justify-content:center}.xl\:cu-jc-end{justify-content:end}.xl\:cu-jc-between{justify-content:space-between}.xl\:cu-fd-row{flex-direction:row}.xl\:cu-fd-column{flex-direction:column}.xl\:cu-fd-row-reverse{flex-direction:row-reverse}.xl\:cu-fd-column-reverse{flex-direction:column-reverse}}:where(a){color:inherit;text-decoration:none}:root{--canon-font-regular:system-ui;--canon-font-monospace:ui-monospace,"Menlo","Monaco","Consolas","Liberation Mono","Courier New",monospace;--canon-font-weight-regular:400;--canon-font-weight-bold:600;--canon-font-size-1:.625rem;--canon-font-size-2:.75rem;--canon-font-size-3:.875rem;--canon-font-size-4:1rem;--canon-font-size-5:1.25rem;--canon-font-size-6:1.5rem;--canon-font-size-7:2rem;--canon-font-size-8:3rem;--canon-font-size-9:4rem;--canon-font-size-10:5.75rem;--canon-space:.25rem;--canon-space-0_5:calc(var(--canon-space)*.5);--canon-space-1:var(--canon-space);--canon-space-1_5:calc(var(--canon-space)*1.5);--canon-space-2:calc(var(--canon-space)*2);--canon-space-3:calc(var(--canon-space)*3);--canon-space-4:calc(var(--canon-space)*4);--canon-space-5:calc(var(--canon-space)*5);--canon-space-6:calc(var(--canon-space)*6);--canon-space-7:calc(var(--canon-space)*7);--canon-space-8:calc(var(--canon-space)*8);--canon-space-9:calc(var(--canon-space)*9);--canon-space-10:calc(var(--canon-space)*10);--canon-space-11:calc(var(--canon-space)*11);--canon-space-12:calc(var(--canon-space)*12);--canon-space-13:calc(var(--canon-space)*13);--canon-space-14:calc(var(--canon-space)*14);--canon-radius-1:calc(.125rem);--canon-radius-2:calc(.25rem);--canon-radius-3:calc(.5rem);--canon-radius-4:calc(.75rem);--canon-radius-5:calc(1rem);--canon-radius-6:calc(1.25rem);--canon-radius-full:9999px;--canon-black:#000;--canon-white:#fff;--canon-gray-1:#f8f8f8;--canon-gray-2:#ececec;--canon-gray-3:#d9d9d9;--canon-gray-4:#c1c1c1;--canon-gray-5:#9e9e9e;--canon-gray-6:#8c8c8c;--canon-gray-7:#757575;--canon-gray-8:#595959;--canon-bg:var(--canon-gray-1);--canon-bg-surface-1:var(--canon-white);--canon-bg-surface-2:var(--canon-gray-2);--canon-bg-solid:#1f5493;--canon-bg-solid-hover:#163a66;--canon-bg-solid-pressed:#0f2b4e;--canon-bg-solid-disabled:#ebebeb;--canon-bg-tint:transparent;--canon-bg-tint-hover:#1f549366;--canon-bg-tint-pressed:#1f549399;--canon-bg-tint-disabled:#ebebeb;--canon-bg-danger:#feebe7;--canon-bg-warning:#fff2b2;--canon-bg-success:#e6f6eb;--canon-fg-primary:var(--canon-black);--canon-fg-secondary:var(--canon-gray-7);--canon-fg-link:#1f5493;--canon-fg-link-hover:#1f2d5c;--canon-fg-disabled:#9e9e9e;--canon-fg-solid:var(--canon-white);--canon-fg-solid-disabled:#9c9c9c;--canon-fg-tint:#1f5493;--canon-fg-tint-disabled:var(--canon-gray-5);--canon-fg-danger:#e22b2b;--canon-fg-warning:#e36d05;--canon-fg-success:#1db954;--canon-border:#0000001a;--canon-border-hover:#0003;--canon-border-pressed:#0006;--canon-border-disabled:#0000001a;--canon-border-danger:#f87a7a;--canon-border-warning:#e36d05;--canon-border-success:#53db83;--canon-ring:#1f5493;--canon-scrollbar:#a0a0a03b;--canon-scrollbar-thumb:#a0a0a0}[data-theme=dark]{--canon-gray-1:#191919;--canon-gray-2:#242424;--canon-gray-3:#373737;--canon-gray-4:#464646;--canon-gray-5:#575757;--canon-gray-6:#7b7b7b;--canon-gray-7:#9e9e9e;--canon-gray-8:#b4b4b4;--canon-bg:var(--canon-black);--canon-bg-surface-1:var(--canon-gray-1);--canon-bg-surface-2:var(--canon-gray-2);--canon-bg-solid:#9cc9ff;--canon-bg-solid-hover:#83b9fd;--canon-bg-solid-pressed:#83b9fd;--canon-bg-solid-disabled:#222;--canon-bg-tint:transparent;--canon-bg-tint-hover:#9cc9ff1f;--canon-bg-tint-pressed:#9cc9ff29;--canon-bg-tint-disabled:transparent;--canon-bg-danger:#3b1219;--canon-bg-warning:#302008;--canon-bg-success:#132d21;--canon-fg-primary:var(--canon-white);--canon-fg-secondary:var(--canon-gray-7);--canon-fg-link:#9cc9ff;--canon-fg-link-hover:#7eb5f7;--canon-fg-disabled:var(--canon-gray-7);--canon-fg-solid:#101821;--canon-fg-solid-disabled:var(--canon-gray-5);--canon-fg-tint:#9cc9ff;--canon-fg-tint-disabled:var(--canon-gray-5);--canon-fg-danger:#e22b2b;--canon-fg-warning:#e36d05;--canon-fg-success:#1db954;--canon-border:#fff3;--canon-border-hover:#fff6;--canon-border-pressed:#ffffff80;--canon-border-disabled:#fff3;--canon-border-danger:#f87a7a;--canon-border-warning:#e36d05;--canon-border-success:#53db83;--canon-ring:#1f5493;--canon-scrollbar:#3636363a;--canon-scrollbar-thumb:#575757}.canon-AvatarRoot{vertical-align:middle;user-select:none;color:var(--canon-fg-primary);background-color:var(--canon-bg-surface-2);border-radius:100%;justify-content:center;align-items:center;width:2rem;height:2rem;font-size:1rem;font-weight:500;line-height:1;display:inline-flex;overflow:hidden}.canon-AvatarRoot[data-size=small]{width:1.5rem;height:1.5rem}.canon-AvatarRoot[data-size=medium]{width:2rem;height:2rem}.canon-AvatarRoot[data-size=large]{width:3rem;height:3rem}.canon-AvatarImage{object-fit:cover;width:100%;height:100%}.canon-AvatarFallback{width:100%;height:100%;font-size:var(--canon-font-size-3);font-weight:var(--canon-font-weight-regular);box-shadow:inset 0 0 0 1px var(--canon-border);border-radius:var(--canon-radius-full);justify-content:center;align-items:center;display:flex}.canon-Box{font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-primary)}.canon-Button{user-select:none;font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-bold);cursor:pointer;border-radius:var(--canon-radius-2);justify-content:center;align-items:center;gap:var(--canon-space-1_5);border:none;padding:0;display:inline-flex;&:disabled{cursor:not-allowed}}.canon-Button[data-variant=primary]{background-color:var(--canon-bg-solid);color:var(--canon-fg-solid);transition:background-color .15s,box-shadow .15s;&:hover{background-color:var(--canon-bg-solid-hover)}&:active{background-color:var(--canon-bg-solid-pressed)}&:focus-visible{outline:2px solid var(--canon-ring);outline-offset:2px}&:disabled{background-color:var(--canon-bg-solid-disabled);color:var(--canon-fg-solid-disabled)}}.canon-Button[data-variant=secondary]{background-color:var(--canon-bg-surface-1);box-shadow:inset 0 0 0 1px var(--canon-border);color:var(--canon-fg-primary);transition:box-shadow .15s;&:hover{box-shadow:inset 0 0 0 1px var(--canon-border-hover)}&:active{box-shadow:inset 0 0 0 1px var(--canon-border-pressed)}&:focus-visible{box-shadow:inset 0 0 0 2px var(--canon-ring);outline:none;transition:none}&:disabled{box-shadow:inset 0 0 0 1px var(--canon-border-disabled);color:var(--canon-fg-disabled)}}.canon-Button[data-size=medium]{font-size:var(--canon-font-size-4);padding:0 var(--canon-space-3);height:2.5rem}.canon-Button[data-size=small]{font-size:var(--canon-font-size-3);padding:0 var(--canon-space-2);height:2rem}.canon-Button[data-size=small] svg{width:1rem;height:1rem}.canon-Button[data-size=medium] svg{width:1.25rem;height:1.25rem}.canon-ButtonIcon{justify-content:center;align-items:center}.canon-ButtonIcon[data-size=small]{width:2rem;padding:0}.canon-ButtonIcon[data-size=medium]{width:2.5rem;padding:0}.canon-CheckboxRoot{width:1rem;height:1rem;box-shadow:inset 0 0 0 1px var(--canon-border);cursor:pointer;background-color:var(--canon-bg-surface-1);border:none;border-radius:2px;flex-shrink:0;justify-content:center;align-items:center;padding:0;transition:background-color .2s ease-in-out;display:flex}.canon-CheckboxRoot:focus-visible{outline:2px solid var(--canon-ring);outline-offset:2px;transition:none}.canon-CheckboxRoot[data-checked]{background-color:var(--canon-bg-solid);box-shadow:none;color:var(--canon-fg-solid)}.canon-CheckboxLabel{align-items:center;gap:var(--canon-space-2);font-size:var(--canon-font-size-3);font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-primary);user-select:none;flex-direction:row;display:flex;&:hover{& .canon-CheckboxRoot:not([data-checked]){box-shadow:inset 0 0 0 1px var(--canon-border-hover)}}}.canon-CheckboxIndicator{color:var(--canon-fg-solid);justify-content:center;align-items:center;display:flex}.canon-CollapsiblePanel{height:var(--collapsible-panel-height);transition:all .15s ease-out;display:flex;overflow:hidden;&[data-starting-style],&[data-ending-style]{height:0}}.canon-Container{max-width:120rem;padding-inline:var(--canon-space-4);margin-inline:auto;transition:padding .2s ease-in-out}@media (width>=640px){.canon-Container{padding-inline:var(--canon-space-8)}}@media (width>=1024px){.canon-Container{padding-inline:var(--canon-space-12)}}.canon-DataTableRoot{gap:var(--canon-space-3);flex-direction:column;display:flex}.canon-DataTablePagination{padding-top:var(--canon-space-5);justify-content:space-between;align-items:center;display:flex}.canon-DataTablePagination--left{justify-content:space-between;align-items:center;display:flex}.canon-DataTablePagination--right{justify-content:space-between;align-items:center;gap:var(--canon-space-2);display:flex}.canon-DataTablePagination--select{min-width:10.5rem}.canon-FieldLabel{margin-bottom:var(--canon-space-3);gap:var(--canon-space-1);flex-direction:column;display:flex}.canon-FieldLabelLabel{color:var(--canon-fg-primary);cursor:pointer;font-weight:var(--canon-font-weight-regular);font-size:var(--canon-font-size-2);margin-right:auto}.canon-FieldLabelSecondaryLabel{color:var(--canon-fg-secondary);font-weight:var(--canon-font-weight-regular);margin-left:var(--canon-space-1)}.canon-FieldLabelDescription{font-weight:var(--canon-font-weight-regular);font-size:var(--canon-font-size-2);color:var(--canon-fg-secondary);margin:0}.canon-Flex{min-width:0;display:flex}.canon-Grid{display:grid}.canon-Heading{font-family:var(--canon-font-regular);color:var(--canon-fg-primary);margin:0;padding:0;line-height:100%}.canon-Heading[data-variant=display]{font-size:var(--canon-font-size-10);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-variant=title1]{font-size:var(--canon-font-size-9);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-variant=title2]{font-size:var(--canon-font-size-8);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-variant=title3]{font-size:var(--canon-font-size-7);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-variant=title4]{font-size:var(--canon-font-size-6);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-variant=title5]{font-size:var(--canon-font-size-5);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-color=primary]{color:var(--canon-fg-primary)}.canon-Heading[data-color=secondary]{color:var(--canon-fg-secondary)}.canon-Heading[data-truncate]{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.canon-Icon{width:1rem;height:1rem}.canon-Link{font-family:var(--canon-font-regular);color:var(--canon-fg-link);cursor:pointer;margin:0;padding:0;text-decoration-line:none;&:hover{color:var(--canon-fg-link-hover);text-underline-offset:calc(.025em + 2px);text-decoration-line:underline;text-decoration-style:solid;text-decoration-thickness:min(2px,max(1px,.05em));text-decoration-color:color-mix(in srgb,var(--canon-fg-link-hover)30%,transparent)}}.canon-Link[data-variant=body]{font-size:var(--canon-font-size-3);line-height:140%}.canon-Link[data-variant=subtitle]{font-size:var(--canon-font-size-4);line-height:140%}.canon-Link[data-variant=caption]{font-size:var(--canon-font-size-2);line-height:140%}.canon-Link[data-variant=label]{font-size:var(--canon-font-size-1);line-height:140%}.canon-Link[data-weight=regular]{font-weight:var(--canon-font-weight-regular)}.canon-Link[data-weight=bold]{font-weight:var(--canon-font-weight-bold)}.canon-MenuPositioner{outline:0}.canon-MenuPopup{background-color:var(--canon-bg-surface-1);border:1px solid var(--canon-border);color:var(--canon-fg-primary);transform-origin:var(--transform-origin);max-width:min(var(--available-width),340px);max-height:min(var(--available-height),500px);padding-bottom:var(--canon-space-1);border-radius:.375rem;outline:none;flex-direction:column;transition:transform .15s,opacity .15s;display:flex;position:relative;overflow:auto;&[data-starting-style],&[data-ending-style]{opacity:0;transform:scale(.9)}}.canon-MenuItem{user-select:none;align-items:center;gap:var(--canon-space-2);height:32px;color:var(--canon-fg-primary);border-radius:var(--canon-radius-2);margin-inline:var(--canon-space-1);padding-inline:var(--canon-space-2);font-size:var(--canon-font-size-3);cursor:pointer;outline:0;flex-shrink:0;text-decoration:none;display:flex;&:first-child{margin-top:var(--canon-space-1)}&[data-highlighted]{background-color:var(--canon-gray-3)}}.canon-MenuSubmenuTrigger{user-select:none;justify-content:space-between;align-items:center;gap:var(--canon-space-2);height:32px;color:var(--canon-fg-primary);border-radius:var(--canon-radius-2);margin-inline:var(--canon-space-1);padding-inline:var(--canon-space-2);font-size:var(--canon-font-size-3);cursor:pointer;outline:0;flex-shrink:0;text-decoration:none;display:flex;& .canon-Icon{color:var(--canon-fg-secondary)}&:first-child{margin-top:var(--canon-space-1)}&[data-popup-open],&[data-highlighted]{background-color:var(--canon-gray-3);& .canon-Icon{color:var(--canon-fg-primary)}}}.canon-MenuSeparator{background-color:var(--color-gray-200);height:1px;margin:.375rem 1rem}.canon-SubmenuComboboxSearch{padding-inline:var(--canon-space-3);border:none;border-bottom:1px solid var(--canon-border);background-color:var(--canon-bg-surface-1);width:100%;height:32px;color:var(--canon-fg-primary);line-height:140%;font-size:var(--canon-font-size-3);z-index:1;outline:none;position:sticky;top:0;&::placeholder{color:var(--canon-fg-secondary)}&:disabled{opacity:.6;cursor:not-allowed}}.canon-SubmenuComboboxItems{padding-top:var(--canon-space-2);outline:none;flex-direction:column;display:flex;overflow-y:auto}.canon-SubmenuComboboxNoResults{padding-inline:var(--canon-space-3);padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-4);color:var(--canon-fg-secondary);font-size:var(--canon-font-size-3)}.canon-SubmenuComboboxItem{user-select:none;justify-content:space-between;align-items:center;gap:var(--canon-space-2);height:32px;color:var(--canon-fg-primary);border-radius:var(--canon-radius-2);margin-inline:var(--canon-space-1);padding-inline:var(--canon-space-2);font-size:var(--canon-font-size-3);cursor:pointer;outline:0;flex-shrink:0;text-decoration:none;display:flex;&[data-highlighted]{background-color:var(--canon-gray-3)}&[data-disabled]{opacity:.5;cursor:not-allowed}}.canon-SubmenuComboboxItemCheckbox{width:16px;height:16px;color:var(--canon-fg-primary);border-radius:var(--canon-radius-2);border:1px solid var(--canon-border);background:var(--canon-bg-surface-1);flex-shrink:0;justify-content:center;align-items:center;display:flex}.canon-SubmenuComboboxItemLabel{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.canon-TableRoot{caption-side:bottom;border-collapse:collapse;width:100%}.canon-TableHead{text-align:left;padding:var(--canon-space-3);font-size:var(--canon-font-size-3);color:var(--canon-fg-primary)}.canon-TableBody{color:var(--canon-fg-primary)}.canon-TableRow{border-bottom:1px solid var(--canon-border);transition:color .2s ease-in-out}.canon-TableBody .canon-TableRow:hover{background-color:var(--canon-gray-2)}.canon-TableCell{padding:var(--canon-space-3);font-size:var(--canon-font-size-3)}.canon-TableCellText,.canon-TableCellLink{gap:var(--canon-space-0_5);flex-direction:column;display:flex}.canon-TableCellProfile{gap:var(--canon-space-2);flex-direction:row;align-items:center;display:flex}.canon-TableCellProfileAvatar{vertical-align:middle;user-select:none;color:var(--canon-fg-primary);background-color:var(--canon-bg-surface-2);border-radius:100%;justify-content:center;align-items:center;width:1.25rem;height:1.25rem;font-size:1rem;font-weight:500;line-height:1;display:inline-flex;overflow:hidden}.canon-TableCellProfileAvatarImage{object-fit:cover;width:100%;height:100%}.canon-TableCellProfileAvatarFallback{width:100%;height:100%;font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);box-shadow:inset 0 0 0 1px var(--canon-border);border-radius:var(--canon-radius-full);justify-content:center;align-items:center;display:flex}.canon-TabsRoot{border:1px solid var(--color-gray-200);border-radius:.375rem}.canon-TabsList{z-index:0;display:flex;position:relative}.canon-TabsTab{appearance:none;color:var(--canon-fg-secondary);user-select:none;padding-inline:var(--canon-space-3);height:2rem;font-family:inherit;font-size:.875rem;font-weight:500;line-height:1.25rem;font-size:var(--canon-font-size-2);cursor:pointer;background:0 0;border:0;outline:0;justify-content:center;align-items:center;margin:0;padding-block:0;transition:color .2s ease-in-out;display:flex;&[data-selected]{color:var(--canon-fg-primary)}@media (hover:hover){&:hover{color:var(--canon-fg-primary)}}&:focus-visible{position:relative;&:before{content:"";outline:1px solid var(--canon-ring);outline-offset:-1px;border-radius:.25rem;position:absolute;inset:.25rem 0}}}.canon-TabsIndicator{z-index:-1;translate:var(--active-tab-left)-50%;width:var(--active-tab-width);background-color:var(--canon-bg-solid);height:1px;transition-property:translate,width;transition-duration:.2s;transition-timing-function:ease-in-out;position:absolute;bottom:0;left:0}.canon-TabsPanel{&[hidden]{display:none}}.canon-Text{font-family:var(--canon-font-regular);margin:0;padding:0}.canon-Text[data-variant=body]{font-size:var(--canon-font-size-3);line-height:140%}.canon-Text[data-variant=subtitle]{font-size:var(--canon-font-size-4);line-height:140%}.canon-Text[data-variant=caption]{font-size:var(--canon-font-size-2);line-height:140%}.canon-Text[data-variant=label]{font-size:var(--canon-font-size-1);line-height:140%}.canon-Text[data-weight=regular]{font-weight:var(--canon-font-weight-regular)}.canon-Text[data-weight=bold]{font-weight:var(--canon-font-weight-bold)}.canon-Text[data-color=primary]{color:var(--canon-fg-primary)}.canon-Text[data-color=secondary]{color:var(--canon-fg-secondary)}.canon-Text[data-color=danger]{color:var(--canon-fg-danger)}.canon-Text[data-color=warning]{color:var(--canon-fg-warning)}.canon-Text[data-color=success]{color:var(--canon-fg-success)}.canon-Text[data-truncate]{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.canon-TextField{font-family:var(--canon-font-regular);flex-direction:column;width:100%;display:flex}.canon-TextFieldInputWrapper{position:relative}.canon-TextFieldInputWrapper[data-size=small]{height:2rem}.canon-TextFieldInputWrapper[data-size=medium]{height:2.5rem}.canon-TextFieldIcon{left:var(--canon-space-3);margin-right:var(--canon-space-1);color:var(--canon-fg-primary);flex-shrink:0;position:absolute;top:50%;transform:translateY(-50%)}.canon-TextFieldIcon[data-size=small],.canon-TextFieldIcon[data-size=small] svg{width:1rem;height:1rem}.canon-TextFieldIcon[data-size=medium],.canon-TextFieldIcon[data-size=medium] svg{width:1.25rem;height:1.25rem}.canon-TextFieldInput{padding:0 var(--canon-space-3);border-radius:var(--canon-radius-3);border:1px solid var(--canon-border);background-color:var(--canon-bg-surface-1);font-size:var(--canon-font-size-3);font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-primary);width:100%;height:100%;cursor:inherit;align-items:center;transition:border-color .2s ease-in-out,outline-color .2s ease-in-out;display:flex}.canon-TextFieldInput::placeholder{color:var(--canon-fg-secondary)}.canon-TextFieldInput[data-icon]{padding-left:var(--canon-space-8)}.canon-TextFieldInput[data-focused]{outline-color:var(--canon-border-pressed);outline-width:0}.canon-TextFieldInput[data-hovered]{border-color:var(--canon-border-hover)}.canon-TextFieldInput[data-focused]{border-color:var(--canon-border-pressed);outline-width:0}.canon-TextFieldInput[data-invalid]{border-color:var(--canon-fg-danger)}.canon-TextFieldInput[data-disabled]{opacity:.5;cursor:not-allowed;border:1px solid var(--canon-border-disabled)}.canon-TextFieldError{color:var(--canon-fg-danger);font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);margin-top:var(--canon-space-2)}.canon-TooltipPopup{box-sizing:border-box;transform-origin:var(--transform-origin);background-color:canvas;background-color:var(--canon-bg-surface-1);color:var(--canon-fg-primary);outline:1px solid var(--canon-border);box-shadow:0 10px 15px -3px var(--canon-border),0 4px 6px -4px var(--canon-border);border-radius:.375rem;flex-direction:column;padding:.25rem .5rem;font-size:.875rem;line-height:1.25rem;transition:transform .15s,opacity .15s;display:flex;&[data-starting-style],&[data-ending-style]{opacity:0;transform:scale(.9)}&[data-instant]{transition-duration:0s}}.canon-TooltipArrow{display:flex;&[data-side=top]{bottom:-8px;rotate:180deg}&[data-side=bottom]{top:-8px;rotate:none}&[data-side=left]{right:-13px;rotate:90deg}&[data-side=right]{left:-13px;rotate:-90deg}}.canon-TooltipArrow-fill{fill:var(--canon-bg-surface-1)}.canon-TooltipArrow-outer-stroke{@media (prefers-color-scheme:light){&{fill:var(--canon-border)}}}.canon-ScrollAreaRoot{box-sizing:border-box;width:24rem;max-width:calc(100vw - 8rem);height:8.5rem}.canon-ScrollAreaViewport{overscroll-behavior:contain;height:100%}.canon-ScrollAreaContent{padding-block:.75rem;flex-direction:column;gap:1rem;padding-left:1rem;padding-right:1.5rem;display:flex}.canon-ScrollAreaScrollbar{background-color:var(--canon-scrollbar);opacity:0;border-radius:.375rem;justify-content:center;width:.25rem;margin:.5rem;transition:opacity .15s .3s;display:flex;&[data-hovering],&[data-scrolling]{opacity:1;transition-duration:75ms;transition-delay:0s}&:before{content:"";width:1.25rem;height:100%;position:absolute}}.canon-ScrollAreaThumb{border-radius:inherit;background-color:var(--canon-scrollbar-thumb);width:100%}.canon-Select{font-family:var(--canon-font-regular);flex-direction:column;width:100%;display:flex}.canon-SelectLabel{font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-primary);margin-bottom:var(--canon-space-1_5);cursor:pointer}.canon-SelectLabel[data-disabled]{cursor:default}.canon-SelectDescription{font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-secondary);padding-top:var(--canon-space-1_5);margin:0}.canon-SelectError{font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-danger);padding-top:var(--canon-space-1_5);margin:0}.canon-SelectTrigger{box-sizing:border-box;border-radius:var(--canon-radius-3);border:1px solid var(--canon-border);padding:0 var(--canon-space-4);background-color:var(--canon-bg-surface-1);font-size:var(--canon-font-size-3);font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-primary);cursor:pointer;justify-content:space-between;align-items:center;gap:var(--canon-space-2);width:100%;transition:border-color .2s ease-in-out,outline-color .2s ease-in-out;display:flex}.canon-SelectTrigger::placeholder{color:var(--canon-fg-secondary)}.canon-SelectTrigger:hover{border-color:var(--canon-border-hover)}.canon-SelectTrigger:focus-visible{border-color:var(--canon-border-pressed);outline:0}.canon-SelectTrigger[data-invalid]{border-color:var(--canon-fg-danger)}.canon-SelectTrigger[data-invalid]:hover,.canon-SelectTrigger[data-invalid]:focus-visible{border-width:2px}.canon-SelectTrigger[data-disabled]{cursor:not-allowed;border-color:var(--canon-border-disabled);color:var(--canon-fg-disabled)}.canon-SelectTrigger[data-size=small]{height:2rem}.canon-SelectTrigger[data-size=medium]{height:3rem}.canon-SelectIcon{margin-left:var(--canon-space-5);transition:transform .2s}.canon-SelectTrigger[data-popup-open] .canon-SelectIcon{transform:rotate(180deg)}.canon-SelectPopup{box-sizing:border-box;max-height:var(--available-height);background-color:var(--canon-bg-surface-1);border:1px solid var(--canon-border);border-radius:var(--canon-radius-3);padding-block:var(--canon-space-1);z-index:1;transform-origin:var(--transform-origin);outline:0;transition:transform .15s,opacity .15s;overflow-y:auto;box-shadow:0 4px 12px #0003}.canon-SelectPopup[data-starting-style],.canon-SelectPopup[data-ending-style]{opacity:0;transform:scale(.9)}.canon-SelectItem{width:var(--anchor-width);padding-block:var(--canon-space-2);padding-inline:var(--canon-space-4);color:var(--canon-fg-primary);border-radius:var(--canon-radius-3);cursor:pointer;user-select:none;font-size:var(--canon-font-size-3);align-items:center;gap:var(--canon-space-2);outline:none;grid-template-columns:1rem 1fr;grid-template-areas:"icon text";display:grid;position:relative}.canon-SelectItem[data-highlighted]{z-index:0;color:var(--canon-fg-primary);position:relative}.canon-SelectItem[data-highlighted]:before{content:"";z-index:-1;background-color:var(--canon-bg-tint-hover);border-radius:.25rem;position:absolute;inset-block:0;inset-inline:.25rem}.canon-SelectItem[data-disabled]{cursor:not-allowed;color:var(--canon-fg-disabled)}.canon-SelectItemIndicator{grid-area:icon;justify-content:center;align-items:center;display:flex}.canon-SelectItemText{flex:1;grid-area:text}.canon-SelectRequired{color:var(--canon-fg-secondary);font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);margin-left:var(--canon-space-1)}.canon-SelectIcon{justify-content:center;align-items:center;display:flex}.canon-SelectValue{text-overflow:ellipsis;white-space:nowrap;width:100%;overflow:hidden}.canon-Switch{align-items:center;gap:var(--canon-space-3);font-size:var(--canon-font-size-3);color:var(--canon-fg-primary);cursor:pointer;display:flex;position:relative;&[data-pressed] .canon-SwitchIndicator{&:before{background:var(--canon-fg-solid)}}&[data-selected]{& .canon-SwitchIndicator{background:var(--canon-bg-solid);&:before{background:var(--canon-fg-solid);transform:translate(100%)}}&[data-pressed]{& .indicator{background:var(--canon-gray-3)}}}&[data-focus-visible] .canon-SwitchIndicator{outline-offset:2px;outline:2px solid}}.canon-SwitchIndicator{background:var(--canon-gray-3);border:2px;border-radius:1.143rem;width:2rem;height:1.143rem;transition:all .2s;&:before{content:"";background:var(--canon-fg-solid);border-radius:16px;width:.857rem;height:.857rem;margin:.143rem;transition:all .2s;display:block}} \ No newline at end of file From 0a7025d93aeddfd5db8ac74677938b1d71ac9bfc Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 19 Jun 2025 18:12:22 +0100 Subject: [PATCH 220/301] Update styles.css Signed-off-by: Charles de Dreuille --- canon-docs/public/styles.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canon-docs/public/styles.css b/canon-docs/public/styles.css index b669fe7b9b..c4c9ca5566 100644 --- a/canon-docs/public/styles.css +++ b/canon-docs/public/styles.css @@ -1,2 +1,2 @@ /*! modern-normalize v3.0.1 | MIT License | https://github.com/sindresorhus/modern-normalize */ -@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}}.cu-p{padding:var(--p)}.cu-p-0\.5{padding:var(--canon-space-0_5)}.cu-p-1{padding:var(--canon-space-1)}.cu-p-1\.5{padding:var(--canon-space-1_5)}.cu-p-2{padding:var(--canon-space-2)}.cu-p-3{padding:var(--canon-space-3)}.cu-p-4{padding:var(--canon-space-4)}.cu-p-5{padding:var(--canon-space-5)}.cu-p-6{padding:var(--canon-space-6)}.cu-p-7{padding:var(--canon-space-7)}.cu-p-8{padding:var(--canon-space-8)}.cu-p-9{padding:var(--canon-space-9)}.cu-p-10{padding:var(--canon-space-10)}.cu-p-11{padding:var(--canon-space-11)}.cu-p-12{padding:var(--canon-space-12)}.cu-p-13{padding:var(--canon-space-13)}.cu-p-14{padding:var(--canon-space-14)}@media (width>=640px){.xs\:cu-p{padding:var(--p-xs)}.xs\:cu-p-0\.5{padding:var(--canon-space-0_5)}.xs\:cu-p-1{padding:var(--canon-space-1)}.xs\:cu-p-1\.5{padding:var(--canon-space-1_5)}.xs\:cu-p-2{padding:var(--canon-space-2)}.xs\:cu-p-3{padding:var(--canon-space-3)}.xs\:cu-p-4{padding:var(--canon-space-4)}.xs\:cu-p-5{padding:var(--canon-space-5)}.xs\:cu-p-6{padding:var(--canon-space-6)}.xs\:cu-p-7{padding:var(--canon-space-7)}.xs\:cu-p-8{padding:var(--canon-space-8)}.xs\:cu-p-9{padding:var(--canon-space-9)}.xs\:cu-p-10{padding:var(--canon-space-10)}.xs\:cu-p-11{padding:var(--canon-space-11)}.xs\:cu-p-12{padding:var(--canon-space-12)}.xs\:cu-p-13{padding:var(--canon-space-13)}.xs\:cu-p-14{padding:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-p{padding:var(--p-sm)}.sm\:cu-p-0\.5{padding:var(--canon-space-0_5)}.sm\:cu-p-1{padding:var(--canon-space-1)}.sm\:cu-p-1\.5{padding:var(--canon-space-1_5)}.sm\:cu-p-2{padding:var(--canon-space-2)}.sm\:cu-p-3{padding:var(--canon-space-3)}.sm\:cu-p-4{padding:var(--canon-space-4)}.sm\:cu-p-5{padding:var(--canon-space-5)}.sm\:cu-p-6{padding:var(--canon-space-6)}.sm\:cu-p-7{padding:var(--canon-space-7)}.sm\:cu-p-8{padding:var(--canon-space-8)}.sm\:cu-p-9{padding:var(--canon-space-9)}.sm\:cu-p-10{padding:var(--canon-space-10)}.sm\:cu-p-11{padding:var(--canon-space-11)}.sm\:cu-p-12{padding:var(--canon-space-12)}.sm\:cu-p-13{padding:var(--canon-space-13)}.sm\:cu-p-14{padding:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-p{padding:var(--p-md)}.md\:cu-p-0\.5{padding:var(--canon-space-0_5)}.md\:cu-p-1{padding:var(--canon-space-1)}.md\:cu-p-1\.5{padding:var(--canon-space-1_5)}.md\:cu-p-2{padding:var(--canon-space-2)}.md\:cu-p-3{padding:var(--canon-space-3)}.md\:cu-p-4{padding:var(--canon-space-4)}.md\:cu-p-5{padding:var(--canon-space-5)}.md\:cu-p-6{padding:var(--canon-space-6)}.md\:cu-p-7{padding:var(--canon-space-7)}.md\:cu-p-8{padding:var(--canon-space-8)}.md\:cu-p-9{padding:var(--canon-space-9)}.md\:cu-p-10{padding:var(--canon-space-10)}.md\:cu-p-11{padding:var(--canon-space-11)}.md\:cu-p-12{padding:var(--canon-space-12)}.md\:cu-p-13{padding:var(--canon-space-13)}.md\:cu-p-14{padding:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-p{padding:var(--p-lg)}.lg\:cu-p-0\.5{padding:var(--canon-space-0_5)}.lg\:cu-p-1{padding:var(--canon-space-1)}.lg\:cu-p-1\.5{padding:var(--canon-space-1_5)}.lg\:cu-p-2{padding:var(--canon-space-2)}.lg\:cu-p-3{padding:var(--canon-space-3)}.lg\:cu-p-4{padding:var(--canon-space-4)}.lg\:cu-p-5{padding:var(--canon-space-5)}.lg\:cu-p-6{padding:var(--canon-space-6)}.lg\:cu-p-7{padding:var(--canon-space-7)}.lg\:cu-p-8{padding:var(--canon-space-8)}.lg\:cu-p-9{padding:var(--canon-space-9)}.lg\:cu-p-10{padding:var(--canon-space-10)}.lg\:cu-p-11{padding:var(--canon-space-11)}.lg\:cu-p-12{padding:var(--canon-space-12)}.lg\:cu-p-13{padding:var(--canon-space-13)}.lg\:cu-p-14{padding:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-p{padding:var(--p-xl)}.xl\:cu-p-0\.5{padding:var(--canon-space-0_5)}.xl\:cu-p-1{padding:var(--canon-space-1)}.xl\:cu-p-1\.5{padding:var(--canon-space-1_5)}.xl\:cu-p-2{padding:var(--canon-space-2)}.xl\:cu-p-3{padding:var(--canon-space-3)}.xl\:cu-p-4{padding:var(--canon-space-4)}.xl\:cu-p-5{padding:var(--canon-space-5)}.xl\:cu-p-6{padding:var(--canon-space-6)}.xl\:cu-p-7{padding:var(--canon-space-7)}.xl\:cu-p-8{padding:var(--canon-space-8)}.xl\:cu-p-9{padding:var(--canon-space-9)}.xl\:cu-p-10{padding:var(--canon-space-10)}.xl\:cu-p-11{padding:var(--canon-space-11)}.xl\:cu-p-12{padding:var(--canon-space-12)}.xl\:cu-p-13{padding:var(--canon-space-13)}.xl\:cu-p-14{padding:var(--canon-space-14)}}.cu-pl{padding-left:var(--pl)}.cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.cu-pl-1{padding-left:var(--canon-space-1)}.cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.cu-pl-2{padding-left:var(--canon-space-2)}.cu-pl-3{padding-left:var(--canon-space-3)}.cu-pl-4{padding-left:var(--canon-space-4)}.cu-pl-5{padding-left:var(--canon-space-5)}.cu-pl-6{padding-left:var(--canon-space-6)}.cu-pl-7{padding-left:var(--canon-space-7)}.cu-pl-8{padding-left:var(--canon-space-8)}.cu-pl-9{padding-left:var(--canon-space-9)}.cu-pl-10{padding-left:var(--canon-space-10)}.cu-pl-11{padding-left:var(--canon-space-11)}.cu-pl-12{padding-left:var(--canon-space-12)}.cu-pl-13{padding-left:var(--canon-space-13)}.cu-pl-14{padding-left:var(--canon-space-14)}@media (width>=640px){.xs\:cu-pl{padding-left:var(--pl-xs)}.xs\:cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.xs\:cu-pl-1{padding-left:var(--canon-space-1)}.xs\:cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.xs\:cu-pl-2{padding-left:var(--canon-space-2)}.xs\:cu-pl-3{padding-left:var(--canon-space-3)}.xs\:cu-pl-4{padding-left:var(--canon-space-4)}.xs\:cu-pl-5{padding-left:var(--canon-space-5)}.xs\:cu-pl-6{padding-left:var(--canon-space-6)}.xs\:cu-pl-7{padding-left:var(--canon-space-7)}.xs\:cu-pl-8{padding-left:var(--canon-space-8)}.xs\:cu-pl-9{padding-left:var(--canon-space-9)}.xs\:cu-pl-10{padding-left:var(--canon-space-10)}.xs\:cu-pl-11{padding-left:var(--canon-space-11)}.xs\:cu-pl-12{padding-left:var(--canon-space-12)}.xs\:cu-pl-13{padding-left:var(--canon-space-13)}.xs\:cu-pl-14{padding-left:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-pl{padding-left:var(--pl-sm)}.sm\:cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.sm\:cu-pl-1{padding-left:var(--canon-space-1)}.sm\:cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.sm\:cu-pl-2{padding-left:var(--canon-space-2)}.sm\:cu-pl-3{padding-left:var(--canon-space-3)}.sm\:cu-pl-4{padding-left:var(--canon-space-4)}.sm\:cu-pl-5{padding-left:var(--canon-space-5)}.sm\:cu-pl-6{padding-left:var(--canon-space-6)}.sm\:cu-pl-7{padding-left:var(--canon-space-7)}.sm\:cu-pl-8{padding-left:var(--canon-space-8)}.sm\:cu-pl-9{padding-left:var(--canon-space-9)}.sm\:cu-pl-10{padding-left:var(--canon-space-10)}.sm\:cu-pl-11{padding-left:var(--canon-space-11)}.sm\:cu-pl-12{padding-left:var(--canon-space-12)}.sm\:cu-pl-13{padding-left:var(--canon-space-13)}.sm\:cu-pl-14{padding-left:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-pl{padding-left:var(--pl-md)}.md\:cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.md\:cu-pl-1{padding-left:var(--canon-space-1)}.md\:cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.md\:cu-pl-2{padding-left:var(--canon-space-2)}.md\:cu-pl-3{padding-left:var(--canon-space-3)}.md\:cu-pl-4{padding-left:var(--canon-space-4)}.md\:cu-pl-5{padding-left:var(--canon-space-5)}.md\:cu-pl-6{padding-left:var(--canon-space-6)}.md\:cu-pl-7{padding-left:var(--canon-space-7)}.md\:cu-pl-8{padding-left:var(--canon-space-8)}.md\:cu-pl-9{padding-left:var(--canon-space-9)}.md\:cu-pl-10{padding-left:var(--canon-space-10)}.md\:cu-pl-11{padding-left:var(--canon-space-11)}.md\:cu-pl-12{padding-left:var(--canon-space-12)}.md\:cu-pl-13{padding-left:var(--canon-space-13)}.md\:cu-pl-14{padding-left:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-pl{padding-left:var(--pl-lg)}.lg\:cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.lg\:cu-pl-1{padding-left:var(--canon-space-1)}.lg\:cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.lg\:cu-pl-2{padding-left:var(--canon-space-2)}.lg\:cu-pl-3{padding-left:var(--canon-space-3)}.lg\:cu-pl-4{padding-left:var(--canon-space-4)}.lg\:cu-pl-5{padding-left:var(--canon-space-5)}.lg\:cu-pl-6{padding-left:var(--canon-space-6)}.lg\:cu-pl-7{padding-left:var(--canon-space-7)}.lg\:cu-pl-8{padding-left:var(--canon-space-8)}.lg\:cu-pl-9{padding-left:var(--canon-space-9)}.lg\:cu-pl-10{padding-left:var(--canon-space-10)}.lg\:cu-pl-11{padding-left:var(--canon-space-11)}.lg\:cu-pl-12{padding-left:var(--canon-space-12)}.lg\:cu-pl-13{padding-left:var(--canon-space-13)}.lg\:cu-pl-14{padding-left:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-pl{padding-left:var(--pl-xl)}.xl\:cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.xl\:cu-pl-1{padding-left:var(--canon-space-1)}.xl\:cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.xl\:cu-pl-2{padding-left:var(--canon-space-2)}.xl\:cu-pl-3{padding-left:var(--canon-space-3)}.xl\:cu-pl-4{padding-left:var(--canon-space-4)}.xl\:cu-pl-5{padding-left:var(--canon-space-5)}.xl\:cu-pl-6{padding-left:var(--canon-space-6)}.xl\:cu-pl-7{padding-left:var(--canon-space-7)}.xl\:cu-pl-8{padding-left:var(--canon-space-8)}.xl\:cu-pl-9{padding-left:var(--canon-space-9)}.xl\:cu-pl-10{padding-left:var(--canon-space-10)}.xl\:cu-pl-11{padding-left:var(--canon-space-11)}.xl\:cu-pl-12{padding-left:var(--canon-space-12)}.xl\:cu-pl-13{padding-left:var(--canon-space-13)}.xl\:cu-pl-14{padding-left:var(--canon-space-14)}}.cu-pr{padding-right:var(--pr)}.cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.cu-pr-1{padding-right:var(--canon-space-1)}.cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.cu-pr-2{padding-right:var(--canon-space-2)}.cu-pr-3{padding-right:var(--canon-space-3)}.cu-pr-4{padding-right:var(--canon-space-4)}.cu-pr-5{padding-right:var(--canon-space-5)}.cu-pr-6{padding-right:var(--canon-space-6)}.cu-pr-7{padding-right:var(--canon-space-7)}.cu-pr-8{padding-right:var(--canon-space-8)}.cu-pr-9{padding-right:var(--canon-space-9)}.cu-pr-10{padding-right:var(--canon-space-10)}.cu-pr-11{padding-right:var(--canon-space-11)}.cu-pr-12{padding-right:var(--canon-space-12)}.cu-pr-13{padding-right:var(--canon-space-13)}.cu-pr-14{padding-right:var(--canon-space-14)}@media (width>=640px){.xs\:cu-pr{padding-right:var(--pr-xs)}.xs\:cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.xs\:cu-pr-1{padding-right:var(--canon-space-1)}.xs\:cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.xs\:cu-pr-2{padding-right:var(--canon-space-2)}.xs\:cu-pr-3{padding-right:var(--canon-space-3)}.xs\:cu-pr-4{padding-right:var(--canon-space-4)}.xs\:cu-pr-5{padding-right:var(--canon-space-5)}.xs\:cu-pr-6{padding-right:var(--canon-space-6)}.xs\:cu-pr-7{padding-right:var(--canon-space-7)}.xs\:cu-pr-8{padding-right:var(--canon-space-8)}.xs\:cu-pr-9{padding-right:var(--canon-space-9)}.xs\:cu-pr-10{padding-right:var(--canon-space-10)}.xs\:cu-pr-11{padding-right:var(--canon-space-11)}.xs\:cu-pr-12{padding-right:var(--canon-space-12)}.xs\:cu-pr-13{padding-right:var(--canon-space-13)}.xs\:cu-pr-14{padding-right:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-pr{padding-right:var(--pr-sm)}.sm\:cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.sm\:cu-pr-1{padding-right:var(--canon-space-1)}.sm\:cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.sm\:cu-pr-2{padding-right:var(--canon-space-2)}.sm\:cu-pr-3{padding-right:var(--canon-space-3)}.sm\:cu-pr-4{padding-right:var(--canon-space-4)}.sm\:cu-pr-5{padding-right:var(--canon-space-5)}.sm\:cu-pr-6{padding-right:var(--canon-space-6)}.sm\:cu-pr-7{padding-right:var(--canon-space-7)}.sm\:cu-pr-8{padding-right:var(--canon-space-8)}.sm\:cu-pr-9{padding-right:var(--canon-space-9)}.sm\:cu-pr-10{padding-right:var(--canon-space-10)}.sm\:cu-pr-11{padding-right:var(--canon-space-11)}.sm\:cu-pr-12{padding-right:var(--canon-space-12)}.sm\:cu-pr-13{padding-right:var(--canon-space-13)}.sm\:cu-pr-14{padding-right:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-pr{padding-right:var(--pr-md)}.md\:cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.md\:cu-pr-1{padding-right:var(--canon-space-1)}.md\:cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.md\:cu-pr-2{padding-right:var(--canon-space-2)}.md\:cu-pr-3{padding-right:var(--canon-space-3)}.md\:cu-pr-4{padding-right:var(--canon-space-4)}.md\:cu-pr-5{padding-right:var(--canon-space-5)}.md\:cu-pr-6{padding-right:var(--canon-space-6)}.md\:cu-pr-7{padding-right:var(--canon-space-7)}.md\:cu-pr-8{padding-right:var(--canon-space-8)}.md\:cu-pr-9{padding-right:var(--canon-space-9)}.md\:cu-pr-10{padding-right:var(--canon-space-10)}.md\:cu-pr-11{padding-right:var(--canon-space-11)}.md\:cu-pr-12{padding-right:var(--canon-space-12)}.md\:cu-pr-13{padding-right:var(--canon-space-13)}.md\:cu-pr-14{padding-right:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-pr{padding-right:var(--pr-lg)}.lg\:cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.lg\:cu-pr-1{padding-right:var(--canon-space-1)}.lg\:cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.lg\:cu-pr-2{padding-right:var(--canon-space-2)}.lg\:cu-pr-3{padding-right:var(--canon-space-3)}.lg\:cu-pr-4{padding-right:var(--canon-space-4)}.lg\:cu-pr-5{padding-right:var(--canon-space-5)}.lg\:cu-pr-6{padding-right:var(--canon-space-6)}.lg\:cu-pr-7{padding-right:var(--canon-space-7)}.lg\:cu-pr-8{padding-right:var(--canon-space-8)}.lg\:cu-pr-9{padding-right:var(--canon-space-9)}.lg\:cu-pr-10{padding-right:var(--canon-space-10)}.lg\:cu-pr-11{padding-right:var(--canon-space-11)}.lg\:cu-pr-12{padding-right:var(--canon-space-12)}.lg\:cu-pr-13{padding-right:var(--canon-space-13)}.lg\:cu-pr-14{padding-right:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-pr{padding-right:var(--pr-xl)}.xl\:cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.xl\:cu-pr-1{padding-right:var(--canon-space-1)}.xl\:cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.xl\:cu-pr-2{padding-right:var(--canon-space-2)}.xl\:cu-pr-3{padding-right:var(--canon-space-3)}.xl\:cu-pr-4{padding-right:var(--canon-space-4)}.xl\:cu-pr-5{padding-right:var(--canon-space-5)}.xl\:cu-pr-6{padding-right:var(--canon-space-6)}.xl\:cu-pr-7{padding-right:var(--canon-space-7)}.xl\:cu-pr-8{padding-right:var(--canon-space-8)}.xl\:cu-pr-9{padding-right:var(--canon-space-9)}.xl\:cu-pr-10{padding-right:var(--canon-space-10)}.xl\:cu-pr-11{padding-right:var(--canon-space-11)}.xl\:cu-pr-12{padding-right:var(--canon-space-12)}.xl\:cu-pr-13{padding-right:var(--canon-space-13)}.xl\:cu-pr-14{padding-right:var(--canon-space-14)}}.cu-pt{padding-top:var(--pt)}.cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.cu-pt-1{padding-top:var(--canon-space-1)}.cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.cu-pt-2{padding-top:var(--canon-space-2)}.cu-pt-3{padding-top:var(--canon-space-3)}.cu-pt-4{padding-top:var(--canon-space-4)}.cu-pt-5{padding-top:var(--canon-space-5)}.cu-pt-6{padding-top:var(--canon-space-6)}.cu-pt-7{padding-top:var(--canon-space-7)}.cu-pt-8{padding-top:var(--canon-space-8)}.cu-pt-9{padding-top:var(--canon-space-9)}.cu-pt-10{padding-top:var(--canon-space-10)}.cu-pt-11{padding-top:var(--canon-space-11)}.cu-pt-12{padding-top:var(--canon-space-12)}.cu-pt-13{padding-top:var(--canon-space-13)}.cu-pt-14{padding-top:var(--canon-space-14)}@media (width>=640px){.xs\:cu-pt{padding-top:var(--pt-xs)}.xs\:cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.xs\:cu-pt-1{padding-top:var(--canon-space-1)}.xs\:cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.xs\:cu-pt-2{padding-top:var(--canon-space-2)}.xs\:cu-pt-3{padding-top:var(--canon-space-3)}.xs\:cu-pt-4{padding-top:var(--canon-space-4)}.xs\:cu-pt-5{padding-top:var(--canon-space-5)}.xs\:cu-pt-6{padding-top:var(--canon-space-6)}.xs\:cu-pt-7{padding-top:var(--canon-space-7)}.xs\:cu-pt-8{padding-top:var(--canon-space-8)}.xs\:cu-pt-9{padding-top:var(--canon-space-9)}.xs\:cu-pt-10{padding-top:var(--canon-space-10)}.xs\:cu-pt-11{padding-top:var(--canon-space-11)}.xs\:cu-pt-12{padding-top:var(--canon-space-12)}.xs\:cu-pt-13{padding-top:var(--canon-space-13)}.xs\:cu-pt-14{padding-top:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-pt{padding-top:var(--pt-sm)}.sm\:cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.sm\:cu-pt-1{padding-top:var(--canon-space-1)}.sm\:cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.sm\:cu-pt-2{padding-top:var(--canon-space-2)}.sm\:cu-pt-3{padding-top:var(--canon-space-3)}.sm\:cu-pt-4{padding-top:var(--canon-space-4)}.sm\:cu-pt-5{padding-top:var(--canon-space-5)}.sm\:cu-pt-6{padding-top:var(--canon-space-6)}.sm\:cu-pt-7{padding-top:var(--canon-space-7)}.sm\:cu-pt-8{padding-top:var(--canon-space-8)}.sm\:cu-pt-9{padding-top:var(--canon-space-9)}.sm\:cu-pt-10{padding-top:var(--canon-space-10)}.sm\:cu-pt-11{padding-top:var(--canon-space-11)}.sm\:cu-pt-12{padding-top:var(--canon-space-12)}.sm\:cu-pt-13{padding-top:var(--canon-space-13)}.sm\:cu-pt-14{padding-top:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-pt{padding-top:var(--pt-md)}.md\:cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.md\:cu-pt-1{padding-top:var(--canon-space-1)}.md\:cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.md\:cu-pt-2{padding-top:var(--canon-space-2)}.md\:cu-pt-3{padding-top:var(--canon-space-3)}.md\:cu-pt-4{padding-top:var(--canon-space-4)}.md\:cu-pt-5{padding-top:var(--canon-space-5)}.md\:cu-pt-6{padding-top:var(--canon-space-6)}.md\:cu-pt-7{padding-top:var(--canon-space-7)}.md\:cu-pt-8{padding-top:var(--canon-space-8)}.md\:cu-pt-9{padding-top:var(--canon-space-9)}.md\:cu-pt-10{padding-top:var(--canon-space-10)}.md\:cu-pt-11{padding-top:var(--canon-space-11)}.md\:cu-pt-12{padding-top:var(--canon-space-12)}.md\:cu-pt-13{padding-top:var(--canon-space-13)}.md\:cu-pt-14{padding-top:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-pt{padding-top:var(--pt-lg)}.lg\:cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.lg\:cu-pt-1{padding-top:var(--canon-space-1)}.lg\:cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.lg\:cu-pt-2{padding-top:var(--canon-space-2)}.lg\:cu-pt-3{padding-top:var(--canon-space-3)}.lg\:cu-pt-4{padding-top:var(--canon-space-4)}.lg\:cu-pt-5{padding-top:var(--canon-space-5)}.lg\:cu-pt-6{padding-top:var(--canon-space-6)}.lg\:cu-pt-7{padding-top:var(--canon-space-7)}.lg\:cu-pt-8{padding-top:var(--canon-space-8)}.lg\:cu-pt-9{padding-top:var(--canon-space-9)}.lg\:cu-pt-10{padding-top:var(--canon-space-10)}.lg\:cu-pt-11{padding-top:var(--canon-space-11)}.lg\:cu-pt-12{padding-top:var(--canon-space-12)}.lg\:cu-pt-13{padding-top:var(--canon-space-13)}.lg\:cu-pt-14{padding-top:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-pt{padding-top:var(--pt-xl)}.xl\:cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.xl\:cu-pt-1{padding-top:var(--canon-space-1)}.xl\:cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.xl\:cu-pt-2{padding-top:var(--canon-space-2)}.xl\:cu-pt-3{padding-top:var(--canon-space-3)}.xl\:cu-pt-4{padding-top:var(--canon-space-4)}.xl\:cu-pt-5{padding-top:var(--canon-space-5)}.xl\:cu-pt-6{padding-top:var(--canon-space-6)}.xl\:cu-pt-7{padding-top:var(--canon-space-7)}.xl\:cu-pt-8{padding-top:var(--canon-space-8)}.xl\:cu-pt-9{padding-top:var(--canon-space-9)}.xl\:cu-pt-10{padding-top:var(--canon-space-10)}.xl\:cu-pt-11{padding-top:var(--canon-space-11)}.xl\:cu-pt-12{padding-top:var(--canon-space-12)}.xl\:cu-pt-13{padding-top:var(--canon-space-13)}.xl\:cu-pt-14{padding-top:var(--canon-space-14)}}.cu-pb{padding-bottom:var(--pb)}.cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.cu-pb-1{padding-bottom:var(--canon-space-1)}.cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.cu-pb-2{padding-bottom:var(--canon-space-2)}.cu-pb-3{padding-bottom:var(--canon-space-3)}.cu-pb-4{padding-bottom:var(--canon-space-4)}.cu-pb-5{padding-bottom:var(--canon-space-5)}.cu-pb-6{padding-bottom:var(--canon-space-6)}.cu-pb-7{padding-bottom:var(--canon-space-7)}.cu-pb-8{padding-bottom:var(--canon-space-8)}.cu-pb-9{padding-bottom:var(--canon-space-9)}.cu-pb-10{padding-bottom:var(--canon-space-10)}.cu-pb-11{padding-bottom:var(--canon-space-11)}.cu-pb-12{padding-bottom:var(--canon-space-12)}.cu-pb-13{padding-bottom:var(--canon-space-13)}.cu-pb-14{padding-bottom:var(--canon-space-14)}@media (width>=640px){.xs\:cu-pb{padding-bottom:var(--pb-xs)}.xs\:cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.xs\:cu-pb-1{padding-bottom:var(--canon-space-1)}.xs\:cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.xs\:cu-pb-2{padding-bottom:var(--canon-space-2)}.xs\:cu-pb-3{padding-bottom:var(--canon-space-3)}.xs\:cu-pb-4{padding-bottom:var(--canon-space-4)}.xs\:cu-pb-5{padding-bottom:var(--canon-space-5)}.xs\:cu-pb-6{padding-bottom:var(--canon-space-6)}.xs\:cu-pb-7{padding-bottom:var(--canon-space-7)}.xs\:cu-pb-8{padding-bottom:var(--canon-space-8)}.xs\:cu-pb-9{padding-bottom:var(--canon-space-9)}.xs\:cu-pb-10{padding-bottom:var(--canon-space-10)}.xs\:cu-pb-11{padding-bottom:var(--canon-space-11)}.xs\:cu-pb-12{padding-bottom:var(--canon-space-12)}.xs\:cu-pb-13{padding-bottom:var(--canon-space-13)}.xs\:cu-pb-14{padding-bottom:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-pb{padding-bottom:var(--pb-sm)}.sm\:cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.sm\:cu-pb-1{padding-bottom:var(--canon-space-1)}.sm\:cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.sm\:cu-pb-2{padding-bottom:var(--canon-space-2)}.sm\:cu-pb-3{padding-bottom:var(--canon-space-3)}.sm\:cu-pb-4{padding-bottom:var(--canon-space-4)}.sm\:cu-pb-5{padding-bottom:var(--canon-space-5)}.sm\:cu-pb-6{padding-bottom:var(--canon-space-6)}.sm\:cu-pb-7{padding-bottom:var(--canon-space-7)}.sm\:cu-pb-8{padding-bottom:var(--canon-space-8)}.sm\:cu-pb-9{padding-bottom:var(--canon-space-9)}.sm\:cu-pb-10{padding-bottom:var(--canon-space-10)}.sm\:cu-pb-11{padding-bottom:var(--canon-space-11)}.sm\:cu-pb-12{padding-bottom:var(--canon-space-12)}.sm\:cu-pb-13{padding-bottom:var(--canon-space-13)}.sm\:cu-pb-14{padding-bottom:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-pb{padding-bottom:var(--pb-md)}.md\:cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.md\:cu-pb-1{padding-bottom:var(--canon-space-1)}.md\:cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.md\:cu-pb-2{padding-bottom:var(--canon-space-2)}.md\:cu-pb-3{padding-bottom:var(--canon-space-3)}.md\:cu-pb-4{padding-bottom:var(--canon-space-4)}.md\:cu-pb-5{padding-bottom:var(--canon-space-5)}.md\:cu-pb-6{padding-bottom:var(--canon-space-6)}.md\:cu-pb-7{padding-bottom:var(--canon-space-7)}.md\:cu-pb-8{padding-bottom:var(--canon-space-8)}.md\:cu-pb-9{padding-bottom:var(--canon-space-9)}.md\:cu-pb-10{padding-bottom:var(--canon-space-10)}.md\:cu-pb-11{padding-bottom:var(--canon-space-11)}.md\:cu-pb-12{padding-bottom:var(--canon-space-12)}.md\:cu-pb-13{padding-bottom:var(--canon-space-13)}.md\:cu-pb-14{padding-bottom:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-pb{padding-bottom:var(--pb-lg)}.lg\:cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.lg\:cu-pb-1{padding-bottom:var(--canon-space-1)}.lg\:cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.lg\:cu-pb-2{padding-bottom:var(--canon-space-2)}.lg\:cu-pb-3{padding-bottom:var(--canon-space-3)}.lg\:cu-pb-4{padding-bottom:var(--canon-space-4)}.lg\:cu-pb-5{padding-bottom:var(--canon-space-5)}.lg\:cu-pb-6{padding-bottom:var(--canon-space-6)}.lg\:cu-pb-7{padding-bottom:var(--canon-space-7)}.lg\:cu-pb-8{padding-bottom:var(--canon-space-8)}.lg\:cu-pb-9{padding-bottom:var(--canon-space-9)}.lg\:cu-pb-10{padding-bottom:var(--canon-space-10)}.lg\:cu-pb-11{padding-bottom:var(--canon-space-11)}.lg\:cu-pb-12{padding-bottom:var(--canon-space-12)}.lg\:cu-pb-13{padding-bottom:var(--canon-space-13)}.lg\:cu-pb-14{padding-bottom:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-pb{padding-bottom:var(--pb-xl)}.xl\:cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.xl\:cu-pb-1{padding-bottom:var(--canon-space-1)}.xl\:cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.xl\:cu-pb-2{padding-bottom:var(--canon-space-2)}.xl\:cu-pb-3{padding-bottom:var(--canon-space-3)}.xl\:cu-pb-4{padding-bottom:var(--canon-space-4)}.xl\:cu-pb-5{padding-bottom:var(--canon-space-5)}.xl\:cu-pb-6{padding-bottom:var(--canon-space-6)}.xl\:cu-pb-7{padding-bottom:var(--canon-space-7)}.xl\:cu-pb-8{padding-bottom:var(--canon-space-8)}.xl\:cu-pb-9{padding-bottom:var(--canon-space-9)}.xl\:cu-pb-10{padding-bottom:var(--canon-space-10)}.xl\:cu-pb-11{padding-bottom:var(--canon-space-11)}.xl\:cu-pb-12{padding-bottom:var(--canon-space-12)}.xl\:cu-pb-13{padding-bottom:var(--canon-space-13)}.xl\:cu-pb-14{padding-bottom:var(--canon-space-14)}}.cu-py{padding-top:var(--py);padding-bottom:var(--py)}.cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}@media (width>=640px){.xs\:cu-py{padding-top:var(--py-xs);padding-bottom:var(--py-xs)}.xs\:cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.xs\:cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.xs\:cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.xs\:cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.xs\:cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.xs\:cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.xs\:cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.xs\:cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.xs\:cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.xs\:cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.xs\:cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.xs\:cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.xs\:cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.xs\:cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.xs\:cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.xs\:cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-py{padding-top:var(--py-sm);padding-bottom:var(--py-sm)}.sm\:cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.sm\:cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.sm\:cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.sm\:cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.sm\:cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.sm\:cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.sm\:cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.sm\:cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.sm\:cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.sm\:cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.sm\:cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.sm\:cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.sm\:cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.sm\:cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.sm\:cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.sm\:cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-py{padding-top:var(--py-md);padding-bottom:var(--py-md)}.md\:cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.md\:cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.md\:cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.md\:cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.md\:cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.md\:cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.md\:cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.md\:cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.md\:cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.md\:cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.md\:cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.md\:cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.md\:cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.md\:cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.md\:cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.md\:cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-py{padding-top:var(--py-lg);padding-bottom:var(--py-lg)}.lg\:cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.lg\:cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.lg\:cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.lg\:cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.lg\:cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.lg\:cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.lg\:cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.lg\:cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.lg\:cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.lg\:cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.lg\:cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.lg\:cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.lg\:cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.lg\:cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.lg\:cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.lg\:cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-py{padding-top:var(--py-xl);padding-bottom:var(--py-xl)}.xl\:cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.xl\:cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.xl\:cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.xl\:cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.xl\:cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.xl\:cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.xl\:cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.xl\:cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.xl\:cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.xl\:cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.xl\:cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.xl\:cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.xl\:cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.xl\:cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.xl\:cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.xl\:cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}}.cu-px{padding-left:var(--px);padding-right:var(--px)}.cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}@media (width>=640px){.xs\:cu-px{padding-left:var(--px-xs);padding-right:var(--px-xs)}.xs\:cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.xs\:cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.xs\:cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.xs\:cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.xs\:cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.xs\:cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.xs\:cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.xs\:cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.xs\:cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.xs\:cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.xs\:cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.xs\:cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.xs\:cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.xs\:cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.xs\:cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.xs\:cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-px{padding-left:var(--px-sm);padding-right:var(--px-sm)}.sm\:cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.sm\:cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.sm\:cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.sm\:cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.sm\:cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.sm\:cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.sm\:cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.sm\:cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.sm\:cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.sm\:cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.sm\:cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.sm\:cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.sm\:cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.sm\:cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.sm\:cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.sm\:cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-px{padding-left:var(--px-md);padding-right:var(--px-md)}.md\:cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.md\:cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.md\:cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.md\:cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.md\:cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.md\:cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.md\:cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.md\:cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.md\:cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.md\:cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.md\:cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.md\:cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.md\:cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.md\:cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.md\:cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.md\:cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-px{padding-left:var(--px-lg);padding-right:var(--px-lg)}.lg\:cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.lg\:cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.lg\:cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.lg\:cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.lg\:cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.lg\:cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.lg\:cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.lg\:cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.lg\:cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.lg\:cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.lg\:cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.lg\:cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.lg\:cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.lg\:cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.lg\:cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.lg\:cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-px{padding-left:var(--px-xl);padding-right:var(--px-xl)}.xl\:cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.xl\:cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.xl\:cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.xl\:cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.xl\:cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.xl\:cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.xl\:cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.xl\:cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.xl\:cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.xl\:cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.xl\:cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.xl\:cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.xl\:cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.xl\:cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.xl\:cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.xl\:cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}}.cu-m{margin:var(--m)}.cu-m-0\.5{margin:var(--canon-space-0_5)}.cu-m-1{margin:var(--canon-space-1)}.cu-m-1\.5{margin:var(--canon-space-1_5)}.cu-m-2{margin:var(--canon-space-2)}.cu-m-3{margin:var(--canon-space-3)}.cu-m-4{margin:var(--canon-space-4)}.cu-m-5{margin:var(--canon-space-5)}.cu-m-6{margin:var(--canon-space-6)}.cu-m-7{margin:var(--canon-space-7)}.cu-m-8{margin:var(--canon-space-8)}.cu-m-9{margin:var(--canon-space-9)}.cu-m-10{margin:var(--canon-space-10)}.cu-m-11{margin:var(--canon-space-11)}.cu-m-12{margin:var(--canon-space-12)}.cu-m-13{margin:var(--canon-space-13)}.cu-m-14{margin:var(--canon-space-14)}@media (width>=640px){.xs\:cu-m{margin:var(--p-xs)}.xs\:cu-m-0\.5{margin:var(--canon-space-0_5)}.xs\:cu-m-1{margin:var(--canon-space-1)}.xs\:cu-m-1\.5{margin:var(--canon-space-1_5)}.xs\:cu-m-2{margin:var(--canon-space-2)}.xs\:cu-m-3{margin:var(--canon-space-3)}.xs\:cu-m-4{margin:var(--canon-space-4)}.xs\:cu-m-5{margin:var(--canon-space-5)}.xs\:cu-m-6{margin:var(--canon-space-6)}.xs\:cu-m-7{margin:var(--canon-space-7)}.xs\:cu-m-8{margin:var(--canon-space-8)}.xs\:cu-m-9{margin:var(--canon-space-9)}.xs\:cu-m-10{margin:var(--canon-space-10)}.xs\:cu-m-11{margin:var(--canon-space-11)}.xs\:cu-m-12{margin:var(--canon-space-12)}.xs\:cu-m-13{margin:var(--canon-space-13)}.xs\:cu-m-14{margin:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-m{margin:var(--p-sm)}.sm\:cu-m-0\.5{margin:var(--canon-space-0_5)}.sm\:cu-m-1{margin:var(--canon-space-1)}.sm\:cu-m-1\.5{margin:var(--canon-space-1_5)}.sm\:cu-m-2{margin:var(--canon-space-2)}.sm\:cu-m-3{margin:var(--canon-space-3)}.sm\:cu-m-4{margin:var(--canon-space-4)}.sm\:cu-m-5{margin:var(--canon-space-5)}.sm\:cu-m-6{margin:var(--canon-space-6)}.sm\:cu-m-7{margin:var(--canon-space-7)}.sm\:cu-m-8{margin:var(--canon-space-8)}.sm\:cu-m-9{margin:var(--canon-space-9)}.sm\:cu-m-10{margin:var(--canon-space-10)}.sm\:cu-m-11{margin:var(--canon-space-11)}.sm\:cu-m-12{margin:var(--canon-space-12)}.sm\:cu-m-13{margin:var(--canon-space-13)}.sm\:cu-m-14{margin:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-m{margin:var(--p-md)}.md\:cu-m-0\.5{margin:var(--canon-space-0_5)}.md\:cu-m-1{margin:var(--canon-space-1)}.md\:cu-m-1\.5{margin:var(--canon-space-1_5)}.md\:cu-m-2{margin:var(--canon-space-2)}.md\:cu-m-3{margin:var(--canon-space-3)}.md\:cu-m-4{margin:var(--canon-space-4)}.md\:cu-m-5{margin:var(--canon-space-5)}.md\:cu-m-6{margin:var(--canon-space-6)}.md\:cu-m-7{margin:var(--canon-space-7)}.md\:cu-m-8{margin:var(--canon-space-8)}.md\:cu-m-9{margin:var(--canon-space-9)}.md\:cu-m-10{margin:var(--canon-space-10)}.md\:cu-m-11{margin:var(--canon-space-11)}.md\:cu-m-12{margin:var(--canon-space-12)}.md\:cu-m-13{margin:var(--canon-space-13)}.md\:cu-m-14{margin:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-m{margin:var(--p-lg)}.lg\:cu-m-0\.5{margin:var(--canon-space-0_5)}.lg\:cu-m-1{margin:var(--canon-space-1)}.lg\:cu-m-1\.5{margin:var(--canon-space-1_5)}.lg\:cu-m-2{margin:var(--canon-space-2)}.lg\:cu-m-3{margin:var(--canon-space-3)}.lg\:cu-m-4{margin:var(--canon-space-4)}.lg\:cu-m-5{margin:var(--canon-space-5)}.lg\:cu-m-6{margin:var(--canon-space-6)}.lg\:cu-m-7{margin:var(--canon-space-7)}.lg\:cu-m-8{margin:var(--canon-space-8)}.lg\:cu-m-9{margin:var(--canon-space-9)}.lg\:cu-m-10{margin:var(--canon-space-10)}.lg\:cu-m-11{margin:var(--canon-space-11)}.lg\:cu-m-12{margin:var(--canon-space-12)}.lg\:cu-m-13{margin:var(--canon-space-13)}.lg\:cu-m-14{margin:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-m{margin:var(--p-xl)}.xl\:cu-m-0\.5{margin:var(--canon-space-0_5)}.xl\:cu-m-1{margin:var(--canon-space-1)}.xl\:cu-m-1\.5{margin:var(--canon-space-1_5)}.xl\:cu-m-2{margin:var(--canon-space-2)}.xl\:cu-m-3{margin:var(--canon-space-3)}.xl\:cu-m-4{margin:var(--canon-space-4)}.xl\:cu-m-5{margin:var(--canon-space-5)}.xl\:cu-m-6{margin:var(--canon-space-6)}.xl\:cu-m-7{margin:var(--canon-space-7)}.xl\:cu-m-8{margin:var(--canon-space-8)}.xl\:cu-m-9{margin:var(--canon-space-9)}.xl\:cu-m-10{margin:var(--canon-space-10)}.xl\:cu-m-11{margin:var(--canon-space-11)}.xl\:cu-m-12{margin:var(--canon-space-12)}.xl\:cu-m-13{margin:var(--canon-space-13)}.xl\:cu-m-14{margin:var(--canon-space-14)}}.cu-ml{margin-left:var(--ml)}.cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.cu-ml-1{margin-left:var(--canon-space-1)}.cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.cu-ml-2{margin-left:var(--canon-space-2)}.cu-ml-3{margin-left:var(--canon-space-3)}.cu-ml-4{margin-left:var(--canon-space-4)}.cu-ml-5{margin-left:var(--canon-space-5)}.cu-ml-6{margin-left:var(--canon-space-6)}.cu-ml-7{margin-left:var(--canon-space-7)}.cu-ml-8{margin-left:var(--canon-space-8)}.cu-ml-9{margin-left:var(--canon-space-9)}.cu-ml-10{margin-left:var(--canon-space-10)}.cu-ml-11{margin-left:var(--canon-space-11)}.cu-ml-12{margin-left:var(--canon-space-12)}.cu-ml-13{margin-left:var(--canon-space-13)}.cu-ml-14{margin-left:var(--canon-space-14)}@media (width>=640px){.xs\:cu-ml{margin-left:var(--pl-xs)}.xs\:cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.xs\:cu-ml-1{margin-left:var(--canon-space-1)}.xs\:cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.xs\:cu-ml-2{margin-left:var(--canon-space-2)}.xs\:cu-ml-3{margin-left:var(--canon-space-3)}.xs\:cu-ml-4{margin-left:var(--canon-space-4)}.xs\:cu-ml-5{margin-left:var(--canon-space-5)}.xs\:cu-ml-6{margin-left:var(--canon-space-6)}.xs\:cu-ml-7{margin-left:var(--canon-space-7)}.xs\:cu-ml-8{margin-left:var(--canon-space-8)}.xs\:cu-ml-9{margin-left:var(--canon-space-9)}.xs\:cu-ml-10{margin-left:var(--canon-space-10)}.xs\:cu-ml-11{margin-left:var(--canon-space-11)}.xs\:cu-ml-12{margin-left:var(--canon-space-12)}.xs\:cu-ml-13{margin-left:var(--canon-space-13)}.xs\:cu-ml-14{margin-left:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-ml{margin-left:var(--pl-sm)}.sm\:cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.sm\:cu-ml-1{margin-left:var(--canon-space-1)}.sm\:cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.sm\:cu-ml-2{margin-left:var(--canon-space-2)}.sm\:cu-ml-3{margin-left:var(--canon-space-3)}.sm\:cu-ml-4{margin-left:var(--canon-space-4)}.sm\:cu-ml-5{margin-left:var(--canon-space-5)}.sm\:cu-ml-6{margin-left:var(--canon-space-6)}.sm\:cu-ml-7{margin-left:var(--canon-space-7)}.sm\:cu-ml-8{margin-left:var(--canon-space-8)}.sm\:cu-ml-9{margin-left:var(--canon-space-9)}.sm\:cu-ml-10{margin-left:var(--canon-space-10)}.sm\:cu-ml-11{margin-left:var(--canon-space-11)}.sm\:cu-ml-12{margin-left:var(--canon-space-12)}.sm\:cu-ml-13{margin-left:var(--canon-space-13)}.sm\:cu-ml-14{margin-left:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-ml{margin-left:var(--pl-md)}.md\:cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.md\:cu-ml-1{margin-left:var(--canon-space-1)}.md\:cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.md\:cu-ml-2{margin-left:var(--canon-space-2)}.md\:cu-ml-3{margin-left:var(--canon-space-3)}.md\:cu-ml-4{margin-left:var(--canon-space-4)}.md\:cu-ml-5{margin-left:var(--canon-space-5)}.md\:cu-ml-6{margin-left:var(--canon-space-6)}.md\:cu-ml-7{margin-left:var(--canon-space-7)}.md\:cu-ml-8{margin-left:var(--canon-space-8)}.md\:cu-ml-9{margin-left:var(--canon-space-9)}.md\:cu-ml-10{margin-left:var(--canon-space-10)}.md\:cu-ml-11{margin-left:var(--canon-space-11)}.md\:cu-ml-12{margin-left:var(--canon-space-12)}.md\:cu-ml-13{margin-left:var(--canon-space-13)}.md\:cu-ml-14{margin-left:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-ml{margin-left:var(--pl-lg)}.lg\:cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.lg\:cu-ml-1{margin-left:var(--canon-space-1)}.lg\:cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.lg\:cu-ml-2{margin-left:var(--canon-space-2)}.lg\:cu-ml-3{margin-left:var(--canon-space-3)}.lg\:cu-ml-4{margin-left:var(--canon-space-4)}.lg\:cu-ml-5{margin-left:var(--canon-space-5)}.lg\:cu-ml-6{margin-left:var(--canon-space-6)}.lg\:cu-ml-7{margin-left:var(--canon-space-7)}.lg\:cu-ml-8{margin-left:var(--canon-space-8)}.lg\:cu-ml-9{margin-left:var(--canon-space-9)}.lg\:cu-ml-10{margin-left:var(--canon-space-10)}.lg\:cu-ml-11{margin-left:var(--canon-space-11)}.lg\:cu-ml-12{margin-left:var(--canon-space-12)}.lg\:cu-ml-13{margin-left:var(--canon-space-13)}.lg\:cu-ml-14{margin-left:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-ml{margin-left:var(--pl-xl)}.xl\:cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.xl\:cu-ml-1{margin-left:var(--canon-space-1)}.xl\:cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.xl\:cu-ml-2{margin-left:var(--canon-space-2)}.xl\:cu-ml-3{margin-left:var(--canon-space-3)}.xl\:cu-ml-4{margin-left:var(--canon-space-4)}.xl\:cu-ml-5{margin-left:var(--canon-space-5)}.xl\:cu-ml-6{margin-left:var(--canon-space-6)}.xl\:cu-ml-7{margin-left:var(--canon-space-7)}.xl\:cu-ml-8{margin-left:var(--canon-space-8)}.xl\:cu-ml-9{margin-left:var(--canon-space-9)}.xl\:cu-ml-10{margin-left:var(--canon-space-10)}.xl\:cu-ml-11{margin-left:var(--canon-space-11)}.xl\:cu-ml-12{margin-left:var(--canon-space-12)}.xl\:cu-ml-13{margin-left:var(--canon-space-13)}.xl\:cu-ml-14{margin-left:var(--canon-space-14)}}.cu-mr{margin-right:var(--mr)}.cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.cu-mr-1{margin-right:var(--canon-space-1)}.cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.cu-mr-2{margin-right:var(--canon-space-2)}.cu-mr-3{margin-right:var(--canon-space-3)}.cu-mr-4{margin-right:var(--canon-space-4)}.cu-mr-5{margin-right:var(--canon-space-5)}.cu-mr-6{margin-right:var(--canon-space-6)}.cu-mr-7{margin-right:var(--canon-space-7)}.cu-mr-8{margin-right:var(--canon-space-8)}.cu-mr-9{margin-right:var(--canon-space-9)}.cu-mr-10{margin-right:var(--canon-space-10)}.cu-mr-11{margin-right:var(--canon-space-11)}.cu-mr-12{margin-right:var(--canon-space-12)}.cu-mr-13{margin-right:var(--canon-space-13)}.cu-mr-14{margin-right:var(--canon-space-14)}@media (width>=640px){.xs\:cu-mr{margin-right:var(--pr-xs)}.xs\:cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.xs\:cu-mr-1{margin-right:var(--canon-space-1)}.xs\:cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.xs\:cu-mr-2{margin-right:var(--canon-space-2)}.xs\:cu-mr-3{margin-right:var(--canon-space-3)}.xs\:cu-mr-4{margin-right:var(--canon-space-4)}.xs\:cu-mr-5{margin-right:var(--canon-space-5)}.xs\:cu-mr-6{margin-right:var(--canon-space-6)}.xs\:cu-mr-7{margin-right:var(--canon-space-7)}.xs\:cu-mr-8{margin-right:var(--canon-space-8)}.xs\:cu-mr-9{margin-right:var(--canon-space-9)}.xs\:cu-mr-10{margin-right:var(--canon-space-10)}.xs\:cu-mr-11{margin-right:var(--canon-space-11)}.xs\:cu-mr-12{margin-right:var(--canon-space-12)}.xs\:cu-mr-13{margin-right:var(--canon-space-13)}.xs\:cu-mr-14{margin-right:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-mr{margin-right:var(--pr-sm)}.sm\:cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.sm\:cu-mr-1{margin-right:var(--canon-space-1)}.sm\:cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.sm\:cu-mr-2{margin-right:var(--canon-space-2)}.sm\:cu-mr-3{margin-right:var(--canon-space-3)}.sm\:cu-mr-4{margin-right:var(--canon-space-4)}.sm\:cu-mr-5{margin-right:var(--canon-space-5)}.sm\:cu-mr-6{margin-right:var(--canon-space-6)}.sm\:cu-mr-7{margin-right:var(--canon-space-7)}.sm\:cu-mr-8{margin-right:var(--canon-space-8)}.sm\:cu-mr-9{margin-right:var(--canon-space-9)}.sm\:cu-mr-10{margin-right:var(--canon-space-10)}.sm\:cu-mr-11{margin-right:var(--canon-space-11)}.sm\:cu-mr-12{margin-right:var(--canon-space-12)}.sm\:cu-mr-13{margin-right:var(--canon-space-13)}.sm\:cu-mr-14{margin-right:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-mr{margin-right:var(--pr-md)}.md\:cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.md\:cu-mr-1{margin-right:var(--canon-space-1)}.md\:cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.md\:cu-mr-2{margin-right:var(--canon-space-2)}.md\:cu-mr-3{margin-right:var(--canon-space-3)}.md\:cu-mr-4{margin-right:var(--canon-space-4)}.md\:cu-mr-5{margin-right:var(--canon-space-5)}.md\:cu-mr-6{margin-right:var(--canon-space-6)}.md\:cu-mr-7{margin-right:var(--canon-space-7)}.md\:cu-mr-8{margin-right:var(--canon-space-8)}.md\:cu-mr-9{margin-right:var(--canon-space-9)}.md\:cu-mr-10{margin-right:var(--canon-space-10)}.md\:cu-mr-11{margin-right:var(--canon-space-11)}.md\:cu-mr-12{margin-right:var(--canon-space-12)}.md\:cu-mr-13{margin-right:var(--canon-space-13)}.md\:cu-mr-14{margin-right:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-mr{margin-right:var(--pr-lg)}.lg\:cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.lg\:cu-mr-1{margin-right:var(--canon-space-1)}.lg\:cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.lg\:cu-mr-2{margin-right:var(--canon-space-2)}.lg\:cu-mr-3{margin-right:var(--canon-space-3)}.lg\:cu-mr-4{margin-right:var(--canon-space-4)}.lg\:cu-mr-5{margin-right:var(--canon-space-5)}.lg\:cu-mr-6{margin-right:var(--canon-space-6)}.lg\:cu-mr-7{margin-right:var(--canon-space-7)}.lg\:cu-mr-8{margin-right:var(--canon-space-8)}.lg\:cu-mr-9{margin-right:var(--canon-space-9)}.lg\:cu-mr-10{margin-right:var(--canon-space-10)}.lg\:cu-mr-11{margin-right:var(--canon-space-11)}.lg\:cu-mr-12{margin-right:var(--canon-space-12)}.lg\:cu-mr-13{margin-right:var(--canon-space-13)}.lg\:cu-mr-14{margin-right:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-mr{margin-right:var(--pr-xl)}.xl\:cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.xl\:cu-mr-1{margin-right:var(--canon-space-1)}.xl\:cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.xl\:cu-mr-2{margin-right:var(--canon-space-2)}.xl\:cu-mr-3{margin-right:var(--canon-space-3)}.xl\:cu-mr-4{margin-right:var(--canon-space-4)}.xl\:cu-mr-5{margin-right:var(--canon-space-5)}.xl\:cu-mr-6{margin-right:var(--canon-space-6)}.xl\:cu-mr-7{margin-right:var(--canon-space-7)}.xl\:cu-mr-8{margin-right:var(--canon-space-8)}.xl\:cu-mr-9{margin-right:var(--canon-space-9)}.xl\:cu-mr-10{margin-right:var(--canon-space-10)}.xl\:cu-mr-11{margin-right:var(--canon-space-11)}.xl\:cu-mr-12{margin-right:var(--canon-space-12)}.xl\:cu-mr-13{margin-right:var(--canon-space-13)}.xl\:cu-mr-14{margin-right:var(--canon-space-14)}}.cu-mt{margin-top:var(--mt)}.cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.cu-mt-1{margin-top:var(--canon-space-1)}.cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.cu-mt-2{margin-top:var(--canon-space-2)}.cu-mt-3{margin-top:var(--canon-space-3)}.cu-mt-4{margin-top:var(--canon-space-4)}.cu-mt-5{margin-top:var(--canon-space-5)}.cu-mt-6{margin-top:var(--canon-space-6)}.cu-mt-7{margin-top:var(--canon-space-7)}.cu-mt-8{margin-top:var(--canon-space-8)}.cu-mt-9{margin-top:var(--canon-space-9)}.cu-mt-10{margin-top:var(--canon-space-10)}.cu-mt-11{margin-top:var(--canon-space-11)}.cu-mt-12{margin-top:var(--canon-space-12)}.cu-mt-13{margin-top:var(--canon-space-13)}.cu-mt-14{margin-top:var(--canon-space-14)}@media (width>=640px){.xs\:cu-mt{margin-top:var(--pt-xs)}.xs\:cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.xs\:cu-mt-1{margin-top:var(--canon-space-1)}.xs\:cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.xs\:cu-mt-2{margin-top:var(--canon-space-2)}.xs\:cu-mt-3{margin-top:var(--canon-space-3)}.xs\:cu-mt-4{margin-top:var(--canon-space-4)}.xs\:cu-mt-5{margin-top:var(--canon-space-5)}.xs\:cu-mt-6{margin-top:var(--canon-space-6)}.xs\:cu-mt-7{margin-top:var(--canon-space-7)}.xs\:cu-mt-8{margin-top:var(--canon-space-8)}.xs\:cu-mt-9{margin-top:var(--canon-space-9)}.xs\:cu-mt-10{margin-top:var(--canon-space-10)}.xs\:cu-mt-11{margin-top:var(--canon-space-11)}.xs\:cu-mt-12{margin-top:var(--canon-space-12)}.xs\:cu-mt-13{margin-top:var(--canon-space-13)}.xs\:cu-mt-14{margin-top:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-mt{margin-top:var(--pt-sm)}.sm\:cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.sm\:cu-mt-1{margin-top:var(--canon-space-1)}.sm\:cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.sm\:cu-mt-2{margin-top:var(--canon-space-2)}.sm\:cu-mt-3{margin-top:var(--canon-space-3)}.sm\:cu-mt-4{margin-top:var(--canon-space-4)}.sm\:cu-mt-5{margin-top:var(--canon-space-5)}.sm\:cu-mt-6{margin-top:var(--canon-space-6)}.sm\:cu-mt-7{margin-top:var(--canon-space-7)}.sm\:cu-mt-8{margin-top:var(--canon-space-8)}.sm\:cu-mt-9{margin-top:var(--canon-space-9)}.sm\:cu-mt-10{margin-top:var(--canon-space-10)}.sm\:cu-mt-11{margin-top:var(--canon-space-11)}.sm\:cu-mt-12{margin-top:var(--canon-space-12)}.sm\:cu-mt-13{margin-top:var(--canon-space-13)}.sm\:cu-mt-14{margin-top:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-mt{margin-top:var(--pt-md)}.md\:cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.md\:cu-mt-1{margin-top:var(--canon-space-1)}.md\:cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.md\:cu-mt-2{margin-top:var(--canon-space-2)}.md\:cu-mt-3{margin-top:var(--canon-space-3)}.md\:cu-mt-4{margin-top:var(--canon-space-4)}.md\:cu-mt-5{margin-top:var(--canon-space-5)}.md\:cu-mt-6{margin-top:var(--canon-space-6)}.md\:cu-mt-7{margin-top:var(--canon-space-7)}.md\:cu-mt-8{margin-top:var(--canon-space-8)}.md\:cu-mt-9{margin-top:var(--canon-space-9)}.md\:cu-mt-10{margin-top:var(--canon-space-10)}.md\:cu-mt-11{margin-top:var(--canon-space-11)}.md\:cu-mt-12{margin-top:var(--canon-space-12)}.md\:cu-mt-13{margin-top:var(--canon-space-13)}.md\:cu-mt-14{margin-top:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-mt{margin-top:var(--pt-lg)}.lg\:cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.lg\:cu-mt-1{margin-top:var(--canon-space-1)}.lg\:cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.lg\:cu-mt-2{margin-top:var(--canon-space-2)}.lg\:cu-mt-3{margin-top:var(--canon-space-3)}.lg\:cu-mt-4{margin-top:var(--canon-space-4)}.lg\:cu-mt-5{margin-top:var(--canon-space-5)}.lg\:cu-mt-6{margin-top:var(--canon-space-6)}.lg\:cu-mt-7{margin-top:var(--canon-space-7)}.lg\:cu-mt-8{margin-top:var(--canon-space-8)}.lg\:cu-mt-9{margin-top:var(--canon-space-9)}.lg\:cu-mt-10{margin-top:var(--canon-space-10)}.lg\:cu-mt-11{margin-top:var(--canon-space-11)}.lg\:cu-mt-12{margin-top:var(--canon-space-12)}.lg\:cu-mt-13{margin-top:var(--canon-space-13)}.lg\:cu-mt-14{margin-top:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-mt{margin-top:var(--pt-xl)}.xl\:cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.xl\:cu-mt-1{margin-top:var(--canon-space-1)}.xl\:cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.xl\:cu-mt-2{margin-top:var(--canon-space-2)}.xl\:cu-mt-3{margin-top:var(--canon-space-3)}.xl\:cu-mt-4{margin-top:var(--canon-space-4)}.xl\:cu-mt-5{margin-top:var(--canon-space-5)}.xl\:cu-mt-6{margin-top:var(--canon-space-6)}.xl\:cu-mt-7{margin-top:var(--canon-space-7)}.xl\:cu-mt-8{margin-top:var(--canon-space-8)}.xl\:cu-mt-9{margin-top:var(--canon-space-9)}.xl\:cu-mt-10{margin-top:var(--canon-space-10)}.xl\:cu-mt-11{margin-top:var(--canon-space-11)}.xl\:cu-mt-12{margin-top:var(--canon-space-12)}.xl\:cu-mt-13{margin-top:var(--canon-space-13)}.xl\:cu-mt-14{margin-top:var(--canon-space-14)}}.cu-mb{margin-bottom:var(--mb)}.cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.cu-mb-1{margin-bottom:var(--canon-space-1)}.cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.cu-mb-2{margin-bottom:var(--canon-space-2)}.cu-mb-3{margin-bottom:var(--canon-space-3)}.cu-mb-4{margin-bottom:var(--canon-space-4)}.cu-mb-5{margin-bottom:var(--canon-space-5)}.cu-mb-6{margin-bottom:var(--canon-space-6)}.cu-mb-7{margin-bottom:var(--canon-space-7)}.cu-mb-8{margin-bottom:var(--canon-space-8)}.cu-mb-9{margin-bottom:var(--canon-space-9)}.cu-mb-10{margin-bottom:var(--canon-space-10)}.cu-mb-11{margin-bottom:var(--canon-space-11)}.cu-mb-12{margin-bottom:var(--canon-space-12)}.cu-mb-13{margin-bottom:var(--canon-space-13)}.cu-mb-14{margin-bottom:var(--canon-space-14)}@media (width>=640px){.xs\:cu-mb{margin-bottom:var(--pb-xs)}.xs\:cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.xs\:cu-mb-1{margin-bottom:var(--canon-space-1)}.xs\:cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.xs\:cu-mb-2{margin-bottom:var(--canon-space-2)}.xs\:cu-mb-3{margin-bottom:var(--canon-space-3)}.xs\:cu-mb-4{margin-bottom:var(--canon-space-4)}.xs\:cu-mb-5{margin-bottom:var(--canon-space-5)}.xs\:cu-mb-6{margin-bottom:var(--canon-space-6)}.xs\:cu-mb-7{margin-bottom:var(--canon-space-7)}.xs\:cu-mb-8{margin-bottom:var(--canon-space-8)}.xs\:cu-mb-9{margin-bottom:var(--canon-space-9)}.xs\:cu-mb-10{margin-bottom:var(--canon-space-10)}.xs\:cu-mb-11{margin-bottom:var(--canon-space-11)}.xs\:cu-mb-12{margin-bottom:var(--canon-space-12)}.xs\:cu-mb-13{margin-bottom:var(--canon-space-13)}.xs\:cu-mb-14{margin-bottom:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-mb{margin-bottom:var(--pb-sm)}.sm\:cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.sm\:cu-mb-1{margin-bottom:var(--canon-space-1)}.sm\:cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.sm\:cu-mb-2{margin-bottom:var(--canon-space-2)}.sm\:cu-mb-3{margin-bottom:var(--canon-space-3)}.sm\:cu-mb-4{margin-bottom:var(--canon-space-4)}.sm\:cu-mb-5{margin-bottom:var(--canon-space-5)}.sm\:cu-mb-6{margin-bottom:var(--canon-space-6)}.sm\:cu-mb-7{margin-bottom:var(--canon-space-7)}.sm\:cu-mb-8{margin-bottom:var(--canon-space-8)}.sm\:cu-mb-9{margin-bottom:var(--canon-space-9)}.sm\:cu-mb-10{margin-bottom:var(--canon-space-10)}.sm\:cu-mb-11{margin-bottom:var(--canon-space-11)}.sm\:cu-mb-12{margin-bottom:var(--canon-space-12)}.sm\:cu-mb-13{margin-bottom:var(--canon-space-13)}.sm\:cu-mb-14{margin-bottom:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-mb{margin-bottom:var(--pb-md)}.md\:cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.md\:cu-mb-1{margin-bottom:var(--canon-space-1)}.md\:cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.md\:cu-mb-2{margin-bottom:var(--canon-space-2)}.md\:cu-mb-3{margin-bottom:var(--canon-space-3)}.md\:cu-mb-4{margin-bottom:var(--canon-space-4)}.md\:cu-mb-5{margin-bottom:var(--canon-space-5)}.md\:cu-mb-6{margin-bottom:var(--canon-space-6)}.md\:cu-mb-7{margin-bottom:var(--canon-space-7)}.md\:cu-mb-8{margin-bottom:var(--canon-space-8)}.md\:cu-mb-9{margin-bottom:var(--canon-space-9)}.md\:cu-mb-10{margin-bottom:var(--canon-space-10)}.md\:cu-mb-11{margin-bottom:var(--canon-space-11)}.md\:cu-mb-12{margin-bottom:var(--canon-space-12)}.md\:cu-mb-13{margin-bottom:var(--canon-space-13)}.md\:cu-mb-14{margin-bottom:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-mb{margin-bottom:var(--pb-lg)}.lg\:cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.lg\:cu-mb-1{margin-bottom:var(--canon-space-1)}.lg\:cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.lg\:cu-mb-2{margin-bottom:var(--canon-space-2)}.lg\:cu-mb-3{margin-bottom:var(--canon-space-3)}.lg\:cu-mb-4{margin-bottom:var(--canon-space-4)}.lg\:cu-mb-5{margin-bottom:var(--canon-space-5)}.lg\:cu-mb-6{margin-bottom:var(--canon-space-6)}.lg\:cu-mb-7{margin-bottom:var(--canon-space-7)}.lg\:cu-mb-8{margin-bottom:var(--canon-space-8)}.lg\:cu-mb-9{margin-bottom:var(--canon-space-9)}.lg\:cu-mb-10{margin-bottom:var(--canon-space-10)}.lg\:cu-mb-11{margin-bottom:var(--canon-space-11)}.lg\:cu-mb-12{margin-bottom:var(--canon-space-12)}.lg\:cu-mb-13{margin-bottom:var(--canon-space-13)}.lg\:cu-mb-14{margin-bottom:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-mb{margin-bottom:var(--pb-xl)}.xl\:cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.xl\:cu-mb-1{margin-bottom:var(--canon-space-1)}.xl\:cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.xl\:cu-mb-2{margin-bottom:var(--canon-space-2)}.xl\:cu-mb-3{margin-bottom:var(--canon-space-3)}.xl\:cu-mb-4{margin-bottom:var(--canon-space-4)}.xl\:cu-mb-5{margin-bottom:var(--canon-space-5)}.xl\:cu-mb-6{margin-bottom:var(--canon-space-6)}.xl\:cu-mb-7{margin-bottom:var(--canon-space-7)}.xl\:cu-mb-8{margin-bottom:var(--canon-space-8)}.xl\:cu-mb-9{margin-bottom:var(--canon-space-9)}.xl\:cu-mb-10{margin-bottom:var(--canon-space-10)}.xl\:cu-mb-11{margin-bottom:var(--canon-space-11)}.xl\:cu-mb-12{margin-bottom:var(--canon-space-12)}.xl\:cu-mb-13{margin-bottom:var(--canon-space-13)}.xl\:cu-mb-14{margin-bottom:var(--canon-space-14)}}.cu-my{margin-top:var(--my);margin-bottom:var(--my)}.cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}@media (width>=640px){.xs\:cu-my{margin-top:var(--py-xs);margin-bottom:var(--py-xs)}.xs\:cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.xs\:cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.xs\:cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.xs\:cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.xs\:cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.xs\:cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.xs\:cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.xs\:cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.xs\:cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.xs\:cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.xs\:cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.xs\:cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.xs\:cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.xs\:cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.xs\:cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.xs\:cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-my{margin-top:var(--py-sm);margin-bottom:var(--py-sm)}.sm\:cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.sm\:cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.sm\:cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.sm\:cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.sm\:cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.sm\:cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.sm\:cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.sm\:cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.sm\:cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.sm\:cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.sm\:cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.sm\:cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.sm\:cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.sm\:cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.sm\:cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.sm\:cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-my{margin-top:var(--py-md);margin-bottom:var(--py-md)}.md\:cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.md\:cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.md\:cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.md\:cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.md\:cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.md\:cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.md\:cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.md\:cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.md\:cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.md\:cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.md\:cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.md\:cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.md\:cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.md\:cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.md\:cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.md\:cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-my{margin-top:var(--py-lg);margin-bottom:var(--py-lg)}.lg\:cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.lg\:cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.lg\:cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.lg\:cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.lg\:cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.lg\:cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.lg\:cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.lg\:cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.lg\:cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.lg\:cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.lg\:cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.lg\:cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.lg\:cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.lg\:cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.lg\:cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.lg\:cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-my{margin-top:var(--py-xl);margin-bottom:var(--py-xl)}.xl\:cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.xl\:cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.xl\:cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.xl\:cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.xl\:cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.xl\:cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.xl\:cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.xl\:cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.xl\:cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.xl\:cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.xl\:cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.xl\:cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.xl\:cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.xl\:cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.xl\:cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.xl\:cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}}.cu-mx{margin-left:var(--mx);margin-right:var(--mx)}.cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}@media (width>=640px){.xs\:cu-mx{margin-left:var(--px-xs);margin-right:var(--px-xs)}.xs\:cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.xs\:cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.xs\:cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.xs\:cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.xs\:cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.xs\:cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.xs\:cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.xs\:cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.xs\:cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.xs\:cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.xs\:cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.xs\:cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.xs\:cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.xs\:cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.xs\:cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.xs\:cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-mx{margin-left:var(--px-sm);margin-right:var(--px-sm)}.sm\:cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.sm\:cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.sm\:cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.sm\:cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.sm\:cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.sm\:cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.sm\:cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.sm\:cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.sm\:cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.sm\:cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.sm\:cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.sm\:cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.sm\:cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.sm\:cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.sm\:cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.sm\:cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-mx{margin-left:var(--px-md);margin-right:var(--px-md)}.md\:cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.md\:cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.md\:cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.md\:cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.md\:cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.md\:cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.md\:cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.md\:cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.md\:cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.md\:cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.md\:cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.md\:cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.md\:cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.md\:cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.md\:cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.md\:cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-mx{margin-left:var(--px-lg);margin-right:var(--px-lg)}.lg\:cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.lg\:cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.lg\:cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.lg\:cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.lg\:cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.lg\:cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.lg\:cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.lg\:cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.lg\:cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.lg\:cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.lg\:cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.lg\:cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.lg\:cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.lg\:cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.lg\:cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.lg\:cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-mx{margin-left:var(--px-xl);margin-right:var(--px-xl)}.xl\:cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.xl\:cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.xl\:cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.xl\:cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.xl\:cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.xl\:cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.xl\:cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.xl\:cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.xl\:cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.xl\:cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.xl\:cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.xl\:cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.xl\:cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.xl\:cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.xl\:cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.xl\:cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}}.cu-display-none{display:none}.cu-display-inline{display:inline}.cu-display-inline-block{display:inline-block}.cu-display-block{display:block}@media (width>=640px){.xs\:cu-display-none{display:none}.xs\:cu-display-inline{display:inline}.xs\:cu-display-inline-block{display:inline-block}.xs\:cu-display-block{display:block}}@media (width>=768px){.sm\:cu-display-none{display:none}.sm\:cu-display-inline{display:inline}.sm\:cu-display-inline-block{display:inline-block}.sm\:cu-display-block{display:block}}@media (width>=1024px){.md\:cu-display-none{display:none}.md\:cu-display-inline{display:inline}.md\:cu-display-inline-block{display:inline-block}.md\:cu-display-block{display:block}}@media (width>=1280px){.lg\:cu-display-none{display:none}.lg\:cu-display-inline{display:inline}.lg\:cu-display-inline-block{display:inline-block}.lg\:cu-display-block{display:block}}@media (width>=1536px){.xl\:cu-display-none{display:none}.xl\:cu-display-inline{display:inline}.xl\:cu-display-inline-block{display:inline-block}.xl\:cu-display-block{display:block}}.cu-w{width:var(--width)}.cu-min-w{min-width:var(--min-width)}.cu-max-w{max-width:var(--max-width)}@media (width>=640px){.xs\:cu-w{width:var(--width)}.xs\:cu-min-w{min-width:var(--min-width)}.xs\:cu-max-w{max-width:var(--max-width)}}@media (width>=768px){.sm\:cu-w{width:var(--width)}.sm\:cu-min-w{min-width:var(--min-width)}.sm\:cu-max-w{max-width:var(--max-width)}}@media (width>=1024px){.md\:cu-w{width:var(--width)}.md\:cu-min-w{min-width:var(--min-width)}.md\:cu-max-w{max-width:var(--max-width)}}@media (width>=1280px){.lg\:cu-w{width:var(--width)}.lg\:cu-min-w{min-width:var(--min-width)}.lg\:cu-max-w{max-width:var(--max-width)}}@media (width>=1536px){.xl\:cu-w{width:var(--width)}.xl\:cu-min-w{min-width:var(--min-width)}.xl\:cu-max-w{max-width:var(--max-width)}}.cu-h{height:var(--height)}.cu-min-h{min-height:var(--min-height)}.cu-max-h{max-height:var(--max-height)}@media (width>=640px){.xs\:cu-h{height:var(--height)}.xs\:cu-min-h{min-height:var(--min-height)}.xs\:cu-max-h{max-height:var(--max-height)}}@media (width>=768px){.sm\:cu-h{height:var(--height)}.sm\:cu-min-h{min-height:var(--min-height)}.sm\:cu-max-h{max-height:var(--max-height)}}@media (width>=1024px){.md\:cu-h{height:var(--height)}.md\:cu-min-h{min-height:var(--min-height)}.md\:cu-max-h{max-height:var(--max-height)}}@media (width>=1280px){.lg\:cu-h{height:var(--height)}.lg\:cu-min-h{min-height:var(--min-height)}.lg\:cu-max-h{max-height:var(--max-height)}}@media (width>=1536px){.xl\:cu-h{height:var(--height)}.xl\:cu-min-h{min-height:var(--min-height)}.xl\:cu-max-h{max-height:var(--max-height)}}.cu-position-absolute{position:absolute}.cu-position-fixed{position:fixed}.cu-position-sticky{position:sticky}.cu-position-relative{position:relative}.cu-position-static{position:static}@media (width>=640px){.xs\:cu-position-absolute{position:absolute}.xs\:cu-position-fixed{position:fixed}.xs\:cu-position-sticky{position:sticky}.xs\:cu-position-relative{position:relative}.xs\:cu-position-static{position:static}}@media (width>=768px){.sm\:cu-position-absolute{position:absolute}.sm\:cu-position-fixed{position:fixed}.sm\:cu-position-sticky{position:sticky}.sm\:cu-position-relative{position:relative}.sm\:cu-position-static{position:static}}@media (width>=1024px){.md\:cu-position-absolute{position:absolute}.md\:cu-position-fixed{position:fixed}.md\:cu-position-sticky{position:sticky}.md\:cu-position-relative{position:relative}.md\:cu-position-static{position:static}}@media (width>=1280px){.lg\:cu-position-absolute{position:absolute}.lg\:cu-position-fixed{position:fixed}.lg\:cu-position-sticky{position:sticky}.lg\:cu-position-relative{position:relative}.lg\:cu-position-static{position:static}}@media (width>=1536px){.xl\:cu-position-absolute{position:absolute}.xl\:cu-position-fixed{position:fixed}.xl\:cu-position-sticky{position:sticky}.xl\:cu-position-relative{position:relative}.xl\:cu-position-static{position:static}}.cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.cu-col-span-1{grid-column:span 1/span 1}.cu-col-span-2{grid-column:span 2/span 2}.cu-col-span-3{grid-column:span 3/span 3}.cu-col-span-4{grid-column:span 4/span 4}.cu-col-span-5{grid-column:span 5/span 5}.cu-col-span-6{grid-column:span 6/span 6}.cu-col-span-7{grid-column:span 7/span 7}.cu-col-span-8{grid-column:span 8/span 8}.cu-col-span-9{grid-column:span 9/span 9}.cu-col-span-10{grid-column:span 10/span 10}.cu-col-span-11{grid-column:span 11/span 11}.cu-col-span-12{grid-column:span 12/span 12}.cu-col-span-auto{grid-column:span auto/span auto}.cu-col-start-1{grid-column-start:1}.cu-col-start-2{grid-column-start:2}.cu-col-start-3{grid-column-start:3}.cu-col-start-4{grid-column-start:4}.cu-col-start-5{grid-column-start:5}.cu-col-start-6{grid-column-start:6}.cu-col-start-7{grid-column-start:7}.cu-col-start-8{grid-column-start:8}.cu-col-start-9{grid-column-start:9}.cu-col-start-10{grid-column-start:10}.cu-col-start-11{grid-column-start:11}.cu-col-start-12{grid-column-start:12}.cu-col-start-13{grid-column-start:13}.cu-col-start-auto{grid-column-start:auto}.cu-col-end-1{grid-column-end:1}.cu-col-end-2{grid-column-end:2}.cu-col-end-3{grid-column-end:3}.cu-col-end-4{grid-column-end:4}.cu-col-end-5{grid-column-end:5}.cu-col-end-6{grid-column-end:6}.cu-col-end-7{grid-column-end:7}.cu-col-end-8{grid-column-end:8}.cu-col-end-9{grid-column-end:9}.cu-col-end-10{grid-column-end:10}.cu-col-end-11{grid-column-end:11}.cu-col-end-12{grid-column-end:12}.cu-col-end-13{grid-column-end:13}.cu-col-end-auto{grid-column-end:auto}.cu-row-span-1{grid-row:span 1/span 1}.cu-row-span-2{grid-row:span 2/span 2}.cu-row-span-3{grid-row:span 3/span 3}.cu-row-span-4{grid-row:span 4/span 4}.cu-row-span-5{grid-row:span 5/span 5}.cu-row-span-6{grid-row:span 6/span 6}.cu-row-span-7{grid-row:span 7/span 7}.cu-row-span-8{grid-row:span 8/span 8}.cu-row-span-9{grid-row:span 9/span 9}.cu-row-span-10{grid-row:span 10/span 10}.cu-row-span-11{grid-row:span 11/span 11}.cu-row-span-12{grid-row:span 12/span 12}.cu-row-span-auto{grid-row:span auto/span auto}@media (width>=640px){.xs\:cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xs\:cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xs\:cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xs\:cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xs\:cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xs\:cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xs\:cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xs\:cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xs\:cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xs\:cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xs\:cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xs\:cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xs\:cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xs\:cu-col-span-1{grid-column:span 1/span 1}.xs\:cu-col-span-2{grid-column:span 2/span 2}.xs\:cu-col-span-3{grid-column:span 3/span 3}.xs\:cu-col-span-4{grid-column:span 4/span 4}.xs\:cu-col-span-5{grid-column:span 5/span 5}.xs\:cu-col-span-6{grid-column:span 6/span 6}.xs\:cu-col-span-7{grid-column:span 7/span 7}.xs\:cu-col-span-8{grid-column:span 8/span 8}.xs\:cu-col-span-9{grid-column:span 9/span 9}.xs\:cu-col-span-10{grid-column:span 10/span 10}.xs\:cu-col-span-11{grid-column:span 11/span 11}.xs\:cu-col-span-12{grid-column:span 12/span 12}.xs\:cu-col-span-auto{grid-column:span auto/span auto}.xs\:cu-col-start-1{grid-column-start:1}.xs\:cu-col-start-2{grid-column-start:2}.xs\:cu-col-start-3{grid-column-start:3}.xs\:cu-col-start-4{grid-column-start:4}.xs\:cu-col-start-5{grid-column-start:5}.xs\:cu-col-start-6{grid-column-start:6}.xs\:cu-col-start-7{grid-column-start:7}.xs\:cu-col-start-8{grid-column-start:8}.xs\:cu-col-start-9{grid-column-start:9}.xs\:cu-col-start-10{grid-column-start:10}.xs\:cu-col-start-11{grid-column-start:11}.xs\:cu-col-start-12{grid-column-start:12}.xs\:cu-col-start-13{grid-column-start:13}.xs\:cu-col-start-auto{grid-column-start:auto}.xs\:cu-col-end-1{grid-column-end:1}.xs\:cu-col-end-2{grid-column-end:2}.xs\:cu-col-end-3{grid-column-end:3}.xs\:cu-col-end-4{grid-column-end:4}.xs\:cu-col-end-5{grid-column-end:5}.xs\:cu-col-end-6{grid-column-end:6}.xs\:cu-col-end-7{grid-column-end:7}.xs\:cu-col-end-8{grid-column-end:8}.xs\:cu-col-end-9{grid-column-end:9}.xs\:cu-col-end-10{grid-column-end:10}.xs\:cu-col-end-11{grid-column-end:11}.xs\:cu-col-end-12{grid-column-end:12}.xs\:cu-col-end-13{grid-column-end:13}.xs\:cu-col-end-auto{grid-column-end:auto}.xs\:cu-row-span-1{grid-row:span 1/span 1}.xs\:cu-row-span-2{grid-row:span 2/span 2}.xs\:cu-row-span-3{grid-row:span 3/span 3}.xs\:cu-row-span-4{grid-row:span 4/span 4}.xs\:cu-row-span-5{grid-row:span 5/span 5}.xs\:cu-row-span-6{grid-row:span 6/span 6}.xs\:cu-row-span-7{grid-row:span 7/span 7}.xs\:cu-row-span-8{grid-row:span 8/span 8}.xs\:cu-row-span-9{grid-row:span 9/span 9}.xs\:cu-row-span-10{grid-row:span 10/span 10}.xs\:cu-row-span-11{grid-row:span 11/span 11}.xs\:cu-row-span-12{grid-row:span 12/span 12}.xs\:cu-row-span-auto{grid-row:span auto/span auto}}@media (width>=768px){.sm\:cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.sm\:cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.sm\:cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.sm\:cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.sm\:cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:cu-col-span-1{grid-column:span 1/span 1}.sm\:cu-col-span-2{grid-column:span 2/span 2}.sm\:cu-col-span-3{grid-column:span 3/span 3}.sm\:cu-col-span-4{grid-column:span 4/span 4}.sm\:cu-col-span-5{grid-column:span 5/span 5}.sm\:cu-col-span-6{grid-column:span 6/span 6}.sm\:cu-col-span-7{grid-column:span 7/span 7}.sm\:cu-col-span-8{grid-column:span 8/span 8}.sm\:cu-col-span-9{grid-column:span 9/span 9}.sm\:cu-col-span-10{grid-column:span 10/span 10}.sm\:cu-col-span-11{grid-column:span 11/span 11}.sm\:cu-col-span-12{grid-column:span 12/span 12}.sm\:cu-col-span-auto{grid-column:span auto/span auto}.sm\:cu-col-start-1{grid-column-start:1}.sm\:cu-col-start-2{grid-column-start:2}.sm\:cu-col-start-3{grid-column-start:3}.sm\:cu-col-start-4{grid-column-start:4}.sm\:cu-col-start-5{grid-column-start:5}.sm\:cu-col-start-6{grid-column-start:6}.sm\:cu-col-start-7{grid-column-start:7}.sm\:cu-col-start-8{grid-column-start:8}.sm\:cu-col-start-9{grid-column-start:9}.sm\:cu-col-start-10{grid-column-start:10}.sm\:cu-col-start-11{grid-column-start:11}.sm\:cu-col-start-12{grid-column-start:12}.sm\:cu-col-start-13{grid-column-start:13}.sm\:cu-col-start-auto{grid-column-start:auto}.sm\:cu-col-end-1{grid-column-end:1}.sm\:cu-col-end-2{grid-column-end:2}.sm\:cu-col-end-3{grid-column-end:3}.sm\:cu-col-end-4{grid-column-end:4}.sm\:cu-col-end-5{grid-column-end:5}.sm\:cu-col-end-6{grid-column-end:6}.sm\:cu-col-end-7{grid-column-end:7}.sm\:cu-col-end-8{grid-column-end:8}.sm\:cu-col-end-9{grid-column-end:9}.sm\:cu-col-end-10{grid-column-end:10}.sm\:cu-col-end-11{grid-column-end:11}.sm\:cu-col-end-12{grid-column-end:12}.sm\:cu-col-end-13{grid-column-end:13}.sm\:cu-col-end-auto{grid-column-end:auto}.sm\:cu-row-span-1{grid-row:span 1/span 1}.sm\:cu-row-span-2{grid-row:span 2/span 2}.sm\:cu-row-span-3{grid-row:span 3/span 3}.sm\:cu-row-span-4{grid-row:span 4/span 4}.sm\:cu-row-span-5{grid-row:span 5/span 5}.sm\:cu-row-span-6{grid-row:span 6/span 6}.sm\:cu-row-span-7{grid-row:span 7/span 7}.sm\:cu-row-span-8{grid-row:span 8/span 8}.sm\:cu-row-span-9{grid-row:span 9/span 9}.sm\:cu-row-span-10{grid-row:span 10/span 10}.sm\:cu-row-span-11{grid-row:span 11/span 11}.sm\:cu-row-span-12{grid-row:span 12/span 12}.sm\:cu-row-span-auto{grid-row:span auto/span auto}}@media (width>=1024px){.md\:cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.md\:cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.md\:cu-col-span-1{grid-column:span 1/span 1}.md\:cu-col-span-2{grid-column:span 2/span 2}.md\:cu-col-span-3{grid-column:span 3/span 3}.md\:cu-col-span-4{grid-column:span 4/span 4}.md\:cu-col-span-5{grid-column:span 5/span 5}.md\:cu-col-span-6{grid-column:span 6/span 6}.md\:cu-col-span-7{grid-column:span 7/span 7}.md\:cu-col-span-8{grid-column:span 8/span 8}.md\:cu-col-span-9{grid-column:span 9/span 9}.md\:cu-col-span-10{grid-column:span 10/span 10}.md\:cu-col-span-11{grid-column:span 11/span 11}.md\:cu-col-span-12{grid-column:span 12/span 12}.md\:cu-col-span-auto{grid-column:span auto/span auto}.md\:cu-col-start-1{grid-column-start:1}.md\:cu-col-start-2{grid-column-start:2}.md\:cu-col-start-3{grid-column-start:3}.md\:cu-col-start-4{grid-column-start:4}.md\:cu-col-start-5{grid-column-start:5}.md\:cu-col-start-6{grid-column-start:6}.md\:cu-col-start-7{grid-column-start:7}.md\:cu-col-start-8{grid-column-start:8}.md\:cu-col-start-9{grid-column-start:9}.md\:cu-col-start-10{grid-column-start:10}.md\:cu-col-start-11{grid-column-start:11}.md\:cu-col-start-12{grid-column-start:12}.md\:cu-col-start-13{grid-column-start:13}.md\:cu-col-start-auto{grid-column-start:auto}.md\:cu-col-end-1{grid-column-end:1}.md\:cu-col-end-2{grid-column-end:2}.md\:cu-col-end-3{grid-column-end:3}.md\:cu-col-end-4{grid-column-end:4}.md\:cu-col-end-5{grid-column-end:5}.md\:cu-col-end-6{grid-column-end:6}.md\:cu-col-end-7{grid-column-end:7}.md\:cu-col-end-8{grid-column-end:8}.md\:cu-col-end-9{grid-column-end:9}.md\:cu-col-end-10{grid-column-end:10}.md\:cu-col-end-11{grid-column-end:11}.md\:cu-col-end-12{grid-column-end:12}.md\:cu-col-end-13{grid-column-end:13}.md\:cu-col-end-auto{grid-column-end:auto}.md\:cu-row-span-1{grid-row:span 1/span 1}.md\:cu-row-span-2{grid-row:span 2/span 2}.md\:cu-row-span-3{grid-row:span 3/span 3}.md\:cu-row-span-4{grid-row:span 4/span 4}.md\:cu-row-span-5{grid-row:span 5/span 5}.md\:cu-row-span-6{grid-row:span 6/span 6}.md\:cu-row-span-7{grid-row:span 7/span 7}.md\:cu-row-span-8{grid-row:span 8/span 8}.md\:cu-row-span-9{grid-row:span 9/span 9}.md\:cu-row-span-10{grid-row:span 10/span 10}.md\:cu-row-span-11{grid-row:span 11/span 11}.md\:cu-row-span-12{grid-row:span 12/span 12}.md\:cu-row-span-auto{grid-row:span auto/span auto}}@media (width>=1280px){.lg\:cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.lg\:cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.lg\:cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.lg\:cu-col-span-1{grid-column:span 1/span 1}.lg\:cu-col-span-2{grid-column:span 2/span 2}.lg\:cu-col-span-3{grid-column:span 3/span 3}.lg\:cu-col-span-4{grid-column:span 4/span 4}.lg\:cu-col-span-5{grid-column:span 5/span 5}.lg\:cu-col-span-6{grid-column:span 6/span 6}.lg\:cu-col-span-7{grid-column:span 7/span 7}.lg\:cu-col-span-8{grid-column:span 8/span 8}.lg\:cu-col-span-9{grid-column:span 9/span 9}.lg\:cu-col-span-10{grid-column:span 10/span 10}.lg\:cu-col-span-11{grid-column:span 11/span 11}.lg\:cu-col-span-12{grid-column:span 12/span 12}.lg\:cu-col-span-auto{grid-column:span auto/span auto}.lg\:cu-col-start-1{grid-column-start:1}.lg\:cu-col-start-2{grid-column-start:2}.lg\:cu-col-start-3{grid-column-start:3}.lg\:cu-col-start-4{grid-column-start:4}.lg\:cu-col-start-5{grid-column-start:5}.lg\:cu-col-start-6{grid-column-start:6}.lg\:cu-col-start-7{grid-column-start:7}.lg\:cu-col-start-8{grid-column-start:8}.lg\:cu-col-start-9{grid-column-start:9}.lg\:cu-col-start-10{grid-column-start:10}.lg\:cu-col-start-11{grid-column-start:11}.lg\:cu-col-start-12{grid-column-start:12}.lg\:cu-col-start-13{grid-column-start:13}.lg\:cu-col-start-auto{grid-column-start:auto}.lg\:cu-col-end-1{grid-column-end:1}.lg\:cu-col-end-2{grid-column-end:2}.lg\:cu-col-end-3{grid-column-end:3}.lg\:cu-col-end-4{grid-column-end:4}.lg\:cu-col-end-5{grid-column-end:5}.lg\:cu-col-end-6{grid-column-end:6}.lg\:cu-col-end-7{grid-column-end:7}.lg\:cu-col-end-8{grid-column-end:8}.lg\:cu-col-end-9{grid-column-end:9}.lg\:cu-col-end-10{grid-column-end:10}.lg\:cu-col-end-11{grid-column-end:11}.lg\:cu-col-end-12{grid-column-end:12}.lg\:cu-col-end-13{grid-column-end:13}.lg\:cu-col-end-auto{grid-column-end:auto}.lg\:cu-row-span-1{grid-row:span 1/span 1}.lg\:cu-row-span-2{grid-row:span 2/span 2}.lg\:cu-row-span-3{grid-row:span 3/span 3}.lg\:cu-row-span-4{grid-row:span 4/span 4}.lg\:cu-row-span-5{grid-row:span 5/span 5}.lg\:cu-row-span-6{grid-row:span 6/span 6}.lg\:cu-row-span-7{grid-row:span 7/span 7}.lg\:cu-row-span-8{grid-row:span 8/span 8}.lg\:cu-row-span-9{grid-row:span 9/span 9}.lg\:cu-row-span-10{grid-row:span 10/span 10}.lg\:cu-row-span-11{grid-row:span 11/span 11}.lg\:cu-row-span-12{grid-row:span 12/span 12}.lg\:cu-row-span-auto{grid-row:span auto/span auto}}@media (width>=1536px){.xl\:cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xl\:cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xl\:cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xl\:cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xl\:cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xl\:cu-col-span-1{grid-column:span 1/span 1}.xl\:cu-col-span-2{grid-column:span 2/span 2}.xl\:cu-col-span-3{grid-column:span 3/span 3}.xl\:cu-col-span-4{grid-column:span 4/span 4}.xl\:cu-col-span-5{grid-column:span 5/span 5}.xl\:cu-col-span-6{grid-column:span 6/span 6}.xl\:cu-col-span-7{grid-column:span 7/span 7}.xl\:cu-col-span-8{grid-column:span 8/span 8}.xl\:cu-col-span-9{grid-column:span 9/span 9}.xl\:cu-col-span-10{grid-column:span 10/span 10}.xl\:cu-col-span-11{grid-column:span 11/span 11}.xl\:cu-col-span-12{grid-column:span 12/span 12}.xl\:cu-col-span-auto{grid-column:span auto/span auto}.xl\:cu-col-start-1{grid-column-start:1}.xl\:cu-col-start-2{grid-column-start:2}.xl\:cu-col-start-3{grid-column-start:3}.xl\:cu-col-start-4{grid-column-start:4}.xl\:cu-col-start-5{grid-column-start:5}.xl\:cu-col-start-6{grid-column-start:6}.xl\:cu-col-start-7{grid-column-start:7}.xl\:cu-col-start-8{grid-column-start:8}.xl\:cu-col-start-9{grid-column-start:9}.xl\:cu-col-start-10{grid-column-start:10}.xl\:cu-col-start-11{grid-column-start:11}.xl\:cu-col-start-12{grid-column-start:12}.xl\:cu-col-start-13{grid-column-start:13}.xl\:cu-col-start-auto{grid-column-start:auto}.xl\:cu-col-end-1{grid-column-end:1}.xl\:cu-col-end-2{grid-column-end:2}.xl\:cu-col-end-3{grid-column-end:3}.xl\:cu-col-end-4{grid-column-end:4}.xl\:cu-col-end-5{grid-column-end:5}.xl\:cu-col-end-6{grid-column-end:6}.xl\:cu-col-end-7{grid-column-end:7}.xl\:cu-col-end-8{grid-column-end:8}.xl\:cu-col-end-9{grid-column-end:9}.xl\:cu-col-end-10{grid-column-end:10}.xl\:cu-col-end-11{grid-column-end:11}.xl\:cu-col-end-12{grid-column-end:12}.xl\:cu-col-end-13{grid-column-end:13}.xl\:cu-col-end-auto{grid-column-end:auto}.xl\:cu-row-span-1{grid-row:span 1/span 1}.xl\:cu-row-span-2{grid-row:span 2/span 2}.xl\:cu-row-span-3{grid-row:span 3/span 3}.xl\:cu-row-span-4{grid-row:span 4/span 4}.xl\:cu-row-span-5{grid-row:span 5/span 5}.xl\:cu-row-span-6{grid-row:span 6/span 6}.xl\:cu-row-span-7{grid-row:span 7/span 7}.xl\:cu-row-span-8{grid-row:span 8/span 8}.xl\:cu-row-span-9{grid-row:span 9/span 9}.xl\:cu-row-span-10{grid-row:span 10/span 10}.xl\:cu-row-span-11{grid-row:span 11/span 11}.xl\:cu-row-span-12{grid-row:span 12/span 12}.xl\:cu-row-span-auto{grid-row:span auto/span auto}}.cu-gap{gap:var(--gap)}.cu-gap-0\.5{gap:var(--canon-space-0_5)}.cu-gap-1{gap:var(--canon-space-1)}.cu-gap-1\.5{gap:var(--canon-space-1_5)}.cu-gap-2{gap:var(--canon-space-2)}.cu-gap-3{gap:var(--canon-space-3)}.cu-gap-4{gap:var(--canon-space-4)}.cu-gap-5{gap:var(--canon-space-5)}.cu-gap-6{gap:var(--canon-space-6)}.cu-gap-7{gap:var(--canon-space-7)}.cu-gap-8{gap:var(--canon-space-8)}.cu-gap-9{gap:var(--canon-space-9)}.cu-gap-10{gap:var(--canon-space-10)}.cu-gap-11{gap:var(--canon-space-11)}.cu-gap-12{gap:var(--canon-space-12)}.cu-gap-13{gap:var(--canon-space-13)}.cu-gap-14{gap:var(--canon-space-14)}@media (width>=640px){.xs\:cu-gap{gap:var(--gap-xs)}.xs\:cu-gap-0\.5{gap:var(--canon-space-0_5)}.xs\:cu-gap-1{gap:var(--canon-space-1)}.xs\:cu-gap-1\.5{gap:var(--canon-space-1_5)}.xs\:cu-gap-2{gap:var(--canon-space-2)}.xs\:cu-gap-3{gap:var(--canon-space-3)}.xs\:cu-gap-4{gap:var(--canon-space-4)}.xs\:cu-gap-5{gap:var(--canon-space-5)}.xs\:cu-gap-6{gap:var(--canon-space-6)}.xs\:cu-gap-7{gap:var(--canon-space-7)}.xs\:cu-gap-8{gap:var(--canon-space-8)}.xs\:cu-gap-9{gap:var(--canon-space-9)}.xs\:cu-gap-10{gap:var(--canon-space-10)}.xs\:cu-gap-11{gap:var(--canon-space-11)}.xs\:cu-gap-12{gap:var(--canon-space-12)}.xs\:cu-gap-13{gap:var(--canon-space-13)}.xs\:cu-gap-14{gap:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-gap{gap:var(--gap-sm)}.sm\:cu-gap-0\.5{gap:var(--canon-space-0_5)}.sm\:cu-gap-1{gap:var(--canon-space-1)}.sm\:cu-gap-1\.5{gap:var(--canon-space-1_5)}.sm\:cu-gap-2{gap:var(--canon-space-2)}.sm\:cu-gap-3{gap:var(--canon-space-3)}.sm\:cu-gap-4{gap:var(--canon-space-4)}.sm\:cu-gap-5{gap:var(--canon-space-5)}.sm\:cu-gap-6{gap:var(--canon-space-6)}.sm\:cu-gap-7{gap:var(--canon-space-7)}.sm\:cu-gap-8{gap:var(--canon-space-8)}.sm\:cu-gap-9{gap:var(--canon-space-9)}.sm\:cu-gap-10{gap:var(--canon-space-10)}.sm\:cu-gap-11{gap:var(--canon-space-11)}.sm\:cu-gap-12{gap:var(--canon-space-12)}.sm\:cu-gap-13{gap:var(--canon-space-13)}.sm\:cu-gap-14{gap:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-gap{gap:var(--gap-md)}.md\:cu-gap-0\.5{gap:var(--canon-space-0_5)}.md\:cu-gap-1{gap:var(--canon-space-1)}.md\:cu-gap-1\.5{gap:var(--canon-space-1_5)}.md\:cu-gap-2{gap:var(--canon-space-2)}.md\:cu-gap-3{gap:var(--canon-space-3)}.md\:cu-gap-4{gap:var(--canon-space-4)}.md\:cu-gap-5{gap:var(--canon-space-5)}.md\:cu-gap-6{gap:var(--canon-space-6)}.md\:cu-gap-7{gap:var(--canon-space-7)}.md\:cu-gap-8{gap:var(--canon-space-8)}.md\:cu-gap-9{gap:var(--canon-space-9)}.md\:cu-gap-10{gap:var(--canon-space-10)}.md\:cu-gap-11{gap:var(--canon-space-11)}.md\:cu-gap-12{gap:var(--canon-space-12)}.md\:cu-gap-13{gap:var(--canon-space-13)}.md\:cu-gap-14{gap:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-gap{gap:var(--gap-lg)}.lg\:cu-gap-0\.5{gap:var(--canon-space-0_5)}.lg\:cu-gap-1{gap:var(--canon-space-1)}.lg\:cu-gap-1\.5{gap:var(--canon-space-1_5)}.lg\:cu-gap-2{gap:var(--canon-space-2)}.lg\:cu-gap-3{gap:var(--canon-space-3)}.lg\:cu-gap-4{gap:var(--canon-space-4)}.lg\:cu-gap-5{gap:var(--canon-space-5)}.lg\:cu-gap-6{gap:var(--canon-space-6)}.lg\:cu-gap-7{gap:var(--canon-space-7)}.lg\:cu-gap-8{gap:var(--canon-space-8)}.lg\:cu-gap-9{gap:var(--canon-space-9)}.lg\:cu-gap-10{gap:var(--canon-space-10)}.lg\:cu-gap-11{gap:var(--canon-space-11)}.lg\:cu-gap-12{gap:var(--canon-space-12)}.lg\:cu-gap-13{gap:var(--canon-space-13)}.lg\:cu-gap-14{gap:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-gap{gap:var(--gap-xl)}.xl\:cu-gap-0\.5{gap:var(--canon-space-0_5)}.xl\:cu-gap-1{gap:var(--canon-space-1)}.xl\:cu-gap-1\.5{gap:var(--canon-space-1_5)}.xl\:cu-gap-2{gap:var(--canon-space-2)}.xl\:cu-gap-3{gap:var(--canon-space-3)}.xl\:cu-gap-4{gap:var(--canon-space-4)}.xl\:cu-gap-5{gap:var(--canon-space-5)}.xl\:cu-gap-6{gap:var(--canon-space-6)}.xl\:cu-gap-7{gap:var(--canon-space-7)}.xl\:cu-gap-8{gap:var(--canon-space-8)}.xl\:cu-gap-9{gap:var(--canon-space-9)}.xl\:cu-gap-10{gap:var(--canon-space-10)}.xl\:cu-gap-11{gap:var(--canon-space-11)}.xl\:cu-gap-12{gap:var(--canon-space-12)}.xl\:cu-gap-13{gap:var(--canon-space-13)}.xl\:cu-gap-14{gap:var(--canon-space-14)}}.cu-align-start{align-items:start}.cu-align-center{align-items:center}.cu-align-end{align-items:end}.cu-align-stretch{align-items:stretch}.cu-jc-start{justify-content:start}.cu-jc-center{justify-content:center}.cu-jc-end{justify-content:end}.cu-jc-between{justify-content:space-between}.cu-fd-row{flex-direction:row}.cu-fd-column{flex-direction:column}.cu-fd-row-reverse{flex-direction:row-reverse}.cu-fd-column-reverse{flex-direction:column-reverse}@media (width>=640px){.xs\:cu-align-start{align-items:start}.xs\:cu-align-center{align-items:center}.xs\:cu-align-end{align-items:end}.xs\:cu-align-stretch{align-items:stretch}.xs\:cu-jc-start{justify-content:start}.xs\:cu-jc-center{justify-content:center}.xs\:cu-jc-end{justify-content:end}.xs\:cu-jc-between{justify-content:space-between}.xs\:cu-fd-row{flex-direction:row}.xs\:cu-fd-column{flex-direction:column}.xs\:cu-fd-row-reverse{flex-direction:row-reverse}.xs\:cu-fd-column-reverse{flex-direction:column-reverse}}@media (width>=768px){.sm\:cu-align-start{align-items:start}.sm\:cu-align-center{align-items:center}.sm\:cu-align-end{align-items:end}.sm\:cu-align-stretch{align-items:stretch}.sm\:cu-jc-start{justify-content:start}.sm\:cu-jc-center{justify-content:center}.sm\:cu-jc-end{justify-content:end}.sm\:cu-jc-between{justify-content:space-between}.sm\:cu-fd-row{flex-direction:row}.sm\:cu-fd-column{flex-direction:column}.sm\:cu-fd-row-reverse{flex-direction:row-reverse}.sm\:cu-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1024px){.md\:cu-align-start{align-items:start}.md\:cu-align-center{align-items:center}.md\:cu-align-end{align-items:end}.md\:cu-align-stretch{align-items:stretch}.md\:cu-jc-start{justify-content:start}.md\:cu-jc-center{justify-content:center}.md\:cu-jc-end{justify-content:end}.md\:cu-jc-between{justify-content:space-between}.md\:cu-fd-row{flex-direction:row}.md\:cu-fd-column{flex-direction:column}.md\:cu-fd-row-reverse{flex-direction:row-reverse}.md\:cu-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1280px){.lg\:cu-align-start{align-items:start}.lg\:cu-align-center{align-items:center}.lg\:cu-align-end{align-items:end}.lg\:cu-align-stretch{align-items:stretch}.lg\:cu-jc-start{justify-content:start}.lg\:cu-jc-center{justify-content:center}.lg\:cu-jc-end{justify-content:end}.lg\:cu-jc-between{justify-content:space-between}.lg\:cu-fd-row{flex-direction:row}.lg\:cu-fd-column{flex-direction:column}.lg\:cu-fd-row-reverse{flex-direction:row-reverse}.lg\:cu-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1536px){.xl\:cu-align-start{align-items:start}.xl\:cu-align-center{align-items:center}.xl\:cu-align-end{align-items:end}.xl\:cu-align-stretch{align-items:stretch}.xl\:cu-jc-start{justify-content:start}.xl\:cu-jc-center{justify-content:center}.xl\:cu-jc-end{justify-content:end}.xl\:cu-jc-between{justify-content:space-between}.xl\:cu-fd-row{flex-direction:row}.xl\:cu-fd-column{flex-direction:column}.xl\:cu-fd-row-reverse{flex-direction:row-reverse}.xl\:cu-fd-column-reverse{flex-direction:column-reverse}}:where(a){color:inherit;text-decoration:none}:root{--canon-font-regular:system-ui;--canon-font-monospace:ui-monospace,"Menlo","Monaco","Consolas","Liberation Mono","Courier New",monospace;--canon-font-weight-regular:400;--canon-font-weight-bold:600;--canon-font-size-1:.625rem;--canon-font-size-2:.75rem;--canon-font-size-3:.875rem;--canon-font-size-4:1rem;--canon-font-size-5:1.25rem;--canon-font-size-6:1.5rem;--canon-font-size-7:2rem;--canon-font-size-8:3rem;--canon-font-size-9:4rem;--canon-font-size-10:5.75rem;--canon-space:.25rem;--canon-space-0_5:calc(var(--canon-space)*.5);--canon-space-1:var(--canon-space);--canon-space-1_5:calc(var(--canon-space)*1.5);--canon-space-2:calc(var(--canon-space)*2);--canon-space-3:calc(var(--canon-space)*3);--canon-space-4:calc(var(--canon-space)*4);--canon-space-5:calc(var(--canon-space)*5);--canon-space-6:calc(var(--canon-space)*6);--canon-space-7:calc(var(--canon-space)*7);--canon-space-8:calc(var(--canon-space)*8);--canon-space-9:calc(var(--canon-space)*9);--canon-space-10:calc(var(--canon-space)*10);--canon-space-11:calc(var(--canon-space)*11);--canon-space-12:calc(var(--canon-space)*12);--canon-space-13:calc(var(--canon-space)*13);--canon-space-14:calc(var(--canon-space)*14);--canon-radius-1:calc(.125rem);--canon-radius-2:calc(.25rem);--canon-radius-3:calc(.5rem);--canon-radius-4:calc(.75rem);--canon-radius-5:calc(1rem);--canon-radius-6:calc(1.25rem);--canon-radius-full:9999px;--canon-black:#000;--canon-white:#fff;--canon-gray-1:#f8f8f8;--canon-gray-2:#ececec;--canon-gray-3:#d9d9d9;--canon-gray-4:#c1c1c1;--canon-gray-5:#9e9e9e;--canon-gray-6:#8c8c8c;--canon-gray-7:#757575;--canon-gray-8:#595959;--canon-bg:var(--canon-gray-1);--canon-bg-surface-1:var(--canon-white);--canon-bg-surface-2:var(--canon-gray-2);--canon-bg-solid:#1f5493;--canon-bg-solid-hover:#163a66;--canon-bg-solid-pressed:#0f2b4e;--canon-bg-solid-disabled:#ebebeb;--canon-bg-tint:transparent;--canon-bg-tint-hover:#1f549366;--canon-bg-tint-pressed:#1f549399;--canon-bg-tint-disabled:#ebebeb;--canon-bg-danger:#feebe7;--canon-bg-warning:#fff2b2;--canon-bg-success:#e6f6eb;--canon-fg-primary:var(--canon-black);--canon-fg-secondary:var(--canon-gray-7);--canon-fg-link:#1f5493;--canon-fg-link-hover:#1f2d5c;--canon-fg-disabled:#9e9e9e;--canon-fg-solid:var(--canon-white);--canon-fg-solid-disabled:#9c9c9c;--canon-fg-tint:#1f5493;--canon-fg-tint-disabled:var(--canon-gray-5);--canon-fg-danger:#e22b2b;--canon-fg-warning:#e36d05;--canon-fg-success:#1db954;--canon-border:#0000001a;--canon-border-hover:#0003;--canon-border-pressed:#0006;--canon-border-disabled:#0000001a;--canon-border-danger:#f87a7a;--canon-border-warning:#e36d05;--canon-border-success:#53db83;--canon-ring:#1f5493;--canon-scrollbar:#a0a0a03b;--canon-scrollbar-thumb:#a0a0a0}[data-theme=dark]{--canon-gray-1:#191919;--canon-gray-2:#242424;--canon-gray-3:#373737;--canon-gray-4:#464646;--canon-gray-5:#575757;--canon-gray-6:#7b7b7b;--canon-gray-7:#9e9e9e;--canon-gray-8:#b4b4b4;--canon-bg:var(--canon-black);--canon-bg-surface-1:var(--canon-gray-1);--canon-bg-surface-2:var(--canon-gray-2);--canon-bg-solid:#9cc9ff;--canon-bg-solid-hover:#83b9fd;--canon-bg-solid-pressed:#83b9fd;--canon-bg-solid-disabled:#222;--canon-bg-tint:transparent;--canon-bg-tint-hover:#9cc9ff1f;--canon-bg-tint-pressed:#9cc9ff29;--canon-bg-tint-disabled:transparent;--canon-bg-danger:#3b1219;--canon-bg-warning:#302008;--canon-bg-success:#132d21;--canon-fg-primary:var(--canon-white);--canon-fg-secondary:var(--canon-gray-7);--canon-fg-link:#9cc9ff;--canon-fg-link-hover:#7eb5f7;--canon-fg-disabled:var(--canon-gray-7);--canon-fg-solid:#101821;--canon-fg-solid-disabled:var(--canon-gray-5);--canon-fg-tint:#9cc9ff;--canon-fg-tint-disabled:var(--canon-gray-5);--canon-fg-danger:#e22b2b;--canon-fg-warning:#e36d05;--canon-fg-success:#1db954;--canon-border:#fff3;--canon-border-hover:#fff6;--canon-border-pressed:#ffffff80;--canon-border-disabled:#fff3;--canon-border-danger:#f87a7a;--canon-border-warning:#e36d05;--canon-border-success:#53db83;--canon-ring:#1f5493;--canon-scrollbar:#3636363a;--canon-scrollbar-thumb:#575757}.canon-AvatarRoot{vertical-align:middle;user-select:none;color:var(--canon-fg-primary);background-color:var(--canon-bg-surface-2);border-radius:100%;justify-content:center;align-items:center;width:2rem;height:2rem;font-size:1rem;font-weight:500;line-height:1;display:inline-flex;overflow:hidden}.canon-AvatarRoot[data-size=small]{width:1.5rem;height:1.5rem}.canon-AvatarRoot[data-size=medium]{width:2rem;height:2rem}.canon-AvatarRoot[data-size=large]{width:3rem;height:3rem}.canon-AvatarImage{object-fit:cover;width:100%;height:100%}.canon-AvatarFallback{width:100%;height:100%;font-size:var(--canon-font-size-3);font-weight:var(--canon-font-weight-regular);box-shadow:inset 0 0 0 1px var(--canon-border);border-radius:var(--canon-radius-full);justify-content:center;align-items:center;display:flex}.canon-Box{font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-primary)}.canon-Button{user-select:none;font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-bold);cursor:pointer;border-radius:var(--canon-radius-2);justify-content:center;align-items:center;gap:var(--canon-space-1_5);border:none;padding:0;display:inline-flex;&:disabled{cursor:not-allowed}}.canon-Button[data-variant=primary]{background-color:var(--canon-bg-solid);color:var(--canon-fg-solid);transition:background-color .15s,box-shadow .15s;&:hover{background-color:var(--canon-bg-solid-hover)}&:active{background-color:var(--canon-bg-solid-pressed)}&:focus-visible{outline:2px solid var(--canon-ring);outline-offset:2px}&:disabled{background-color:var(--canon-bg-solid-disabled);color:var(--canon-fg-solid-disabled)}}.canon-Button[data-variant=secondary]{background-color:var(--canon-bg-surface-1);box-shadow:inset 0 0 0 1px var(--canon-border);color:var(--canon-fg-primary);transition:box-shadow .15s;&:hover{box-shadow:inset 0 0 0 1px var(--canon-border-hover)}&:active{box-shadow:inset 0 0 0 1px var(--canon-border-pressed)}&:focus-visible{box-shadow:inset 0 0 0 2px var(--canon-ring);outline:none;transition:none}&:disabled{box-shadow:inset 0 0 0 1px var(--canon-border-disabled);color:var(--canon-fg-disabled)}}.canon-Button[data-size=medium]{font-size:var(--canon-font-size-4);padding:0 var(--canon-space-3);height:40px}.canon-Button[data-size=small]{font-size:var(--canon-font-size-3);padding:0 var(--canon-space-2);height:32px}.canon-ButtonIcon[data-size=small],.canon-ButtonIcon[data-size=small] svg{width:1rem;height:1rem}.canon-ButtonIcon[data-size=medium],.canon-ButtonIcon[data-size=medium] svg{width:1.25rem;height:1.25rem}.canon-CheckboxRoot{width:1rem;height:1rem;box-shadow:inset 0 0 0 1px var(--canon-border);cursor:pointer;background-color:var(--canon-bg-surface-1);border:none;border-radius:2px;flex-shrink:0;justify-content:center;align-items:center;padding:0;transition:background-color .2s ease-in-out;display:flex}.canon-CheckboxRoot:focus-visible{outline:2px solid var(--canon-ring);outline-offset:2px;transition:none}.canon-CheckboxRoot[data-checked]{background-color:var(--canon-bg-solid);box-shadow:none;color:var(--canon-fg-solid)}.canon-CheckboxLabel{align-items:center;gap:var(--canon-space-2);font-size:var(--canon-font-size-3);font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-primary);user-select:none;flex-direction:row;display:flex;&:hover{& .canon-CheckboxRoot:not([data-checked]){box-shadow:inset 0 0 0 1px var(--canon-border-hover)}}}.canon-CheckboxIndicator{color:var(--canon-fg-solid);justify-content:center;align-items:center;display:flex}.canon-CollapsiblePanel{height:var(--collapsible-panel-height);transition:all .15s ease-out;display:flex;overflow:hidden;&[data-starting-style],&[data-ending-style]{height:0}}.canon-Container{max-width:120rem;padding-inline:var(--canon-space-4);margin-inline:auto;transition:padding .2s ease-in-out}@media (width>=640px){.canon-Container{padding-inline:var(--canon-space-8)}}@media (width>=1024px){.canon-Container{padding-inline:var(--canon-space-12)}}.canon-DataTableRoot{gap:var(--canon-space-3);flex-direction:column;display:flex}.canon-DataTablePagination{padding-top:var(--canon-space-5);justify-content:space-between;align-items:center;display:flex}.canon-DataTablePagination--left{justify-content:space-between;align-items:center;display:flex}.canon-DataTablePagination--right{justify-content:space-between;align-items:center;gap:var(--canon-space-2);display:flex}.canon-DataTablePagination--select{min-width:10.5rem}.canon-FieldLabel{margin-bottom:var(--canon-space-3);gap:var(--canon-space-1);flex-direction:column;display:flex}.canon-FieldLabelLabel{color:var(--canon-fg-primary);cursor:pointer;font-weight:var(--canon-font-weight-regular);font-size:var(--canon-font-size-2);margin-right:auto}.canon-FieldLabelSecondaryLabel{color:var(--canon-fg-secondary);font-weight:var(--canon-font-weight-regular);margin-left:var(--canon-space-1)}.canon-FieldLabelDescription{font-weight:var(--canon-font-weight-regular);font-size:var(--canon-font-size-2);color:var(--canon-fg-secondary);margin:0}.canon-Flex{min-width:0;display:flex}.canon-Grid{display:grid}.canon-Heading{font-family:var(--canon-font-regular);color:var(--canon-fg-primary);margin:0;padding:0;line-height:100%}.canon-Heading[data-variant=display]{font-size:var(--canon-font-size-10);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-variant=title1]{font-size:var(--canon-font-size-9);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-variant=title2]{font-size:var(--canon-font-size-8);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-variant=title3]{font-size:var(--canon-font-size-7);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-variant=title4]{font-size:var(--canon-font-size-6);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-variant=title5]{font-size:var(--canon-font-size-5);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-color=primary]{color:var(--canon-fg-primary)}.canon-Heading[data-color=secondary]{color:var(--canon-fg-secondary)}.canon-Heading[data-truncate]{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.canon-Icon{width:1rem;height:1rem}.canon-IconButton{user-select:none;font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-bold);cursor:pointer;border-radius:var(--canon-radius-2);justify-content:center;align-items:center;gap:var(--canon-space-1_5);border:none;padding:0;display:inline-flex;&:disabled{cursor:not-allowed}}.canon-IconButton[data-variant=primary]{background-color:var(--canon-bg-solid);color:var(--canon-fg-solid);transition:background-color .15s,box-shadow .15s;&:hover{background-color:var(--canon-bg-solid-hover)}&:active{background-color:var(--canon-bg-solid-pressed)}&:focus-visible{outline:2px solid var(--canon-ring);outline-offset:2px}&:disabled{background-color:var(--canon-bg-solid-disabled);color:var(--canon-fg-solid-disabled)}}.canon-IconButton[data-variant=secondary]{background-color:var(--canon-bg-surface-1);box-shadow:inset 0 0 0 1px var(--canon-border);color:var(--canon-fg-primary);transition:box-shadow .15s;&:hover{box-shadow:inset 0 0 0 1px var(--canon-border-hover)}&:active{box-shadow:inset 0 0 0 1px var(--canon-border-pressed)}&:focus-visible{box-shadow:inset 0 0 0 2px var(--canon-ring);outline:none;transition:none}&:disabled{box-shadow:inset 0 0 0 1px var(--canon-border-disabled);color:var(--canon-fg-disabled)}}.canon-IconButton[data-size=medium]{font-size:var(--canon-font-size-4);width:40px;height:40px}.canon-IconButton[data-size=small]{font-size:var(--canon-font-size-3);width:32px;height:32px}.canon-IconButtonIcon[data-size=small],.canon-IconButtonIcon[data-size=small] svg{width:1rem;height:1rem}.canon-IconButtonIcon[data-size=medium],.canon-IconButtonIcon[data-size=medium] svg{width:1.25rem;height:1.25rem}.canon-Link{font-family:var(--canon-font-regular);color:var(--canon-fg-link);cursor:pointer;margin:0;padding:0;text-decoration-line:none;&:hover{color:var(--canon-fg-link-hover);text-underline-offset:calc(.025em + 2px);text-decoration-line:underline;text-decoration-style:solid;text-decoration-thickness:min(2px,max(1px,.05em));text-decoration-color:color-mix(in srgb,var(--canon-fg-link-hover)30%,transparent)}}.canon-Link[data-variant=body]{font-size:var(--canon-font-size-3);line-height:140%}.canon-Link[data-variant=subtitle]{font-size:var(--canon-font-size-4);line-height:140%}.canon-Link[data-variant=caption]{font-size:var(--canon-font-size-2);line-height:140%}.canon-Link[data-variant=label]{font-size:var(--canon-font-size-1);line-height:140%}.canon-Link[data-weight=regular]{font-weight:var(--canon-font-weight-regular)}.canon-Link[data-weight=bold]{font-weight:var(--canon-font-weight-bold)}.canon-MenuPositioner{outline:0}.canon-MenuPopup{background-color:var(--canon-bg-surface-1);border:1px solid var(--canon-border);color:var(--canon-fg-primary);transform-origin:var(--transform-origin);max-width:min(var(--available-width),340px);max-height:min(var(--available-height),500px);padding-bottom:var(--canon-space-1);border-radius:.375rem;outline:none;flex-direction:column;transition:transform .15s,opacity .15s;display:flex;position:relative;overflow:auto;&[data-starting-style],&[data-ending-style]{opacity:0;transform:scale(.9)}}.canon-MenuItem{user-select:none;align-items:center;gap:var(--canon-space-2);height:32px;color:var(--canon-fg-primary);border-radius:var(--canon-radius-2);margin-inline:var(--canon-space-1);padding-inline:var(--canon-space-2);font-size:var(--canon-font-size-3);cursor:pointer;outline:0;flex-shrink:0;text-decoration:none;display:flex;&:first-child{margin-top:var(--canon-space-1)}&[data-highlighted]{background-color:var(--canon-gray-3)}}.canon-MenuSubmenuTrigger{user-select:none;justify-content:space-between;align-items:center;gap:var(--canon-space-2);height:32px;color:var(--canon-fg-primary);border-radius:var(--canon-radius-2);margin-inline:var(--canon-space-1);padding-inline:var(--canon-space-2);font-size:var(--canon-font-size-3);cursor:pointer;outline:0;flex-shrink:0;text-decoration:none;display:flex;& .canon-Icon{color:var(--canon-fg-secondary)}&:first-child{margin-top:var(--canon-space-1)}&[data-popup-open],&[data-highlighted]{background-color:var(--canon-gray-3);& .canon-Icon{color:var(--canon-fg-primary)}}}.canon-MenuSeparator{background-color:var(--color-gray-200);height:1px;margin:.375rem 1rem}.canon-SubmenuComboboxSearch{padding-inline:var(--canon-space-3);border:none;border-bottom:1px solid var(--canon-border);background-color:var(--canon-bg-surface-1);width:100%;height:32px;color:var(--canon-fg-primary);line-height:140%;font-size:var(--canon-font-size-3);z-index:1;outline:none;position:sticky;top:0;&::placeholder{color:var(--canon-fg-secondary)}&:disabled{opacity:.6;cursor:not-allowed}}.canon-SubmenuComboboxItems{padding-top:var(--canon-space-2);outline:none;flex-direction:column;display:flex;overflow-y:auto}.canon-SubmenuComboboxNoResults{padding-inline:var(--canon-space-3);padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-4);color:var(--canon-fg-secondary);font-size:var(--canon-font-size-3)}.canon-SubmenuComboboxItem{user-select:none;justify-content:space-between;align-items:center;gap:var(--canon-space-2);height:32px;color:var(--canon-fg-primary);border-radius:var(--canon-radius-2);margin-inline:var(--canon-space-1);padding-inline:var(--canon-space-2);font-size:var(--canon-font-size-3);cursor:pointer;outline:0;flex-shrink:0;text-decoration:none;display:flex;&[data-highlighted]{background-color:var(--canon-gray-3)}&[data-disabled]{opacity:.5;cursor:not-allowed}}.canon-SubmenuComboboxItemCheckbox{width:16px;height:16px;color:var(--canon-fg-primary);border-radius:var(--canon-radius-2);border:1px solid var(--canon-border);background:var(--canon-bg-surface-1);flex-shrink:0;justify-content:center;align-items:center;display:flex}.canon-SubmenuComboboxItemLabel{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.canon-TableRoot{caption-side:bottom;border-collapse:collapse;width:100%}.canon-TableHead{text-align:left;padding:var(--canon-space-3);font-size:var(--canon-font-size-3);color:var(--canon-fg-primary)}.canon-TableBody{color:var(--canon-fg-primary)}.canon-TableRow{border-bottom:1px solid var(--canon-border);transition:color .2s ease-in-out}.canon-TableBody .canon-TableRow:hover{background-color:var(--canon-gray-2)}.canon-TableCell{padding:var(--canon-space-3);font-size:var(--canon-font-size-3)}.canon-TableCellText,.canon-TableCellLink{gap:var(--canon-space-0_5);flex-direction:column;display:flex}.canon-TableCellProfile{gap:var(--canon-space-2);flex-direction:row;align-items:center;display:flex}.canon-TableCellProfileAvatar{vertical-align:middle;user-select:none;color:var(--canon-fg-primary);background-color:var(--canon-bg-surface-2);border-radius:100%;justify-content:center;align-items:center;width:1.25rem;height:1.25rem;font-size:1rem;font-weight:500;line-height:1;display:inline-flex;overflow:hidden}.canon-TableCellProfileAvatarImage{object-fit:cover;width:100%;height:100%}.canon-TableCellProfileAvatarFallback{width:100%;height:100%;font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);box-shadow:inset 0 0 0 1px var(--canon-border);border-radius:var(--canon-radius-full);justify-content:center;align-items:center;display:flex}.canon-TabsRoot{border:1px solid var(--color-gray-200);border-radius:.375rem}.canon-TabsList{z-index:0;display:flex;position:relative}.canon-TabsTab{appearance:none;color:var(--canon-fg-secondary);user-select:none;padding-inline:var(--canon-space-3);height:2rem;font-family:inherit;font-size:.875rem;font-weight:500;line-height:1.25rem;font-size:var(--canon-font-size-2);cursor:pointer;background:0 0;border:0;outline:0;justify-content:center;align-items:center;margin:0;padding-block:0;transition:color .2s ease-in-out;display:flex;&[data-selected]{color:var(--canon-fg-primary)}@media (hover:hover){&:hover{color:var(--canon-fg-primary)}}&:focus-visible{position:relative;&:before{content:"";outline:1px solid var(--canon-ring);outline-offset:-1px;border-radius:.25rem;position:absolute;inset:.25rem 0}}}.canon-TabsIndicator{z-index:-1;translate:var(--active-tab-left)-50%;width:var(--active-tab-width);background-color:var(--canon-bg-solid);height:1px;transition-property:translate,width;transition-duration:.2s;transition-timing-function:ease-in-out;position:absolute;bottom:0;left:0}.canon-TabsPanel{&[hidden]{display:none}}.canon-Text{font-family:var(--canon-font-regular);margin:0;padding:0}.canon-Text[data-variant=body]{font-size:var(--canon-font-size-3);line-height:140%}.canon-Text[data-variant=subtitle]{font-size:var(--canon-font-size-4);line-height:140%}.canon-Text[data-variant=caption]{font-size:var(--canon-font-size-2);line-height:140%}.canon-Text[data-variant=label]{font-size:var(--canon-font-size-1);line-height:140%}.canon-Text[data-weight=regular]{font-weight:var(--canon-font-weight-regular)}.canon-Text[data-weight=bold]{font-weight:var(--canon-font-weight-bold)}.canon-Text[data-color=primary]{color:var(--canon-fg-primary)}.canon-Text[data-color=secondary]{color:var(--canon-fg-secondary)}.canon-Text[data-color=danger]{color:var(--canon-fg-danger)}.canon-Text[data-color=warning]{color:var(--canon-fg-warning)}.canon-Text[data-color=success]{color:var(--canon-fg-success)}.canon-Text[data-truncate]{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.canon-TextField{font-family:var(--canon-font-regular);flex-direction:column;width:100%;display:flex}.canon-TextFieldInputWrapper{position:relative}.canon-TextFieldInputWrapper[data-size=small]{height:2rem}.canon-TextFieldInputWrapper[data-size=medium]{height:2.5rem}.canon-TextFieldIcon{left:var(--canon-space-3);margin-right:var(--canon-space-1);color:var(--canon-fg-primary);flex-shrink:0;position:absolute;top:50%;transform:translateY(-50%)}.canon-TextFieldIcon[data-size=small],.canon-TextFieldIcon[data-size=small] svg{width:1rem;height:1rem}.canon-TextFieldIcon[data-size=medium],.canon-TextFieldIcon[data-size=medium] svg{width:1.25rem;height:1.25rem}.canon-TextFieldInput{padding:0 var(--canon-space-3);border-radius:var(--canon-radius-3);border:1px solid var(--canon-border);background-color:var(--canon-bg-surface-1);font-size:var(--canon-font-size-3);font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-primary);width:100%;height:100%;cursor:inherit;align-items:center;transition:border-color .2s ease-in-out,outline-color .2s ease-in-out;display:flex}.canon-TextFieldInput::placeholder{color:var(--canon-fg-secondary)}.canon-TextFieldInput[data-icon]{padding-left:var(--canon-space-8)}.canon-TextFieldInput[data-focused]{outline-color:var(--canon-border-pressed);outline-width:0}.canon-TextFieldInput[data-hovered]{border-color:var(--canon-border-hover)}.canon-TextFieldInput[data-focused]{border-color:var(--canon-border-pressed);outline-width:0}.canon-TextFieldInput[data-invalid]{border-color:var(--canon-fg-danger)}.canon-TextFieldInput[data-disabled]{opacity:.5;cursor:not-allowed;border:1px solid var(--canon-border-disabled)}.canon-TextFieldError{color:var(--canon-fg-danger);font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);margin-top:var(--canon-space-2)}.canon-TooltipPopup{box-sizing:border-box;transform-origin:var(--transform-origin);background-color:canvas;background-color:var(--canon-bg-surface-1);color:var(--canon-fg-primary);outline:1px solid var(--canon-border);box-shadow:0 10px 15px -3px var(--canon-border),0 4px 6px -4px var(--canon-border);border-radius:.375rem;flex-direction:column;padding:.25rem .5rem;font-size:.875rem;line-height:1.25rem;transition:transform .15s,opacity .15s;display:flex;&[data-starting-style],&[data-ending-style]{opacity:0;transform:scale(.9)}&[data-instant]{transition-duration:0s}}.canon-TooltipArrow{display:flex;&[data-side=top]{bottom:-8px;rotate:180deg}&[data-side=bottom]{top:-8px;rotate:none}&[data-side=left]{right:-13px;rotate:90deg}&[data-side=right]{left:-13px;rotate:-90deg}}.canon-TooltipArrow-fill{fill:var(--canon-bg-surface-1)}.canon-TooltipArrow-outer-stroke{@media (prefers-color-scheme:light){fill:var(--canon-border)}}.canon-ScrollAreaRoot{box-sizing:border-box;width:24rem;max-width:calc(100vw - 8rem);height:8.5rem}.canon-ScrollAreaViewport{overscroll-behavior:contain;height:100%}.canon-ScrollAreaContent{padding-block:.75rem;flex-direction:column;gap:1rem;padding-left:1rem;padding-right:1.5rem;display:flex}.canon-ScrollAreaScrollbar{background-color:var(--canon-scrollbar);opacity:0;border-radius:.375rem;justify-content:center;width:.25rem;margin:.5rem;transition:opacity .15s .3s;display:flex;&[data-hovering],&[data-scrolling]{opacity:1;transition-duration:75ms;transition-delay:0s}&:before{content:"";width:1.25rem;height:100%;position:absolute}}.canon-ScrollAreaThumb{border-radius:inherit;background-color:var(--canon-scrollbar-thumb);width:100%}.canon-Select{font-family:var(--canon-font-regular);flex-direction:column;width:100%;display:flex}.canon-SelectLabel{font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-primary);margin-bottom:var(--canon-space-1_5);cursor:pointer}.canon-SelectLabel[data-disabled]{cursor:default}.canon-SelectDescription{font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-secondary);padding-top:var(--canon-space-1_5);margin:0}.canon-SelectError{font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-danger);padding-top:var(--canon-space-1_5);margin:0}.canon-SelectTrigger{box-sizing:border-box;border-radius:var(--canon-radius-3);border:1px solid var(--canon-border);padding:0 var(--canon-space-4);background-color:var(--canon-bg-surface-1);font-size:var(--canon-font-size-3);font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-primary);cursor:pointer;justify-content:space-between;align-items:center;gap:var(--canon-space-2);width:100%;transition:border-color .2s ease-in-out,outline-color .2s ease-in-out;display:flex}.canon-SelectTrigger::placeholder{color:var(--canon-fg-secondary)}.canon-SelectTrigger:hover{border-color:var(--canon-border-hover)}.canon-SelectTrigger:focus-visible{border-color:var(--canon-border-pressed);outline:0}.canon-SelectTrigger[data-invalid]{border-color:var(--canon-fg-danger)}.canon-SelectTrigger[data-invalid]:hover,.canon-SelectTrigger[data-invalid]:focus-visible{border-width:2px}.canon-SelectTrigger[data-disabled]{cursor:not-allowed;border-color:var(--canon-border-disabled);color:var(--canon-fg-disabled)}.canon-SelectTrigger[data-size=small]{height:2rem}.canon-SelectTrigger[data-size=medium]{height:3rem}.canon-SelectIcon{margin-left:var(--canon-space-5);transition:transform .2s}.canon-SelectTrigger[data-popup-open] .canon-SelectIcon{transform:rotate(180deg)}.canon-SelectPopup{box-sizing:border-box;max-height:var(--available-height);background-color:var(--canon-bg-surface-1);border:1px solid var(--canon-border);border-radius:var(--canon-radius-3);padding-block:var(--canon-space-1);z-index:1;transform-origin:var(--transform-origin);outline:0;transition:transform .15s,opacity .15s;overflow-y:auto;box-shadow:0 4px 12px #0003}.canon-SelectPopup[data-starting-style],.canon-SelectPopup[data-ending-style]{opacity:0;transform:scale(.9)}.canon-SelectItem{width:var(--anchor-width);padding-block:var(--canon-space-2);padding-inline:var(--canon-space-4);color:var(--canon-fg-primary);border-radius:var(--canon-radius-3);cursor:pointer;user-select:none;font-size:var(--canon-font-size-3);align-items:center;gap:var(--canon-space-2);outline:none;grid-template-columns:1rem 1fr;grid-template-areas:"icon text";display:grid;position:relative}.canon-SelectItem[data-highlighted]{z-index:0;color:var(--canon-fg-primary);position:relative}.canon-SelectItem[data-highlighted]:before{content:"";z-index:-1;background-color:var(--canon-bg-tint-hover);border-radius:.25rem;position:absolute;inset-block:0;inset-inline:.25rem}.canon-SelectItem[data-disabled]{cursor:not-allowed;color:var(--canon-fg-disabled)}.canon-SelectItemIndicator{grid-area:icon;justify-content:center;align-items:center;display:flex}.canon-SelectItemText{flex:1;grid-area:text}.canon-SelectRequired{color:var(--canon-fg-secondary);font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);margin-left:var(--canon-space-1)}.canon-SelectIcon{justify-content:center;align-items:center;display:flex}.canon-SelectValue{text-overflow:ellipsis;white-space:nowrap;width:100%;overflow:hidden}.canon-Switch{align-items:center;gap:var(--canon-space-3);font-size:var(--canon-font-size-3);color:var(--canon-fg-primary);cursor:pointer;display:flex;position:relative;&[data-pressed] .canon-SwitchIndicator{&:before{background:var(--canon-fg-solid)}}&[data-selected]{& .canon-SwitchIndicator{background:var(--canon-bg-solid);&:before{background:var(--canon-fg-solid);transform:translate(100%)}}&[data-pressed]{& .indicator{background:var(--canon-gray-3)}}}&[data-focus-visible] .canon-SwitchIndicator{outline-offset:2px;outline:2px solid}}.canon-SwitchIndicator{background:var(--canon-gray-3);border:2px;border-radius:1.143rem;width:2rem;height:1.143rem;transition:all .2s;&:before{content:"";background:var(--canon-fg-solid);border-radius:16px;width:.857rem;height:.857rem;margin:.143rem;transition:all .2s;display:block}} \ No newline at end of file +@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}}.cu-p{padding:var(--p)}.cu-p-0\.5{padding:var(--canon-space-0_5)}.cu-p-1{padding:var(--canon-space-1)}.cu-p-1\.5{padding:var(--canon-space-1_5)}.cu-p-2{padding:var(--canon-space-2)}.cu-p-3{padding:var(--canon-space-3)}.cu-p-4{padding:var(--canon-space-4)}.cu-p-5{padding:var(--canon-space-5)}.cu-p-6{padding:var(--canon-space-6)}.cu-p-7{padding:var(--canon-space-7)}.cu-p-8{padding:var(--canon-space-8)}.cu-p-9{padding:var(--canon-space-9)}.cu-p-10{padding:var(--canon-space-10)}.cu-p-11{padding:var(--canon-space-11)}.cu-p-12{padding:var(--canon-space-12)}.cu-p-13{padding:var(--canon-space-13)}.cu-p-14{padding:var(--canon-space-14)}@media (width>=640px){.xs\:cu-p{padding:var(--p-xs)}.xs\:cu-p-0\.5{padding:var(--canon-space-0_5)}.xs\:cu-p-1{padding:var(--canon-space-1)}.xs\:cu-p-1\.5{padding:var(--canon-space-1_5)}.xs\:cu-p-2{padding:var(--canon-space-2)}.xs\:cu-p-3{padding:var(--canon-space-3)}.xs\:cu-p-4{padding:var(--canon-space-4)}.xs\:cu-p-5{padding:var(--canon-space-5)}.xs\:cu-p-6{padding:var(--canon-space-6)}.xs\:cu-p-7{padding:var(--canon-space-7)}.xs\:cu-p-8{padding:var(--canon-space-8)}.xs\:cu-p-9{padding:var(--canon-space-9)}.xs\:cu-p-10{padding:var(--canon-space-10)}.xs\:cu-p-11{padding:var(--canon-space-11)}.xs\:cu-p-12{padding:var(--canon-space-12)}.xs\:cu-p-13{padding:var(--canon-space-13)}.xs\:cu-p-14{padding:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-p{padding:var(--p-sm)}.sm\:cu-p-0\.5{padding:var(--canon-space-0_5)}.sm\:cu-p-1{padding:var(--canon-space-1)}.sm\:cu-p-1\.5{padding:var(--canon-space-1_5)}.sm\:cu-p-2{padding:var(--canon-space-2)}.sm\:cu-p-3{padding:var(--canon-space-3)}.sm\:cu-p-4{padding:var(--canon-space-4)}.sm\:cu-p-5{padding:var(--canon-space-5)}.sm\:cu-p-6{padding:var(--canon-space-6)}.sm\:cu-p-7{padding:var(--canon-space-7)}.sm\:cu-p-8{padding:var(--canon-space-8)}.sm\:cu-p-9{padding:var(--canon-space-9)}.sm\:cu-p-10{padding:var(--canon-space-10)}.sm\:cu-p-11{padding:var(--canon-space-11)}.sm\:cu-p-12{padding:var(--canon-space-12)}.sm\:cu-p-13{padding:var(--canon-space-13)}.sm\:cu-p-14{padding:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-p{padding:var(--p-md)}.md\:cu-p-0\.5{padding:var(--canon-space-0_5)}.md\:cu-p-1{padding:var(--canon-space-1)}.md\:cu-p-1\.5{padding:var(--canon-space-1_5)}.md\:cu-p-2{padding:var(--canon-space-2)}.md\:cu-p-3{padding:var(--canon-space-3)}.md\:cu-p-4{padding:var(--canon-space-4)}.md\:cu-p-5{padding:var(--canon-space-5)}.md\:cu-p-6{padding:var(--canon-space-6)}.md\:cu-p-7{padding:var(--canon-space-7)}.md\:cu-p-8{padding:var(--canon-space-8)}.md\:cu-p-9{padding:var(--canon-space-9)}.md\:cu-p-10{padding:var(--canon-space-10)}.md\:cu-p-11{padding:var(--canon-space-11)}.md\:cu-p-12{padding:var(--canon-space-12)}.md\:cu-p-13{padding:var(--canon-space-13)}.md\:cu-p-14{padding:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-p{padding:var(--p-lg)}.lg\:cu-p-0\.5{padding:var(--canon-space-0_5)}.lg\:cu-p-1{padding:var(--canon-space-1)}.lg\:cu-p-1\.5{padding:var(--canon-space-1_5)}.lg\:cu-p-2{padding:var(--canon-space-2)}.lg\:cu-p-3{padding:var(--canon-space-3)}.lg\:cu-p-4{padding:var(--canon-space-4)}.lg\:cu-p-5{padding:var(--canon-space-5)}.lg\:cu-p-6{padding:var(--canon-space-6)}.lg\:cu-p-7{padding:var(--canon-space-7)}.lg\:cu-p-8{padding:var(--canon-space-8)}.lg\:cu-p-9{padding:var(--canon-space-9)}.lg\:cu-p-10{padding:var(--canon-space-10)}.lg\:cu-p-11{padding:var(--canon-space-11)}.lg\:cu-p-12{padding:var(--canon-space-12)}.lg\:cu-p-13{padding:var(--canon-space-13)}.lg\:cu-p-14{padding:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-p{padding:var(--p-xl)}.xl\:cu-p-0\.5{padding:var(--canon-space-0_5)}.xl\:cu-p-1{padding:var(--canon-space-1)}.xl\:cu-p-1\.5{padding:var(--canon-space-1_5)}.xl\:cu-p-2{padding:var(--canon-space-2)}.xl\:cu-p-3{padding:var(--canon-space-3)}.xl\:cu-p-4{padding:var(--canon-space-4)}.xl\:cu-p-5{padding:var(--canon-space-5)}.xl\:cu-p-6{padding:var(--canon-space-6)}.xl\:cu-p-7{padding:var(--canon-space-7)}.xl\:cu-p-8{padding:var(--canon-space-8)}.xl\:cu-p-9{padding:var(--canon-space-9)}.xl\:cu-p-10{padding:var(--canon-space-10)}.xl\:cu-p-11{padding:var(--canon-space-11)}.xl\:cu-p-12{padding:var(--canon-space-12)}.xl\:cu-p-13{padding:var(--canon-space-13)}.xl\:cu-p-14{padding:var(--canon-space-14)}}.cu-pl{padding-left:var(--pl)}.cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.cu-pl-1{padding-left:var(--canon-space-1)}.cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.cu-pl-2{padding-left:var(--canon-space-2)}.cu-pl-3{padding-left:var(--canon-space-3)}.cu-pl-4{padding-left:var(--canon-space-4)}.cu-pl-5{padding-left:var(--canon-space-5)}.cu-pl-6{padding-left:var(--canon-space-6)}.cu-pl-7{padding-left:var(--canon-space-7)}.cu-pl-8{padding-left:var(--canon-space-8)}.cu-pl-9{padding-left:var(--canon-space-9)}.cu-pl-10{padding-left:var(--canon-space-10)}.cu-pl-11{padding-left:var(--canon-space-11)}.cu-pl-12{padding-left:var(--canon-space-12)}.cu-pl-13{padding-left:var(--canon-space-13)}.cu-pl-14{padding-left:var(--canon-space-14)}@media (width>=640px){.xs\:cu-pl{padding-left:var(--pl-xs)}.xs\:cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.xs\:cu-pl-1{padding-left:var(--canon-space-1)}.xs\:cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.xs\:cu-pl-2{padding-left:var(--canon-space-2)}.xs\:cu-pl-3{padding-left:var(--canon-space-3)}.xs\:cu-pl-4{padding-left:var(--canon-space-4)}.xs\:cu-pl-5{padding-left:var(--canon-space-5)}.xs\:cu-pl-6{padding-left:var(--canon-space-6)}.xs\:cu-pl-7{padding-left:var(--canon-space-7)}.xs\:cu-pl-8{padding-left:var(--canon-space-8)}.xs\:cu-pl-9{padding-left:var(--canon-space-9)}.xs\:cu-pl-10{padding-left:var(--canon-space-10)}.xs\:cu-pl-11{padding-left:var(--canon-space-11)}.xs\:cu-pl-12{padding-left:var(--canon-space-12)}.xs\:cu-pl-13{padding-left:var(--canon-space-13)}.xs\:cu-pl-14{padding-left:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-pl{padding-left:var(--pl-sm)}.sm\:cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.sm\:cu-pl-1{padding-left:var(--canon-space-1)}.sm\:cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.sm\:cu-pl-2{padding-left:var(--canon-space-2)}.sm\:cu-pl-3{padding-left:var(--canon-space-3)}.sm\:cu-pl-4{padding-left:var(--canon-space-4)}.sm\:cu-pl-5{padding-left:var(--canon-space-5)}.sm\:cu-pl-6{padding-left:var(--canon-space-6)}.sm\:cu-pl-7{padding-left:var(--canon-space-7)}.sm\:cu-pl-8{padding-left:var(--canon-space-8)}.sm\:cu-pl-9{padding-left:var(--canon-space-9)}.sm\:cu-pl-10{padding-left:var(--canon-space-10)}.sm\:cu-pl-11{padding-left:var(--canon-space-11)}.sm\:cu-pl-12{padding-left:var(--canon-space-12)}.sm\:cu-pl-13{padding-left:var(--canon-space-13)}.sm\:cu-pl-14{padding-left:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-pl{padding-left:var(--pl-md)}.md\:cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.md\:cu-pl-1{padding-left:var(--canon-space-1)}.md\:cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.md\:cu-pl-2{padding-left:var(--canon-space-2)}.md\:cu-pl-3{padding-left:var(--canon-space-3)}.md\:cu-pl-4{padding-left:var(--canon-space-4)}.md\:cu-pl-5{padding-left:var(--canon-space-5)}.md\:cu-pl-6{padding-left:var(--canon-space-6)}.md\:cu-pl-7{padding-left:var(--canon-space-7)}.md\:cu-pl-8{padding-left:var(--canon-space-8)}.md\:cu-pl-9{padding-left:var(--canon-space-9)}.md\:cu-pl-10{padding-left:var(--canon-space-10)}.md\:cu-pl-11{padding-left:var(--canon-space-11)}.md\:cu-pl-12{padding-left:var(--canon-space-12)}.md\:cu-pl-13{padding-left:var(--canon-space-13)}.md\:cu-pl-14{padding-left:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-pl{padding-left:var(--pl-lg)}.lg\:cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.lg\:cu-pl-1{padding-left:var(--canon-space-1)}.lg\:cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.lg\:cu-pl-2{padding-left:var(--canon-space-2)}.lg\:cu-pl-3{padding-left:var(--canon-space-3)}.lg\:cu-pl-4{padding-left:var(--canon-space-4)}.lg\:cu-pl-5{padding-left:var(--canon-space-5)}.lg\:cu-pl-6{padding-left:var(--canon-space-6)}.lg\:cu-pl-7{padding-left:var(--canon-space-7)}.lg\:cu-pl-8{padding-left:var(--canon-space-8)}.lg\:cu-pl-9{padding-left:var(--canon-space-9)}.lg\:cu-pl-10{padding-left:var(--canon-space-10)}.lg\:cu-pl-11{padding-left:var(--canon-space-11)}.lg\:cu-pl-12{padding-left:var(--canon-space-12)}.lg\:cu-pl-13{padding-left:var(--canon-space-13)}.lg\:cu-pl-14{padding-left:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-pl{padding-left:var(--pl-xl)}.xl\:cu-pl-0\.5{padding-left:var(--canon-space-0_5)}.xl\:cu-pl-1{padding-left:var(--canon-space-1)}.xl\:cu-pl-1\.5{padding-left:var(--canon-space-1_5)}.xl\:cu-pl-2{padding-left:var(--canon-space-2)}.xl\:cu-pl-3{padding-left:var(--canon-space-3)}.xl\:cu-pl-4{padding-left:var(--canon-space-4)}.xl\:cu-pl-5{padding-left:var(--canon-space-5)}.xl\:cu-pl-6{padding-left:var(--canon-space-6)}.xl\:cu-pl-7{padding-left:var(--canon-space-7)}.xl\:cu-pl-8{padding-left:var(--canon-space-8)}.xl\:cu-pl-9{padding-left:var(--canon-space-9)}.xl\:cu-pl-10{padding-left:var(--canon-space-10)}.xl\:cu-pl-11{padding-left:var(--canon-space-11)}.xl\:cu-pl-12{padding-left:var(--canon-space-12)}.xl\:cu-pl-13{padding-left:var(--canon-space-13)}.xl\:cu-pl-14{padding-left:var(--canon-space-14)}}.cu-pr{padding-right:var(--pr)}.cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.cu-pr-1{padding-right:var(--canon-space-1)}.cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.cu-pr-2{padding-right:var(--canon-space-2)}.cu-pr-3{padding-right:var(--canon-space-3)}.cu-pr-4{padding-right:var(--canon-space-4)}.cu-pr-5{padding-right:var(--canon-space-5)}.cu-pr-6{padding-right:var(--canon-space-6)}.cu-pr-7{padding-right:var(--canon-space-7)}.cu-pr-8{padding-right:var(--canon-space-8)}.cu-pr-9{padding-right:var(--canon-space-9)}.cu-pr-10{padding-right:var(--canon-space-10)}.cu-pr-11{padding-right:var(--canon-space-11)}.cu-pr-12{padding-right:var(--canon-space-12)}.cu-pr-13{padding-right:var(--canon-space-13)}.cu-pr-14{padding-right:var(--canon-space-14)}@media (width>=640px){.xs\:cu-pr{padding-right:var(--pr-xs)}.xs\:cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.xs\:cu-pr-1{padding-right:var(--canon-space-1)}.xs\:cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.xs\:cu-pr-2{padding-right:var(--canon-space-2)}.xs\:cu-pr-3{padding-right:var(--canon-space-3)}.xs\:cu-pr-4{padding-right:var(--canon-space-4)}.xs\:cu-pr-5{padding-right:var(--canon-space-5)}.xs\:cu-pr-6{padding-right:var(--canon-space-6)}.xs\:cu-pr-7{padding-right:var(--canon-space-7)}.xs\:cu-pr-8{padding-right:var(--canon-space-8)}.xs\:cu-pr-9{padding-right:var(--canon-space-9)}.xs\:cu-pr-10{padding-right:var(--canon-space-10)}.xs\:cu-pr-11{padding-right:var(--canon-space-11)}.xs\:cu-pr-12{padding-right:var(--canon-space-12)}.xs\:cu-pr-13{padding-right:var(--canon-space-13)}.xs\:cu-pr-14{padding-right:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-pr{padding-right:var(--pr-sm)}.sm\:cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.sm\:cu-pr-1{padding-right:var(--canon-space-1)}.sm\:cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.sm\:cu-pr-2{padding-right:var(--canon-space-2)}.sm\:cu-pr-3{padding-right:var(--canon-space-3)}.sm\:cu-pr-4{padding-right:var(--canon-space-4)}.sm\:cu-pr-5{padding-right:var(--canon-space-5)}.sm\:cu-pr-6{padding-right:var(--canon-space-6)}.sm\:cu-pr-7{padding-right:var(--canon-space-7)}.sm\:cu-pr-8{padding-right:var(--canon-space-8)}.sm\:cu-pr-9{padding-right:var(--canon-space-9)}.sm\:cu-pr-10{padding-right:var(--canon-space-10)}.sm\:cu-pr-11{padding-right:var(--canon-space-11)}.sm\:cu-pr-12{padding-right:var(--canon-space-12)}.sm\:cu-pr-13{padding-right:var(--canon-space-13)}.sm\:cu-pr-14{padding-right:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-pr{padding-right:var(--pr-md)}.md\:cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.md\:cu-pr-1{padding-right:var(--canon-space-1)}.md\:cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.md\:cu-pr-2{padding-right:var(--canon-space-2)}.md\:cu-pr-3{padding-right:var(--canon-space-3)}.md\:cu-pr-4{padding-right:var(--canon-space-4)}.md\:cu-pr-5{padding-right:var(--canon-space-5)}.md\:cu-pr-6{padding-right:var(--canon-space-6)}.md\:cu-pr-7{padding-right:var(--canon-space-7)}.md\:cu-pr-8{padding-right:var(--canon-space-8)}.md\:cu-pr-9{padding-right:var(--canon-space-9)}.md\:cu-pr-10{padding-right:var(--canon-space-10)}.md\:cu-pr-11{padding-right:var(--canon-space-11)}.md\:cu-pr-12{padding-right:var(--canon-space-12)}.md\:cu-pr-13{padding-right:var(--canon-space-13)}.md\:cu-pr-14{padding-right:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-pr{padding-right:var(--pr-lg)}.lg\:cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.lg\:cu-pr-1{padding-right:var(--canon-space-1)}.lg\:cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.lg\:cu-pr-2{padding-right:var(--canon-space-2)}.lg\:cu-pr-3{padding-right:var(--canon-space-3)}.lg\:cu-pr-4{padding-right:var(--canon-space-4)}.lg\:cu-pr-5{padding-right:var(--canon-space-5)}.lg\:cu-pr-6{padding-right:var(--canon-space-6)}.lg\:cu-pr-7{padding-right:var(--canon-space-7)}.lg\:cu-pr-8{padding-right:var(--canon-space-8)}.lg\:cu-pr-9{padding-right:var(--canon-space-9)}.lg\:cu-pr-10{padding-right:var(--canon-space-10)}.lg\:cu-pr-11{padding-right:var(--canon-space-11)}.lg\:cu-pr-12{padding-right:var(--canon-space-12)}.lg\:cu-pr-13{padding-right:var(--canon-space-13)}.lg\:cu-pr-14{padding-right:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-pr{padding-right:var(--pr-xl)}.xl\:cu-pr-0\.5{padding-right:var(--canon-space-0_5)}.xl\:cu-pr-1{padding-right:var(--canon-space-1)}.xl\:cu-pr-1\.5{padding-right:var(--canon-space-1_5)}.xl\:cu-pr-2{padding-right:var(--canon-space-2)}.xl\:cu-pr-3{padding-right:var(--canon-space-3)}.xl\:cu-pr-4{padding-right:var(--canon-space-4)}.xl\:cu-pr-5{padding-right:var(--canon-space-5)}.xl\:cu-pr-6{padding-right:var(--canon-space-6)}.xl\:cu-pr-7{padding-right:var(--canon-space-7)}.xl\:cu-pr-8{padding-right:var(--canon-space-8)}.xl\:cu-pr-9{padding-right:var(--canon-space-9)}.xl\:cu-pr-10{padding-right:var(--canon-space-10)}.xl\:cu-pr-11{padding-right:var(--canon-space-11)}.xl\:cu-pr-12{padding-right:var(--canon-space-12)}.xl\:cu-pr-13{padding-right:var(--canon-space-13)}.xl\:cu-pr-14{padding-right:var(--canon-space-14)}}.cu-pt{padding-top:var(--pt)}.cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.cu-pt-1{padding-top:var(--canon-space-1)}.cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.cu-pt-2{padding-top:var(--canon-space-2)}.cu-pt-3{padding-top:var(--canon-space-3)}.cu-pt-4{padding-top:var(--canon-space-4)}.cu-pt-5{padding-top:var(--canon-space-5)}.cu-pt-6{padding-top:var(--canon-space-6)}.cu-pt-7{padding-top:var(--canon-space-7)}.cu-pt-8{padding-top:var(--canon-space-8)}.cu-pt-9{padding-top:var(--canon-space-9)}.cu-pt-10{padding-top:var(--canon-space-10)}.cu-pt-11{padding-top:var(--canon-space-11)}.cu-pt-12{padding-top:var(--canon-space-12)}.cu-pt-13{padding-top:var(--canon-space-13)}.cu-pt-14{padding-top:var(--canon-space-14)}@media (width>=640px){.xs\:cu-pt{padding-top:var(--pt-xs)}.xs\:cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.xs\:cu-pt-1{padding-top:var(--canon-space-1)}.xs\:cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.xs\:cu-pt-2{padding-top:var(--canon-space-2)}.xs\:cu-pt-3{padding-top:var(--canon-space-3)}.xs\:cu-pt-4{padding-top:var(--canon-space-4)}.xs\:cu-pt-5{padding-top:var(--canon-space-5)}.xs\:cu-pt-6{padding-top:var(--canon-space-6)}.xs\:cu-pt-7{padding-top:var(--canon-space-7)}.xs\:cu-pt-8{padding-top:var(--canon-space-8)}.xs\:cu-pt-9{padding-top:var(--canon-space-9)}.xs\:cu-pt-10{padding-top:var(--canon-space-10)}.xs\:cu-pt-11{padding-top:var(--canon-space-11)}.xs\:cu-pt-12{padding-top:var(--canon-space-12)}.xs\:cu-pt-13{padding-top:var(--canon-space-13)}.xs\:cu-pt-14{padding-top:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-pt{padding-top:var(--pt-sm)}.sm\:cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.sm\:cu-pt-1{padding-top:var(--canon-space-1)}.sm\:cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.sm\:cu-pt-2{padding-top:var(--canon-space-2)}.sm\:cu-pt-3{padding-top:var(--canon-space-3)}.sm\:cu-pt-4{padding-top:var(--canon-space-4)}.sm\:cu-pt-5{padding-top:var(--canon-space-5)}.sm\:cu-pt-6{padding-top:var(--canon-space-6)}.sm\:cu-pt-7{padding-top:var(--canon-space-7)}.sm\:cu-pt-8{padding-top:var(--canon-space-8)}.sm\:cu-pt-9{padding-top:var(--canon-space-9)}.sm\:cu-pt-10{padding-top:var(--canon-space-10)}.sm\:cu-pt-11{padding-top:var(--canon-space-11)}.sm\:cu-pt-12{padding-top:var(--canon-space-12)}.sm\:cu-pt-13{padding-top:var(--canon-space-13)}.sm\:cu-pt-14{padding-top:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-pt{padding-top:var(--pt-md)}.md\:cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.md\:cu-pt-1{padding-top:var(--canon-space-1)}.md\:cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.md\:cu-pt-2{padding-top:var(--canon-space-2)}.md\:cu-pt-3{padding-top:var(--canon-space-3)}.md\:cu-pt-4{padding-top:var(--canon-space-4)}.md\:cu-pt-5{padding-top:var(--canon-space-5)}.md\:cu-pt-6{padding-top:var(--canon-space-6)}.md\:cu-pt-7{padding-top:var(--canon-space-7)}.md\:cu-pt-8{padding-top:var(--canon-space-8)}.md\:cu-pt-9{padding-top:var(--canon-space-9)}.md\:cu-pt-10{padding-top:var(--canon-space-10)}.md\:cu-pt-11{padding-top:var(--canon-space-11)}.md\:cu-pt-12{padding-top:var(--canon-space-12)}.md\:cu-pt-13{padding-top:var(--canon-space-13)}.md\:cu-pt-14{padding-top:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-pt{padding-top:var(--pt-lg)}.lg\:cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.lg\:cu-pt-1{padding-top:var(--canon-space-1)}.lg\:cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.lg\:cu-pt-2{padding-top:var(--canon-space-2)}.lg\:cu-pt-3{padding-top:var(--canon-space-3)}.lg\:cu-pt-4{padding-top:var(--canon-space-4)}.lg\:cu-pt-5{padding-top:var(--canon-space-5)}.lg\:cu-pt-6{padding-top:var(--canon-space-6)}.lg\:cu-pt-7{padding-top:var(--canon-space-7)}.lg\:cu-pt-8{padding-top:var(--canon-space-8)}.lg\:cu-pt-9{padding-top:var(--canon-space-9)}.lg\:cu-pt-10{padding-top:var(--canon-space-10)}.lg\:cu-pt-11{padding-top:var(--canon-space-11)}.lg\:cu-pt-12{padding-top:var(--canon-space-12)}.lg\:cu-pt-13{padding-top:var(--canon-space-13)}.lg\:cu-pt-14{padding-top:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-pt{padding-top:var(--pt-xl)}.xl\:cu-pt-0\.5{padding-top:var(--canon-space-0_5)}.xl\:cu-pt-1{padding-top:var(--canon-space-1)}.xl\:cu-pt-1\.5{padding-top:var(--canon-space-1_5)}.xl\:cu-pt-2{padding-top:var(--canon-space-2)}.xl\:cu-pt-3{padding-top:var(--canon-space-3)}.xl\:cu-pt-4{padding-top:var(--canon-space-4)}.xl\:cu-pt-5{padding-top:var(--canon-space-5)}.xl\:cu-pt-6{padding-top:var(--canon-space-6)}.xl\:cu-pt-7{padding-top:var(--canon-space-7)}.xl\:cu-pt-8{padding-top:var(--canon-space-8)}.xl\:cu-pt-9{padding-top:var(--canon-space-9)}.xl\:cu-pt-10{padding-top:var(--canon-space-10)}.xl\:cu-pt-11{padding-top:var(--canon-space-11)}.xl\:cu-pt-12{padding-top:var(--canon-space-12)}.xl\:cu-pt-13{padding-top:var(--canon-space-13)}.xl\:cu-pt-14{padding-top:var(--canon-space-14)}}.cu-pb{padding-bottom:var(--pb)}.cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.cu-pb-1{padding-bottom:var(--canon-space-1)}.cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.cu-pb-2{padding-bottom:var(--canon-space-2)}.cu-pb-3{padding-bottom:var(--canon-space-3)}.cu-pb-4{padding-bottom:var(--canon-space-4)}.cu-pb-5{padding-bottom:var(--canon-space-5)}.cu-pb-6{padding-bottom:var(--canon-space-6)}.cu-pb-7{padding-bottom:var(--canon-space-7)}.cu-pb-8{padding-bottom:var(--canon-space-8)}.cu-pb-9{padding-bottom:var(--canon-space-9)}.cu-pb-10{padding-bottom:var(--canon-space-10)}.cu-pb-11{padding-bottom:var(--canon-space-11)}.cu-pb-12{padding-bottom:var(--canon-space-12)}.cu-pb-13{padding-bottom:var(--canon-space-13)}.cu-pb-14{padding-bottom:var(--canon-space-14)}@media (width>=640px){.xs\:cu-pb{padding-bottom:var(--pb-xs)}.xs\:cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.xs\:cu-pb-1{padding-bottom:var(--canon-space-1)}.xs\:cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.xs\:cu-pb-2{padding-bottom:var(--canon-space-2)}.xs\:cu-pb-3{padding-bottom:var(--canon-space-3)}.xs\:cu-pb-4{padding-bottom:var(--canon-space-4)}.xs\:cu-pb-5{padding-bottom:var(--canon-space-5)}.xs\:cu-pb-6{padding-bottom:var(--canon-space-6)}.xs\:cu-pb-7{padding-bottom:var(--canon-space-7)}.xs\:cu-pb-8{padding-bottom:var(--canon-space-8)}.xs\:cu-pb-9{padding-bottom:var(--canon-space-9)}.xs\:cu-pb-10{padding-bottom:var(--canon-space-10)}.xs\:cu-pb-11{padding-bottom:var(--canon-space-11)}.xs\:cu-pb-12{padding-bottom:var(--canon-space-12)}.xs\:cu-pb-13{padding-bottom:var(--canon-space-13)}.xs\:cu-pb-14{padding-bottom:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-pb{padding-bottom:var(--pb-sm)}.sm\:cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.sm\:cu-pb-1{padding-bottom:var(--canon-space-1)}.sm\:cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.sm\:cu-pb-2{padding-bottom:var(--canon-space-2)}.sm\:cu-pb-3{padding-bottom:var(--canon-space-3)}.sm\:cu-pb-4{padding-bottom:var(--canon-space-4)}.sm\:cu-pb-5{padding-bottom:var(--canon-space-5)}.sm\:cu-pb-6{padding-bottom:var(--canon-space-6)}.sm\:cu-pb-7{padding-bottom:var(--canon-space-7)}.sm\:cu-pb-8{padding-bottom:var(--canon-space-8)}.sm\:cu-pb-9{padding-bottom:var(--canon-space-9)}.sm\:cu-pb-10{padding-bottom:var(--canon-space-10)}.sm\:cu-pb-11{padding-bottom:var(--canon-space-11)}.sm\:cu-pb-12{padding-bottom:var(--canon-space-12)}.sm\:cu-pb-13{padding-bottom:var(--canon-space-13)}.sm\:cu-pb-14{padding-bottom:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-pb{padding-bottom:var(--pb-md)}.md\:cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.md\:cu-pb-1{padding-bottom:var(--canon-space-1)}.md\:cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.md\:cu-pb-2{padding-bottom:var(--canon-space-2)}.md\:cu-pb-3{padding-bottom:var(--canon-space-3)}.md\:cu-pb-4{padding-bottom:var(--canon-space-4)}.md\:cu-pb-5{padding-bottom:var(--canon-space-5)}.md\:cu-pb-6{padding-bottom:var(--canon-space-6)}.md\:cu-pb-7{padding-bottom:var(--canon-space-7)}.md\:cu-pb-8{padding-bottom:var(--canon-space-8)}.md\:cu-pb-9{padding-bottom:var(--canon-space-9)}.md\:cu-pb-10{padding-bottom:var(--canon-space-10)}.md\:cu-pb-11{padding-bottom:var(--canon-space-11)}.md\:cu-pb-12{padding-bottom:var(--canon-space-12)}.md\:cu-pb-13{padding-bottom:var(--canon-space-13)}.md\:cu-pb-14{padding-bottom:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-pb{padding-bottom:var(--pb-lg)}.lg\:cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.lg\:cu-pb-1{padding-bottom:var(--canon-space-1)}.lg\:cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.lg\:cu-pb-2{padding-bottom:var(--canon-space-2)}.lg\:cu-pb-3{padding-bottom:var(--canon-space-3)}.lg\:cu-pb-4{padding-bottom:var(--canon-space-4)}.lg\:cu-pb-5{padding-bottom:var(--canon-space-5)}.lg\:cu-pb-6{padding-bottom:var(--canon-space-6)}.lg\:cu-pb-7{padding-bottom:var(--canon-space-7)}.lg\:cu-pb-8{padding-bottom:var(--canon-space-8)}.lg\:cu-pb-9{padding-bottom:var(--canon-space-9)}.lg\:cu-pb-10{padding-bottom:var(--canon-space-10)}.lg\:cu-pb-11{padding-bottom:var(--canon-space-11)}.lg\:cu-pb-12{padding-bottom:var(--canon-space-12)}.lg\:cu-pb-13{padding-bottom:var(--canon-space-13)}.lg\:cu-pb-14{padding-bottom:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-pb{padding-bottom:var(--pb-xl)}.xl\:cu-pb-0\.5{padding-bottom:var(--canon-space-0_5)}.xl\:cu-pb-1{padding-bottom:var(--canon-space-1)}.xl\:cu-pb-1\.5{padding-bottom:var(--canon-space-1_5)}.xl\:cu-pb-2{padding-bottom:var(--canon-space-2)}.xl\:cu-pb-3{padding-bottom:var(--canon-space-3)}.xl\:cu-pb-4{padding-bottom:var(--canon-space-4)}.xl\:cu-pb-5{padding-bottom:var(--canon-space-5)}.xl\:cu-pb-6{padding-bottom:var(--canon-space-6)}.xl\:cu-pb-7{padding-bottom:var(--canon-space-7)}.xl\:cu-pb-8{padding-bottom:var(--canon-space-8)}.xl\:cu-pb-9{padding-bottom:var(--canon-space-9)}.xl\:cu-pb-10{padding-bottom:var(--canon-space-10)}.xl\:cu-pb-11{padding-bottom:var(--canon-space-11)}.xl\:cu-pb-12{padding-bottom:var(--canon-space-12)}.xl\:cu-pb-13{padding-bottom:var(--canon-space-13)}.xl\:cu-pb-14{padding-bottom:var(--canon-space-14)}}.cu-py{padding-top:var(--py);padding-bottom:var(--py)}.cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}@media (width>=640px){.xs\:cu-py{padding-top:var(--py-xs);padding-bottom:var(--py-xs)}.xs\:cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.xs\:cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.xs\:cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.xs\:cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.xs\:cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.xs\:cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.xs\:cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.xs\:cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.xs\:cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.xs\:cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.xs\:cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.xs\:cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.xs\:cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.xs\:cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.xs\:cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.xs\:cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-py{padding-top:var(--py-sm);padding-bottom:var(--py-sm)}.sm\:cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.sm\:cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.sm\:cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.sm\:cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.sm\:cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.sm\:cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.sm\:cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.sm\:cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.sm\:cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.sm\:cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.sm\:cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.sm\:cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.sm\:cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.sm\:cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.sm\:cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.sm\:cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-py{padding-top:var(--py-md);padding-bottom:var(--py-md)}.md\:cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.md\:cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.md\:cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.md\:cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.md\:cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.md\:cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.md\:cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.md\:cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.md\:cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.md\:cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.md\:cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.md\:cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.md\:cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.md\:cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.md\:cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.md\:cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-py{padding-top:var(--py-lg);padding-bottom:var(--py-lg)}.lg\:cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.lg\:cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.lg\:cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.lg\:cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.lg\:cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.lg\:cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.lg\:cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.lg\:cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.lg\:cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.lg\:cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.lg\:cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.lg\:cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.lg\:cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.lg\:cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.lg\:cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.lg\:cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-py{padding-top:var(--py-xl);padding-bottom:var(--py-xl)}.xl\:cu-py-0\.5{padding-top:var(--canon-space-0_5);padding-bottom:var(--canon-space-0_5)}.xl\:cu-py-1{padding-top:var(--canon-space-1);padding-bottom:var(--canon-space-1)}.xl\:cu-py-1\.5{padding-top:var(--canon-space-1_5);padding-bottom:var(--canon-space-1_5)}.xl\:cu-py-2{padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-2)}.xl\:cu-py-3{padding-top:var(--canon-space-3);padding-bottom:var(--canon-space-3)}.xl\:cu-py-4{padding-top:var(--canon-space-4);padding-bottom:var(--canon-space-4)}.xl\:cu-py-5{padding-top:var(--canon-space-5);padding-bottom:var(--canon-space-5)}.xl\:cu-py-6{padding-top:var(--canon-space-6);padding-bottom:var(--canon-space-6)}.xl\:cu-py-7{padding-top:var(--canon-space-7);padding-bottom:var(--canon-space-7)}.xl\:cu-py-8{padding-top:var(--canon-space-8);padding-bottom:var(--canon-space-8)}.xl\:cu-py-9{padding-top:var(--canon-space-9);padding-bottom:var(--canon-space-9)}.xl\:cu-py-10{padding-top:var(--canon-space-10);padding-bottom:var(--canon-space-10)}.xl\:cu-py-11{padding-top:var(--canon-space-11);padding-bottom:var(--canon-space-11)}.xl\:cu-py-12{padding-top:var(--canon-space-12);padding-bottom:var(--canon-space-12)}.xl\:cu-py-13{padding-top:var(--canon-space-13);padding-bottom:var(--canon-space-13)}.xl\:cu-py-14{padding-top:var(--canon-space-14);padding-bottom:var(--canon-space-14)}}.cu-px{padding-left:var(--px);padding-right:var(--px)}.cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}@media (width>=640px){.xs\:cu-px{padding-left:var(--px-xs);padding-right:var(--px-xs)}.xs\:cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.xs\:cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.xs\:cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.xs\:cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.xs\:cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.xs\:cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.xs\:cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.xs\:cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.xs\:cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.xs\:cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.xs\:cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.xs\:cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.xs\:cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.xs\:cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.xs\:cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.xs\:cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-px{padding-left:var(--px-sm);padding-right:var(--px-sm)}.sm\:cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.sm\:cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.sm\:cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.sm\:cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.sm\:cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.sm\:cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.sm\:cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.sm\:cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.sm\:cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.sm\:cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.sm\:cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.sm\:cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.sm\:cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.sm\:cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.sm\:cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.sm\:cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-px{padding-left:var(--px-md);padding-right:var(--px-md)}.md\:cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.md\:cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.md\:cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.md\:cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.md\:cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.md\:cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.md\:cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.md\:cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.md\:cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.md\:cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.md\:cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.md\:cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.md\:cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.md\:cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.md\:cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.md\:cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-px{padding-left:var(--px-lg);padding-right:var(--px-lg)}.lg\:cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.lg\:cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.lg\:cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.lg\:cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.lg\:cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.lg\:cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.lg\:cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.lg\:cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.lg\:cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.lg\:cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.lg\:cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.lg\:cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.lg\:cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.lg\:cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.lg\:cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.lg\:cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-px{padding-left:var(--px-xl);padding-right:var(--px-xl)}.xl\:cu-px-0\.5{padding-left:var(--canon-space-0_5);padding-right:var(--canon-space-0_5)}.xl\:cu-px-1{padding-left:var(--canon-space-1);padding-right:var(--canon-space-1)}.xl\:cu-px-1\.5{padding-left:var(--canon-space-1_5);padding-right:var(--canon-space-1_5)}.xl\:cu-px-2{padding-left:var(--canon-space-2);padding-right:var(--canon-space-2)}.xl\:cu-px-3{padding-left:var(--canon-space-3);padding-right:var(--canon-space-3)}.xl\:cu-px-4{padding-left:var(--canon-space-4);padding-right:var(--canon-space-4)}.xl\:cu-px-5{padding-left:var(--canon-space-5);padding-right:var(--canon-space-5)}.xl\:cu-px-6{padding-left:var(--canon-space-6);padding-right:var(--canon-space-6)}.xl\:cu-px-7{padding-left:var(--canon-space-7);padding-right:var(--canon-space-7)}.xl\:cu-px-8{padding-left:var(--canon-space-8);padding-right:var(--canon-space-8)}.xl\:cu-px-9{padding-left:var(--canon-space-9);padding-right:var(--canon-space-9)}.xl\:cu-px-10{padding-left:var(--canon-space-10);padding-right:var(--canon-space-10)}.xl\:cu-px-11{padding-left:var(--canon-space-11);padding-right:var(--canon-space-11)}.xl\:cu-px-12{padding-left:var(--canon-space-12);padding-right:var(--canon-space-12)}.xl\:cu-px-13{padding-left:var(--canon-space-13);padding-right:var(--canon-space-13)}.xl\:cu-px-14{padding-left:var(--canon-space-14);padding-right:var(--canon-space-14)}}.cu-m{margin:var(--m)}.cu-m-0\.5{margin:var(--canon-space-0_5)}.cu-m-1{margin:var(--canon-space-1)}.cu-m-1\.5{margin:var(--canon-space-1_5)}.cu-m-2{margin:var(--canon-space-2)}.cu-m-3{margin:var(--canon-space-3)}.cu-m-4{margin:var(--canon-space-4)}.cu-m-5{margin:var(--canon-space-5)}.cu-m-6{margin:var(--canon-space-6)}.cu-m-7{margin:var(--canon-space-7)}.cu-m-8{margin:var(--canon-space-8)}.cu-m-9{margin:var(--canon-space-9)}.cu-m-10{margin:var(--canon-space-10)}.cu-m-11{margin:var(--canon-space-11)}.cu-m-12{margin:var(--canon-space-12)}.cu-m-13{margin:var(--canon-space-13)}.cu-m-14{margin:var(--canon-space-14)}@media (width>=640px){.xs\:cu-m{margin:var(--p-xs)}.xs\:cu-m-0\.5{margin:var(--canon-space-0_5)}.xs\:cu-m-1{margin:var(--canon-space-1)}.xs\:cu-m-1\.5{margin:var(--canon-space-1_5)}.xs\:cu-m-2{margin:var(--canon-space-2)}.xs\:cu-m-3{margin:var(--canon-space-3)}.xs\:cu-m-4{margin:var(--canon-space-4)}.xs\:cu-m-5{margin:var(--canon-space-5)}.xs\:cu-m-6{margin:var(--canon-space-6)}.xs\:cu-m-7{margin:var(--canon-space-7)}.xs\:cu-m-8{margin:var(--canon-space-8)}.xs\:cu-m-9{margin:var(--canon-space-9)}.xs\:cu-m-10{margin:var(--canon-space-10)}.xs\:cu-m-11{margin:var(--canon-space-11)}.xs\:cu-m-12{margin:var(--canon-space-12)}.xs\:cu-m-13{margin:var(--canon-space-13)}.xs\:cu-m-14{margin:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-m{margin:var(--p-sm)}.sm\:cu-m-0\.5{margin:var(--canon-space-0_5)}.sm\:cu-m-1{margin:var(--canon-space-1)}.sm\:cu-m-1\.5{margin:var(--canon-space-1_5)}.sm\:cu-m-2{margin:var(--canon-space-2)}.sm\:cu-m-3{margin:var(--canon-space-3)}.sm\:cu-m-4{margin:var(--canon-space-4)}.sm\:cu-m-5{margin:var(--canon-space-5)}.sm\:cu-m-6{margin:var(--canon-space-6)}.sm\:cu-m-7{margin:var(--canon-space-7)}.sm\:cu-m-8{margin:var(--canon-space-8)}.sm\:cu-m-9{margin:var(--canon-space-9)}.sm\:cu-m-10{margin:var(--canon-space-10)}.sm\:cu-m-11{margin:var(--canon-space-11)}.sm\:cu-m-12{margin:var(--canon-space-12)}.sm\:cu-m-13{margin:var(--canon-space-13)}.sm\:cu-m-14{margin:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-m{margin:var(--p-md)}.md\:cu-m-0\.5{margin:var(--canon-space-0_5)}.md\:cu-m-1{margin:var(--canon-space-1)}.md\:cu-m-1\.5{margin:var(--canon-space-1_5)}.md\:cu-m-2{margin:var(--canon-space-2)}.md\:cu-m-3{margin:var(--canon-space-3)}.md\:cu-m-4{margin:var(--canon-space-4)}.md\:cu-m-5{margin:var(--canon-space-5)}.md\:cu-m-6{margin:var(--canon-space-6)}.md\:cu-m-7{margin:var(--canon-space-7)}.md\:cu-m-8{margin:var(--canon-space-8)}.md\:cu-m-9{margin:var(--canon-space-9)}.md\:cu-m-10{margin:var(--canon-space-10)}.md\:cu-m-11{margin:var(--canon-space-11)}.md\:cu-m-12{margin:var(--canon-space-12)}.md\:cu-m-13{margin:var(--canon-space-13)}.md\:cu-m-14{margin:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-m{margin:var(--p-lg)}.lg\:cu-m-0\.5{margin:var(--canon-space-0_5)}.lg\:cu-m-1{margin:var(--canon-space-1)}.lg\:cu-m-1\.5{margin:var(--canon-space-1_5)}.lg\:cu-m-2{margin:var(--canon-space-2)}.lg\:cu-m-3{margin:var(--canon-space-3)}.lg\:cu-m-4{margin:var(--canon-space-4)}.lg\:cu-m-5{margin:var(--canon-space-5)}.lg\:cu-m-6{margin:var(--canon-space-6)}.lg\:cu-m-7{margin:var(--canon-space-7)}.lg\:cu-m-8{margin:var(--canon-space-8)}.lg\:cu-m-9{margin:var(--canon-space-9)}.lg\:cu-m-10{margin:var(--canon-space-10)}.lg\:cu-m-11{margin:var(--canon-space-11)}.lg\:cu-m-12{margin:var(--canon-space-12)}.lg\:cu-m-13{margin:var(--canon-space-13)}.lg\:cu-m-14{margin:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-m{margin:var(--p-xl)}.xl\:cu-m-0\.5{margin:var(--canon-space-0_5)}.xl\:cu-m-1{margin:var(--canon-space-1)}.xl\:cu-m-1\.5{margin:var(--canon-space-1_5)}.xl\:cu-m-2{margin:var(--canon-space-2)}.xl\:cu-m-3{margin:var(--canon-space-3)}.xl\:cu-m-4{margin:var(--canon-space-4)}.xl\:cu-m-5{margin:var(--canon-space-5)}.xl\:cu-m-6{margin:var(--canon-space-6)}.xl\:cu-m-7{margin:var(--canon-space-7)}.xl\:cu-m-8{margin:var(--canon-space-8)}.xl\:cu-m-9{margin:var(--canon-space-9)}.xl\:cu-m-10{margin:var(--canon-space-10)}.xl\:cu-m-11{margin:var(--canon-space-11)}.xl\:cu-m-12{margin:var(--canon-space-12)}.xl\:cu-m-13{margin:var(--canon-space-13)}.xl\:cu-m-14{margin:var(--canon-space-14)}}.cu-ml{margin-left:var(--ml)}.cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.cu-ml-1{margin-left:var(--canon-space-1)}.cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.cu-ml-2{margin-left:var(--canon-space-2)}.cu-ml-3{margin-left:var(--canon-space-3)}.cu-ml-4{margin-left:var(--canon-space-4)}.cu-ml-5{margin-left:var(--canon-space-5)}.cu-ml-6{margin-left:var(--canon-space-6)}.cu-ml-7{margin-left:var(--canon-space-7)}.cu-ml-8{margin-left:var(--canon-space-8)}.cu-ml-9{margin-left:var(--canon-space-9)}.cu-ml-10{margin-left:var(--canon-space-10)}.cu-ml-11{margin-left:var(--canon-space-11)}.cu-ml-12{margin-left:var(--canon-space-12)}.cu-ml-13{margin-left:var(--canon-space-13)}.cu-ml-14{margin-left:var(--canon-space-14)}@media (width>=640px){.xs\:cu-ml{margin-left:var(--pl-xs)}.xs\:cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.xs\:cu-ml-1{margin-left:var(--canon-space-1)}.xs\:cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.xs\:cu-ml-2{margin-left:var(--canon-space-2)}.xs\:cu-ml-3{margin-left:var(--canon-space-3)}.xs\:cu-ml-4{margin-left:var(--canon-space-4)}.xs\:cu-ml-5{margin-left:var(--canon-space-5)}.xs\:cu-ml-6{margin-left:var(--canon-space-6)}.xs\:cu-ml-7{margin-left:var(--canon-space-7)}.xs\:cu-ml-8{margin-left:var(--canon-space-8)}.xs\:cu-ml-9{margin-left:var(--canon-space-9)}.xs\:cu-ml-10{margin-left:var(--canon-space-10)}.xs\:cu-ml-11{margin-left:var(--canon-space-11)}.xs\:cu-ml-12{margin-left:var(--canon-space-12)}.xs\:cu-ml-13{margin-left:var(--canon-space-13)}.xs\:cu-ml-14{margin-left:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-ml{margin-left:var(--pl-sm)}.sm\:cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.sm\:cu-ml-1{margin-left:var(--canon-space-1)}.sm\:cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.sm\:cu-ml-2{margin-left:var(--canon-space-2)}.sm\:cu-ml-3{margin-left:var(--canon-space-3)}.sm\:cu-ml-4{margin-left:var(--canon-space-4)}.sm\:cu-ml-5{margin-left:var(--canon-space-5)}.sm\:cu-ml-6{margin-left:var(--canon-space-6)}.sm\:cu-ml-7{margin-left:var(--canon-space-7)}.sm\:cu-ml-8{margin-left:var(--canon-space-8)}.sm\:cu-ml-9{margin-left:var(--canon-space-9)}.sm\:cu-ml-10{margin-left:var(--canon-space-10)}.sm\:cu-ml-11{margin-left:var(--canon-space-11)}.sm\:cu-ml-12{margin-left:var(--canon-space-12)}.sm\:cu-ml-13{margin-left:var(--canon-space-13)}.sm\:cu-ml-14{margin-left:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-ml{margin-left:var(--pl-md)}.md\:cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.md\:cu-ml-1{margin-left:var(--canon-space-1)}.md\:cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.md\:cu-ml-2{margin-left:var(--canon-space-2)}.md\:cu-ml-3{margin-left:var(--canon-space-3)}.md\:cu-ml-4{margin-left:var(--canon-space-4)}.md\:cu-ml-5{margin-left:var(--canon-space-5)}.md\:cu-ml-6{margin-left:var(--canon-space-6)}.md\:cu-ml-7{margin-left:var(--canon-space-7)}.md\:cu-ml-8{margin-left:var(--canon-space-8)}.md\:cu-ml-9{margin-left:var(--canon-space-9)}.md\:cu-ml-10{margin-left:var(--canon-space-10)}.md\:cu-ml-11{margin-left:var(--canon-space-11)}.md\:cu-ml-12{margin-left:var(--canon-space-12)}.md\:cu-ml-13{margin-left:var(--canon-space-13)}.md\:cu-ml-14{margin-left:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-ml{margin-left:var(--pl-lg)}.lg\:cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.lg\:cu-ml-1{margin-left:var(--canon-space-1)}.lg\:cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.lg\:cu-ml-2{margin-left:var(--canon-space-2)}.lg\:cu-ml-3{margin-left:var(--canon-space-3)}.lg\:cu-ml-4{margin-left:var(--canon-space-4)}.lg\:cu-ml-5{margin-left:var(--canon-space-5)}.lg\:cu-ml-6{margin-left:var(--canon-space-6)}.lg\:cu-ml-7{margin-left:var(--canon-space-7)}.lg\:cu-ml-8{margin-left:var(--canon-space-8)}.lg\:cu-ml-9{margin-left:var(--canon-space-9)}.lg\:cu-ml-10{margin-left:var(--canon-space-10)}.lg\:cu-ml-11{margin-left:var(--canon-space-11)}.lg\:cu-ml-12{margin-left:var(--canon-space-12)}.lg\:cu-ml-13{margin-left:var(--canon-space-13)}.lg\:cu-ml-14{margin-left:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-ml{margin-left:var(--pl-xl)}.xl\:cu-ml-0\.5{margin-left:var(--canon-space-0_5)}.xl\:cu-ml-1{margin-left:var(--canon-space-1)}.xl\:cu-ml-1\.5{margin-left:var(--canon-space-1_5)}.xl\:cu-ml-2{margin-left:var(--canon-space-2)}.xl\:cu-ml-3{margin-left:var(--canon-space-3)}.xl\:cu-ml-4{margin-left:var(--canon-space-4)}.xl\:cu-ml-5{margin-left:var(--canon-space-5)}.xl\:cu-ml-6{margin-left:var(--canon-space-6)}.xl\:cu-ml-7{margin-left:var(--canon-space-7)}.xl\:cu-ml-8{margin-left:var(--canon-space-8)}.xl\:cu-ml-9{margin-left:var(--canon-space-9)}.xl\:cu-ml-10{margin-left:var(--canon-space-10)}.xl\:cu-ml-11{margin-left:var(--canon-space-11)}.xl\:cu-ml-12{margin-left:var(--canon-space-12)}.xl\:cu-ml-13{margin-left:var(--canon-space-13)}.xl\:cu-ml-14{margin-left:var(--canon-space-14)}}.cu-mr{margin-right:var(--mr)}.cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.cu-mr-1{margin-right:var(--canon-space-1)}.cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.cu-mr-2{margin-right:var(--canon-space-2)}.cu-mr-3{margin-right:var(--canon-space-3)}.cu-mr-4{margin-right:var(--canon-space-4)}.cu-mr-5{margin-right:var(--canon-space-5)}.cu-mr-6{margin-right:var(--canon-space-6)}.cu-mr-7{margin-right:var(--canon-space-7)}.cu-mr-8{margin-right:var(--canon-space-8)}.cu-mr-9{margin-right:var(--canon-space-9)}.cu-mr-10{margin-right:var(--canon-space-10)}.cu-mr-11{margin-right:var(--canon-space-11)}.cu-mr-12{margin-right:var(--canon-space-12)}.cu-mr-13{margin-right:var(--canon-space-13)}.cu-mr-14{margin-right:var(--canon-space-14)}@media (width>=640px){.xs\:cu-mr{margin-right:var(--pr-xs)}.xs\:cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.xs\:cu-mr-1{margin-right:var(--canon-space-1)}.xs\:cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.xs\:cu-mr-2{margin-right:var(--canon-space-2)}.xs\:cu-mr-3{margin-right:var(--canon-space-3)}.xs\:cu-mr-4{margin-right:var(--canon-space-4)}.xs\:cu-mr-5{margin-right:var(--canon-space-5)}.xs\:cu-mr-6{margin-right:var(--canon-space-6)}.xs\:cu-mr-7{margin-right:var(--canon-space-7)}.xs\:cu-mr-8{margin-right:var(--canon-space-8)}.xs\:cu-mr-9{margin-right:var(--canon-space-9)}.xs\:cu-mr-10{margin-right:var(--canon-space-10)}.xs\:cu-mr-11{margin-right:var(--canon-space-11)}.xs\:cu-mr-12{margin-right:var(--canon-space-12)}.xs\:cu-mr-13{margin-right:var(--canon-space-13)}.xs\:cu-mr-14{margin-right:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-mr{margin-right:var(--pr-sm)}.sm\:cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.sm\:cu-mr-1{margin-right:var(--canon-space-1)}.sm\:cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.sm\:cu-mr-2{margin-right:var(--canon-space-2)}.sm\:cu-mr-3{margin-right:var(--canon-space-3)}.sm\:cu-mr-4{margin-right:var(--canon-space-4)}.sm\:cu-mr-5{margin-right:var(--canon-space-5)}.sm\:cu-mr-6{margin-right:var(--canon-space-6)}.sm\:cu-mr-7{margin-right:var(--canon-space-7)}.sm\:cu-mr-8{margin-right:var(--canon-space-8)}.sm\:cu-mr-9{margin-right:var(--canon-space-9)}.sm\:cu-mr-10{margin-right:var(--canon-space-10)}.sm\:cu-mr-11{margin-right:var(--canon-space-11)}.sm\:cu-mr-12{margin-right:var(--canon-space-12)}.sm\:cu-mr-13{margin-right:var(--canon-space-13)}.sm\:cu-mr-14{margin-right:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-mr{margin-right:var(--pr-md)}.md\:cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.md\:cu-mr-1{margin-right:var(--canon-space-1)}.md\:cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.md\:cu-mr-2{margin-right:var(--canon-space-2)}.md\:cu-mr-3{margin-right:var(--canon-space-3)}.md\:cu-mr-4{margin-right:var(--canon-space-4)}.md\:cu-mr-5{margin-right:var(--canon-space-5)}.md\:cu-mr-6{margin-right:var(--canon-space-6)}.md\:cu-mr-7{margin-right:var(--canon-space-7)}.md\:cu-mr-8{margin-right:var(--canon-space-8)}.md\:cu-mr-9{margin-right:var(--canon-space-9)}.md\:cu-mr-10{margin-right:var(--canon-space-10)}.md\:cu-mr-11{margin-right:var(--canon-space-11)}.md\:cu-mr-12{margin-right:var(--canon-space-12)}.md\:cu-mr-13{margin-right:var(--canon-space-13)}.md\:cu-mr-14{margin-right:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-mr{margin-right:var(--pr-lg)}.lg\:cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.lg\:cu-mr-1{margin-right:var(--canon-space-1)}.lg\:cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.lg\:cu-mr-2{margin-right:var(--canon-space-2)}.lg\:cu-mr-3{margin-right:var(--canon-space-3)}.lg\:cu-mr-4{margin-right:var(--canon-space-4)}.lg\:cu-mr-5{margin-right:var(--canon-space-5)}.lg\:cu-mr-6{margin-right:var(--canon-space-6)}.lg\:cu-mr-7{margin-right:var(--canon-space-7)}.lg\:cu-mr-8{margin-right:var(--canon-space-8)}.lg\:cu-mr-9{margin-right:var(--canon-space-9)}.lg\:cu-mr-10{margin-right:var(--canon-space-10)}.lg\:cu-mr-11{margin-right:var(--canon-space-11)}.lg\:cu-mr-12{margin-right:var(--canon-space-12)}.lg\:cu-mr-13{margin-right:var(--canon-space-13)}.lg\:cu-mr-14{margin-right:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-mr{margin-right:var(--pr-xl)}.xl\:cu-mr-0\.5{margin-right:var(--canon-space-0_5)}.xl\:cu-mr-1{margin-right:var(--canon-space-1)}.xl\:cu-mr-1\.5{margin-right:var(--canon-space-1_5)}.xl\:cu-mr-2{margin-right:var(--canon-space-2)}.xl\:cu-mr-3{margin-right:var(--canon-space-3)}.xl\:cu-mr-4{margin-right:var(--canon-space-4)}.xl\:cu-mr-5{margin-right:var(--canon-space-5)}.xl\:cu-mr-6{margin-right:var(--canon-space-6)}.xl\:cu-mr-7{margin-right:var(--canon-space-7)}.xl\:cu-mr-8{margin-right:var(--canon-space-8)}.xl\:cu-mr-9{margin-right:var(--canon-space-9)}.xl\:cu-mr-10{margin-right:var(--canon-space-10)}.xl\:cu-mr-11{margin-right:var(--canon-space-11)}.xl\:cu-mr-12{margin-right:var(--canon-space-12)}.xl\:cu-mr-13{margin-right:var(--canon-space-13)}.xl\:cu-mr-14{margin-right:var(--canon-space-14)}}.cu-mt{margin-top:var(--mt)}.cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.cu-mt-1{margin-top:var(--canon-space-1)}.cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.cu-mt-2{margin-top:var(--canon-space-2)}.cu-mt-3{margin-top:var(--canon-space-3)}.cu-mt-4{margin-top:var(--canon-space-4)}.cu-mt-5{margin-top:var(--canon-space-5)}.cu-mt-6{margin-top:var(--canon-space-6)}.cu-mt-7{margin-top:var(--canon-space-7)}.cu-mt-8{margin-top:var(--canon-space-8)}.cu-mt-9{margin-top:var(--canon-space-9)}.cu-mt-10{margin-top:var(--canon-space-10)}.cu-mt-11{margin-top:var(--canon-space-11)}.cu-mt-12{margin-top:var(--canon-space-12)}.cu-mt-13{margin-top:var(--canon-space-13)}.cu-mt-14{margin-top:var(--canon-space-14)}@media (width>=640px){.xs\:cu-mt{margin-top:var(--pt-xs)}.xs\:cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.xs\:cu-mt-1{margin-top:var(--canon-space-1)}.xs\:cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.xs\:cu-mt-2{margin-top:var(--canon-space-2)}.xs\:cu-mt-3{margin-top:var(--canon-space-3)}.xs\:cu-mt-4{margin-top:var(--canon-space-4)}.xs\:cu-mt-5{margin-top:var(--canon-space-5)}.xs\:cu-mt-6{margin-top:var(--canon-space-6)}.xs\:cu-mt-7{margin-top:var(--canon-space-7)}.xs\:cu-mt-8{margin-top:var(--canon-space-8)}.xs\:cu-mt-9{margin-top:var(--canon-space-9)}.xs\:cu-mt-10{margin-top:var(--canon-space-10)}.xs\:cu-mt-11{margin-top:var(--canon-space-11)}.xs\:cu-mt-12{margin-top:var(--canon-space-12)}.xs\:cu-mt-13{margin-top:var(--canon-space-13)}.xs\:cu-mt-14{margin-top:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-mt{margin-top:var(--pt-sm)}.sm\:cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.sm\:cu-mt-1{margin-top:var(--canon-space-1)}.sm\:cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.sm\:cu-mt-2{margin-top:var(--canon-space-2)}.sm\:cu-mt-3{margin-top:var(--canon-space-3)}.sm\:cu-mt-4{margin-top:var(--canon-space-4)}.sm\:cu-mt-5{margin-top:var(--canon-space-5)}.sm\:cu-mt-6{margin-top:var(--canon-space-6)}.sm\:cu-mt-7{margin-top:var(--canon-space-7)}.sm\:cu-mt-8{margin-top:var(--canon-space-8)}.sm\:cu-mt-9{margin-top:var(--canon-space-9)}.sm\:cu-mt-10{margin-top:var(--canon-space-10)}.sm\:cu-mt-11{margin-top:var(--canon-space-11)}.sm\:cu-mt-12{margin-top:var(--canon-space-12)}.sm\:cu-mt-13{margin-top:var(--canon-space-13)}.sm\:cu-mt-14{margin-top:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-mt{margin-top:var(--pt-md)}.md\:cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.md\:cu-mt-1{margin-top:var(--canon-space-1)}.md\:cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.md\:cu-mt-2{margin-top:var(--canon-space-2)}.md\:cu-mt-3{margin-top:var(--canon-space-3)}.md\:cu-mt-4{margin-top:var(--canon-space-4)}.md\:cu-mt-5{margin-top:var(--canon-space-5)}.md\:cu-mt-6{margin-top:var(--canon-space-6)}.md\:cu-mt-7{margin-top:var(--canon-space-7)}.md\:cu-mt-8{margin-top:var(--canon-space-8)}.md\:cu-mt-9{margin-top:var(--canon-space-9)}.md\:cu-mt-10{margin-top:var(--canon-space-10)}.md\:cu-mt-11{margin-top:var(--canon-space-11)}.md\:cu-mt-12{margin-top:var(--canon-space-12)}.md\:cu-mt-13{margin-top:var(--canon-space-13)}.md\:cu-mt-14{margin-top:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-mt{margin-top:var(--pt-lg)}.lg\:cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.lg\:cu-mt-1{margin-top:var(--canon-space-1)}.lg\:cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.lg\:cu-mt-2{margin-top:var(--canon-space-2)}.lg\:cu-mt-3{margin-top:var(--canon-space-3)}.lg\:cu-mt-4{margin-top:var(--canon-space-4)}.lg\:cu-mt-5{margin-top:var(--canon-space-5)}.lg\:cu-mt-6{margin-top:var(--canon-space-6)}.lg\:cu-mt-7{margin-top:var(--canon-space-7)}.lg\:cu-mt-8{margin-top:var(--canon-space-8)}.lg\:cu-mt-9{margin-top:var(--canon-space-9)}.lg\:cu-mt-10{margin-top:var(--canon-space-10)}.lg\:cu-mt-11{margin-top:var(--canon-space-11)}.lg\:cu-mt-12{margin-top:var(--canon-space-12)}.lg\:cu-mt-13{margin-top:var(--canon-space-13)}.lg\:cu-mt-14{margin-top:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-mt{margin-top:var(--pt-xl)}.xl\:cu-mt-0\.5{margin-top:var(--canon-space-0_5)}.xl\:cu-mt-1{margin-top:var(--canon-space-1)}.xl\:cu-mt-1\.5{margin-top:var(--canon-space-1_5)}.xl\:cu-mt-2{margin-top:var(--canon-space-2)}.xl\:cu-mt-3{margin-top:var(--canon-space-3)}.xl\:cu-mt-4{margin-top:var(--canon-space-4)}.xl\:cu-mt-5{margin-top:var(--canon-space-5)}.xl\:cu-mt-6{margin-top:var(--canon-space-6)}.xl\:cu-mt-7{margin-top:var(--canon-space-7)}.xl\:cu-mt-8{margin-top:var(--canon-space-8)}.xl\:cu-mt-9{margin-top:var(--canon-space-9)}.xl\:cu-mt-10{margin-top:var(--canon-space-10)}.xl\:cu-mt-11{margin-top:var(--canon-space-11)}.xl\:cu-mt-12{margin-top:var(--canon-space-12)}.xl\:cu-mt-13{margin-top:var(--canon-space-13)}.xl\:cu-mt-14{margin-top:var(--canon-space-14)}}.cu-mb{margin-bottom:var(--mb)}.cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.cu-mb-1{margin-bottom:var(--canon-space-1)}.cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.cu-mb-2{margin-bottom:var(--canon-space-2)}.cu-mb-3{margin-bottom:var(--canon-space-3)}.cu-mb-4{margin-bottom:var(--canon-space-4)}.cu-mb-5{margin-bottom:var(--canon-space-5)}.cu-mb-6{margin-bottom:var(--canon-space-6)}.cu-mb-7{margin-bottom:var(--canon-space-7)}.cu-mb-8{margin-bottom:var(--canon-space-8)}.cu-mb-9{margin-bottom:var(--canon-space-9)}.cu-mb-10{margin-bottom:var(--canon-space-10)}.cu-mb-11{margin-bottom:var(--canon-space-11)}.cu-mb-12{margin-bottom:var(--canon-space-12)}.cu-mb-13{margin-bottom:var(--canon-space-13)}.cu-mb-14{margin-bottom:var(--canon-space-14)}@media (width>=640px){.xs\:cu-mb{margin-bottom:var(--pb-xs)}.xs\:cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.xs\:cu-mb-1{margin-bottom:var(--canon-space-1)}.xs\:cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.xs\:cu-mb-2{margin-bottom:var(--canon-space-2)}.xs\:cu-mb-3{margin-bottom:var(--canon-space-3)}.xs\:cu-mb-4{margin-bottom:var(--canon-space-4)}.xs\:cu-mb-5{margin-bottom:var(--canon-space-5)}.xs\:cu-mb-6{margin-bottom:var(--canon-space-6)}.xs\:cu-mb-7{margin-bottom:var(--canon-space-7)}.xs\:cu-mb-8{margin-bottom:var(--canon-space-8)}.xs\:cu-mb-9{margin-bottom:var(--canon-space-9)}.xs\:cu-mb-10{margin-bottom:var(--canon-space-10)}.xs\:cu-mb-11{margin-bottom:var(--canon-space-11)}.xs\:cu-mb-12{margin-bottom:var(--canon-space-12)}.xs\:cu-mb-13{margin-bottom:var(--canon-space-13)}.xs\:cu-mb-14{margin-bottom:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-mb{margin-bottom:var(--pb-sm)}.sm\:cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.sm\:cu-mb-1{margin-bottom:var(--canon-space-1)}.sm\:cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.sm\:cu-mb-2{margin-bottom:var(--canon-space-2)}.sm\:cu-mb-3{margin-bottom:var(--canon-space-3)}.sm\:cu-mb-4{margin-bottom:var(--canon-space-4)}.sm\:cu-mb-5{margin-bottom:var(--canon-space-5)}.sm\:cu-mb-6{margin-bottom:var(--canon-space-6)}.sm\:cu-mb-7{margin-bottom:var(--canon-space-7)}.sm\:cu-mb-8{margin-bottom:var(--canon-space-8)}.sm\:cu-mb-9{margin-bottom:var(--canon-space-9)}.sm\:cu-mb-10{margin-bottom:var(--canon-space-10)}.sm\:cu-mb-11{margin-bottom:var(--canon-space-11)}.sm\:cu-mb-12{margin-bottom:var(--canon-space-12)}.sm\:cu-mb-13{margin-bottom:var(--canon-space-13)}.sm\:cu-mb-14{margin-bottom:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-mb{margin-bottom:var(--pb-md)}.md\:cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.md\:cu-mb-1{margin-bottom:var(--canon-space-1)}.md\:cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.md\:cu-mb-2{margin-bottom:var(--canon-space-2)}.md\:cu-mb-3{margin-bottom:var(--canon-space-3)}.md\:cu-mb-4{margin-bottom:var(--canon-space-4)}.md\:cu-mb-5{margin-bottom:var(--canon-space-5)}.md\:cu-mb-6{margin-bottom:var(--canon-space-6)}.md\:cu-mb-7{margin-bottom:var(--canon-space-7)}.md\:cu-mb-8{margin-bottom:var(--canon-space-8)}.md\:cu-mb-9{margin-bottom:var(--canon-space-9)}.md\:cu-mb-10{margin-bottom:var(--canon-space-10)}.md\:cu-mb-11{margin-bottom:var(--canon-space-11)}.md\:cu-mb-12{margin-bottom:var(--canon-space-12)}.md\:cu-mb-13{margin-bottom:var(--canon-space-13)}.md\:cu-mb-14{margin-bottom:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-mb{margin-bottom:var(--pb-lg)}.lg\:cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.lg\:cu-mb-1{margin-bottom:var(--canon-space-1)}.lg\:cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.lg\:cu-mb-2{margin-bottom:var(--canon-space-2)}.lg\:cu-mb-3{margin-bottom:var(--canon-space-3)}.lg\:cu-mb-4{margin-bottom:var(--canon-space-4)}.lg\:cu-mb-5{margin-bottom:var(--canon-space-5)}.lg\:cu-mb-6{margin-bottom:var(--canon-space-6)}.lg\:cu-mb-7{margin-bottom:var(--canon-space-7)}.lg\:cu-mb-8{margin-bottom:var(--canon-space-8)}.lg\:cu-mb-9{margin-bottom:var(--canon-space-9)}.lg\:cu-mb-10{margin-bottom:var(--canon-space-10)}.lg\:cu-mb-11{margin-bottom:var(--canon-space-11)}.lg\:cu-mb-12{margin-bottom:var(--canon-space-12)}.lg\:cu-mb-13{margin-bottom:var(--canon-space-13)}.lg\:cu-mb-14{margin-bottom:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-mb{margin-bottom:var(--pb-xl)}.xl\:cu-mb-0\.5{margin-bottom:var(--canon-space-0_5)}.xl\:cu-mb-1{margin-bottom:var(--canon-space-1)}.xl\:cu-mb-1\.5{margin-bottom:var(--canon-space-1_5)}.xl\:cu-mb-2{margin-bottom:var(--canon-space-2)}.xl\:cu-mb-3{margin-bottom:var(--canon-space-3)}.xl\:cu-mb-4{margin-bottom:var(--canon-space-4)}.xl\:cu-mb-5{margin-bottom:var(--canon-space-5)}.xl\:cu-mb-6{margin-bottom:var(--canon-space-6)}.xl\:cu-mb-7{margin-bottom:var(--canon-space-7)}.xl\:cu-mb-8{margin-bottom:var(--canon-space-8)}.xl\:cu-mb-9{margin-bottom:var(--canon-space-9)}.xl\:cu-mb-10{margin-bottom:var(--canon-space-10)}.xl\:cu-mb-11{margin-bottom:var(--canon-space-11)}.xl\:cu-mb-12{margin-bottom:var(--canon-space-12)}.xl\:cu-mb-13{margin-bottom:var(--canon-space-13)}.xl\:cu-mb-14{margin-bottom:var(--canon-space-14)}}.cu-my{margin-top:var(--my);margin-bottom:var(--my)}.cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}@media (width>=640px){.xs\:cu-my{margin-top:var(--py-xs);margin-bottom:var(--py-xs)}.xs\:cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.xs\:cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.xs\:cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.xs\:cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.xs\:cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.xs\:cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.xs\:cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.xs\:cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.xs\:cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.xs\:cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.xs\:cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.xs\:cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.xs\:cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.xs\:cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.xs\:cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.xs\:cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-my{margin-top:var(--py-sm);margin-bottom:var(--py-sm)}.sm\:cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.sm\:cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.sm\:cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.sm\:cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.sm\:cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.sm\:cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.sm\:cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.sm\:cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.sm\:cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.sm\:cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.sm\:cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.sm\:cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.sm\:cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.sm\:cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.sm\:cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.sm\:cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-my{margin-top:var(--py-md);margin-bottom:var(--py-md)}.md\:cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.md\:cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.md\:cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.md\:cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.md\:cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.md\:cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.md\:cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.md\:cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.md\:cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.md\:cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.md\:cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.md\:cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.md\:cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.md\:cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.md\:cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.md\:cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-my{margin-top:var(--py-lg);margin-bottom:var(--py-lg)}.lg\:cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.lg\:cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.lg\:cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.lg\:cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.lg\:cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.lg\:cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.lg\:cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.lg\:cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.lg\:cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.lg\:cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.lg\:cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.lg\:cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.lg\:cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.lg\:cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.lg\:cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.lg\:cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-my{margin-top:var(--py-xl);margin-bottom:var(--py-xl)}.xl\:cu-my-0\.5{margin-top:var(--canon-space-0_5);margin-bottom:var(--canon-space-0_5)}.xl\:cu-my-1{margin-top:var(--canon-space-1);margin-bottom:var(--canon-space-1)}.xl\:cu-my-1\.5{margin-top:var(--canon-space-1_5);margin-bottom:var(--canon-space-1_5)}.xl\:cu-my-2{margin-top:var(--canon-space-2);margin-bottom:var(--canon-space-2)}.xl\:cu-my-3{margin-top:var(--canon-space-3);margin-bottom:var(--canon-space-3)}.xl\:cu-my-4{margin-top:var(--canon-space-4);margin-bottom:var(--canon-space-4)}.xl\:cu-my-5{margin-top:var(--canon-space-5);margin-bottom:var(--canon-space-5)}.xl\:cu-my-6{margin-top:var(--canon-space-6);margin-bottom:var(--canon-space-6)}.xl\:cu-my-7{margin-top:var(--canon-space-7);margin-bottom:var(--canon-space-7)}.xl\:cu-my-8{margin-top:var(--canon-space-8);margin-bottom:var(--canon-space-8)}.xl\:cu-my-9{margin-top:var(--canon-space-9);margin-bottom:var(--canon-space-9)}.xl\:cu-my-10{margin-top:var(--canon-space-10);margin-bottom:var(--canon-space-10)}.xl\:cu-my-11{margin-top:var(--canon-space-11);margin-bottom:var(--canon-space-11)}.xl\:cu-my-12{margin-top:var(--canon-space-12);margin-bottom:var(--canon-space-12)}.xl\:cu-my-13{margin-top:var(--canon-space-13);margin-bottom:var(--canon-space-13)}.xl\:cu-my-14{margin-top:var(--canon-space-14);margin-bottom:var(--canon-space-14)}}.cu-mx{margin-left:var(--mx);margin-right:var(--mx)}.cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}@media (width>=640px){.xs\:cu-mx{margin-left:var(--px-xs);margin-right:var(--px-xs)}.xs\:cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.xs\:cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.xs\:cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.xs\:cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.xs\:cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.xs\:cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.xs\:cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.xs\:cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.xs\:cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.xs\:cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.xs\:cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.xs\:cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.xs\:cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.xs\:cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.xs\:cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.xs\:cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-mx{margin-left:var(--px-sm);margin-right:var(--px-sm)}.sm\:cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.sm\:cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.sm\:cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.sm\:cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.sm\:cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.sm\:cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.sm\:cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.sm\:cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.sm\:cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.sm\:cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.sm\:cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.sm\:cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.sm\:cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.sm\:cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.sm\:cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.sm\:cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-mx{margin-left:var(--px-md);margin-right:var(--px-md)}.md\:cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.md\:cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.md\:cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.md\:cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.md\:cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.md\:cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.md\:cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.md\:cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.md\:cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.md\:cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.md\:cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.md\:cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.md\:cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.md\:cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.md\:cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.md\:cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-mx{margin-left:var(--px-lg);margin-right:var(--px-lg)}.lg\:cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.lg\:cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.lg\:cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.lg\:cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.lg\:cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.lg\:cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.lg\:cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.lg\:cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.lg\:cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.lg\:cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.lg\:cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.lg\:cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.lg\:cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.lg\:cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.lg\:cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.lg\:cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-mx{margin-left:var(--px-xl);margin-right:var(--px-xl)}.xl\:cu-mx-0\.5{margin-left:var(--canon-space-0_5);margin-right:var(--canon-space-0_5)}.xl\:cu-mx-1{margin-left:var(--canon-space-1);margin-right:var(--canon-space-1)}.xl\:cu-mx-1\.5{margin-left:var(--canon-space-1_5);margin-right:var(--canon-space-1_5)}.xl\:cu-mx-2{margin-left:var(--canon-space-2);margin-right:var(--canon-space-2)}.xl\:cu-mx-3{margin-left:var(--canon-space-3);margin-right:var(--canon-space-3)}.xl\:cu-mx-4{margin-left:var(--canon-space-4);margin-right:var(--canon-space-4)}.xl\:cu-mx-5{margin-left:var(--canon-space-5);margin-right:var(--canon-space-5)}.xl\:cu-mx-6{margin-left:var(--canon-space-6);margin-right:var(--canon-space-6)}.xl\:cu-mx-7{margin-left:var(--canon-space-7);margin-right:var(--canon-space-7)}.xl\:cu-mx-8{margin-left:var(--canon-space-8);margin-right:var(--canon-space-8)}.xl\:cu-mx-9{margin-left:var(--canon-space-9);margin-right:var(--canon-space-9)}.xl\:cu-mx-10{margin-left:var(--canon-space-10);margin-right:var(--canon-space-10)}.xl\:cu-mx-11{margin-left:var(--canon-space-11);margin-right:var(--canon-space-11)}.xl\:cu-mx-12{margin-left:var(--canon-space-12);margin-right:var(--canon-space-12)}.xl\:cu-mx-13{margin-left:var(--canon-space-13);margin-right:var(--canon-space-13)}.xl\:cu-mx-14{margin-left:var(--canon-space-14);margin-right:var(--canon-space-14)}}.cu-display-none{display:none}.cu-display-inline{display:inline}.cu-display-inline-block{display:inline-block}.cu-display-block{display:block}@media (width>=640px){.xs\:cu-display-none{display:none}.xs\:cu-display-inline{display:inline}.xs\:cu-display-inline-block{display:inline-block}.xs\:cu-display-block{display:block}}@media (width>=768px){.sm\:cu-display-none{display:none}.sm\:cu-display-inline{display:inline}.sm\:cu-display-inline-block{display:inline-block}.sm\:cu-display-block{display:block}}@media (width>=1024px){.md\:cu-display-none{display:none}.md\:cu-display-inline{display:inline}.md\:cu-display-inline-block{display:inline-block}.md\:cu-display-block{display:block}}@media (width>=1280px){.lg\:cu-display-none{display:none}.lg\:cu-display-inline{display:inline}.lg\:cu-display-inline-block{display:inline-block}.lg\:cu-display-block{display:block}}@media (width>=1536px){.xl\:cu-display-none{display:none}.xl\:cu-display-inline{display:inline}.xl\:cu-display-inline-block{display:inline-block}.xl\:cu-display-block{display:block}}.cu-w{width:var(--width)}.cu-min-w{min-width:var(--min-width)}.cu-max-w{max-width:var(--max-width)}@media (width>=640px){.xs\:cu-w{width:var(--width)}.xs\:cu-min-w{min-width:var(--min-width)}.xs\:cu-max-w{max-width:var(--max-width)}}@media (width>=768px){.sm\:cu-w{width:var(--width)}.sm\:cu-min-w{min-width:var(--min-width)}.sm\:cu-max-w{max-width:var(--max-width)}}@media (width>=1024px){.md\:cu-w{width:var(--width)}.md\:cu-min-w{min-width:var(--min-width)}.md\:cu-max-w{max-width:var(--max-width)}}@media (width>=1280px){.lg\:cu-w{width:var(--width)}.lg\:cu-min-w{min-width:var(--min-width)}.lg\:cu-max-w{max-width:var(--max-width)}}@media (width>=1536px){.xl\:cu-w{width:var(--width)}.xl\:cu-min-w{min-width:var(--min-width)}.xl\:cu-max-w{max-width:var(--max-width)}}.cu-h{height:var(--height)}.cu-min-h{min-height:var(--min-height)}.cu-max-h{max-height:var(--max-height)}@media (width>=640px){.xs\:cu-h{height:var(--height)}.xs\:cu-min-h{min-height:var(--min-height)}.xs\:cu-max-h{max-height:var(--max-height)}}@media (width>=768px){.sm\:cu-h{height:var(--height)}.sm\:cu-min-h{min-height:var(--min-height)}.sm\:cu-max-h{max-height:var(--max-height)}}@media (width>=1024px){.md\:cu-h{height:var(--height)}.md\:cu-min-h{min-height:var(--min-height)}.md\:cu-max-h{max-height:var(--max-height)}}@media (width>=1280px){.lg\:cu-h{height:var(--height)}.lg\:cu-min-h{min-height:var(--min-height)}.lg\:cu-max-h{max-height:var(--max-height)}}@media (width>=1536px){.xl\:cu-h{height:var(--height)}.xl\:cu-min-h{min-height:var(--min-height)}.xl\:cu-max-h{max-height:var(--max-height)}}.cu-position-absolute{position:absolute}.cu-position-fixed{position:fixed}.cu-position-sticky{position:sticky}.cu-position-relative{position:relative}.cu-position-static{position:static}@media (width>=640px){.xs\:cu-position-absolute{position:absolute}.xs\:cu-position-fixed{position:fixed}.xs\:cu-position-sticky{position:sticky}.xs\:cu-position-relative{position:relative}.xs\:cu-position-static{position:static}}@media (width>=768px){.sm\:cu-position-absolute{position:absolute}.sm\:cu-position-fixed{position:fixed}.sm\:cu-position-sticky{position:sticky}.sm\:cu-position-relative{position:relative}.sm\:cu-position-static{position:static}}@media (width>=1024px){.md\:cu-position-absolute{position:absolute}.md\:cu-position-fixed{position:fixed}.md\:cu-position-sticky{position:sticky}.md\:cu-position-relative{position:relative}.md\:cu-position-static{position:static}}@media (width>=1280px){.lg\:cu-position-absolute{position:absolute}.lg\:cu-position-fixed{position:fixed}.lg\:cu-position-sticky{position:sticky}.lg\:cu-position-relative{position:relative}.lg\:cu-position-static{position:static}}@media (width>=1536px){.xl\:cu-position-absolute{position:absolute}.xl\:cu-position-fixed{position:fixed}.xl\:cu-position-sticky{position:sticky}.xl\:cu-position-relative{position:relative}.xl\:cu-position-static{position:static}}.cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.cu-col-span-1{grid-column:span 1/span 1}.cu-col-span-2{grid-column:span 2/span 2}.cu-col-span-3{grid-column:span 3/span 3}.cu-col-span-4{grid-column:span 4/span 4}.cu-col-span-5{grid-column:span 5/span 5}.cu-col-span-6{grid-column:span 6/span 6}.cu-col-span-7{grid-column:span 7/span 7}.cu-col-span-8{grid-column:span 8/span 8}.cu-col-span-9{grid-column:span 9/span 9}.cu-col-span-10{grid-column:span 10/span 10}.cu-col-span-11{grid-column:span 11/span 11}.cu-col-span-12{grid-column:span 12/span 12}.cu-col-span-auto{grid-column:span auto/span auto}.cu-col-start-1{grid-column-start:1}.cu-col-start-2{grid-column-start:2}.cu-col-start-3{grid-column-start:3}.cu-col-start-4{grid-column-start:4}.cu-col-start-5{grid-column-start:5}.cu-col-start-6{grid-column-start:6}.cu-col-start-7{grid-column-start:7}.cu-col-start-8{grid-column-start:8}.cu-col-start-9{grid-column-start:9}.cu-col-start-10{grid-column-start:10}.cu-col-start-11{grid-column-start:11}.cu-col-start-12{grid-column-start:12}.cu-col-start-13{grid-column-start:13}.cu-col-start-auto{grid-column-start:auto}.cu-col-end-1{grid-column-end:1}.cu-col-end-2{grid-column-end:2}.cu-col-end-3{grid-column-end:3}.cu-col-end-4{grid-column-end:4}.cu-col-end-5{grid-column-end:5}.cu-col-end-6{grid-column-end:6}.cu-col-end-7{grid-column-end:7}.cu-col-end-8{grid-column-end:8}.cu-col-end-9{grid-column-end:9}.cu-col-end-10{grid-column-end:10}.cu-col-end-11{grid-column-end:11}.cu-col-end-12{grid-column-end:12}.cu-col-end-13{grid-column-end:13}.cu-col-end-auto{grid-column-end:auto}.cu-row-span-1{grid-row:span 1/span 1}.cu-row-span-2{grid-row:span 2/span 2}.cu-row-span-3{grid-row:span 3/span 3}.cu-row-span-4{grid-row:span 4/span 4}.cu-row-span-5{grid-row:span 5/span 5}.cu-row-span-6{grid-row:span 6/span 6}.cu-row-span-7{grid-row:span 7/span 7}.cu-row-span-8{grid-row:span 8/span 8}.cu-row-span-9{grid-row:span 9/span 9}.cu-row-span-10{grid-row:span 10/span 10}.cu-row-span-11{grid-row:span 11/span 11}.cu-row-span-12{grid-row:span 12/span 12}.cu-row-span-auto{grid-row:span auto/span auto}@media (width>=640px){.xs\:cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xs\:cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xs\:cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xs\:cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xs\:cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xs\:cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xs\:cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xs\:cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xs\:cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xs\:cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xs\:cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xs\:cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xs\:cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xs\:cu-col-span-1{grid-column:span 1/span 1}.xs\:cu-col-span-2{grid-column:span 2/span 2}.xs\:cu-col-span-3{grid-column:span 3/span 3}.xs\:cu-col-span-4{grid-column:span 4/span 4}.xs\:cu-col-span-5{grid-column:span 5/span 5}.xs\:cu-col-span-6{grid-column:span 6/span 6}.xs\:cu-col-span-7{grid-column:span 7/span 7}.xs\:cu-col-span-8{grid-column:span 8/span 8}.xs\:cu-col-span-9{grid-column:span 9/span 9}.xs\:cu-col-span-10{grid-column:span 10/span 10}.xs\:cu-col-span-11{grid-column:span 11/span 11}.xs\:cu-col-span-12{grid-column:span 12/span 12}.xs\:cu-col-span-auto{grid-column:span auto/span auto}.xs\:cu-col-start-1{grid-column-start:1}.xs\:cu-col-start-2{grid-column-start:2}.xs\:cu-col-start-3{grid-column-start:3}.xs\:cu-col-start-4{grid-column-start:4}.xs\:cu-col-start-5{grid-column-start:5}.xs\:cu-col-start-6{grid-column-start:6}.xs\:cu-col-start-7{grid-column-start:7}.xs\:cu-col-start-8{grid-column-start:8}.xs\:cu-col-start-9{grid-column-start:9}.xs\:cu-col-start-10{grid-column-start:10}.xs\:cu-col-start-11{grid-column-start:11}.xs\:cu-col-start-12{grid-column-start:12}.xs\:cu-col-start-13{grid-column-start:13}.xs\:cu-col-start-auto{grid-column-start:auto}.xs\:cu-col-end-1{grid-column-end:1}.xs\:cu-col-end-2{grid-column-end:2}.xs\:cu-col-end-3{grid-column-end:3}.xs\:cu-col-end-4{grid-column-end:4}.xs\:cu-col-end-5{grid-column-end:5}.xs\:cu-col-end-6{grid-column-end:6}.xs\:cu-col-end-7{grid-column-end:7}.xs\:cu-col-end-8{grid-column-end:8}.xs\:cu-col-end-9{grid-column-end:9}.xs\:cu-col-end-10{grid-column-end:10}.xs\:cu-col-end-11{grid-column-end:11}.xs\:cu-col-end-12{grid-column-end:12}.xs\:cu-col-end-13{grid-column-end:13}.xs\:cu-col-end-auto{grid-column-end:auto}.xs\:cu-row-span-1{grid-row:span 1/span 1}.xs\:cu-row-span-2{grid-row:span 2/span 2}.xs\:cu-row-span-3{grid-row:span 3/span 3}.xs\:cu-row-span-4{grid-row:span 4/span 4}.xs\:cu-row-span-5{grid-row:span 5/span 5}.xs\:cu-row-span-6{grid-row:span 6/span 6}.xs\:cu-row-span-7{grid-row:span 7/span 7}.xs\:cu-row-span-8{grid-row:span 8/span 8}.xs\:cu-row-span-9{grid-row:span 9/span 9}.xs\:cu-row-span-10{grid-row:span 10/span 10}.xs\:cu-row-span-11{grid-row:span 11/span 11}.xs\:cu-row-span-12{grid-row:span 12/span 12}.xs\:cu-row-span-auto{grid-row:span auto/span auto}}@media (width>=768px){.sm\:cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.sm\:cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.sm\:cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.sm\:cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.sm\:cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:cu-col-span-1{grid-column:span 1/span 1}.sm\:cu-col-span-2{grid-column:span 2/span 2}.sm\:cu-col-span-3{grid-column:span 3/span 3}.sm\:cu-col-span-4{grid-column:span 4/span 4}.sm\:cu-col-span-5{grid-column:span 5/span 5}.sm\:cu-col-span-6{grid-column:span 6/span 6}.sm\:cu-col-span-7{grid-column:span 7/span 7}.sm\:cu-col-span-8{grid-column:span 8/span 8}.sm\:cu-col-span-9{grid-column:span 9/span 9}.sm\:cu-col-span-10{grid-column:span 10/span 10}.sm\:cu-col-span-11{grid-column:span 11/span 11}.sm\:cu-col-span-12{grid-column:span 12/span 12}.sm\:cu-col-span-auto{grid-column:span auto/span auto}.sm\:cu-col-start-1{grid-column-start:1}.sm\:cu-col-start-2{grid-column-start:2}.sm\:cu-col-start-3{grid-column-start:3}.sm\:cu-col-start-4{grid-column-start:4}.sm\:cu-col-start-5{grid-column-start:5}.sm\:cu-col-start-6{grid-column-start:6}.sm\:cu-col-start-7{grid-column-start:7}.sm\:cu-col-start-8{grid-column-start:8}.sm\:cu-col-start-9{grid-column-start:9}.sm\:cu-col-start-10{grid-column-start:10}.sm\:cu-col-start-11{grid-column-start:11}.sm\:cu-col-start-12{grid-column-start:12}.sm\:cu-col-start-13{grid-column-start:13}.sm\:cu-col-start-auto{grid-column-start:auto}.sm\:cu-col-end-1{grid-column-end:1}.sm\:cu-col-end-2{grid-column-end:2}.sm\:cu-col-end-3{grid-column-end:3}.sm\:cu-col-end-4{grid-column-end:4}.sm\:cu-col-end-5{grid-column-end:5}.sm\:cu-col-end-6{grid-column-end:6}.sm\:cu-col-end-7{grid-column-end:7}.sm\:cu-col-end-8{grid-column-end:8}.sm\:cu-col-end-9{grid-column-end:9}.sm\:cu-col-end-10{grid-column-end:10}.sm\:cu-col-end-11{grid-column-end:11}.sm\:cu-col-end-12{grid-column-end:12}.sm\:cu-col-end-13{grid-column-end:13}.sm\:cu-col-end-auto{grid-column-end:auto}.sm\:cu-row-span-1{grid-row:span 1/span 1}.sm\:cu-row-span-2{grid-row:span 2/span 2}.sm\:cu-row-span-3{grid-row:span 3/span 3}.sm\:cu-row-span-4{grid-row:span 4/span 4}.sm\:cu-row-span-5{grid-row:span 5/span 5}.sm\:cu-row-span-6{grid-row:span 6/span 6}.sm\:cu-row-span-7{grid-row:span 7/span 7}.sm\:cu-row-span-8{grid-row:span 8/span 8}.sm\:cu-row-span-9{grid-row:span 9/span 9}.sm\:cu-row-span-10{grid-row:span 10/span 10}.sm\:cu-row-span-11{grid-row:span 11/span 11}.sm\:cu-row-span-12{grid-row:span 12/span 12}.sm\:cu-row-span-auto{grid-row:span auto/span auto}}@media (width>=1024px){.md\:cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.md\:cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.md\:cu-col-span-1{grid-column:span 1/span 1}.md\:cu-col-span-2{grid-column:span 2/span 2}.md\:cu-col-span-3{grid-column:span 3/span 3}.md\:cu-col-span-4{grid-column:span 4/span 4}.md\:cu-col-span-5{grid-column:span 5/span 5}.md\:cu-col-span-6{grid-column:span 6/span 6}.md\:cu-col-span-7{grid-column:span 7/span 7}.md\:cu-col-span-8{grid-column:span 8/span 8}.md\:cu-col-span-9{grid-column:span 9/span 9}.md\:cu-col-span-10{grid-column:span 10/span 10}.md\:cu-col-span-11{grid-column:span 11/span 11}.md\:cu-col-span-12{grid-column:span 12/span 12}.md\:cu-col-span-auto{grid-column:span auto/span auto}.md\:cu-col-start-1{grid-column-start:1}.md\:cu-col-start-2{grid-column-start:2}.md\:cu-col-start-3{grid-column-start:3}.md\:cu-col-start-4{grid-column-start:4}.md\:cu-col-start-5{grid-column-start:5}.md\:cu-col-start-6{grid-column-start:6}.md\:cu-col-start-7{grid-column-start:7}.md\:cu-col-start-8{grid-column-start:8}.md\:cu-col-start-9{grid-column-start:9}.md\:cu-col-start-10{grid-column-start:10}.md\:cu-col-start-11{grid-column-start:11}.md\:cu-col-start-12{grid-column-start:12}.md\:cu-col-start-13{grid-column-start:13}.md\:cu-col-start-auto{grid-column-start:auto}.md\:cu-col-end-1{grid-column-end:1}.md\:cu-col-end-2{grid-column-end:2}.md\:cu-col-end-3{grid-column-end:3}.md\:cu-col-end-4{grid-column-end:4}.md\:cu-col-end-5{grid-column-end:5}.md\:cu-col-end-6{grid-column-end:6}.md\:cu-col-end-7{grid-column-end:7}.md\:cu-col-end-8{grid-column-end:8}.md\:cu-col-end-9{grid-column-end:9}.md\:cu-col-end-10{grid-column-end:10}.md\:cu-col-end-11{grid-column-end:11}.md\:cu-col-end-12{grid-column-end:12}.md\:cu-col-end-13{grid-column-end:13}.md\:cu-col-end-auto{grid-column-end:auto}.md\:cu-row-span-1{grid-row:span 1/span 1}.md\:cu-row-span-2{grid-row:span 2/span 2}.md\:cu-row-span-3{grid-row:span 3/span 3}.md\:cu-row-span-4{grid-row:span 4/span 4}.md\:cu-row-span-5{grid-row:span 5/span 5}.md\:cu-row-span-6{grid-row:span 6/span 6}.md\:cu-row-span-7{grid-row:span 7/span 7}.md\:cu-row-span-8{grid-row:span 8/span 8}.md\:cu-row-span-9{grid-row:span 9/span 9}.md\:cu-row-span-10{grid-row:span 10/span 10}.md\:cu-row-span-11{grid-row:span 11/span 11}.md\:cu-row-span-12{grid-row:span 12/span 12}.md\:cu-row-span-auto{grid-row:span auto/span auto}}@media (width>=1280px){.lg\:cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.lg\:cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.lg\:cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.lg\:cu-col-span-1{grid-column:span 1/span 1}.lg\:cu-col-span-2{grid-column:span 2/span 2}.lg\:cu-col-span-3{grid-column:span 3/span 3}.lg\:cu-col-span-4{grid-column:span 4/span 4}.lg\:cu-col-span-5{grid-column:span 5/span 5}.lg\:cu-col-span-6{grid-column:span 6/span 6}.lg\:cu-col-span-7{grid-column:span 7/span 7}.lg\:cu-col-span-8{grid-column:span 8/span 8}.lg\:cu-col-span-9{grid-column:span 9/span 9}.lg\:cu-col-span-10{grid-column:span 10/span 10}.lg\:cu-col-span-11{grid-column:span 11/span 11}.lg\:cu-col-span-12{grid-column:span 12/span 12}.lg\:cu-col-span-auto{grid-column:span auto/span auto}.lg\:cu-col-start-1{grid-column-start:1}.lg\:cu-col-start-2{grid-column-start:2}.lg\:cu-col-start-3{grid-column-start:3}.lg\:cu-col-start-4{grid-column-start:4}.lg\:cu-col-start-5{grid-column-start:5}.lg\:cu-col-start-6{grid-column-start:6}.lg\:cu-col-start-7{grid-column-start:7}.lg\:cu-col-start-8{grid-column-start:8}.lg\:cu-col-start-9{grid-column-start:9}.lg\:cu-col-start-10{grid-column-start:10}.lg\:cu-col-start-11{grid-column-start:11}.lg\:cu-col-start-12{grid-column-start:12}.lg\:cu-col-start-13{grid-column-start:13}.lg\:cu-col-start-auto{grid-column-start:auto}.lg\:cu-col-end-1{grid-column-end:1}.lg\:cu-col-end-2{grid-column-end:2}.lg\:cu-col-end-3{grid-column-end:3}.lg\:cu-col-end-4{grid-column-end:4}.lg\:cu-col-end-5{grid-column-end:5}.lg\:cu-col-end-6{grid-column-end:6}.lg\:cu-col-end-7{grid-column-end:7}.lg\:cu-col-end-8{grid-column-end:8}.lg\:cu-col-end-9{grid-column-end:9}.lg\:cu-col-end-10{grid-column-end:10}.lg\:cu-col-end-11{grid-column-end:11}.lg\:cu-col-end-12{grid-column-end:12}.lg\:cu-col-end-13{grid-column-end:13}.lg\:cu-col-end-auto{grid-column-end:auto}.lg\:cu-row-span-1{grid-row:span 1/span 1}.lg\:cu-row-span-2{grid-row:span 2/span 2}.lg\:cu-row-span-3{grid-row:span 3/span 3}.lg\:cu-row-span-4{grid-row:span 4/span 4}.lg\:cu-row-span-5{grid-row:span 5/span 5}.lg\:cu-row-span-6{grid-row:span 6/span 6}.lg\:cu-row-span-7{grid-row:span 7/span 7}.lg\:cu-row-span-8{grid-row:span 8/span 8}.lg\:cu-row-span-9{grid-row:span 9/span 9}.lg\:cu-row-span-10{grid-row:span 10/span 10}.lg\:cu-row-span-11{grid-row:span 11/span 11}.lg\:cu-row-span-12{grid-row:span 12/span 12}.lg\:cu-row-span-auto{grid-row:span auto/span auto}}@media (width>=1536px){.xl\:cu-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:cu-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:cu-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:cu-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:cu-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:cu-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:cu-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xl\:cu-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xl\:cu-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xl\:cu-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:cu-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xl\:cu-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:cu-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xl\:cu-col-span-1{grid-column:span 1/span 1}.xl\:cu-col-span-2{grid-column:span 2/span 2}.xl\:cu-col-span-3{grid-column:span 3/span 3}.xl\:cu-col-span-4{grid-column:span 4/span 4}.xl\:cu-col-span-5{grid-column:span 5/span 5}.xl\:cu-col-span-6{grid-column:span 6/span 6}.xl\:cu-col-span-7{grid-column:span 7/span 7}.xl\:cu-col-span-8{grid-column:span 8/span 8}.xl\:cu-col-span-9{grid-column:span 9/span 9}.xl\:cu-col-span-10{grid-column:span 10/span 10}.xl\:cu-col-span-11{grid-column:span 11/span 11}.xl\:cu-col-span-12{grid-column:span 12/span 12}.xl\:cu-col-span-auto{grid-column:span auto/span auto}.xl\:cu-col-start-1{grid-column-start:1}.xl\:cu-col-start-2{grid-column-start:2}.xl\:cu-col-start-3{grid-column-start:3}.xl\:cu-col-start-4{grid-column-start:4}.xl\:cu-col-start-5{grid-column-start:5}.xl\:cu-col-start-6{grid-column-start:6}.xl\:cu-col-start-7{grid-column-start:7}.xl\:cu-col-start-8{grid-column-start:8}.xl\:cu-col-start-9{grid-column-start:9}.xl\:cu-col-start-10{grid-column-start:10}.xl\:cu-col-start-11{grid-column-start:11}.xl\:cu-col-start-12{grid-column-start:12}.xl\:cu-col-start-13{grid-column-start:13}.xl\:cu-col-start-auto{grid-column-start:auto}.xl\:cu-col-end-1{grid-column-end:1}.xl\:cu-col-end-2{grid-column-end:2}.xl\:cu-col-end-3{grid-column-end:3}.xl\:cu-col-end-4{grid-column-end:4}.xl\:cu-col-end-5{grid-column-end:5}.xl\:cu-col-end-6{grid-column-end:6}.xl\:cu-col-end-7{grid-column-end:7}.xl\:cu-col-end-8{grid-column-end:8}.xl\:cu-col-end-9{grid-column-end:9}.xl\:cu-col-end-10{grid-column-end:10}.xl\:cu-col-end-11{grid-column-end:11}.xl\:cu-col-end-12{grid-column-end:12}.xl\:cu-col-end-13{grid-column-end:13}.xl\:cu-col-end-auto{grid-column-end:auto}.xl\:cu-row-span-1{grid-row:span 1/span 1}.xl\:cu-row-span-2{grid-row:span 2/span 2}.xl\:cu-row-span-3{grid-row:span 3/span 3}.xl\:cu-row-span-4{grid-row:span 4/span 4}.xl\:cu-row-span-5{grid-row:span 5/span 5}.xl\:cu-row-span-6{grid-row:span 6/span 6}.xl\:cu-row-span-7{grid-row:span 7/span 7}.xl\:cu-row-span-8{grid-row:span 8/span 8}.xl\:cu-row-span-9{grid-row:span 9/span 9}.xl\:cu-row-span-10{grid-row:span 10/span 10}.xl\:cu-row-span-11{grid-row:span 11/span 11}.xl\:cu-row-span-12{grid-row:span 12/span 12}.xl\:cu-row-span-auto{grid-row:span auto/span auto}}.cu-gap{gap:var(--gap)}.cu-gap-0\.5{gap:var(--canon-space-0_5)}.cu-gap-1{gap:var(--canon-space-1)}.cu-gap-1\.5{gap:var(--canon-space-1_5)}.cu-gap-2{gap:var(--canon-space-2)}.cu-gap-3{gap:var(--canon-space-3)}.cu-gap-4{gap:var(--canon-space-4)}.cu-gap-5{gap:var(--canon-space-5)}.cu-gap-6{gap:var(--canon-space-6)}.cu-gap-7{gap:var(--canon-space-7)}.cu-gap-8{gap:var(--canon-space-8)}.cu-gap-9{gap:var(--canon-space-9)}.cu-gap-10{gap:var(--canon-space-10)}.cu-gap-11{gap:var(--canon-space-11)}.cu-gap-12{gap:var(--canon-space-12)}.cu-gap-13{gap:var(--canon-space-13)}.cu-gap-14{gap:var(--canon-space-14)}@media (width>=640px){.xs\:cu-gap{gap:var(--gap-xs)}.xs\:cu-gap-0\.5{gap:var(--canon-space-0_5)}.xs\:cu-gap-1{gap:var(--canon-space-1)}.xs\:cu-gap-1\.5{gap:var(--canon-space-1_5)}.xs\:cu-gap-2{gap:var(--canon-space-2)}.xs\:cu-gap-3{gap:var(--canon-space-3)}.xs\:cu-gap-4{gap:var(--canon-space-4)}.xs\:cu-gap-5{gap:var(--canon-space-5)}.xs\:cu-gap-6{gap:var(--canon-space-6)}.xs\:cu-gap-7{gap:var(--canon-space-7)}.xs\:cu-gap-8{gap:var(--canon-space-8)}.xs\:cu-gap-9{gap:var(--canon-space-9)}.xs\:cu-gap-10{gap:var(--canon-space-10)}.xs\:cu-gap-11{gap:var(--canon-space-11)}.xs\:cu-gap-12{gap:var(--canon-space-12)}.xs\:cu-gap-13{gap:var(--canon-space-13)}.xs\:cu-gap-14{gap:var(--canon-space-14)}}@media (width>=768px){.sm\:cu-gap{gap:var(--gap-sm)}.sm\:cu-gap-0\.5{gap:var(--canon-space-0_5)}.sm\:cu-gap-1{gap:var(--canon-space-1)}.sm\:cu-gap-1\.5{gap:var(--canon-space-1_5)}.sm\:cu-gap-2{gap:var(--canon-space-2)}.sm\:cu-gap-3{gap:var(--canon-space-3)}.sm\:cu-gap-4{gap:var(--canon-space-4)}.sm\:cu-gap-5{gap:var(--canon-space-5)}.sm\:cu-gap-6{gap:var(--canon-space-6)}.sm\:cu-gap-7{gap:var(--canon-space-7)}.sm\:cu-gap-8{gap:var(--canon-space-8)}.sm\:cu-gap-9{gap:var(--canon-space-9)}.sm\:cu-gap-10{gap:var(--canon-space-10)}.sm\:cu-gap-11{gap:var(--canon-space-11)}.sm\:cu-gap-12{gap:var(--canon-space-12)}.sm\:cu-gap-13{gap:var(--canon-space-13)}.sm\:cu-gap-14{gap:var(--canon-space-14)}}@media (width>=1024px){.md\:cu-gap{gap:var(--gap-md)}.md\:cu-gap-0\.5{gap:var(--canon-space-0_5)}.md\:cu-gap-1{gap:var(--canon-space-1)}.md\:cu-gap-1\.5{gap:var(--canon-space-1_5)}.md\:cu-gap-2{gap:var(--canon-space-2)}.md\:cu-gap-3{gap:var(--canon-space-3)}.md\:cu-gap-4{gap:var(--canon-space-4)}.md\:cu-gap-5{gap:var(--canon-space-5)}.md\:cu-gap-6{gap:var(--canon-space-6)}.md\:cu-gap-7{gap:var(--canon-space-7)}.md\:cu-gap-8{gap:var(--canon-space-8)}.md\:cu-gap-9{gap:var(--canon-space-9)}.md\:cu-gap-10{gap:var(--canon-space-10)}.md\:cu-gap-11{gap:var(--canon-space-11)}.md\:cu-gap-12{gap:var(--canon-space-12)}.md\:cu-gap-13{gap:var(--canon-space-13)}.md\:cu-gap-14{gap:var(--canon-space-14)}}@media (width>=1280px){.lg\:cu-gap{gap:var(--gap-lg)}.lg\:cu-gap-0\.5{gap:var(--canon-space-0_5)}.lg\:cu-gap-1{gap:var(--canon-space-1)}.lg\:cu-gap-1\.5{gap:var(--canon-space-1_5)}.lg\:cu-gap-2{gap:var(--canon-space-2)}.lg\:cu-gap-3{gap:var(--canon-space-3)}.lg\:cu-gap-4{gap:var(--canon-space-4)}.lg\:cu-gap-5{gap:var(--canon-space-5)}.lg\:cu-gap-6{gap:var(--canon-space-6)}.lg\:cu-gap-7{gap:var(--canon-space-7)}.lg\:cu-gap-8{gap:var(--canon-space-8)}.lg\:cu-gap-9{gap:var(--canon-space-9)}.lg\:cu-gap-10{gap:var(--canon-space-10)}.lg\:cu-gap-11{gap:var(--canon-space-11)}.lg\:cu-gap-12{gap:var(--canon-space-12)}.lg\:cu-gap-13{gap:var(--canon-space-13)}.lg\:cu-gap-14{gap:var(--canon-space-14)}}@media (width>=1536px){.xl\:cu-gap{gap:var(--gap-xl)}.xl\:cu-gap-0\.5{gap:var(--canon-space-0_5)}.xl\:cu-gap-1{gap:var(--canon-space-1)}.xl\:cu-gap-1\.5{gap:var(--canon-space-1_5)}.xl\:cu-gap-2{gap:var(--canon-space-2)}.xl\:cu-gap-3{gap:var(--canon-space-3)}.xl\:cu-gap-4{gap:var(--canon-space-4)}.xl\:cu-gap-5{gap:var(--canon-space-5)}.xl\:cu-gap-6{gap:var(--canon-space-6)}.xl\:cu-gap-7{gap:var(--canon-space-7)}.xl\:cu-gap-8{gap:var(--canon-space-8)}.xl\:cu-gap-9{gap:var(--canon-space-9)}.xl\:cu-gap-10{gap:var(--canon-space-10)}.xl\:cu-gap-11{gap:var(--canon-space-11)}.xl\:cu-gap-12{gap:var(--canon-space-12)}.xl\:cu-gap-13{gap:var(--canon-space-13)}.xl\:cu-gap-14{gap:var(--canon-space-14)}}.cu-align-start{align-items:start}.cu-align-center{align-items:center}.cu-align-end{align-items:end}.cu-align-stretch{align-items:stretch}.cu-jc-start{justify-content:start}.cu-jc-center{justify-content:center}.cu-jc-end{justify-content:end}.cu-jc-between{justify-content:space-between}.cu-fd-row{flex-direction:row}.cu-fd-column{flex-direction:column}.cu-fd-row-reverse{flex-direction:row-reverse}.cu-fd-column-reverse{flex-direction:column-reverse}@media (width>=640px){.xs\:cu-align-start{align-items:start}.xs\:cu-align-center{align-items:center}.xs\:cu-align-end{align-items:end}.xs\:cu-align-stretch{align-items:stretch}.xs\:cu-jc-start{justify-content:start}.xs\:cu-jc-center{justify-content:center}.xs\:cu-jc-end{justify-content:end}.xs\:cu-jc-between{justify-content:space-between}.xs\:cu-fd-row{flex-direction:row}.xs\:cu-fd-column{flex-direction:column}.xs\:cu-fd-row-reverse{flex-direction:row-reverse}.xs\:cu-fd-column-reverse{flex-direction:column-reverse}}@media (width>=768px){.sm\:cu-align-start{align-items:start}.sm\:cu-align-center{align-items:center}.sm\:cu-align-end{align-items:end}.sm\:cu-align-stretch{align-items:stretch}.sm\:cu-jc-start{justify-content:start}.sm\:cu-jc-center{justify-content:center}.sm\:cu-jc-end{justify-content:end}.sm\:cu-jc-between{justify-content:space-between}.sm\:cu-fd-row{flex-direction:row}.sm\:cu-fd-column{flex-direction:column}.sm\:cu-fd-row-reverse{flex-direction:row-reverse}.sm\:cu-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1024px){.md\:cu-align-start{align-items:start}.md\:cu-align-center{align-items:center}.md\:cu-align-end{align-items:end}.md\:cu-align-stretch{align-items:stretch}.md\:cu-jc-start{justify-content:start}.md\:cu-jc-center{justify-content:center}.md\:cu-jc-end{justify-content:end}.md\:cu-jc-between{justify-content:space-between}.md\:cu-fd-row{flex-direction:row}.md\:cu-fd-column{flex-direction:column}.md\:cu-fd-row-reverse{flex-direction:row-reverse}.md\:cu-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1280px){.lg\:cu-align-start{align-items:start}.lg\:cu-align-center{align-items:center}.lg\:cu-align-end{align-items:end}.lg\:cu-align-stretch{align-items:stretch}.lg\:cu-jc-start{justify-content:start}.lg\:cu-jc-center{justify-content:center}.lg\:cu-jc-end{justify-content:end}.lg\:cu-jc-between{justify-content:space-between}.lg\:cu-fd-row{flex-direction:row}.lg\:cu-fd-column{flex-direction:column}.lg\:cu-fd-row-reverse{flex-direction:row-reverse}.lg\:cu-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1536px){.xl\:cu-align-start{align-items:start}.xl\:cu-align-center{align-items:center}.xl\:cu-align-end{align-items:end}.xl\:cu-align-stretch{align-items:stretch}.xl\:cu-jc-start{justify-content:start}.xl\:cu-jc-center{justify-content:center}.xl\:cu-jc-end{justify-content:end}.xl\:cu-jc-between{justify-content:space-between}.xl\:cu-fd-row{flex-direction:row}.xl\:cu-fd-column{flex-direction:column}.xl\:cu-fd-row-reverse{flex-direction:row-reverse}.xl\:cu-fd-column-reverse{flex-direction:column-reverse}}:where(a){color:inherit;text-decoration:none}:root{--canon-font-regular:system-ui;--canon-font-monospace:ui-monospace,"Menlo","Monaco","Consolas","Liberation Mono","Courier New",monospace;--canon-font-weight-regular:400;--canon-font-weight-bold:600;--canon-font-size-1:.625rem;--canon-font-size-2:.75rem;--canon-font-size-3:.875rem;--canon-font-size-4:1rem;--canon-font-size-5:1.25rem;--canon-font-size-6:1.5rem;--canon-font-size-7:2rem;--canon-font-size-8:3rem;--canon-font-size-9:4rem;--canon-font-size-10:5.75rem;--canon-space:.25rem;--canon-space-0_5:calc(var(--canon-space)*.5);--canon-space-1:var(--canon-space);--canon-space-1_5:calc(var(--canon-space)*1.5);--canon-space-2:calc(var(--canon-space)*2);--canon-space-3:calc(var(--canon-space)*3);--canon-space-4:calc(var(--canon-space)*4);--canon-space-5:calc(var(--canon-space)*5);--canon-space-6:calc(var(--canon-space)*6);--canon-space-7:calc(var(--canon-space)*7);--canon-space-8:calc(var(--canon-space)*8);--canon-space-9:calc(var(--canon-space)*9);--canon-space-10:calc(var(--canon-space)*10);--canon-space-11:calc(var(--canon-space)*11);--canon-space-12:calc(var(--canon-space)*12);--canon-space-13:calc(var(--canon-space)*13);--canon-space-14:calc(var(--canon-space)*14);--canon-radius-1:calc(.125rem);--canon-radius-2:calc(.25rem);--canon-radius-3:calc(.5rem);--canon-radius-4:calc(.75rem);--canon-radius-5:calc(1rem);--canon-radius-6:calc(1.25rem);--canon-radius-full:9999px;--canon-black:#000;--canon-white:#fff;--canon-gray-1:#f8f8f8;--canon-gray-2:#ececec;--canon-gray-3:#d9d9d9;--canon-gray-4:#c1c1c1;--canon-gray-5:#9e9e9e;--canon-gray-6:#8c8c8c;--canon-gray-7:#757575;--canon-gray-8:#595959;--canon-bg:var(--canon-gray-1);--canon-bg-surface-1:var(--canon-white);--canon-bg-surface-2:var(--canon-gray-2);--canon-bg-solid:#1f5493;--canon-bg-solid-hover:#163a66;--canon-bg-solid-pressed:#0f2b4e;--canon-bg-solid-disabled:#ebebeb;--canon-bg-tint:transparent;--canon-bg-tint-hover:#1f549366;--canon-bg-tint-pressed:#1f549399;--canon-bg-tint-disabled:#ebebeb;--canon-bg-danger:#feebe7;--canon-bg-warning:#fff2b2;--canon-bg-success:#e6f6eb;--canon-fg-primary:var(--canon-black);--canon-fg-secondary:var(--canon-gray-7);--canon-fg-link:#1f5493;--canon-fg-link-hover:#1f2d5c;--canon-fg-disabled:#9e9e9e;--canon-fg-solid:var(--canon-white);--canon-fg-solid-disabled:#9c9c9c;--canon-fg-tint:#1f5493;--canon-fg-tint-disabled:var(--canon-gray-5);--canon-fg-danger:#e22b2b;--canon-fg-warning:#e36d05;--canon-fg-success:#1db954;--canon-border:#0000001a;--canon-border-hover:#0003;--canon-border-pressed:#0006;--canon-border-disabled:#0000001a;--canon-border-danger:#f87a7a;--canon-border-warning:#e36d05;--canon-border-success:#53db83;--canon-ring:#1f5493;--canon-scrollbar:#a0a0a03b;--canon-scrollbar-thumb:#a0a0a0}[data-theme=dark]{--canon-gray-1:#191919;--canon-gray-2:#242424;--canon-gray-3:#373737;--canon-gray-4:#464646;--canon-gray-5:#575757;--canon-gray-6:#7b7b7b;--canon-gray-7:#9e9e9e;--canon-gray-8:#b4b4b4;--canon-bg:var(--canon-black);--canon-bg-surface-1:var(--canon-gray-1);--canon-bg-surface-2:var(--canon-gray-2);--canon-bg-solid:#9cc9ff;--canon-bg-solid-hover:#83b9fd;--canon-bg-solid-pressed:#83b9fd;--canon-bg-solid-disabled:#222;--canon-bg-tint:transparent;--canon-bg-tint-hover:#9cc9ff1f;--canon-bg-tint-pressed:#9cc9ff29;--canon-bg-tint-disabled:transparent;--canon-bg-danger:#3b1219;--canon-bg-warning:#302008;--canon-bg-success:#132d21;--canon-fg-primary:var(--canon-white);--canon-fg-secondary:var(--canon-gray-7);--canon-fg-link:#9cc9ff;--canon-fg-link-hover:#7eb5f7;--canon-fg-disabled:var(--canon-gray-7);--canon-fg-solid:#101821;--canon-fg-solid-disabled:var(--canon-gray-5);--canon-fg-tint:#9cc9ff;--canon-fg-tint-disabled:var(--canon-gray-5);--canon-fg-danger:#e22b2b;--canon-fg-warning:#e36d05;--canon-fg-success:#1db954;--canon-border:#fff3;--canon-border-hover:#fff6;--canon-border-pressed:#ffffff80;--canon-border-disabled:#fff3;--canon-border-danger:#f87a7a;--canon-border-warning:#e36d05;--canon-border-success:#53db83;--canon-ring:#1f5493;--canon-scrollbar:#3636363a;--canon-scrollbar-thumb:#575757}.canon-AvatarRoot{vertical-align:middle;user-select:none;color:var(--canon-fg-primary);background-color:var(--canon-bg-surface-2);border-radius:100%;justify-content:center;align-items:center;width:2rem;height:2rem;font-size:1rem;font-weight:500;line-height:1;display:inline-flex;overflow:hidden}.canon-AvatarRoot[data-size=small]{width:1.5rem;height:1.5rem}.canon-AvatarRoot[data-size=medium]{width:2rem;height:2rem}.canon-AvatarRoot[data-size=large]{width:3rem;height:3rem}.canon-AvatarImage{object-fit:cover;width:100%;height:100%}.canon-AvatarFallback{width:100%;height:100%;font-size:var(--canon-font-size-3);font-weight:var(--canon-font-weight-regular);box-shadow:inset 0 0 0 1px var(--canon-border);border-radius:var(--canon-radius-full);justify-content:center;align-items:center;display:flex}.canon-Box{font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-primary)}.canon-Button{user-select:none;font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-bold);cursor:pointer;border-radius:var(--canon-radius-2);justify-content:center;align-items:center;gap:var(--canon-space-1_5);border:none;padding:0;display:inline-flex;&:disabled{cursor:not-allowed}}.canon-Button[data-variant=primary]{background-color:var(--canon-bg-solid);color:var(--canon-fg-solid);transition:background-color .15s,box-shadow .15s;&:hover{background-color:var(--canon-bg-solid-hover)}&:active{background-color:var(--canon-bg-solid-pressed)}&:focus-visible{outline:2px solid var(--canon-ring);outline-offset:2px}&:disabled{background-color:var(--canon-bg-solid-disabled);color:var(--canon-fg-solid-disabled)}}.canon-Button[data-variant=secondary]{background-color:var(--canon-bg-surface-1);box-shadow:inset 0 0 0 1px var(--canon-border);color:var(--canon-fg-primary);transition:box-shadow .15s;&:hover{box-shadow:inset 0 0 0 1px var(--canon-border-hover)}&:active{box-shadow:inset 0 0 0 1px var(--canon-border-pressed)}&:focus-visible{box-shadow:inset 0 0 0 2px var(--canon-ring);outline:none;transition:none}&:disabled{box-shadow:inset 0 0 0 1px var(--canon-border-disabled);color:var(--canon-fg-disabled)}}.canon-Button[data-size=medium]{font-size:var(--canon-font-size-4);padding:0 var(--canon-space-3);height:2.5rem}.canon-Button[data-size=small]{font-size:var(--canon-font-size-3);padding:0 var(--canon-space-2);height:2rem}.canon-Button[data-size=small] svg{width:1rem;height:1rem}.canon-Button[data-size=medium] svg{width:1.25rem;height:1.25rem}.canon-ButtonIcon{justify-content:center;align-items:center}.canon-ButtonIcon[data-size=small]{width:2rem;padding:0}.canon-ButtonIcon[data-size=medium]{width:2.5rem;padding:0}.canon-CheckboxRoot{width:1rem;height:1rem;box-shadow:inset 0 0 0 1px var(--canon-border);cursor:pointer;background-color:var(--canon-bg-surface-1);border:none;border-radius:2px;flex-shrink:0;justify-content:center;align-items:center;padding:0;transition:background-color .2s ease-in-out;display:flex}.canon-CheckboxRoot:focus-visible{outline:2px solid var(--canon-ring);outline-offset:2px;transition:none}.canon-CheckboxRoot[data-checked]{background-color:var(--canon-bg-solid);box-shadow:none;color:var(--canon-fg-solid)}.canon-CheckboxLabel{align-items:center;gap:var(--canon-space-2);font-size:var(--canon-font-size-3);font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-primary);user-select:none;flex-direction:row;display:flex;&:hover{& .canon-CheckboxRoot:not([data-checked]){box-shadow:inset 0 0 0 1px var(--canon-border-hover)}}}.canon-CheckboxIndicator{color:var(--canon-fg-solid);justify-content:center;align-items:center;display:flex}.canon-CollapsiblePanel{height:var(--collapsible-panel-height);transition:all .15s ease-out;display:flex;overflow:hidden;&[data-starting-style],&[data-ending-style]{height:0}}.canon-Container{max-width:120rem;padding-inline:var(--canon-space-4);margin-inline:auto;transition:padding .2s ease-in-out}@media (width>=640px){.canon-Container{padding-inline:var(--canon-space-8)}}@media (width>=1024px){.canon-Container{padding-inline:var(--canon-space-12)}}.canon-DataTableRoot{gap:var(--canon-space-3);flex-direction:column;display:flex}.canon-DataTablePagination{padding-top:var(--canon-space-5);justify-content:space-between;align-items:center;display:flex}.canon-DataTablePagination--left{justify-content:space-between;align-items:center;display:flex}.canon-DataTablePagination--right{justify-content:space-between;align-items:center;gap:var(--canon-space-2);display:flex}.canon-DataTablePagination--select{min-width:10.5rem}.canon-FieldLabel{margin-bottom:var(--canon-space-3);gap:var(--canon-space-1);flex-direction:column;display:flex}.canon-FieldLabelLabel{color:var(--canon-fg-primary);cursor:pointer;font-weight:var(--canon-font-weight-regular);font-size:var(--canon-font-size-2);margin-right:auto}.canon-FieldLabelSecondaryLabel{color:var(--canon-fg-secondary);font-weight:var(--canon-font-weight-regular);margin-left:var(--canon-space-1)}.canon-FieldLabelDescription{font-weight:var(--canon-font-weight-regular);font-size:var(--canon-font-size-2);color:var(--canon-fg-secondary);margin:0}.canon-Flex{min-width:0;display:flex}.canon-Grid{display:grid}.canon-Heading{font-family:var(--canon-font-regular);color:var(--canon-fg-primary);margin:0;padding:0;line-height:100%}.canon-Heading[data-variant=display]{font-size:var(--canon-font-size-10);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-variant=title1]{font-size:var(--canon-font-size-9);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-variant=title2]{font-size:var(--canon-font-size-8);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-variant=title3]{font-size:var(--canon-font-size-7);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-variant=title4]{font-size:var(--canon-font-size-6);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-variant=title5]{font-size:var(--canon-font-size-5);font-weight:var(--canon-font-weight-bold)}.canon-Heading[data-color=primary]{color:var(--canon-fg-primary)}.canon-Heading[data-color=secondary]{color:var(--canon-fg-secondary)}.canon-Heading[data-truncate]{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.canon-Icon{width:1rem;height:1rem}.canon-Link{font-family:var(--canon-font-regular);color:var(--canon-fg-link);cursor:pointer;margin:0;padding:0;text-decoration-line:none;&:hover{color:var(--canon-fg-link-hover);text-underline-offset:calc(.025em + 2px);text-decoration-line:underline;text-decoration-style:solid;text-decoration-thickness:min(2px,max(1px,.05em));text-decoration-color:color-mix(in srgb,var(--canon-fg-link-hover)30%,transparent)}}.canon-Link[data-variant=body]{font-size:var(--canon-font-size-3);line-height:140%}.canon-Link[data-variant=subtitle]{font-size:var(--canon-font-size-4);line-height:140%}.canon-Link[data-variant=caption]{font-size:var(--canon-font-size-2);line-height:140%}.canon-Link[data-variant=label]{font-size:var(--canon-font-size-1);line-height:140%}.canon-Link[data-weight=regular]{font-weight:var(--canon-font-weight-regular)}.canon-Link[data-weight=bold]{font-weight:var(--canon-font-weight-bold)}.canon-MenuPositioner{outline:0}.canon-MenuPopup{background-color:var(--canon-bg-surface-1);border:1px solid var(--canon-border);color:var(--canon-fg-primary);transform-origin:var(--transform-origin);max-width:min(var(--available-width),340px);max-height:min(var(--available-height),500px);padding-bottom:var(--canon-space-1);border-radius:.375rem;outline:none;flex-direction:column;transition:transform .15s,opacity .15s;display:flex;position:relative;overflow:auto;&[data-starting-style],&[data-ending-style]{opacity:0;transform:scale(.9)}}.canon-MenuItem{user-select:none;align-items:center;gap:var(--canon-space-2);height:32px;color:var(--canon-fg-primary);border-radius:var(--canon-radius-2);margin-inline:var(--canon-space-1);padding-inline:var(--canon-space-2);font-size:var(--canon-font-size-3);cursor:pointer;outline:0;flex-shrink:0;text-decoration:none;display:flex;&:first-child{margin-top:var(--canon-space-1)}&[data-highlighted]{background-color:var(--canon-gray-3)}}.canon-MenuSubmenuTrigger{user-select:none;justify-content:space-between;align-items:center;gap:var(--canon-space-2);height:32px;color:var(--canon-fg-primary);border-radius:var(--canon-radius-2);margin-inline:var(--canon-space-1);padding-inline:var(--canon-space-2);font-size:var(--canon-font-size-3);cursor:pointer;outline:0;flex-shrink:0;text-decoration:none;display:flex;& .canon-Icon{color:var(--canon-fg-secondary)}&:first-child{margin-top:var(--canon-space-1)}&[data-popup-open],&[data-highlighted]{background-color:var(--canon-gray-3);& .canon-Icon{color:var(--canon-fg-primary)}}}.canon-MenuSeparator{background-color:var(--color-gray-200);height:1px;margin:.375rem 1rem}.canon-SubmenuComboboxSearch{padding-inline:var(--canon-space-3);border:none;border-bottom:1px solid var(--canon-border);background-color:var(--canon-bg-surface-1);width:100%;height:32px;color:var(--canon-fg-primary);line-height:140%;font-size:var(--canon-font-size-3);z-index:1;outline:none;position:sticky;top:0;&::placeholder{color:var(--canon-fg-secondary)}&:disabled{opacity:.6;cursor:not-allowed}}.canon-SubmenuComboboxItems{padding-top:var(--canon-space-2);outline:none;flex-direction:column;display:flex;overflow-y:auto}.canon-SubmenuComboboxNoResults{padding-inline:var(--canon-space-3);padding-top:var(--canon-space-2);padding-bottom:var(--canon-space-4);color:var(--canon-fg-secondary);font-size:var(--canon-font-size-3)}.canon-SubmenuComboboxItem{user-select:none;justify-content:space-between;align-items:center;gap:var(--canon-space-2);height:32px;color:var(--canon-fg-primary);border-radius:var(--canon-radius-2);margin-inline:var(--canon-space-1);padding-inline:var(--canon-space-2);font-size:var(--canon-font-size-3);cursor:pointer;outline:0;flex-shrink:0;text-decoration:none;display:flex;&[data-highlighted]{background-color:var(--canon-gray-3)}&[data-disabled]{opacity:.5;cursor:not-allowed}}.canon-SubmenuComboboxItemCheckbox{width:16px;height:16px;color:var(--canon-fg-primary);border-radius:var(--canon-radius-2);border:1px solid var(--canon-border);background:var(--canon-bg-surface-1);flex-shrink:0;justify-content:center;align-items:center;display:flex}.canon-SubmenuComboboxItemLabel{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.canon-TableRoot{caption-side:bottom;border-collapse:collapse;width:100%}.canon-TableHead{text-align:left;padding:var(--canon-space-3);font-size:var(--canon-font-size-3);color:var(--canon-fg-primary)}.canon-TableBody{color:var(--canon-fg-primary)}.canon-TableRow{border-bottom:1px solid var(--canon-border);transition:color .2s ease-in-out}.canon-TableBody .canon-TableRow:hover{background-color:var(--canon-gray-2)}.canon-TableCell{padding:var(--canon-space-3);font-size:var(--canon-font-size-3)}.canon-TableCellText,.canon-TableCellLink{gap:var(--canon-space-0_5);flex-direction:column;display:flex}.canon-TableCellProfile{gap:var(--canon-space-2);flex-direction:row;align-items:center;display:flex}.canon-TableCellProfileAvatar{vertical-align:middle;user-select:none;color:var(--canon-fg-primary);background-color:var(--canon-bg-surface-2);border-radius:100%;justify-content:center;align-items:center;width:1.25rem;height:1.25rem;font-size:1rem;font-weight:500;line-height:1;display:inline-flex;overflow:hidden}.canon-TableCellProfileAvatarImage{object-fit:cover;width:100%;height:100%}.canon-TableCellProfileAvatarFallback{width:100%;height:100%;font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);box-shadow:inset 0 0 0 1px var(--canon-border);border-radius:var(--canon-radius-full);justify-content:center;align-items:center;display:flex}.canon-TabsRoot{border:1px solid var(--color-gray-200);border-radius:.375rem}.canon-TabsList{z-index:0;display:flex;position:relative}.canon-TabsTab{appearance:none;color:var(--canon-fg-secondary);user-select:none;padding-inline:var(--canon-space-3);height:2rem;font-family:inherit;font-size:.875rem;font-weight:500;line-height:1.25rem;font-size:var(--canon-font-size-2);cursor:pointer;background:0 0;border:0;outline:0;justify-content:center;align-items:center;margin:0;padding-block:0;transition:color .2s ease-in-out;display:flex;&[data-selected]{color:var(--canon-fg-primary)}@media (hover:hover){&:hover{color:var(--canon-fg-primary)}}&:focus-visible{position:relative;&:before{content:"";outline:1px solid var(--canon-ring);outline-offset:-1px;border-radius:.25rem;position:absolute;inset:.25rem 0}}}.canon-TabsIndicator{z-index:-1;translate:var(--active-tab-left)-50%;width:var(--active-tab-width);background-color:var(--canon-bg-solid);height:1px;transition-property:translate,width;transition-duration:.2s;transition-timing-function:ease-in-out;position:absolute;bottom:0;left:0}.canon-TabsPanel{&[hidden]{display:none}}.canon-Text{font-family:var(--canon-font-regular);margin:0;padding:0}.canon-Text[data-variant=body]{font-size:var(--canon-font-size-3);line-height:140%}.canon-Text[data-variant=subtitle]{font-size:var(--canon-font-size-4);line-height:140%}.canon-Text[data-variant=caption]{font-size:var(--canon-font-size-2);line-height:140%}.canon-Text[data-variant=label]{font-size:var(--canon-font-size-1);line-height:140%}.canon-Text[data-weight=regular]{font-weight:var(--canon-font-weight-regular)}.canon-Text[data-weight=bold]{font-weight:var(--canon-font-weight-bold)}.canon-Text[data-color=primary]{color:var(--canon-fg-primary)}.canon-Text[data-color=secondary]{color:var(--canon-fg-secondary)}.canon-Text[data-color=danger]{color:var(--canon-fg-danger)}.canon-Text[data-color=warning]{color:var(--canon-fg-warning)}.canon-Text[data-color=success]{color:var(--canon-fg-success)}.canon-Text[data-truncate]{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.canon-TextField{font-family:var(--canon-font-regular);flex-direction:column;width:100%;display:flex}.canon-TextFieldInputWrapper{position:relative}.canon-TextFieldInputWrapper[data-size=small]{height:2rem}.canon-TextFieldInputWrapper[data-size=medium]{height:2.5rem}.canon-TextFieldIcon{left:var(--canon-space-3);margin-right:var(--canon-space-1);color:var(--canon-fg-primary);flex-shrink:0;position:absolute;top:50%;transform:translateY(-50%)}.canon-TextFieldIcon[data-size=small],.canon-TextFieldIcon[data-size=small] svg{width:1rem;height:1rem}.canon-TextFieldIcon[data-size=medium],.canon-TextFieldIcon[data-size=medium] svg{width:1.25rem;height:1.25rem}.canon-TextFieldInput{padding:0 var(--canon-space-3);border-radius:var(--canon-radius-3);border:1px solid var(--canon-border);background-color:var(--canon-bg-surface-1);font-size:var(--canon-font-size-3);font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-primary);width:100%;height:100%;cursor:inherit;align-items:center;transition:border-color .2s ease-in-out,outline-color .2s ease-in-out;display:flex}.canon-TextFieldInput::placeholder{color:var(--canon-fg-secondary)}.canon-TextFieldInput[data-icon]{padding-left:var(--canon-space-8)}.canon-TextFieldInput[data-focused]{outline-color:var(--canon-border-pressed);outline-width:0}.canon-TextFieldInput[data-hovered]{border-color:var(--canon-border-hover)}.canon-TextFieldInput[data-focused]{border-color:var(--canon-border-pressed);outline-width:0}.canon-TextFieldInput[data-invalid]{border-color:var(--canon-fg-danger)}.canon-TextFieldInput[data-disabled]{opacity:.5;cursor:not-allowed;border:1px solid var(--canon-border-disabled)}.canon-TextFieldError{color:var(--canon-fg-danger);font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);margin-top:var(--canon-space-2)}.canon-TooltipPopup{box-sizing:border-box;transform-origin:var(--transform-origin);background-color:canvas;background-color:var(--canon-bg-surface-1);color:var(--canon-fg-primary);outline:1px solid var(--canon-border);box-shadow:0 10px 15px -3px var(--canon-border),0 4px 6px -4px var(--canon-border);border-radius:.375rem;flex-direction:column;padding:.25rem .5rem;font-size:.875rem;line-height:1.25rem;transition:transform .15s,opacity .15s;display:flex;&[data-starting-style],&[data-ending-style]{opacity:0;transform:scale(.9)}&[data-instant]{transition-duration:0s}}.canon-TooltipArrow{display:flex;&[data-side=top]{bottom:-8px;rotate:180deg}&[data-side=bottom]{top:-8px;rotate:none}&[data-side=left]{right:-13px;rotate:90deg}&[data-side=right]{left:-13px;rotate:-90deg}}.canon-TooltipArrow-fill{fill:var(--canon-bg-surface-1)}.canon-TooltipArrow-outer-stroke{@media (prefers-color-scheme:light){fill:var(--canon-border)}}.canon-ScrollAreaRoot{box-sizing:border-box;width:24rem;max-width:calc(100vw - 8rem);height:8.5rem}.canon-ScrollAreaViewport{overscroll-behavior:contain;height:100%}.canon-ScrollAreaContent{padding-block:.75rem;flex-direction:column;gap:1rem;padding-left:1rem;padding-right:1.5rem;display:flex}.canon-ScrollAreaScrollbar{background-color:var(--canon-scrollbar);opacity:0;border-radius:.375rem;justify-content:center;width:.25rem;margin:.5rem;transition:opacity .15s .3s;display:flex;&[data-hovering],&[data-scrolling]{opacity:1;transition-duration:75ms;transition-delay:0s}&:before{content:"";width:1.25rem;height:100%;position:absolute}}.canon-ScrollAreaThumb{border-radius:inherit;background-color:var(--canon-scrollbar-thumb);width:100%}.canon-Select{font-family:var(--canon-font-regular);flex-direction:column;width:100%;display:flex}.canon-SelectLabel{font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-primary);margin-bottom:var(--canon-space-1_5);cursor:pointer}.canon-SelectLabel[data-disabled]{cursor:default}.canon-SelectDescription{font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-secondary);padding-top:var(--canon-space-1_5);margin:0}.canon-SelectError{font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-danger);padding-top:var(--canon-space-1_5);margin:0}.canon-SelectTrigger{box-sizing:border-box;border-radius:var(--canon-radius-3);border:1px solid var(--canon-border);padding:0 var(--canon-space-4);background-color:var(--canon-bg-surface-1);font-size:var(--canon-font-size-3);font-family:var(--canon-font-regular);font-weight:var(--canon-font-weight-regular);color:var(--canon-fg-primary);cursor:pointer;justify-content:space-between;align-items:center;gap:var(--canon-space-2);width:100%;transition:border-color .2s ease-in-out,outline-color .2s ease-in-out;display:flex}.canon-SelectTrigger::placeholder{color:var(--canon-fg-secondary)}.canon-SelectTrigger:hover{border-color:var(--canon-border-hover)}.canon-SelectTrigger:focus-visible{border-color:var(--canon-border-pressed);outline:0}.canon-SelectTrigger[data-invalid]{border-color:var(--canon-fg-danger)}.canon-SelectTrigger[data-invalid]:hover,.canon-SelectTrigger[data-invalid]:focus-visible{border-width:2px}.canon-SelectTrigger[data-disabled]{cursor:not-allowed;border-color:var(--canon-border-disabled);color:var(--canon-fg-disabled)}.canon-SelectTrigger[data-size=small]{height:2rem}.canon-SelectTrigger[data-size=medium]{height:3rem}.canon-SelectIcon{margin-left:var(--canon-space-5);transition:transform .2s}.canon-SelectTrigger[data-popup-open] .canon-SelectIcon{transform:rotate(180deg)}.canon-SelectPopup{box-sizing:border-box;max-height:var(--available-height);background-color:var(--canon-bg-surface-1);border:1px solid var(--canon-border);border-radius:var(--canon-radius-3);padding-block:var(--canon-space-1);z-index:1;transform-origin:var(--transform-origin);outline:0;transition:transform .15s,opacity .15s;overflow-y:auto;box-shadow:0 4px 12px #0003}.canon-SelectPopup[data-starting-style],.canon-SelectPopup[data-ending-style]{opacity:0;transform:scale(.9)}.canon-SelectItem{width:var(--anchor-width);padding-block:var(--canon-space-2);padding-inline:var(--canon-space-4);color:var(--canon-fg-primary);border-radius:var(--canon-radius-3);cursor:pointer;user-select:none;font-size:var(--canon-font-size-3);align-items:center;gap:var(--canon-space-2);outline:none;grid-template-columns:1rem 1fr;grid-template-areas:"icon text";display:grid;position:relative}.canon-SelectItem[data-highlighted]{z-index:0;color:var(--canon-fg-primary);position:relative}.canon-SelectItem[data-highlighted]:before{content:"";z-index:-1;background-color:var(--canon-bg-tint-hover);border-radius:.25rem;position:absolute;inset-block:0;inset-inline:.25rem}.canon-SelectItem[data-disabled]{cursor:not-allowed;color:var(--canon-fg-disabled)}.canon-SelectItemIndicator{grid-area:icon;justify-content:center;align-items:center;display:flex}.canon-SelectItemText{flex:1;grid-area:text}.canon-SelectRequired{color:var(--canon-fg-secondary);font-size:var(--canon-font-size-2);font-weight:var(--canon-font-weight-regular);margin-left:var(--canon-space-1)}.canon-SelectIcon{justify-content:center;align-items:center;display:flex}.canon-SelectValue{text-overflow:ellipsis;white-space:nowrap;width:100%;overflow:hidden}.canon-Switch{align-items:center;gap:var(--canon-space-3);font-size:var(--canon-font-size-3);color:var(--canon-fg-primary);cursor:pointer;display:flex;position:relative;&[data-pressed] .canon-SwitchIndicator{&:before{background:var(--canon-fg-solid)}}&[data-selected]{& .canon-SwitchIndicator{background:var(--canon-bg-solid);&:before{background:var(--canon-fg-solid);transform:translate(100%)}}&[data-pressed]{& .indicator{background:var(--canon-gray-3)}}}&[data-focus-visible] .canon-SwitchIndicator{outline-offset:2px;outline:2px solid}}.canon-SwitchIndicator{background:var(--canon-gray-3);border:2px;border-radius:1.143rem;width:2rem;height:1.143rem;transition:all .2s;&:before{content:"";background:var(--canon-fg-solid);border-radius:16px;width:.857rem;height:.857rem;margin:.143rem;transition:all .2s;display:block}} \ No newline at end of file From 140f65207ef771f84cd912a06369ac9934218c04 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 19 Jun 2025 18:13:52 +0100 Subject: [PATCH 221/301] Create curly-zoos-thank.md Signed-off-by: Charles de Dreuille --- .changeset/curly-zoos-thank.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/curly-zoos-thank.md diff --git a/.changeset/curly-zoos-thank.md b/.changeset/curly-zoos-thank.md new file mode 100644 index 0000000000..ec55b2abb9 --- /dev/null +++ b/.changeset/curly-zoos-thank.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +We are consolidating all css files into a single styles.css in Canon. From 6910892548c9713fd979638f72a13dfe5d428870 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 19 Jun 2025 20:29:32 +0100 Subject: [PATCH 222/301] Add RadioGroup + Radio Signed-off-by: Charles de Dreuille --- .changeset/empty-poets-give.md | 5 + packages/canon/css/components.css | 90 +++++++++++ packages/canon/css/radiogroup.css | 89 +++++++++++ packages/canon/css/styles.css | 90 +++++++++++ packages/canon/report.api.md | 46 ++++++ .../RadioGroup/RadioGroup.stories.tsx | 147 ++++++++++++++++++ .../RadioGroup/RadioGroup.styles.css | 95 +++++++++++ .../src/components/RadioGroup/RadioGroup.tsx | 86 ++++++++++ .../canon/src/components/RadioGroup/index.ts | 18 +++ .../canon/src/components/RadioGroup/types.ts | 32 ++++ packages/canon/src/css/components.css | 1 + packages/canon/src/index.ts | 22 +-- 12 files changed, 711 insertions(+), 10 deletions(-) create mode 100644 .changeset/empty-poets-give.md create mode 100644 packages/canon/css/radiogroup.css create mode 100644 packages/canon/src/components/RadioGroup/RadioGroup.stories.tsx create mode 100644 packages/canon/src/components/RadioGroup/RadioGroup.styles.css create mode 100644 packages/canon/src/components/RadioGroup/RadioGroup.tsx create mode 100644 packages/canon/src/components/RadioGroup/index.ts create mode 100644 packages/canon/src/components/RadioGroup/types.ts diff --git a/.changeset/empty-poets-give.md b/.changeset/empty-poets-give.md new file mode 100644 index 0000000000..2eb63c2ed5 --- /dev/null +++ b/.changeset/empty-poets-give.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Add new `RadioGroup` + `Radio` component to Canon diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index 327f168696..74e678f845 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -591,6 +591,96 @@ overflow: hidden; } +.canon-RadioGroup { + color: var(--canon-fg-primary); + flex-direction: column; + display: flex; +} + +.canon-RadioGroup[data-orientation="horizontal"] .canon-RadioGroupContent { + gap: var(--canon-space-4); + flex-direction: row; +} + +.canon-RadioGroupContent { + gap: var(--canon-space-2); + flex-direction: column; + display: flex; +} + +.canon-Radio { + align-items: center; + gap: var(--canon-space-2); + font-size: var(--canon-font-size-2); + color: var(--canon-fg-primary); + forced-color-adjust: none; + display: flex; + position: relative; + + &:before { + content: ""; + box-sizing: border-box; + border: .125rem solid var(--canon-border); + background: var(--canon-gray-1); + border-radius: var(--canon-radius-full); + width: 1rem; + height: 1rem; + transition: all .2s; + display: block; + } + + &[data-pressed]:before { + border-color: var(--canon-border); + } + + &[data-selected] { + &:before { + border-color: var(--canon-bg-solid); + border-width: .25rem; + } + + &[data-pressed]:before { + border-color: var(--canon-bg-solid); + } + } + + &[data-focus-visible]:before { + outline: 2px solid var(--canon-ring); + outline-offset: 2px; + } + + &[data-disabled] { + cursor: not-allowed; + color: var(--canon-fg-disabled); + + &:before { + border-color: var(--canon-border-disabled); + background: var(--canon-bg-disabled); + } + + &[data-selected]:before { + border-color: var(--canon-border-disabled); + } + } + + &[data-invalid]:before, &[data-invalid][data-selected]:before { + border-color: var(--canon-border-danger); + } + + &[data-disabled][data-invalid] { + color: var(--canon-fg-disabled); + + &:before { + border-color: var(--canon-border-disabled); + background: var(--canon-bg-disabled); + } + + &[data-selected]:before { + border-color: var(--canon-border-disabled); + } + } +} + .canon-TableRoot { caption-side: bottom; border-collapse: collapse; diff --git a/packages/canon/css/radiogroup.css b/packages/canon/css/radiogroup.css new file mode 100644 index 0000000000..61abc2b8ac --- /dev/null +++ b/packages/canon/css/radiogroup.css @@ -0,0 +1,89 @@ +.canon-RadioGroup { + color: var(--canon-fg-primary); + flex-direction: column; + display: flex; +} + +.canon-RadioGroup[data-orientation="horizontal"] .canon-RadioGroupContent { + gap: var(--canon-space-4); + flex-direction: row; +} + +.canon-RadioGroupContent { + gap: var(--canon-space-2); + flex-direction: column; + display: flex; +} + +.canon-Radio { + align-items: center; + gap: var(--canon-space-2); + font-size: var(--canon-font-size-2); + color: var(--canon-fg-primary); + forced-color-adjust: none; + display: flex; + position: relative; + + &:before { + content: ""; + box-sizing: border-box; + border: .125rem solid var(--canon-border); + background: var(--canon-gray-1); + border-radius: var(--canon-radius-full); + width: 1rem; + height: 1rem; + transition: all .2s; + display: block; + } + + &[data-pressed]:before { + border-color: var(--canon-border); + } + + &[data-selected] { + &:before { + border-color: var(--canon-bg-solid); + border-width: .25rem; + } + + &[data-pressed]:before { + border-color: var(--canon-bg-solid); + } + } + + &[data-focus-visible]:before { + outline: 2px solid var(--canon-ring); + outline-offset: 2px; + } + + &[data-disabled] { + cursor: not-allowed; + color: var(--canon-fg-disabled); + + &:before { + border-color: var(--canon-border-disabled); + background: var(--canon-bg-disabled); + } + + &[data-selected]:before { + border-color: var(--canon-border-disabled); + } + } + + &[data-invalid]:before, &[data-invalid][data-selected]:before { + border-color: var(--canon-border-danger); + } + + &[data-disabled][data-invalid] { + color: var(--canon-fg-disabled); + + &:before { + border-color: var(--canon-border-disabled); + background: var(--canon-bg-disabled); + } + + &[data-selected]:before { + border-color: var(--canon-border-disabled); + } + } +} diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index 9f32da5d66..55effe9948 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -9815,6 +9815,96 @@ overflow: hidden; } +.canon-RadioGroup { + color: var(--canon-fg-primary); + flex-direction: column; + display: flex; +} + +.canon-RadioGroup[data-orientation="horizontal"] .canon-RadioGroupContent { + gap: var(--canon-space-4); + flex-direction: row; +} + +.canon-RadioGroupContent { + gap: var(--canon-space-2); + flex-direction: column; + display: flex; +} + +.canon-Radio { + align-items: center; + gap: var(--canon-space-2); + font-size: var(--canon-font-size-2); + color: var(--canon-fg-primary); + forced-color-adjust: none; + display: flex; + position: relative; + + &:before { + content: ""; + box-sizing: border-box; + border: .125rem solid var(--canon-border); + background: var(--canon-gray-1); + border-radius: var(--canon-radius-full); + width: 1rem; + height: 1rem; + transition: all .2s; + display: block; + } + + &[data-pressed]:before { + border-color: var(--canon-border); + } + + &[data-selected] { + &:before { + border-color: var(--canon-bg-solid); + border-width: .25rem; + } + + &[data-pressed]:before { + border-color: var(--canon-bg-solid); + } + } + + &[data-focus-visible]:before { + outline: 2px solid var(--canon-ring); + outline-offset: 2px; + } + + &[data-disabled] { + cursor: not-allowed; + color: var(--canon-fg-disabled); + + &:before { + border-color: var(--canon-border-disabled); + background: var(--canon-bg-disabled); + } + + &[data-selected]:before { + border-color: var(--canon-border-disabled); + } + } + + &[data-invalid]:before, &[data-invalid][data-selected]:before { + border-color: var(--canon-border-danger); + } + + &[data-disabled][data-invalid] { + color: var(--canon-fg-disabled); + + &:before { + border-color: var(--canon-border-disabled); + background: var(--canon-bg-disabled); + } + + &[data-selected]:before { + border-color: var(--canon-border-disabled); + } + } +} + .canon-TableRoot { caption-side: bottom; border-collapse: collapse; diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index bd0b9cbee0..97b547a66a 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -18,7 +18,10 @@ import { FocusEvent as FocusEvent_2 } from 'react'; import { ForwardRefExoticComponent } from 'react'; import { HTMLAttributes } from 'react'; import { JSX as JSX_2 } from 'react/jsx-runtime'; +import { LinkProps as LinkProps_2 } from 'react-aria-components'; import { Menu as Menu_2 } from '@base-ui-components/react/menu'; +import type { RadioGroupProps as RadioGroupProps_2 } from 'react-aria-components'; +import type { RadioProps as RadioProps_2 } from 'react-aria-components'; import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { RefAttributes } from 'react'; @@ -174,6 +177,28 @@ export interface ButtonIconProps extends ButtonProps_2 { | Partial>; } +// @public (undocumented) +export const ButtonLink: ForwardRefExoticComponent< + ButtonLinkProps & RefAttributes +>; + +// @public +export interface ButtonLinkProps extends LinkProps_2 { + // (undocumented) + children?: ReactNode; + // (undocumented) + iconEnd?: ReactElement; + // (undocumented) + iconStart?: ReactElement; + // (undocumented) + size?: 'small' | 'medium' | Partial>; + // (undocumented) + variant?: + | 'primary' + | 'secondary' + | Partial>; +} + // @public export interface ButtonProps extends ButtonProps_2 { // (undocumented) @@ -1009,6 +1034,27 @@ export type PositionProps = GetPropDefTypes; // @public (undocumented) export type PropDef = RegularPropDef | ResponsivePropDef; +// @public (undocumented) +export const Radio: ForwardRefExoticComponent< + RadioProps & RefAttributes +>; + +// @public (undocumented) +export const RadioGroup: ForwardRefExoticComponent< + RadioGroupProps & RefAttributes +>; + +// @public (undocumented) +export interface RadioGroupProps + extends Omit, + Omit { + // (undocumented) + children?: ReactNode; +} + +// @public (undocumented) +export interface RadioProps extends RadioProps_2 {} + // @public (undocumented) export type ReactNodePropDef = { type: 'ReactNode'; diff --git a/packages/canon/src/components/RadioGroup/RadioGroup.stories.tsx b/packages/canon/src/components/RadioGroup/RadioGroup.stories.tsx new file mode 100644 index 0000000000..4f28e04bfc --- /dev/null +++ b/packages/canon/src/components/RadioGroup/RadioGroup.stories.tsx @@ -0,0 +1,147 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { RadioGroup, Radio } from './RadioGroup'; + +const meta = { + title: 'Forms/RadioGroup', + component: RadioGroup, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + label: 'What is your favorite pokemon?', + }, + render: args => ( + + Bulbasaur + Charmander + Squirtle + + ), +}; + +export const Horizontal: Story = { + args: { + ...Default.args, + orientation: 'horizontal', + }, + render: args => ( + + Bulbasaur + Charmander + Squirtle + + ), +}; + +export const Disabled: Story = { + args: { + ...Default.args, + isDisabled: true, + }, + render: args => ( + + Bulbasaur + Charmander + Squirtle + + ), +}; + +export const DisabledSingle: Story = { + args: { + ...Default.args, + }, + render: args => ( + + Bulbasaur + + Charmander + + Squirtle + + ), +}; + +export const DisabledAndSelected: Story = { + args: { + ...Default.args, + value: 'charmander', + }, + render: args => ( + + Bulbasaur + + Charmander + + Squirtle + + ), +}; + +export const Invalid: Story = { + args: { + ...Default.args, + name: 'pokemon', + isInvalid: true, + }, + render: args => ( + + Bulbasaur + + Charmander + + Squirtle + + ), +}; + +export const Validation: Story = { + args: { + ...Default.args, + name: 'pokemon', + defaultValue: 'charmander', + validationBehavior: 'aria', + validate: value => (value === 'charmander' ? 'Nice try!' : null), + }, + render: args => ( + + Bulbasaur + Charmander + Squirtle + + ), +}; + +export const ReadOnly: Story = { + args: { + ...Default.args, + isReadOnly: true, + defaultValue: 'charmander', + }, + render: args => ( + + Bulbasaur + Charmander + Squirtle + + ), +}; diff --git a/packages/canon/src/components/RadioGroup/RadioGroup.styles.css b/packages/canon/src/components/RadioGroup/RadioGroup.styles.css new file mode 100644 index 0000000000..a712f9031f --- /dev/null +++ b/packages/canon/src/components/RadioGroup/RadioGroup.styles.css @@ -0,0 +1,95 @@ +.canon-RadioGroup { + display: flex; + flex-direction: column; + color: var(--canon-fg-primary); +} + +.canon-RadioGroup[data-orientation='horizontal'] .canon-RadioGroupContent { + flex-direction: row; + gap: var(--canon-space-4); +} + +.canon-RadioGroupContent { + display: flex; + flex-direction: column; + gap: var(--canon-space-2); +} + +.canon-Radio { + display: flex; + /* This is needed so the HiddenInput is positioned correctly */ + position: relative; + align-items: center; + gap: var(--canon-space-2); + font-size: var(--canon-font-size-2); + color: var(--canon-fg-primary); + forced-color-adjust: none; + + &:before { + content: ''; + display: block; + width: 1rem; + height: 1rem; + box-sizing: border-box; + border: 0.125rem solid var(--canon-border); + background: var(--canon-gray-1); + border-radius: var(--canon-radius-full); + transition: all 200ms; + } + + &[data-pressed]:before { + border-color: var(--canon-border); + } + + &[data-selected] { + &:before { + border-color: var(--canon-bg-solid); + border-width: 0.25rem; + } + + &[data-pressed]:before { + border-color: var(--canon-bg-solid); + } + } + + &[data-focus-visible]:before { + outline: 2px solid var(--canon-ring); + outline-offset: 2px; + } + + &[data-disabled] { + cursor: not-allowed; + color: var(--canon-fg-disabled); + + &:before { + border-color: var(--canon-border-disabled); + background: var(--canon-bg-disabled); + } + + &[data-selected]:before { + border-color: var(--canon-border-disabled); + } + } + + &[data-invalid]:before { + border-color: var(--canon-border-danger); + } + + &[data-invalid][data-selected]:before { + border-color: var(--canon-border-danger); + } + + /* Ensure disabled state prevails over invalid state */ + &[data-disabled][data-invalid] { + color: var(--canon-fg-disabled); + + &:before { + border-color: var(--canon-border-disabled); + background: var(--canon-bg-disabled); + } + + &[data-selected]:before { + border-color: var(--canon-border-disabled); + } + } +} diff --git a/packages/canon/src/components/RadioGroup/RadioGroup.tsx b/packages/canon/src/components/RadioGroup/RadioGroup.tsx new file mode 100644 index 0000000000..5bdfcb8a20 --- /dev/null +++ b/packages/canon/src/components/RadioGroup/RadioGroup.tsx @@ -0,0 +1,86 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { forwardRef, useEffect } from 'react'; +import { + RadioGroup as AriaRadioGroup, + Radio as AriaRadio, + FieldError, +} from 'react-aria-components'; +import clsx from 'clsx'; +import { FieldLabel } from '../FieldLabel'; + +import type { RadioGroupProps, RadioProps } from './types'; + +/** @public */ +export const RadioGroup = forwardRef( + (props, ref) => { + const { + className, + label, + secondaryLabel, + description, + isRequired, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, + children, + ...rest + } = props; + + useEffect(() => { + if (!label && !ariaLabel && !ariaLabelledBy) { + console.warn( + 'RadioGroup requires either a visible label, aria-label, or aria-labelledby for accessibility', + ); + } + }, [label, ariaLabel, ariaLabelledBy]); + + // If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required. + const secondaryLabelText = + secondaryLabel || (isRequired ? 'Required' : null); + + return ( + + +
{children}
+ +
+ ); + }, +); + +RadioGroup.displayName = 'RadioGroup'; + +/** @public */ +export const Radio = forwardRef((props, ref) => { + const { className, ...rest } = props; + + return ( + + ); +}); + +RadioGroup.displayName = 'RadioGroup'; diff --git a/packages/canon/src/components/RadioGroup/index.ts b/packages/canon/src/components/RadioGroup/index.ts new file mode 100644 index 0000000000..fd9572f667 --- /dev/null +++ b/packages/canon/src/components/RadioGroup/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './RadioGroup'; +export * from './types'; diff --git a/packages/canon/src/components/RadioGroup/types.ts b/packages/canon/src/components/RadioGroup/types.ts new file mode 100644 index 0000000000..029ef4baf9 --- /dev/null +++ b/packages/canon/src/components/RadioGroup/types.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { + RadioGroupProps as AriaRadioGroupProps, + RadioProps as AriaRadioProps, +} from 'react-aria-components'; +import type { FieldLabelProps } from '../FieldLabel/types'; +import { ReactNode } from 'react'; + +/** @public */ +export interface RadioGroupProps + extends Omit, + Omit { + children?: ReactNode; +} + +/** @public */ +export interface RadioProps extends AriaRadioProps {} diff --git a/packages/canon/src/css/components.css b/packages/canon/src/css/components.css index aa453f7314..4199bc04eb 100644 --- a/packages/canon/src/css/components.css +++ b/packages/canon/src/css/components.css @@ -30,6 +30,7 @@ @import '../components/Icon/styles.css'; @import '../components/Link/styles.css'; @import '../components/Menu/Menu.styles.css'; +@import '../components/RadioGroup/RadioGroup.styles.css'; @import '../components/Table/styles.css'; @import '../components/Table/TableCell/TableCell.styles.css'; @import '../components/Table/TableCellText/TableCellText.styles.css'; diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts index 69c302bdc5..79b295484c 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -28,27 +28,29 @@ export * from './components/Box'; export * from './components/Grid'; export * from './components/Flex'; export * from './components/Container'; -export * from './components/Text'; -export * from './components/Heading'; // UI components export * from './components/Avatar'; export * from './components/Button'; +export * from './components/ButtonIcon'; +export * from './components/ButtonLink'; +export * from './components/Checkbox'; export * from './components/Collapsible'; export * from './components/DataTable'; export * from './components/FieldLabel'; +export * from './components/Heading'; export * from './components/Icon'; -export * from './components/ButtonIcon'; -export * from './components/Checkbox'; -export * from './components/Table'; -export * from './components/Tabs'; -export * from './components/TextField'; -export * from './components/Tooltip'; -export * from './components/Menu'; -export * from './components/ScrollArea'; export * from './components/Link'; +export * from './components/Menu'; +export * from './components/RadioGroup'; +export * from './components/ScrollArea'; export * from './components/Select'; export * from './components/Switch'; +export * from './components/Table'; +export * from './components/Tabs'; +export * from './components/Text'; +export * from './components/TextField'; +export * from './components/Tooltip'; // Types export * from './types'; From 4f1b4ad6c018de7db200d865dcdd0fd4f3df6d1e Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 19 Jun 2025 21:32:43 +0100 Subject: [PATCH 223/301] Add doscs + FieldError Signed-off-by: Charles de Dreuille --- .../src/content/components/radio-group.mdx | 98 +++++++++++++++++++ .../content/components/radio-group.props.ts | 64 ++++++++++++ canon-docs/src/snippets/stories-snippets.tsx | 12 +++ canon-docs/src/utils/data.ts | 5 + .../FieldError/FieldError.stories.tsx | 87 ++++++++++++++++ .../FieldError/FieldError.styles.css | 6 ++ .../src/components/FieldError/FieldError.tsx | 39 ++++++++ .../canon/src/components/FieldError/index.ts | 17 ++++ .../src/components/RadioGroup/RadioGroup.tsx | 4 +- .../TextField/TextField.stories.tsx | 20 +++- .../components/TextField/TextField.styles.css | 7 -- .../src/components/TextField/TextField.tsx | 9 +- packages/canon/src/css/components.css | 1 + packages/canon/src/index.ts | 1 + 14 files changed, 351 insertions(+), 19 deletions(-) create mode 100644 canon-docs/src/content/components/radio-group.mdx create mode 100644 canon-docs/src/content/components/radio-group.props.ts create mode 100644 packages/canon/src/components/FieldError/FieldError.stories.tsx create mode 100644 packages/canon/src/components/FieldError/FieldError.styles.css create mode 100644 packages/canon/src/components/FieldError/FieldError.tsx create mode 100644 packages/canon/src/components/FieldError/index.ts diff --git a/canon-docs/src/content/components/radio-group.mdx b/canon-docs/src/content/components/radio-group.mdx new file mode 100644 index 0000000000..703ac6b8dd --- /dev/null +++ b/canon-docs/src/content/components/radio-group.mdx @@ -0,0 +1,98 @@ +import { PropsTable } from '@/components/PropsTable'; +import { Snippet } from '@/components/Snippet'; +import { RadioGroupSnippet } from '@/snippets/stories-snippets'; +import { + radioGroupPropDefs, + radioGroupUsageSnippet, + radioGroupDefaultSnippet, + radioGroupDescriptionSnippet, + radioGroupHorizontalSnippet, + radioGroupDisabledSnippet, + radioGroupDisabledSingleSnippet, + radioGroupValidationSnippet, + radioGroupReadOnlySnippet, +} from './radio-group.props'; +import { ComponentInfos } from '@/components/ComponentInfos'; + +# RadioGroup + +A radio group allows a user to select a single item from a list of mutually exclusive options. + +} + code={radioGroupDefaultSnippet} +/> + + + +## API reference + + + +## Examples + +### Horizontal + +Here's a simple TextField with a description. + +} + code={radioGroupHorizontalSnippet} +/> + +### Disabled + +You can disable the entire radio group by adding the `isDisabled` prop to the `RadioGroup` component. + +} + code={radioGroupDisabledSnippet} +/> + +### Disabled Single radio + +You can disable a single radio by adding the `isDisabled` prop to the `Radio` component. + +} + code={radioGroupDisabledSingleSnippet} +/> + +### Validation + +Here's an example of a radio group with errors. + +} + code={radioGroupValidationSnippet} +/> + +### Read only + +You can make the radio group read only by adding the `isReadOnly` prop to the `RadioGroup` component. + +} + code={radioGroupReadOnlySnippet} +/> diff --git a/canon-docs/src/content/components/radio-group.props.ts b/canon-docs/src/content/components/radio-group.props.ts new file mode 100644 index 0000000000..c016be5ac6 --- /dev/null +++ b/canon-docs/src/content/components/radio-group.props.ts @@ -0,0 +1,64 @@ +import { + classNamePropDefs, + stylePropDefs, + type PropDef, +} from '@/utils/propDefs'; + +export const radioGroupPropDefs: Record = { + size: { + type: 'enum', + values: ['small', 'medium'], + default: 'small', + responsive: true, + }, + label: { + type: 'string', + }, + icon: { + type: 'enum', + values: ['ReactNode'], + }, + description: { + type: 'string', + }, + name: { + type: 'string', + required: true, + }, + ...classNamePropDefs, + ...stylePropDefs, +}; + +export const radioGroupUsageSnippet = `import { RadioGroup } from '@backstage/canon'; + +`; + +export const radioGroupDefaultSnippet = ``; + +export const radioGroupDescriptionSnippet = ``; + +export const radioGroupHorizontalSnippet = ``; + +export const radioGroupDisabledSnippet = ` + Bulbasaur + Charmander + Squirtle +`; + +export const radioGroupDisabledSingleSnippet = ` + Bulbasaur + Charmander + Squirtle +`; + +export const radioGroupValidationSnippet = ` (value === \'charmander\' ? \'Nice try!\' : null)> + Bulbasaur + Charmander + Squirtle +`; + +export const radioGroupReadOnlySnippet = ` + Bulbasaur + Charmander + Squirtle +`; diff --git a/canon-docs/src/snippets/stories-snippets.tsx b/canon-docs/src/snippets/stories-snippets.tsx index e5a7464b43..ef25a37900 100644 --- a/canon-docs/src/snippets/stories-snippets.tsx +++ b/canon-docs/src/snippets/stories-snippets.tsx @@ -18,6 +18,7 @@ import * as MenuStories from '../../../packages/canon/src/components/Menu/Menu.s import * as LinkStories from '../../../packages/canon/src/components/Link/Link.stories'; import * as AvatarStories from '../../../packages/canon/src/components/Avatar/Avatar.stories'; import * as CollapsibleStories from '../../../packages/canon/src/components/Collapsible/Collapsible.stories'; +import * as RadioGroupStories from '../../../packages/canon/src/components/RadioGroup/RadioGroup.stories'; import * as TabsStories from '../../../packages/canon/src/components/Tabs/Tabs.stories'; import * as SwitchStories from '../../../packages/canon/src/components/Switch/Switch.stories'; @@ -197,3 +198,14 @@ export const SwitchSnippet = ({ return StoryComponent ? : null; }; + +export const RadioGroupSnippet = ({ + story, +}: { + story: keyof typeof RadioGroupStories; +}) => { + const stories = composeStories(RadioGroupStories); + const StoryComponent = stories[story as keyof typeof stories]; + + return StoryComponent ? : null; +}; diff --git a/canon-docs/src/utils/data.ts b/canon-docs/src/utils/data.ts index a741ad5160..92f0fdbf87 100644 --- a/canon-docs/src/utils/data.ts +++ b/canon-docs/src/utils/data.ts @@ -116,6 +116,11 @@ export const components: Page[] = [ slug: 'menu', status: 'alpha', }, + { + title: 'RadioGroup', + slug: 'radio-group', + status: 'alpha', + }, { title: 'Select', slug: 'select', diff --git a/packages/canon/src/components/FieldError/FieldError.stories.tsx b/packages/canon/src/components/FieldError/FieldError.stories.tsx new file mode 100644 index 0000000000..39714d16d2 --- /dev/null +++ b/packages/canon/src/components/FieldError/FieldError.stories.tsx @@ -0,0 +1,87 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { TextField, Input, Form } from 'react-aria-components'; +import { FieldError } from './FieldError'; + +const meta = { + title: 'Forms/FieldError', + component: FieldError, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// Show error with server validation using Form component +export const WithServerValidation: Story = { + render: () => ( +
+ + + + +
+ ), +}; + +// Show error using children +export const WithCustomMessage: Story = { + render: () => ( + + + This is a custom error message. + + ), +}; + +// Show error with render prop function +export const WithRenderProp: Story = { + render: () => ( + 'This field is invalid'} + style={{ + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + }} + > + + + {({ validationErrors }) => + validationErrors.length > 0 ? validationErrors[0] : 'Field is invalid' + } + + + ), +}; diff --git a/packages/canon/src/components/FieldError/FieldError.styles.css b/packages/canon/src/components/FieldError/FieldError.styles.css new file mode 100644 index 0000000000..816eb3c35e --- /dev/null +++ b/packages/canon/src/components/FieldError/FieldError.styles.css @@ -0,0 +1,6 @@ +.canon-FieldError { + color: var(--canon-fg-danger); + font-size: var(--canon-font-size-2); + font-weight: var(--canon-font-weight-regular); + margin-top: var(--canon-space-2); +} diff --git a/packages/canon/src/components/FieldError/FieldError.tsx b/packages/canon/src/components/FieldError/FieldError.tsx new file mode 100644 index 0000000000..b90d7b3bd3 --- /dev/null +++ b/packages/canon/src/components/FieldError/FieldError.tsx @@ -0,0 +1,39 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { forwardRef } from 'react'; +import { + FieldError as AriaFieldError, + type FieldErrorProps, +} from 'react-aria-components'; +import clsx from 'clsx'; + +/** @public */ +export const FieldError = forwardRef( + (props: FieldErrorProps, ref) => { + const { className, ...rest } = props; + + return ( + + ); + }, +); + +FieldError.displayName = 'FieldError'; diff --git a/packages/canon/src/components/FieldError/index.ts b/packages/canon/src/components/FieldError/index.ts new file mode 100644 index 0000000000..5b61f83ace --- /dev/null +++ b/packages/canon/src/components/FieldError/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './FieldError'; diff --git a/packages/canon/src/components/RadioGroup/RadioGroup.tsx b/packages/canon/src/components/RadioGroup/RadioGroup.tsx index 5bdfcb8a20..d1d7451a92 100644 --- a/packages/canon/src/components/RadioGroup/RadioGroup.tsx +++ b/packages/canon/src/components/RadioGroup/RadioGroup.tsx @@ -18,10 +18,10 @@ import { forwardRef, useEffect } from 'react'; import { RadioGroup as AriaRadioGroup, Radio as AriaRadio, - FieldError, } from 'react-aria-components'; import clsx from 'clsx'; import { FieldLabel } from '../FieldLabel'; +import { FieldError } from '../FieldError'; import type { RadioGroupProps, RadioProps } from './types'; @@ -66,7 +66,7 @@ export const RadioGroup = forwardRef( description={description} />
{children}
- + ); }, diff --git a/packages/canon/src/components/TextField/TextField.stories.tsx b/packages/canon/src/components/TextField/TextField.stories.tsx index e07aa6ea05..c8fc5b5710 100644 --- a/packages/canon/src/components/TextField/TextField.stories.tsx +++ b/packages/canon/src/components/TextField/TextField.stories.tsx @@ -97,16 +97,28 @@ export const Disabled: Story = { export const WithIcon: Story = { args: { ...Default.args, - defaultValue: 'Search...', - icon: , }, + render: args => ( + } + defaultValue="Search..." + /> + ), }; export const DisabledWithIcon: Story = { args: { - ...WithIcon.args, - isDisabled: true, + ...Default.args, }, + render: args => ( + } + defaultValue="Search..." + isDisabled + /> + ), }; export const ShowError: Story = { diff --git a/packages/canon/src/components/TextField/TextField.styles.css b/packages/canon/src/components/TextField/TextField.styles.css index 9f2039b3af..c0432d88aa 100644 --- a/packages/canon/src/components/TextField/TextField.styles.css +++ b/packages/canon/src/components/TextField/TextField.styles.css @@ -104,10 +104,3 @@ cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } - -.canon-TextFieldError { - color: var(--canon-fg-danger); - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - margin-top: var(--canon-space-2); -} diff --git a/packages/canon/src/components/TextField/TextField.tsx b/packages/canon/src/components/TextField/TextField.tsx index 444e76e983..d20e68ef88 100644 --- a/packages/canon/src/components/TextField/TextField.tsx +++ b/packages/canon/src/components/TextField/TextField.tsx @@ -15,14 +15,11 @@ */ import { forwardRef, useEffect } from 'react'; -import { - Input, - TextField as AriaTextField, - FieldError, -} from 'react-aria-components'; +import { Input, TextField as AriaTextField } from 'react-aria-components'; import { useResponsiveValue } from '../../hooks/useResponsiveValue'; import clsx from 'clsx'; import { FieldLabel } from '../FieldLabel'; +import { FieldError } from '../FieldError'; import type { TextFieldProps } from './types'; @@ -86,7 +83,7 @@ export const TextField = forwardRef( {...(icon && { 'data-icon': true })} />
- + ); }, diff --git a/packages/canon/src/css/components.css b/packages/canon/src/css/components.css index 4199bc04eb..407723fcc9 100644 --- a/packages/canon/src/css/components.css +++ b/packages/canon/src/css/components.css @@ -23,6 +23,7 @@ @import '../components/Container/styles.css'; @import '../components/DataTable/Root/DataTableRoot.styles.css'; @import '../components/DataTable/Pagination/DataTablePagination.styles.css'; +@import '../components/FieldError/FieldError.styles.css'; @import '../components/FieldLabel/FieldLabel.styles.css'; @import '../components/Flex/styles.css'; @import '../components/Grid/styles.css'; diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts index 79b295484c..dc0b3c1cfc 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -38,6 +38,7 @@ export * from './components/Checkbox'; export * from './components/Collapsible'; export * from './components/DataTable'; export * from './components/FieldLabel'; +export * from './components/FieldError'; export * from './components/Heading'; export * from './components/Icon'; export * from './components/Link'; From a042eed1cdac0cc86fc70ca6bd0f02c1c3727190 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 19 Jun 2025 21:35:28 +0100 Subject: [PATCH 224/301] Add report Signed-off-by: Charles de Dreuille --- packages/canon/css/components.css | 14 +++++++------- packages/canon/css/fielderror.css | 6 ++++++ packages/canon/css/styles.css | 14 +++++++------- packages/canon/css/textfield.css | 7 ------- packages/canon/report.api.md | 6 ++++++ 5 files changed, 26 insertions(+), 21 deletions(-) create mode 100644 packages/canon/css/fielderror.css diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index 74e678f845..b080e11f82 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -272,6 +272,13 @@ min-width: 10.5rem; } +.canon-FieldError { + color: var(--canon-fg-danger); + font-size: var(--canon-font-size-2); + font-weight: var(--canon-font-weight-regular); + margin-top: var(--canon-space-2); +} + .canon-FieldLabel { margin-bottom: var(--canon-space-3); gap: var(--canon-space-1); @@ -985,13 +992,6 @@ border: 1px solid var(--canon-border-disabled); } -.canon-TextFieldError { - color: var(--canon-fg-danger); - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - margin-top: var(--canon-space-2); -} - .canon-TooltipPopup { box-sizing: border-box; transform-origin: var(--transform-origin); diff --git a/packages/canon/css/fielderror.css b/packages/canon/css/fielderror.css new file mode 100644 index 0000000000..816eb3c35e --- /dev/null +++ b/packages/canon/css/fielderror.css @@ -0,0 +1,6 @@ +.canon-FieldError { + color: var(--canon-fg-danger); + font-size: var(--canon-font-size-2); + font-weight: var(--canon-font-weight-regular); + margin-top: var(--canon-space-2); +} diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index 55effe9948..806ee771d2 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -9496,6 +9496,13 @@ min-width: 10.5rem; } +.canon-FieldError { + color: var(--canon-fg-danger); + font-size: var(--canon-font-size-2); + font-weight: var(--canon-font-weight-regular); + margin-top: var(--canon-space-2); +} + .canon-FieldLabel { margin-bottom: var(--canon-space-3); gap: var(--canon-space-1); @@ -10209,13 +10216,6 @@ border: 1px solid var(--canon-border-disabled); } -.canon-TextFieldError { - color: var(--canon-fg-danger); - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - margin-top: var(--canon-space-2); -} - .canon-TooltipPopup { box-sizing: border-box; transform-origin: var(--transform-origin); diff --git a/packages/canon/css/textfield.css b/packages/canon/css/textfield.css index fdb3621016..45759c1c4c 100644 --- a/packages/canon/css/textfield.css +++ b/packages/canon/css/textfield.css @@ -85,10 +85,3 @@ cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } - -.canon-TextFieldError { - color: var(--canon-fg-danger); - font-size: var(--canon-font-size-2); - font-weight: var(--canon-font-weight-regular); - margin-top: var(--canon-space-2); -} diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index 97b547a66a..34e5379156 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -14,6 +14,7 @@ import { Context } from 'react'; import type { CSSProperties } from 'react'; import type { ElementType } from 'react'; import { FC } from 'react'; +import { FieldErrorProps } from 'react-aria-components'; import { FocusEvent as FocusEvent_2 } from 'react'; import { ForwardRefExoticComponent } from 'react'; import { HTMLAttributes } from 'react'; @@ -388,6 +389,11 @@ export type EnumPropDef = { required?: boolean; }; +// @public (undocumented) +export const FieldError: ForwardRefExoticComponent< + FieldErrorProps & RefAttributes +>; + // @public (undocumented) export const FieldLabel: ForwardRefExoticComponent< FieldLabelProps & RefAttributes From 9699967f657df80e99036dd41fbbd7d4457c5a4a Mon Sep 17 00:00:00 2001 From: Hermione Bird Date: Fri, 20 Jun 2025 14:37:39 +0100 Subject: [PATCH 225/301] adding export for Button Link in Canon Signed-off-by: Hermione Bird --- packages/canon/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts index 69c302bdc5..d35883bbe6 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -39,6 +39,7 @@ export * from './components/DataTable'; export * from './components/FieldLabel'; export * from './components/Icon'; export * from './components/ButtonIcon'; +export * from './components/ButtonLink'; export * from './components/Checkbox'; export * from './components/Table'; export * from './components/Tabs'; From e71333aee1cc671dadbcfbdc4d0a91cb90a8e865 Mon Sep 17 00:00:00 2001 From: Hermione Bird Date: Fri, 20 Jun 2025 14:49:29 +0100 Subject: [PATCH 226/301] adding changeset Signed-off-by: Hermione Bird --- .changeset/three-shoes-behave.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/three-shoes-behave.md diff --git a/.changeset/three-shoes-behave.md b/.changeset/three-shoes-behave.md new file mode 100644 index 0000000000..51205ffa21 --- /dev/null +++ b/.changeset/three-shoes-behave.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +adding export for ButtonLink so it's importable From e34d2c814785dd94209fdbe602fcd1f3054db516 Mon Sep 17 00:00:00 2001 From: Hermione Bird Date: Fri, 20 Jun 2025 15:17:26 +0100 Subject: [PATCH 227/301] running api-report Signed-off-by: Hermione Bird --- packages/canon/report.api.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index bd0b9cbee0..abe30ea93b 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -18,6 +18,7 @@ import { FocusEvent as FocusEvent_2 } from 'react'; import { ForwardRefExoticComponent } from 'react'; import { HTMLAttributes } from 'react'; import { JSX as JSX_2 } from 'react/jsx-runtime'; +import { LinkProps as LinkProps_2 } from 'react-aria-components'; import { Menu as Menu_2 } from '@base-ui-components/react/menu'; import { ReactElement } from 'react'; import { ReactNode } from 'react'; @@ -174,6 +175,28 @@ export interface ButtonIconProps extends ButtonProps_2 { | Partial>; } +// @public (undocumented) +export const ButtonLink: ForwardRefExoticComponent< + ButtonLinkProps & RefAttributes +>; + +// @public +export interface ButtonLinkProps extends LinkProps_2 { + // (undocumented) + children?: ReactNode; + // (undocumented) + iconEnd?: ReactElement; + // (undocumented) + iconStart?: ReactElement; + // (undocumented) + size?: 'small' | 'medium' | Partial>; + // (undocumented) + variant?: + | 'primary' + | 'secondary' + | Partial>; +} + // @public export interface ButtonProps extends ButtonProps_2 { // (undocumented) From dbf9161645be840c5dfe38ab098ab230642220e0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 15:00:42 +0000 Subject: [PATCH 228/301] chore(deps): update dependency @changesets/cli to v2.29.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/yarn.lock b/yarn.lock index da694cf51f..f27b409a61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8757,9 +8757,9 @@ __metadata: languageName: node linkType: hard -"@changesets/assemble-release-plan@npm:^6.0.8": - version: 6.0.8 - resolution: "@changesets/assemble-release-plan@npm:6.0.8" +"@changesets/assemble-release-plan@npm:^6.0.9": + version: 6.0.9 + resolution: "@changesets/assemble-release-plan@npm:6.0.9" dependencies: "@changesets/errors": "npm:^0.2.0" "@changesets/get-dependents-graph": "npm:^2.1.3" @@ -8767,7 +8767,7 @@ __metadata: "@changesets/types": "npm:^6.1.0" "@manypkg/get-packages": "npm:^1.1.3" semver: "npm:^7.5.3" - checksum: 10/5d01fc42c67229874cc70b93fbdc971e11909aa7a72f1909c585ecb3fdc69f3ac105d243e1341cd5b07c02dee133be461fa48138125f00d137e71f8b7e8f428e + checksum: 10/f84656eabb700ed77f97751b282e1701636ed45a44b443abd9af0291870495cc046fee301478010f39a1dc455799065ae007b9d7d2bb5ae8b793b65bbb8e052a languageName: node linkType: hard @@ -8781,16 +8781,16 @@ __metadata: linkType: hard "@changesets/cli@npm:^2.14.0": - version: 2.29.4 - resolution: "@changesets/cli@npm:2.29.4" + version: 2.29.5 + resolution: "@changesets/cli@npm:2.29.5" dependencies: "@changesets/apply-release-plan": "npm:^7.0.12" - "@changesets/assemble-release-plan": "npm:^6.0.8" + "@changesets/assemble-release-plan": "npm:^6.0.9" "@changesets/changelog-git": "npm:^0.2.1" "@changesets/config": "npm:^3.1.1" "@changesets/errors": "npm:^0.2.0" "@changesets/get-dependents-graph": "npm:^2.1.3" - "@changesets/get-release-plan": "npm:^4.0.12" + "@changesets/get-release-plan": "npm:^4.0.13" "@changesets/git": "npm:^3.0.4" "@changesets/logger": "npm:^0.1.1" "@changesets/pre": "npm:^2.0.2" @@ -8814,7 +8814,7 @@ __metadata: term-size: "npm:^2.1.0" bin: changeset: bin.js - checksum: 10/fc325447b81a811464107e72a687f6c0414c5f928e518cb122d1efde1d71c205b1972464795ab97fb26087900ea55b99a551e9a010c9681def3d7561fd1c3f0b + checksum: 10/f401da29025d7bcc07b732bb09a9627f785bfc21c7c2005861d11ffea732bc14d33394fc2fcae50cc5f2b710f6080c5babe2fa90d432de5fdb47ae6afc147936 languageName: node linkType: hard @@ -8854,17 +8854,17 @@ __metadata: languageName: node linkType: hard -"@changesets/get-release-plan@npm:^4.0.12": - version: 4.0.12 - resolution: "@changesets/get-release-plan@npm:4.0.12" +"@changesets/get-release-plan@npm:^4.0.13": + version: 4.0.13 + resolution: "@changesets/get-release-plan@npm:4.0.13" dependencies: - "@changesets/assemble-release-plan": "npm:^6.0.8" + "@changesets/assemble-release-plan": "npm:^6.0.9" "@changesets/config": "npm:^3.1.1" "@changesets/pre": "npm:^2.0.2" "@changesets/read": "npm:^0.6.5" "@changesets/types": "npm:^6.1.0" "@manypkg/get-packages": "npm:^1.1.3" - checksum: 10/d6482ecb6f1c2c47266493a36d05b484f0950d0a4472820649e953d073e3fdd612cdd8a4df9e3d7e00756d4e446dae639f9d6e0dab8a25f76bb6df77cd91c21c + checksum: 10/9983fae5a68012c4c418ddd62f2fb3d325363f21160252ff7b868503a1a2effb8fdd32e4a0289b72653afc3605ce19d163ff69205c942a0004efb571a5f78fd0 languageName: node linkType: hard From cb2ac11b1bdb9d04fc2c5276f3f56c6e8ee4d9ba Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 20 Jun 2025 14:42:27 +0100 Subject: [PATCH 229/301] Create first version of useStyles hook Signed-off-by: Charles de Dreuille --- .../canon/src/components/Button/Button.tsx | 13 +- packages/canon/src/hooks/useStyles.ts | 87 ++++++ packages/canon/src/index.ts | 3 + packages/canon/src/types.ts | 35 +++ .../canon/src/utils/componentDefinitions.ts | 281 ++++++++++++++++++ 5 files changed, 413 insertions(+), 6 deletions(-) create mode 100644 packages/canon/src/hooks/useStyles.ts create mode 100644 packages/canon/src/utils/componentDefinitions.ts diff --git a/packages/canon/src/components/Button/Button.tsx b/packages/canon/src/components/Button/Button.tsx index 9d256a5815..29593ea922 100644 --- a/packages/canon/src/components/Button/Button.tsx +++ b/packages/canon/src/components/Button/Button.tsx @@ -17,8 +17,8 @@ import clsx from 'clsx'; import { forwardRef, Ref } from 'react'; import { Button as RAButton } from 'react-aria-components'; -import { useResponsiveValue } from '../../hooks/useResponsiveValue'; import type { ButtonProps } from './types'; +import { useStyles } from '../../definition'; /** @public */ export const Button = forwardRef( @@ -33,15 +33,16 @@ export const Button = forwardRef( ...rest } = props; - const responsiveSize = useResponsiveValue(size); - const responsiveVariant = useResponsiveValue(variant); + const { classNames, dataAttributes } = useStyles('Button', { + size, + variant, + }); return ( {iconStart} diff --git a/packages/canon/src/hooks/useStyles.ts b/packages/canon/src/hooks/useStyles.ts new file mode 100644 index 0000000000..d6c9d0c76b --- /dev/null +++ b/packages/canon/src/hooks/useStyles.ts @@ -0,0 +1,87 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useBreakpoint, breakpoints } from './useBreakpoint'; +import { componentDefinitions } from '../utils/componentDefinitions'; +import type { ComponentDefinitionName } from '../types'; + +/** + * Resolve a responsive value based on the current breakpoint + * @param value - The responsive value (string or object with breakpoint keys) + * @param breakpoint - The current breakpoint + * @returns The resolved value for the current breakpoint + */ +function resolveResponsiveValue( + value: string | Record, + breakpoint: string, +): string | undefined { + if (typeof value === 'string') { + return value; + } + + if (typeof value === 'object' && value !== null) { + const index = breakpoints.findIndex(b => b.id === breakpoint); + + // Look for value at current breakpoint or smaller + for (let i = index; i >= 0; i--) { + if (value[breakpoints[i].id]) { + return value[breakpoints[i].id]; + } + } + + // If no value found, check from smallest breakpoint up + for (let i = 0; i < breakpoints.length; i++) { + if (value[breakpoints[i].id]) { + return value[breakpoints[i].id]; + } + } + } + + return undefined; +} + +/** + * React hook to get class names and data attributes for a component with responsive support + * @param componentName - The name of the component + * @param props - Object with prop values (can be responsive) + * @returns Object with classNames and dataAttributes + */ +export function useStyles( + componentName: T, + props: Record = {}, +) { + const { breakpoint } = useBreakpoint(); + const classNames = componentDefinitions[componentName].classNames; + + // Resolve responsive values and generate data attributes + const dataAttributes: Record = {}; + const resolvedProps: Record = {}; + + for (const [key, value] of Object.entries(props)) { + if (value !== undefined && value !== null) { + const resolvedValue = resolveResponsiveValue(value, breakpoint); + if (resolvedValue !== undefined) { + resolvedProps[key] = resolvedValue; + dataAttributes[`data-${key}`] = resolvedValue; + } + } + } + + return { + classNames, + dataAttributes, + resolvedProps, // Also return resolved props for convenience + }; +} diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts index d35883bbe6..4da616638c 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -57,3 +57,6 @@ export * from './props'; // Hooks export { useBreakpoint } from './hooks/useBreakpoint'; + +// Component Definitions +export * from './utils/componentDefinitions'; diff --git a/packages/canon/src/types.ts b/packages/canon/src/types.ts index ee5d954576..056d4772ba 100644 --- a/packages/canon/src/types.ts +++ b/packages/canon/src/types.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { componentDefinitions } from './utils/componentDefinitions'; + /** @public */ export type AsProps = | 'div' @@ -131,3 +133,36 @@ export interface UtilityProps extends SpaceProps { justifyContent?: Responsive; rowSpan?: Responsive; } + +// Base types for the component styles structure +type ClassNamesMap = Record; +type DataAttributeValues = readonly (string | number | boolean)[]; +type DataAttributesMap = Record; + +export interface ComponentDefinition { + classNames: ClassNamesMap; + dataAttributes?: DataAttributesMap; +} + +// Type utilities for extracting information from the component styles +export type ComponentDefinitionName = keyof typeof componentDefinitions; + +export type ComponentClassNames = + (typeof componentDefinitions)[T]['classNames']; + +export type ComponentDataAttributes = + (typeof componentDefinitions)[T] extends { dataAttributes: infer DA } + ? DA + : Record; + +export type ComponentDataAttributeProps = { + [K in keyof ComponentDataAttributes]?: ComponentDataAttributes[K] extends readonly (infer U)[] + ? U + : never; +}; + +// Helper type to check if a component has data attributes +export type HasDataAttributes = + (typeof componentDefinitions)[T] extends { dataAttributes: any } + ? true + : false; diff --git a/packages/canon/src/utils/componentDefinitions.ts b/packages/canon/src/utils/componentDefinitions.ts new file mode 100644 index 0000000000..9d6926a002 --- /dev/null +++ b/packages/canon/src/utils/componentDefinitions.ts @@ -0,0 +1,281 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ComponentDefinition } from '../types'; + +export const componentDefinitions = { + // Layout Components + Box: { + classNames: { + root: 'canon-Box', + }, + }, + + Container: { + classNames: { + root: 'canon-Container', + }, + }, + + Flex: { + classNames: { + root: 'canon-Flex', + }, + }, + + Grid: { + classNames: { + root: 'canon-Grid', + }, + }, + + // Typography Components + Text: { + classNames: { + root: 'canon-Text', + }, + dataAttributes: { + variant: ['body', 'caption', 'label'] as const, + weight: ['regular', 'medium', 'bold'] as const, + color: ['primary', 'secondary', 'muted'] as const, + truncate: [true, false] as const, + }, + }, + + Heading: { + classNames: { + root: 'canon-Heading', + }, + dataAttributes: { + variant: ['title1', 'title2', 'title3', 'subtitle'] as const, + color: ['primary', 'secondary', 'muted'] as const, + truncate: [true, false] as const, + }, + }, + + // Form Components + Button: { + classNames: { + root: 'canon-Button', + }, + dataAttributes: { + size: ['small', 'medium', 'large'] as const, + variant: ['primary', 'secondary', 'ghost'] as const, + }, + }, + + ButtonIcon: { + classNames: { + root: 'canon-ButtonIcon', + }, + dataAttributes: { + size: ['small', 'medium', 'large'] as const, + variant: ['primary', 'secondary', 'ghost'] as const, + }, + }, + + ButtonLink: { + classNames: { + root: 'canon-ButtonLink', + }, + }, + + TextField: { + classNames: { + root: 'canon-TextField', + input: 'canon-TextField-input', + label: 'canon-TextField-label', + error: 'canon-TextField-error', + helper: 'canon-TextField-helper', + container: 'canon-TextField-container', + }, + dataAttributes: { + invalid: [true, false] as const, + disabled: [true, false] as const, + }, + }, + + FieldLabel: { + classNames: { + root: 'canon-FieldLabel', + required: 'canon-FieldLabel-required', + }, + dataAttributes: { + required: [true, false] as const, + }, + }, + + Select: { + classNames: { + root: 'canon-Select', + trigger: 'canon-Select-trigger', + content: 'canon-Select-content', + item: 'canon-Select-item', + value: 'canon-Select-value', + icon: 'canon-Select-icon', + separator: 'canon-Select-separator', + }, + dataAttributes: { + invalid: [true, false] as const, + disabled: [true, false] as const, + open: [true, false] as const, + }, + }, + + Checkbox: { + classNames: { + root: 'canon-Checkbox', + input: 'canon-Checkbox-input', + indicator: 'canon-Checkbox-indicator', + label: 'canon-Checkbox-label', + }, + dataAttributes: { + checked: [true, false] as const, + indeterminate: [true, false] as const, + disabled: [true, false] as const, + }, + }, + + Switch: { + classNames: { + root: 'canon-Switch', + thumb: 'canon-Switch-thumb', + track: 'canon-Switch-track', + }, + dataAttributes: { + checked: [true, false] as const, + disabled: [true, false] as const, + }, + }, + + // Navigation Components + Link: { + classNames: { + root: 'canon-Link', + }, + dataAttributes: { + variant: ['primary', 'secondary'] as const, + }, + }, + + Menu: { + classNames: { + root: 'canon-Menu', + trigger: 'canon-Menu-trigger', + content: 'canon-Menu-content', + item: 'canon-Menu-item', + separator: 'canon-Menu-separator', + label: 'canon-Menu-label', + group: 'canon-Menu-group', + }, + }, + + Tabs: { + classNames: { + root: 'canon-Tabs', + list: 'canon-Tabs-list', + tab: 'canon-Tabs-tab', + panel: 'canon-Tabs-panel', + trigger: 'canon-Tabs-trigger', + }, + }, + + // Data Display Components + Table: { + classNames: { + root: 'canon-Table', + header: 'canon-Table-header', + body: 'canon-Table-body', + footer: 'canon-Table-footer', + row: 'canon-Table-row', + cell: 'canon-Table-cell', + headerCell: 'canon-Table-headerCell', + caption: 'canon-Table-caption', + }, + }, + + DataTable: { + classNames: { + root: 'canon-DataTable', + container: 'canon-DataTable-container', + header: 'canon-DataTable-header', + body: 'canon-DataTable-body', + row: 'canon-DataTable-row', + cell: 'canon-DataTable-cell', + headerCell: 'canon-DataTable-headerCell', + toolbar: 'canon-DataTable-toolbar', + pagination: 'canon-DataTable-pagination', + search: 'canon-DataTable-search', + filters: 'canon-DataTable-filters', + }, + }, + + Avatar: { + classNames: { + root: 'canon-Avatar', + image: 'canon-Avatar-image', + fallback: 'canon-Avatar-fallback', + indicator: 'canon-Avatar-indicator', + }, + dataAttributes: { + size: ['small', 'medium', 'large'] as const, + }, + }, + + Icon: { + classNames: { + root: 'canon-Icon', + }, + dataAttributes: { + size: ['small', 'medium', 'large'] as const, + }, + }, + + // Feedback Components + Tooltip: { + classNames: { + root: 'canon-Tooltip', + trigger: 'canon-Tooltip-trigger', + content: 'canon-Tooltip-content', + arrow: 'canon-Tooltip-arrow', + }, + }, + + // Disclosure Components + Collapsible: { + classNames: { + root: 'canon-Collapsible', + trigger: 'canon-Collapsible-trigger', + content: 'canon-Collapsible-content', + icon: 'canon-Collapsible-icon', + }, + dataAttributes: { + open: [true, false] as const, + }, + }, + + // Utility Components + ScrollArea: { + classNames: { + root: 'canon-ScrollArea', + viewport: 'canon-ScrollArea-viewport', + scrollbar: 'canon-ScrollArea-scrollbar', + thumb: 'canon-ScrollArea-thumb', + corner: 'canon-ScrollArea-corner', + track: 'canon-ScrollArea-track', + }, + }, +} as const satisfies Record; From e2d70b7f5e391c6c839a5301f4f31acb51e94e5e Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 20 Jun 2025 14:58:52 +0100 Subject: [PATCH 230/301] replace buttons Signed-off-by: Charles de Dreuille --- .../canon/src/components/Avatar/Avatar.tsx | 44 +++++++----- .../canon/src/components/Button/Button.tsx | 2 +- .../src/components/ButtonIcon/ButtonIcon.tsx | 15 ++-- .../src/components/ButtonLink/ButtonLink.tsx | 15 ++-- packages/canon/src/hooks/useStyles.ts | 11 ++- .../canon/src/utils/componentDefinitions.ts | 69 ++++++++----------- 6 files changed, 82 insertions(+), 74 deletions(-) diff --git a/packages/canon/src/components/Avatar/Avatar.tsx b/packages/canon/src/components/Avatar/Avatar.tsx index 472bec39fb..4e899170de 100644 --- a/packages/canon/src/components/Avatar/Avatar.tsx +++ b/packages/canon/src/components/Avatar/Avatar.tsx @@ -18,28 +18,36 @@ import { forwardRef, ElementRef } from 'react'; import { Avatar as AvatarPrimitive } from '@base-ui-components/react/avatar'; import clsx from 'clsx'; import { AvatarProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; /** @public */ export const Avatar = forwardRef< ElementRef, AvatarProps ->(({ className, src, name, size = 'medium', ...props }, ref) => ( - - - - {(name || '') - .split(' ') - .map(word => word[0]) - .join('') - .toLocaleUpperCase('en-US') - .slice(0, 2)} - - -)); +>((props, ref) => { + const { className, src, name, size = 'medium', ...rest } = props; + const { classNames } = useStyles('Avatar', { + size, + }); + + return ( + + + + {(name || '') + .split(' ') + .map(word => word[0]) + .join('') + .toLocaleUpperCase('en-US') + .slice(0, 2)} + + + ); +}); Avatar.displayName = AvatarPrimitive.Root.displayName; diff --git a/packages/canon/src/components/Button/Button.tsx b/packages/canon/src/components/Button/Button.tsx index 29593ea922..40879e51ed 100644 --- a/packages/canon/src/components/Button/Button.tsx +++ b/packages/canon/src/components/Button/Button.tsx @@ -18,7 +18,7 @@ import clsx from 'clsx'; import { forwardRef, Ref } from 'react'; import { Button as RAButton } from 'react-aria-components'; import type { ButtonProps } from './types'; -import { useStyles } from '../../definition'; +import { useStyles } from '../../hooks/useStyles'; /** @public */ export const Button = forwardRef( diff --git a/packages/canon/src/components/ButtonIcon/ButtonIcon.tsx b/packages/canon/src/components/ButtonIcon/ButtonIcon.tsx index 43c2f598ac..072ed20963 100644 --- a/packages/canon/src/components/ButtonIcon/ButtonIcon.tsx +++ b/packages/canon/src/components/ButtonIcon/ButtonIcon.tsx @@ -17,8 +17,8 @@ import clsx from 'clsx'; import { forwardRef, Ref } from 'react'; import { Button as RAButton } from 'react-aria-components'; -import { useResponsiveValue } from '../../hooks/useResponsiveValue'; import type { ButtonIconProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; /** @public */ export const ButtonIcon = forwardRef( @@ -32,15 +32,18 @@ export const ButtonIcon = forwardRef( ...rest } = props; - const responsiveSize = useResponsiveValue(size); - const responsiveVariant = useResponsiveValue(variant); + const { classNames, dataAttributes } = useStyles('Button', { + size, + variant, + }); + + const { classNames: classNamesButtonIcon } = useStyles('ButtonIcon'); return ( {icon} diff --git a/packages/canon/src/components/ButtonLink/ButtonLink.tsx b/packages/canon/src/components/ButtonLink/ButtonLink.tsx index cb68401160..153643a530 100644 --- a/packages/canon/src/components/ButtonLink/ButtonLink.tsx +++ b/packages/canon/src/components/ButtonLink/ButtonLink.tsx @@ -17,8 +17,8 @@ import clsx from 'clsx'; import { forwardRef, Ref } from 'react'; import { Link as RALink } from 'react-aria-components'; -import { useResponsiveValue } from '../../hooks/useResponsiveValue'; import type { ButtonLinkProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; /** @public */ export const ButtonLink = forwardRef( @@ -33,15 +33,18 @@ export const ButtonLink = forwardRef( ...rest } = props; - const responsiveSize = useResponsiveValue(size); - const responsiveVariant = useResponsiveValue(variant); + const { classNames, dataAttributes } = useStyles('Button', { + size, + variant, + }); + + const { classNames: classNamesButtonLink } = useStyles('ButtonLink'); return ( {iconStart} diff --git a/packages/canon/src/hooks/useStyles.ts b/packages/canon/src/hooks/useStyles.ts index d6c9d0c76b..19f9823236 100644 --- a/packages/canon/src/hooks/useStyles.ts +++ b/packages/canon/src/hooks/useStyles.ts @@ -15,7 +15,7 @@ */ import { useBreakpoint, breakpoints } from './useBreakpoint'; import { componentDefinitions } from '../utils/componentDefinitions'; -import type { ComponentDefinitionName } from '../types'; +import type { ComponentDefinitionName, ComponentClassNames } from '../types'; /** * Resolve a responsive value based on the current breakpoint @@ -61,9 +61,14 @@ function resolveResponsiveValue( export function useStyles( componentName: T, props: Record = {}, -) { +): { + classNames: ComponentClassNames; + dataAttributes: Record; + resolvedProps: Record; +} { const { breakpoint } = useBreakpoint(); - const classNames = componentDefinitions[componentName].classNames; + const classNames = componentDefinitions[componentName] + .classNames as ComponentClassNames; // Resolve responsive values and generate data attributes const dataAttributes: Record = {}; diff --git a/packages/canon/src/utils/componentDefinitions.ts b/packages/canon/src/utils/componentDefinitions.ts index 9d6926a002..97261f80e7 100644 --- a/packages/canon/src/utils/componentDefinitions.ts +++ b/packages/canon/src/utils/componentDefinitions.ts @@ -17,12 +17,40 @@ import type { ComponentDefinition } from '../types'; export const componentDefinitions = { - // Layout Components + Avatar: { + classNames: { + root: 'canon-AvatarRoot', + image: 'canon-AvatarImage', + fallback: 'canon-AvatarFallback', + }, + dataAttributes: { + size: ['small', 'medium', 'large'] as const, + }, + }, Box: { classNames: { root: 'canon-Box', }, }, + Button: { + classNames: { + root: 'canon-Button', + }, + dataAttributes: { + size: ['small', 'medium', 'large'] as const, + variant: ['primary', 'secondary', 'ghost'] as const, + }, + }, + ButtonIcon: { + classNames: { + root: 'canon-ButtonIcon', + }, + }, + ButtonLink: { + classNames: { + root: 'canon-ButtonLink', + }, + }, Container: { classNames: { @@ -66,33 +94,6 @@ export const componentDefinitions = { }, }, - // Form Components - Button: { - classNames: { - root: 'canon-Button', - }, - dataAttributes: { - size: ['small', 'medium', 'large'] as const, - variant: ['primary', 'secondary', 'ghost'] as const, - }, - }, - - ButtonIcon: { - classNames: { - root: 'canon-ButtonIcon', - }, - dataAttributes: { - size: ['small', 'medium', 'large'] as const, - variant: ['primary', 'secondary', 'ghost'] as const, - }, - }, - - ButtonLink: { - classNames: { - root: 'canon-ButtonLink', - }, - }, - TextField: { classNames: { root: 'canon-TextField', @@ -223,18 +224,6 @@ export const componentDefinitions = { }, }, - Avatar: { - classNames: { - root: 'canon-Avatar', - image: 'canon-Avatar-image', - fallback: 'canon-Avatar-fallback', - indicator: 'canon-Avatar-indicator', - }, - dataAttributes: { - size: ['small', 'medium', 'large'] as const, - }, - }, - Icon: { classNames: { root: 'canon-Icon', From 4ef1850bf1d73660be8991f0b8e8b35f7785df78 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 20 Jun 2025 16:34:55 +0100 Subject: [PATCH 231/301] Fix report Signed-off-by: Charles de Dreuille --- packages/canon/report.api.md | 253 ++++++++++++++++++ packages/canon/src/types.ts | 51 ++-- .../canon/src/utils/componentDefinitions.ts | 4 + 3 files changed, 286 insertions(+), 22 deletions(-) diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index abe30ea93b..e382eb1cc7 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -243,6 +243,9 @@ export interface CheckboxProps { value?: string; } +// @public +export type ClassNamesMap = Record; + // @public export const Collapsible: { Root: ForwardRefExoticComponent< @@ -265,6 +268,250 @@ export const Collapsible: { // @public (undocumented) export type Columns = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 'auto'; +// @public +export type ComponentClassNames = + (typeof componentDefinitions)[T]['classNames']; + +// @public +export interface ComponentDefinition { + // (undocumented) + classNames: ClassNamesMap; + // (undocumented) + dataAttributes?: DataAttributesMap; +} + +// @public +export type ComponentDefinitionName = keyof typeof componentDefinitions; + +// @public +export const componentDefinitions: { + readonly Avatar: { + readonly classNames: { + readonly root: 'canon-AvatarRoot'; + readonly image: 'canon-AvatarImage'; + readonly fallback: 'canon-AvatarFallback'; + }; + readonly dataAttributes: { + readonly size: readonly ['small', 'medium', 'large']; + }; + }; + readonly Box: { + readonly classNames: { + readonly root: 'canon-Box'; + }; + }; + readonly Button: { + readonly classNames: { + readonly root: 'canon-Button'; + }; + readonly dataAttributes: { + readonly size: readonly ['small', 'medium', 'large']; + readonly variant: readonly ['primary', 'secondary', 'ghost']; + }; + }; + readonly ButtonIcon: { + readonly classNames: { + readonly root: 'canon-ButtonIcon'; + }; + }; + readonly ButtonLink: { + readonly classNames: { + readonly root: 'canon-ButtonLink'; + }; + }; + readonly Container: { + readonly classNames: { + readonly root: 'canon-Container'; + }; + }; + readonly Flex: { + readonly classNames: { + readonly root: 'canon-Flex'; + }; + }; + readonly Grid: { + readonly classNames: { + readonly root: 'canon-Grid'; + }; + }; + readonly Text: { + readonly classNames: { + readonly root: 'canon-Text'; + }; + readonly dataAttributes: { + readonly variant: readonly ['body', 'caption', 'label']; + readonly weight: readonly ['regular', 'medium', 'bold']; + readonly color: readonly ['primary', 'secondary', 'muted']; + readonly truncate: readonly [true, false]; + }; + }; + readonly Heading: { + readonly classNames: { + readonly root: 'canon-Heading'; + }; + readonly dataAttributes: { + readonly variant: readonly ['title1', 'title2', 'title3', 'subtitle']; + readonly color: readonly ['primary', 'secondary', 'muted']; + readonly truncate: readonly [true, false]; + }; + }; + readonly TextField: { + readonly classNames: { + readonly root: 'canon-TextField'; + readonly input: 'canon-TextField-input'; + readonly label: 'canon-TextField-label'; + readonly error: 'canon-TextField-error'; + readonly helper: 'canon-TextField-helper'; + readonly container: 'canon-TextField-container'; + }; + readonly dataAttributes: { + readonly invalid: readonly [true, false]; + readonly disabled: readonly [true, false]; + }; + }; + readonly FieldLabel: { + readonly classNames: { + readonly root: 'canon-FieldLabel'; + readonly required: 'canon-FieldLabel-required'; + }; + readonly dataAttributes: { + readonly required: readonly [true, false]; + }; + }; + readonly Select: { + readonly classNames: { + readonly root: 'canon-Select'; + readonly trigger: 'canon-Select-trigger'; + readonly content: 'canon-Select-content'; + readonly item: 'canon-Select-item'; + readonly value: 'canon-Select-value'; + readonly icon: 'canon-Select-icon'; + readonly separator: 'canon-Select-separator'; + }; + readonly dataAttributes: { + readonly invalid: readonly [true, false]; + readonly disabled: readonly [true, false]; + readonly open: readonly [true, false]; + }; + }; + readonly Checkbox: { + readonly classNames: { + readonly root: 'canon-Checkbox'; + readonly input: 'canon-Checkbox-input'; + readonly indicator: 'canon-Checkbox-indicator'; + readonly label: 'canon-Checkbox-label'; + }; + readonly dataAttributes: { + readonly checked: readonly [true, false]; + readonly indeterminate: readonly [true, false]; + readonly disabled: readonly [true, false]; + }; + }; + readonly Switch: { + readonly classNames: { + readonly root: 'canon-Switch'; + readonly thumb: 'canon-Switch-thumb'; + readonly track: 'canon-Switch-track'; + }; + readonly dataAttributes: { + readonly checked: readonly [true, false]; + readonly disabled: readonly [true, false]; + }; + }; + readonly Link: { + readonly classNames: { + readonly root: 'canon-Link'; + }; + readonly dataAttributes: { + readonly variant: readonly ['primary', 'secondary']; + }; + }; + readonly Menu: { + readonly classNames: { + readonly root: 'canon-Menu'; + readonly trigger: 'canon-Menu-trigger'; + readonly content: 'canon-Menu-content'; + readonly item: 'canon-Menu-item'; + readonly separator: 'canon-Menu-separator'; + readonly label: 'canon-Menu-label'; + readonly group: 'canon-Menu-group'; + }; + }; + readonly Tabs: { + readonly classNames: { + readonly root: 'canon-Tabs'; + readonly list: 'canon-Tabs-list'; + readonly tab: 'canon-Tabs-tab'; + readonly panel: 'canon-Tabs-panel'; + readonly trigger: 'canon-Tabs-trigger'; + }; + }; + readonly Table: { + readonly classNames: { + readonly root: 'canon-Table'; + readonly header: 'canon-Table-header'; + readonly body: 'canon-Table-body'; + readonly footer: 'canon-Table-footer'; + readonly row: 'canon-Table-row'; + readonly cell: 'canon-Table-cell'; + readonly headerCell: 'canon-Table-headerCell'; + readonly caption: 'canon-Table-caption'; + }; + }; + readonly DataTable: { + readonly classNames: { + readonly root: 'canon-DataTable'; + readonly container: 'canon-DataTable-container'; + readonly header: 'canon-DataTable-header'; + readonly body: 'canon-DataTable-body'; + readonly row: 'canon-DataTable-row'; + readonly cell: 'canon-DataTable-cell'; + readonly headerCell: 'canon-DataTable-headerCell'; + readonly toolbar: 'canon-DataTable-toolbar'; + readonly pagination: 'canon-DataTable-pagination'; + readonly search: 'canon-DataTable-search'; + readonly filters: 'canon-DataTable-filters'; + }; + }; + readonly Icon: { + readonly classNames: { + readonly root: 'canon-Icon'; + }; + readonly dataAttributes: { + readonly size: readonly ['small', 'medium', 'large']; + }; + }; + readonly Tooltip: { + readonly classNames: { + readonly root: 'canon-Tooltip'; + readonly trigger: 'canon-Tooltip-trigger'; + readonly content: 'canon-Tooltip-content'; + readonly arrow: 'canon-Tooltip-arrow'; + }; + }; + readonly Collapsible: { + readonly classNames: { + readonly root: 'canon-Collapsible'; + readonly trigger: 'canon-Collapsible-trigger'; + readonly content: 'canon-Collapsible-content'; + readonly icon: 'canon-Collapsible-icon'; + }; + readonly dataAttributes: { + readonly open: readonly [true, false]; + }; + }; + readonly ScrollArea: { + readonly classNames: { + readonly root: 'canon-ScrollArea'; + readonly viewport: 'canon-ScrollArea-viewport'; + readonly scrollbar: 'canon-ScrollArea-scrollbar'; + readonly thumb: 'canon-ScrollArea-thumb'; + readonly corner: 'canon-ScrollArea-corner'; + readonly track: 'canon-ScrollArea-track'; + }; + }; +}; + // @public (undocumented) export const Container: ForwardRefExoticComponent< ContainerProps & RefAttributes @@ -292,6 +539,12 @@ export interface ContainerProps { style?: React.CSSProperties; } +// @public +export type DataAttributesMap = Record; + +// @public +export type DataAttributeValues = readonly (string | number | boolean)[]; + // @public export const DataTable: { Root: ( diff --git a/packages/canon/src/types.ts b/packages/canon/src/types.ts index 056d4772ba..073aa0d6cb 100644 --- a/packages/canon/src/types.ts +++ b/packages/canon/src/types.ts @@ -134,35 +134,42 @@ export interface UtilityProps extends SpaceProps { rowSpan?: Responsive; } -// Base types for the component styles structure -type ClassNamesMap = Record; -type DataAttributeValues = readonly (string | number | boolean)[]; -type DataAttributesMap = Record; +/** + * Base type for the component styles structure + * @public + */ +export type ClassNamesMap = Record; +/** + * Base type for the component styles structure + * @public + */ +export type DataAttributeValues = readonly (string | number | boolean)[]; + +/** + * Base type for the component styles structure + * @public + */ +export type DataAttributesMap = Record; + +/** + * Base type for the component styles structure + * @public + */ export interface ComponentDefinition { classNames: ClassNamesMap; dataAttributes?: DataAttributesMap; } -// Type utilities for extracting information from the component styles +/** + * Type utilities for extracting information from the component styles + * @public + */ export type ComponentDefinitionName = keyof typeof componentDefinitions; +/** + * Helper type to extract class names for a component + * @public + */ export type ComponentClassNames = (typeof componentDefinitions)[T]['classNames']; - -export type ComponentDataAttributes = - (typeof componentDefinitions)[T] extends { dataAttributes: infer DA } - ? DA - : Record; - -export type ComponentDataAttributeProps = { - [K in keyof ComponentDataAttributes]?: ComponentDataAttributes[K] extends readonly (infer U)[] - ? U - : never; -}; - -// Helper type to check if a component has data attributes -export type HasDataAttributes = - (typeof componentDefinitions)[T] extends { dataAttributes: any } - ? true - : false; diff --git a/packages/canon/src/utils/componentDefinitions.ts b/packages/canon/src/utils/componentDefinitions.ts index 97261f80e7..009b61c9f0 100644 --- a/packages/canon/src/utils/componentDefinitions.ts +++ b/packages/canon/src/utils/componentDefinitions.ts @@ -16,6 +16,10 @@ import type { ComponentDefinition } from '../types'; +/** + * Component definitions for the Canon library + * @public + */ export const componentDefinitions = { Avatar: { classNames: { From 81835d38c2e026939bb70dafb488f330bc6c4848 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 20 Jun 2025 16:51:40 +0100 Subject: [PATCH 232/301] Add Checkbox Signed-off-by: Charles de Dreuille --- .../src/components/Checkbox/Checkbox.tsx | 5 +++- .../canon/src/utils/componentDefinitions.ts | 25 ++++++++----------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/canon/src/components/Checkbox/Checkbox.tsx b/packages/canon/src/components/Checkbox/Checkbox.tsx index 5bf6520891..8fbb4d40af 100644 --- a/packages/canon/src/components/Checkbox/Checkbox.tsx +++ b/packages/canon/src/components/Checkbox/Checkbox.tsx @@ -18,6 +18,7 @@ import { forwardRef } from 'react'; import { Checkbox as CheckboxPrimitive } from '@base-ui-components/react/checkbox'; import { Icon } from '@backstage/canon'; import type { CheckboxProps } from './types'; +import { useStyles } from '../../hooks/useStyles'; import clsx from 'clsx'; /** @public */ @@ -35,10 +36,12 @@ export const Checkbox = forwardRef( style, } = props; + const { classNames } = useStyles('Checkbox'); + const checkboxElement = ( Date: Fri, 20 Jun 2025 15:52:47 +0000 Subject: [PATCH 233/301] chore(deps): update dependency @playwright/test to v1.53.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index f27b409a61..3912e0e7fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14865,13 +14865,13 @@ __metadata: linkType: hard "@playwright/test@npm:^1.32.3": - version: 1.53.0 - resolution: "@playwright/test@npm:1.53.0" + version: 1.53.1 + resolution: "@playwright/test@npm:1.53.1" dependencies: - playwright: "npm:1.53.0" + playwright: "npm:1.53.1" bin: playwright: cli.js - checksum: 10/968df4fba133dd18b8c65504c3cc5a3a6071e49f0706c6524711cdfab321a51debfeb506b9ff0a8f7dd8ce3015921d82fa51429d8f11d392cc68de1938703c33 + checksum: 10/98fb9b962710183d465b695daab2006296fd9a703ecb1b763a38cd12a39f7d6066f9539d1758e54313d393353fafb16b90fb31e4add1ca99ffec99b8b1b40fb9 languageName: node linkType: hard @@ -42032,27 +42032,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.53.0": - version: 1.53.0 - resolution: "playwright-core@npm:1.53.0" +"playwright-core@npm:1.53.1": + version: 1.53.1 + resolution: "playwright-core@npm:1.53.1" bin: playwright-core: cli.js - checksum: 10/881f27a9b7edd9954700489a5a4212cb91bcada226fd1d79a239b2eab0f333df1e2e41e275e6fa846d7f57c6a92afe14dca33ca7a2ce303dfb687d02511b7c69 + checksum: 10/d0ea8674c3abb76069255ca81bc0dfdef3f9548207f1404eec036bb8724135710f25ee791bfd7c043d5b9c2ccfa42288b0308d61dc5efc60a13b18811a15c4cd languageName: node linkType: hard -"playwright@npm:1.53.0": - version: 1.53.0 - resolution: "playwright@npm:1.53.0" +"playwright@npm:1.53.1": + version: 1.53.1 + resolution: "playwright@npm:1.53.1" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.53.0" + playwright-core: "npm:1.53.1" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10/0b0258630f39b4d6ff1555d008ee4d591fe45cbe1e0f643a612397e3e6b1f7a99a2037a957eaa7351edd907ba10966ba105b2d244eafd1b247378910b660f086 + checksum: 10/74b3178d5ae3fde8de08fe6c221578530368f1abb8794fce234d06e0043178201eb3b7410354418517f23c105bd54ac1432da5f46c50353a5e6a80198a95f2cf languageName: node linkType: hard From a8a8514b67fb01ddd98fc6831978d59cf2639133 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 20 Jun 2025 16:55:26 +0100 Subject: [PATCH 234/301] Create shaggy-cobras-rhyme.md Signed-off-by: Charles de Dreuille --- .changeset/shaggy-cobras-rhyme.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/shaggy-cobras-rhyme.md diff --git a/.changeset/shaggy-cobras-rhyme.md b/.changeset/shaggy-cobras-rhyme.md new file mode 100644 index 0000000000..d8adefd112 --- /dev/null +++ b/.changeset/shaggy-cobras-rhyme.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +We are transforming how we structure our class names and data attributes definitions for all components. They are now all set in the same place. From 95f7e4e9bd29198497a23a0b5464a536359d9a8c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 16:46:04 +0000 Subject: [PATCH 235/301] chore(deps): update dependency @types/lodash to v4.17.18 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3912e0e7fb..bde6730c01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21782,9 +21782,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.151": - version: 4.17.17 - resolution: "@types/lodash@npm:4.17.17" - checksum: 10/496459a3cb1a0733bb60532de3899ad6297717af0b9b26ad6821154b2005fec86f29ccd47a2e6f4da4a8c7c818bb8ae73901144e8057ea86b7b02a3d7bb9d13f + version: 4.17.18 + resolution: "@types/lodash@npm:4.17.18" + checksum: 10/54ebb15b29925112dbe9da3abd99fb80d7202bc5ba20fc1b4fc8ea835d0012f00cbd9a3e7f367b70e7c3f2d5ee635964e3920a489625647b558f02994b3dd381 languageName: node linkType: hard From 6525f78e755a8aede427db03fff4a989711dcd6b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 17:36:11 +0000 Subject: [PATCH 236/301] fix(deps): update dependency @dagrejs/dagre to v1.1.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index bde6730c01..9e13c331b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9105,11 +9105,11 @@ __metadata: linkType: hard "@dagrejs/dagre@npm:^1.1.4": - version: 1.1.4 - resolution: "@dagrejs/dagre@npm:1.1.4" + version: 1.1.5 + resolution: "@dagrejs/dagre@npm:1.1.5" dependencies: "@dagrejs/graphlib": "npm:2.2.4" - checksum: 10/0b3744b170c68ae0666e03aca19c3100d5131feafeb54b3ea096b749a9f0fe5385b8bd8889c11a49493cfab945b2486b9e30bc41b321755ed718e9f5cb4b74f1 + checksum: 10/c00abd1e04d19f90ad8dfa0a4e16365371bc4309affead3827a1b39f6b0b946643b8af0b1e5519011deca3fda4c7471b27e9ebb03423309a94f95ac0b881ac4f languageName: node linkType: hard From 948ce38fcb9068750d64d7fd3d6d048333e4f095 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 18:18:17 +0000 Subject: [PATCH 237/301] fix(deps): update nextjs monorepo to v15.3.4 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- canon-docs/package.json | 6 +-- canon-docs/yarn.lock | 114 ++++++++++++++++++++-------------------- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/canon-docs/package.json b/canon-docs/package.json index c9bc1ae96b..dba4abb78f 100644 --- a/canon-docs/package.json +++ b/canon-docs/package.json @@ -17,13 +17,13 @@ "@lezer/highlight": "^1.2.1", "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", - "@next/mdx": "15.3.3", + "@next/mdx": "15.3.4", "@storybook/react": "^8.6.8", "@uiw/codemirror-themes": "^4.23.7", "@uiw/react-codemirror": "^4.23.7", "html-react-parser": "^5.2.5", "motion": "^12.4.1", - "next": "15.3.3", + "next": "15.3.4", "next-mdx-remote-client": "^2.1.2", "react": "19.1.0", "react-dom": "19.1.0", @@ -37,7 +37,7 @@ "@types/react": "19.1.8", "@types/react-dom": "19.1.6", "eslint": "^8", - "eslint-config-next": "15.3.3", + "eslint-config-next": "15.3.4", "lightningcss": "^1.28.2", "typescript": "^5" } diff --git a/canon-docs/yarn.lock b/canon-docs/yarn.lock index 9232edb7af..7cfd30ebfe 100644 --- a/canon-docs/yarn.lock +++ b/canon-docs/yarn.lock @@ -732,25 +732,25 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:15.3.3": - version: 15.3.3 - resolution: "@next/env@npm:15.3.3" - checksum: 10/f71fd8b397c60b80ee2ba836152d2c8bc772f4f840b976bfd82ca0c1452539b4832a1ce6702a90da1d4edb874383944ea2662add5c0729423cd4d1cb2e58fc3d +"@next/env@npm:15.3.4": + version: 15.3.4 + resolution: "@next/env@npm:15.3.4" + checksum: 10/40ea0bee2eca72dce6102d30ac50029309bf68a281585270e38f920ae47043f240352ad250058d462169a436dea3d8f78a935406ee61088bd4a614524b4a315b languageName: node linkType: hard -"@next/eslint-plugin-next@npm:15.3.3": - version: 15.3.3 - resolution: "@next/eslint-plugin-next@npm:15.3.3" +"@next/eslint-plugin-next@npm:15.3.4": + version: 15.3.4 + resolution: "@next/eslint-plugin-next@npm:15.3.4" dependencies: fast-glob: "npm:3.3.1" - checksum: 10/08b19a39fa01ac69c7b117a29ebdb193353a29cb3d51b2e3f72fabd7f2b014d08e37d819aed8e440a8a0a6e1acd9053dbf939b5f76603e6a87de0042fbf0bd4b + checksum: 10/8a473bd32a06f62c16f60f9c40b7b8149cf91170ce4c1546cda57e9c85ac8481c7ad3aa4a77e8c3c7000e80d3de809d2db7df6cb79e13fd258fff382729abf60 languageName: node linkType: hard -"@next/mdx@npm:15.3.3": - version: 15.3.3 - resolution: "@next/mdx@npm:15.3.3" +"@next/mdx@npm:15.3.4": + version: 15.3.4 + resolution: "@next/mdx@npm:15.3.4" dependencies: source-map: "npm:^0.7.0" peerDependencies: @@ -761,62 +761,62 @@ __metadata: optional: true "@mdx-js/react": optional: true - checksum: 10/5583f01e986322bee6eae1052fce27964b33796b8fbd2ea360340a65ccf60cd348c614524bcbf8323be178cace1da5c74f45f8a111cf9e62682aad3a0a9d12e3 + checksum: 10/07904beda049317e43857f9dfe659adb6e57c4417475de1d21a7605f8ace5d9aa992a06465d1a94fe4b62331901765b15659555e8aadedbd410f86df1bebd1bd languageName: node linkType: hard -"@next/swc-darwin-arm64@npm:15.3.3": - version: 15.3.3 - resolution: "@next/swc-darwin-arm64@npm:15.3.3" +"@next/swc-darwin-arm64@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-darwin-arm64@npm:15.3.4" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@next/swc-darwin-x64@npm:15.3.3": - version: 15.3.3 - resolution: "@next/swc-darwin-x64@npm:15.3.3" +"@next/swc-darwin-x64@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-darwin-x64@npm:15.3.4" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@next/swc-linux-arm64-gnu@npm:15.3.3": - version: 15.3.3 - resolution: "@next/swc-linux-arm64-gnu@npm:15.3.3" +"@next/swc-linux-arm64-gnu@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-linux-arm64-gnu@npm:15.3.4" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:15.3.3": - version: 15.3.3 - resolution: "@next/swc-linux-arm64-musl@npm:15.3.3" +"@next/swc-linux-arm64-musl@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-linux-arm64-musl@npm:15.3.4" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:15.3.3": - version: 15.3.3 - resolution: "@next/swc-linux-x64-gnu@npm:15.3.3" +"@next/swc-linux-x64-gnu@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-linux-x64-gnu@npm:15.3.4" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:15.3.3": - version: 15.3.3 - resolution: "@next/swc-linux-x64-musl@npm:15.3.3" +"@next/swc-linux-x64-musl@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-linux-x64-musl@npm:15.3.4" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:15.3.3": - version: 15.3.3 - resolution: "@next/swc-win32-arm64-msvc@npm:15.3.3" +"@next/swc-win32-arm64-msvc@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-win32-arm64-msvc@npm:15.3.4" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@next/swc-win32-x64-msvc@npm:15.3.3": - version: 15.3.3 - resolution: "@next/swc-win32-x64-msvc@npm:15.3.3" +"@next/swc-win32-x64-msvc@npm:15.3.4": + version: 15.3.4 + resolution: "@next/swc-win32-x64-msvc@npm:15.3.4" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -1839,7 +1839,7 @@ __metadata: "@lezer/highlight": "npm:^1.2.1" "@mdx-js/loader": "npm:^3.1.0" "@mdx-js/react": "npm:^3.1.0" - "@next/mdx": "npm:15.3.3" + "@next/mdx": "npm:15.3.4" "@storybook/react": "npm:^8.6.8" "@types/mdx": "npm:^2.0.13" "@types/node": "npm:^20" @@ -1848,11 +1848,11 @@ __metadata: "@uiw/codemirror-themes": "npm:^4.23.7" "@uiw/react-codemirror": "npm:^4.23.7" eslint: "npm:^8" - eslint-config-next: "npm:15.3.3" + eslint-config-next: "npm:15.3.4" html-react-parser: "npm:^5.2.5" lightningcss: "npm:^1.28.2" motion: "npm:^12.4.1" - next: "npm:15.3.3" + next: "npm:15.3.4" next-mdx-remote-client: "npm:^2.1.2" react: "npm:19.1.0" react-dom: "npm:19.1.0" @@ -2505,11 +2505,11 @@ __metadata: languageName: node linkType: hard -"eslint-config-next@npm:15.3.3": - version: 15.3.3 - resolution: "eslint-config-next@npm:15.3.3" +"eslint-config-next@npm:15.3.4": + version: 15.3.4 + resolution: "eslint-config-next@npm:15.3.4" dependencies: - "@next/eslint-plugin-next": "npm:15.3.3" + "@next/eslint-plugin-next": "npm:15.3.4" "@rushstack/eslint-patch": "npm:^1.10.3" "@typescript-eslint/eslint-plugin": "npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0" "@typescript-eslint/parser": "npm:^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0" @@ -2525,7 +2525,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10/63c51a9e4cc3f54073d15aff85b1088459439169721c379af1202df62c3a757c3849a194f46d4ec95a9c202322aa25d1ddab24159addcca290532a09afda3532 + checksum: 10/6c21254d3383b9158ff5f3b2881cc702bee3d2635b4326757965945691f6e65e25fdfef4f2964382fb4b2f52d9f03b929cb71d267709727df7365e7da80c8c3a languageName: node linkType: hard @@ -4623,19 +4623,19 @@ __metadata: languageName: node linkType: hard -"next@npm:15.3.3": - version: 15.3.3 - resolution: "next@npm:15.3.3" +"next@npm:15.3.4": + version: 15.3.4 + resolution: "next@npm:15.3.4" dependencies: - "@next/env": "npm:15.3.3" - "@next/swc-darwin-arm64": "npm:15.3.3" - "@next/swc-darwin-x64": "npm:15.3.3" - "@next/swc-linux-arm64-gnu": "npm:15.3.3" - "@next/swc-linux-arm64-musl": "npm:15.3.3" - "@next/swc-linux-x64-gnu": "npm:15.3.3" - "@next/swc-linux-x64-musl": "npm:15.3.3" - "@next/swc-win32-arm64-msvc": "npm:15.3.3" - "@next/swc-win32-x64-msvc": "npm:15.3.3" + "@next/env": "npm:15.3.4" + "@next/swc-darwin-arm64": "npm:15.3.4" + "@next/swc-darwin-x64": "npm:15.3.4" + "@next/swc-linux-arm64-gnu": "npm:15.3.4" + "@next/swc-linux-arm64-musl": "npm:15.3.4" + "@next/swc-linux-x64-gnu": "npm:15.3.4" + "@next/swc-linux-x64-musl": "npm:15.3.4" + "@next/swc-win32-arm64-msvc": "npm:15.3.4" + "@next/swc-win32-x64-msvc": "npm:15.3.4" "@swc/counter": "npm:0.1.3" "@swc/helpers": "npm:0.5.15" busboy: "npm:1.6.0" @@ -4680,7 +4680,7 @@ __metadata: optional: true bin: next: dist/bin/next - checksum: 10/287e3b24aade1763b13f95246d7df0e23d5fdde2d8c4b170360b22fabfa38184e102eb64886260e0034f4fc6979a1d47929e423d7946f5da57d306034645f6c7 + checksum: 10/bcc781ca5abba6a29408431f81b57598708871796fe4c70d1187cc92ca4905467a6a3cde32840cb7911099d54a93c1543c130e4dea2b59dbf9753f5850a8efdf languageName: node linkType: hard From dd47e7bdebd03988f97f4dadb7043018af2e4735 Mon Sep 17 00:00:00 2001 From: Jamie Tang Date: Fri, 20 Jun 2025 14:42:36 -0700 Subject: [PATCH 238/301] Update StepInitAnalyzeUrl.tsx Signed-off-by: Jamie Tang --- .../src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx index 5a6cad055c..2100d3da81 100644 --- a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx +++ b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx @@ -130,7 +130,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { } } } catch (e: any) { - setError(e?.data?.error?.message ?? e.message); + setError(e?.body?.error?.message ?? e.message); setSubmitted(false); } }, From 406b8b8e6a708567caa5d159b94cfb787e236bee Mon Sep 17 00:00:00 2001 From: Jamie Tang Date: Fri, 20 Jun 2025 15:31:57 -0700 Subject: [PATCH 239/301] Added changeset Signed-off-by: Jamie Tang --- .changeset/stupid-donuts-sit.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/stupid-donuts-sit.md diff --git a/.changeset/stupid-donuts-sit.md b/.changeset/stupid-donuts-sit.md new file mode 100644 index 0000000000..e829d49843 --- /dev/null +++ b/.changeset/stupid-donuts-sit.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': patch +--- + +Fixed bug with error message since ResponseError is now thrown from CatalogClient From 3772e0c73527f1173f7c90ae062c62e9e747df43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Jerna=C5=9B?= Date: Sat, 21 Jun 2025 21:19:11 +0200 Subject: [PATCH 240/301] docs(scaffolder): Remove old JSON schema version from custom action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Łukasz Jernaś --- .../writing-custom-actions.md | 78 +++++-------------- 1 file changed, 18 insertions(+), 60 deletions(-) diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index 26773c15c6..1a64406b04 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -12,7 +12,7 @@ by writing custom actions which can be used alongside our When adding custom actions, the actions array will **replace the built-in actions too**. Meaning, you will no longer be able to use them. -If you want to continue using the builtin actions, include them in the actions +If you want to continue using the builtin actions, include them in the `actions` array when registering your custom actions, as seen below. ::: @@ -52,19 +52,20 @@ its generated unit test. We will replace the existing placeholder code with our import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import fs from 'fs-extra'; -import { z } from 'zod'; +import { type z } from 'zod'; export const createNewFileAction = () => { return createTemplateAction({ id: 'acme:file:create', description: 'Create an Acme file.', schema: { - input: z.object({ - contents: z.string().describe('The contents of the file'), - filename: z - .string() - .describe('The filename of the file that will be created'), - }), + input: { + contents: z => z.string({ description: 'The contents of the file' }), + filename: z => + z.string({ + description: 'The filename of the file that will be created', + }), + }, }, async handler(ctx) { @@ -95,53 +96,11 @@ The `createTemplateAction` takes an object which specifies the following: function using `ctx.output` - `handler` - the actual code which is run as part of the action, with a context -You can also choose to define your custom action using JSON schema instead of `zod`: - -```ts title="With JSON Schema" -import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import { writeFile } from 'fs'; - -export const createNewFileAction = () => { - return createTemplateAction<{ contents: string; filename: string }>({ - id: 'acme:file:create', - description: 'Create an Acme file.', - schema: { - input: { - required: ['contents', 'filename'], - type: 'object', - properties: { - contents: { - type: 'string', - title: 'Contents', - description: 'The contents of the file', - }, - filename: { - type: 'string', - title: 'Filename', - description: 'The filename of the file that will be created', - }, - }, - }, - }, - async handler(ctx) { - const { signal } = ctx; - await writeFile( - resolveSafeChildPath(ctx.workspacePath, ctx.input.filename), - ctx.input.contents, - { signal }, - _ => {}, - ); - }, - }); -}; -``` - ### Naming Conventions Try to keep names consistent for both your own custom actions, and any actions contributed to open source. We've found that a separation of `:` and using a verb as the last part of the name works well. -We follow `provider:entity:verb` or as close to this as possible for our built in actions. For example, +We follow `provider:entity:verb` or as close to this as possible for our built-in actions. For example, `github:actions:create` or `github:repo:create`. Also feel free to use your company name to namespace them if you prefer too, for example `acme:file:create` like above. @@ -151,14 +110,14 @@ and writing of template entity definitions. ### Adding a TemplateExample -A TemplateExample is a way to document different ways that your custom action can be used. Once added it will be visible +A TemplateExample is a way to document different ways that your custom action can be used. Once added, it will be visible in your Backstage instance under the [/create/actions](https://demo.backstage.io/create/actions) path. You can have multiple examples for one action that can demonstrate different combinations of inputs and how to use them. #### Define TemplateExamples Below is a sample TemplateExample that is used for `publish:github`. The source code is available -on [github](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/src/actions/github.examples.ts) +on [GitHub](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/src/actions/github.examples.ts) and preview on [demo.backstage.io/create/actions](https://demo.backstage.io/create/actions#publish-github) ```ts title="With JSON Schema" @@ -222,7 +181,7 @@ return createTemplateAction({ #### Test TemplateAction examples It is also possible to test your example TemplateActions. You can see a sample test -on [github](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts) +on [GitHub](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts) ### The context object @@ -234,13 +193,12 @@ argument. It looks like the following: implement [idempotency of the actions](https://github.com/backstage/backstage/tree/master/beps/0004-scaffolder-task-idempotency) by not re-running the same function again if it was executed successfully on the previous run. -- `ctx.logger` - a Winston logger for additional logging inside your action -- `ctx.logStream` - a stream version of the logger if needed +- `ctx.logger` - a [LoggerService](../../backend-system/core-services/logger.md) instance for additional logging inside your action - `ctx.workspacePath` - a string of the working directory of the template run - `ctx.input` - an object which should match the `zod` or JSON schema provided in the `schema.input` part of the action definition - `ctx.output` - a function which you can call to set outputs that match the - JSON schema or `zod` in `schema.output` for ex. `ctx.output('downloadUrl', myDownloadUrl)` + `zod` schema in `schema.output` for ex. `ctx.output('downloadUrl', myDownloadUrl)` - `createTemporaryDirectory` a function to call to give you a temporary directory somewhere on the runner, so you can store some files there rather than polluting the `workspacePath` @@ -249,7 +207,7 @@ argument. It looks like the following: ## Registering Custom Actions -To register your new custom action in the Backend System you will need to create a backend module. Here is a very +To register your new custom action in the Backend System, you will need to create a backend module. Here is a very simplified example of how to do that: ```ts title="packages/backend/src/index.ts" @@ -327,8 +285,8 @@ const res = await ctx.checkpoint?.({ }); ``` -You have to define the unique key in scope of the scaffolder task for your checkpoint. During the execution task engine -will check if the checkpoint with such key was already executed or not, if yes, and the run was successful, the callback +You have to define the unique key in the scope of the scaffolder task for your checkpoint. During the execution task engine +will check if the checkpoint with such a key was already executed or not, if yes, and the run was successful, the callback will be skipped and instead the stored value will be returned. Whenever you change the return type of the checkpoint, we encourage you to change the ID. From 9a5a73ff02d19aa67e54633bb19d3668ba10ea38 Mon Sep 17 00:00:00 2001 From: Adam Kunicki Date: Sat, 21 Jun 2025 09:38:45 -0700 Subject: [PATCH 241/301] Fix addTopic migration in notifications-backend plugin The migration code fails when user_settings is non-empty because it adds a non-nullable constraint before DML is applied. This fixes that issue and adds a test case for that particular migration. Signed-off-by: Adam Kunicki --- .changeset/red-trams-join.md | 5 + .../migrations/20250317_addTopic.js | 12 +-- .../src/tests/migrations.test.ts | 99 +++++++++++++++++++ 3 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 .changeset/red-trams-join.md create mode 100644 plugins/notifications-backend/src/tests/migrations.test.ts diff --git a/.changeset/red-trams-join.md b/.changeset/red-trams-join.md new file mode 100644 index 0000000000..3ee245e2a8 --- /dev/null +++ b/.changeset/red-trams-join.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend': patch +--- + +Fix `addTopic` migration when `user_settings` present diff --git a/plugins/notifications-backend/migrations/20250317_addTopic.js b/plugins/notifications-backend/migrations/20250317_addTopic.js index c8f78a5640..e17b8d0789 100644 --- a/plugins/notifications-backend/migrations/20250317_addTopic.js +++ b/plugins/notifications-backend/migrations/20250317_addTopic.js @@ -18,14 +18,10 @@ const crypto = require('crypto'); exports.up = async function up(knex) { await knex.schema.alterTable('user_settings', table => { table.string('topic').nullable().after('origin'); - table.string('settings_key_hash', 64).notNullable(); + table.string('settings_key_hash', 64).nullable(); table.dropUnique([], 'user_settings_unique_idx'); }); - await knex.schema.alterTable('user_settings', table => { - table.unique(['settings_key_hash'], 'user_settings_unique_idx'); - }); - const rows = await knex('user_settings').select('user', 'channel', 'origin'); for (const row of rows) { const rawKey = `${row.user}|${row.channel}|${row.origin}|}`; @@ -35,10 +31,14 @@ exports.up = async function up(knex) { user: row.user, channel: row.channel, origin: row.origin, - topic: row.topic, }) .update({ settings_key_hash: hash }); } + + await knex.schema.alterTable('user_settings', table => { + table.string('settings_key_hash', 64).notNullable().alter(); + table.unique(['settings_key_hash'], 'user_settings_unique_idx'); + }); }; exports.down = async function down(knex) { diff --git a/plugins/notifications-backend/src/tests/migrations.test.ts b/plugins/notifications-backend/src/tests/migrations.test.ts new file mode 100644 index 0000000000..aa3ba7ae9a --- /dev/null +++ b/plugins/notifications-backend/src/tests/migrations.test.ts @@ -0,0 +1,99 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Knex } from 'knex'; +import { TestDatabases } from '@backstage/backend-test-utils'; +import fs from 'fs'; + +const migrationsDir = `${__dirname}/../../migrations`; +const migrationsFiles = fs.readdirSync(migrationsDir).sort(); + +async function migrateUpOnce(knex: Knex): Promise { + await knex.migrate.up({ directory: migrationsDir }); +} + +async function migrateDownOnce(knex: Knex): Promise { + await knex.migrate.down({ directory: migrationsDir }); +} + +async function migrateUntilBefore(knex: Knex, target: string): Promise { + const index = migrationsFiles.indexOf(target); + if (index === -1) { + throw new Error(`Migration ${target} not found`); + } + for (let i = 0; i < index; i++) { + await migrateUpOnce(knex); + } +} + +jest.setTimeout(60_000); + +describe('migrations', () => { + const databases = TestDatabases.create(); + + it.each(databases.eachSupportedId())( + '20221109192547_search_add_original_value_column.js, %p', + async databaseId => { + const knex = await databases.init(databaseId); + + await migrateUntilBefore(knex, '20250317_addTopic.js'); + + await knex + .insert({ + user: 'user1', + channel: 'channel1', + origin: 'origin1', + enabled: true, + }) + .into('user_settings'); + + await migrateUpOnce(knex); + + let rows = await knex('user_settings'); + let normalized = rows.map(r => ({ ...r, enabled: !!r.enabled })); + + expect(normalized).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + user: 'user1', + channel: 'channel1', + origin: 'origin1', + enabled: true, + settings_key_hash: + '73f97aff883b8b08a7f4e366234ef4f86827702b0016574ac4c1bf313c703d15', + topic: null, + }), + ]), + ); + + await migrateDownOnce(knex); + + rows = await knex('user_settings'); + normalized = rows.map(r => ({ ...r, enabled: !!r.enabled })); + + expect(normalized).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + user: 'user1', + channel: 'channel1', + origin: 'origin1', + enabled: true, + }), + ]), + ); + }, + ); +}); From fc70b43a368a8947477cb3042f0cd01221e88019 Mon Sep 17 00:00:00 2001 From: ShaoWei Teo Date: Sun, 22 Jun 2025 08:24:48 +0800 Subject: [PATCH 242/301] chore(deps): remove @backstage/backend-common Signed-off-by: ShaoWei Teo --- .changeset/sharp-stars-report.md | 5 +++++ plugins/scaffolder-backend/.eslintrc.js | 4 ++-- plugins/scaffolder-backend/package.json | 1 - .../src/scaffolder/tasks/DatabaseTaskStore.test.ts | 12 +++++++++--- .../src/scaffolder/tasks/StorageTaskBroker.test.ts | 7 +++++-- .../src/scaffolder/tasks/TaskWorker.test.ts | 7 +++++-- .../scaffolder-backend/src/service/router.test.ts | 7 +++++-- yarn.lock | 1 - 8 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 .changeset/sharp-stars-report.md diff --git a/.changeset/sharp-stars-report.md b/.changeset/sharp-stars-report.md new file mode 100644 index 0000000000..c37e0e39ea --- /dev/null +++ b/.changeset/sharp-stars-report.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Replaced deprecated uses of `@backstage/backend-common` with the equivalents in `@backstage/backend-defaults` and `@backstage/backend-plugin-api`. diff --git a/plugins/scaffolder-backend/.eslintrc.js b/plugins/scaffolder-backend/.eslintrc.js index 953af54f90..3c5416068f 100644 --- a/plugins/scaffolder-backend/.eslintrc.js +++ b/plugins/scaffolder-backend/.eslintrc.js @@ -5,13 +5,13 @@ module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { name: 'path', importNames: ['resolve'], message: - 'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-common` instead as it prevents security issues', + 'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-plugin-api` instead as it prevents security issues', }, ], restrictedSrcSyntax: [ { message: - 'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-common` instead as it prevents security issues', + 'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-plugin-api` instead as it prevents security issues', selector: 'MemberExpression[object.name="path"][property.name="resolve"]', }, ], diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index d3fb0fa81b..91e669c4f5 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -61,7 +61,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "^0.25.0", "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-model": "workspace:^", diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts index eb0418b5ed..f95381e0ed 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts @@ -14,12 +14,15 @@ * limitations under the License. */ -import { DatabaseManager } from '@backstage/backend-common'; +import { DatabaseManager } from '@backstage/backend-defaults/database'; import { ConfigReader } from '@backstage/config'; import { DatabaseTaskStore, RawDbTaskEventRow } from './DatabaseTaskStore'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { ConflictError } from '@backstage/errors'; -import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + mockServices, + createMockDirectory, +} from '@backstage/backend-test-utils'; import fs from 'fs-extra'; import { EventsService } from '@backstage/plugin-events-node'; @@ -33,7 +36,10 @@ const createStore = async (events?: EventsService) => { }, }, }), - ).forPlugin('scaffolder'); + ).forPlugin('scaffolder', { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }); const store = await DatabaseTaskStore.create({ database: manager, events, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index dc89100b95..2b13130908 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { DatabaseManager } from '@backstage/backend-common'; +import { DatabaseManager } from '@backstage/backend-defaults/database'; import { ConfigReader } from '@backstage/config'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { @@ -36,7 +36,10 @@ async function createStore(): Promise { }, }, }), - ).forPlugin('scaffolder'); + ).forPlugin('scaffolder', { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }); return await DatabaseTaskStore.create({ database: manager, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index dd9233a5ee..6fedc4df40 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -15,7 +15,7 @@ */ import os from 'os'; -import { DatabaseManager } from '@backstage/backend-common'; +import { DatabaseManager } from '@backstage/backend-defaults/database'; import { ConfigReader } from '@backstage/config'; import { DatabaseTaskStore } from './DatabaseTaskStore'; import { StorageTaskBroker } from './StorageTaskBroker'; @@ -49,7 +49,10 @@ async function createStore(): Promise { }, }, }), - ).forPlugin('scaffolder'); + ).forPlugin('scaffolder', { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }); return await DatabaseTaskStore.create({ database: manager, }); diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 679ac44640..2820c8b8dc 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { DatabaseManager } from '@backstage/backend-common'; +import { DatabaseManager } from '@backstage/backend-defaults/database'; import { ConfigReader } from '@backstage/config'; import request from 'supertest'; import ObservableImpl from 'zen-observable'; @@ -77,7 +77,10 @@ function createDatabase(): DatabaseService { }, }, }), - ).forPlugin('scaffolder'); + ).forPlugin('scaffolder', { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }); } const config = new ConfigReader({}); diff --git a/yarn.lock b/yarn.lock index 9e13c331b5..c6264bde1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7499,7 +7499,6 @@ __metadata: resolution: "@backstage/plugin-scaffolder-backend@workspace:plugins/scaffolder-backend" dependencies: "@backstage/backend-app-api": "workspace:^" - "@backstage/backend-common": "npm:^0.25.0" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" From 5f049e48a4a1fc238e6fa3e6677480e07516f2e2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 08:01:05 +0000 Subject: [PATCH 243/301] fix(deps): update dependency @types/express to v4.17.23 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9e13c331b5..d5a2cf8085 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21407,15 +21407,15 @@ __metadata: languageName: node linkType: hard -"@types/express-serve-static-core@npm:*": - version: 5.0.0 - resolution: "@types/express-serve-static-core@npm:5.0.0" +"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^5.0.0": + version: 5.0.6 + resolution: "@types/express-serve-static-core@npm:5.0.6" dependencies: "@types/node": "npm:*" "@types/qs": "npm:*" "@types/range-parser": "npm:*" "@types/send": "npm:*" - checksum: 10/fc40cdeae61113d8b2335f4b0f9334a7a64388a0931f2e98f8fc9bdadd0b13b501a70da14c256ae4aa140db49bd2eff75a99a683266d561e62540784a61dc489 + checksum: 10/9dc51bdee7da9ad4792e97dd1be5b3071b5128f26d3b87a753070221bb36c8f9d16074b95a8b972acc965641e987b1e279a44675e7312ac8f3e18ec9abe93940 languageName: node linkType: hard @@ -21440,15 +21440,26 @@ __metadata: languageName: node linkType: hard -"@types/express@npm:*, @types/express@npm:^4.16.1, @types/express@npm:^4.17.21, @types/express@npm:^4.17.6": - version: 4.17.21 - resolution: "@types/express@npm:4.17.21" +"@types/express@npm:*": + version: 5.0.3 + resolution: "@types/express@npm:5.0.3" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^5.0.0" + "@types/serve-static": "npm:*" + checksum: 10/bb6f10c14c8e3cce07f79ee172688aa9592852abd7577b663cd0c2054307f172c2b2b36468c918fed0d4ac359b99695807b384b3da6157dfa79acbac2226b59b + languageName: node + linkType: hard + +"@types/express@npm:^4.16.1, @types/express@npm:^4.17.21, @types/express@npm:^4.17.6": + version: 4.17.23 + resolution: "@types/express@npm:4.17.23" dependencies: "@types/body-parser": "npm:*" "@types/express-serve-static-core": "npm:^4.17.33" "@types/qs": "npm:*" "@types/serve-static": "npm:*" - checksum: 10/7a6d26cf6f43d3151caf4fec66ea11c9d23166e4f3102edfe45a94170654a54ea08cf3103d26b3928d7ebcc24162c90488e33986b7e3a5f8941225edd5eb18c7 + checksum: 10/cf4d540bbd90801cdc79a46107b8873404698a7fd0c3e8dd42989d52d3bd7f5b8768672e54c20835e41e27349c319bb47a404ad14c0f8db0e9d055ba1cb8a05b languageName: node linkType: hard From 47b3f582cc28ba28af23bdaf24c4b277008a1c74 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 23 Jun 2025 12:13:20 +0100 Subject: [PATCH 244/301] Add SearchField Signed-off-by: Charles de Dreuille --- .../SearchField/SearchField.stories.tsx | 148 ++++++++++++++++++ .../SearchField/SearchField.styles.css | 55 +++++++ .../components/SearchField/SearchField.tsx | 113 +++++++++++++ .../canon/src/components/SearchField/index.ts | 18 +++ .../canon/src/components/SearchField/types.ts | 41 +++++ .../components/TextField/TextField.styles.css | 37 +++-- .../src/components/TextField/TextField.tsx | 9 +- packages/canon/src/css/components.css | 1 + 8 files changed, 400 insertions(+), 22 deletions(-) create mode 100644 packages/canon/src/components/SearchField/SearchField.stories.tsx create mode 100644 packages/canon/src/components/SearchField/SearchField.styles.css create mode 100644 packages/canon/src/components/SearchField/SearchField.tsx create mode 100644 packages/canon/src/components/SearchField/index.ts create mode 100644 packages/canon/src/components/SearchField/types.ts diff --git a/packages/canon/src/components/SearchField/SearchField.stories.tsx b/packages/canon/src/components/SearchField/SearchField.stories.tsx new file mode 100644 index 0000000000..fc2e72aff1 --- /dev/null +++ b/packages/canon/src/components/SearchField/SearchField.stories.tsx @@ -0,0 +1,148 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import { SearchField } from './SearchField'; +import { Form } from 'react-aria-components'; +import { Icon } from '../Icon'; +import { Flex } from '../Flex'; +import { FieldLabel } from '../FieldLabel'; + +const meta = { + title: 'Forms/SearchField', + component: SearchField, + argTypes: { + isRequired: { + control: 'boolean', + }, + icon: { + control: 'object', + }, + placeholder: { + control: 'text', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + name: 'url', + style: { + maxWidth: '300px', + }, + }, +}; + +export const Sizes: Story = { + args: { + ...Default.args, + }, + render: args => ( + + + + + ), +}; + +export const DefaultValue: Story = { + args: { + ...Default.args, + defaultValue: 'https://example.com', + }, +}; + +export const WithLabel: Story = { + args: { + ...Default.args, + label: 'Label', + }, +}; + +export const WithDescription: Story = { + args: { + ...WithLabel.args, + description: 'Description', + }, +}; + +export const Required: Story = { + args: { + ...WithLabel.args, + isRequired: true, + }, +}; + +export const Disabled: Story = { + args: { + ...Default.args, + isDisabled: true, + }, +}; + +export const WithIcon: Story = { + args: { + ...Default.args, + defaultValue: 'Search...', + icon: , + }, +}; + +export const DisabledWithIcon: Story = { + args: { + ...WithIcon.args, + isDisabled: true, + }, +}; + +export const ShowError: Story = { + args: { + ...WithLabel.args, + }, + render: args => ( +
+ + + ), +}; + +export const Validation: Story = { + args: { + ...WithLabel.args, + validate: value => (value === 'admin' ? 'Nice try!' : null), + }, +}; + +export const CustomField: Story = { + render: () => ( + <> + + + + ), +}; diff --git a/packages/canon/src/components/SearchField/SearchField.styles.css b/packages/canon/src/components/SearchField/SearchField.styles.css new file mode 100644 index 0000000000..70bd20e2ac --- /dev/null +++ b/packages/canon/src/components/SearchField/SearchField.styles.css @@ -0,0 +1,55 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.canon-SearchField[data-empty] .canon-InputClear { + display: none; +} + +.canon-InputClear { + position: absolute; + right: 0; + top: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + background-color: transparent; + border: none; + padding: 0; + margin: 0; + cursor: pointer; + color: var(--canon-fg-secondary); + transition: color 0.2s ease-in-out; +} + +.canon-InputClear:hover { + color: var(--canon-fg-primary); +} + +.canon-InputClear[data-size='small'] { + width: 2rem; + height: 2rem; +} + +.canon-InputClear[data-size='medium'] { + width: 2.5rem; + height: 2.5rem; +} + +.canon-InputClear svg { + width: 1rem; + height: 1rem; +} diff --git a/packages/canon/src/components/SearchField/SearchField.tsx b/packages/canon/src/components/SearchField/SearchField.tsx new file mode 100644 index 0000000000..c04c99571e --- /dev/null +++ b/packages/canon/src/components/SearchField/SearchField.tsx @@ -0,0 +1,113 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { forwardRef, useEffect } from 'react'; +import { + Input, + SearchField as AriaSearchField, + FieldError, + Button, + Label, + Text, +} from 'react-aria-components'; +import { useResponsiveValue } from '../../hooks/useResponsiveValue'; +import clsx from 'clsx'; +import { FieldLabel } from '../FieldLabel'; +import { RiSearch2Line, RiCloseCircleLine } from '@remixicon/react'; + +import type { SearchFieldProps } from './types'; + +/** @public */ +export const SearchField = forwardRef( + (props, ref) => { + const { + className, + icon, + size = 'small', + label, + secondaryLabel, + description, + isRequired, + placeholder = 'Search', + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, + ...rest + } = props; + + useEffect(() => { + if (!label && !ariaLabel && !ariaLabelledBy) { + console.warn( + 'SearchField requires either a visible label, aria-label, or aria-labelledby for accessibility', + ); + } + }, [label, ariaLabel, ariaLabelledBy]); + + // Get the responsive value for the variant + const responsiveSize = useResponsiveValue(size); + + // If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required. + const secondaryLabelText = + secondaryLabel || (isRequired ? 'Required' : null); + + // return ( + // + //
+ + + ); + }, +); + +SearchField.displayName = 'searchField'; diff --git a/packages/canon/src/components/SearchField/index.ts b/packages/canon/src/components/SearchField/index.ts new file mode 100644 index 0000000000..288c3fe44a --- /dev/null +++ b/packages/canon/src/components/SearchField/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './SearchField'; +export * from './types'; diff --git a/packages/canon/src/components/SearchField/types.ts b/packages/canon/src/components/SearchField/types.ts new file mode 100644 index 0000000000..b561292684 --- /dev/null +++ b/packages/canon/src/components/SearchField/types.ts @@ -0,0 +1,41 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { SearchFieldProps as AriaSearchFieldProps } from 'react-aria-components'; +import { ReactNode } from 'react'; +import type { Breakpoint } from '../../types'; +import type { FieldLabelProps } from '../FieldLabel/types'; + +/** @public */ +export interface SearchFieldProps + extends AriaSearchFieldProps, + Omit { + /** + * An icon to render before the input + */ + icon?: ReactNode | false; + + /** + * The size of the text field + * @defaultValue 'medium' + */ + size?: 'small' | 'medium' | Partial>; + + /** + * The placeholder text for the input + */ + placeholder?: string; +} diff --git a/packages/canon/src/components/TextField/TextField.styles.css b/packages/canon/src/components/TextField/TextField.styles.css index 9f2039b3af..58e1cba403 100644 --- a/packages/canon/src/components/TextField/TextField.styles.css +++ b/packages/canon/src/components/TextField/TextField.styles.css @@ -21,19 +21,19 @@ width: 100%; } -.canon-TextFieldInputWrapper { +.canon-Input { position: relative; } -.canon-TextFieldInputWrapper[data-size='small'] { +.canon-Input[data-size='small'] input { height: 2rem; } -.canon-TextFieldInputWrapper[data-size='medium'] { +.canon-Input[data-size='medium'] input { height: 2.5rem; } -.canon-TextFieldIcon { +.canon-InputIcon { position: absolute; left: var(--canon-space-3); top: 50%; @@ -43,19 +43,19 @@ flex-shrink: 0; } -.canon-TextFieldIcon[data-size='small'], -.canon-TextFieldIcon[data-size='small'] svg { +.canon-InputIcon[data-size='small'], +.canon-InputIcon[data-size='small'] svg { width: 1rem; height: 1rem; } -.canon-TextFieldIcon[data-size='medium'], -.canon-TextFieldIcon[data-size='medium'] svg { +.canon-InputIcon[data-size='medium'], +.canon-InputIcon[data-size='medium'] svg { width: 1.25rem; height: 1.25rem; } -.canon-TextFieldInput { +.canon-Input input { display: flex; align-items: center; padding: 0 var(--canon-space-3); @@ -71,35 +71,40 @@ width: 100%; height: 100%; cursor: inherit; + + &::-webkit-search-cancel-button, + &::-webkit-search-decoration { + -webkit-appearance: none; + } } -.canon-TextFieldInput::placeholder { +.canon-Input input::placeholder { color: var(--canon-fg-secondary); } -.canon-TextFieldInput[data-icon] { +.canon-Input input[data-icon] { padding-left: var(--canon-space-8); } -.canon-TextFieldInput[data-focused] { +.canon-Input input[data-focused] { outline-color: var(--canon-border-pressed); outline-width: 0px; } -.canon-TextFieldInput[data-hovered] { +.canon-Input input[data-hovered] { border-color: var(--canon-border-hover); } -.canon-TextFieldInput[data-focused] { +.canon-Input input[data-focused] { border-color: var(--canon-border-pressed); outline-width: 0px; } -.canon-TextFieldInput[data-invalid] { +.canon-Input input[data-invalid] { border-color: var(--canon-fg-danger); } -.canon-TextFieldInput[data-disabled] { +.canon-Input input[data-disabled] { opacity: 0.5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); diff --git a/packages/canon/src/components/TextField/TextField.tsx b/packages/canon/src/components/TextField/TextField.tsx index 444e76e983..5e53614302 100644 --- a/packages/canon/src/components/TextField/TextField.tsx +++ b/packages/canon/src/components/TextField/TextField.tsx @@ -71,20 +71,17 @@ export const TextField = forwardRef( secondaryLabel={secondaryLabelText} description={description} /> -
+
{icon && ( )} - +
diff --git a/packages/canon/src/css/components.css b/packages/canon/src/css/components.css index aa453f7314..c1a9b7cc0d 100644 --- a/packages/canon/src/css/components.css +++ b/packages/canon/src/css/components.css @@ -38,6 +38,7 @@ @import '../components/Tabs/Tabs.styles.css'; @import '../components/Text/styles.css'; @import '../components/TextField/TextField.styles.css'; +@import '../components/SearchField/SearchField.styles.css'; @import '../components/Tooltip/Tooltip.styles.css'; @import '../components/ScrollArea/ScrollArea.styles.css'; @import '../components/Select/Select.styles.css'; From cefd8ceb24152b381e3505f09427883d904b3ffb Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Mon, 23 Jun 2025 13:15:51 +0200 Subject: [PATCH 245/301] chore: perhaps fix gendocs? Signed-off-by: benjdlambert --- package.json | 3 ++- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4e20ea9d63..fe2808e251 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,8 @@ "csstype@npm:^3.0.2": "3.0.9", "csstype@npm:^3.1.2": "3.0.9", "csstype@npm:^3.1.3": "3.0.9", - "jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch" + "jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch", + "GendocuPublicApis": "npm:gendocu-public-apis@latest" }, "dependencies": { "@backstage/errors": "workspace:^", diff --git a/yarn.lock b/yarn.lock index c6264bde1c..f507f1ba59 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24384,13 +24384,13 @@ __metadata: languageName: node linkType: hard -"GendocuPublicApis@https://git.gendocu.com/gendocu/GendocuPublicApis.git#master": - version: 1.0.0 - resolution: "GendocuPublicApis@https://git.gendocu.com/gendocu/GendocuPublicApis.git#commit=6e8d4c1d2342556a2e8e719f19385b266001ebae" +"GendocuPublicApis@npm:gendocu-public-apis@latest": + version: 1.0.3 + resolution: "gendocu-public-apis@npm:1.0.3" dependencies: "@improbable-eng/grpc-web": "npm:^0.14.0" google-protobuf: "npm:^3.15.8" - checksum: 10/a34386a6137d92f392121305fb899f63ba1631a1b60c80781f6e56e160c1d462a8ce4b3ee93cb67c73c079b53c6de57ba19f514f29dd11c9783cd0d8227c6968 + checksum: 10/261a5cc97b599a7dfb492ac048e6bfce3f3bf64627f2b80ef195d5b6cefdbd4668d2b2ccd10676f01eb4f7862a5d19da567dd6ade9fc39a15268e81862308f7c languageName: node linkType: hard From 3965776ae62742d149af34c998e2966a17ef246c Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Mon, 23 Jun 2025 13:21:13 +0200 Subject: [PATCH 246/301] chore: small change Signed-off-by: benjdlambert --- package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fe2808e251..d926639622 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "csstype@npm:^3.1.2": "3.0.9", "csstype@npm:^3.1.3": "3.0.9", "jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch", - "GendocuPublicApis": "npm:gendocu-public-apis@latest" + "GendocuPublicApis": "npm:gendocu-public-apis@^1.0.0" }, "dependencies": { "@backstage/errors": "workspace:^", diff --git a/yarn.lock b/yarn.lock index f507f1ba59..c162b71503 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24384,7 +24384,7 @@ __metadata: languageName: node linkType: hard -"GendocuPublicApis@npm:gendocu-public-apis@latest": +"GendocuPublicApis@npm:gendocu-public-apis@^1.0.0": version: 1.0.3 resolution: "gendocu-public-apis@npm:1.0.3" dependencies: From 6febe2a7a378fe093086cb7949237f3512d7bbc1 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Mon, 23 Jun 2025 13:25:41 +0100 Subject: [PATCH 247/301] Convert more components Signed-off-by: Charles de Dreuille --- packages/canon/src/components/Box/Box.tsx | 4 +- .../src/components/Checkbox/Checkbox.tsx | 4 +- .../components/Collapsible/Collapsible.tsx | 55 ++-- .../src/components/Container/Container.tsx | 5 +- .../src/components/FieldLabel/FieldLabel.tsx | 14 +- packages/canon/src/components/Flex/Flex.tsx | 4 +- packages/canon/src/components/Grid/Grid.tsx | 8 +- .../canon/src/components/Heading/Heading.tsx | 15 +- packages/canon/src/components/Icon/Icon.tsx | 5 +- packages/canon/src/components/Link/Link.tsx | 10 +- packages/canon/src/components/Menu/Menu.tsx | 281 +++++++++++------- .../canon/src/utils/componentDefinitions.ts | 140 +++++---- 12 files changed, 315 insertions(+), 230 deletions(-) diff --git a/packages/canon/src/components/Box/Box.tsx b/packages/canon/src/components/Box/Box.tsx index 8718c77724..4273ee86f9 100644 --- a/packages/canon/src/components/Box/Box.tsx +++ b/packages/canon/src/components/Box/Box.tsx @@ -24,6 +24,7 @@ import { widthPropDefs } from '../../props/width.props'; import { heightPropDefs } from '../../props/height.props'; import { positionPropDefs } from '../../props/position.props'; import { displayPropDefs } from '../../props/display.props'; +import { useStyles } from '../../hooks/useStyles'; /** @public */ export const Box = forwardRef((props, ref) => { @@ -38,11 +39,12 @@ export const Box = forwardRef((props, ref) => { ...boxPropDefs, }; + const { classNames } = useStyles('Box'); const { className, style } = extractProps(props, propDefs); return createElement(props.as || 'div', { ref, - className: clsx('canon-Box', className), + className: clsx(classNames.root, className), style, children, }); diff --git a/packages/canon/src/components/Checkbox/Checkbox.tsx b/packages/canon/src/components/Checkbox/Checkbox.tsx index 8fbb4d40af..aaa6fb105b 100644 --- a/packages/canon/src/components/Checkbox/Checkbox.tsx +++ b/packages/canon/src/components/Checkbox/Checkbox.tsx @@ -50,14 +50,14 @@ export const Checkbox = forwardRef( value={value} style={style} > - + ); return label ? ( -