Merge pull request #32608 from backstage/bui-links

BUI - Improve Link component styles
This commit is contained in:
Charles de Dreuille
2026-01-31 09:05:55 +00:00
committed by GitHub
30 changed files with 396 additions and 208 deletions
+14
View File
@@ -0,0 +1,14 @@
---
'@backstage/plugin-mui-to-bui': patch
---
Updated MUI to BUI theme converter to align with latest token changes
**Changes:**
- Removed generation of deprecated tokens: `--bui-fg-link`, `--bui-fg-link-hover`, `--bui-fg-tint`, `--bui-fg-tint-disabled`, `--bui-bg-tint` and all its variants
- Added generation of new `info` status tokens: `--bui-fg-info`, `--bui-fg-info-on-bg`, `--bui-bg-info`, `--bui-border-info`
- Updated status color mapping to generate both standalone and `-on-bg` variants for danger, warning, success, and info
- Status colors now use `.main` for standalone variants and `.dark` for `-on-bg` variants, providing better visual hierarchy
The converter now generates tokens that match the updated BUI design system structure, with clear distinction between status colors for standalone use vs. use on colored backgrounds.
+59
View File
@@ -0,0 +1,59 @@
---
'@backstage/ui': minor
---
**BREAKING**: Removed link and tint color tokens, added new status foreground tokens, and improved Link component styling
The following color tokens have been removed:
- `--bui-fg-link` (and all related tokens: `-hover`, `-pressed`, `-disabled`)
- `--bui-fg-tint` (and all related tokens: `-hover`, `-pressed`, `-disabled`)
- `--bui-bg-tint` (and all related tokens: `-hover`, `-pressed`, `-disabled`)
- `--bui-border-tint` (and all related tokens)
**New Status Tokens:**
Added dedicated tokens for status colors that distinguish between usage on status backgrounds vs. standalone usage:
- `--bui-fg-danger-on-bg` / `--bui-fg-danger`
- `--bui-fg-warning-on-bg` / `--bui-fg-warning`
- `--bui-fg-success-on-bg` / `--bui-fg-success`
- `--bui-fg-info-on-bg` / `--bui-fg-info`
The `-on-bg` variants are designed for text on colored backgrounds, while the base variants are for standalone status indicators with improved visibility and contrast.
**Migration:**
For link colors, migrate to one of the following alternatives:
```diff
.custom-link {
- color: var(--bui-fg-link);
+ color: var(--bui-fg-info); /* For informational links */
+ /* or */
+ color: var(--bui-fg-primary); /* For standard text links */
}
```
For tint colors (backgrounds, foregrounds, borders), migrate to appropriate status or neutral colors:
```diff
.info-section {
- background: var(--bui-bg-tint);
+ background: var(--bui-bg-info); /* For informational sections */
+ /* or */
+ background: var(--bui-bg-neutral-on-surface-0); /* For neutral emphasis */
}
```
If you're using status foreground colors on colored backgrounds, update to the new `-on-bg` tokens:
```diff
.error-badge {
- color: var(--bui-fg-danger);
+ color: var(--bui-fg-danger-on-bg);
background: var(--bui-bg-danger);
}
```
**Affected components:** Link
-4
View File
@@ -259,13 +259,9 @@
--bui-fg-primary: var(--bui-white);
--bui-fg-secondary: var(--bui-gray-7);
--bui-fg-link: var(--bui-white);
--bui-fg-link-hover: var(--bui-white);
--bui-fg-disabled: var(--bui-gray-5);
--bui-fg-solid: var(--bui-black);
--bui-fg-solid-disabled: #072f15;
--bui-fg-tint: var(--bui-white);
--bui-fg-tint-disabled: var(--bui-gray-5);
--bui-fg-danger: #e22b2b;
--bui-fg-warning: #e36d05;
--bui-fg-success: #1db954;
+30 -10
View File
@@ -7,7 +7,9 @@ import { MemoryRouter } from 'react-router-dom';
export const Default = () => {
return (
<MemoryRouter>
<Link href="/">Sign up for Backstage</Link>
<Link href="/" variant="body-large">
Sign up for Backstage
</Link>
</MemoryRouter>
);
};
@@ -15,7 +17,7 @@ export const Default = () => {
export const ExternalLink = () => {
return (
<MemoryRouter>
<Link href="https://backstage.io" target="_blank">
<Link href="https://backstage.io" target="_blank" variant="body-large">
Sign up for Backstage
</Link>
</MemoryRouter>
@@ -58,22 +60,25 @@ export const AllVariants = () => {
export const AllColors = () => {
return (
<MemoryRouter>
<Flex gap="4" direction="column">
<Link href="#" color="primary">
<Flex gap="2" direction="column">
<Link href="#" color="primary" variant="body-large">
Primary
</Link>
<Link href="#" color="secondary">
<Link href="#" color="secondary" variant="body-large">
Secondary
</Link>
<Link href="#" color="danger">
<Link href="#" color="danger" variant="body-large">
Danger
</Link>
<Link href="#" color="warning">
<Link href="#" color="warning" variant="body-large">
Warning
</Link>
<Link href="#" color="success">
<Link href="#" color="success" variant="body-large">
Success
</Link>
<Link href="#" color="info" variant="body-large">
Info
</Link>
</Flex>
</MemoryRouter>
);
@@ -83,13 +88,28 @@ export const Weight = () => {
return (
<MemoryRouter>
<Flex gap="4">
<Link href="#" weight="regular">
<Link href="#" weight="regular" variant="body-large">
Regular
</Link>
<Link href="#" weight="bold">
<Link href="#" weight="bold" variant="body-large">
Bold
</Link>
</Flex>
</MemoryRouter>
);
};
export const Standalone = () => {
return (
<MemoryRouter>
<Flex gap="4">
<Link href="#" variant="body-large">
Default link
</Link>
<Link href="#" variant="body-large" standalone>
Standalone link
</Link>
</Flex>
</MemoryRouter>
);
};
+22 -1
View File
@@ -10,6 +10,7 @@ import {
allVariantsSnippet,
allColorsSnippet,
weightSnippet,
standaloneSnippet,
} from './snippets';
import {
Default,
@@ -17,6 +18,7 @@ import {
AllVariants,
AllColors,
Weight,
Standalone,
} from './components';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
@@ -55,6 +57,7 @@ Use `target="_blank"` to open links in a new tab.
py={4}
preview={<ExternalLink />}
code={externalLinkSnippet}
layout="side-by-side"
/>
### Variants
@@ -79,7 +82,25 @@ Status colors for contextual links.
### Weight
<Snippet align="center" py={4} preview={<Weight />} code={weightSnippet} />
<Snippet
align="center"
py={4}
preview={<Weight />}
code={weightSnippet}
layout="side-by-side"
/>
### Standalone
Use `standalone` to remove the underline by default. The underline will appear on hover.
<Snippet
align="center"
py={4}
preview={<Standalone />}
code={standaloneSnippet}
layout="side-by-side"
/>
<Theming definition={LinkDefinition} />
@@ -66,6 +66,12 @@ export const linkPropDefs: Record<string, PropDef> = {
type: 'boolean',
description:
'Truncates text with ellipsis when it overflows its container.',
default: 'false',
},
standalone: {
type: 'boolean',
description: 'Removes underline by default. Underline appears on hover.',
default: 'false',
},
...childrenPropDefs,
...classNamePropDefs,
+7 -1
View File
@@ -4,7 +4,7 @@ export const linkUsageSnippet = `import { Link } from '@backstage/ui';
export const defaultSnippet = `<Link href="/">Sign up for Backstage</Link>`;
export const externalLinkSnippet = `<Link href="https://backstage.io" target="_blank">
export const externalLinkSnippet = `<Link href="#" target="_blank">
Sign up for Backstage
</Link>`;
@@ -25,9 +25,15 @@ export const allColorsSnippet = `<Flex gap="4" direction="column">
<Link href="#" color="danger">Danger</Link>
<Link href="#" color="warning">Warning</Link>
<Link href="#" color="success">Success</Link>
<Link href="#" color="info">Info</Link>
</Flex>`;
export const weightSnippet = `<Flex gap="4">
<Link href="#" weight="regular">Regular</Link>
<Link href="#" weight="bold">Bold</Link>
</Flex>`;
export const standaloneSnippet = `<Flex gap="4">
<Link href="#">Default link</Link>
<Link href="#" standalone>Standalone link</Link>
</Flex>`;
@@ -30,6 +30,7 @@ export const Colors = () => {
<Text color="danger">Danger</Text>
<Text color="warning">Warning</Text>
<Text color="success">Success</Text>
<Text color="info">Info</Text>
</Flex>
);
};
@@ -21,6 +21,7 @@ export const colorsSnippet = `<Flex direction="column" gap="2">
<Text color="danger">Danger</Text>
<Text color="warning">Warning</Text>
<Text color="success">Success</Text>
<Text color="info">Info</Text>
</Flex>`;
export const weightsSnippet = `<Flex direction="column" gap="2">
+50 -58
View File
@@ -240,30 +240,6 @@ color of your app.
</Table.Cell>
<Table.Cell>Used for solid background colors when disabled.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-bg-tint</Chip>
</Table.Cell>
<Table.Cell>Used for tint background colors.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-bg-tint-hover</Chip>
</Table.Cell>
<Table.Cell>Used for tint background colors when hovered.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-bg-tint-focus</Chip>
</Table.Cell>
<Table.Cell>Used for tint background colors when active.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-bg-tint-disabled</Chip>
</Table.Cell>
<Table.Cell>Used for tint background colors when disabled.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-bg-danger</Chip>
@@ -282,6 +258,12 @@ color of your app.
</Table.Cell>
<Table.Cell>Used to show success information.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-bg-info</Chip>
</Table.Cell>
<Table.Cell>Used to show informational content.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
@@ -315,22 +297,6 @@ are prefixed with `fg` to make it easier to identify.
It should be used on top of main background surfaces.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-fg-link</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of main background surfaces.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-fg-link-hover</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of main background surfaces.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-fg-disabled</Chip>
@@ -347,33 +313,45 @@ are prefixed with `fg` to make it easier to identify.
It should be used on top of solid background colors.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-fg-tint</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of tint background colors.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-fg-tint-disabled</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of tint background colors when disabled.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-fg-danger</Chip>
</Table.Cell>
<Table.Cell>Used for error states and destructive actions.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-fg-warning</Chip>
</Table.Cell>
<Table.Cell>
Used for warning states and cautionary information.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-fg-success</Chip>
</Table.Cell>
<Table.Cell>Used for success states and positive feedback.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-fg-info</Chip>
</Table.Cell>
<Table.Cell>
Used for informational content and neutral status.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-fg-danger-on-bg</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of danger background colors.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-fg-warning</Chip>
<Chip head>--bui-fg-warning-on-bg</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of warning background colors.
@@ -381,12 +359,20 @@ are prefixed with `fg` to make it easier to identify.
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-fg-success</Chip>
<Chip head>--bui-fg-success-on-bg</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of success background colors.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-fg-info-on-bg</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of info background colors.
</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
@@ -449,6 +435,12 @@ low contrast to help as a separator with the different background colors.
</Table.Cell>
<Table.Cell>It should be used on top of `--bui-bg-success`.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--bui-border-info</Chip>
</Table.Cell>
<Table.Cell>It should be used on top of `--bui-bg-info`.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
+16 -16
View File
@@ -79,17 +79,17 @@
--bui-bg-info: #dbeafe;
--bui-fg-primary: var(--bui-black);
--bui-fg-secondary: var(--bui-gray-7);
--bui-fg-link: #1f5493;
--bui-fg-link-hover: #1f2d5c;
--bui-fg-disabled: #9e9e9e;
--bui-fg-solid: var(--bui-white);
--bui-fg-solid-disabled: #98a8bc;
--bui-fg-tint: #1f5493;
--bui-fg-tint-disabled: var(--bui-gray-5);
--bui-fg-danger: #991919;
--bui-fg-warning: #92310a;
--bui-fg-success: #116932;
--bui-fg-info: #173da6;
--bui-fg-danger-on-bg: #991919;
--bui-fg-warning-on-bg: #92310a;
--bui-fg-success-on-bg: #116932;
--bui-fg-info-on-bg: #173da6;
--bui-fg-danger: #ec3b18;
--bui-fg-warning: #ef7a32;
--bui-fg-success: #1ed760;
--bui-fg-info: #0d74ce;
--bui-border: #0000001a;
--bui-border-hover: #0003;
--bui-border-pressed: #0006;
@@ -143,17 +143,17 @@
--bui-bg-info: #132049;
--bui-fg-primary: var(--bui-white);
--bui-fg-secondary: var(--bui-gray-7);
--bui-fg-link: #9cc9ff;
--bui-fg-link-hover: #7eb5f7;
--bui-fg-disabled: var(--bui-gray-7);
--bui-fg-solid: #101821;
--bui-fg-solid-disabled: #6191cc;
--bui-fg-tint: #9cc9ff;
--bui-fg-tint-disabled: var(--bui-gray-5);
--bui-fg-danger: #fca5a5;
--bui-fg-warning: #fdba74;
--bui-fg-success: #86efac;
--bui-fg-info: #a3cfff;
--bui-fg-danger-on-bg: #fca5a5;
--bui-fg-warning-on-bg: #fdba74;
--bui-fg-success-on-bg: #86efac;
--bui-fg-info-on-bg: #a3cfff;
--bui-fg-danger: #ff5a30;
--bui-fg-warning: #ffa057;
--bui-fg-success: #1ed760;
--bui-fg-info: #70b8ff;
--bui-border: #ffffff1f;
--bui-border-hover: #fff6;
--bui-border-pressed: #ffffff80;
-4
View File
@@ -222,13 +222,9 @@
--bui-bg-success: #132d21;
--bui-fg-primary: var(--bui-white);
--bui-fg-secondary: var(--bui-gray-7);
--bui-fg-link: var(--bui-white);
--bui-fg-link-hover: var(--bui-white);
--bui-fg-disabled: var(--bui-gray-5);
--bui-fg-solid: var(--bui-black);
--bui-fg-solid-disabled: #072f15;
--bui-fg-tint: var(--bui-white);
--bui-fg-tint-disabled: var(--bui-gray-5);
--bui-fg-danger: #e22b2b;
--bui-fg-warning: #e36d05;
--bui-fg-success: #1db954;
+19 -18
View File
@@ -131,7 +131,10 @@ And if youd like to go even further, you can target specific component class
| `--bui-fg-solid` | This is for texts or icons on top of a solid backgrounds. |
| `--bui-fg-primary` | Your primary text or icon colours. |
| `--bui-fg-secondary` | Your secondary text or icon colours. |
| `--bui-fg-link` | Used for links. |
| `--bui-fg-danger` | Used for error states and destructive actions. |
| `--bui-fg-warning` | Used for warning states and cautionary information. |
| `--bui-fg-success` | Used for success states and positive feedback. |
| `--bui-fg-info` | Used for informational content and neutral status. |
| `--bui-border` | Main borders around surfaces like `Card`, `Dialog`, ... |
| `--bui-font-regular` | The main font of your app. |
@@ -169,31 +172,29 @@ These colors are used for the background of your application. We are mostly usin
| `--bui-bg-solid-hover` | Used for solid background colors when hovered. |
| `--bui-bg-solid-pressed` | Used for solid background colors when pressed. |
| `--bui-bg-solid-disabled` | Used for solid background colors when disabled. |
| `--bui-bg-tint` | Used for tint background colors. |
| `--bui-bg-tint-hover` | Used for tint background colors when hovered. |
| `--bui-bg-tint-focus` | Used for tint background colors when active. |
| `--bui-bg-tint-disabled` | Used for tint background colors when disabled. |
| `--bui-bg-danger` | Used to show errors information. |
| `--bui-bg-warning` | Used to show warnings information. |
| `--bui-bg-success` | Used to show success information. |
| `--bui-bg-info` | Used to show informational content. |
#### Foreground colors
Foreground colours are meant to work in pair with a background colours. Typically this would work for icons, texts, shapes, ... Use a matching name to know what foreground color to use. These colors are prefixed with `fg` to make it easier to identify.
| Token Name | Description |
| ------------------------ | ----------------------------------------------------------------- |
| `--bui-fg-primary` | It should be used on top of main background surfaces. |
| `--bui-fg-secondary` | It should be used on top of main background surfaces. |
| `--bui-fg-link` | It should be used on top of main background surfaces. |
| `--bui-fg-link-hover` | It should be used on top of main background surfaces. |
| `--bui-fg-disabled` | It should be used on top of main background surfaces. |
| `--bui-fg-solid` | It should be used on top of solid background colors. |
| `--bui-fg-tint` | It should be used on top of tint background colors. |
| `--bui-fg-tint-disabled` | It should be used on top of tint background colors when disabled. |
| `--bui-fg-danger` | It should be used on top of danger background colors. |
| `--bui-fg-warning` | It should be used on top of warning background colors. |
| `--bui-fg-success` | It should be used on top of success background colors. |
| Token Name | Description |
| ------------------------ | ------------------------------------------------------ |
| `--bui-fg-primary` | It should be used on top of main background surfaces. |
| `--bui-fg-secondary` | It should be used on top of main background surfaces. |
| `--bui-fg-disabled` | It should be used on top of main background surfaces. |
| `--bui-fg-solid` | It should be used on top of solid background colors. |
| `--bui-fg-danger` | Used for error states and destructive actions. |
| `--bui-fg-warning` | Used for warning states and cautionary information. |
| `--bui-fg-success` | Used for success states and positive feedback. |
| `--bui-fg-info` | Used for informational content and neutral status. |
| `--bui-fg-danger-on-bg` | It should be used on top of danger background colors. |
| `--bui-fg-warning-on-bg` | It should be used on top of warning background colors. |
| `--bui-fg-success-on-bg` | It should be used on top of success background colors. |
| `--bui-fg-info-on-bg` | It should be used on top of info background colors. |
#### Border colors
+16 -16
View File
@@ -81,17 +81,17 @@
--bui-bg-info: #dbeafe;
--bui-fg-primary: var(--bui-black);
--bui-fg-secondary: var(--bui-gray-7);
--bui-fg-link: #1f5493;
--bui-fg-link-hover: #1f2d5c;
--bui-fg-disabled: #9e9e9e;
--bui-fg-solid: var(--bui-white);
--bui-fg-solid-disabled: #98a8bc;
--bui-fg-tint: #1f5493;
--bui-fg-tint-disabled: var(--bui-gray-5);
--bui-fg-danger: #991919;
--bui-fg-warning: #92310a;
--bui-fg-success: #116932;
--bui-fg-info: #173da6;
--bui-fg-danger-on-bg: #991919;
--bui-fg-warning-on-bg: #92310a;
--bui-fg-success-on-bg: #116932;
--bui-fg-info-on-bg: #173da6;
--bui-fg-danger: #ec3b18;
--bui-fg-warning: #ef7a32;
--bui-fg-success: #1ed760;
--bui-fg-info: #0d74ce;
--bui-border: #0000001a;
--bui-border-hover: #0003;
--bui-border-pressed: #0006;
@@ -146,17 +146,17 @@
--bui-bg-info: #132049;
--bui-fg-primary: var(--bui-white);
--bui-fg-secondary: var(--bui-gray-7);
--bui-fg-link: #9cc9ff;
--bui-fg-link-hover: #7eb5f7;
--bui-fg-disabled: var(--bui-gray-7);
--bui-fg-solid: #101821;
--bui-fg-solid-disabled: #6191cc;
--bui-fg-tint: #9cc9ff;
--bui-fg-tint-disabled: var(--bui-gray-5);
--bui-fg-danger: #fca5a5;
--bui-fg-warning: #fdba74;
--bui-fg-success: #86efac;
--bui-fg-info: #a3cfff;
--bui-fg-danger-on-bg: #fca5a5;
--bui-fg-warning-on-bg: #fdba74;
--bui-fg-success-on-bg: #86efac;
--bui-fg-info-on-bg: #a3cfff;
--bui-fg-danger: #ff5a30;
--bui-fg-warning: #ffa057;
--bui-fg-success: #1ed760;
--bui-fg-info: #70b8ff;
--bui-border: #ffffff1f;
--bui-border-hover: #fff6;
--bui-border-pressed: #ffffff80;
+6 -1
View File
@@ -1113,8 +1113,10 @@ export const LinkDefinition: {
'danger',
'warning',
'success',
'info',
];
readonly truncate: readonly [true, false];
readonly standalone: readonly [true, false];
};
};
@@ -1128,6 +1130,8 @@ export interface LinkProps extends LinkProps_2 {
| TextColorStatus
| Partial<Record<Breakpoint, TextColors | TextColorStatus>>;
// (undocumented)
standalone?: boolean;
// (undocumented)
title?: string;
// (undocumented)
truncate?: boolean;
@@ -1906,7 +1910,7 @@ export { Text_2 as Text };
export type TextColors = 'primary' | 'secondary';
// @public (undocumented)
export type TextColorStatus = 'danger' | 'warning' | 'success';
export type TextColorStatus = 'danger' | 'warning' | 'success' | 'info';
// @public
export const TextDefinition: {
@@ -1922,6 +1926,7 @@ export const TextDefinition: {
'danger',
'warning',
'success',
'info',
];
readonly truncate: readonly [true, false];
};
@@ -36,22 +36,22 @@
.bui-Alert[data-status='info'] {
--alert-bg: var(--bui-bg-info);
--alert-fg: var(--bui-fg-info);
--alert-fg: var(--bui-fg-info-on-bg);
}
.bui-Alert[data-status='success'] {
--alert-bg: var(--bui-bg-success);
--alert-fg: var(--bui-fg-success);
--alert-fg: var(--bui-fg-success-on-bg);
}
.bui-Alert[data-status='warning'] {
--alert-bg: var(--bui-bg-warning);
--alert-fg: var(--bui-fg-warning);
--alert-fg: var(--bui-fg-warning-on-bg);
}
.bui-Alert[data-status='danger'] {
--alert-bg: var(--bui-bg-danger);
--alert-fg: var(--bui-fg-danger);
--alert-fg: var(--bui-fg-danger-on-bg);
}
.bui-AlertIcon {
@@ -56,6 +56,7 @@ export const HeaderPage = (props: HeaderPageProps) => {
color="secondary"
truncate
style={{ maxWidth: '240px' }}
standalone
>
{breadcrumb.label}
</Link>
@@ -22,9 +22,14 @@
padding: 0;
margin: 0;
cursor: pointer;
text-decoration-line: none;
display: inline-block;
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, currentColor 30%, transparent);
&:hover {
text-decoration-line: underline;
text-decoration-style: solid;
@@ -102,9 +107,25 @@
color: var(--bui-fg-success);
}
.bui-Link[data-color='info'] {
color: var(--bui-fg-info);
}
.bui-Link[data-truncate] {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.bui-Link[data-standalone] {
text-decoration-line: none;
&: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, currentColor 30%, transparent);
}
}
}
@@ -102,6 +102,12 @@ export const AllColors = meta.story({
color="success"
children="I am success"
/>
<Link
href="https://ui.backstage.io"
variant="title-small"
color="info"
children="I am info"
/>
</Flex>
),
});
@@ -235,6 +241,25 @@ export const Truncate = meta.story({
},
});
export const Standalone = meta.story({
args: {
href: '/',
children: 'Standalone link (no underline by default)',
standalone: true,
},
});
export const StandaloneComparison = meta.story({
render: () => (
<Flex gap="4" direction="column">
<Text>Default link (underline by default):</Text>
<Link href="/" children="Sign up for Backstage" />
<Text>Standalone link (underline on hover only):</Text>
<Link href="/" standalone children="Sign up for Backstage" />
</Flex>
),
});
export const Responsive = meta.story({
args: {
...Default.input.args,
@@ -244,27 +269,3 @@ export const Responsive = meta.story({
},
},
});
export const Playground = meta.story({
args: {
...Default.input.args,
},
render: args => (
<Flex gap="4" direction="column">
<Text>Title X Small</Text>
<Link variant="title-x-small" style={{ maxWidth: '600px' }} {...args} />
<Text>Body X Small</Text>
<Link variant="body-x-small" style={{ maxWidth: '600px' }} {...args} />
<Text>Body Small</Text>
<Link variant="body-small" style={{ maxWidth: '600px' }} {...args} />
<Text>Body Medium</Text>
<Link variant="body-medium" style={{ maxWidth: '600px' }} {...args} />
<Text>Body Large</Text>
<Link variant="body-large" style={{ maxWidth: '600px' }} {...args} />
<Text>Title Small</Text>
<Link variant="title-small" style={{ maxWidth: '600px' }} {...args} />
<Text>Title Medium</Text>
<Link variant="title-medium" style={{ maxWidth: '600px' }} {...args} />
</Flex>
),
});
+1
View File
@@ -44,6 +44,7 @@ const LinkInternal = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
weight,
color,
truncate,
standalone,
slot,
...restProps
} = cleanedProps;
@@ -27,7 +27,15 @@ export const LinkDefinition = {
dataAttributes: {
variant: ['subtitle', 'body', 'caption', 'label'] as const,
weight: ['regular', 'bold'] as const,
color: ['primary', 'secondary', 'danger', 'warning', 'success'] as const,
color: [
'primary',
'secondary',
'danger',
'warning',
'success',
'info',
] as const,
truncate: [true, false] as const,
standalone: [true, false] as const,
},
} as const satisfies ComponentDefinition;
+1
View File
@@ -33,6 +33,7 @@ export interface LinkProps extends AriaLinkProps {
| TextColorStatus
| Partial<Record<Breakpoint, TextColors | TextColorStatus>>;
truncate?: boolean;
standalone?: boolean;
// This is used to set the title attribute on the link
title?: string;
@@ -91,6 +91,10 @@
color: var(--bui-fg-success);
}
.bui-Text[data-color='info'] {
color: var(--bui-fg-info);
}
.bui-Text[data-truncate] {
overflow: hidden;
text-overflow: ellipsis;
@@ -101,6 +101,7 @@ export const AllColors = meta.story({
<Text {...args} color="danger" children="I am danger" />
<Text {...args} color="warning" children="I am warning" />
<Text {...args} color="success" children="I am success" />
<Text {...args} color="info" children="I am info" />
</Flex>
),
});
@@ -27,7 +27,14 @@ export const TextDefinition = {
dataAttributes: {
variant: ['subtitle', 'body', 'caption', 'label'] as const,
weight: ['regular', 'bold'] as const,
color: ['primary', 'secondary', 'danger', 'warning', 'success'] as const,
color: [
'primary',
'secondary',
'danger',
'warning',
'success',
'info',
] as const,
truncate: [true, false] as const,
},
} as const satisfies ComponentDefinition;
+20 -16
View File
@@ -122,17 +122,19 @@
/* Foreground colors */
--bui-fg-primary: var(--bui-black);
--bui-fg-secondary: var(--bui-gray-7);
--bui-fg-link: #1f5493;
--bui-fg-link-hover: #1f2d5c;
--bui-fg-disabled: #9e9e9e;
--bui-fg-solid: var(--bui-white);
--bui-fg-solid-disabled: #98a8bc;
--bui-fg-tint: #1f5493;
--bui-fg-tint-disabled: var(--bui-gray-5);
--bui-fg-danger: #991919;
--bui-fg-warning: #92310a;
--bui-fg-success: #116932;
--bui-fg-info: #173da6;
/* Foreground Statuses */
--bui-fg-danger-on-bg: #991919;
--bui-fg-warning-on-bg: #92310a;
--bui-fg-success-on-bg: #116932;
--bui-fg-info-on-bg: #173da6;
--bui-fg-danger: #ec3b18;
--bui-fg-warning: #ef7a32;
--bui-fg-success: #1ed760;
--bui-fg-info: #0d74ce;
/* Border colors */
--bui-border: rgba(0, 0, 0, 0.1);
@@ -210,17 +212,19 @@
/* Foreground colors */
--bui-fg-primary: var(--bui-white);
--bui-fg-secondary: var(--bui-gray-7);
--bui-fg-link: #9cc9ff;
--bui-fg-link-hover: #7eb5f7;
--bui-fg-disabled: var(--bui-gray-7);
--bui-fg-solid: #101821;
--bui-fg-solid-disabled: #6191cc;
--bui-fg-tint: #9cc9ff;
--bui-fg-tint-disabled: var(--bui-gray-5);
--bui-fg-danger: #fca5a5;
--bui-fg-warning: #fdba74;
--bui-fg-success: #86efac;
--bui-fg-info: #a3cfff;
/* Foreground statuses */
--bui-fg-danger-on-bg: #fca5a5;
--bui-fg-warning-on-bg: #fdba74;
--bui-fg-success-on-bg: #86efac;
--bui-fg-info-on-bg: #a3cfff;
--bui-fg-danger: #ff5a30;
--bui-fg-warning: #ffa057;
--bui-fg-success: #1ed760;
--bui-fg-info: #70b8ff;
/* Border colors */
--bui-border: rgba(255, 255, 255, 0.12);
+1 -1
View File
@@ -131,7 +131,7 @@ export type TextVariants =
export type TextColors = 'primary' | 'secondary';
/** @public */
export type TextColorStatus = 'danger' | 'warning' | 'success';
export type TextColorStatus = 'danger' | 'warning' | 'success' | 'info';
/** @public */
export type TextWeights = 'regular' | 'bold';
@@ -151,6 +151,10 @@ describe('convertMuiToBuiTheme', () => {
expect(result.css).toContain('--bui-bg-danger: #ffcdd2;');
expect(result.css).toContain('--bui-bg-warning: #ffe0b2;');
expect(result.css).toContain('--bui-bg-success: #c8e6c9;');
// Info background may be hex or rgba depending on theme
expect(result.css).toMatch(
/--bui-bg-info:\s*(#[0-9a-f]{6}|rgba?\([^)]*\));/i,
);
});
it('should generate foreground colors correctly', () => {
@@ -178,10 +182,8 @@ describe('convertMuiToBuiTheme', () => {
const result = convertMuiToBuiTheme(theme);
expect(result.css).toContain('--bui-fg-link: #1976d2;');
expect(result.css).toContain('--bui-fg-link-hover: #115293;');
expect(result.css).toContain('--bui-fg-disabled: #cccccc;');
// Foreground danger/warning/success may be hex or rgb depending on theme
// Foreground status colors (standalone)
expect(result.css).toMatch(
/--bui-fg-danger:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i,
);
@@ -191,6 +193,22 @@ describe('convertMuiToBuiTheme', () => {
expect(result.css).toMatch(
/--bui-fg-success:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i,
);
expect(result.css).toMatch(
/--bui-fg-info:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i,
);
// Foreground status colors (on background)
expect(result.css).toMatch(
/--bui-fg-danger-on-bg:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i,
);
expect(result.css).toMatch(
/--bui-fg-warning-on-bg:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i,
);
expect(result.css).toMatch(
/--bui-fg-success-on-bg:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i,
);
expect(result.css).toMatch(
/--bui-fg-info-on-bg:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i,
);
});
it('should generate border colors correctly', () => {
@@ -215,6 +233,10 @@ describe('convertMuiToBuiTheme', () => {
expect(result.css).toContain('--bui-border-danger: #f44336;');
expect(result.css).toContain('--bui-border-warning: #ff9800;');
expect(result.css).toContain('--bui-border-success: #4caf50;');
// Info border may be hex or rgb depending on theme
expect(result.css).toMatch(
/--bui-border-info:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i,
);
});
it('should handle function-based spacing', () => {
@@ -94,16 +94,17 @@ function generateBuiVariables(theme: Mui5Theme): Record<string, string> {
Object.entries({
primary: palette.text.primary,
secondary: palette.textSubtle,
link: palette.link ?? palette.primary.main,
'link-hover': palette.linkHover ?? palette.primary.dark,
disabled: palette.text.disabled,
solid: palette.primary.contrastText,
'solid-disabled': palette.text.disabled,
tint: palette.textSubtle,
'tint-disabled': palette.textVerySubtle,
danger: palette.error.dark,
warning: palette.warning.dark,
success: palette.success.dark,
danger: palette.error.main,
warning: palette.warning.main,
success: palette.success.main,
info: palette.info?.main ?? palette.primary.main,
'danger-on-bg': palette.error.dark,
'warning-on-bg': palette.warning.dark,
'success-on-bg': palette.success.dark,
'info-on-bg': palette.info?.dark ?? palette.primary.dark,
}).forEach(([key, value]) => {
styleObject[`--bui-fg-${key}`] = value;
});
@@ -118,13 +119,10 @@ function generateBuiVariables(theme: Mui5Theme): Record<string, string> {
'solid-hover': blend(palette.primary.main, palette.primary.dark, 0.5),
'solid-pressed': palette.primary.dark,
'solid-disabled': palette.action.disabledBackground,
tint: 'transparent',
'tint-hover': alpha(palette.primary.main, 0.4),
'tint-pressed': alpha(palette.primary.main, 0.6),
'tint-disabled': palette.action.disabledBackground,
danger: palette.error.light,
warning: palette.warning.light,
success: palette.success.light,
info: palette.info?.light ?? alpha(palette.primary.main, 0.1),
}).forEach(([key, value]) => {
styleObject[`--bui-bg-${key}`] = value;
});
@@ -134,6 +132,7 @@ function generateBuiVariables(theme: Mui5Theme): Record<string, string> {
danger: palette.error.main,
warning: palette.warning.main,
success: palette.success.main,
info: palette.info?.main ?? palette.primary.main,
}).forEach(([key, value]) => {
styleObject[`--bui-border-${key}`] = value;
});
+17 -17
View File
@@ -27589,6 +27589,13 @@ __metadata:
languageName: node
linkType: hard
"cookie-signature@npm:1.0.7, cookie-signature@npm:~1.0.6":
version: 1.0.7
resolution: "cookie-signature@npm:1.0.7"
checksum: 10/1a62808cd30d15fb43b70e19829b64d04b0802d8ef00275b57d152de4ae6a3208ca05c197b6668d104c4d9de389e53ccc2d3bc6bcaaffd9602461417d8c40710
languageName: node
linkType: hard
"cookie-signature@npm:^1.2.1, cookie-signature@npm:^1.2.2":
version: 1.2.2
resolution: "cookie-signature@npm:1.2.2"
@@ -27596,14 +27603,7 @@ __metadata:
languageName: node
linkType: hard
"cookie-signature@npm:~1.0.6, cookie-signature@npm:~1.0.7":
version: 1.0.7
resolution: "cookie-signature@npm:1.0.7"
checksum: 10/1a62808cd30d15fb43b70e19829b64d04b0802d8ef00275b57d152de4ae6a3208ca05c197b6668d104c4d9de389e53ccc2d3bc6bcaaffd9602461417d8c40710
languageName: node
linkType: hard
"cookie@npm:0.7.2, cookie@npm:^0.7.0, cookie@npm:^0.7.1, cookie@npm:^0.7.2, cookie@npm:~0.7.1, cookie@npm:~0.7.2":
"cookie@npm:0.7.2, cookie@npm:^0.7.0, cookie@npm:^0.7.1, cookie@npm:^0.7.2, cookie@npm:~0.7.1":
version: 0.7.2
resolution: "cookie@npm:0.7.2"
checksum: 10/24b286c556420d4ba4e9bc09120c9d3db7d28ace2bd0f8ccee82422ce42322f73c8312441271e5eefafbead725980e5996cc02766dbb89a90ac7f5636ede608f
@@ -28487,7 +28487,7 @@ __metadata:
languageName: node
linkType: hard
"debug@npm:2.6.9, debug@npm:^2.6.0, debug@npm:~2.6.9":
"debug@npm:2.6.9, debug@npm:^2.6.0":
version: 2.6.9
resolution: "debug@npm:2.6.9"
dependencies:
@@ -31059,18 +31059,18 @@ __metadata:
linkType: hard
"express-session@npm:^1.17.1, express-session@npm:^1.17.3":
version: 1.19.0
resolution: "express-session@npm:1.19.0"
version: 1.18.2
resolution: "express-session@npm:1.18.2"
dependencies:
cookie: "npm:~0.7.2"
cookie-signature: "npm:~1.0.7"
debug: "npm:~2.6.9"
cookie: "npm:0.7.2"
cookie-signature: "npm:1.0.7"
debug: "npm:2.6.9"
depd: "npm:~2.0.0"
on-headers: "npm:~1.1.0"
parseurl: "npm:~1.3.3"
safe-buffer: "npm:~5.2.1"
safe-buffer: "npm:5.2.1"
uid-safe: "npm:~2.1.5"
checksum: 10/5f36b51d344ec40adb5b9cd8a915fd694cdaf64832632411d4f6e6de8e9a83d873a20389e9c3cce966fece76a18c0384c5f2763b3ac089f801e8d6e92cb543e6
checksum: 10/1a89a4d3e579e1d2a729dc604c254c3e942b24a279681371c2d8042b1080f9e6de4ca787a091ec1a9178c9f2cc5c19bcb9f3929632d44db8972263e26c2cc4b8
languageName: node
linkType: hard
@@ -45678,7 +45678,7 @@ __metadata:
languageName: node
linkType: hard
"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0, safe-buffer@npm:~5.2.1":
"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0":
version: 5.2.1
resolution: "safe-buffer@npm:5.2.1"
checksum: 10/32872cd0ff68a3ddade7a7617b8f4c2ae8764d8b7d884c651b74457967a9e0e886267d3ecc781220629c44a865167b61c375d2da6c720c840ecd73f45d5d9451