Merge branch 'master' into canon-menu

This commit is contained in:
Charles de Dreuille
2025-03-16 18:47:37 +00:00
38 changed files with 944 additions and 336 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/config-loader': patch
---
Added `@types/minimist` to `devDependencies`
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-defaults': patch
---
Remove unused dependencies
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': minor
---
We added a new IconButton component with fixed sizes showcasing a single icon.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-app-api': patch
---
Remove unused dependencies
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-permission-node': patch
---
Deprecated `createPermissionIntegrationRouter` and related types, which has been replaced by `PermissionRegistryService`. For more information, including how to migrate existing plugins, see the [service docs](https://backstage.io/docs/backend-system/core-services/permissions-registry).
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': minor
---
Added a new ScrollArea component for Canon.
@@ -0,0 +1,111 @@
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { Tabs } from '@/components/Tabs';
import { CodeBlock } from '@/components/CodeBlock';
import {
IconButtonPreview,
IconButtonSizes,
IconButtonDisabled,
IconButtonResponsive,
} from '@/snippets/icon-button';
import { iconButtonVariants } from '@/snippets/_snippets';
import { iconButtonPropDefs } from '../../../../../../packages/canon/src/components/IconButton/IconButton.props';
# Icon Button
A button component with a single icon that can be used to trigger actions.
<Snippet
align="center"
py={4}
preview={<IconButtonPreview />}
code={`<Flex align="center">
<IconButton icon="cloud" variant="primary" />
<IconButton icon="cloud" variant="secondary" />
</Flex>`}
/>
<Tabs.Root>
<Tabs.List>
<Tabs.Tab>Usage</Tabs.Tab>
<Tabs.Tab>Theming</Tabs.Tab>
</Tabs.List>
<Tabs.Panel>
<CodeBlock
code={`import { IconButton } from '@backstage/canon';
<IconButton />
`}
/>
</Tabs.Panel>
<Tabs.Panel>
We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with
your brand. For additional flexibility, you can use the provided class names for each element listed below.
<CodeBlock
code={`<IconButton className="canon-IconButton" />`}
/>
</Tabs.Panel>
</Tabs.Root>
## API reference
<PropsTable
data={{
...iconButtonPropDefs,
icon: { values: 'icon', responsive: false },
}}
/>
## Examples
### Variants
Here's a view when buttons have different variants.
<Snippet
align="center"
py={4}
open
preview={<IconButtonPreview />}
code={iconButtonVariants}
/>
### Sizes
Here's a view when buttons have different sizes.
<Snippet
align="center"
py={4}
open
preview={<IconButtonSizes />}
code={`<Flex align="center">
<IconButton icon="cloud" size="small" />
<IconButton icon="cloud" size="medium" />
</Flex>`}
/>
### Disabled
Here's a view when buttons are disabled.
<Snippet
align="center"
py={4}
open
preview={<IconButtonDisabled />}
code={`<IconButton icon="cloud" disabled />`}
/>
### Responsive
Here's a view when buttons are responsive.
<Snippet
align="center"
py={4}
open
preview={<IconButtonResponsive />}
code={`<IconButton icon="cloud" variant={{ initial: 'primary', lg: 'secondary' }} />`}
/>
@@ -19,10 +19,6 @@ export const PropsTable = <T extends Record<string, PropData>>({
}) => {
const completeData = {
...data,
children: {
values: 'ReactNode',
responsive: false,
},
className: {
values: 'string',
responsive: false,
+8 -2
View File
@@ -38,7 +38,7 @@ export const grid = `import { Grid } from '@backstage/canon';
</Grid>
`;
export const buttonVariants = `<Inline alignY="center">
export const buttonVariants = `<Flex align="center">
<Button iconStart="cloud" variant="primary">
Button
</Button>
@@ -48,7 +48,13 @@ export const buttonVariants = `<Inline alignY="center">
<Button iconStart="cloud" variant="tertiary">
Button
</Button>
</Inline>
</Flex>
`;
export const iconButtonVariants = `<Flex align="center">
<IconButton icon="cloud" variant="primary" />
<IconButton icon="cloud" variant="secondary" />
</Flex>
`;
export const flexFAQ1 = `<Grid columns={3} gap="4">
+57
View File
@@ -0,0 +1,57 @@
'use client';
import { IconButton, Flex, ButtonProps, Text } from '../../../packages/canon';
export const IconButtonPreview = () => {
return (
<Flex align="center">
<IconButton icon="cloud" variant="primary" />
<IconButton icon="cloud" variant="secondary" />
</Flex>
);
};
export const IconButtonSizes = () => {
return (
<Flex align="center">
<IconButton icon="cloud" size="medium" />
<IconButton icon="cloud" size="small" />
</Flex>
);
};
export const IconButtonDisabled = () => {
return <IconButton icon="cloud" disabled />;
};
export const IconButtonResponsive = () => {
return (
<IconButton
icon="cloud"
variant={{ initial: 'primary', lg: 'secondary' }}
/>
);
};
export const IconButtonPlayground = () => {
const variants: string[] = ['primary', 'secondary'];
return (
<Flex direction="column">
{variants.map(variant => (
<Flex direction="column" key={variant}>
<Text>{variant}</Text>
{['small', 'medium'].map(size => (
<Flex align="center" key={size}>
<IconButton
icon="cloud"
variant={variant as ButtonProps['variant']}
size={size as ButtonProps['size']}
/>
</Flex>
))}
</Flex>
))}
</Flex>
);
};
+5
View File
@@ -87,6 +87,11 @@ export const components: Page[] = [
slug: 'icon',
status: 'alpha',
},
{
title: 'IconButton',
slug: 'icon-button',
status: 'alpha',
},
{
title: 'Input',
slug: 'input',
@@ -1,5 +1,5 @@
---
id: permissions
id: permissions-registry
title: Permissions Registry Service
sidebar_label: Permissions Registry
description: Documentation for the Permissions Registry service
+1
View File
@@ -450,6 +450,7 @@ export default {
'backend-system/core-services/lifecycle',
'backend-system/core-services/logger',
'backend-system/core-services/permissions',
'backend-system/core-services/permissions-registry',
'backend-system/core-services/plugin-metadata',
'backend-system/core-services/root-config',
'backend-system/core-services/root-health',
+1 -42
View File
@@ -1,49 +1,8 @@
# Knip report
## Unused dependencies (27)
| Name | Location | Severity |
| :-------------------------------- | :----------- | :------- |
| @backstage/plugin-permission-node | package.json | error |
| @backstage/plugin-auth-node | package.json | error |
| @backstage/config-loader | package.json | error |
| @backstage/cli-common | package.json | error |
| @manypkg/get-packages | package.json | error |
| @backstage/config | package.json | error |
| winston-transport | package.json | error |
| @backstage/types | package.json | error |
| path-to-regexp | package.json | error |
| compression | package.json | error |
| triple-beam | package.json | error |
| node-forge | package.json | error |
| selfsigned | package.json | error |
| minimatch | package.json | error |
| stoppable | package.json | error |
| minimist | package.json | error |
| logform | package.json | error |
| winston | package.json | error |
| cookie | package.json | error |
| helmet | package.json | error |
| lodash | package.json | error |
| morgan | package.json | error |
| luxon | package.json | error |
| cors | package.json | error |
| jose | package.json | error |
| knex | package.json | error |
| uuid | package.json | error |
## Unused devDependencies (10)
## Unused devDependencies (1)
| Name | Location | Severity |
| :-------------------------- | :----------- | :------- |
| @backstage/backend-defaults | package.json | error |
| @types/compression | package.json | error |
| @types/http-errors | package.json | error |
| @types/node-forge | package.json | error |
| @types/stoppable | package.json | error |
| @types/minimist | package.json | error |
| @types/morgan | package.json | error |
| http-errors | package.json | error |
| supertest | package.json | error |
| msw | package.json | error |
+2 -38
View File
@@ -47,48 +47,12 @@
},
"dependencies": {
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/cli-common": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/config-loader": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/plugin-auth-node": "workspace:^",
"@backstage/plugin-permission-node": "workspace:^",
"@backstage/types": "workspace:^",
"@manypkg/get-packages": "^1.1.3",
"compression": "^1.7.4",
"cookie": "^0.7.0",
"cors": "^2.8.5",
"helmet": "^6.0.0",
"jose": "^5.0.0",
"knex": "^3.0.0",
"lodash": "^4.17.21",
"logform": "^2.3.2",
"luxon": "^3.0.0",
"minimatch": "^9.0.0",
"minimist": "^1.2.5",
"morgan": "^1.10.0",
"node-forge": "^1.3.1",
"path-to-regexp": "^8.0.0",
"selfsigned": "^2.0.0",
"stoppable": "^1.1.0",
"triple-beam": "^1.4.1",
"uuid": "^11.0.0",
"winston": "^3.2.1",
"winston-transport": "^4.5.0"
"@backstage/errors": "workspace:^"
},
"devDependencies": {
"@backstage/backend-defaults": "workspace:^",
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@types/compression": "^1.7.0",
"@types/http-errors": "^2.0.0",
"@types/minimist": "^1.2.0",
"@types/morgan": "^1.9.0",
"@types/node-forge": "^1.3.0",
"@types/stoppable": "^1.1.0",
"http-errors": "^2.0.0",
"msw": "^1.0.0",
"supertest": "^7.0.0"
"@backstage/cli": "workspace:^"
},
"configSchema": "config.d.ts"
}
+5 -15
View File
@@ -1,21 +1,11 @@
# Knip report
## Unused dependencies (6)
## Unused dependencies (2)
| Name | Location | Severity |
| :-------------------- | :----------- | :------- |
| @backstage/cli-common | package.json | error |
| @azure/identity | package.json | error |
| better-sqlite3 | package.json | error |
| isomorphic-git | package.json | error |
| minimist | package.json | error |
| mysql2 | package.json | error |
## Unused devDependencies (1)
| Name | Location | Severity |
| :--------------- | :----------- | :------- |
| @types/stoppable | package.json | error |
| Name | Location | Severity |
| :------------- | :----------- | :------- |
| better-sqlite3 | package.json | error |
| mysql2 | package.json | error |
## Referenced optional peerDependencies (1)
+1 -5
View File
@@ -127,12 +127,10 @@
"@aws-sdk/client-s3": "^3.350.0",
"@aws-sdk/credential-providers": "^3.350.0",
"@aws-sdk/types": "^3.347.0",
"@azure/identity": "^4.0.0",
"@azure/storage-blob": "^12.5.0",
"@backstage/backend-app-api": "workspace:^",
"@backstage/backend-dev-utils": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/cli-common": "workspace:^",
"@backstage/cli-node": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/config-loader": "workspace:^",
@@ -164,7 +162,6 @@
"fs-extra": "^11.2.0",
"git-url-parse": "^15.0.0",
"helmet": "^6.0.0",
"isomorphic-git": "^1.23.0",
"jose": "^5.0.0",
"keyv": "^5.2.1",
"knex": "^3.0.0",
@@ -172,7 +169,6 @@
"logform": "^2.3.2",
"luxon": "^3.0.0",
"minimatch": "^9.0.0",
"minimist": "^1.2.5",
"mysql2": "^3.0.0",
"node-fetch": "^2.7.0",
"node-forge": "^1.3.1",
@@ -201,11 +197,11 @@
"@google-cloud/cloud-sql-connector": "^1.4.0",
"@types/archiver": "^6.0.0",
"@types/base64-stream": "^1.0.2",
"@types/compression": "^1.7.5",
"@types/concat-stream": "^2.0.0",
"@types/http-errors": "^2.0.0",
"@types/node-forge": "^1.3.0",
"@types/pg-format": "^1.0.5",
"@types/stoppable": "^1.1.0",
"@types/yauzl": "^2.10.0",
"aws-sdk-client-mock": "^4.0.0",
"http-errors": "^2.0.0",
@@ -35,14 +35,6 @@ const treeResponseFactory = DefaultReadTreeResponseFactory.create({
config: new ConfigReader({}),
});
jest.mock('./git', () => ({
Git: {
fromAuth: () => ({
clone: jest.fn(() => Promise.resolve({})),
}),
},
}));
const giteaProcessor = new GiteaUrlReader(
new GiteaIntegration(
readGiteaConfig(
@@ -35,14 +35,6 @@ const treeResponseFactory = DefaultReadTreeResponseFactory.create({
config: new ConfigReader({}),
});
jest.mock('./git', () => ({
Git: {
fromAuth: () => ({
clone: jest.fn(() => Promise.resolve({})),
}),
},
}));
const harnessProcessor = new HarnessUrlReader(
new HarnessIntegration(
readHarnessConfig(
@@ -1,148 +0,0 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fs from 'fs-extra';
import isomorphicGit, { ProgressCallback, AuthCallback } from 'isomorphic-git';
import http from 'isomorphic-git/http/node';
import { LoggerService } from '@backstage/backend-plugin-api';
/**
* Configure static credential for authentication
*
* @public
*/
export type StaticAuthOptions = {
username?: string;
password?: string;
token?: string;
logger?: LoggerService;
};
/**
* Configure an authentication callback that can provide credentials on demand
*
* @public
*/
export type AuthCallbackOptions = {
onAuth: AuthCallback;
logger?: LoggerService;
};
function isAuthCallbackOptions(
options: StaticAuthOptions | AuthCallbackOptions,
): options is AuthCallbackOptions {
return 'onAuth' in options;
}
/*
provider username password
Azure 'notempty' token
Bitbucket Cloud 'x-token-auth' token
Bitbucket Server username password or token
GitHub 'x-access-token' token
GitLab 'oauth2' token
From : https://isomorphic-git.org/docs/en/onAuth with fix for GitHub
Or token provided as `token` for Bearer auth header
instead of Basic Auth (e.g., Bitbucket Server).
*/
/**
* A convenience wrapper around the `isomorphic-git` library.
*
* @public
*/
export class Git {
private readonly headers: {
[x: string]: string;
};
private constructor(
private readonly config: {
onAuth: AuthCallback;
token?: string;
logger?: LoggerService;
},
) {
this.onAuth = config.onAuth;
this.headers = {
'user-agent': 'git/@isomorphic-git',
...(config.token ? { Authorization: `Bearer ${config.token}` } : {}),
};
}
/** https://isomorphic-git.org/docs/en/clone */
async clone(options: {
url: string;
dir: string;
ref?: string;
depth?: number;
noCheckout?: boolean;
}): Promise<void> {
const { url, dir, ref, depth, noCheckout } = options;
this.config.logger?.info(`Cloning repo {dir=${dir},url=${url}}`);
try {
return await isomorphicGit.clone({
fs,
http,
url,
dir,
ref,
singleBranch: true,
depth: depth ?? 1,
noCheckout,
onProgress: this.onProgressHandler(),
headers: this.headers,
onAuth: this.onAuth,
});
} catch (ex) {
this.config.logger?.error(`Failed to clone repo {dir=${dir},url=${url}}`);
if (ex.data) {
throw new Error(`${ex.message} {data=${JSON.stringify(ex.data)}}`);
}
throw ex;
}
}
private onAuth: AuthCallback;
private onProgressHandler = (): ProgressCallback => {
let currentPhase = '';
return event => {
if (currentPhase !== event.phase) {
currentPhase = event.phase;
this.config.logger?.info(event.phase);
}
const total = event.total
? `${Math.round((event.loaded / event.total) * 100)}%`
: event.loaded;
this.config.logger?.debug(`status={${event.phase},total={${total}}}`);
};
};
static fromAuth = (options: StaticAuthOptions | AuthCallbackOptions) => {
if (isAuthCallbackOptions(options)) {
const { onAuth, logger } = options;
return new Git({ onAuth, logger });
}
const { username, password, token, logger } = options;
return new Git({ onAuth: () => ({ username, password }), token, logger });
};
}
+67
View File
@@ -13,6 +13,7 @@ import { default as React_2 } from 'react';
import * as React_3 from 'react';
import { ReactNode } from 'react';
import { RefAttributes } from 'react';
import { ScrollArea as ScrollArea_2 } from '@base-ui-components/react/scroll-area';
// @public (undocumented)
export type AlignItems = 'stretch' | 'start' | 'center' | 'end';
@@ -590,6 +591,40 @@ export type HeightProps = GetPropDefTypes<typeof heightPropDefs>;
// @public (undocumented)
export const Icon: (props: IconProps) => React_2.JSX.Element;
// @public (undocumented)
export const IconButton: React_2.ForwardRefExoticComponent<
IconButtonProps & React_2.RefAttributes<HTMLButtonElement>
>;
// @public (undocumented)
export type IconButtonOwnProps = GetPropDefTypes<typeof iconButtonPropDefs>;
// @public (undocumented)
export const iconButtonPropDefs: {
variant: {
type: 'enum';
values: ('primary' | 'secondary')[];
className: string;
default: 'primary';
responsive: true;
};
size: {
type: 'enum';
values: ('small' | 'medium')[];
className: string;
default: 'medium';
responsive: true;
};
};
// @public
export interface IconButtonProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
icon: IconNames;
size?: IconButtonOwnProps['size'];
variant?: IconButtonOwnProps['variant'];
}
// @public (undocumented)
export const IconContext: Context<IconContextProps>;
@@ -852,6 +887,38 @@ export type ResponsivePropDef<T = any> = RegularPropDef<T> & {
responsive: true;
};
// @public (undocumented)
export const ScrollArea: {
Root: React_2.ForwardRefExoticComponent<
Omit<
ScrollArea_2.Root.Props & React_2.RefAttributes<HTMLDivElement>,
'ref'
> &
React_2.RefAttributes<HTMLDivElement>
>;
Viewport: React_2.ForwardRefExoticComponent<
Omit<
ScrollArea_2.Viewport.Props & React_2.RefAttributes<HTMLDivElement>,
'ref'
> &
React_2.RefAttributes<HTMLDivElement>
>;
Scrollbar: React_2.ForwardRefExoticComponent<
Omit<
ScrollArea_2.Scrollbar.Props & React_2.RefAttributes<HTMLDivElement>,
'ref'
> &
React_2.RefAttributes<HTMLDivElement>
>;
Thumb: React_2.ForwardRefExoticComponent<
Omit<
ScrollArea_2.Thumb.Props & React_2.RefAttributes<HTMLDivElement>,
'ref'
> &
React_2.RefAttributes<HTMLDivElement>
>;
};
// @public (undocumented)
export type Space =
| '0.5'
@@ -0,0 +1,41 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { PropDef, GetPropDefTypes } from '../../props/prop-def';
/** @public */
export const iconButtonPropDefs = {
variant: {
type: 'enum',
values: ['primary', 'secondary'],
className: 'canon-Button--variant',
default: 'primary',
responsive: true,
},
size: {
type: 'enum',
values: ['small', 'medium'],
className: 'canon-Button--size',
default: 'medium',
responsive: true,
},
} satisfies {
variant: PropDef<'primary' | 'secondary'>;
size: PropDef<'small' | 'medium'>;
};
/** @public */
export type IconButtonOwnProps = GetPropDefTypes<typeof iconButtonPropDefs>;
@@ -0,0 +1,140 @@
/*
* 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 React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { IconButton } from './IconButton';
import { Flex } from '../Flex';
import { Text } from '../Text';
import { IconButtonProps } from './types';
const meta = {
title: 'Components/IconButton',
component: IconButton,
argTypes: {
size: {
control: 'select',
options: ['small', 'medium'],
},
variant: {
control: 'select',
options: ['primary', 'secondary'],
},
},
args: {
size: 'medium',
variant: 'primary',
},
} satisfies Meta<typeof IconButton>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Variants: Story = {
args: {
icon: 'cloud',
},
parameters: {
argTypes: {
variant: {
control: false,
},
},
},
render: args => (
<Flex align="center">
<IconButton {...args} variant="primary" />
<IconButton {...args} variant="secondary" />
</Flex>
),
};
export const Sizes: Story = {
args: {
icon: 'cloud',
},
render: args => (
<Flex align="center">
<IconButton {...args} size="medium" />
<IconButton {...args} size="small" />
</Flex>
),
};
export const Disabled: Story = {
args: {
icon: 'cloud',
disabled: true,
},
render: args => (
<Flex direction="row" gap="4">
<IconButton {...args} variant="primary" />
<IconButton {...args} variant="secondary" />
</Flex>
),
};
export const Responsive: Story = {
args: {
icon: 'cloud',
variant: {
initial: 'primary',
sm: 'secondary',
},
size: {
xs: 'small',
sm: 'medium',
},
},
};
const variants: string[] = ['primary', 'secondary'];
export const Playground: Story = {
args: {
icon: 'cloud',
},
render: args => (
<Flex direction="column">
{variants.map(variant => (
<Flex direction="column" key={variant}>
<Text>{variant}</Text>
{['small', 'medium'].map(size => (
<Flex align="center" key={size}>
<IconButton
{...args}
variant={variant as IconButtonProps['variant']}
size={size as IconButtonProps['size']}
/>
<IconButton
{...args}
icon="chevronRight"
variant={variant as IconButtonProps['variant']}
size={size as IconButtonProps['size']}
/>
<IconButton
{...args}
icon="chevronRight"
variant={variant as IconButtonProps['variant']}
size={size as IconButtonProps['size']}
/>
</Flex>
))}
</Flex>
))}
</Flex>
),
};
@@ -0,0 +1,57 @@
/*
* 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 React, { forwardRef } from 'react';
import { Icon } from '../Icon';
import clsx from 'clsx';
import { useResponsiveValue } from '../../hooks/useResponsiveValue';
import type { IconButtonProps } from './types';
/** @public */
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
(props: IconButtonProps, ref) => {
const {
size = 'medium',
variant = 'primary',
icon,
className,
style,
...rest
} = props;
const responsiveSize = useResponsiveValue(size);
const responsiveVariant = useResponsiveValue(variant);
return (
<button
ref={ref}
className={clsx(
'canon-IconButton',
`canon-IconButton--size-${responsiveSize}`,
`canon-IconButton--variant-${responsiveVariant}`,
className,
)}
style={style}
{...rest}
>
<Icon name={icon} className="canon-IconButton--icon" />
</button>
);
},
);
export default IconButton;
@@ -0,0 +1,20 @@
/*
* 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 { IconButton } from './IconButton';
export type { IconButtonProps } from './types';
export { iconButtonPropDefs } from './IconButton.props';
export type { IconButtonOwnProps } from './IconButton.props';
@@ -0,0 +1,105 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.canon-IconButton {
border: none;
display: inline-flex;
align-items: center;
justify-content: center;
user-select: none;
font-family: var(--canon-font-regular);
font-weight: var(--canon-font-weight-bold);
padding: 0;
cursor: pointer;
border-radius: var(--canon-radius-2);
gap: var(--canon-space-1_5);
&:disabled {
cursor: not-allowed;
}
}
.canon-IconButton--variant-primary {
background-color: var(--canon-bg-solid);
color: var(--canon-fg-solid);
transition: background-color 150ms ease, box-shadow 150ms ease;
&:hover {
background-color: var(--canon-bg-solid-hover);
}
&:active {
background-color: var(--canon-bg-solid-pressed);
}
&:focus-visible {
outline: 2px solid var(--canon-ring);
outline-offset: 2px;
}
&:disabled {
background-color: var(--canon-bg-solid-disabled);
color: var(--canon-fg-solid-disabled);
}
}
.canon-IconButton--variant-secondary {
background-color: var(--canon-bg-surface-1);
box-shadow: inset 0 0 0 1px var(--canon-border);
color: var(--canon-fg-primary);
transition: box-shadow 150ms ease;
&:hover {
box-shadow: inset 0 0 0 1px var(--canon-border-hover);
}
&:active {
box-shadow: inset 0 0 0 1px var(--canon-border-pressed);
}
&:focus-visible {
outline: none;
transition: none;
box-shadow: inset 0 0 0 2px var(--canon-ring);
}
&:disabled {
box-shadow: inset 0 0 0 1px var(--canon-border-disabled);
color: var(--canon-fg-disabled);
}
}
.canon-IconButton--size-medium {
font-size: var(--canon-font-size-4);
height: 40px;
width: 40px;
}
.canon-IconButton--size-small {
font-size: var(--canon-font-size-3);
height: 32px;
width: 32px;
}
.canon-IconButton--size-small .canon-IconButton--icon {
width: 1rem;
height: 1rem;
}
.canon-IconButton--size-medium .canon-IconButton--icon {
width: 1.5rem;
height: 1.5rem;
}
@@ -0,0 +1,42 @@
/*
* 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 { IconNames } from '../Icon';
import type { IconButtonOwnProps } from './IconButton.props';
/**
* Properties for {@link IconButton}
*
* @public
*/
export interface IconButtonProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
/**
* The size of the button
* @defaultValue 'medium'
*/
size?: IconButtonOwnProps['size'];
/**
* The visual variant of the button
* @defaultValue 'primary'
*/
variant?: IconButtonOwnProps['variant'];
/**
* Icon to display at the start of the button
*/
icon: IconNames;
}
@@ -0,0 +1,68 @@
/*
* 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 React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { ScrollArea } from './ScrollArea';
import { Text } from '../Text/Text';
const meta = {
title: 'Components/ScrollArea',
component: ScrollArea.Root,
} satisfies Meta<typeof ScrollArea.Root>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => (
<ScrollArea.Root style={{ width: '24rem', height: '8.5rem' }}>
<ScrollArea.Viewport
style={{ border: '1px solid var(--canon-border)', borderRadius: '4px' }}
>
<div style={{ padding: '0.75rem', paddingRight: '1.5rem' }}>
<Text style={{ paddingBottom: '1rem' }}>
Vernacular architecture is building done outside any academic
tradition, and without professional guidance. It is not a particular
architectural movement or style, but rather a broad category,
encompassing a wide range and variety of building types, with
differing methods of construction, from around the world, both
historical and extant and classical and modern. Vernacular
architecture constitutes 95% of the world's built environment, as
estimated in 1995 by Amos Rapoport, as measured against the small
percentage of new buildings every year designed by architects and
built by engineers.
</Text>
<Text>
This type of architecture usually serves immediate, local needs, is
constrained by the materials available in its particular region and
reflects local traditions and cultural practices. The study of
vernacular architecture does not examine formally schooled
architects, but instead that of the design skills and tradition of
local builders, who were rarely given any attribution for the work.
More recently, vernacular architecture has been examined by
designers and the building industry in an effort to be more energy
conscious with contemporary design and constructionpart of a
broader interest in sustainable design.
</Text>
</div>
</ScrollArea.Viewport>
<ScrollArea.Scrollbar orientation="vertical">
<ScrollArea.Thumb />
</ScrollArea.Scrollbar>
</ScrollArea.Root>
),
};
@@ -0,0 +1,67 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.canon-ScrollAreaRoot {
box-sizing: border-box;
width: 24rem;
height: 8.5rem;
max-width: calc(100vw - 8rem);
}
.canon-ScrollAreaViewport {
height: 100%;
overscroll-behavior: contain;
}
.canon-ScrollAreaContent {
display: flex;
flex-direction: column;
gap: 1rem;
padding-block: 0.75rem;
padding-left: 1rem;
padding-right: 1.5rem;
}
.canon-ScrollAreaScrollbar {
display: flex;
justify-content: center;
background-color: var(--canon-scrollbar);
width: 0.25rem;
border-radius: 0.375rem;
margin: 0.5rem;
opacity: 0;
transition: opacity 150ms 300ms;
&[data-hovering],
&[data-scrolling] {
opacity: 1;
transition-duration: 75ms;
transition-delay: 0ms;
}
&::before {
content: '';
position: absolute;
width: 1.25rem;
height: 100%;
}
}
.canon-ScrollAreaThumb {
width: 100%;
border-radius: inherit;
background-color: var(--canon-scrollbar-thumb);
}
@@ -0,0 +1,75 @@
/*
* 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 React from 'react';
import { ScrollArea as ScrollAreaPrimitive } from '@base-ui-components/react/scroll-area';
import clsx from 'clsx';
const ScrollAreaRoot = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={clsx('canon-ScrollAreaRoot', className)}
{...props}
/>
));
ScrollAreaRoot.displayName = ScrollAreaPrimitive.Root.displayName;
const ScrollAreaViewport = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<ScrollAreaPrimitive.Viewport
ref={ref}
className={clsx('canon-ScrollAreaViewport', className)}
{...props}
/>
));
ScrollAreaViewport.displayName = ScrollAreaPrimitive.Viewport.displayName;
const ScrollAreaScrollbar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Scrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Scrollbar>
>(({ className, ...props }, ref) => (
<ScrollAreaPrimitive.Scrollbar
ref={ref}
className={clsx('canon-ScrollAreaScrollbar', className)}
{...props}
/>
));
ScrollAreaScrollbar.displayName = ScrollAreaPrimitive.Scrollbar.displayName;
const ScrollAreaThumb = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Thumb>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Thumb>
>(({ className, ...props }, ref) => (
<ScrollAreaPrimitive.Thumb
ref={ref}
className={clsx('canon-ScrollAreaThumb', className)}
{...props}
/>
));
ScrollAreaThumb.displayName = ScrollAreaPrimitive.Thumb.displayName;
/** @public */
export const ScrollArea = {
Root: ScrollAreaRoot,
Viewport: ScrollAreaViewport,
Scrollbar: ScrollAreaScrollbar,
Thumb: ScrollAreaThumb,
};
@@ -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 './ScrollArea';
+2
View File
@@ -24,7 +24,9 @@
@import '../components/Table/styles.css';
@import '../components/Text/styles.css';
@import '../components/Heading/styles.css';
@import '../components/IconButton/styles.css';
@import '../components/Input/Input.styles.css';
@import '../components/Field/Field.styles.css';
@import '../components/Menu/Menu.styles.css';
@import '../components/Link/styles.css';
@import '../components/ScrollArea/ScrollArea.styles.css';
+8 -1
View File
@@ -109,8 +109,10 @@
--canon-border-warning: #e36d05;
--canon-border-success: #53db83;
/* Ring Colors */
/* Special Colors */
--canon-ring: #1f5493;
--canon-scrollbar: #a0a0a03b;
--canon-scrollbar-thumb: #a0a0a0;
}
/* Dark theme tokens */
@@ -153,4 +155,9 @@
--canon-border-danger: #f87a7a;
--canon-border-warning: #e36d05;
--canon-border-success: #53db83;
/* Special Colors */
--canon-ring: #1f5493;
--canon-scrollbar: #3636363a;
--canon-scrollbar-thumb: #575757;
}
+2
View File
@@ -34,11 +34,13 @@ export * from './components/Heading';
// UI components
export * from './components/Button';
export * from './components/Icon';
export * from './components/IconButton';
export * from './components/Checkbox';
export * from './components/Table';
export * from './components/Input';
export * from './components/Field';
export * from './components/Menu';
export * from './components/ScrollArea';
// Types
export * from './types';
+1
View File
@@ -56,6 +56,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@types/json-schema-merge-allof": "^0.6.0",
"@types/minimist": "^1.2.5",
"msw": "^1.0.0",
"zen-observable": "^0.10.0"
}
+3 -3
View File
@@ -138,7 +138,7 @@ export function createConditionTransformer<
TRules extends PermissionRule<any, TQuery, string>[],
>(permissionRules: [...TRules]): ConditionTransformer<TQuery>;
// @public
// @public @deprecated
export function createPermissionIntegrationRouter<
TResourceType1 extends string,
TResource1,
@@ -177,7 +177,7 @@ export function createPermissionIntegrationRouter<
): PermissionRuleset<TResource, TQuery, TResourceType>;
};
// @public
// @public @deprecated
export type CreatePermissionIntegrationRouterResourceOptions<
TResourceType extends string,
TResource,
@@ -266,7 +266,7 @@ export type MetadataResponse = MetadataResponse_2;
// @public @deprecated
export type MetadataResponseSerializedRule = MetadataResponseSerializedRule_2;
// @public
// @public @deprecated
export type PermissionIntegrationRouterOptions<
TResourceType1 extends string = string,
TResource1 = any,
@@ -201,6 +201,7 @@ export function createConditionAuthorizer<TResource, TQuery>(
* for a particular resource type.
*
* @public
* @deprecated {@link createPermissionIntegrationRouter} is deprecated
*/
export type CreatePermissionIntegrationRouterResourceOptions<
TResourceType extends string,
@@ -223,6 +224,7 @@ export type CreatePermissionIntegrationRouterResourceOptions<
* permissions and rules from multiple resource types.
*
* @public
* @deprecated {@link createPermissionIntegrationRouter} is deprecated
*/
export type PermissionIntegrationRouterOptions<
TResourceType1 extends string = string,
@@ -410,6 +412,7 @@ class PermissionIntegrationMetadataStore {
* need to be evaluated.
*
* @public
* @deprecated use `PermissionRegistryService` instead, see {@link https://backstage.io/docs/backend-system/core-services/permissions-registry#migrating-from-createpermissionintegrationrouter | the migration section in the service docs} for more details.
*/
export function createPermissionIntegrationRouter<
TResourceType1 extends string,
+4 -61
View File
@@ -3464,43 +3464,7 @@ __metadata:
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/backend-test-utils": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/cli-common": "workspace:^"
"@backstage/config": "workspace:^"
"@backstage/config-loader": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/plugin-auth-node": "workspace:^"
"@backstage/plugin-permission-node": "workspace:^"
"@backstage/types": "workspace:^"
"@manypkg/get-packages": ^1.1.3
"@types/compression": ^1.7.0
"@types/http-errors": ^2.0.0
"@types/minimist": ^1.2.0
"@types/morgan": ^1.9.0
"@types/node-forge": ^1.3.0
"@types/stoppable": ^1.1.0
compression: ^1.7.4
cookie: ^0.7.0
cors: ^2.8.5
helmet: ^6.0.0
http-errors: ^2.0.0
jose: ^5.0.0
knex: ^3.0.0
lodash: ^4.17.21
logform: ^2.3.2
luxon: ^3.0.0
minimatch: ^9.0.0
minimist: ^1.2.5
morgan: ^1.10.0
msw: ^1.0.0
node-forge: ^1.3.1
path-to-regexp: ^8.0.0
selfsigned: ^2.0.0
stoppable: ^1.1.0
supertest: ^7.0.0
triple-beam: ^1.4.1
uuid: ^11.0.0
winston: ^3.2.1
winston-transport: ^4.5.0
languageName: unknown
linkType: soft
@@ -3591,14 +3555,12 @@ __metadata:
"@aws-sdk/credential-providers": ^3.350.0
"@aws-sdk/types": ^3.347.0
"@aws-sdk/util-stream-node": ^3.350.0
"@azure/identity": ^4.0.0
"@azure/storage-blob": ^12.5.0
"@backstage/backend-app-api": "workspace:^"
"@backstage/backend-dev-utils": "workspace:^"
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/backend-test-utils": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/cli-common": "workspace:^"
"@backstage/cli-node": "workspace:^"
"@backstage/config": "workspace:^"
"@backstage/config-loader": "workspace:^"
@@ -3618,13 +3580,13 @@ __metadata:
"@opentelemetry/api": ^1.9.0
"@types/archiver": ^6.0.0
"@types/base64-stream": ^1.0.2
"@types/compression": ^1.7.5
"@types/concat-stream": ^2.0.0
"@types/cors": ^2.8.6
"@types/express": ^4.17.6
"@types/http-errors": ^2.0.0
"@types/node-forge": ^1.3.0
"@types/pg-format": ^1.0.5
"@types/stoppable": ^1.1.0
"@types/yauzl": ^2.10.0
archiver: ^7.0.0
aws-sdk-client-mock: ^4.0.0
@@ -3641,7 +3603,6 @@ __metadata:
git-url-parse: ^15.0.0
helmet: ^6.0.0
http-errors: ^2.0.0
isomorphic-git: ^1.23.0
jose: ^5.0.0
keyv: ^5.2.1
knex: ^3.0.0
@@ -3649,7 +3610,6 @@ __metadata:
logform: ^2.3.2
luxon: ^3.0.0
minimatch: ^9.0.0
minimist: ^1.2.5
msw: ^1.0.0
mysql2: ^3.0.0
node-fetch: ^2.7.0
@@ -4150,6 +4110,7 @@ __metadata:
"@backstage/types": "workspace:^"
"@types/json-schema": ^7.0.6
"@types/json-schema-merge-allof": ^0.6.0
"@types/minimist": ^1.2.5
ajv: ^8.10.0
chokidar: ^3.5.2
fs-extra: ^11.2.0
@@ -19535,7 +19496,7 @@ __metadata:
languageName: node
linkType: hard
"@types/compression@npm:^1.7.0":
"@types/compression@npm:^1.7.5":
version: 1.7.5
resolution: "@types/compression@npm:1.7.5"
dependencies:
@@ -20253,22 +20214,13 @@ __metadata:
languageName: node
linkType: hard
"@types/minimist@npm:^1.2.0":
"@types/minimist@npm:^1.2.5":
version: 1.2.5
resolution: "@types/minimist@npm:1.2.5"
checksum: 477047b606005058ab0263c4f58097136268007f320003c348794f74adedc3166ffc47c80ec3e94687787f2ab7f4e72c468223946e79892cf0fd9e25e9970a90
languageName: node
linkType: hard
"@types/morgan@npm:^1.9.0":
version: 1.9.9
resolution: "@types/morgan@npm:1.9.9"
dependencies:
"@types/node": "*"
checksum: 54bcb432f6ddb82b94bc1970204bedb3465a9afdcced6c2c6b481cf5f276266663ba3edc2b728b0118aa9720bfe5d8561c0259daaad6b027017e35936b107db0
languageName: node
linkType: hard
"@types/ms@npm:*":
version: 0.7.31
resolution: "@types/ms@npm:0.7.31"
@@ -20920,15 +20872,6 @@ __metadata:
languageName: node
linkType: hard
"@types/stoppable@npm:^1.1.0":
version: 1.1.3
resolution: "@types/stoppable@npm:1.1.3"
dependencies:
"@types/node": "*"
checksum: 53cbe08da33d2babb4ee98fe123c1e08bc5a26f547a2eaeac7c279743bbee5f9b49c0772db6a106471a07092916deb9bb7e0632fd502585d45d9c398f036938d
languageName: node
linkType: hard
"@types/stream-buffers@npm:^3.0.3":
version: 3.0.7
resolution: "@types/stream-buffers@npm:3.0.7"