Merge branch 'master' into docs-ui

This commit is contained in:
Charles de Dreuille
2025-07-22 16:05:38 +01:00
35 changed files with 597 additions and 1010 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/ui': minor
---
**Breaking** We are upgrading our `Text` component to support all font sizes making the `Heading` component redundant. The new `Text` component introduces 4 sizes for title and 4 sizes for body text. All of these work in multiple colors and font weights. We improved the `as` prop to include all possible values. The `Link` component has also been updated to match the new `Text` component.
File diff suppressed because one or more lines are too long
@@ -1,71 +0,0 @@
import { PropsTable } from '@/components/PropsTable';
import { HeadingSnippet } from '@/snippets/stories-snippets';
import { Snippet } from '@/components/Snippet';
import { CodeBlock } from '@/components/CodeBlock';
import {
headingPropDefs,
headingUsageSnippet,
headingDefaultSnippet,
headingVariantsSnippet,
headingTruncateSnippet,
headingResponsiveSnippet,
} from './heading.props';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { ChangelogComponent } from '@/components/ChangelogComponent';
<PageTitle
title="Heading"
description="Headings are used to structure the content of your page."
/>
<Snippet
py={4}
preview={<HeadingSnippet story="Title1" />}
code={headingDefaultSnippet}
/>
## Usage
<CodeBlock code={headingUsageSnippet} />
## API reference
<PropsTable data={headingPropDefs} />
## Examples
### All variants
The `Heading` component has a `variant` prop that can be used to change the
appearance of the heading.
<Snippet
py={2}
open
preview={<HeadingSnippet story="AllVariants" />}
code={headingVariantsSnippet}
/>
### Truncate
The `Heading` component has a `truncate` prop that can be used to truncate the
heading.
<Snippet
py={2}
open
preview={<HeadingSnippet story="Truncate" />}
code={headingTruncateSnippet}
/>
### Responsive
You can also use the `variant` prop to change the appearance of the text based
on the screen size.
<CodeBlock code={headingResponsiveSnippet} />
<Theming component="Heading" />
<ChangelogComponent component="heading" />
@@ -1,48 +0,0 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const headingPropDefs: Record<string, PropDef> = {
variant: {
type: 'enum',
values: ['display', 'title1', 'title2', 'title3', 'title4', 'title5'],
responsive: true,
},
children: {
type: 'enum',
values: ['ReactNode'],
responsive: false,
},
truncate: {
type: 'boolean',
default: 'false',
},
...classNamePropDefs,
...stylePropDefs,
};
export const headingUsageSnippet = `import { Heading } from '@backstage/ui';
<Heading />`;
export const headingDefaultSnippet = `<Heading variant="title1">Hello World!</Heading>`;
export const headingVariantsSnippet = `<Flex direction="column" gap="4">
<Heading variant="display">Display</Heading>
<Heading variant="title1">Title 1</Heading>
<Heading variant="title2">Title 2</Heading>
<Heading variant="title3">Title 3</Heading>
<Heading variant="title4">Title 4</Heading>
</Flex>`;
export const headingTruncateSnippet = `<Heading style={{ maxWidth: '400px' }} truncate>
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?
</Heading>`;
export const headingResponsiveSnippet = `<Heading variant={{ initial: 'title2', lg: 'title1' }}>
Responsive heading
</Heading>`;
+34
View File
@@ -12,6 +12,9 @@ import {
linkDefaultSnippet,
linkVariantsSnippet,
linkWeightsSnippet,
linkColorsSnippet,
linkRouterSnippet,
linkTruncateSnippet,
} from './link.props';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
@@ -37,6 +40,15 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
<PropsTable data={linkPropDefs} />
## Router Integration
The `Link` component handles both internal and external navigation. It automatically detects whether the provided URL is internal (relative path) or external (absolute URL with protocol) and renders the appropriate element:
- **Internal routes**: Uses `react-router-dom`'s `Link` component for client-side navigation
- **External URLs**: Renders a standard `<a>` element for traditional navigation
<CodeBlock language="tsx" code={linkRouterSnippet} />
## Examples
### Variants
@@ -63,6 +75,28 @@ Here's a view when links have different weights.
code={linkWeightsSnippet}
/>
### Colors
Here's a view when links have different colors.
<Snippet
align="center"
py={4}
open
preview={<LinkSnippet story="AllColors" />}
code={linkColorsSnippet}
/>
### Truncate
The `Link` component has a `truncate` prop that can be used to truncate the text.
<Snippet
open
preview={<LinkSnippet story="Truncate" />}
code={linkTruncateSnippet}
/>
<Theming component="Link" />
<ChangelogComponent component="link" />
+49 -11
View File
@@ -5,13 +5,22 @@ import {
} from '@/utils/propDefs';
export const linkPropDefs: Record<string, PropDef> = {
to: {
href: {
type: 'string',
},
variant: {
type: 'enum',
values: ['subtitle', 'body', 'caption', 'label'],
default: 'body',
values: [
'title-large',
'title-medium',
'title-small',
'title-x-small',
'body-large',
'body-medium',
'body-small',
'body-x-small',
],
default: 'body-medium',
responsive: true,
},
weight: {
@@ -20,24 +29,53 @@ export const linkPropDefs: Record<string, PropDef> = {
default: 'regular',
responsive: true,
},
color: {
type: 'enum',
values: ['primary', 'secondary', 'danger', 'warning', 'success'],
default: 'primary',
responsive: true,
},
...classNamePropDefs,
...stylePropDefs,
};
export const linkUsageSnippet = `import { Link } from '@backstage/ui';
<Link href="https://backstage.io">Sign up for Backstage</Link>`;
<Link href="/sign-up">Sign up for Backstage</Link>`;
export const linkDefaultSnippet = `<Link href="https://backstage.io">Sign up for Backstage</Link>`;
export const linkDefaultSnippet = `<Link href="/">Sign up for Backstage</Link>`;
export const linkVariantsSnippet = `<Flex gap="4" direction="column">
<Link href="https://ui.backstage.io" variant="subtitle" />
<Link href="https://ui.backstage.io" variant="body" />
<Link href="https://ui.backstage.io" variant="caption" />
<Link href="https://ui.backstage.io" variant="label" />
<Link href="/" variant="title-large">...</Link>
<Link href="/" variant="title-medium">...</Link>
<Link href="/" variant="title-small">...</Link>
<Link href="/" variant="title-x-small">...</Link>
<Link href="/" variant="body-large">...</Link>
<Link href="/" variant="body-medium">...</Link>
<Link href="/" variant="body-small">...</Link>
<Link href="/" variant="body-x-small">...</Link>
</Flex>`;
export const linkWeightsSnippet = `<Flex gap="4" direction="column">
<Link href="https://ui.backstage.io" weight="regular" />
<Link href="https://ui.backstage.io" weight="bold" />
<Link href="/" weight="regular" />
<Link href="/" weight="bold" />
</Flex>`;
export const linkColorsSnippet = `<Flex gap="4" direction="column">
<Link href="/" color="primary">I am primary</Link>
<Link href="/" color="secondary">I am secondary</Link>
<Link href="/" color="danger">I am danger</Link>
<Link href="/" color="warning">I am warning</Link>
<Link href="/" color="success">I am success</Link>
</Flex>`;
export const linkRouterSnippet = `import { Link } from '@backstage/ui';
// Internal route
<Link href="/home">Home</Link>
// External URL
<Link href="https://backstage.io">Backstage</Link>
`;
export const linkTruncateSnippet = `<Link href="/" truncate>...</Link>`;
+13 -2
View File
@@ -10,6 +10,7 @@ import {
textWeightsSnippet,
textTruncateSnippet,
textResponsiveSnippet,
textColorsSnippet,
} from './text.props';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
@@ -58,10 +59,20 @@ appearance of the text.
code={textWeightsSnippet}
/>
### All colors
The `Text` component has a `color` prop that can be used to change the
appearance of the text.
<Snippet
open
preview={<TextSnippet story="AllColors" />}
code={textColorsSnippet}
/>
### Truncate
The `Text` component has a `truncate` prop that can be used to truncate the
text.
The `Text` component has a `truncate` prop that can be used to truncate the text.
<Snippet
open
+85 -36
View File
@@ -6,9 +6,38 @@ import {
import type { PropDef } from '@/utils/propDefs';
export const textPropDefs: Record<string, PropDef> = {
as: {
type: 'enum',
values: [
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'p',
'span',
'label',
'div',
'strong',
'em',
'small',
],
default: 'span',
responsive: true,
},
variant: {
type: 'enum',
values: ['display', 'title1', 'title2', 'title3', 'title4', 'title5'],
values: [
'title-large',
'title-medium',
'title-small',
'title-x-small',
'body-large',
'body-medium',
'body-small',
'body-x-small',
],
responsive: true,
},
weight: {
@@ -16,6 +45,12 @@ export const textPropDefs: Record<string, PropDef> = {
values: ['regular', 'bold'],
responsive: true,
},
color: {
type: 'enum',
values: ['primary', 'secondary', 'danger', 'warning', 'success'],
default: 'primary',
responsive: true,
},
truncate: {
type: 'boolean',
default: 'false',
@@ -36,46 +71,60 @@ export const textDefaultSnippet = `<Text style={{ maxWidth: '600px' }}>
</Text>`;
export const textVariantsSnippet = `<Flex direction="column" 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&apos;s father is my father&apos;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&apos;s father is my father&apos;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&apos;s father is my father&apos;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&apos;s father is my father&apos;s son." Who is
in the painting?
</Text>
<Text variant="title-large">...</Text>
<Text variant="title-medium">...</Text>
<Text variant="title-small">...</Text>
<Text variant="title-x-small">...</Text>
<Text variant="body-large">...</Text>
<Text variant="body-medium">...</Text>
<Text variant="body-small">...</Text>
<Text variant="body-x-small">...</Text>
</Flex>`;
export const textWeightsSnippet = `<Flex direction="column" 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&apos;s father is my father&apos;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&apos;s father is my father&apos;s son." Who is
in the painting?
</Text>
<Flex>
<Text variant="title-large" weight="regular">A fox</Text>
<Text variant="title-large" weight="bold">A turtle</Text>
</Flex>
<Flex>
<Text variant="title-medium" weight="regular">A fox</Text>
<Text variant="title-medium" weight="bold">A turtle</Text>
</Flex>
<Flex>
<Text variant="title-small" weight="regular">A fox</Text>
<Text variant="title-small" weight="bold">A turtle</Text>
</Flex>
<Flex>
<Text variant="title-x-small" weight="regular">A fox</Text>
<Text variant="title-x-small" weight="bold">A turtle</Text>
</Flex>
<Flex>
<Text variant="body-large" weight="regular">A fox</Text>
<Text variant="body-large" weight="bold">A turtle</Text>
</Flex>
<Flex>
<Text variant="body-medium" weight="regular">A fox</Text>
<Text variant="body-medium" weight="bold">A turtle</Text>
</Flex>
<Flex>
<Text variant="body-small" weight="regular">A fox</Text>
<Text variant="body-small" weight="bold">A turtle</Text>
</Flex>
<Flex>
<Text variant="body-x-small" weight="regular">A fox</Text>
<Text variant="body-x-small" weight="bold">A turtle</Text>
</Flex>
</Flex>`;
export const textTruncateSnippet = `<Text weight="regular" style={{ maxWidth: '600px' }} truncate>
A man looks at a painting in a museum and says, "Brothers and sisters I
have none, but that man&apos;s father is my father&apos;s son." Who is
in the painting?
</Text>`;
export const textColorsSnippet = `<Flex direction="column" gap="4">
<Text color="primary">I am primary</Text>
<Text color="secondary">I am secondary</Text>
<Text color="danger">I am danger</Text>
<Text color="warning">I am warning</Text>
<Text color="success">I am success</Text>
</Flex>`;
export const textTruncateSnippet = `<Text as="p" truncate>...</Text>`;
export const textResponsiveSnippet = `<Text variant={{ initial: 'body', lg: 'subtitle' }}>
Responsive text
-12
View File
@@ -8,7 +8,6 @@ import * as ButtonLinkStories from '../../../packages/ui/src/components/ButtonLi
import * as CheckboxStories from '../../../packages/ui/src/components/Checkbox/Checkbox.stories';
import * as ContainerStories from '../../../packages/ui/src/components/Container/Container.stories';
import * as GridStories from '../../../packages/ui/src/components/Grid/Grid.stories';
import * as HeadingStories from '../../../packages/ui/src/components/Heading/Heading.stories';
import * as IconStories from '../../../packages/ui/src/components/Icon/Icon.stories';
import * as TextFieldStories from '../../../packages/ui/src/components/TextField/TextField.stories';
import * as TextStories from '../../../packages/ui/src/components/Text/Text.stories';
@@ -113,17 +112,6 @@ export const GridSnippet = ({ story }: { story: keyof typeof GridStories }) => {
return StoryComponent ? <StoryComponent /> : null;
};
export const HeadingSnippet = ({
story,
}: {
story: keyof typeof HeadingStories;
}) => {
const stories = composeStories(HeadingStories);
const StoryComponent = stories[story as keyof typeof stories];
return StoryComponent ? <StoryComponent /> : null;
};
export const IconSnippet = ({ story }: { story: keyof typeof IconStories }) => {
const stories = composeStories(IconStories);
const StoryComponent = stories[story as keyof typeof stories];
-5
View File
@@ -101,11 +101,6 @@ export const components: Page[] = [
slug: 'collapsible',
status: 'alpha',
},
{
title: 'Heading',
slug: 'heading',
status: 'alpha',
},
{
title: 'Icon',
slug: 'icon',
+31 -114
View File
@@ -9732,58 +9732,6 @@
display: flex;
}
.bui-Heading {
font-family: var(--bui-font-regular);
color: var(--bui-fg-primary);
margin: 0;
padding: 0;
line-height: 100%;
}
.bui-Heading[data-variant="display"] {
font-size: var(--bui-font-size-10);
font-weight: var(--bui-font-weight-bold);
}
.bui-Heading[data-variant="title1"] {
font-size: var(--bui-font-size-9);
font-weight: var(--bui-font-weight-bold);
}
.bui-Heading[data-variant="title2"] {
font-size: var(--bui-font-size-8);
font-weight: var(--bui-font-weight-bold);
}
.bui-Heading[data-variant="title3"] {
font-size: var(--bui-font-size-7);
font-weight: var(--bui-font-weight-bold);
}
.bui-Heading[data-variant="title4"] {
font-size: var(--bui-font-size-6);
font-weight: var(--bui-font-weight-bold);
}
.bui-Heading[data-variant="title5"] {
font-size: var(--bui-font-size-5);
font-weight: var(--bui-font-weight-bold);
}
.bui-Heading[data-color="primary"] {
color: var(--bui-fg-primary);
}
.bui-Heading[data-color="secondary"] {
color: var(--bui-fg-secondary);
}
.bui-Heading[data-truncate] {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.bui-Icon {
width: 1rem;
height: 1rem;
@@ -9795,72 +9743,17 @@
margin: 0;
padding: 0;
text-decoration-line: none;
display: inline-block;
&:hover {
text-underline-offset: calc(.025em + 2px);
text-decoration-line: underline;
text-decoration-style: solid;
text-decoration-thickness: min(2px, max(1px, .05em));
text-decoration-color: color-mix(in srgb, var(--bui-fg-link-hover) 30%, transparent);
text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);
}
}
.bui-Link[data-variant="title-large"] {
font-size: var(--bui-font-size-8);
line-height: 140%;
}
.bui-Link[data-variant="title-medium"] {
font-size: var(--bui-font-size-7);
line-height: 140%;
}
.bui-Link[data-variant="title-small"] {
font-size: var(--bui-font-size-6);
line-height: 140%;
}
.bui-Link[data-variant="title-x-small"] {
font-size: var(--bui-font-size-5);
line-height: 140%;
}
.bui-Link[data-variant="body-large"] {
font-size: var(--bui-font-size-4);
line-height: 140%;
}
.bui-Link[data-variant="body-medium"] {
font-size: var(--bui-font-size-3);
line-height: 140%;
}
.bui-Link[data-variant="body-small"] {
font-size: var(--bui-font-size-2);
line-height: 140%;
}
.bui-Link[data-variant="body-x-small"] {
font-size: var(--bui-font-size-1);
line-height: 140%;
}
.bui-Link[data-weight="regular"] {
font-weight: var(--bui-font-weight-regular);
}
.bui-Link[data-weight="bold"] {
font-weight: var(--bui-font-weight-bold);
}
.bui-Link[data-color="primary"] {
color: var(--bui-fg-primary);
}
.bui-Link[data-color="secondary"] {
color: var(--bui-fg-secondary);
}
.bui-MenuPositioner {
z-index: 100;
outline: 0;
@@ -10303,22 +10196,42 @@
padding: 0;
}
.bui-Text[data-variant="body"] {
font-size: var(--bui-font-size-3);
.bui-Text[data-variant="title-large"] {
font-size: var(--bui-font-size-8);
line-height: 140%;
}
.bui-Text[data-variant="subtitle"] {
.bui-Text[data-variant="title-medium"] {
font-size: var(--bui-font-size-7);
line-height: 140%;
}
.bui-Text[data-variant="title-small"] {
font-size: var(--bui-font-size-6);
line-height: 140%;
}
.bui-Text[data-variant="title-x-small"] {
font-size: var(--bui-font-size-5);
line-height: 140%;
}
.bui-Text[data-variant="body-large"] {
font-size: var(--bui-font-size-4);
line-height: 140%;
}
.bui-Text[data-variant="caption"] {
.bui-Text[data-variant="body-medium"] {
font-size: var(--bui-font-size-3);
line-height: 140%;
}
.bui-Text[data-variant="body-small"] {
font-size: var(--bui-font-size-2);
line-height: 140%;
}
.bui-Text[data-variant="label"] {
.bui-Text[data-variant="body-x-small"] {
font-size: var(--bui-font-size-1);
line-height: 140%;
}
@@ -10357,6 +10270,10 @@
overflow: hidden;
}
.bui-Text[data-as="span"], .bui-Text[data-as="label"], .bui-Text[data-as="strong"], .bui-Text[data-as="em"], .bui-Text[data-as="small"] {
display: inline-block;
}
.bui-TextField {
font-family: var(--bui-font-regular);
flex-direction: column;
+47 -105
View File
@@ -47,25 +47,6 @@ export type ArbitraryStylingPropDef = {
parseValue?: (value: string) => string | undefined;
};
// @public (undocumented)
export type AsProps =
| 'div'
| 'span'
| 'p'
| 'article'
| 'section'
| 'main'
| 'nav'
| 'aside'
| 'ul'
| 'ol'
| 'li'
| 'details'
| 'summary'
| 'dd'
| 'dl'
| 'dt';
// @public (undocumented)
export const Avatar: ForwardRefExoticComponent<
AvatarProps & RefAttributes<HTMLSpanElement>
@@ -1060,45 +1041,6 @@ export interface HeaderTab {
matchStrategy?: TabMatchStrategy;
}
// @public (undocumented)
export const Heading: {
<T extends ElementType = 'h1'>(
props: HeadingProps<T> & {
ref?: React.ComponentPropsWithRef<T>['ref'];
},
): React.ReactElement<HeadingProps<T>, T>;
displayName: string;
};
// @public (undocumented)
export type HeadingOwnProps = {
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
variant?:
| 'display'
| 'title1'
| 'title2'
| 'title3'
| 'title4'
| 'title5'
| Partial<
Record<
Breakpoint,
'display' | 'title1' | 'title2' | 'title3' | 'title4' | 'title5'
>
>;
color?:
| 'primary'
| 'secondary'
| Partial<Record<Breakpoint, 'primary' | 'secondary'>>;
truncate?: boolean;
className?: string;
style?: React.CSSProperties;
};
// @public (undocumented)
export type HeadingProps<T extends ElementType = 'h1'> = HeadingOwnProps &
Omit<ComponentPropsWithRef<T>, keyof HeadingOwnProps>;
// @public (undocumented)
export const heightPropDefs: {
height: {
@@ -1238,34 +1180,15 @@ export const Link: ForwardRefExoticComponent<
export interface LinkProps extends LinkProps_2 {
// (undocumented)
color?:
| 'primary'
| 'secondary'
| Partial<Record<Breakpoint, 'primary' | 'secondary'>>;
| TextColors
| TextColorStatus
| Partial<Record<Breakpoint, TextColors | TextColorStatus>>;
// (undocumented)
variant?:
| 'title-large'
| 'title-medium'
| 'title-small'
| 'title-x-small'
| 'body-large'
| 'body-medium'
| 'body-small'
| 'body-x-small'
| Partial<
Record<
Breakpoint,
| 'title-large'
| 'title-medium'
| 'title-small'
| 'title-x-small'
| 'body-large'
| 'body-medium'
| 'body-small'
| 'body-x-small'
>
>;
truncate?: boolean;
// (undocumented)
weight?: 'regular' | 'bold' | Partial<Record<Breakpoint, 'regular' | 'bold'>>;
variant?: TextVariants | Partial<Record<Breakpoint, TextVariants>>;
// (undocumented)
weight?: TextWeights | Partial<Record<Breakpoint, TextWeights>>;
}
// @public (undocumented)
@@ -1753,6 +1676,12 @@ const Text_2: {
};
export { Text_2 as Text };
// @public (undocumented)
export type TextColors = 'primary' | 'secondary';
// @public (undocumented)
export type TextColorStatus = 'danger' | 'warning' | 'success';
// @public (undocumented)
export const TextField: ForwardRefExoticComponent<
TextFieldProps & RefAttributes<HTMLDivElement>
@@ -1769,35 +1698,48 @@ export interface TextFieldProps
// @public (undocumented)
export type TextOwnProps = {
as?: 'p' | 'span' | 'label';
variant?:
| 'subtitle'
| 'body'
| 'caption'
as?:
| 'h1'
| 'h2'
| 'h3'
| 'h4'
| 'h5'
| 'h6'
| 'p'
| 'span'
| 'label'
| Partial<Record<Breakpoint, 'subtitle' | 'body' | 'caption' | 'label'>>;
weight?: 'regular' | 'bold' | Partial<Record<Breakpoint, 'regular' | 'bold'>>;
| 'div'
| 'strong'
| 'em'
| 'small'
| 'legend';
variant?: TextVariants | Partial<Record<Breakpoint, TextVariants>>;
weight?: TextWeights | Partial<Record<Breakpoint, TextWeights>>;
color?:
| 'primary'
| 'secondary'
| 'danger'
| 'warning'
| 'success'
| Partial<
Record<
Breakpoint,
'primary' | 'secondary' | 'danger' | 'warning' | 'success'
>
>;
| TextColors
| TextColorStatus
| Partial<Record<Breakpoint, TextColors | TextColorStatus>>;
truncate?: boolean;
className?: string;
style?: React.CSSProperties;
};
// @public (undocumented)
export type TextProps<T extends ElementType = 'p'> = TextOwnProps &
export type TextProps<T extends ElementType = 'span'> = TextOwnProps &
Omit<ComponentPropsWithRef<T>, keyof TextOwnProps>;
// @public (undocumented)
export type TextVariants =
| 'title-large'
| 'title-medium'
| 'title-small'
| 'title-x-small'
| 'body-large'
| 'body-medium'
| 'body-small'
| 'body-x-small';
// @public (undocumented)
export type TextWeights = 'regular' | 'bold';
// @public (undocumented)
export const Tooltip: ForwardRefExoticComponent<
TooltipProps & RefAttributes<HTMLDivElement>
@@ -70,7 +70,7 @@ const DataTablePagination = forwardRef(
)}
</div>
<div className="bui-DataTablePagination--right">
<Text variant="body">{`${fromCount} - ${toCount} of ${rowCount}`}</Text>
<Text variant="body-medium">{`${fromCount} - ${toCount} of ${rowCount}`}</Text>
<ButtonIcon
variant="secondary"
size="small"
@@ -268,10 +268,10 @@ export const WithMockedURLCampaigns: Story = {
<MemoryRouter initialEntries={['/campaigns']}>
<Header {...args} />
<Container>
<Text>
<Text as="p">
Current URL is mocked to be: <strong>/campaigns</strong>
</Text>
<Text>
<Text as="p">
Notice how the "Campaigns" tab is selected (highlighted) because it
matches the current path.
</Text>
@@ -288,10 +288,10 @@ export const WithMockedURLIntegrations: Story = {
<MemoryRouter initialEntries={['/integrations']}>
<Header {...args} />
<Container>
<Text>
<Text as="p">
Current URL is mocked to be: <strong>/integrations</strong>
</Text>
<Text>
<Text as="p">
Notice how the "Integrations" tab is selected (highlighted) because it
matches the current path.
</Text>
@@ -308,14 +308,14 @@ export const WithMockedURLNoMatch: Story = {
<MemoryRouter initialEntries={['/some-other-page']}>
<Header {...args} />
<Container>
<Text>
<Text as="p">
Current URL is mocked to be: <strong>/some-other-page</strong>
</Text>
<Text>
<Text as="p">
No tab is selected because the current path doesn't match any tab's
href.
</Text>
<Text>
<Text as="p">
Tabs without href (like "Overview", "Checks", "Tracks") fall back to
React Aria's internal state.
</Text>
@@ -99,7 +99,7 @@ export const HeaderToolbar = (props: HeaderToolbarProps) => {
const titleContent = (
<>
<div className={classNames.toolbarIcon}>{icon || <RiShapesLine />}</div>
<Text variant="body">{title || 'Your plugin'}</Text>
<Text variant="body-medium">{title || 'Your plugin'}</Text>
</>
);
@@ -15,8 +15,8 @@
*/
import type { HeaderPageProps } from './types';
import { Heading } from '../Heading';
import { Menu } from '../Menu';
import { Text } from '../Text';
import { ButtonIcon } from '../ButtonIcon';
import { RiMore2Line } from '@remixicon/react';
import { Tabs, TabList, Tab } from '../Tabs';
@@ -34,7 +34,9 @@ export const HeaderPage = (props: HeaderPageProps) => {
return (
<div className={classNames.root}>
<div className={classNames.content}>
<Heading variant="title4">{title}</Heading>
<Text variant="title-small" weight="bold">
{title}
</Text>
<div className={classNames.controls}>
{customActions}
{menuItems && (
@@ -1,114 +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 type { Meta, StoryObj } from '@storybook/react';
import { Heading } from './Heading';
import { Flex } from '../Flex';
const meta = {
title: 'Components/Heading',
component: Heading,
args: {
children: 'Heading',
},
} satisfies Meta<typeof Heading>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
export const Title1: Story = {
args: {
children: 'Look mum, no hands!',
variant: 'title1',
},
};
export const AllVariants: Story = {
render: () => (
<Flex direction="column" gap="4">
<Heading variant="display">Display</Heading>
<Heading variant="title1">Title 1</Heading>
<Heading variant="title2">Title 2</Heading>
<Heading variant="title3">Title 3</Heading>
<Heading variant="title4">Title 4</Heading>
<Heading variant="title5">Title 5</Heading>
</Flex>
),
};
export const AllColors: Story = {
args: {
...Default.args,
},
render: args => (
<Flex gap="4" direction="column">
<Heading color="primary" {...args} />
<Heading color="secondary" {...args} />
</Flex>
),
};
export const Truncate: Story = {
args: {
...Title1.args,
truncate: true,
style: { maxWidth: '400px' },
},
};
export const Responsive: Story = {
args: {
variant: {
xs: 'title4',
md: 'display',
},
},
};
export const WrappedInLink: Story = {
args: {
...Default.args,
},
decorators: [
Story => (
<a href="/">
<Story />
</a>
),
],
};
export const CustomRender: Story = {
args: {
...Default.args,
as: 'h4',
},
};
export const Playground: Story = {
render: () => (
<Flex direction="column" gap="4">
<Heading variant="display">Display</Heading>
<Heading variant="title1">Title 1</Heading>
<Heading variant="title2">Title 2</Heading>
<Heading variant="title3">Title 3</Heading>
<Heading variant="title4">Title 4</Heading>
<Heading variant="title5">Title 5</Heading>
</Flex>
),
};
@@ -1,64 +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 { forwardRef } from 'react';
import clsx from 'clsx';
import type { ElementType } from 'react';
import type { HeadingProps } from './types';
import { useStyles } from '../../hooks/useStyles';
function HeadingComponent<T extends ElementType = 'h1'>(
{
as,
variant = 'title1',
color = 'primary',
truncate,
className,
style,
...restProps
}: HeadingProps<T>,
ref: React.Ref<any>,
) {
const Component = as || 'h1';
const { classNames, dataAttributes } = useStyles('Heading', {
variant,
color,
});
return (
<Component
ref={ref}
className={clsx(classNames.root, className)}
data-truncate={truncate}
{...dataAttributes}
style={style}
{...restProps}
/>
);
}
HeadingComponent.displayName = 'Heading';
/** @public */
export const Heading = forwardRef(HeadingComponent) as {
<T extends ElementType = 'h1'>(
props: HeadingProps<T> & { ref?: React.ComponentPropsWithRef<T>['ref'] },
): React.ReactElement<HeadingProps<T>, T>;
displayName: string;
};
Heading.displayName = 'Heading';
@@ -1,18 +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.
*/
export * from './Heading';
export * from './types';
@@ -1,67 +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.
*/
.bui-Heading {
font-family: var(--bui-font-regular);
color: var(--bui-fg-primary);
line-height: 100%;
padding: 0;
margin: 0;
}
.bui-Heading[data-variant='display'] {
font-size: var(--bui-font-size-10);
font-weight: var(--bui-font-weight-bold);
}
.bui-Heading[data-variant='title1'] {
font-size: var(--bui-font-size-9);
font-weight: var(--bui-font-weight-bold);
}
.bui-Heading[data-variant='title2'] {
font-size: var(--bui-font-size-8);
font-weight: var(--bui-font-weight-bold);
}
.bui-Heading[data-variant='title3'] {
font-size: var(--bui-font-size-7);
font-weight: var(--bui-font-weight-bold);
}
.bui-Heading[data-variant='title4'] {
font-size: var(--bui-font-size-6);
font-weight: var(--bui-font-weight-bold);
}
.bui-Heading[data-variant='title5'] {
font-size: var(--bui-font-size-5);
font-weight: var(--bui-font-weight-bold);
}
.bui-Heading[data-color='primary'] {
color: var(--bui-fg-primary);
}
.bui-Heading[data-color='secondary'] {
color: var(--bui-fg-secondary);
}
.bui-Heading[data-truncate] {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@@ -1,47 +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 type { ElementType, ComponentPropsWithRef } from 'react';
import type { Breakpoint } from '../../types';
/** @public */
export type HeadingOwnProps = {
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
variant?:
| 'display'
| 'title1'
| 'title2'
| 'title3'
| 'title4'
| 'title5'
| Partial<
Record<
Breakpoint,
'display' | 'title1' | 'title2' | 'title3' | 'title4' | 'title5'
>
>;
color?:
| 'primary'
| 'secondary'
| Partial<Record<Breakpoint, 'primary' | 'secondary'>>;
truncate?: boolean;
className?: string;
style?: React.CSSProperties;
};
/** @public */
export type HeadingProps<T extends ElementType = 'h1'> = HeadingOwnProps &
Omit<ComponentPropsWithRef<T>, keyof HeadingOwnProps>;
+145 -107
View File
@@ -40,7 +40,7 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
href: '/home',
href: '/',
children: 'Sign up for Backstage',
},
};
@@ -72,133 +72,171 @@ export const AllVariants: Story = {
};
export const AllColors: Story = {
args: {
...Default.args,
},
render: args => (
render: () => (
<Flex gap="4" direction="column">
<Link
href="https://ui.backstage.io"
variant="title-small"
color="primary"
{...args}
children="I am primary"
/>
<Link
href="https://ui.backstage.io"
variant="title-small"
color="secondary"
{...args}
children="I am secondary"
/>
<Link
href="https://ui.backstage.io"
variant="title-small"
color="danger"
children="I am danger"
/>
<Link
href="https://ui.backstage.io"
variant="title-small"
color="warning"
children="I am warning"
/>
<Link
href="https://ui.backstage.io"
variant="title-small"
color="success"
children="I am success"
/>
</Flex>
),
};
export const AllWeights: Story = {
args: {
...Default.args,
},
render: args => (
render: () => (
<Flex gap="4" direction="column">
<Link
href="https://ui.backstage.io"
variant="title-large"
weight="regular"
{...args}
/>
<Link
href="https://ui.backstage.io"
variant="title-large"
weight="bold"
{...args}
/>
<Link
href="https://ui.backstage.io"
variant="title-medium"
weight="regular"
{...args}
/>
<Link
href="https://ui.backstage.io"
variant="title-medium"
weight="bold"
{...args}
/>
<Link
href="https://ui.backstage.io"
variant="title-small"
weight="regular"
{...args}
/>
<Link
href="https://ui.backstage.io"
variant="title-small"
weight="bold"
{...args}
/>
<Link
href="https://ui.backstage.io"
variant="title-x-small"
weight="regular"
{...args}
/>
<Link
href="https://ui.backstage.io"
variant="title-x-small"
weight="bold"
{...args}
/>
<Link
href="https://ui.backstage.io"
variant="body-large"
weight="regular"
{...args}
/>
<Link
href="https://ui.backstage.io"
variant="body-large"
weight="bold"
{...args}
/>
<Link
href="https://ui.backstage.io"
variant="body-medium"
weight="regular"
{...args}
/>
<Link
href="https://ui.backstage.io"
variant="body-medium"
weight="bold"
{...args}
/>
<Link
href="https://ui.backstage.io"
variant="body-small"
weight="regular"
{...args}
/>
<Link
href="https://ui.backstage.io"
variant="body-small"
weight="bold"
{...args}
/>
<Link
href="https://ui.backstage.io"
variant="body-x-small"
weight="regular"
{...args}
/>
<Link
href="https://ui.backstage.io"
variant="body-x-small"
weight="bold"
{...args}
/>
<Flex>
<Link
href="https://ui.backstage.io"
variant="title-large"
weight="regular"
children="A fox"
/>
<Link
href="https://ui.backstage.io"
variant="title-large"
weight="bold"
children="A turtle"
/>
</Flex>
<Flex>
<Link
href="https://ui.backstage.io"
variant="title-medium"
weight="regular"
children="A fox"
/>
<Link
href="https://ui.backstage.io"
variant="title-medium"
weight="bold"
children="A turtle"
/>
</Flex>
<Flex>
<Link
href="https://ui.backstage.io"
variant="title-small"
weight="regular"
children="A fox"
/>
<Link
href="https://ui.backstage.io"
variant="title-small"
weight="bold"
children="A turtle"
/>
</Flex>
<Flex>
<Link
href="https://ui.backstage.io"
variant="title-x-small"
weight="regular"
children="A fox"
/>
<Link
href="https://ui.backstage.io"
variant="title-x-small"
weight="bold"
children="A turtle"
/>
</Flex>
<Flex>
<Link
href="https://ui.backstage.io"
variant="body-large"
weight="regular"
children="A fox"
/>
<Link
href="https://ui.backstage.io"
variant="body-large"
weight="bold"
children="A turtle"
/>
</Flex>
<Flex>
<Link
href="https://ui.backstage.io"
variant="body-medium"
weight="regular"
children="A fox"
/>
<Link
href="https://ui.backstage.io"
variant="body-medium"
weight="bold"
children="A turtle"
/>
</Flex>
<Flex>
<Link
href="https://ui.backstage.io"
variant="body-small"
weight="regular"
children="A fox"
/>
<Link
href="https://ui.backstage.io"
variant="body-small"
weight="bold"
children="A turtle"
/>
</Flex>
<Flex>
<Link
href="https://ui.backstage.io"
variant="body-x-small"
weight="regular"
children="A fox"
/>
<Link
href="https://ui.backstage.io"
variant="body-x-small"
weight="bold"
children="A turtle"
/>
</Flex>
</Flex>
),
};
export const Truncate: Story = {
args: {
children:
"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?",
href: '/',
truncate: true,
style: { width: '480px' },
},
};
export const Responsive: Story = {
args: {
...Default.args,
+14 -9
View File
@@ -51,15 +51,18 @@ export const Link = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
variant = 'body',
weight = 'regular',
color = 'primary',
truncate,
href,
...restProps
} = props;
const { classNames, dataAttributes } = useStyles('Link', {
variant,
weight,
color,
});
const { classNames: linkClassNames } = useStyles('Link');
const { classNames: textClassNames, dataAttributes: textDataAttributes } =
useStyles('Text', {
variant,
weight,
color,
});
const isExternal = isExternalLink(href);
@@ -68,9 +71,10 @@ export const Link = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
return (
<AriaLink
ref={ref}
className={clsx(classNames.root, className)}
className={clsx(textClassNames.root, linkClassNames.root, className)}
data-truncate={truncate}
href={href}
{...dataAttributes}
{...textDataAttributes}
{...restProps}
/>
);
@@ -81,9 +85,10 @@ export const Link = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
<RouterProvider navigate={navigate} useHref={useHref}>
<AriaLink
ref={ref}
className={clsx(classNames.root, className)}
className={clsx(textClassNames.root, linkClassNames.root, className)}
data-truncate={truncate}
{...textDataAttributes}
href={href}
{...dataAttributes}
{...restProps}
/>
</RouterProvider>
+2 -61
View File
@@ -20,72 +20,13 @@
margin: 0;
cursor: pointer;
text-decoration-line: none;
display: inline-block;
&: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(--bui-fg-link-hover) 30%,
transparent
);
text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);
}
}
.bui-Link[data-variant='title-large'] {
font-size: var(--bui-font-size-8);
line-height: 140%;
}
.bui-Link[data-variant='title-medium'] {
font-size: var(--bui-font-size-7);
line-height: 140%;
}
.bui-Link[data-variant='title-small'] {
font-size: var(--bui-font-size-6);
line-height: 140%;
}
.bui-Link[data-variant='title-x-small'] {
font-size: var(--bui-font-size-5);
line-height: 140%;
}
.bui-Link[data-variant='body-large'] {
font-size: var(--bui-font-size-4);
line-height: 140%;
}
.bui-Link[data-variant='body-medium'] {
font-size: var(--bui-font-size-3);
line-height: 140%;
}
.bui-Link[data-variant='body-small'] {
font-size: var(--bui-font-size-2);
line-height: 140%;
}
.bui-Link[data-variant='body-x-small'] {
font-size: var(--bui-font-size-1);
line-height: 140%;
}
.bui-Link[data-weight='regular'] {
font-weight: var(--bui-font-weight-regular);
}
.bui-Link[data-weight='bold'] {
font-weight: var(--bui-font-weight-bold);
}
.bui-Link[data-color='primary'] {
color: var(--bui-fg-primary);
}
.bui-Link[data-color='secondary'] {
color: var(--bui-fg-secondary);
}
+13 -27
View File
@@ -14,36 +14,22 @@
* limitations under the License.
*/
import type { Breakpoint } from '../../types';
import type {
Breakpoint,
TextColors,
TextColorStatus,
TextVariants,
TextWeights,
} from '../../types';
import type { LinkProps as AriaLinkProps } from 'react-aria-components';
/** @public */
export interface LinkProps extends AriaLinkProps {
variant?:
| 'title-large'
| 'title-medium'
| 'title-small'
| 'title-x-small'
| 'body-large'
| 'body-medium'
| 'body-small'
| 'body-x-small'
| Partial<
Record<
Breakpoint,
| 'title-large'
| 'title-medium'
| 'title-small'
| 'title-x-small'
| 'body-large'
| 'body-medium'
| 'body-small'
| 'body-x-small'
>
>;
weight?: 'regular' | 'bold' | Partial<Record<Breakpoint, 'regular' | 'bold'>>;
variant?: TextVariants | Partial<Record<Breakpoint, TextVariants>>;
weight?: TextWeights | Partial<Record<Breakpoint, TextWeights>>;
color?:
| 'primary'
| 'secondary'
| Partial<Record<Breakpoint, 'primary' | 'secondary'>>;
| TextColors
| TextColorStatus
| Partial<Record<Breakpoint, TextColors | TextColorStatus>>;
truncate?: boolean;
}
@@ -34,7 +34,7 @@ const TableCellLink = forwardRef<HTMLDivElement, TableCellLinkProps>(
>
{title && <Link href={href}>{title}</Link>}
{description && (
<Text variant="body" color="secondary">
<Text variant="body-medium" color="secondary">
{description}
</Text>
)}
@@ -54,7 +54,7 @@ const TableCellProfile = forwardRef<HTMLDivElement, TableCellProfileProps>(
{name && to ? (
<Link href={to}>{name}</Link>
) : (
<Text variant="body">{name}</Text>
<Text variant="body-medium">{name}</Text>
)}
</div>
);
@@ -31,9 +31,9 @@ const TableCellText = forwardRef<HTMLDivElement, TableCellTextProps>(
className={clsx(classNames.cellText, className)}
{...props}
>
{title && <Text variant="body">{title}</Text>}
{title && <Text variant="body-medium">{title}</Text>}
{description && (
<Text variant="body" color="secondary">
<Text variant="body-medium" color="secondary">
{description}
</Text>
)}
@@ -33,7 +33,6 @@ export const Default: Story = {
args: {
children:
"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?",
style: { maxWidth: '600px' },
},
};
@@ -42,23 +41,54 @@ export const AllVariants: Story = {
...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 gap="6" direction="column">
<Text {...args} variant="title-large" style={{ maxWidth: '1160px' }} />
<Text {...args} variant="title-medium" style={{ maxWidth: '760px' }} />
<Text {...args} variant="title-small" style={{ maxWidth: '580px' }} />
<Text {...args} variant="title-x-small" style={{ maxWidth: '480px' }} />
<Text {...args} variant="body-large" style={{ maxWidth: '380px' }} />
<Text {...args} variant="body-medium" style={{ maxWidth: '320px' }} />
<Text {...args} variant="body-small" style={{ maxWidth: '264px' }} />
<Text {...args} variant="body-x-small" style={{ maxWidth: '224px' }} />
</Flex>
),
};
export const AllWeights: Story = {
args: {
...Default.args,
},
render: args => (
render: () => (
<Flex gap="4" direction="column">
<Text weight="regular" {...args} />
<Text weight="bold" {...args} />
<Flex>
<Text variant="title-large" weight="regular" children="A fox" />
<Text variant="title-large" weight="bold" children="A turtle" />
</Flex>
<Flex>
<Text variant="title-medium" weight="regular" children="A fox" />
<Text variant="title-medium" weight="bold" children="A turtle" />
</Flex>
<Flex>
<Text variant="title-small" weight="regular" children="A fox" />
<Text variant="title-small" weight="bold" children="A turtle" />
</Flex>
<Flex>
<Text variant="title-x-small" weight="regular" children="A fox" />
<Text variant="title-x-small" weight="bold" children="A turtle" />
</Flex>
<Flex>
<Text variant="body-large" weight="regular" children="A fox" />
<Text variant="body-large" weight="bold" children="A turtle" />
</Flex>
<Flex>
<Text variant="body-medium" weight="regular" children="A fox" />
<Text variant="body-medium" weight="bold" children="A turtle" />
</Flex>
<Flex>
<Text variant="body-small" weight="regular" children="A fox" />
<Text variant="body-small" weight="bold" children="A turtle" />
</Flex>
<Flex>
<Text variant="body-x-small" weight="regular" children="A fox" />
<Text variant="body-x-small" weight="bold" children="A turtle" />
</Flex>
</Flex>
),
};
@@ -69,11 +99,11 @@ export const AllColors: Story = {
},
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} />
<Text {...args} color="primary" children="I am primary" />
<Text {...args} color="secondary" children="I am secondary" />
<Text {...args} color="danger" children="I am danger" />
<Text {...args} color="warning" children="I am warning" />
<Text {...args} color="success" children="I am success" />
</Flex>
),
};
@@ -82,6 +112,8 @@ export const Truncate: Story = {
args: {
...Default.args,
truncate: true,
as: 'p',
style: { width: '480px' },
},
};
@@ -89,8 +121,8 @@ export const Responsive: Story = {
args: {
...Default.args,
variant: {
xs: 'label',
md: 'body',
xs: 'title-x-small',
md: 'body-large',
},
},
};
@@ -111,7 +143,7 @@ export const WrappedInLink: Story = {
export const CustomRender: Story = {
args: {
...Default.args,
as: 'span',
as: 'label',
},
};
@@ -119,25 +151,25 @@ export const Playground: Story = {
render: () => (
<Flex gap="4" direction="column">
<Text>Subtitle</Text>
<Text variant="subtitle" style={{ maxWidth: '600px' }}>
<Text variant="title-large" style={{ maxWidth: '600px' }}>
A man looks at a painting in a museum and says, Brothers and sisters I
have none, but that man&apos;s father is my father&apos;s son. Who is
in the painting?
</Text>
<Text>Body</Text>
<Text variant="body" style={{ maxWidth: '600px' }}>
<Text variant="body-medium" style={{ maxWidth: '600px' }}>
A man looks at a painting in a museum and says, Brothers and sisters I
have none, but that man&apos;s father is my father&apos;s son. Who is
in the painting?
</Text>
<Text>Caption</Text>
<Text variant="caption" style={{ maxWidth: '600px' }}>
<Text variant="body-x-small" style={{ maxWidth: '600px' }}>
A man looks at a painting in a museum and says, Brothers and sisters I
have none, but that man&apos;s father is my father&apos;s son. Who is
in the painting?
</Text>
<Text>Label</Text>
<Text variant="label" style={{ maxWidth: '600px' }}>
<Text variant="title-x-small" style={{ maxWidth: '600px' }}>
A man looks at a painting in a museum and says, Brothers and sisters I
have none, but that man&apos;s father is my father&apos;s son. Who is
in the painting?
+4 -3
View File
@@ -20,10 +20,10 @@ import type { ElementType } from 'react';
import type { TextProps } from './types';
import { useStyles } from '../../hooks/useStyles';
function TextComponent<T extends ElementType = 'p'>(
function TextComponent<T extends ElementType = 'span'>(
{
as,
variant = 'body',
variant = 'body-medium',
weight = 'regular',
color = 'primary',
className,
@@ -33,7 +33,7 @@ function TextComponent<T extends ElementType = 'p'>(
}: TextProps<T>,
ref: React.Ref<any>,
) {
const Component = as || 'p';
const Component = as || 'span';
const { classNames, dataAttributes } = useStyles('Text', {
variant,
@@ -46,6 +46,7 @@ function TextComponent<T extends ElementType = 'p'>(
ref={ref}
className={clsx(classNames.root, className)}
data-truncate={truncate}
data-as={as}
{...dataAttributes}
style={style}
{...restProps}
+33 -5
View File
@@ -20,22 +20,42 @@
margin: 0;
}
.bui-Text[data-variant='body'] {
font-size: var(--bui-font-size-3);
.bui-Text[data-variant='title-large'] {
font-size: var(--bui-font-size-8);
line-height: 140%;
}
.bui-Text[data-variant='subtitle'] {
.bui-Text[data-variant='title-medium'] {
font-size: var(--bui-font-size-7);
line-height: 140%;
}
.bui-Text[data-variant='title-small'] {
font-size: var(--bui-font-size-6);
line-height: 140%;
}
.bui-Text[data-variant='title-x-small'] {
font-size: var(--bui-font-size-5);
line-height: 140%;
}
.bui-Text[data-variant='body-large'] {
font-size: var(--bui-font-size-4);
line-height: 140%;
}
.bui-Text[data-variant='caption'] {
.bui-Text[data-variant='body-medium'] {
font-size: var(--bui-font-size-3);
line-height: 140%;
}
.bui-Text[data-variant='body-small'] {
font-size: var(--bui-font-size-2);
line-height: 140%;
}
.bui-Text[data-variant='label'] {
.bui-Text[data-variant='body-x-small'] {
font-size: var(--bui-font-size-1);
line-height: 140%;
}
@@ -73,3 +93,11 @@
text-overflow: ellipsis;
white-space: nowrap;
}
.bui-Text[data-as='span'],
.bui-Text[data-as='label'],
.bui-Text[data-as='strong'],
.bui-Text[data-as='em'],
.bui-Text[data-as='small'] {
display: inline-block;
}
+27 -22
View File
@@ -15,35 +15,40 @@
*/
import type { ElementType, ComponentPropsWithRef } from 'react';
import type { Breakpoint } from '../../types';
import type {
Breakpoint,
TextVariants,
TextWeights,
TextColors,
TextColorStatus,
} from '../../types';
/** @public */
export type TextOwnProps = {
as?: 'p' | 'span' | 'label';
variant?:
| 'subtitle'
| 'body'
| 'caption'
as?:
| 'h1'
| 'h2'
| 'h3'
| 'h4'
| 'h5'
| 'h6'
| 'p'
| 'span'
| 'label'
| Partial<Record<Breakpoint, 'subtitle' | 'body' | 'caption' | 'label'>>;
weight?: 'regular' | 'bold' | Partial<Record<Breakpoint, 'regular' | 'bold'>>;
| 'div'
| 'strong'
| 'em'
| 'small'
| 'legend';
variant?: TextVariants | Partial<Record<Breakpoint, TextVariants>>;
weight?: TextWeights | Partial<Record<Breakpoint, TextWeights>>;
color?:
| 'primary'
| 'secondary'
| 'danger'
| 'warning'
| 'success'
| Partial<
Record<
Breakpoint,
'primary' | 'secondary' | 'danger' | 'warning' | 'success'
>
>;
| TextColors
| TextColorStatus
| Partial<Record<Breakpoint, TextColors | TextColorStatus>>;
truncate?: boolean;
className?: string;
style?: React.CSSProperties;
};
/** @public */
export type TextProps<T extends ElementType = 'p'> = TextOwnProps &
export type TextProps<T extends ElementType = 'span'> = TextOwnProps &
Omit<ComponentPropsWithRef<T>, keyof TextOwnProps>;
-1
View File
@@ -30,7 +30,6 @@
@import '../components/Grid/styles.css';
@import '../components/Header/Header.styles.css';
@import '../components/HeaderPage/HeaderPage.styles.css';
@import '../components/Heading/styles.css';
@import '../components/Icon/styles.css';
@import '../components/Link/styles.css';
@import '../components/Menu/Menu.styles.css';
+1 -2
View File
@@ -28,8 +28,6 @@ export * from './components/Box';
export * from './components/Grid';
export * from './components/Flex';
export * from './components/Container';
export * from './components/Text';
export * from './components/Heading';
// UI components
export * from './components/Avatar';
@@ -47,6 +45,7 @@ export * from './components/Checkbox';
export * from './components/RadioGroup';
export * from './components/Table';
export * from './components/Tabs';
export * from './components/Text';
export * from './components/TextField';
export * from './components/Tooltip';
export * from './components/Menu';
+20 -19
View File
@@ -16,25 +16,6 @@
import { componentDefinitions } from './utils/componentDefinitions';
/** @public */
export type AsProps =
| 'div'
| 'span'
| 'p'
| 'article'
| 'section'
| 'main'
| 'nav'
| 'aside'
| 'ul'
| 'ol'
| 'li'
| 'details'
| 'summary'
| 'dd'
| 'dl'
| 'dt';
/** @public */
export type Breakpoint = 'initial' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
@@ -117,6 +98,26 @@ export interface SpaceProps {
py?: Responsive<Space>;
}
/** @public */
export type TextVariants =
| 'title-large'
| 'title-medium'
| 'title-small'
| 'title-x-small'
| 'body-large'
| 'body-medium'
| 'body-small'
| 'body-x-small';
/** @public */
export type TextColors = 'primary' | 'secondary';
/** @public */
export type TextColorStatus = 'danger' | 'warning' | 'success';
/** @public */
export type TextWeights = 'regular' | 'bold';
/** @public */
export interface UtilityProps extends SpaceProps {
alignItems?: Responsive<AlignItems>;