Merge branch 'master' into canon-searchfield

This commit is contained in:
Charles de Dreuille
2025-06-23 18:56:53 +01:00
84 changed files with 1264 additions and 12590 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-graph': patch
---
Catalog graph plugin support i18n
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': patch
---
We are consolidating all css files into a single styles.css in Canon.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': patch
---
Add new `RadioGroup` + `Radio` component to Canon
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': patch
---
Added placeholder prop to TextField component.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-import': patch
---
Fixed bug with error message since ResponseError is now thrown from CatalogClient
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/integration-react': patch
---
Separated gitlab `write_repository` and `api` scope to pass checks in `RefreshingAuthSessionManager`
+6
View File
@@ -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
+2 -2
View File
@@ -52,14 +52,14 @@ 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 }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Build and push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
+3 -3
View File
@@ -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"
}
File diff suppressed because one or more lines are too long
+3 -8
View File
@@ -5,10 +5,8 @@ const { bundle } = require('lightningcss');
const source = '../../packages/canon/src/css';
const destination = '../public';
const source1 = path.join(__dirname, `${source}/core.css`);
const destination1 = path.join(__dirname, `${destination}/core.css`);
const source2 = path.join(__dirname, `${source}/components.css`);
const destination2 = path.join(__dirname, `${destination}/components.css`);
const source1 = path.join(__dirname, `${source}/styles.css`);
const destination1 = path.join(__dirname, `${destination}/styles.css`);
// Function to bundle and copy the CSS file
const bundleAndCopyFile = async (source, destination) => {
@@ -26,10 +24,7 @@ const bundleAndCopyFile = async (source, destination) => {
};
// Initial bundle and copy
Promise.all([
bundleAndCopyFile(source1, destination1),
bundleAndCopyFile(source2, destination2),
])
Promise.all([bundleAndCopyFile(source1, destination1)])
.then(() => {
// Add an empty line after all operations are complete - It looks better in the terminal :)
console.log('');
@@ -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.
<Snippet
align="center"
py={4}
preview={<RadioGroupSnippet story="Default" />}
code={radioGroupDefaultSnippet}
/>
<ComponentInfos
component="radio-group"
classNames={['canon-RadioGroup', 'canon-RadioGroupContent']}
usageCode={radioGroupUsageSnippet}
/>
## API reference
<PropsTable data={radioGroupPropDefs} />
## Examples
### Horizontal
Here's a simple TextField with a description.
<Snippet
align="center"
py={4}
open
preview={<RadioGroupSnippet story="Horizontal" />}
code={radioGroupHorizontalSnippet}
/>
### Disabled
You can disable the entire radio group by adding the `isDisabled` prop to the `RadioGroup` component.
<Snippet
align="center"
py={4}
open
preview={<RadioGroupSnippet story="Disabled" />}
code={radioGroupDisabledSnippet}
/>
### Disabled Single radio
You can disable a single radio by adding the `isDisabled` prop to the `Radio` component.
<Snippet
align="center"
py={4}
open
preview={<RadioGroupSnippet story="DisabledSingle" />}
code={radioGroupDisabledSingleSnippet}
/>
### Validation
Here's an example of a radio group with errors.
<Snippet
align="center"
py={4}
open
preview={<RadioGroupSnippet story="Validation" />}
code={radioGroupValidationSnippet}
/>
### Read only
You can make the radio group read only by adding the `isReadOnly` prop to the `RadioGroup` component.
<Snippet
align="center"
py={4}
open
preview={<RadioGroupSnippet story="ReadOnly" />}
code={radioGroupReadOnlySnippet}
/>
@@ -0,0 +1,64 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const radioGroupPropDefs: Record<string, PropDef> = {
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';
<RadioGroup />`;
export const radioGroupDefaultSnippet = `<RadioGroup label="Label" />`;
export const radioGroupDescriptionSnippet = `<TextField label="Label" description="Description" placeholder="Enter a URL" />`;
export const radioGroupHorizontalSnippet = `<RadioGroup label="Label" orientation="horizontal" />`;
export const radioGroupDisabledSnippet = `<RadioGroup label="Label" isDisabled>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>`;
export const radioGroupDisabledSingleSnippet = `<RadioGroup label="Label">
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander" isDisabled>Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>`;
export const radioGroupValidationSnippet = `<RadioGroup validate: value => (value === \'charmander\' ? \'Nice try!\' : null)>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>`;
export const radioGroupReadOnlySnippet = `<RadioGroup label="Label" isReadOnly>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>`;
@@ -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 ? <StoryComponent /> : null;
};
export const RadioGroupSnippet = ({
story,
}: {
story: keyof typeof RadioGroupStories;
}) => {
const stories = composeStories(RadioGroupStories);
const StoryComponent = stories[story as keyof typeof stories];
return StoryComponent ? <StoryComponent /> : null;
};
+5
View File
@@ -116,6 +116,11 @@ export const components: Page[] = [
slug: 'menu',
status: 'alpha',
},
{
title: 'RadioGroup',
slug: 'radio-group',
status: 'alpha',
},
{
title: 'Select',
slug: 'select',
+57 -57
View File
@@ -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
+1
View File
@@ -103,6 +103,7 @@
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@yarnpkg/plugin-npm@npm:^3.1.0": "patch:@yarnpkg/plugin-npm@npm%3A3.1.0#~/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch",
"GendocuPublicApis": "npm:gendocu-public-apis@^1.0.0",
"ast-types@0.14.2": "patch:ast-types@npm%3A0.14.2#./.yarn/patches/ast-types-npm-0.14.2-43c4ac4b0d.patch",
"ast-types@^0.14.1": "patch:ast-types@npm%3A0.14.2#./.yarn/patches/ast-types-npm-0.14.2-43c4ac4b0d.patch",
"csstype@npm:^3.0.2": "3.0.9",
@@ -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:
-49
View File
@@ -1,49 +0,0 @@
.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;
}
-5
View File
@@ -1,5 +0,0 @@
.canon-Box {
font-family: var(--canon-font-regular);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
}
-89
View File
@@ -1,89 +0,0 @@
.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;
}
-14
View File
@@ -1,14 +0,0 @@
.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;
}
-52
View File
@@ -1,52 +0,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;
}
-10
View File
@@ -1,10 +0,0 @@
.canon-CollapsiblePanel {
height: var(--collapsible-panel-height);
transition: all .15s ease-out;
display: flex;
overflow: hidden;
&[data-starting-style], &[data-ending-style] {
height: 0;
}
}
File diff suppressed because it is too large Load Diff
-18
View File
@@ -1,18 +0,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);
}
}
File diff suppressed because it is too large Load Diff
-23
View File
@@ -1,23 +0,0 @@
.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;
}
-27
View File
@@ -1,27 +0,0 @@
.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;
}
-4
View File
@@ -1,4 +0,0 @@
.canon-Flex {
min-width: 0;
display: flex;
}
-3
View File
@@ -1,3 +0,0 @@
.canon-Grid {
display: grid;
}
-51
View File
@@ -1,51 +0,0 @@
.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;
}
-4
View File
@@ -1,4 +0,0 @@
.canon-Icon {
width: 1rem;
height: 1rem;
}
-89
View File
@@ -1,89 +0,0 @@
.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;
}
-45
View File
@@ -1,45 +0,0 @@
.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);
}
-178
View File
@@ -1,178 +0,0 @@
.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;
}
-50
View File
@@ -1,50 +0,0 @@
.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%;
}
-188
View File
@@ -1,188 +0,0 @@
.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;
}
+97 -7
View File
@@ -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);
@@ -9815,6 +9822,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;
@@ -10119,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);
-57
View File
@@ -1,57 +0,0 @@
.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;
}
}
-4
View File
@@ -1,4 +0,0 @@
.canon-TableCell {
padding: var(--canon-space-3);
font-size: var(--canon-font-size-3);
}
-76
View File
@@ -1,76 +0,0 @@
.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;
}
}
-59
View File
@@ -1,59 +0,0 @@
.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;
}
-94
View File
@@ -1,94 +0,0 @@
.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);
}
-61
View File
@@ -1,61 +0,0 @@
.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);
}
}
}
+24
View File
@@ -20,6 +20,8 @@ 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';
@@ -1032,6 +1034,27 @@ export type PositionProps = GetPropDefTypes<typeof positionPropDefs>;
// @public (undocumented)
export type PropDef<T = any> = RegularPropDef<T> | ResponsivePropDef<T>;
// @public (undocumented)
export const Radio: ForwardRefExoticComponent<
RadioProps & RefAttributes<HTMLLabelElement>
>;
// @public (undocumented)
export const RadioGroup: ForwardRefExoticComponent<
RadioGroupProps & RefAttributes<HTMLDivElement>
>;
// @public (undocumented)
export interface RadioGroupProps
extends Omit<RadioGroupProps_2, 'children'>,
Omit<FieldLabelProps, 'htmlFor' | 'id'> {
// (undocumented)
children?: ReactNode;
}
// @public (undocumented)
export interface RadioProps extends RadioProps_2 {}
// @public (undocumented)
export type ReactNodePropDef = {
type: 'ReactNode';
@@ -1299,6 +1322,7 @@ export interface TextFieldProps
extends TextFieldProps_2,
Omit<FieldLabelProps, 'htmlFor' | 'id'> {
icon?: ReactNode;
placeholder?: string;
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
}
+25 -59
View File
@@ -17,89 +17,55 @@
/* eslint-disable no-restricted-imports */
import { transform, bundle } from 'lightningcss';
import fs from 'fs';
import path from 'path';
import chalk from 'chalk';
import { glob } from 'glob';
/* eslint-enable no-restricted-imports */
// Check if core.css and components.css exist
const cssDir = 'src/css';
const srcFile = 'src/css/styles.css';
const distDir = 'css';
const componentsDir = 'src/components';
const distFile = `${distDir}/styles.css`;
// Core files
const cssFiles = [
{ path: `${cssDir}/core.css`, newName: 'core.css' },
{ path: `${cssDir}/components.css`, newName: 'components.css' },
{ path: `${cssDir}/styles.css`, newName: 'styles.css' },
];
// Components files
const componentsFiles = glob
.sync('**/*.css', { cwd: componentsDir })
.map(file => {
const folderName = file.split('/')[0].toLocaleLowerCase('en-US');
return { path: `${componentsDir}/${file}`, newName: `${folderName}.css` };
});
// Combine core and components files
cssFiles.push(...componentsFiles);
// Check if files exist
cssFiles.forEach(file => {
if (!fs.existsSync(file.path)) {
console.error(`${file.originalName} does not exist`);
process.exit(1);
}
});
// Check if styles.css exists
if (!fs.existsSync(srcFile)) {
console.error(`${srcFile} does not exist`);
process.exit(1);
}
// Ensure the dist/css directory exists
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir, { recursive: true });
}
// Add watch mode support
const args = process.argv.slice(2);
const watchMode = args.includes('--watch');
async function buildCSS(logs = true) {
// Bundle and transform files
cssFiles.forEach(file => {
let { code: bundleCode } = bundle({
filename: file.path,
});
const css = fs.readFileSync(srcFile);
let { code } = transform({
filename: `${distDir}/${file.newName}`,
code: bundleCode,
});
fs.writeFileSync(`${distDir}/${file.newName}`, code);
if (logs) {
console.log(chalk.blue('CSS bundled: ') + file.newName);
}
let { code: bundleCode } = bundle({
filename: srcFile,
});
const { code } = transform({
filename: distFile,
code: bundleCode,
minify: false,
});
fs.writeFileSync(distFile, code);
if (logs) {
console.log(chalk.green('CSS files bundled successfully!'));
console.log(chalk.blue('CSS transformed and minified: ') + 'styles.css');
console.log(chalk.green('CSS file built successfully!'));
}
}
if (watchMode) {
// Watch both directories for changes
[cssDir, componentsDir].forEach(dir => {
fs.watch(dir, { recursive: true }, (eventType, filename) => {
if (filename?.endsWith('.css')) {
console.log(
chalk.yellow(`Changes detected in ${filename}, rebuilding...`),
);
buildCSS(false).catch(console.error);
}
});
fs.watch('src/css', { recursive: true }, (eventType, filename) => {
if (filename === 'styles.css') {
console.log(
chalk.yellow(`Changes detected in ${filename}, rebuilding...`),
);
buildCSS(false).catch(console.error);
}
});
// Initial build
buildCSS().catch(console.error);
console.log(chalk.yellow('Watching for CSS changes...'));
} else {
@@ -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<typeof FieldError>;
export default meta;
type Story = StoryObj<typeof meta>;
// Show error with server validation using Form component
export const WithServerValidation: Story = {
render: () => (
<Form validationErrors={{ demo: 'This is a server validation error.' }}>
<TextField
name="demo"
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
}}
>
<Input />
<FieldError />
</TextField>
</Form>
),
};
// Show error using children
export const WithCustomMessage: Story = {
render: () => (
<TextField
isInvalid
validationBehavior="aria"
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
}}
>
<Input />
<FieldError>This is a custom error message.</FieldError>
</TextField>
),
};
// Show error with render prop function
export const WithRenderProp: Story = {
render: () => (
<TextField
isInvalid
validationBehavior="aria"
validate={() => 'This field is invalid'}
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
}}
>
<Input />
<FieldError>
{({ validationErrors }) =>
validationErrors.length > 0 ? validationErrors[0] : 'Field is invalid'
}
</FieldError>
</TextField>
),
};
@@ -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);
}
@@ -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<HTMLDivElement, FieldErrorProps>(
(props: FieldErrorProps, ref) => {
const { className, ...rest } = props;
return (
<AriaFieldError
className={clsx('canon-FieldError', className)}
ref={ref}
{...rest}
/>
);
},
);
FieldError.displayName = 'FieldError';
@@ -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';
@@ -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<typeof RadioGroup>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
label: 'What is your favorite pokemon?',
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
export const Horizontal: Story = {
args: {
...Default.args,
orientation: 'horizontal',
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
export const Disabled: Story = {
args: {
...Default.args,
isDisabled: true,
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
export const DisabledSingle: Story = {
args: {
...Default.args,
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander" isDisabled>
Charmander
</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
export const DisabledAndSelected: Story = {
args: {
...Default.args,
value: 'charmander',
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander" isDisabled>
Charmander
</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
export const Invalid: Story = {
args: {
...Default.args,
name: 'pokemon',
isInvalid: true,
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander" isDisabled>
Charmander
</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
export const Validation: Story = {
args: {
...Default.args,
name: 'pokemon',
defaultValue: 'charmander',
validationBehavior: 'aria',
validate: value => (value === 'charmander' ? 'Nice try!' : null),
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
export const ReadOnly: Story = {
args: {
...Default.args,
isReadOnly: true,
defaultValue: 'charmander',
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
@@ -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);
}
}
}
@@ -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,
} from 'react-aria-components';
import clsx from 'clsx';
import { FieldLabel } from '../FieldLabel';
import { FieldError } from '../FieldError';
import type { RadioGroupProps, RadioProps } from './types';
/** @public */
export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(
(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 (
<AriaRadioGroup
className={clsx('canon-RadioGroup', className)}
aria-label={ariaLabel}
aria-labelledby={ariaLabelledBy}
{...rest}
ref={ref}
>
<FieldLabel
label={label}
secondaryLabel={secondaryLabelText}
description={description}
/>
<div className="canon-RadioGroupContent">{children}</div>
<FieldError />
</AriaRadioGroup>
);
},
);
RadioGroup.displayName = 'RadioGroup';
/** @public */
export const Radio = forwardRef<HTMLLabelElement, RadioProps>((props, ref) => {
const { className, ...rest } = props;
return (
<AriaRadio className={clsx('canon-Radio', className)} {...rest} ref={ref} />
);
});
RadioGroup.displayName = 'RadioGroup';
@@ -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';
@@ -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<AriaRadioGroupProps, 'children'>,
Omit<FieldLabelProps, 'htmlFor' | 'id'> {
children?: ReactNode;
}
/** @public */
export interface RadioProps extends AriaRadioProps {}
@@ -40,7 +40,7 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
name: 'url',
defaultValue: 'Enter a URL',
placeholder: 'Enter a URL',
style: {
maxWidth: '300px',
},
@@ -97,7 +97,7 @@ export const Disabled: Story = {
export const WithIcon: Story = {
args: {
...Default.args,
defaultValue: 'Search...',
placeholder: 'Search...',
icon: <Icon name="search" />,
},
};
@@ -109,10 +109,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);
}
@@ -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';
@@ -39,6 +36,7 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
isRequired,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledBy,
placeholder,
...rest
} = props;
@@ -81,9 +79,12 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
{icon}
</div>
)}
<Input {...(icon && { 'data-icon': true })} />
<Input
{...(icon && { 'data-icon': true })}
placeholder={placeholder}
/>
</div>
<FieldError className="canon-TextFieldError" />
<FieldError />
</AriaTextField>
);
},
@@ -33,4 +33,9 @@ export interface TextFieldProps
* @defaultValue 'medium'
*/
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
/**
* Text to display in the input when it has no value
*/
placeholder?: string;
}
+2
View File
@@ -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';
@@ -30,6 +31,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';
+1
View File
@@ -41,6 +41,7 @@ export * from './components/Icon';
export * from './components/ButtonIcon';
export * from './components/ButtonLink';
export * from './components/Checkbox';
export * from './components/RadioGroup';
export * from './components/Table';
export * from './components/Tabs';
export * from './components/TextField';
@@ -173,7 +173,7 @@ export class ScmAuth implements ScmAuthApi {
const host = options?.host ?? 'gitlab.com';
return new ScmAuth('gitlab', gitlabAuthApi, host, {
default: ['read_user', 'read_api', 'read_repository'],
repoWrite: ['write_repository api'],
repoWrite: ['write_repository', 'api'],
});
}
+1
View File
@@ -588,6 +588,7 @@ export function getGitilesAuthenticationUrl(
export function getGitLabFileFetchUrl(
url: string,
config: GitLabIntegrationConfig,
token?: string,
): Promise<string>;
// @public
+4 -2
View File
@@ -40,8 +40,9 @@ import {
export async function getGitLabFileFetchUrl(
url: string,
config: GitLabIntegrationConfig,
token?: string,
): Promise<string> {
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<number> {
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();
+29
View File
@@ -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 &amp; 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<
+2
View File
@@ -96,3 +96,5 @@ export default createFrontendPlugin({
},
extensions: [CatalogGraphPage, CatalogGraphEntityCard],
});
export { catalogGraphTranslationRef } from './translation';
@@ -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('<CatalogGraphCard/>', () => {
let entity: Entity;
@@ -52,7 +53,10 @@ describe('<CatalogGraphCard/>', () => {
namespace: 'd',
},
};
apis = TestApiRegistry.from([catalogApiRef, catalog]);
apis = TestApiRegistry.from(
[catalogApiRef, catalog],
[translationApiRef, mockApis.translation()],
);
wrapper = (
<ApiProvider apis={apis}>
@@ -213,7 +217,12 @@ describe('<CatalogGraphCard/>', () => {
const analyticsApi = mockApis.analytics();
await renderInTestApp(
<TestApiProvider apis={[[analyticsApiRef, analyticsApi]]}>
<TestApiProvider
apis={[
[analyticsApiRef, analyticsApi],
[translationApiRef, mockApis.translation()],
]}
>
{wrapper}
</TestApiProvider>,
{
@@ -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,
}}
>
@@ -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 (
<Page themeId="home">
<Header
title="Catalog Graph"
title={t('catalogGraphPage.title')}
subtitle={rootEntityNames.map(e => humanizeEntityRef(e)).join(', ')}
/>
<Content stretch className={classes.content}>
@@ -203,13 +205,12 @@ export const CatalogGraphPage = (
selected={showFilters}
onChange={() => toggleShowFilters()}
>
<FilterListIcon /> Filters
<FilterListIcon /> {t('catalogGraphPage.filterToggleButtonTitle')}
</ToggleButton>
}
>
<SupportButton>
Start tracking your component in by adding it to the software
catalog.
{t('catalogGraphPage.supportButtonDescription')}
</SupportButton>
</ContentHeader>
<Grid container alignItems="stretch" className={classes.container}>
@@ -230,12 +231,12 @@ export const CatalogGraphPage = (
<SwitchFilter
value={unidirectional}
onChange={setUnidirectional}
label="Simplified"
label={t('catalogGraphPage.simplifiedSwitchLabel')}
/>
<SwitchFilter
value={mergeRelations}
onChange={setMergeRelations}
label="Merge Relations"
label={t('catalogGraphPage.mergeRelationsSwitchLabel')}
/>
</Grid>
)}
@@ -247,9 +248,8 @@ export const CatalogGraphPage = (
display="block"
className={classes.legend}
>
<ZoomOutMap className="icon" /> Use pinch &amp; zoom to move
around the diagram. Click to change active node, shift click to
navigate to entity.
<ZoomOutMap className="icon" />{' '}
{t('catalogGraphPage.zoomOutDescription')}
</Typography>
<EntityRelationsGraph
{...props}
@@ -14,21 +14,26 @@
* limitations under the License.
*/
import { render, waitFor, screen, within } from '@testing-library/react';
import { waitFor, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { CurveFilter } from './CurveFilter';
import { renderInTestApp } from '@backstage/test-utils';
describe('<CurveFilter/>', () => {
test('should display current curve label', () => {
test('should display current curve label', async () => {
const onChange = jest.fn();
render(<CurveFilter value="curveMonotoneX" onChange={onChange} />);
await renderInTestApp(
<CurveFilter value="curveMonotoneX" onChange={onChange} />,
);
expect(screen.getByText('Monotone X')).toBeInTheDocument();
});
test('should select an alternative curve factory', async () => {
const onChange = jest.fn();
render(<CurveFilter value="curveStepBefore" onChange={onChange} />);
await renderInTestApp(
<CurveFilter value="curveStepBefore" onChange={onChange} />,
);
expect(screen.getByText('Step Before')).toBeInTheDocument();
@@ -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<Curve, string> = {
curveMonotoneX: 'Monotone X',
curveStepBefore: 'Step Before',
};
export type Props = {
value: Curve;
@@ -31,6 +29,12 @@ export type Props = {
const curves: Array<Curve> = ['curveMonotoneX', 'curveStepBefore'];
export const CurveFilter = ({ value, onChange }: Props) => {
const { t } = useTranslationRef(catalogGraphTranslationRef);
const CURVE_DISPLAY_NAMES: Record<Curve, string> = {
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 (
<Box pb={1} pt={1}>
<Select
label="Curve"
label={t('catalogGraphPage.curveFilter.title')}
selected={value}
items={curves.map(v => ({
label: CURVE_DISPLAY_NAMES[v],
@@ -14,14 +14,15 @@
* limitations under the License.
*/
import { render, waitFor, screen, within } from '@testing-library/react';
import { waitFor, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Direction } from '../EntityRelationsGraph';
import { DirectionFilter } from './DirectionFilter';
import { renderInTestApp } from '@backstage/test-utils';
describe('<DirectionFilter/>', () => {
test('should display current value', () => {
render(
test('should display current value', async () => {
await renderInTestApp(
<DirectionFilter value={Direction.LEFT_RIGHT} onChange={() => {}} />,
);
@@ -30,7 +31,7 @@ describe('<DirectionFilter/>', () => {
test('should select direction', async () => {
const onChange = jest.fn();
render(
await renderInTestApp(
<DirectionFilter value={Direction.RIGHT_LEFT} onChange={onChange} />,
);
@@ -17,13 +17,8 @@ import { Select, SelectedItems } from '@backstage/core-components';
import Box from '@material-ui/core/Box';
import { useCallback } from 'react';
import { Direction } from '../EntityRelationsGraph';
const DIRECTION_DISPLAY_NAMES = {
[Direction.LEFT_RIGHT]: 'Left to right',
[Direction.RIGHT_LEFT]: 'Right to left',
[Direction.TOP_BOTTOM]: 'Top to bottom',
[Direction.BOTTOM_TOP]: 'Bottom to top',
};
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { catalogGraphTranslationRef } from '../../translation';
export type Props = {
value: Direction;
@@ -31,6 +26,13 @@ export type Props = {
};
export const DirectionFilter = ({ value, onChange }: Props) => {
const { t } = useTranslationRef(catalogGraphTranslationRef);
const DIRECTION_DISPLAY_NAMES = {
[Direction.LEFT_RIGHT]: t('catalogGraphPage.directionFilter.leftToRight'),
[Direction.RIGHT_LEFT]: t('catalogGraphPage.directionFilter.rightToLeft'),
[Direction.TOP_BOTTOM]: t('catalogGraphPage.directionFilter.topToBottom'),
[Direction.BOTTOM_TOP]: t('catalogGraphPage.directionFilter.bottomToTop'),
};
const handleChange = useCallback(
(v: SelectedItems) => onChange(v as Direction),
[onChange],
@@ -39,7 +41,7 @@ export const DirectionFilter = ({ value, onChange }: Props) => {
return (
<Box pb={1} pt={1}>
<Select
label="Direction"
label={t('catalogGraphPage.directionFilter.title')}
selected={value}
items={Object.values(Direction).map(v => ({
label: DIRECTION_DISPLAY_NAMES[v],
@@ -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('<MaxDepthFilter/>', () => {
test('should display current value', () => {
render(<MaxDepthFilter value={5} onChange={() => {}} />);
test('should display current value', async () => {
await renderInTestApp(<MaxDepthFilter value={5} onChange={() => {}} />);
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(
<MaxDepthFilter value={Number.POSITIVE_INFINITY} onChange={() => {}} />,
);
@@ -37,7 +38,7 @@ describe('<MaxDepthFilter/>', () => {
test('should clear max depth', async () => {
const onChange = jest.fn();
render(<MaxDepthFilter value={10} onChange={onChange} />);
await renderInTestApp(<MaxDepthFilter value={10} onChange={onChange} />);
expect(onChange).not.toHaveBeenCalled();
await user.click(screen.getByLabelText('clear max depth'));
@@ -46,7 +47,7 @@ describe('<MaxDepthFilter/>', () => {
test('should set max depth to undefined if below one', async () => {
const onChange = jest.fn();
render(<MaxDepthFilter value={1} onChange={onChange} />);
await renderInTestApp(<MaxDepthFilter value={1} onChange={onChange} />);
await user.clear(screen.getByLabelText('maxp'));
await user.type(screen.getByLabelText('maxp'), '0');
@@ -56,7 +57,7 @@ describe('<MaxDepthFilter/>', () => {
test('should select direction', async () => {
let value = 5;
render(
await renderInTestApp(
<MaxDepthFilter
value={value}
onChange={v => {
@@ -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 (
<Box pb={1} pt={1}>
<FormControl variant="outlined" className={classes.formControl}>
<Typography variant="button">Max Depth</Typography>
<Typography variant="button">
{t('catalogGraphPage.maxDepthFilter.title')}
</Typography>
<OutlinedInput
type="number"
placeholder="∞ Infinite"
placeholder={t('catalogGraphPage.maxDepthFilter.inputPlaceholder')}
value={Number.isFinite(currentValue) ? String(currentValue) : ''}
onChange={handleChange}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="clear max depth"
aria-label={t(
'catalogGraphPage.maxDepthFilter.clearButtonAriaLabel',
)}
onClick={reset}
edge="end"
>
@@ -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('<SelectedKindsFilter/>', () => {
@@ -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 (
<Box pb={1} pt={1}>
<Typography variant="button">Kinds</Typography>
<Typography variant="button">
{t('catalogGraphPage.selectedKindsFilter.title')}
</Typography>
<Autocomplete
className={classes.formControl}
multiple
limitTags={4}
disableCloseOnSelect
aria-label="Kinds"
aria-label={t('catalogGraphPage.selectedKindsFilter.title')}
options={normalizedKinds}
value={value ?? normalizedKinds}
getOptionLabel={k => kinds[normalizedKinds.indexOf(k)] ?? k}
@@ -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('<SelectedRelationsFilter/>', () => {
test('should render current value', () => {
render(
test('should render current value', async () => {
await renderInTestApp(
<SelectedRelationsFilter
relationPairs={ALL_RELATION_PAIRS}
value={[RELATION_OWNED_BY, RELATION_CHILD_OF]}
@@ -40,7 +41,7 @@ describe('<SelectedRelationsFilter/>', () => {
test('should select value', async () => {
const onChange = jest.fn();
render(
await renderInTestApp(
<SelectedRelationsFilter
relationPairs={ALL_RELATION_PAIRS}
value={[RELATION_OWNED_BY, RELATION_CHILD_OF]}
@@ -67,7 +68,7 @@ describe('<SelectedRelationsFilter/>', () => {
test('should return undefined if all values are selected', async () => {
const onChange = jest.fn();
render(
await renderInTestApp(
<SelectedRelationsFilter
relationPairs={ALL_RELATION_PAIRS}
value={ALL_RELATION_PAIRS.flatMap(p => p).filter(
@@ -92,7 +93,7 @@ describe('<SelectedRelationsFilter/>', () => {
test('should return all values when cleared', async () => {
const onChange = jest.fn();
render(
await renderInTestApp(
<SelectedRelationsFilter
relationPairs={ALL_RELATION_PAIRS}
value={[]}
@@ -25,6 +25,8 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import Autocomplete from '@material-ui/lab/Autocomplete';
import { useCallback, useMemo } from 'react';
import { RelationPairs } from '../EntityRelationsGraph';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { catalogGraphTranslationRef } from '../../translation';
/** @public */
export type SelectedRelationsFilterClassKey = 'formControl';
@@ -51,6 +53,7 @@ export const SelectedRelationsFilter = ({
}: Props) => {
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 (
<Box pb={1} pt={1}>
<Typography variant="button">Relations</Typography>
<Typography variant="button">
{t('catalogGraphPage.selectedRelationsFilter.title')}
</Typography>
<Autocomplete
className={classes.formControl}
multiple
limitTags={4}
disableCloseOnSelect
aria-label="Relations"
aria-label={t('catalogGraphPage.selectedRelationsFilter.title')}
options={relations}
value={value ?? relations}
onChange={handleChange}
+60
View File
@@ -0,0 +1,60 @@
/*
* 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';
/** @alpha */
export const catalogGraphTranslationRef = createTranslationRef({
id: 'catalog-graph',
messages: {
catalogGraphCard: {
title: 'Relations',
deepLinkTitle: 'View graph',
},
catalogGraphPage: {
title: 'Catalog Graph',
filterToggleButtonTitle: 'Filters',
supportButtonDescription:
'Start tracking your component in by adding it to the software catalog.',
simplifiedSwitchLabel: 'Simplified',
mergeRelationsSwitchLabel: 'Merge relations',
zoomOutDescription:
'Use pinch &amp; zoom to move around the diagram. Click to change active node, shift click to navigate to entity.',
curveFilter: {
title: 'Curve',
curveMonotoneX: 'Monotone X',
curveStepBefore: 'Step Before',
},
directionFilter: {
title: 'Direction',
leftToRight: 'Left to right',
rightToLeft: 'Right to left',
topToBottom: 'Top to bottom',
bottomToTop: 'Bottom to top',
},
maxDepthFilter: {
title: 'Max depth',
inputPlaceholder: '∞ Infinite',
clearButtonAriaLabel: 'clear max depth',
},
selectedKindsFilter: {
title: 'Kinds',
},
selectedRelationsFilter: {
title: 'Relations',
},
},
},
});
@@ -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);
}
},
+59 -600
View File
File diff suppressed because it is too large Load Diff
+24 -36
View File
@@ -11251,14 +11251,14 @@ __metadata:
linkType: hard
"@keyv/redis@npm:^4.0.1":
version: 4.4.0
resolution: "@keyv/redis@npm:4.4.0"
version: 4.4.1
resolution: "@keyv/redis@npm:4.4.1"
dependencies:
"@redis/client": "npm:^1.6.0"
cluster-key-slot: "npm:^1.1.2"
peerDependencies:
keyv: ^5.3.3
checksum: 10/61ad0a026f1ee8bbd0f09bb1b3c731619b37dc4bba0d5f2111cb5249e643e6a776816786090c53d5ac1fa62a43e1b43d406dae06cfb119abcb47b6ac837eebb0
keyv: ^5.3.4
checksum: 10/85bf5830d5a19f45fbba864f7eb830cff0059f3f7c59c711a65b439821008a3321a94fb06c4d1f09295f0f233422586458652d266696c0925b5c8d1d695700ec
languageName: node
linkType: hard
@@ -11272,11 +11272,11 @@ __metadata:
linkType: hard
"@keyv/valkey@npm:^1.0.1":
version: 1.0.4
resolution: "@keyv/valkey@npm:1.0.4"
version: 1.0.6
resolution: "@keyv/valkey@npm:1.0.6"
dependencies:
iovalkey: "npm:^0.3.1"
checksum: 10/93362c51c14bb8e515a9af8df4a22e028fcec5a09931eb68f854698676cb16cecad691330c75c1dca8b6a986a4763cac9f1b2fd4e5b8216f06648d9379f55cc6
iovalkey: "npm:^0.3.3"
checksum: 10/36e48930928845e6a14dfb4f9f8069851ff10d3aeef62277f78c8dcea735f8dca5faf4399f67fc2730231b24270d9ab6be77a46e122e9006d3f6e55cc5c0b97a
languageName: node
linkType: hard
@@ -21406,19 +21406,7 @@ __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"
dependencies:
"@types/node": "npm:*"
"@types/qs": "npm:*"
"@types/range-parser": "npm:*"
"@types/send": "npm:*"
checksum: 10/fc40cdeae61113d8b2335f4b0f9334a7a64388a0931f2e98f8fc9bdadd0b13b501a70da14c256ae4aa140db49bd2eff75a99a683266d561e62540784a61dc489
languageName: node
linkType: hard
"@types/express-serve-static-core@npm:^4.17.21, @types/express-serve-static-core@npm:^4.17.33, @types/express-serve-static-core@npm:^4.17.5":
"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.21, @types/express-serve-static-core@npm:^4.17.33, @types/express-serve-static-core@npm:^4.17.5":
version: 4.19.6
resolution: "@types/express-serve-static-core@npm:4.19.6"
dependencies:
@@ -21440,14 +21428,14 @@ __metadata:
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"
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
@@ -24384,13 +24372,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@^1.0.0":
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
@@ -31221,11 +31209,11 @@ __metadata:
linkType: hard
"express-rate-limit@npm:^7.5.0":
version: 7.5.0
resolution: "express-rate-limit@npm:7.5.0"
version: 7.5.1
resolution: "express-rate-limit@npm:7.5.1"
peerDependencies:
express: ^4.11 || 5 || ^5.0.0-beta.1
checksum: 10/eff34c83bf586789933a332a339b66649e2cca95c8e977d193aa8bead577d3182ac9f0e9c26f39389287539b8038890ff023f910b54ebb506a26a2ce135b92ca
express: ">= 4.11"
checksum: 10/357c3398450144ab7bbce2841d0bf4f93a0f3fd9d1d5ed9a0ee331b557af969cc790941dc37b47f8d9b5672964aa0e31666f770e1f48b334dc7d1e69f6433040
languageName: node
linkType: hard
@@ -34282,9 +34270,9 @@ __metadata:
languageName: node
linkType: hard
"iovalkey@npm:^0.3.1":
version: 0.3.1
resolution: "iovalkey@npm:0.3.1"
"iovalkey@npm:^0.3.3":
version: 0.3.3
resolution: "iovalkey@npm:0.3.3"
dependencies:
"@iovalkey/commands": "npm:^0.1.0"
cluster-key-slot: "npm:^1.1.0"
@@ -34295,7 +34283,7 @@ __metadata:
redis-errors: "npm:^1.2.0"
redis-parser: "npm:^3.0.0"
standard-as-callback: "npm:^2.1.0"
checksum: 10/afe5e0218810d902263dca2b22dd4501fb74698111f1850804d0948bd6a97793a7f5006757f9b6e8c8131bac6bd532d07ad971e7776bed7f6dc1f6e471706c53
checksum: 10/39368842e6a9dbc85e92dfcd4c1c917a76240bac5c4210e19a0bd3bc7984af9b567dce5de7c560c4517d5d4cb03af229f38582c1dd055c6fd452060774318e8c
languageName: node
linkType: hard