Merge branch 'master' into canon-use-style

This commit is contained in:
Charles de Dreuille
2025-06-23 15:41:39 +01:00
70 changed files with 1255 additions and 11987 deletions
+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
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/backend-defaults': patch
'@backstage/backend-test-utils': minor
---
Add a standard `toString` on credentials objects
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Replaced deprecated uses of `@backstage/backend-common` with the equivalents in `@backstage/backend-defaults` and `@backstage/backend-plugin-api`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': patch
---
Added placeholder prop to TextField component.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/integration-react': patch
---
Separated gitlab `write_repository` and `api` scope to pass checks in `RefreshingAuthSessionManager`
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',
@@ -12,7 +12,7 @@ by writing custom actions which can be used alongside our
When adding custom actions, the actions array will **replace the
built-in actions too**. Meaning, you will no longer be able to use them.
If you want to continue using the builtin actions, include them in the actions
If you want to continue using the builtin actions, include them in the `actions`
array when registering your custom actions, as seen below.
:::
@@ -52,19 +52,20 @@ its generated unit test. We will replace the existing placeholder code with our
import { resolveSafeChildPath } from '@backstage/backend-plugin-api';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import fs from 'fs-extra';
import { z } from 'zod';
import { type z } from 'zod';
export const createNewFileAction = () => {
return createTemplateAction({
id: 'acme:file:create',
description: 'Create an Acme file.',
schema: {
input: z.object({
contents: z.string().describe('The contents of the file'),
filename: z
.string()
.describe('The filename of the file that will be created'),
}),
input: {
contents: z => z.string({ description: 'The contents of the file' }),
filename: z =>
z.string({
description: 'The filename of the file that will be created',
}),
},
},
async handler(ctx) {
@@ -95,53 +96,11 @@ The `createTemplateAction` takes an object which specifies the following:
function using `ctx.output`
- `handler` - the actual code which is run as part of the action, with a context
You can also choose to define your custom action using JSON schema instead of `zod`:
```ts title="With JSON Schema"
import { resolveSafeChildPath } from '@backstage/backend-plugin-api';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import { writeFile } from 'fs';
export const createNewFileAction = () => {
return createTemplateAction<{ contents: string; filename: string }>({
id: 'acme:file:create',
description: 'Create an Acme file.',
schema: {
input: {
required: ['contents', 'filename'],
type: 'object',
properties: {
contents: {
type: 'string',
title: 'Contents',
description: 'The contents of the file',
},
filename: {
type: 'string',
title: 'Filename',
description: 'The filename of the file that will be created',
},
},
},
},
async handler(ctx) {
const { signal } = ctx;
await writeFile(
resolveSafeChildPath(ctx.workspacePath, ctx.input.filename),
ctx.input.contents,
{ signal },
_ => {},
);
},
});
};
```
### Naming Conventions
Try to keep names consistent for both your own custom actions, and any actions contributed to open source. We've found
that a separation of `:` and using a verb as the last part of the name works well.
We follow `provider:entity:verb` or as close to this as possible for our built in actions. For example,
We follow `provider:entity:verb` or as close to this as possible for our built-in actions. For example,
`github:actions:create` or `github:repo:create`.
Also feel free to use your company name to namespace them if you prefer too, for example `acme:file:create` like above.
@@ -151,14 +110,14 @@ and writing of template entity definitions.
### Adding a TemplateExample
A TemplateExample is a way to document different ways that your custom action can be used. Once added it will be visible
A TemplateExample is a way to document different ways that your custom action can be used. Once added, it will be visible
in your Backstage instance under the [/create/actions](https://demo.backstage.io/create/actions) path. You can have multiple
examples for one action that can demonstrate different combinations of inputs and how to use them.
#### Define TemplateExamples
Below is a sample TemplateExample that is used for `publish:github`. The source code is available
on [github](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/src/actions/github.examples.ts)
on [GitHub](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/src/actions/github.examples.ts)
and preview on [demo.backstage.io/create/actions](https://demo.backstage.io/create/actions#publish-github)
```ts title="With JSON Schema"
@@ -222,7 +181,7 @@ return createTemplateAction({
#### Test TemplateAction examples
It is also possible to test your example TemplateActions. You can see a sample test
on [github](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts)
on [GitHub](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts)
### The context object
@@ -234,13 +193,12 @@ argument. It looks like the following:
implement [idempotency of the actions](https://github.com/backstage/backstage/tree/master/beps/0004-scaffolder-task-idempotency)
by not re-running the same function again if it was
executed successfully on the previous run.
- `ctx.logger` - a Winston logger for additional logging inside your action
- `ctx.logStream` - a stream version of the logger if needed
- `ctx.logger` - a [LoggerService](../../backend-system/core-services/logger.md) instance for additional logging inside your action
- `ctx.workspacePath` - a string of the working directory of the template run
- `ctx.input` - an object which should match the `zod` or JSON schema provided in the
`schema.input` part of the action definition
- `ctx.output` - a function which you can call to set outputs that match the
JSON schema or `zod` in `schema.output` for ex. `ctx.output('downloadUrl', myDownloadUrl)`
`zod` schema in `schema.output` for ex. `ctx.output('downloadUrl', myDownloadUrl)`
- `createTemporaryDirectory` a function to call to give you a temporary
directory somewhere on the runner, so you can store some files there rather
than polluting the `workspacePath`
@@ -249,7 +207,7 @@ argument. It looks like the following:
## Registering Custom Actions
To register your new custom action in the Backend System you will need to create a backend module. Here is a very
To register your new custom action in the Backend System, you will need to create a backend module. Here is a very
simplified example of how to do that:
```ts title="packages/backend/src/index.ts"
@@ -327,8 +285,8 @@ const res = await ctx.checkpoint?.({
});
```
You have to define the unique key in scope of the scaffolder task for your checkpoint. During the execution task engine
will check if the checkpoint with such key was already executed or not, if yes, and the run was successful, the callback
You have to define the unique key in the scope of the scaffolder task for your checkpoint. During the execution task engine
will check if the checkpoint with such a key was already executed or not, if yes, and the run was successful, the callback
will be skipped and instead the stored value will be returned.
Whenever you change the return type of the checkpoint, we encourage you to change the ID.
+2 -1
View File
@@ -108,7 +108,8 @@
"csstype@npm:^3.0.2": "3.0.9",
"csstype@npm:^3.1.2": "3.0.9",
"csstype@npm:^3.1.3": "3.0.9",
"jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch"
"jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch",
"GendocuPublicApis": "npm:gendocu-public-apis@^1.0.0"
},
"dependencies": {
"@backstage/errors": "workspace:^",
@@ -42,6 +42,23 @@ describe('credentials', () => {
},
});
expect(
createCredentialsWithUserPrincipal(
'user:default/mock',
'my-token',
undefined,
'my-actor',
),
).toEqual({
$$type: '@backstage/BackstageCredentials',
version: 'v1',
principal: {
type: 'user',
userEntityRef: 'user:default/mock',
actor: { type: 'service', subject: 'my-actor' },
},
});
expect(createCredentialsWithNonePrincipal()).toEqual({
$$type: '@backstage/BackstageCredentials',
version: 'v1',
@@ -64,4 +81,63 @@ describe('credentials', () => {
),
).not.toMatch(/my-token/);
});
it('should have a serializable form both as strings and as JSON', () => {
const simpleService = createCredentialsWithServicePrincipal('my-service');
expect(String(simpleService)).toMatchInlineSnapshot(
`"backstageCredentials{servicePrincipal{my-service}}"`,
);
expect(JSON.stringify(simpleService)).toMatchInlineSnapshot(
`"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"service","subject":"my-service"}}"`,
);
const serviceWithAccessRestrictions = createCredentialsWithServicePrincipal(
'my-service',
undefined,
{
permissionNames: ['perm'],
permissionAttributes: {
action: ['read'],
},
},
);
expect(String(serviceWithAccessRestrictions)).toMatchInlineSnapshot(
`"backstageCredentials{servicePrincipal{my-service,accessRestrictions=cXWOJgUirHkHNZIowUi/YO5nwEwhTicC38iXi2XTYCk}}"`,
);
expect(JSON.stringify(serviceWithAccessRestrictions)).toMatchInlineSnapshot(
`"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"service","subject":"my-service","accessRestrictions":{"permissionNames":["perm"],"permissionAttributes":{"action":["read"]}}}}"`,
);
const simpleUser = createCredentialsWithUserPrincipal(
'user:default/mock',
'my-token',
);
expect(String(simpleUser)).toMatchInlineSnapshot(
`"backstageCredentials{userPrincipal{user:default/mock}}"`,
);
expect(JSON.stringify(simpleUser)).toMatchInlineSnapshot(
`"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"user","userEntityRef":"user:default/mock"}}"`,
);
const userWithActor = createCredentialsWithUserPrincipal(
'user:default/mock',
'my-token',
undefined,
'my-actor',
);
expect(String(userWithActor)).toMatchInlineSnapshot(
`"backstageCredentials{userPrincipal{user:default/mock,actor={servicePrincipal{my-actor}}}}"`,
);
expect(JSON.stringify(userWithActor)).toMatchInlineSnapshot(
`"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"user","userEntityRef":"user:default/mock","actor":{"type":"service","subject":"my-actor"}}}"`,
);
const none = createCredentialsWithNonePrincipal();
expect(String(none)).toMatchInlineSnapshot(
`"backstageCredentials{nonePrincipal}"`,
);
expect(JSON.stringify(none)).toMatchInlineSnapshot(
`"{"$$type":"@backstage/BackstageCredentials","version":"v1","principal":{"type":"none"}}"`,
);
});
});
@@ -22,29 +22,34 @@ import {
BackstageUserPrincipal,
} from '@backstage/backend-plugin-api';
import { InternalBackstageCredentials } from './types';
import { createHash } from 'crypto';
export function createCredentialsWithServicePrincipal(
sub: string,
token?: string,
accessRestrictions?: BackstagePrincipalAccessRestrictions,
): InternalBackstageCredentials<BackstageServicePrincipal> {
return Object.defineProperty(
{
$$type: '@backstage/BackstageCredentials',
version: 'v1',
principal: {
type: 'service',
subject: sub,
accessRestrictions,
},
},
'token',
{
const principal = createServicePrincipal(sub, accessRestrictions);
const result = {
$$type: '@backstage/BackstageCredentials',
version: 'v1',
principal,
} as const;
Object.defineProperties(result, {
token: {
enumerable: false,
configurable: true,
writable: true,
value: token,
},
);
toString: {
enumerable: false,
configurable: true,
writable: true,
value: () => `backstageCredentials{${principal}}`,
},
});
return result;
}
export function createCredentialsWithUserPrincipal(
@@ -53,36 +58,49 @@ export function createCredentialsWithUserPrincipal(
expiresAt?: Date,
actor?: string,
): InternalBackstageCredentials<BackstageUserPrincipal> {
return Object.defineProperty(
{
$$type: '@backstage/BackstageCredentials',
version: 'v1',
expiresAt,
principal: {
type: 'user',
userEntityRef: sub,
...(actor && {
actor: { type: 'service', subject: actor },
}),
},
},
'token',
{
const principal = createUserPrincipal(
sub,
actor ? createServicePrincipal(actor) : undefined,
);
const result = {
$$type: '@backstage/BackstageCredentials',
version: 'v1',
expiresAt,
principal,
} as const;
Object.defineProperties(result, {
token: {
enumerable: false,
configurable: true,
writable: true,
value: token,
},
);
toString: {
enumerable: false,
configurable: true,
writable: true,
value: () => `backstageCredentials{${principal}}`,
},
});
return result;
}
export function createCredentialsWithNonePrincipal(): InternalBackstageCredentials<BackstageNonePrincipal> {
return {
const principal = createNonePrincipal();
const result = {
$$type: '@backstage/BackstageCredentials',
version: 'v1',
principal: {
type: 'none',
principal,
} as const;
Object.defineProperties(result, {
toString: {
enumerable: false,
configurable: true,
writable: true,
value: () => `backstageCredentials{${principal}}`,
},
};
});
return result;
}
export function toInternalBackstageCredentials(
@@ -106,3 +124,74 @@ export function toInternalBackstageCredentials(
return internalCredentials;
}
function createServicePrincipal(
sub: string,
accessRestrictions?: BackstagePrincipalAccessRestrictions,
): BackstageServicePrincipal {
const result = {
type: 'service',
subject: sub,
accessRestrictions,
} as const;
Object.defineProperties(result, {
toString: {
enumerable: false,
configurable: true,
writable: true,
value: () => {
let parts = sub;
if (accessRestrictions) {
const hash = createHash('sha256')
.update(JSON.stringify(accessRestrictions))
.digest('base64')
.replace(/=+$/, '');
parts += `,accessRestrictions=${hash}`;
}
return `servicePrincipal{${parts}}`;
},
},
});
return result;
}
function createUserPrincipal(
userEntityRef: string,
actor?: BackstageServicePrincipal,
): BackstageUserPrincipal {
const result = {
type: 'user',
userEntityRef,
actor,
} as const;
Object.defineProperties(result, {
toString: {
enumerable: false,
configurable: true,
writable: true,
value: () => {
let parts = userEntityRef;
if (actor) {
parts += `,actor={${actor}}`;
}
return `userPrincipal{${parts}}`;
},
},
});
return result;
}
function createNonePrincipal(): BackstageNonePrincipal {
const result = {
type: 'none',
} as const;
Object.defineProperties(result, {
toString: {
enumerable: false,
configurable: true,
writable: true,
value: () => 'nonePrincipal',
},
});
return result;
}
@@ -170,4 +170,27 @@ describe('mockCredentials', () => {
"Invalid user entity reference 'wrong', expected <kind>:<namespace>/<name>",
);
});
it('should have a serializable form', () => {
expect(String(mockCredentials.service('my-service'))).toMatchInlineSnapshot(
`"mockCredentials{servicePrincipal{my-service}}"`,
);
expect(
String(mockCredentials.user('user:default/mock')),
).toMatchInlineSnapshot(
`"mockCredentials{userPrincipal{user:default/mock}}"`,
);
expect(
String(
mockCredentials.user('user:default/mock', {
actor: { subject: 'my-actor' },
}),
),
).toMatchInlineSnapshot(
`"mockCredentials{userPrincipal{user:default/mock,actor={my-actor}}}"`,
);
expect(String(mockCredentials.none())).toMatchInlineSnapshot(
`"mockCredentials{nonePrincipal}"`,
);
});
});
@@ -76,10 +76,19 @@ export namespace mockCredentials {
* Creates a mocked credentials object for a unauthenticated principal.
*/
export function none(): BackstageCredentials<BackstageNonePrincipal> {
return {
const result = {
$$type: '@backstage/BackstageCredentials',
principal: { type: 'none' },
};
} as const;
Object.defineProperties(result, {
toString: {
enumerable: false,
configurable: true,
writable: true,
value: () => `mockCredentials{nonePrincipal}`,
},
});
return result;
}
/**
@@ -111,24 +120,32 @@ export namespace mockCredentials {
options?: { actor?: { subject: string } },
): BackstageCredentials<BackstageUserPrincipal> {
validateUserEntityRef(userEntityRef);
return Object.defineProperty(
{
$$type: '@backstage/BackstageCredentials',
principal: {
type: 'user',
userEntityRef,
...(options?.actor && {
actor: { type: 'service', subject: options.actor.subject },
}),
},
const result = {
$$type: '@backstage/BackstageCredentials',
principal: {
type: 'user',
userEntityRef,
...(options?.actor && {
actor: { type: 'service', subject: options.actor.subject } as const,
}),
},
'token',
{
} as const;
Object.defineProperties(result, {
toString: {
enumerable: false,
configurable: true,
value: () =>
`mockCredentials{userPrincipal{${userEntityRef}${
options?.actor ? `,actor={${options.actor.subject}}` : ''
}}}`,
},
token: {
enumerable: false,
configurable: true,
value: user.token(),
},
);
});
return result;
}
/**
@@ -231,14 +248,27 @@ export namespace mockCredentials {
subject: string = DEFAULT_MOCK_SERVICE_SUBJECT,
accessRestrictions?: BackstagePrincipalAccessRestrictions,
): BackstageCredentials<BackstageServicePrincipal> {
return {
const result = {
$$type: '@backstage/BackstageCredentials',
principal: {
type: 'service',
subject,
...(accessRestrictions ? { accessRestrictions } : {}),
},
};
} as const;
Object.defineProperties(result, {
toString: {
enumerable: false,
configurable: true,
value: () =>
`mockCredentials{servicePrincipal{${subject}${
accessRestrictions
? `,accessRestrictions=${JSON.stringify(accessRestrictions)}`
: ''
}}}`,
},
});
return result;
}
/**
-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';
@@ -1285,6 +1287,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';
@@ -1552,6 +1575,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" />,
},
};
@@ -104,10 +104,3 @@
cursor: not-allowed;
border: 1px solid var(--canon-border-disabled);
}
.canon-TextFieldError {
color: var(--canon-fg-danger);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
margin-top: var(--canon-space-2);
}
@@ -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;
@@ -84,9 +82,10 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
<Input
className="canon-TextFieldInput"
{...(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'],
});
}
+2 -2
View File
@@ -5,13 +5,13 @@ module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
name: 'path',
importNames: ['resolve'],
message:
'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-common` instead as it prevents security issues',
'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-plugin-api` instead as it prevents security issues',
},
],
restrictedSrcSyntax: [
{
message:
'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-common` instead as it prevents security issues',
'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-plugin-api` instead as it prevents security issues',
selector: 'MemberExpression[object.name="path"][property.name="resolve"]',
},
],
-1
View File
@@ -61,7 +61,6 @@
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/backend-common": "^0.25.0",
"@backstage/backend-defaults": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/catalog-model": "workspace:^",
@@ -14,12 +14,15 @@
* limitations under the License.
*/
import { DatabaseManager } from '@backstage/backend-common';
import { DatabaseManager } from '@backstage/backend-defaults/database';
import { ConfigReader } from '@backstage/config';
import { DatabaseTaskStore, RawDbTaskEventRow } from './DatabaseTaskStore';
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
import { ConflictError } from '@backstage/errors';
import { createMockDirectory } from '@backstage/backend-test-utils';
import {
mockServices,
createMockDirectory,
} from '@backstage/backend-test-utils';
import fs from 'fs-extra';
import { EventsService } from '@backstage/plugin-events-node';
@@ -33,7 +36,10 @@ const createStore = async (events?: EventsService) => {
},
},
}),
).forPlugin('scaffolder');
).forPlugin('scaffolder', {
logger: mockServices.logger.mock(),
lifecycle: mockServices.lifecycle.mock(),
});
const store = await DatabaseTaskStore.create({
database: manager,
events,
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { DatabaseManager } from '@backstage/backend-common';
import { DatabaseManager } from '@backstage/backend-defaults/database';
import { ConfigReader } from '@backstage/config';
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
import {
@@ -36,7 +36,10 @@ async function createStore(): Promise<DatabaseTaskStore> {
},
},
}),
).forPlugin('scaffolder');
).forPlugin('scaffolder', {
logger: mockServices.logger.mock(),
lifecycle: mockServices.lifecycle.mock(),
});
return await DatabaseTaskStore.create({
database: manager,
@@ -15,7 +15,7 @@
*/
import os from 'os';
import { DatabaseManager } from '@backstage/backend-common';
import { DatabaseManager } from '@backstage/backend-defaults/database';
import { ConfigReader } from '@backstage/config';
import { DatabaseTaskStore } from './DatabaseTaskStore';
import { StorageTaskBroker } from './StorageTaskBroker';
@@ -49,7 +49,10 @@ async function createStore(): Promise<DatabaseTaskStore> {
},
},
}),
).forPlugin('scaffolder');
).forPlugin('scaffolder', {
logger: mockServices.logger.mock(),
lifecycle: mockServices.lifecycle.mock(),
});
return await DatabaseTaskStore.create({
database: manager,
});
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { DatabaseManager } from '@backstage/backend-common';
import { DatabaseManager } from '@backstage/backend-defaults/database';
import { ConfigReader } from '@backstage/config';
import request from 'supertest';
import ObservableImpl from 'zen-observable';
@@ -77,7 +77,10 @@ function createDatabase(): DatabaseService {
},
},
}),
).forPlugin('scaffolder');
).forPlugin('scaffolder', {
logger: mockServices.logger.mock(),
lifecycle: mockServices.lifecycle.mock(),
});
}
const config = new ConfigReader({});
+37 -38
View File
@@ -7499,7 +7499,6 @@ __metadata:
resolution: "@backstage/plugin-scaffolder-backend@workspace:plugins/scaffolder-backend"
dependencies:
"@backstage/backend-app-api": "workspace:^"
"@backstage/backend-common": "npm:^0.25.0"
"@backstage/backend-defaults": "workspace:^"
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/backend-test-utils": "workspace:^"
@@ -8757,9 +8756,9 @@ __metadata:
languageName: node
linkType: hard
"@changesets/assemble-release-plan@npm:^6.0.8":
version: 6.0.8
resolution: "@changesets/assemble-release-plan@npm:6.0.8"
"@changesets/assemble-release-plan@npm:^6.0.9":
version: 6.0.9
resolution: "@changesets/assemble-release-plan@npm:6.0.9"
dependencies:
"@changesets/errors": "npm:^0.2.0"
"@changesets/get-dependents-graph": "npm:^2.1.3"
@@ -8767,7 +8766,7 @@ __metadata:
"@changesets/types": "npm:^6.1.0"
"@manypkg/get-packages": "npm:^1.1.3"
semver: "npm:^7.5.3"
checksum: 10/5d01fc42c67229874cc70b93fbdc971e11909aa7a72f1909c585ecb3fdc69f3ac105d243e1341cd5b07c02dee133be461fa48138125f00d137e71f8b7e8f428e
checksum: 10/f84656eabb700ed77f97751b282e1701636ed45a44b443abd9af0291870495cc046fee301478010f39a1dc455799065ae007b9d7d2bb5ae8b793b65bbb8e052a
languageName: node
linkType: hard
@@ -8781,16 +8780,16 @@ __metadata:
linkType: hard
"@changesets/cli@npm:^2.14.0":
version: 2.29.4
resolution: "@changesets/cli@npm:2.29.4"
version: 2.29.5
resolution: "@changesets/cli@npm:2.29.5"
dependencies:
"@changesets/apply-release-plan": "npm:^7.0.12"
"@changesets/assemble-release-plan": "npm:^6.0.8"
"@changesets/assemble-release-plan": "npm:^6.0.9"
"@changesets/changelog-git": "npm:^0.2.1"
"@changesets/config": "npm:^3.1.1"
"@changesets/errors": "npm:^0.2.0"
"@changesets/get-dependents-graph": "npm:^2.1.3"
"@changesets/get-release-plan": "npm:^4.0.12"
"@changesets/get-release-plan": "npm:^4.0.13"
"@changesets/git": "npm:^3.0.4"
"@changesets/logger": "npm:^0.1.1"
"@changesets/pre": "npm:^2.0.2"
@@ -8814,7 +8813,7 @@ __metadata:
term-size: "npm:^2.1.0"
bin:
changeset: bin.js
checksum: 10/fc325447b81a811464107e72a687f6c0414c5f928e518cb122d1efde1d71c205b1972464795ab97fb26087900ea55b99a551e9a010c9681def3d7561fd1c3f0b
checksum: 10/f401da29025d7bcc07b732bb09a9627f785bfc21c7c2005861d11ffea732bc14d33394fc2fcae50cc5f2b710f6080c5babe2fa90d432de5fdb47ae6afc147936
languageName: node
linkType: hard
@@ -8854,17 +8853,17 @@ __metadata:
languageName: node
linkType: hard
"@changesets/get-release-plan@npm:^4.0.12":
version: 4.0.12
resolution: "@changesets/get-release-plan@npm:4.0.12"
"@changesets/get-release-plan@npm:^4.0.13":
version: 4.0.13
resolution: "@changesets/get-release-plan@npm:4.0.13"
dependencies:
"@changesets/assemble-release-plan": "npm:^6.0.8"
"@changesets/assemble-release-plan": "npm:^6.0.9"
"@changesets/config": "npm:^3.1.1"
"@changesets/pre": "npm:^2.0.2"
"@changesets/read": "npm:^0.6.5"
"@changesets/types": "npm:^6.1.0"
"@manypkg/get-packages": "npm:^1.1.3"
checksum: 10/d6482ecb6f1c2c47266493a36d05b484f0950d0a4472820649e953d073e3fdd612cdd8a4df9e3d7e00756d4e446dae639f9d6e0dab8a25f76bb6df77cd91c21c
checksum: 10/9983fae5a68012c4c418ddd62f2fb3d325363f21160252ff7b868503a1a2effb8fdd32e4a0289b72653afc3605ce19d163ff69205c942a0004efb571a5f78fd0
languageName: node
linkType: hard
@@ -9105,11 +9104,11 @@ __metadata:
linkType: hard
"@dagrejs/dagre@npm:^1.1.4":
version: 1.1.4
resolution: "@dagrejs/dagre@npm:1.1.4"
version: 1.1.5
resolution: "@dagrejs/dagre@npm:1.1.5"
dependencies:
"@dagrejs/graphlib": "npm:2.2.4"
checksum: 10/0b3744b170c68ae0666e03aca19c3100d5131feafeb54b3ea096b749a9f0fe5385b8bd8889c11a49493cfab945b2486b9e30bc41b321755ed718e9f5cb4b74f1
checksum: 10/c00abd1e04d19f90ad8dfa0a4e16365371bc4309affead3827a1b39f6b0b946643b8af0b1e5519011deca3fda4c7471b27e9ebb03423309a94f95ac0b881ac4f
languageName: node
linkType: hard
@@ -14865,13 +14864,13 @@ __metadata:
linkType: hard
"@playwright/test@npm:^1.32.3":
version: 1.53.0
resolution: "@playwright/test@npm:1.53.0"
version: 1.53.1
resolution: "@playwright/test@npm:1.53.1"
dependencies:
playwright: "npm:1.53.0"
playwright: "npm:1.53.1"
bin:
playwright: cli.js
checksum: 10/968df4fba133dd18b8c65504c3cc5a3a6071e49f0706c6524711cdfab321a51debfeb506b9ff0a8f7dd8ce3015921d82fa51429d8f11d392cc68de1938703c33
checksum: 10/98fb9b962710183d465b695daab2006296fd9a703ecb1b763a38cd12a39f7d6066f9539d1758e54313d393353fafb16b90fb31e4add1ca99ffec99b8b1b40fb9
languageName: node
linkType: hard
@@ -21782,9 +21781,9 @@ __metadata:
linkType: hard
"@types/lodash@npm:^4.14.151":
version: 4.17.17
resolution: "@types/lodash@npm:4.17.17"
checksum: 10/496459a3cb1a0733bb60532de3899ad6297717af0b9b26ad6821154b2005fec86f29ccd47a2e6f4da4a8c7c818bb8ae73901144e8057ea86b7b02a3d7bb9d13f
version: 4.17.18
resolution: "@types/lodash@npm:4.17.18"
checksum: 10/54ebb15b29925112dbe9da3abd99fb80d7202bc5ba20fc1b4fc8ea835d0012f00cbd9a3e7f367b70e7c3f2d5ee635964e3920a489625647b558f02994b3dd381
languageName: node
linkType: hard
@@ -24385,13 +24384,13 @@ __metadata:
languageName: node
linkType: hard
"GendocuPublicApis@https://git.gendocu.com/gendocu/GendocuPublicApis.git#master":
version: 1.0.0
resolution: "GendocuPublicApis@https://git.gendocu.com/gendocu/GendocuPublicApis.git#commit=6e8d4c1d2342556a2e8e719f19385b266001ebae"
"GendocuPublicApis@npm:gendocu-public-apis@^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
@@ -42032,27 +42031,27 @@ __metadata:
languageName: node
linkType: hard
"playwright-core@npm:1.53.0":
version: 1.53.0
resolution: "playwright-core@npm:1.53.0"
"playwright-core@npm:1.53.1":
version: 1.53.1
resolution: "playwright-core@npm:1.53.1"
bin:
playwright-core: cli.js
checksum: 10/881f27a9b7edd9954700489a5a4212cb91bcada226fd1d79a239b2eab0f333df1e2e41e275e6fa846d7f57c6a92afe14dca33ca7a2ce303dfb687d02511b7c69
checksum: 10/d0ea8674c3abb76069255ca81bc0dfdef3f9548207f1404eec036bb8724135710f25ee791bfd7c043d5b9c2ccfa42288b0308d61dc5efc60a13b18811a15c4cd
languageName: node
linkType: hard
"playwright@npm:1.53.0":
version: 1.53.0
resolution: "playwright@npm:1.53.0"
"playwright@npm:1.53.1":
version: 1.53.1
resolution: "playwright@npm:1.53.1"
dependencies:
fsevents: "npm:2.3.2"
playwright-core: "npm:1.53.0"
playwright-core: "npm:1.53.1"
dependenciesMeta:
fsevents:
optional: true
bin:
playwright: cli.js
checksum: 10/0b0258630f39b4d6ff1555d008ee4d591fe45cbe1e0f643a612397e3e6b1f7a99a2037a957eaa7351edd907ba10966ba105b2d244eafd1b247378910b660f086
checksum: 10/74b3178d5ae3fde8de08fe6c221578530368f1abb8794fce234d06e0043178201eb3b7410354418517f23c105bd54ac1432da5f46c50353a5e6a80198a95f2cf
languageName: node
linkType: hard