Merge branch 'master' into canon-styles
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/canon': minor
|
||||
---
|
||||
|
||||
Added a new Tooltip component to Canon.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search-backend-module-techdocs': patch
|
||||
---
|
||||
|
||||
Added an extension point that allows for custom entity filtering during document collation.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/config-loader': patch
|
||||
---
|
||||
|
||||
Added `@types/minimist` to `devDependencies`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/integration': patch
|
||||
---
|
||||
|
||||
Gerrit integration now exports `getGitilesAuthenticationUrl`. This enables its usage by the `GerritUrlReader`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/canon': patch
|
||||
---
|
||||
|
||||
Fix Button types that was preventing the use of native attributes like onClick.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-github': patch
|
||||
---
|
||||
|
||||
Made "github:pages:enable" action idempotent
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
Remove unused dependencies
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-gerrit': minor
|
||||
---
|
||||
|
||||
**BREAKING** The optional `branch` configuration parameter now defaults to the default branch of the project (where `HEAD` points to).
|
||||
This parameter was previously using `master` as the default value. In most cases this change should be transparent as Gerrit defaults to using `master`.
|
||||
|
||||
This change also allow to specify a custom `catalogPath` in the `catalog.providers.gerrit` configuration.
|
||||
If not set, it defaults to `catalog-info.yaml` files at the root of repositories. This default was the value before this change.
|
||||
|
||||
With the changes made in the `GerritUrlReader`, `catalogPath` allows to use `minimatch`'s glob-patterns.
|
||||
|
||||
```diff
|
||||
catalog:
|
||||
providers:
|
||||
gerrit:
|
||||
all: # identifies your dataset / provider independent of config changes
|
||||
host: gerrit.company.com
|
||||
query: 'state=ACTIVE&type=CODE'
|
||||
+ # This will search for catalog manifests anywhere in the repositories
|
||||
+ catalogPath: '**/catalog-info.{yml,yaml}'
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
|
||||
---
|
||||
|
||||
Made "gitlab:pipeline:trigger" action idempotent
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/canon': minor
|
||||
---
|
||||
|
||||
We added a new IconButton component with fixed sizes showcasing a single icon.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-permission-node': minor
|
||||
---
|
||||
|
||||
**BREAKING** The `ServerPermissionClient` can no longer be instantiated with a `tokenManager` and must instead be instantiated with an `auth` service. If you are still on the legacy backend system, use `createLegacyAuthAdapters()` from `@backstage/backend-common` to create a compatible `auth` service.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
|
||||
---
|
||||
|
||||
Made gitlab:repo:push action idempotent.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
|
||||
---
|
||||
|
||||
Made "gitlab:projectDeployToken:create" action idempotent
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-github': patch
|
||||
---
|
||||
|
||||
Made "github:issues:label" action idempotent
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': patch
|
||||
---
|
||||
|
||||
Added support for PostgreSQL version 17
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
|
||||
---
|
||||
|
||||
Made "gitlab:projectVariable:create" action idempotent
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-github': patch
|
||||
---
|
||||
|
||||
Made "github:repo:push" action idempotent
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
|
||||
---
|
||||
|
||||
Made "gitlab:projectAccessToken:create" action idempotent
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/canon': minor
|
||||
---
|
||||
|
||||
Added a new Menu component to Canon.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
Avoid Layout Shift for `DismissableBanner` when using a `storageApi` with latency (e.g. `user-settings-backend`)
|
||||
|
||||
Properly handle the `unknown` state of the `storageApi`. There's a trade-off: this may lead to some Layout Shift if the banner has not been dismissed, but once it has been dismissed, you won't have any.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
`GerritUrlReader` is now able to `search` files matching a given pattern URL (using `minimatch` glob patterns).
|
||||
|
||||
This allows the Gerrit Discovery to find all Backstage manifests inside a repository using the `**/catalog-info.yaml` pattern.
|
||||
@@ -2,4 +2,4 @@
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Excludes svg styling from sanitization
|
||||
Excludes SVG styling from sanitization
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/canon': minor
|
||||
---
|
||||
|
||||
Updating styles for Text and Link components as well as global surface tokens.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-github': patch
|
||||
---
|
||||
|
||||
Made "github:repo:create" action idempotent
|
||||
@@ -2,4 +2,4 @@
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Adds the page name of techdoc to the document's title.
|
||||
Adds the page name of techdocs to the document's title.
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Updates logic to check for SVG sources when inlining svgs.
|
||||
Updates logic to check for SVG sources when inlining them.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-app-api': patch
|
||||
---
|
||||
|
||||
Remove unused dependencies
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-common': patch
|
||||
---
|
||||
|
||||
Add optional `lifecycle` property to the spec field of Template entities.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
|
||||
---
|
||||
|
||||
Made "gitlab:group:migrate" action idempotent
|
||||
@@ -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).
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/canon': minor
|
||||
---
|
||||
|
||||
Added a new ScrollArea component for Canon.
|
||||
@@ -190,6 +190,7 @@ Indal
|
||||
indexable
|
||||
ingestors
|
||||
inlined
|
||||
inlining
|
||||
inlinehilite
|
||||
integrator's
|
||||
interop
|
||||
|
||||
@@ -161,8 +161,8 @@ jobs:
|
||||
|
||||
name: Test ${{ matrix.node-version }}
|
||||
services:
|
||||
postgres16:
|
||||
image: postgres:16
|
||||
postgres17:
|
||||
image: postgres:17
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
options: >-
|
||||
@@ -172,8 +172,8 @@ jobs:
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432/tcp
|
||||
postgres12:
|
||||
image: postgres:12
|
||||
postgres13:
|
||||
image: postgres:13
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
options: >-
|
||||
@@ -253,8 +253,8 @@ jobs:
|
||||
run: yarn backstage-cli repo test --maxWorkers=3 --workerIdleMemoryLimit=1300M --since origin/master --successCache --successCacheDir .cache/backstage-cli
|
||||
env:
|
||||
BACKSTAGE_TEST_DISABLE_DOCKER: 1
|
||||
BACKSTAGE_TEST_DATABASE_POSTGRES16_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres16.ports[5432] }}
|
||||
BACKSTAGE_TEST_DATABASE_POSTGRES12_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres12.ports[5432] }}
|
||||
BACKSTAGE_TEST_DATABASE_POSTGRES17_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres17.ports[5432] }}
|
||||
BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres13.ports[5432] }}
|
||||
BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored
|
||||
BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING: redis://localhost:${{ job.services.redis.ports[6379] }}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0
|
||||
|
||||
@@ -16,8 +16,8 @@ jobs:
|
||||
node-version: [20.x, 22.x]
|
||||
|
||||
services:
|
||||
postgres16:
|
||||
image: postgres:16
|
||||
postgres17:
|
||||
image: postgres:17
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
options: >-
|
||||
@@ -27,8 +27,8 @@ jobs:
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432/tcp
|
||||
postgres12:
|
||||
image: postgres:12
|
||||
postgres13:
|
||||
image: postgres:13
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
options: >-
|
||||
@@ -122,8 +122,8 @@ jobs:
|
||||
yarn backstage-cli repo test --maxWorkers=3 --workerIdleMemoryLimit=1300M --coverage --successCache --successCacheDir .cache/backstage-cli
|
||||
env:
|
||||
BACKSTAGE_TEST_DISABLE_DOCKER: 1
|
||||
BACKSTAGE_TEST_DATABASE_POSTGRES16_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres16.ports[5432] }}
|
||||
BACKSTAGE_TEST_DATABASE_POSTGRES12_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres12.ports[5432] }}
|
||||
BACKSTAGE_TEST_DATABASE_POSTGRES17_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres17.ports[5432] }}
|
||||
BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres13.ports[5432] }}
|
||||
BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored
|
||||
BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING: redis://localhost:${{ job.services.redis.ports[6379] }}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:12
|
||||
image: postgres:13
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
||||
@@ -132,7 +132,6 @@ Scope: Tooling and Community Repo Maintainers for the Backstage [Community Plugi
|
||||
| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` |
|
||||
| Bethany Griggs | Red Hat | [BethGriggs](https://github.com/BethGriggs) | `bethgriggs` |
|
||||
| Kashish Mittal | Red Hat | [04kash](https://github.com/04kash) | `kashh._.` |
|
||||
| Nick Boldt | Red Hat | [nickboldt](https://github.com/nickboldt) | `nboldt` |
|
||||
| Vincenzo Scamporlino | Spotify | [vinzscam](https://github.com/vinzscam) | `vinzscam` |
|
||||
|
||||
### Events
|
||||
|
||||
@@ -154,30 +154,36 @@ export function createGithubRepoCreateAction(options: {
|
||||
username: owner,
|
||||
});
|
||||
|
||||
await ctx.checkpoint('repo.creation', async () => {
|
||||
const repoCreationPromise =
|
||||
user.data.type === 'Organization'
|
||||
? client.rest.repos.createInOrg({
|
||||
name: repo,
|
||||
org: owner,
|
||||
})
|
||||
: client.rest.repos.createForAuthenticatedUser({
|
||||
name: repo,
|
||||
});
|
||||
const { repoUrl } = await repoCreationPromise;
|
||||
return { repoUrl };
|
||||
await ctx.checkpoint({
|
||||
key: 'repo.creation.v1',
|
||||
fn: async () => {
|
||||
const repoCreationPromise =
|
||||
user.data.type === 'Organization'
|
||||
? client.rest.repos.createInOrg({
|
||||
name: repo,
|
||||
org: owner,
|
||||
})
|
||||
: client.rest.repos.createForAuthenticatedUser({
|
||||
name: repo,
|
||||
});
|
||||
const { repoUrl } = await repoCreationPromise;
|
||||
return { repoUrl };
|
||||
},
|
||||
});
|
||||
|
||||
if (secrets) {
|
||||
await ctx.checkpoint('repo.create.variables', async () => {
|
||||
for (const [key, value] of Object.entries(repoVariables ?? {})) {
|
||||
await client.rest.actions.createRepoVariable({
|
||||
owner,
|
||||
repo,
|
||||
name: key,
|
||||
value: value,
|
||||
});
|
||||
}
|
||||
await ctx.checkpoint({
|
||||
key: 'repo.create.variables',
|
||||
fn: async () => {
|
||||
for (const [key, value] of Object.entries(repoVariables ?? {})) {
|
||||
await client.rest.actions.createRepoVariable({
|
||||
owner,
|
||||
repo,
|
||||
name: key,
|
||||
value: value,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -202,9 +208,12 @@ Checkpoints will allow action authors to create actions where code paths are ign
|
||||
This will be provided on a context object and action of author provide a key and a callback.
|
||||
|
||||
```typescript
|
||||
await ctx.checkpoint('repo.creation', async () => {
|
||||
const { repoUrl } = await client.rest.Repository.create({});
|
||||
return { repoUrl };
|
||||
await ctx.checkpoint({
|
||||
key: 'repo.creation',
|
||||
fn: async () => {
|
||||
const { repoUrl } = await client.rest.Repository.create({});
|
||||
return { repoUrl };
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -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' }} />`}
|
||||
/>
|
||||
@@ -53,7 +53,7 @@ color of your app.
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>--canon-bg-elevated</Chip>
|
||||
<Chip head>--canon-bg-surface-1</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>Use for any panels or elevated surfaces.</Table.Cell>
|
||||
</Table.Row>
|
||||
@@ -189,7 +189,7 @@ are prefixed with `fg` to make it easier to identify.
|
||||
<Chip head>--canon-fg-primary</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of `--canon-bg-app` or `--canon-bg-elevated`.
|
||||
It should be used on top of `--canon-bg-app` or `--canon-bg-surface-1`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
@@ -197,7 +197,7 @@ are prefixed with `fg` to make it easier to identify.
|
||||
<Chip head>--canon-fg-secondary</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of `--canon-bg-app` or `--canon-bg-elevated`.
|
||||
It should be used on top of `--canon-bg-app` or `--canon-bg-surface-1`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
@@ -205,7 +205,7 @@ are prefixed with `fg` to make it easier to identify.
|
||||
<Chip head>--canon-fg-link</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of `--canon-bg-app` or `--canon-bg-elevated`.
|
||||
It should be used on top of `--canon-bg-app` or `--canon-bg-surface-1`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
@@ -213,7 +213,7 @@ are prefixed with `fg` to make it easier to identify.
|
||||
<Chip head>--canon-fg-link-hover</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of `--canon-bg-app` or `--canon-bg-elevated`.
|
||||
It should be used on top of `--canon-bg-app` or `--canon-bg-surface-1`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
@@ -267,7 +267,7 @@ low contrast to help as a separator with the different background colors.
|
||||
<Chip head>--canon-border</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
It should be used on top of `--canon-bg-elevated`.
|
||||
It should be used on top of `--canon-bg-surface-1`.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
|
||||
@@ -17,7 +17,7 @@ const defaultTheme = `:root {
|
||||
const myTheme = createTheme({
|
||||
theme: 'light',
|
||||
settings: {
|
||||
background: 'var(--canon-bg-elevated)',
|
||||
background: 'var(--canon-bg-surface-1)',
|
||||
backgroundImage: '',
|
||||
foreground: '#6182B8',
|
||||
caret: '#5d00ff',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -31,10 +31,7 @@ export const Tab = (props: React.ComponentProps<typeof TabsPrimitive.Tab>) => (
|
||||
<Text
|
||||
variant="subtitle"
|
||||
weight="bold"
|
||||
{...rest}
|
||||
style={{
|
||||
color: state.selected ? 'var(--primary)' : 'var(--secondary)',
|
||||
}}
|
||||
color={state.selected ? 'primary' : 'secondary'}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
|
||||
@@ -5,7 +5,7 @@ export const customTheme = `:root {
|
||||
--canon-font-weight-regular: 400;
|
||||
--canon-font-weight-bold: 600;
|
||||
--canon-bg: #f8f8f8;
|
||||
--canon-bg-elevated: #fff;
|
||||
--canon-bg-surface-1: #fff;
|
||||
/* ... other CSS variables */
|
||||
|
||||
/* Add your custom components styles here */
|
||||
@@ -20,7 +20,7 @@ export const customTheme = `:root {
|
||||
--canon-font-weight-regular: 400;
|
||||
--canon-font-weight-bold: 600;
|
||||
--canon-bg: #f8f8f8;
|
||||
--canon-bg-elevated: #fff;
|
||||
--canon-bg-surface-1: #fff;
|
||||
/* ... other CSS variables */
|
||||
|
||||
/* Add your custom components styles here */
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -87,6 +87,11 @@ export const components: Page[] = [
|
||||
slug: 'icon',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'IconButton',
|
||||
slug: 'icon-button',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'Input',
|
||||
slug: 'input',
|
||||
|
||||
@@ -190,7 +190,7 @@ describe('MyDatabaseClass', () => {
|
||||
// "physical" databases to test against is much costlier than creating the
|
||||
// "logical" databases within them that the individual tests use.
|
||||
const databases = TestDatabases.create({
|
||||
ids: ['POSTGRES_16', 'POSTGRES_12', 'SQLITE_3', 'MYSQL_8'],
|
||||
ids: ['POSTGRES_17', 'POSTGRES_13', 'SQLITE_3', 'MYSQL_8'],
|
||||
});
|
||||
|
||||
// Just an example of how to conveniently bundle up the setup code
|
||||
@@ -236,8 +236,8 @@ your CI environment is able to supply databases natively, the `TestDatabases`
|
||||
support custom connection strings through the use of environment variables that
|
||||
it'll take into account when present.
|
||||
|
||||
- `BACKSTAGE_TEST_DATABASE_POSTGRES17_CONNECTION_STRING`
|
||||
- `BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING`
|
||||
- `BACKSTAGE_TEST_DATABASE_POSTGRES9_CONNECTION_STRING`
|
||||
- `BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING`
|
||||
|
||||
## Testing Service Factories
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
id: permissions
|
||||
id: permissions-registry
|
||||
title: Permissions Registry Service
|
||||
sidebar_label: Permissions Registry
|
||||
description: Documentation for the Permissions Registry service
|
||||
|
||||
@@ -134,6 +134,38 @@ search:
|
||||
timeout: { minutes: 3 }
|
||||
```
|
||||
|
||||
### Filtering through the catalog collator
|
||||
|
||||
The TechDocs collator by default filters through catalog entities where the annotation `metadata.annotations.backstage.io/techdocs-ref` exists. If you wish to further filter out entities, there are two ways to do so through the `techDocsCollatorEntityFilterExtensionPoint`.
|
||||
|
||||
```typescript
|
||||
export const exampleCustomCatalogFiltering = createBackendModule({
|
||||
pluginId: 'search',
|
||||
moduleId: 'search-techdocs-collator-entity-filter',
|
||||
register(reg) {
|
||||
reg.registerInit({
|
||||
deps: {
|
||||
customCollatorFilter: techDocsCollatorEntityFilterExtensionPoint,
|
||||
},
|
||||
async init({ customCollatorFilter }) {
|
||||
/* filtering by catalog params */
|
||||
customCollatorFilter.setCustomCatalogApiFilters([
|
||||
{ kind: ['API', 'Component', ...] },
|
||||
{ metadata: ['...more filters'] },
|
||||
]);
|
||||
|
||||
/* filtering by a custom function */
|
||||
customCollatorFilter.setEntityFilterFunction((entities: Entity[]) =>
|
||||
entities.filter(
|
||||
entity => entity.metadata?.annotations?.abc === 'xyz',
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Community Collators
|
||||
|
||||
Here are some of the known Search Collators available in from the Backstage Community:
|
||||
|
||||
@@ -19,9 +19,11 @@ array when registering your custom actions, as seen below.
|
||||
|
||||
## Streamlining Custom Action Creation with Backstage CLI
|
||||
|
||||
The creation of custom actions in Backstage has never been easier thanks to the Backstage CLI. This tool streamlines the setup process, allowing you to focus on your actions' unique functionality.
|
||||
The creation of custom actions in Backstage has never been easier thanks to the Backstage CLI. This tool streamlines the
|
||||
setup process, allowing you to focus on your actions' unique functionality.
|
||||
|
||||
Start by using the `yarn backstage-cli new` command to generate a scaffolder module. This command sets up the necessary boilerplate code, providing a smooth start:
|
||||
Start by using the `yarn backstage-cli new` command to generate a scaffolder module. This command sets up the necessary
|
||||
boilerplate code, providing a smooth start:
|
||||
|
||||
```
|
||||
$ yarn backstage-cli new
|
||||
@@ -34,13 +36,19 @@ $ yarn backstage-cli new
|
||||
|
||||
You can find a [list](../../tooling/cli/03-commands.md) of all commands provided by the Backstage CLI.
|
||||
|
||||
When prompted, select the option to generate a scaffolder module. This creates a solid foundation for your custom action. Enter the name of the module you wish to create, and the CLI will generate the required files and directory structure.
|
||||
When prompted, select the option to generate a scaffolder module. This creates a solid foundation for your custom
|
||||
action. Enter the name of the module you wish to create, and the CLI will generate the required files and directory
|
||||
structure.
|
||||
|
||||
## Writing your Custom Action
|
||||
|
||||
After running the command, the CLI will create a new directory with your new scaffolder module. This directory will be the working directory for creating the custom action. It will contain all the necessary files and boilerplate code to get started.
|
||||
After running the command, the CLI will create a new directory with your new scaffolder module. This directory will be
|
||||
the working directory for creating the custom action. It will contain all the necessary files and boilerplate code to
|
||||
get started.
|
||||
|
||||
Let's create a simple action that adds a new file and some contents that are passed as `input` to the function. Within the generated directory, locate the file at `src/actions/example/example.ts`. Feel free to rename this file along with its generated unit test. We will replace the existing placeholder code with our custom action code as follows:
|
||||
Let's create a simple action that adds a new file and some contents that are passed as `input` to the function. Within
|
||||
the generated directory, locate the file at `src/actions/example/example.ts`. Feel free to rename this file along with
|
||||
its generated unit test. We will replace the existing placeholder code with our custom action code as follows:
|
||||
|
||||
```ts title="With Zod"
|
||||
import { resolveSafeChildPath } from '@backstage/backend-plugin-api';
|
||||
@@ -82,7 +90,8 @@ The `createTemplateAction` takes an object which specifies the following:
|
||||
- `id` - A unique ID for your custom action. We encourage you to namespace these
|
||||
in some way so that they won't collide with future built-in actions that we
|
||||
may ship with the `scaffolder-backend` plugin.
|
||||
- `description` - An optional field to describe the purpose of the action. This will populate in the `/create/actions` endpoint.
|
||||
- `description` - An optional field to describe the purpose of the action. This will populate in the `/create/actions`
|
||||
endpoint.
|
||||
- `schema.input` - A `zod` or JSON schema object for input values to your function
|
||||
- `schema.output` - A `zod` or JSON schema object for values which are output from the
|
||||
function using `ctx.output`
|
||||
@@ -132,18 +141,24 @@ export const createNewFileAction = () => {
|
||||
|
||||
### 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, `github:actions:create` or `github:repo:create`.
|
||||
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,
|
||||
`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.
|
||||
|
||||
Prefer to use `camelCase` over `snake_case` or `kebab-case` for these actions if possible, which leads to better reading and writing of template entity definitions.
|
||||
Prefer to use `camelCase` over `snake_case` or `kebab-case` for these actions if possible, which leads to better reading
|
||||
and writing of template entity definitions.
|
||||
|
||||
> We're aware that there are some exceptions to this, but try to follow as close as possible. We'll be working on migrating these in the repository over time too.
|
||||
> We're aware that there are some exceptions to this, but try to follow as close as possible. We'll be working on
|
||||
> migrating these in the repository over time too.
|
||||
|
||||
### Adding a TemplateExample
|
||||
|
||||
A TemplateExample is a predefined structure that can be used to create custom actions in your software templates. It serves as a blueprint for users to understand how to use a specific action and its fields as well as to ensure consistency and standardization across different custom actions.
|
||||
A TemplateExample is a predefined structure that can be used to create custom actions in your software templates. It
|
||||
serves as a blueprint for users to understand how to use a specific action and its fields as well as to ensure
|
||||
consistency and standardization across different custom actions.
|
||||
|
||||
#### Define a TemplateExample and add to your Custom Action
|
||||
|
||||
@@ -199,7 +214,8 @@ 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 simplified example of how to do that:
|
||||
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"
|
||||
/* highlight-add-start */
|
||||
@@ -233,7 +249,8 @@ backend.add(import('@backstage/plugin-scaffolder-backend'));
|
||||
backend.add(scaffolderModuleCustomExtensions);
|
||||
```
|
||||
|
||||
If your custom action requires core services such as `config` or `cache` they can be imported in the dependencies and passed to the custom action function.
|
||||
If your custom action requires core services such as `config` or `cache` they can be imported in the dependencies and
|
||||
passed to the custom action function.
|
||||
|
||||
```ts title="packages/backend/src/index.ts"
|
||||
import {
|
||||
@@ -243,17 +260,17 @@ import {
|
||||
|
||||
...
|
||||
|
||||
env.registerInit({
|
||||
deps: {
|
||||
scaffolder: scaffolderActionsExtensionPoint,
|
||||
cache: coreServices.cache,
|
||||
config: coreServices.rootConfig,
|
||||
},
|
||||
async init({ scaffolder, cache, config }) {
|
||||
scaffolder.addActions(
|
||||
customActionNeedingCacheAndConfig({ cache: cache, config: config }),
|
||||
);
|
||||
})
|
||||
env.registerInit({
|
||||
deps: {
|
||||
scaffolder: scaffolderActionsExtensionPoint,
|
||||
cache: coreServices.cache,
|
||||
config: coreServices.rootConfig,
|
||||
},
|
||||
async init({scaffolder, cache, config}) {
|
||||
scaffolder.addActions(
|
||||
customActionNeedingCacheAndConfig({cache: cache, config: config}),
|
||||
);
|
||||
})
|
||||
```
|
||||
|
||||
### Using Checkpoints in Custom Actions (Experimental)
|
||||
@@ -281,6 +298,13 @@ You have to define the unique key in scope of the scaffolder task for your check
|
||||
will check if the checkpoint with such 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.
|
||||
For example, you can embed the versioning or another indicator for that (instead of using key `create.projects`, it can
|
||||
be `create.projects.v1`).
|
||||
If you'll preserve the same key, and you'll try to restart the affected task, it will fail on this checkpoint.
|
||||
The cached result will not match with the expected updated return type.
|
||||
By changing the key, you'll invalidate the cache of the checkpoint.
|
||||
|
||||
### Register Custom Actions with the Legacy Backend System
|
||||
|
||||
Once you have your Custom Action ready for usage with the scaffolder, you'll
|
||||
|
||||
@@ -45,8 +45,9 @@ catalog:
|
||||
gerrit:
|
||||
yourProviderId: # identifies your dataset / provider independent of config changes
|
||||
host: gerrit-your-company.com
|
||||
branch: master # Optional
|
||||
branch: master # Optional, defaults to the repository's default branch
|
||||
query: 'state=ACTIVE&prefix=webapps'
|
||||
catalogPath: 'catalog-info.yaml' # Optional, defaults to catalog-info.yaml
|
||||
schedule:
|
||||
# supports cron, ISO duration, "human duration" as used in code
|
||||
frequency: { minutes: 30 }
|
||||
@@ -56,12 +57,15 @@ catalog:
|
||||
host: gerrit-your-company.com
|
||||
branch: master # Optional
|
||||
query: 'state=ACTIVE&prefix=backend'
|
||||
# catalogPath can be a glob-pattern supported by the minimatch library
|
||||
catalogPath: '{**/catalog-info.{yml,yaml},**/.catalog-info/*.{yml,yaml}}'
|
||||
```
|
||||
|
||||
The provider configuration is composed of three parts:
|
||||
The provider configuration consists of the following parts:
|
||||
|
||||
- **`host`**: the host of the Gerrit integration to use.
|
||||
- **`branch`** _(optional)_: the branch where we will look for catalog entities (defaults to "master").
|
||||
- **`branch`** _(optional)_: the branch where we will look for catalog entities (defaults to the repository's default branch).
|
||||
- **`query`**: this string is directly used as the argument to the "List Project" API.
|
||||
Typically, you will want to have some filter here to exclude projects that will
|
||||
never contain any catalog files.
|
||||
- **`catalogPath`**: path relative to the root of the repository where the Backstage manifests are stored. It can also be a glob pattern supported by [`minimatch`](https://github.com/isaacs/minimatch) to load multiple files.
|
||||
|
||||
@@ -106,7 +106,7 @@ be part of the `1.12` Backstage release.
|
||||
The following versioning policy applies to all packages:
|
||||
|
||||
- Breaking changes are noted in the changelog, and documentation is updated.
|
||||
- Breaking changes are prefixed with `**BREAKING**: ` in the changelog.
|
||||
- Breaking changes are prefixed with `**BREAKING**:` in the changelog.
|
||||
- All public exports are considered stable and will have an entry in the
|
||||
changelog.
|
||||
- Breaking changes are recommended to document a clear upgrade path in the
|
||||
@@ -195,4 +195,4 @@ The Backstage project recommends and supports using PostgreSQL for persistent st
|
||||
|
||||
The PostgreSQL [versioning policy](https://www.postgresql.org/support/versioning/) is to release a new major version every year with new features which is then supported for 5 years after its initial release.
|
||||
|
||||
Our policy mirrors the PostgreSQL versioning policy - we will support the last 5 major versions. We will also test the newest and oldest versions in that range. For example, if the range we support is currently 12 to 16, then we would only test 12 and 16 explicitly.
|
||||
Our policy mirrors the PostgreSQL versioning policy - we will support the last 5 major versions. We will also test the newest and oldest versions in that range. For example, if the range we support is currently 13 to 17, then we would only test 13 and 17 explicitly.
|
||||
|
||||
@@ -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',
|
||||
|
||||
+49
-49
@@ -1627,12 +1627,12 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@babel/runtime-corejs3@npm:^7.22.6":
|
||||
version: 7.23.1
|
||||
resolution: "@babel/runtime-corejs3@npm:7.23.1"
|
||||
version: 7.26.10
|
||||
resolution: "@babel/runtime-corejs3@npm:7.26.10"
|
||||
dependencies:
|
||||
core-js-pure: ^3.30.2
|
||||
regenerator-runtime: ^0.14.0
|
||||
checksum: 5d52b0cc8b5d243e67cf29c584d15acdc0c89b64de4a3fe1cb8a83b84b64a5621802e36931f93ca696cb637884abd11c8514615d890a4edf057ec4464f73915d
|
||||
checksum: 771d986d824fb84503c348d24f71e50c4ff078a2b213412761f0ffc5c83257a2d149caa73db86a70d642fa0e7e57968bbbccc1dd8d0cb21dcca0389d0d39c8bd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2961,90 +2961,90 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-darwin-arm64@npm:1.11.8":
|
||||
version: 1.11.8
|
||||
resolution: "@swc/core-darwin-arm64@npm:1.11.8"
|
||||
"@swc/core-darwin-arm64@npm:1.11.9":
|
||||
version: 1.11.9
|
||||
resolution: "@swc/core-darwin-arm64@npm:1.11.9"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-darwin-x64@npm:1.11.8":
|
||||
version: 1.11.8
|
||||
resolution: "@swc/core-darwin-x64@npm:1.11.8"
|
||||
"@swc/core-darwin-x64@npm:1.11.9":
|
||||
version: 1.11.9
|
||||
resolution: "@swc/core-darwin-x64@npm:1.11.9"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm-gnueabihf@npm:1.11.8":
|
||||
version: 1.11.8
|
||||
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.8"
|
||||
"@swc/core-linux-arm-gnueabihf@npm:1.11.9":
|
||||
version: 1.11.9
|
||||
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.9"
|
||||
conditions: os=linux & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm64-gnu@npm:1.11.8":
|
||||
version: 1.11.8
|
||||
resolution: "@swc/core-linux-arm64-gnu@npm:1.11.8"
|
||||
"@swc/core-linux-arm64-gnu@npm:1.11.9":
|
||||
version: 1.11.9
|
||||
resolution: "@swc/core-linux-arm64-gnu@npm:1.11.9"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-arm64-musl@npm:1.11.8":
|
||||
version: 1.11.8
|
||||
resolution: "@swc/core-linux-arm64-musl@npm:1.11.8"
|
||||
"@swc/core-linux-arm64-musl@npm:1.11.9":
|
||||
version: 1.11.9
|
||||
resolution: "@swc/core-linux-arm64-musl@npm:1.11.9"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-x64-gnu@npm:1.11.8":
|
||||
version: 1.11.8
|
||||
resolution: "@swc/core-linux-x64-gnu@npm:1.11.8"
|
||||
"@swc/core-linux-x64-gnu@npm:1.11.9":
|
||||
version: 1.11.9
|
||||
resolution: "@swc/core-linux-x64-gnu@npm:1.11.9"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-linux-x64-musl@npm:1.11.8":
|
||||
version: 1.11.8
|
||||
resolution: "@swc/core-linux-x64-musl@npm:1.11.8"
|
||||
"@swc/core-linux-x64-musl@npm:1.11.9":
|
||||
version: 1.11.9
|
||||
resolution: "@swc/core-linux-x64-musl@npm:1.11.9"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-arm64-msvc@npm:1.11.8":
|
||||
version: 1.11.8
|
||||
resolution: "@swc/core-win32-arm64-msvc@npm:1.11.8"
|
||||
"@swc/core-win32-arm64-msvc@npm:1.11.9":
|
||||
version: 1.11.9
|
||||
resolution: "@swc/core-win32-arm64-msvc@npm:1.11.9"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-ia32-msvc@npm:1.11.8":
|
||||
version: 1.11.8
|
||||
resolution: "@swc/core-win32-ia32-msvc@npm:1.11.8"
|
||||
"@swc/core-win32-ia32-msvc@npm:1.11.9":
|
||||
version: 1.11.9
|
||||
resolution: "@swc/core-win32-ia32-msvc@npm:1.11.9"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core-win32-x64-msvc@npm:1.11.8":
|
||||
version: 1.11.8
|
||||
resolution: "@swc/core-win32-x64-msvc@npm:1.11.8"
|
||||
"@swc/core-win32-x64-msvc@npm:1.11.9":
|
||||
version: 1.11.9
|
||||
resolution: "@swc/core-win32-x64-msvc@npm:1.11.9"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@swc/core@npm:^1.3.46":
|
||||
version: 1.11.8
|
||||
resolution: "@swc/core@npm:1.11.8"
|
||||
version: 1.11.9
|
||||
resolution: "@swc/core@npm:1.11.9"
|
||||
dependencies:
|
||||
"@swc/core-darwin-arm64": 1.11.8
|
||||
"@swc/core-darwin-x64": 1.11.8
|
||||
"@swc/core-linux-arm-gnueabihf": 1.11.8
|
||||
"@swc/core-linux-arm64-gnu": 1.11.8
|
||||
"@swc/core-linux-arm64-musl": 1.11.8
|
||||
"@swc/core-linux-x64-gnu": 1.11.8
|
||||
"@swc/core-linux-x64-musl": 1.11.8
|
||||
"@swc/core-win32-arm64-msvc": 1.11.8
|
||||
"@swc/core-win32-ia32-msvc": 1.11.8
|
||||
"@swc/core-win32-x64-msvc": 1.11.8
|
||||
"@swc/core-darwin-arm64": 1.11.9
|
||||
"@swc/core-darwin-x64": 1.11.9
|
||||
"@swc/core-linux-arm-gnueabihf": 1.11.9
|
||||
"@swc/core-linux-arm64-gnu": 1.11.9
|
||||
"@swc/core-linux-arm64-musl": 1.11.9
|
||||
"@swc/core-linux-x64-gnu": 1.11.9
|
||||
"@swc/core-linux-x64-musl": 1.11.9
|
||||
"@swc/core-win32-arm64-msvc": 1.11.9
|
||||
"@swc/core-win32-ia32-msvc": 1.11.9
|
||||
"@swc/core-win32-x64-msvc": 1.11.9
|
||||
"@swc/counter": ^0.1.3
|
||||
"@swc/types": ^0.1.19
|
||||
peerDependencies:
|
||||
@@ -3073,7 +3073,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
"@swc/helpers":
|
||||
optional: true
|
||||
checksum: 53aac457ec10ad931e4a15d14be87d4cb53432d2fc2433244835ceb1396e5560f52155e24eadc67bb964bd675b74eea0d99a7570062cd915b02d0336152bf36c
|
||||
checksum: 2c89cc4630f569f9b6e2e0dcc29b168d3c14c730d5f04b2923b4eb0d517be327b22bf8b64eb50fb0412e2960f43ab0ccb4e99f6792ec409a9d24e7a2a85fa12f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5902,8 +5902,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"docusaurus-plugin-openapi-docs@npm:^4.3.0":
|
||||
version: 4.3.6
|
||||
resolution: "docusaurus-plugin-openapi-docs@npm:4.3.6"
|
||||
version: 4.3.7
|
||||
resolution: "docusaurus-plugin-openapi-docs@npm:4.3.7"
|
||||
dependencies:
|
||||
"@apidevtools/json-schema-ref-parser": ^11.5.4
|
||||
"@redocly/openapi-core": ^1.10.5
|
||||
@@ -5925,7 +5925,7 @@ __metadata:
|
||||
"@docusaurus/utils": ^3.5.0
|
||||
"@docusaurus/utils-validation": ^3.5.0
|
||||
react: ^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
checksum: ed80d72f16cf03fc38cc9c6b9916287c80b4bf289040eacbd5a4bacabeed428a9293be974fb1bbede8595e2288820b7d86ca15aaf21366d043edf7e58c45aeae
|
||||
checksum: 714d91c40ea00217ef654b690569dc3249a5048b83532da08be0c552e13f42b326cf1262490063519286e2cd30ad1c1bbd08f678d09c7341f945be9d8cd8e864
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
+9
-9
@@ -1,11 +1,6 @@
|
||||
{
|
||||
"name": "root",
|
||||
"version": "1.37.0-next.2",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage"
|
||||
},
|
||||
"backstage": {
|
||||
"cli": {
|
||||
"new": {
|
||||
@@ -16,6 +11,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*",
|
||||
@@ -51,11 +51,11 @@
|
||||
"snyk:test": "npx snyk test --yarn-workspaces --strict-out-of-sync=false",
|
||||
"snyk:test:package": "yarn snyk:test --include",
|
||||
"start": "yarn workspace example-app start",
|
||||
"start-backend": "yarn workspace example-backend start",
|
||||
"start-backend:legacy": "yarn workspace example-backend-legacy start",
|
||||
"start:lighthouse": "yarn workspaces foreach -A --include example-backend --include example-app --parallel --jobs unlimited -v -i run start",
|
||||
"start:microsite": "cd microsite/ && yarn start",
|
||||
"start:next": "yarn workspace example-app-next start",
|
||||
"start-backend": "yarn workspace example-backend start",
|
||||
"start-backend:legacy": "yarn workspace example-backend-legacy start",
|
||||
"storybook": "yarn ./storybook run storybook",
|
||||
"techdocs-cli": "node scripts/techdocs-cli.js",
|
||||
"techdocs-cli:dev": "cross-env TECHDOCS_CLI_DEV_MODE=true node scripts/techdocs-cli.js",
|
||||
@@ -103,9 +103,9 @@
|
||||
"@material-ui/pickers@^3.3.10": "patch:@material-ui/pickers@npm%3A3.3.11#./.yarn/patches/@material-ui-pickers-npm-3.3.11-1c8f68ea20.patch",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"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",
|
||||
"ast-types@0.14.2": "patch:ast-types@npm%3A0.14.2#./.yarn/patches/ast-types-npm-0.14.2-43c4ac4b0d.patch",
|
||||
"ast-types@^0.14.1": "patch:ast-types@npm%3A0.14.2#./.yarn/patches/ast-types-npm-0.14.2-43c4ac4b0d.patch",
|
||||
"ast-types@0.14.2": "patch:ast-types@npm%3A0.14.2#./.yarn/patches/ast-types-npm-0.14.2-43c4ac4b0d.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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/errors": "workspace:^",
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
@@ -47,48 +47,13 @@
|
||||
},
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ jest.setTimeout(60_000);
|
||||
describe('PluginTaskManagerImpl', () => {
|
||||
const addShutdownHook = jest.fn();
|
||||
const databases = TestDatabases.create({
|
||||
ids: ['POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'],
|
||||
ids: ['POSTGRES_17', 'POSTGRES_13', 'SQLITE_3'],
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
|
||||
+2
-2
@@ -39,8 +39,8 @@ describe('PluginTaskSchedulerJanitor', () => {
|
||||
const databases = TestDatabases.create({
|
||||
ids: [
|
||||
/* 'MYSQL_8' not supported yet */
|
||||
'POSTGRES_16',
|
||||
'POSTGRES_12',
|
||||
'POSTGRES_17',
|
||||
'POSTGRES_13',
|
||||
'SQLITE_3',
|
||||
'MYSQL_8',
|
||||
],
|
||||
|
||||
@@ -423,6 +423,58 @@ describe.skip('GerritUrlReader', () => {
|
||||
|
||||
describe('search', () => {
|
||||
const responseBuffer = Buffer.from('Apache License');
|
||||
const branchAPIUrl =
|
||||
'https://gerrit.com/projects/app%2Fweb/branches/master';
|
||||
const branchAPIresponse = fs.readFileSync(
|
||||
path.resolve(__dirname, '__fixtures__/gerrit/branch-info-response.txt'),
|
||||
);
|
||||
const searchUrl =
|
||||
'https://gerrit.com/gitiles/app/web/+/refs/heads/master/**/catalog-info.yaml';
|
||||
const etag = '52432507a70b677b5674b019c9a46b2e9f29d0a1';
|
||||
const treeRecursiveResponse = fs.readFileSync(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'__fixtures__/gerrit/tree-recursive-response.txt',
|
||||
),
|
||||
);
|
||||
|
||||
beforeEach(async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://gerrit.com/projects/app%2Fweb/branches/master/files/catalog-info.yaml/content',
|
||||
(_, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.body(Buffer.from('Backstage manifest').toString('base64')),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://gerrit.com/gitiles/app/web/\\+/refs/heads/master/',
|
||||
(req, res, ctx) => {
|
||||
if (
|
||||
req.url.searchParams.has('format', 'JSON') &&
|
||||
req.url.searchParams.has('recursive')
|
||||
) {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.set('content-disposition', 'attachment'),
|
||||
ctx.body(treeRecursiveResponse),
|
||||
);
|
||||
}
|
||||
|
||||
return res(ctx.status(404));
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return a single file when given an exact URL', async () => {
|
||||
worker.use(
|
||||
@@ -467,12 +519,53 @@ describe.skip('GerritUrlReader', () => {
|
||||
expect(data.files.length).toBe(0);
|
||||
});
|
||||
|
||||
it('throws if given URL with wildcard', async () => {
|
||||
await expect(
|
||||
gerritProcessor.search(
|
||||
'https://gerrit.com/web/project/+/refs/heads/master/*.yaml',
|
||||
),
|
||||
).rejects.toThrow('Unsupported search pattern URL');
|
||||
it('reads the wanted files correctly using gitiles.', async () => {
|
||||
worker.use(
|
||||
rest.get(branchAPIUrl, (_, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.body(branchAPIresponse));
|
||||
}),
|
||||
);
|
||||
|
||||
const response = await gerritProcessor.search(searchUrl);
|
||||
|
||||
expect(response.etag).toBe(etag);
|
||||
|
||||
expect(response.files.length).toBe(3);
|
||||
|
||||
expect(response.files[0].url).toEqual(
|
||||
'https://gerrit.com/gitiles/app/web/+/refs/heads/master/catalog-info.yaml',
|
||||
);
|
||||
expect(response.files[1].url).toEqual(
|
||||
'https://gerrit.com/gitiles/app/web/+/refs/heads/master/microservices/petstore-api/catalog-info.yaml',
|
||||
);
|
||||
expect(response.files[2].url).toEqual(
|
||||
'https://gerrit.com/gitiles/app/web/+/refs/heads/master/microservices/petstore-consumer/catalog-info.yaml',
|
||||
);
|
||||
|
||||
const docsYaml = await response.files[0].content();
|
||||
expect(docsYaml.toString()).toBe('Backstage manifest');
|
||||
});
|
||||
|
||||
it('throws NotModifiedError for matching etags.', async () => {
|
||||
worker.use(
|
||||
rest.get(branchAPIUrl, (_, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.body(branchAPIresponse));
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(gerritProcessor.search(searchUrl, { etag })).rejects.toThrow(
|
||||
NotModifiedError,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw on failures while getting branch info.', async () => {
|
||||
worker.use(
|
||||
rest.get(branchAPIUrl, (_, res, ctx) => {
|
||||
return res(ctx.status(500, 'Error'));
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(gerritProcessor.search(searchUrl)).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,6 +43,8 @@ import {
|
||||
assertError,
|
||||
} from '@backstage/errors';
|
||||
import { ReadTreeResponseFactory, ReaderFactory } from './types';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { getGitilesAuthenticationUrl } from '@backstage/integration';
|
||||
|
||||
/**
|
||||
* Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files in Gerrit.
|
||||
@@ -150,35 +152,39 @@ export class GerritUrlReader implements UrlReaderService {
|
||||
url: string,
|
||||
options?: UrlReaderServiceSearchOptions,
|
||||
): Promise<UrlReaderServiceSearchResponse> {
|
||||
const { pathname } = new URL(url);
|
||||
const { path } = parseGitilesUrlRef(this.integration.config, url);
|
||||
|
||||
if (pathname.match(/[*?]/)) {
|
||||
throw new Error('Unsupported search pattern URL');
|
||||
}
|
||||
if (!path.match(/[*?]/)) {
|
||||
try {
|
||||
const data = await this.readUrl(url, options);
|
||||
|
||||
try {
|
||||
const data = await this.readUrl(url, options);
|
||||
|
||||
return {
|
||||
files: [
|
||||
{
|
||||
url: url,
|
||||
content: data.buffer,
|
||||
lastModifiedAt: data.lastModifiedAt,
|
||||
},
|
||||
],
|
||||
etag: data.etag ?? '',
|
||||
};
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
if (error.name === 'NotFoundError') {
|
||||
return {
|
||||
files: [],
|
||||
etag: '',
|
||||
files: [
|
||||
{
|
||||
url: url,
|
||||
content: data.buffer,
|
||||
lastModifiedAt: data.lastModifiedAt,
|
||||
},
|
||||
],
|
||||
etag: data.etag ?? '',
|
||||
};
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
if (error.name === 'NotFoundError') {
|
||||
return {
|
||||
files: [],
|
||||
etag: '',
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
const urlRevision = await this.getRevisionForUrl(url, options);
|
||||
|
||||
const files = await this.searchFilesFromGitiles(url, options);
|
||||
|
||||
return { files, etag: urlRevision };
|
||||
}
|
||||
|
||||
toString() {
|
||||
@@ -260,4 +266,65 @@ export class GerritUrlReader implements UrlReaderService {
|
||||
}
|
||||
return branchInfo.revision;
|
||||
}
|
||||
|
||||
private async searchFilesFromGitiles(
|
||||
url: string,
|
||||
options?: UrlReaderServiceReadTreeOptions,
|
||||
): Promise<UrlReaderServiceSearchResponse['files']> {
|
||||
const { path, basePath } = parseGitilesUrlRef(this.integration.config, url);
|
||||
|
||||
const treeUrl = `${basePath}/?format=JSON&recursive`.replace(
|
||||
this.integration.config.gitilesBaseUrl,
|
||||
getGitilesAuthenticationUrl(this.integration.config),
|
||||
);
|
||||
|
||||
const treeResponse = await fetch(treeUrl, {
|
||||
...getGerritRequestOptions(this.integration.config),
|
||||
// TODO(freben): The signal cast is there because pre-3.x versions of
|
||||
// node-fetch have a very slightly deviating AbortSignal type signature.
|
||||
// The difference does not affect us in practice however. The cast can
|
||||
// be removed after we support ESM for CLI dependencies and migrate to
|
||||
// version 3 of node-fetch.
|
||||
// https://github.com/backstage/backstage/issues/8242
|
||||
signal: options?.signal as any,
|
||||
});
|
||||
if (!treeResponse.ok) {
|
||||
throw await ResponseError.fromResponse(treeResponse);
|
||||
}
|
||||
|
||||
const res = (await parseGerritJsonResponse(treeResponse as any)) as {
|
||||
id: string;
|
||||
entries: { mode: number; type: string; id: string; name: string }[];
|
||||
};
|
||||
|
||||
const matcher = new Minimatch(decodeURIComponent(path).replace(/^\/+/, ''));
|
||||
|
||||
const matching = res.entries.filter(
|
||||
item => item.type === 'blob' && item.name && matcher.match(item.name),
|
||||
);
|
||||
|
||||
return matching.map(item => ({
|
||||
url: `${basePath}/${item.name}`,
|
||||
content: async () => {
|
||||
const apiUrl = getGerritFileContentsApiUrl(
|
||||
this.integration.config,
|
||||
`${basePath}/${item.name}`,
|
||||
);
|
||||
const response = await fetch(apiUrl, {
|
||||
method: 'GET',
|
||||
...getGerritRequestOptions(this.integration.config),
|
||||
// TODO(freben): The signal cast is there because pre-3.x versions of
|
||||
// node-fetch have a very slightly deviating AbortSignal type signature.
|
||||
// The difference does not affect us in practice however. The cast can
|
||||
// be removed after we support ESM for CLI dependencies and migrate to
|
||||
// version 3 of node-fetch.
|
||||
// https://github.com/backstage/backstage/issues/8242
|
||||
signal: options?.signal as any,
|
||||
});
|
||||
|
||||
const responseBody = await response.text();
|
||||
return Buffer.from(responseBody, 'base64');
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
)]}'
|
||||
{
|
||||
"id": "d84874ac8e27a8592ece6eb3e61e1fc3cd668348",
|
||||
"entries": [
|
||||
{
|
||||
"mode": 40960,
|
||||
"type": "blob",
|
||||
"id": "9abe6322fdb76e3c2d972f12aad46088fa785fbc",
|
||||
"name": "catalog-info.yaml"
|
||||
},
|
||||
{
|
||||
"mode": 33188,
|
||||
"type": "blob",
|
||||
"id": "7a472379c27f165a1472f0c3d455fa3b811585b9",
|
||||
"name": "README.md"
|
||||
},
|
||||
{
|
||||
"mode": 33199,
|
||||
"type": "blob",
|
||||
"id": "1a472379c27f165a1472f0c3d455fa3b811585b9",
|
||||
"name": "microservices/petstore-api/catalog-info.yaml"
|
||||
},
|
||||
{
|
||||
"mode": 32199,
|
||||
"type": "blob",
|
||||
"id": "2a472379c27f165a1472f0c3d455fa3b811585b9",
|
||||
"name": "microservices/petstore-consumer/catalog-info.yaml"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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 });
|
||||
};
|
||||
}
|
||||
@@ -21,5 +21,5 @@ import { Settings } from 'luxon';
|
||||
Settings.throwOnInvalid = true;
|
||||
|
||||
TestDatabases.setDefaults({
|
||||
ids: ['MYSQL_8', 'POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'],
|
||||
ids: ['MYSQL_8', 'POSTGRES_17', 'POSTGRES_13', 'SQLITE_3'],
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Knip report
|
||||
|
||||
## Unused dependencies (13)
|
||||
## Unused dependencies (12)
|
||||
|
||||
| Name | Location | Severity |
|
||||
| :------------------------------------------------- | :----------- | :------- |
|
||||
@@ -12,7 +12,6 @@
|
||||
| @gitbeaker/node | package.json | error |
|
||||
| better-sqlite3 | package.json | error |
|
||||
| @octokit/rest | package.json | error |
|
||||
| example-app | package.json | error |
|
||||
| dockerode | package.json | error |
|
||||
| mysql2 | package.json | error |
|
||||
| luxon | package.json | error |
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
import Router from 'express-promise-router';
|
||||
import {
|
||||
CacheManager,
|
||||
createLegacyAuthAdapters,
|
||||
createServiceBuilder,
|
||||
DatabaseManager,
|
||||
getRootLogger,
|
||||
@@ -37,7 +38,7 @@ import {
|
||||
import { Config } from '@backstage/config';
|
||||
import healthcheck from './plugins/healthcheck';
|
||||
import { metricsHandler, metricsInit } from './metrics';
|
||||
import auth from './plugins/auth';
|
||||
import authPlugin from './plugins/auth';
|
||||
import catalog from './plugins/catalog';
|
||||
import events from './plugins/events';
|
||||
import kubernetes from './plugins/kubernetes';
|
||||
@@ -59,10 +60,15 @@ function makeCreateEnv(config: Config) {
|
||||
const reader = UrlReaders.default({ logger: root, config });
|
||||
const discovery = HostDiscovery.fromConfig(config);
|
||||
const tokenManager = ServerTokenManager.fromConfig(config, { logger: root });
|
||||
const permissions = ServerPermissionClient.fromConfig(config, {
|
||||
const { auth } = createLegacyAuthAdapters({
|
||||
auth: undefined,
|
||||
discovery,
|
||||
tokenManager,
|
||||
});
|
||||
const permissions = ServerPermissionClient.fromConfig(config, {
|
||||
discovery,
|
||||
auth,
|
||||
});
|
||||
const databaseManager = DatabaseManager.fromConfig(config, { logger: root });
|
||||
const cacheManager = CacheManager.fromConfig(config);
|
||||
const identity = DefaultIdentityClient.create({
|
||||
@@ -137,7 +143,7 @@ async function main() {
|
||||
apiRouter.use('/catalog', await catalog(catalogEnv));
|
||||
apiRouter.use('/events', await events(eventsEnv));
|
||||
apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
|
||||
apiRouter.use('/auth', await auth(authEnv));
|
||||
apiRouter.use('/auth', await authPlugin(authEnv));
|
||||
apiRouter.use('/search', await search(searchEnv));
|
||||
apiRouter.use('/techdocs', await techdocs(techdocsEnv));
|
||||
apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv));
|
||||
|
||||
@@ -478,6 +478,7 @@ export class TestCaches {
|
||||
|
||||
// @public
|
||||
export type TestDatabaseId =
|
||||
| 'POSTGRES_17'
|
||||
| 'POSTGRES_16'
|
||||
| 'POSTGRES_15'
|
||||
| 'POSTGRES_14'
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface Engine {
|
||||
* @public
|
||||
*/
|
||||
export type TestDatabaseId =
|
||||
| 'POSTGRES_17'
|
||||
| 'POSTGRES_16'
|
||||
| 'POSTGRES_15'
|
||||
| 'POSTGRES_14'
|
||||
@@ -47,6 +48,13 @@ export type TestDatabaseProperties = {
|
||||
|
||||
export const allDatabases: Record<TestDatabaseId, TestDatabaseProperties> =
|
||||
Object.freeze({
|
||||
POSTGRES_17: {
|
||||
name: 'Postgres 17.x',
|
||||
driver: 'pg',
|
||||
dockerImageName: getDockerImageForName('postgres:17'),
|
||||
connectionStringEnvironmentVariableName:
|
||||
'BACKSTAGE_TEST_DATABASE_POSTGRES17_CONNECTION_STRING',
|
||||
},
|
||||
POSTGRES_16: {
|
||||
name: 'Postgres 16.x',
|
||||
driver: 'pg',
|
||||
|
||||
+134
-12
@@ -8,10 +8,13 @@ import type { CSSProperties } from 'react';
|
||||
import { Field as Field_2 } from '@base-ui-components/react/field';
|
||||
import { ForwardRefExoticComponent } from 'react';
|
||||
import { Input as Input_2 } from '@base-ui-components/react/input';
|
||||
import { Menu as Menu_2 } from '@base-ui-components/react/menu';
|
||||
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';
|
||||
import { Tooltip as Tooltip_2 } from '@base-ui-components/react/tooltip';
|
||||
|
||||
// @public (undocumented)
|
||||
export type AlignItems = 'stretch' | 'start' | 'center' | 'end';
|
||||
@@ -142,22 +145,12 @@ export const buttonPropDefs: {
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface ButtonProps {
|
||||
// (undocumented)
|
||||
export interface ButtonProps
|
||||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
||||
children: React.ReactNode;
|
||||
// (undocumented)
|
||||
className?: string;
|
||||
// (undocumented)
|
||||
disabled?: boolean;
|
||||
// (undocumented)
|
||||
iconEnd?: IconNames;
|
||||
// (undocumented)
|
||||
iconStart?: IconNames;
|
||||
// (undocumented)
|
||||
size?: ButtonOwnProps['size'];
|
||||
// (undocumented)
|
||||
style?: React.CSSProperties;
|
||||
// (undocumented)
|
||||
variant?: ButtonOwnProps['variant'];
|
||||
}
|
||||
|
||||
@@ -599,6 +592,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>;
|
||||
|
||||
@@ -732,6 +759,30 @@ export const marginPropDefs: (spacingValues: string[]) => {
|
||||
// @public (undocumented)
|
||||
export type MarginProps = GetPropDefTypes<typeof marginPropDefs>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const Menu: MenuComponent;
|
||||
|
||||
// @public (undocumented)
|
||||
export type MenuComponent = {
|
||||
Root: typeof Menu_2.Root;
|
||||
Trigger: typeof Menu_2.Trigger;
|
||||
Portal: typeof Menu_2.Portal;
|
||||
Backdrop: typeof Menu_2.Backdrop;
|
||||
Positioner: typeof Menu_2.Positioner;
|
||||
Popup: typeof Menu_2.Popup;
|
||||
Arrow: typeof Menu_2.Arrow;
|
||||
Item: typeof Menu_2.Item;
|
||||
Group: typeof Menu_2.Group;
|
||||
GroupLabel: typeof Menu_2.GroupLabel;
|
||||
RadioGroup: typeof Menu_2.RadioGroup;
|
||||
RadioItem: typeof Menu_2.RadioItem;
|
||||
RadioItemIndicator: typeof Menu_2.RadioItemIndicator;
|
||||
CheckboxItem: typeof Menu_2.CheckboxItem;
|
||||
CheckboxItemIndicator: typeof Menu_2.CheckboxItemIndicator;
|
||||
SubmenuTrigger: typeof Menu_2.SubmenuTrigger;
|
||||
Separator: typeof Menu_2.Separator;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type NonStylingPropDef = {
|
||||
className?: never;
|
||||
@@ -837,6 +888,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'
|
||||
@@ -957,6 +1040,19 @@ export interface TextProps {
|
||||
// (undocumented)
|
||||
className?: string;
|
||||
// (undocumented)
|
||||
color?:
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'danger'
|
||||
| 'warning'
|
||||
| 'success'
|
||||
| Partial<
|
||||
Record<
|
||||
Breakpoint,
|
||||
'primary' | 'secondary' | 'danger' | 'warning' | 'success'
|
||||
>
|
||||
>;
|
||||
// (undocumented)
|
||||
style?: CSSProperties;
|
||||
// (undocumented)
|
||||
variant?:
|
||||
@@ -969,6 +1065,32 @@ export interface TextProps {
|
||||
weight?: 'regular' | 'bold' | Partial<Record<Breakpoint, 'regular' | 'bold'>>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Tooltip: {
|
||||
Provider: React_2.FC<Tooltip_2.Provider.Props>;
|
||||
Root: React_2.FC<Tooltip_2.Root.Props>;
|
||||
Trigger: React_2.ForwardRefExoticComponent<
|
||||
Omit<Tooltip_2.Trigger.Props & React_2.RefAttributes<any>, 'ref'> &
|
||||
React_2.RefAttributes<any>
|
||||
>;
|
||||
Portal: typeof Tooltip_2.Portal;
|
||||
Positioner: React_2.ForwardRefExoticComponent<
|
||||
Omit<
|
||||
Tooltip_2.Positioner.Props & React_2.RefAttributes<HTMLDivElement>,
|
||||
'ref'
|
||||
> &
|
||||
React_2.RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
Popup: React_2.ForwardRefExoticComponent<
|
||||
Omit<Tooltip_2.Popup.Props & React_2.RefAttributes<HTMLDivElement>, 'ref'> &
|
||||
React_2.RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
Arrow: React_2.ForwardRefExoticComponent<
|
||||
Omit<Tooltip_2.Arrow.Props & React_2.RefAttributes<HTMLDivElement>, 'ref'> &
|
||||
React_2.RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const useIcons: () => IconContextProps;
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
}
|
||||
|
||||
.canon-Button--variant-secondary {
|
||||
background-color: var(--canon-bg-elevated);
|
||||
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;
|
||||
|
||||
@@ -21,13 +21,32 @@ import type { ButtonOwnProps } from './Button.props';
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface ButtonProps {
|
||||
export interface ButtonProps
|
||||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
||||
/**
|
||||
* The size of the button
|
||||
* @defaultValue 'medium'
|
||||
*/
|
||||
size?: ButtonOwnProps['size'];
|
||||
|
||||
/**
|
||||
* The visual variant of the button
|
||||
* @defaultValue 'primary'
|
||||
*/
|
||||
variant?: ButtonOwnProps['variant'];
|
||||
|
||||
/**
|
||||
* The content of the button
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
|
||||
/**
|
||||
* Optional icon to display at the start of the button
|
||||
*/
|
||||
iconStart?: IconNames;
|
||||
|
||||
/**
|
||||
* Optional icon to display at the end of the button
|
||||
*/
|
||||
iconEnd?: IconNames;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
border-radius: var(--canon-radius-3);
|
||||
border: 1px solid var(--canon-border);
|
||||
padding: 0 var(--canon-space-4);
|
||||
background-color: var(--canon-bg-elevated);
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 { Link } from './Link';
|
||||
import { Flex } from '../Flex';
|
||||
import { Text } from '../Text';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Link',
|
||||
component: Link,
|
||||
args: {
|
||||
children: 'Link',
|
||||
},
|
||||
} satisfies Meta<typeof Link>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
href: 'https://canon.backstage.io',
|
||||
children: 'Sign up for Backstage',
|
||||
},
|
||||
};
|
||||
|
||||
export const AllVariants: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
},
|
||||
render: args => (
|
||||
<Flex gap="4" direction="column">
|
||||
<Link href="https://canon.backstage.io" variant="subtitle" {...args} />
|
||||
<Link href="https://canon.backstage.io" variant="body" {...args} />
|
||||
<Link href="https://canon.backstage.io" variant="caption" {...args} />
|
||||
<Link href="https://canon.backstage.io" variant="label" {...args} />
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
|
||||
export const AllWeights: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
},
|
||||
render: args => (
|
||||
<Flex gap="4" direction="column">
|
||||
<Link weight="regular" style={{ maxWidth: '600px' }} {...args} />
|
||||
<Link weight="bold" style={{ maxWidth: '600px' }} {...args} />
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
|
||||
export const Responsive: Story = {
|
||||
args: {
|
||||
variant: {
|
||||
xs: 'label',
|
||||
md: 'body',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Playground: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
},
|
||||
render: args => (
|
||||
<Flex gap="4" direction="column">
|
||||
<Text>Subtitle</Text>
|
||||
<Link variant="subtitle" style={{ maxWidth: '600px' }} {...args} />
|
||||
<Text>Body</Text>
|
||||
<Link variant="body" style={{ maxWidth: '600px' }} {...args} />
|
||||
<Text>Caption</Text>
|
||||
<Link variant="caption" style={{ maxWidth: '600px' }} {...args} />
|
||||
<Text>Label</Text>
|
||||
<Link variant="label" style={{ maxWidth: '600px' }} {...args} />
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { forwardRef } from 'react';
|
||||
import { useResponsiveValue } from '../../hooks/useResponsiveValue';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import type { LinkProps } from './types';
|
||||
|
||||
/** @public */
|
||||
export const Link = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
|
||||
const {
|
||||
children,
|
||||
variant = 'body',
|
||||
weight = 'regular',
|
||||
style,
|
||||
className,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
// Get the responsive values for the variant and weight
|
||||
const responsiveVariant = useResponsiveValue(variant);
|
||||
const responsiveWeight = useResponsiveValue(weight);
|
||||
|
||||
return (
|
||||
<a
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
'canon-Link',
|
||||
responsiveVariant && `canon-Link--variant-${responsiveVariant}`,
|
||||
responsiveWeight && `canon-Link--weight-${responsiveWeight}`,
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
});
|
||||
|
||||
Link.displayName = 'Link';
|
||||
@@ -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 { Link } from './Link';
|
||||
export type { LinkProps } from './types';
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.canon-Link {
|
||||
font-family: var(--canon-font-regular);
|
||||
color: var(--canon-fg-link);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
text-decoration-line: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--canon-fg-link-hover);
|
||||
text-decoration-line: underline;
|
||||
text-decoration-style: solid;
|
||||
text-decoration-thickness: min(2px, max(1px, 0.05em));
|
||||
text-underline-offset: calc(0.025em + 2px);
|
||||
text-decoration-color: color-mix(
|
||||
in srgb,
|
||||
var(--canon-fg-link-hover) 30%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.canon-Link--variant-body {
|
||||
font-size: var(--canon-font-size-3);
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
.canon-Link--variant-subtitle {
|
||||
font-size: var(--canon-font-size-4);
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
.canon-Link--variant-caption {
|
||||
font-size: var(--canon-font-size-2);
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
.canon-Link--variant-label {
|
||||
font-size: var(--canon-font-size-1);
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
.canon-Link--weight-regular {
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
}
|
||||
|
||||
.canon-Link--weight-bold {
|
||||
font-weight: var(--canon-font-weight-bold);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 { CSSProperties, ReactNode } from 'react';
|
||||
import type { Breakpoint } from '../../types';
|
||||
|
||||
/** @public */
|
||||
export interface LinkProps
|
||||
extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||
children: ReactNode;
|
||||
variant?:
|
||||
| 'subtitle'
|
||||
| 'body'
|
||||
| 'caption'
|
||||
| 'label'
|
||||
| Partial<Record<Breakpoint, 'subtitle' | 'body' | 'caption' | 'label'>>;
|
||||
weight?: 'regular' | 'bold' | Partial<Record<Breakpoint, 'regular' | 'bold'>>;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 { Menu } from './Menu';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Menu',
|
||||
component: Menu.Root,
|
||||
} satisfies Meta<typeof Menu.Root>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<Menu.Trigger>Menu</Menu.Trigger>
|
||||
<Menu.Portal>
|
||||
<Menu.Positioner>
|
||||
<Menu.Popup>
|
||||
<Menu.Item>Item 1</Menu.Item>
|
||||
<Menu.Item>Item 2</Menu.Item>
|
||||
<Menu.Item>Item 3</Menu.Item>
|
||||
</Menu.Popup>
|
||||
</Menu.Positioner>
|
||||
</Menu.Portal>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
.canon-MenuTrigger {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.375rem;
|
||||
height: 2.5rem;
|
||||
padding: 0 0.875rem;
|
||||
margin: 0;
|
||||
outline: 0;
|
||||
border: 1px solid var(--color-gray-200);
|
||||
border-radius: 0.375rem;
|
||||
background-color: var(--color-gray-50);
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.5rem;
|
||||
color: var(--color-gray-900);
|
||||
user-select: none;
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
&[data-popup-open] {
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--color-blue);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.canon-MenuPositioner {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.canon-MenuPopup {
|
||||
box-sizing: border-box;
|
||||
padding-block: 0.25rem;
|
||||
border-radius: 0.375rem;
|
||||
background-color: var(--canon-bg-elevated);
|
||||
color: var(--color-fg-primary);
|
||||
transform-origin: var(--transform-origin);
|
||||
transition: transform 150ms, opacity 150ms;
|
||||
|
||||
&[data-starting-style],
|
||||
&[data-ending-style] {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
outline: 1px solid var(--color-gray-200);
|
||||
box-shadow: 0px 10px 15px -3px var(--color-gray-200),
|
||||
0px 4px 6px -4px var(--color-gray-200);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
outline: 1px solid var(--color-gray-300);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.canon-MenuItem {
|
||||
outline: 0;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
padding-block: 0.5rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 2rem;
|
||||
display: flex;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1rem;
|
||||
|
||||
&[data-highlighted] {
|
||||
z-index: 0;
|
||||
position: relative;
|
||||
color: var(--color-gray-50);
|
||||
}
|
||||
|
||||
&[data-highlighted]::before {
|
||||
content: '';
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
inset-inline: 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: var(--color-gray-900);
|
||||
}
|
||||
}
|
||||
|
||||
.canon-MenuSeparator {
|
||||
margin: 0.375rem 1rem;
|
||||
height: 1px;
|
||||
background-color: var(--color-gray-200);
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* 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 { Menu as MenuPrimitive } from '@base-ui-components/react/menu';
|
||||
import clsx from 'clsx';
|
||||
import { MenuComponent } from './types';
|
||||
|
||||
const MenuTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof MenuPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenuPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={clsx('canon-MenuTrigger', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenuTrigger.displayName = MenuPrimitive.Trigger.displayName;
|
||||
|
||||
const MenuBackdrop = React.forwardRef<
|
||||
React.ElementRef<typeof MenuPrimitive.Backdrop>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuPrimitive.Backdrop>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenuPrimitive.Backdrop
|
||||
ref={ref}
|
||||
className={clsx('canon-MenuBackdrop', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenuBackdrop.displayName = MenuPrimitive.Backdrop.displayName;
|
||||
|
||||
const MenuPositioner = React.forwardRef<
|
||||
React.ElementRef<typeof MenuPrimitive.Positioner>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuPrimitive.Positioner>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenuPrimitive.Positioner
|
||||
ref={ref}
|
||||
className={clsx('canon-MenuPositioner', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenuPositioner.displayName = MenuPrimitive.Positioner.displayName;
|
||||
|
||||
const MenuPopup = React.forwardRef<
|
||||
React.ElementRef<typeof MenuPrimitive.Popup>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuPrimitive.Popup>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenuPrimitive.Popup
|
||||
ref={ref}
|
||||
className={clsx('canon-MenuPopup', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenuPopup.displayName = MenuPrimitive.Popup.displayName;
|
||||
|
||||
const MenuArrow = React.forwardRef<
|
||||
React.ElementRef<typeof MenuPrimitive.Arrow>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuPrimitive.Arrow>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenuPrimitive.Arrow
|
||||
ref={ref}
|
||||
className={clsx('canon-MenuArrow', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenuArrow.displayName = MenuPrimitive.Arrow.displayName;
|
||||
|
||||
const MenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof MenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={clsx('canon-MenuItem', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenuItem.displayName = MenuPrimitive.Item.displayName;
|
||||
|
||||
const MenuGroup = React.forwardRef<
|
||||
React.ElementRef<typeof MenuPrimitive.Group>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuPrimitive.Group>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenuPrimitive.Group
|
||||
ref={ref}
|
||||
className={clsx('canon-MenuGroup', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenuGroup.displayName = MenuPrimitive.Group.displayName;
|
||||
|
||||
const MenuGroupLabel = React.forwardRef<
|
||||
React.ElementRef<typeof MenuPrimitive.GroupLabel>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuPrimitive.GroupLabel>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenuPrimitive.GroupLabel
|
||||
ref={ref}
|
||||
className={clsx('canon-MenuGroupLabel', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenuGroupLabel.displayName = MenuPrimitive.GroupLabel.displayName;
|
||||
|
||||
const MenuRadioGroup = React.forwardRef<
|
||||
React.ElementRef<typeof MenuPrimitive.RadioGroup>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuPrimitive.RadioGroup>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenuPrimitive.RadioGroup
|
||||
ref={ref}
|
||||
className={clsx('canon-MenuRadioGroup', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenuRadioGroup.displayName = MenuPrimitive.RadioGroup.displayName;
|
||||
|
||||
const MenuRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof MenuPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuPrimitive.RadioItem>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={clsx('canon-MenuRadioItem', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenuRadioItem.displayName = MenuPrimitive.RadioItem.displayName;
|
||||
|
||||
const MenuRadioItemIndicator = React.forwardRef<
|
||||
React.ElementRef<typeof MenuPrimitive.RadioItemIndicator>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuPrimitive.RadioItemIndicator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenuPrimitive.RadioItemIndicator
|
||||
ref={ref}
|
||||
className={clsx('canon-MenuRadioItemIndicator', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenuRadioItemIndicator.displayName =
|
||||
MenuPrimitive.RadioItemIndicator.displayName;
|
||||
|
||||
const MenuCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof MenuPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuPrimitive.CheckboxItem>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={clsx('canon-MenuCheckboxItem', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenuCheckboxItem.displayName = MenuPrimitive.CheckboxItem.displayName;
|
||||
|
||||
const MenuCheckboxItemIndicator = React.forwardRef<
|
||||
React.ElementRef<typeof MenuPrimitive.CheckboxItemIndicator>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuPrimitive.CheckboxItemIndicator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenuPrimitive.CheckboxItemIndicator
|
||||
ref={ref}
|
||||
className={clsx('canon-MenuCheckboxItemIndicator', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenuCheckboxItemIndicator.displayName =
|
||||
MenuPrimitive.CheckboxItemIndicator.displayName;
|
||||
|
||||
const MenuSubmenuTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof MenuPrimitive.SubmenuTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuPrimitive.SubmenuTrigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenuPrimitive.SubmenuTrigger
|
||||
ref={ref}
|
||||
className={clsx('canon-MenuSubmenuTrigger', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenuSubmenuTrigger.displayName = MenuPrimitive.SubmenuTrigger.displayName;
|
||||
|
||||
const MenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof MenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof MenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={clsx('canon-MenuSeparator', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenuSeparator.displayName = MenuPrimitive.Separator.displayName;
|
||||
|
||||
/** @public */
|
||||
export const Menu: MenuComponent = {
|
||||
Root: MenuPrimitive.Root,
|
||||
Trigger: MenuTrigger,
|
||||
Portal: MenuPrimitive.Portal,
|
||||
Backdrop: MenuBackdrop,
|
||||
Positioner: MenuPositioner,
|
||||
Popup: MenuPopup,
|
||||
Arrow: MenuArrow,
|
||||
Item: MenuItem,
|
||||
Group: MenuGroup,
|
||||
GroupLabel: MenuGroupLabel,
|
||||
RadioGroup: MenuRadioGroup,
|
||||
RadioItem: MenuRadioItem,
|
||||
RadioItemIndicator: MenuRadioItemIndicator,
|
||||
CheckboxItem: MenuCheckboxItem,
|
||||
CheckboxItemIndicator: MenuCheckboxItemIndicator,
|
||||
SubmenuTrigger: MenuSubmenuTrigger,
|
||||
Separator: MenuSeparator,
|
||||
};
|
||||
@@ -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 './Menu';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 { Menu as MenuPrimitive } from '@base-ui-components/react/menu';
|
||||
|
||||
/** @public */
|
||||
export type MenuComponent = {
|
||||
Root: typeof MenuPrimitive.Root;
|
||||
Trigger: typeof MenuPrimitive.Trigger;
|
||||
Portal: typeof MenuPrimitive.Portal;
|
||||
Backdrop: typeof MenuPrimitive.Backdrop;
|
||||
Positioner: typeof MenuPrimitive.Positioner;
|
||||
Popup: typeof MenuPrimitive.Popup;
|
||||
Arrow: typeof MenuPrimitive.Arrow;
|
||||
Item: typeof MenuPrimitive.Item;
|
||||
Group: typeof MenuPrimitive.Group;
|
||||
GroupLabel: typeof MenuPrimitive.GroupLabel;
|
||||
RadioGroup: typeof MenuPrimitive.RadioGroup;
|
||||
RadioItem: typeof MenuPrimitive.RadioItem;
|
||||
RadioItemIndicator: typeof MenuPrimitive.RadioItemIndicator;
|
||||
CheckboxItem: typeof MenuPrimitive.CheckboxItem;
|
||||
CheckboxItemIndicator: typeof MenuPrimitive.CheckboxItemIndicator;
|
||||
SubmenuTrigger: typeof MenuPrimitive.SubmenuTrigger;
|
||||
Separator: typeof MenuPrimitive.Separator;
|
||||
};
|
||||
@@ -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 construction—part 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,7 +2,7 @@
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
background-color: var(--canon-bg-elevated);
|
||||
background-color: var(--canon-bg-surface-1);
|
||||
border-radius: var(--canon-radius-2);
|
||||
padding-bottom: var(--canon-space-0_5);
|
||||
padding-top: var(--canon-space-0_5);
|
||||
@@ -39,16 +39,16 @@
|
||||
& .table-cell:first-child {
|
||||
border-top-left-radius: var(--canon-radius-2);
|
||||
border-bottom-left-radius: var(--canon-radius-2);
|
||||
box-shadow: inset 4px 2px 0 0 var(--canon-bg-elevated),
|
||||
inset 4px -2px 0 0 var(--canon-bg-elevated);
|
||||
box-shadow: inset 4px 2px 0 0 var(--canon-bg-surface-1),
|
||||
inset 4px -2px 0 0 var(--canon-bg-surface-1);
|
||||
padding-left: var(--canon-space-3);
|
||||
}
|
||||
|
||||
& .table-cell:last-child {
|
||||
border-top-right-radius: var(--canon-radius-2);
|
||||
border-bottom-right-radius: var(--canon-radius-2);
|
||||
box-shadow: inset -4px 2px 0 0 var(--canon-bg-elevated),
|
||||
inset -4px -2px 0 0 var(--canon-bg-elevated);
|
||||
box-shadow: inset -4px 2px 0 0 var(--canon-bg-surface-1),
|
||||
inset -4px -2px 0 0 var(--canon-bg-surface-1);
|
||||
padding-right: var(--canon-space-3);
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,6 @@
|
||||
.table-cell {
|
||||
padding: var(--canon-space-3);
|
||||
background-color: var(--canon-bg);
|
||||
box-shadow: inset 0px 2px 0 0 var(--canon-bg-elevated),
|
||||
inset 0px -2px 0 0 var(--canon-bg-elevated);
|
||||
box-shadow: inset 0px 2px 0 0 var(--canon-bg-surface-1),
|
||||
inset 0px -2px 0 0 var(--canon-bg-surface-1);
|
||||
}
|
||||
|
||||
@@ -39,51 +39,49 @@ export const Default: Story = {
|
||||
};
|
||||
|
||||
export const AllVariants: Story = {
|
||||
render: () => (
|
||||
<Flex gap="4">
|
||||
<Text variant="subtitle" style={{ maxWidth: '600px' }}>
|
||||
A man looks at a painting in a museum and says, “Brothers and sisters I
|
||||
have none, but that man's father is my father's son.” Who is
|
||||
in the painting?
|
||||
</Text>
|
||||
<Text variant="body" style={{ maxWidth: '600px' }}>
|
||||
A man looks at a painting in a museum and says, “Brothers and sisters I
|
||||
have none, but that man's father is my father's son.” Who is
|
||||
in the painting?
|
||||
</Text>
|
||||
<Text variant="caption" style={{ maxWidth: '600px' }}>
|
||||
A man looks at a painting in a museum and says, “Brothers and sisters I
|
||||
have none, but that man's father is my father's son.” Who is
|
||||
in the painting?
|
||||
</Text>
|
||||
<Text variant="label" style={{ maxWidth: '600px' }}>
|
||||
A man looks at a painting in a museum and says, “Brothers and sisters I
|
||||
have none, but that man's father is my father's son.” Who is
|
||||
in the painting?
|
||||
</Text>
|
||||
args: {
|
||||
...Default.args,
|
||||
},
|
||||
render: args => (
|
||||
<Flex gap="4" direction="column">
|
||||
<Text variant="subtitle" {...args} />
|
||||
<Text variant="body" {...args} />
|
||||
<Text variant="caption" {...args} />
|
||||
<Text variant="label" {...args} />
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
|
||||
export const AllWeights: Story = {
|
||||
render: () => (
|
||||
<Flex gap="4">
|
||||
<Text weight="regular" style={{ maxWidth: '600px' }}>
|
||||
A man looks at a painting in a museum and says, “Brothers and sisters I
|
||||
have none, but that man's father is my father's son.” Who is
|
||||
in the painting?
|
||||
</Text>
|
||||
<Text weight="bold" style={{ maxWidth: '600px' }}>
|
||||
A man looks at a painting in a museum and says, “Brothers and sisters I
|
||||
have none, but that man's father is my father's son.” Who is
|
||||
in the painting?
|
||||
</Text>
|
||||
args: {
|
||||
...Default.args,
|
||||
},
|
||||
render: args => (
|
||||
<Flex gap="4" direction="column">
|
||||
<Text weight="regular" {...args} />
|
||||
<Text weight="bold" {...args} />
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
|
||||
export const AllColors: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
},
|
||||
render: args => (
|
||||
<Flex gap="4" direction="column">
|
||||
<Text color="primary" {...args} />
|
||||
<Text color="secondary" {...args} />
|
||||
<Text color="danger" {...args} />
|
||||
<Text color="warning" {...args} />
|
||||
<Text color="success" {...args} />
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
|
||||
export const Responsive: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
variant: {
|
||||
xs: 'label',
|
||||
md: 'body',
|
||||
@@ -93,7 +91,7 @@ export const Responsive: Story = {
|
||||
|
||||
export const Playground: Story = {
|
||||
render: () => (
|
||||
<Flex>
|
||||
<Flex gap="4" direction="column">
|
||||
<Text>Subtitle</Text>
|
||||
<Text variant="subtitle" style={{ maxWidth: '600px' }}>
|
||||
A man looks at a painting in a museum and says, “Brothers and sisters I
|
||||
|
||||
@@ -27,6 +27,7 @@ export const Text = forwardRef<HTMLParagraphElement, TextProps>(
|
||||
children,
|
||||
variant = 'body',
|
||||
weight = 'regular',
|
||||
color = 'primary',
|
||||
style,
|
||||
className,
|
||||
...restProps
|
||||
@@ -35,6 +36,7 @@ export const Text = forwardRef<HTMLParagraphElement, TextProps>(
|
||||
// Get the responsive values for the variant and weight
|
||||
const responsiveVariant = useResponsiveValue(variant);
|
||||
const responsiveWeight = useResponsiveValue(weight);
|
||||
const responsiveColor = useResponsiveValue(color);
|
||||
|
||||
return (
|
||||
<p
|
||||
@@ -43,6 +45,7 @@ export const Text = forwardRef<HTMLParagraphElement, TextProps>(
|
||||
'canon-Text',
|
||||
responsiveVariant && `canon-Text--variant-${responsiveVariant}`,
|
||||
responsiveWeight && `canon-Text--weight-${responsiveWeight}`,
|
||||
responsiveColor && `canon-Text--color-${responsiveColor}`,
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
|
||||
@@ -48,3 +48,23 @@
|
||||
.canon-Text--weight-bold {
|
||||
font-weight: var(--canon-font-weight-bold);
|
||||
}
|
||||
|
||||
.canon-Text--color-primary {
|
||||
color: var(--canon-fg-primary);
|
||||
}
|
||||
|
||||
.canon-Text--color-secondary {
|
||||
color: var(--canon-fg-secondary);
|
||||
}
|
||||
|
||||
.canon-Text--color-danger {
|
||||
color: var(--canon-fg-danger);
|
||||
}
|
||||
|
||||
.canon-Text--color-warning {
|
||||
color: var(--canon-fg-warning);
|
||||
}
|
||||
|
||||
.canon-Text--color-success {
|
||||
color: var(--canon-fg-success);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user