GLDS: Text component (#71439)

This commit is contained in:
Laura Fernández
2023-07-20 12:59:42 +02:00
committed by GitHub
parent 1c3606cebe
commit 47f70bdb00
25 changed files with 185 additions and 242 deletions

View File

@ -125,7 +125,7 @@ export function Drawer({
/> />
</div> </div>
<div className={styles.titleWrapper}> <div className={styles.titleWrapper}>
<Text as="h3" {...titleProps}> <Text element="h3" {...titleProps}>
{title} {title}
</Text> </Text>
{subtitle && <div className={styles.subtitle}>{subtitle}</div>} {subtitle && <div className={styles.subtitle}>{subtitle}</div>}

View File

@ -3,7 +3,7 @@ import React from 'react';
import { StoryExample } from '../../utils/storybook/StoryExample'; import { StoryExample } from '../../utils/storybook/StoryExample';
import { VerticalGroup } from '../Layout/Layout'; import { VerticalGroup } from '../Layout/Layout';
import { P } from '../Text/TextElements'; import { Text } from '../Text/Text';
import { TextLink } from './TextLink'; import { TextLink } from './TextLink';
import mdx from './TextLink.mdx'; import mdx from './TextLink.mdx';
@ -44,13 +44,13 @@ export const Example: StoryFn = (args) => {
return ( return (
<VerticalGroup> <VerticalGroup>
<StoryExample name="This is a 'inline + external' link with the default behaviour"> <StoryExample name="This is a 'inline + external' link with the default behaviour">
<P> <Text element="p">
To get started with a forever free Grafana Cloud account, sign up at &#160; To get started with a forever free Grafana Cloud account, sign up at &#160;
<TextLink href="https://grafana.com/" {...args} inline> <TextLink href="https://grafana.com/" {...args} inline>
grafana.com grafana.com
</TextLink> </TextLink>
. .
</P> </Text>
</StoryExample> </StoryExample>
<StoryExample name="This is a 'standalone + external' link with the default behaviour"> <StoryExample name="This is a 'standalone + external' link with the default behaviour">
<TextLink href="https://grafana.com/docs/grafana/latest/" {...args}> <TextLink href="https://grafana.com/docs/grafana/latest/" {...args}>
@ -58,7 +58,9 @@ export const Example: StoryFn = (args) => {
</TextLink> </TextLink>
</StoryExample> </StoryExample>
<hr /> <hr />
<P>*The examples cannot contemplate an internal link due to conflicts between Storybook and React Router</P> <Text element="p">
*The examples cannot contemplate an internal link due to conflicts between Storybook and React Router
</Text>
</VerticalGroup> </VerticalGroup>
); );
}; };

View File

@ -6,7 +6,6 @@ import { VerticalGroup } from '../Layout/Layout';
import { Text } from './Text'; import { Text } from './Text';
import mdx from './Text.mdx'; import mdx from './Text.mdx';
import { H1, H2, H3, H4, H5, H6, Span, P, Legend, TextModifier } from './TextElements';
const meta: Meta = { const meta: Meta = {
title: 'General/Text', title: 'General/Text',
@ -15,7 +14,6 @@ const meta: Meta = {
docs: { docs: {
page: mdx, page: mdx,
}, },
controls: { exclude: ['as'] },
}, },
argTypes: { argTypes: {
variant: { control: 'select', options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'body', 'bodySmall', undefined] }, variant: { control: 'select', options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'body', 'bodySmall', undefined] },
@ -39,93 +37,73 @@ const meta: Meta = {
], ],
}, },
truncate: { control: 'boolean' }, truncate: { control: 'boolean' },
italic: { control: 'boolean' },
textAlignment: { textAlignment: {
control: 'select', control: 'select',
options: ['inherit', 'initial', 'left', 'right', 'center', 'justify', undefined], options: ['inherit', 'initial', 'left', 'right', 'center', 'justify', undefined],
}, },
}, },
args: {
element: 'h1',
variant: undefined,
weight: 'light',
textAlignment: 'left',
truncate: false,
italic: false,
color: 'primary',
children: `This is an example of a Text component`,
},
}; };
export const Example: StoryFn = () => { export const Example: StoryFn = (args) => {
return ( return (
<VerticalGroup> <VerticalGroup>
<StoryExample name="Header, paragraph, span and legend elements"> <StoryExample name="Header, paragraph and span">
<H1>h1. Heading</H1> <Text {...args} element="h1">
<H2>h2. Heading</H2> This is a header
<H3>h3. Heading</H3> </Text>
<H4>h4. Heading</H4> <Text {...args} element="p">
<H5>h5. Heading</H5> This is a paragraph that contains
<H6>h6. Heading</H6> <Text color="success" italic>
<P>This is a paragraph</P> {' '}
<Legend>This is a legend</Legend> a span element with different color and style{' '}
<Span>This is a span</Span> </Text>
but is comprised within the same block text
</Text>
</StoryExample>
<StoryExample name="Paragraph with truncate set to true and wrapping up a span element">
<Text {...args} element="p" truncate>
This is a paragraph that contains
<Text color="warning" italic>
{' '}
a span element{' '}
</Text>
but has truncate set to true
</Text>
</StoryExample> </StoryExample>
</VerticalGroup> </VerticalGroup>
); );
}; };
Example.parameters = { Example.parameters = {
controls: { controls: {
exclude: ['variant', 'weight', 'textAlignment', 'truncate', 'color', 'children'], exclude: ['element', 'variant', 'weight', 'textAlignment', 'truncate', 'italic', 'color', 'children'],
}, },
}; };
export const HeadingComponent: StoryFn = (args) => { export const Basic: StoryFn = (args) => {
return ( return (
<div style={{ width: '300px' }}> <div style={{ width: '300px' }}>
<H1 variant={args.variant} weight={args.weight} textAlignment={args.textAlignment} {...args}> <Text
element={args.element}
variant={args.variant}
weight={args.weight}
textAlignment={args.textAlignment}
{...args}
>
{args.children} {args.children}
</H1> </Text>
</div> </div>
); );
}; };
HeadingComponent.args = {
variant: undefined,
weight: 'light',
textAlignment: 'center',
truncate: false,
color: 'primary',
children: 'This is a H1 component',
};
export const LegendComponent: StoryFn = (args) => {
return (
<div style={{ width: '300px' }}>
<Legend variant={args.variant} weight={args.weight} textAlignment={args.textAlignment} {...args}>
{args.children}
</Legend>
</div>
);
};
LegendComponent.args = {
variant: undefined,
weight: 'bold',
textAlignment: 'center',
truncate: false,
color: 'error',
children: 'This is a lengend component',
};
export const TextModifierComponent: StoryFn = (args) => {
return (
<div style={{ width: '300px' }}>
<H6 variant={args.variant} weight={args.weight} textAlignment={args.textAlignment} {...args}>
{args.children}{' '}
<TextModifier weight="bold" color="error">
{' '}
with a part of its text modified{' '}
</TextModifier>
</H6>
</div>
);
};
TextModifierComponent.args = {
variant: undefined,
weight: 'light',
textAlignment: 'center',
truncate: false,
color: 'maxContrast',
children: 'This is a H6 component',
};
export default meta; export default meta;

View File

@ -7,13 +7,13 @@ import { Text } from './Text';
describe('Text', () => { describe('Text', () => {
it('renders correctly', () => { it('renders correctly', () => {
render(<Text as={'h1'}>This is a text component</Text>); render(<Text element={'h1'}>This is a text component</Text>);
expect(screen.getByText('This is a text component')).toBeInTheDocument(); expect(screen.getByText('This is a text component')).toBeInTheDocument();
}); });
it('keeps the element type but changes its styles', () => { it('keeps the element type but changes its styles', () => {
const customVariant: keyof ThemeTypographyVariantTypes = 'body'; const customVariant: keyof ThemeTypographyVariantTypes = 'body';
render( render(
<Text as={'h1'} variant={customVariant}> <Text element={'h1'} variant={customVariant}>
This is a text component This is a text component
</Text> </Text>
); );
@ -26,7 +26,7 @@ describe('Text', () => {
const customColor = 'info'; const customColor = 'info';
const theme = createTheme(); const theme = createTheme();
render( render(
<Text as={'h1'} color={customColor}> <Text element={'h1'} color={customColor}>
This is a text component This is a text component
</Text> </Text>
); );

View File

@ -5,11 +5,11 @@ import { GrafanaTheme2, ThemeTypographyVariantTypes } from '@grafana/data';
import { useStyles2 } from '../../themes'; import { useStyles2 } from '../../themes';
import { customWeight, customColor } from './utils'; import { customWeight, customColor, customVariant } from './utils';
export interface TextProps { export interface TextProps {
/** Defines what HTML element is defined underneath */ /** Defines what HTML element is defined underneath. "span" by default */
as: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span' | 'p' | 'legend'; element?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span' | 'p';
/** What typograpy variant should be used for the component. Only use if default variant for the defined element is not what is needed */ /** What typograpy variant should be used for the component. Only use if default variant for the defined element is not what is needed */
variant?: keyof ThemeTypographyVariantTypes; variant?: keyof ThemeTypographyVariantTypes;
/** Override the default weight for the used variant */ /** Override the default weight for the used variant */
@ -18,22 +18,24 @@ export interface TextProps {
color?: keyof GrafanaTheme2['colors']['text'] | 'error' | 'success' | 'warning' | 'info'; color?: keyof GrafanaTheme2['colors']['text'] | 'error' | 'success' | 'warning' | 'info';
/** Use to cut the text off with ellipsis if there isn't space to show all of it. On hover shows the rest of the text */ /** Use to cut the text off with ellipsis if there isn't space to show all of it. On hover shows the rest of the text */
truncate?: boolean; truncate?: boolean;
/** If true, show the text as italic. False by default */
italic?: boolean;
/** Whether to align the text to left, center or right */ /** Whether to align the text to left, center or right */
textAlignment?: CSSProperties['textAlign']; textAlignment?: CSSProperties['textAlign'];
children: React.ReactNode; children: React.ReactNode;
} }
export const Text = React.forwardRef<HTMLElement, TextProps>( export const Text = React.forwardRef<HTMLElement, TextProps>(
({ as, variant, weight, color, truncate, textAlignment, children }, ref) => { ({ element = 'span', variant, weight, color, truncate, italic, textAlignment, children }, ref) => {
const styles = useStyles2( const styles = useStyles2(
useCallback( useCallback(
(theme) => getTextStyles(theme, variant, color, weight, truncate, textAlignment), (theme) => getTextStyles(theme, element, variant, color, weight, truncate, italic, textAlignment),
[color, textAlignment, truncate, weight, variant] [color, textAlignment, truncate, italic, weight, variant, element]
) )
); );
return createElement( return createElement(
as, element,
{ {
className: styles, className: styles,
ref, ref,
@ -47,19 +49,22 @@ Text.displayName = 'Text';
const getTextStyles = ( const getTextStyles = (
theme: GrafanaTheme2, theme: GrafanaTheme2,
element?: TextProps['element'],
variant?: keyof ThemeTypographyVariantTypes, variant?: keyof ThemeTypographyVariantTypes,
color?: TextProps['color'], color?: TextProps['color'],
weight?: TextProps['weight'], weight?: TextProps['weight'],
truncate?: TextProps['truncate'], truncate?: TextProps['truncate'],
italic?: TextProps['italic'],
textAlignment?: TextProps['textAlignment'] textAlignment?: TextProps['textAlignment']
) => { ) => {
return css([ return css([
variant && {
...theme.typography[variant],
},
{ {
margin: 0, margin: 0,
padding: 0, padding: 0,
...customVariant(theme, element, variant),
},
variant && {
...theme.typography[variant],
}, },
color && { color && {
color: customColor(color, theme), color: customColor(color, theme),
@ -72,6 +77,9 @@ const getTextStyles = (
textOverflow: 'ellipsis', textOverflow: 'ellipsis',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
}, },
italic && {
fontStyle: 'italic',
},
textAlignment && { textAlignment && {
textAlign: textAlignment, textAlign: textAlignment,
}, },

View File

@ -1,75 +0,0 @@
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Text, TextProps } from './Text';
interface TextElementsProps extends Omit<TextProps, 'as'> {}
interface TextModifierProps {
/** Override the default weight for the used variant */
weight?: 'light' | 'regular' | 'medium' | 'bold';
/** Color to use for text */
color?: keyof GrafanaTheme2['colors']['text'] | 'error' | 'success' | 'warning' | 'info';
children: React.ReactNode;
}
export const H1 = React.forwardRef<HTMLHeadingElement, TextElementsProps>((props, ref) => {
return <Text as="h1" {...props} variant={props.variant || 'h1'} ref={ref} />;
});
H1.displayName = 'H1';
export const H2 = React.forwardRef<HTMLHeadingElement, TextElementsProps>((props, ref) => {
return <Text as="h2" {...props} variant={props.variant || 'h2'} ref={ref} />;
});
H2.displayName = 'H2';
export const H3 = React.forwardRef<HTMLHeadingElement, TextElementsProps>((props, ref) => {
return <Text as="h3" {...props} variant={props.variant || 'h3'} ref={ref} />;
});
H3.displayName = 'H3';
export const H4 = React.forwardRef<HTMLHeadingElement, TextElementsProps>((props, ref) => {
return <Text as="h4" {...props} variant={props.variant || 'h4'} ref={ref} />;
});
H4.displayName = 'H4';
export const H5 = React.forwardRef<HTMLHeadingElement, TextElementsProps>((props, ref) => {
return <Text as="h5" {...props} variant={props.variant || 'h5'} ref={ref} />;
});
H5.displayName = 'H5';
export const H6 = React.forwardRef<HTMLHeadingElement, TextElementsProps>((props, ref) => {
return <Text as="h6" {...props} variant={props.variant || 'h6'} ref={ref} />;
});
H6.displayName = 'H6';
export const P = React.forwardRef<HTMLParagraphElement, TextElementsProps>((props, ref) => {
return <Text as="p" {...props} variant={props.variant || 'body'} ref={ref} />;
});
P.displayName = 'P';
export const Span = React.forwardRef<HTMLSpanElement, TextElementsProps>((props, ref) => {
return <Text as="span" {...props} variant={props.variant || 'bodySmall'} ref={ref} />;
});
Span.displayName = 'Span';
export const Legend = React.forwardRef<HTMLLegendElement, TextElementsProps>((props, ref) => {
return <Text as="legend" {...props} variant={props.variant || 'bodySmall'} ref={ref} />;
});
Legend.displayName = 'Legend';
export const TextModifier = React.forwardRef<HTMLSpanElement, TextModifierProps>((props, ref) => {
return <Text as="span" {...props} ref={ref} />;
});
TextModifier.displayName = 'TextModifier';

View File

@ -1,4 +1,4 @@
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2, ThemeTypographyVariantTypes } from '@grafana/data';
import { TextProps } from './Text'; import { TextProps } from './Text';
@ -30,3 +30,32 @@ export const customColor = (color: TextProps['color'], theme: GrafanaTheme2): st
return color ? theme.colors.text[color] : undefined; return color ? theme.colors.text[color] : undefined;
} }
}; };
export const customVariant = (
theme: GrafanaTheme2,
element: TextProps['element'],
variant?: keyof ThemeTypographyVariantTypes
) => {
if (variant) {
return theme.typography[variant];
}
switch (element) {
//Span elements does not have a default variant to be able to take the parents style
case 'span':
return;
case 'h1':
return theme.typography.h1;
case 'h2':
return theme.typography.h2;
case 'h3':
return theme.typography.h3;
case 'h4':
return theme.typography.h4;
case 'h5':
return theme.typography.h5;
case 'h6':
return theme.typography.h6;
default:
return theme.typography.body;
}
};

View File

@ -9,4 +9,4 @@
* be subject to the standard policies * be subject to the standard policies
*/ */
export * from './components/Text/TextElements'; export * from './components/Text/Text';

View File

@ -5,7 +5,7 @@ import { useToggle } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { Button, Drawer, ToolbarButton, useStyles2 } from '@grafana/ui'; import { Button, Drawer, ToolbarButton, useStyles2 } from '@grafana/ui';
import { H3 } from '@grafana/ui/src/unstable'; import { Text } from '@grafana/ui/src/unstable';
import { t } from 'app/core/internationalization'; import { t } from 'app/core/internationalization';
import { DEFAULT_FEED_URL } from 'app/plugins/panel/news/constants'; import { DEFAULT_FEED_URL } from 'app/plugins/panel/news/constants';
@ -26,7 +26,7 @@ export function NewsContainer({ className }: NewsContainerProps) {
<Drawer <Drawer
title={ title={
<div className={styles.title}> <div className={styles.title}>
<H3>{t('news.title', 'Latest from the blog')}</H3> <Text element="h3">{t('news.title', 'Latest from the blog')}</Text>
<a <a
href="https://grafana.com/blog/" href="https://grafana.com/blog/"
target="_blank" target="_blank"

View File

@ -209,9 +209,8 @@ function Row({ index, style: virtualStyles, data }: RowProps) {
)} )}
<label className={styles.label} id={labelId}> <label className={styles.label} id={labelId}>
<Text as="span" truncate> {/* TODO: text is not truncated properly, it still overflows the container */}
{item.title} <Text truncate>{item.title}</Text>
</Text>
</label> </label>
</div> </div>
</div> </div>

View File

@ -7,7 +7,7 @@ import { useAsync } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { Alert, Button, Icon, Input, LoadingBar, useStyles2 } from '@grafana/ui'; import { Alert, Button, Icon, Input, LoadingBar, useStyles2 } from '@grafana/ui';
import { Text } from '@grafana/ui/src/components/Text/Text'; import { Text } from '@grafana/ui/src/components/Text/Text';
import { Trans, t } from 'app/core/internationalization'; import { t, Trans } from 'app/core/internationalization';
import { skipToken, useGetFolderQuery } from 'app/features/browse-dashboards/api/browseDashboardsAPI'; import { skipToken, useGetFolderQuery } from 'app/features/browse-dashboards/api/browseDashboardsAPI';
import { PAGE_SIZE } from 'app/features/browse-dashboards/api/services'; import { PAGE_SIZE } from 'app/features/browse-dashboards/api/services';
import { import {
@ -209,7 +209,7 @@ export function NestedFolderPicker({ value, onChange }: NestedFolderPickerProps)
{selectedFolder.isLoading ? ( {selectedFolder.isLoading ? (
<Skeleton width={100} /> <Skeleton width={100} />
) : ( ) : (
<Text as="span" truncate> <Text truncate>
{label ?? <Trans i18nKey="browse-dashboards.folder-picker.button-label">Select folder</Trans>} {label ?? <Trans i18nKey="browse-dashboards.folder-picker.button-label">Select folder</Trans>}
</Text> </Text>
)} )}

View File

@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { isFetchError } from '@grafana/runtime'; import { isFetchError } from '@grafana/runtime';
import { Field, IconButton, Input, useStyles2 } from '@grafana/ui'; import { Field, IconButton, Input, useStyles2 } from '@grafana/ui';
import { H1 } from '@grafana/ui/src/unstable'; import { Text } from '@grafana/ui/src/unstable';
export interface Props { export interface Props {
value: string; value: string;
@ -61,7 +61,9 @@ export const EditableTitle = ({ value, onEdit }: Props) => {
this is to prevent the title from flickering back to the old value after the user has edited this is to prevent the title from flickering back to the old value after the user has edited
caused by the delay between the save completing and the new value being refetched caused by the delay between the save completing and the new value being refetched
*/} */}
<H1 truncate>{localValue}</H1> <Text element="h1" truncate>
{localValue}
</Text>
<IconButton name="pen" size="lg" tooltip="Edit title" onClick={() => setIsEditing(true)} /> <IconButton name="pen" size="lg" tooltip="Edit title" onClick={() => setIsEditing(true)} />
</div> </div>
</div> </div>

View File

@ -3,11 +3,11 @@ import React, { ComponentProps, HTMLAttributes } from 'react';
import { Stack } from '@grafana/experimental'; import { Stack } from '@grafana/experimental';
import { Icon, IconName, useStyles2 } from '@grafana/ui'; import { Icon, IconName, useStyles2 } from '@grafana/ui';
import { Span } from '@grafana/ui/src/unstable'; import { Text } from '@grafana/ui/src/unstable';
interface Props extends HTMLAttributes<HTMLDivElement> { interface Props extends HTMLAttributes<HTMLDivElement> {
icon?: IconName; icon?: IconName;
color?: ComponentProps<typeof Span>['color']; color?: ComponentProps<typeof Text>['color'];
} }
const MetaText = ({ children, icon, color = 'secondary', ...rest }: Props) => { const MetaText = ({ children, icon, color = 'secondary', ...rest }: Props) => {
@ -22,12 +22,12 @@ const MetaText = ({ children, icon, color = 'secondary', ...rest }: Props) => {
// allow passing ARIA and data- attributes // allow passing ARIA and data- attributes
{...rest} {...rest}
> >
<Span variant="bodySmall" color={color}> <Text color={color}>
<Stack direction="row" alignItems="center" gap={0.5}> <Stack direction="row" alignItems="center" gap={0.5}>
{icon && <Icon name={icon} />} {icon && <Icon name={icon} />}
{children} {children}
</Stack> </Stack>
</Span> </Text>
</div> </div>
); );
}; };

View File

@ -4,7 +4,7 @@ import React from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { Stack } from '@grafana/experimental'; import { Stack } from '@grafana/experimental';
import { Button, Dropdown, Icon, Menu, Tooltip, useStyles2 } from '@grafana/ui'; import { Button, Dropdown, Icon, Menu, Tooltip, useStyles2 } from '@grafana/ui';
import { Span } from '@grafana/ui/src/unstable'; import { Text } from '@grafana/ui/src/unstable';
import ConditionalWrap from 'app/features/alerting/components/ConditionalWrap'; import ConditionalWrap from 'app/features/alerting/components/ConditionalWrap';
import { GrafanaNotifierType } from 'app/types/alerting'; import { GrafanaNotifierType } from 'app/types/alerting';
@ -104,7 +104,7 @@ const ContactPointHeader = (props: ContactPointHeaderProps) => {
<div className={styles.headerWrapper}> <div className={styles.headerWrapper}>
<Stack direction="row" alignItems="center" gap={1}> <Stack direction="row" alignItems="center" gap={1}>
<Stack alignItems="center" gap={1}> <Stack alignItems="center" gap={1}>
<Span variant="body">{name}</Span> <Text variant="body">{name}</Text>
</Stack> </Stack>
{policies.length > 0 ? ( {policies.length > 0 ? (
<MetaText> <MetaText>
@ -179,14 +179,14 @@ const ContactPointReceiver = (props: ContactPointReceiverProps) => {
<Stack direction="row" alignItems="center" gap={1}> <Stack direction="row" alignItems="center" gap={1}>
<Stack direction="row" alignItems="center" gap={0.5}> <Stack direction="row" alignItems="center" gap={0.5}>
{iconName && <Icon name={iconName} />} {iconName && <Icon name={iconName} />}
<Span variant="body" color="primary"> <Text variant="body" color="primary">
{type} {type}
</Span> </Text>
</Stack> </Stack>
{description && ( {description && (
<Span variant="bodySmall" color="secondary"> <Text variant="bodySmall" color="secondary">
{description} {description}
</Span> </Text>
)} )}
</Stack> </Stack>
</div> </div>
@ -196,7 +196,7 @@ const ContactPointReceiver = (props: ContactPointReceiverProps) => {
<> <>
{/* TODO we might need an error variant for MetaText, dito for success */} {/* TODO we might need an error variant for MetaText, dito for success */}
{/* TODO show error details on hover or elsewhere */} {/* TODO show error details on hover or elsewhere */}
<Span color="error" variant="bodySmall" weight="bold"> <Text color="error" variant="bodySmall" weight="bold">
<Stack direction="row" alignItems={'center'} gap={0.5}> <Stack direction="row" alignItems={'center'} gap={0.5}>
<Tooltip <Tooltip
content={ content={
@ -208,7 +208,7 @@ const ContactPointReceiver = (props: ContactPointReceiverProps) => {
</span> </span>
</Tooltip> </Tooltip>
</Stack> </Stack>
</Span> </Text>
</> </>
) : ( ) : (
<> <>

View File

@ -7,7 +7,7 @@ import { Link } from 'react-router-dom';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { Stack } from '@grafana/experimental'; import { Stack } from '@grafana/experimental';
import { Badge, Button, Dropdown, getTagColorsFromName, Icon, Menu, Tooltip, useStyles2 } from '@grafana/ui'; import { Badge, Button, Dropdown, getTagColorsFromName, Icon, Menu, Tooltip, useStyles2 } from '@grafana/ui';
import { Span } from '@grafana/ui/src/unstable'; import { Text } from '@grafana/ui/src/unstable';
import { contextSrv } from 'app/core/core'; import { contextSrv } from 'app/core/core';
import ConditionalWrap from 'app/features/alerting/components/ConditionalWrap'; import ConditionalWrap from 'app/features/alerting/components/ConditionalWrap';
import { RouteWithID, Receiver, ObjectMatcher, AlertmanagerGroup } from 'app/plugins/datasource/alertmanager/types'; import { RouteWithID, Receiver, ObjectMatcher, AlertmanagerGroup } from 'app/plugins/datasource/alertmanager/types';
@ -554,17 +554,17 @@ const routePropertyToValue = (
if (isNotGrouping) { if (isNotGrouping) {
return ( return (
<Span variant="bodySmall" color="secondary"> <Text variant="bodySmall" color="secondary">
Not grouping Not grouping
</Span> </Text>
); );
} }
if (isSingleGroup) { if (isSingleGroup) {
return ( return (
<Span variant="bodySmall" color="secondary"> <Text variant="bodySmall" color="secondary">
Single group Single group
</Span> </Text>
); );
} }

View File

@ -4,7 +4,7 @@ import React, { lazy, Suspense } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { Button, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; import { Button, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
import { H4 } from '@grafana/ui/src/unstable'; import { Text } from '@grafana/ui/src/unstable';
import { alertRuleApi } from 'app/features/alerting/unified/api/alertRuleApi'; import { alertRuleApi } from 'app/features/alerting/unified/api/alertRuleApi';
import { Stack } from 'app/plugins/datasource/parca/QueryEditor/Stack'; import { Stack } from 'app/plugins/datasource/parca/QueryEditor/Stack';
import { AlertQuery } from 'app/types/unified-alerting-dto'; import { AlertQuery } from 'app/types/unified-alerting-dto';
@ -67,7 +67,7 @@ export const NotificationPreview = ({
<Stack direction="column" gap={2}> <Stack direction="column" gap={2}>
<div className={styles.routePreviewHeaderRow}> <div className={styles.routePreviewHeaderRow}>
<div className={styles.previewHeader}> <div className={styles.previewHeader}>
<H4>Alert instance routing preview</H4> <Text element="h4">Alert instance routing preview</Text>
</div> </div>
<div className={styles.button}> <div className={styles.button}>
<Button icon="sync" variant="secondary" type="button" onClick={onPreview}> <Button icon="sync" variant="secondary" type="button" onClick={onPreview}>

View File

@ -8,7 +8,7 @@ import { selectors } from '@grafana/e2e-selectors';
import { Stack } from '@grafana/experimental'; import { Stack } from '@grafana/experimental';
import { config, getDataSourceSrv } from '@grafana/runtime'; import { config, getDataSourceSrv } from '@grafana/runtime';
import { Alert, Button, Dropdown, Field, Icon, InputControl, Menu, MenuItem, Tooltip, useStyles2 } from '@grafana/ui'; import { Alert, Button, Dropdown, Field, Icon, InputControl, Menu, MenuItem, Tooltip, useStyles2 } from '@grafana/ui';
import { H5 } from '@grafana/ui/src/unstable'; import { Text } from '@grafana/ui/src/components/Text/Text';
import { isExpressionQuery } from 'app/features/expressions/guards'; import { isExpressionQuery } from 'app/features/expressions/guards';
import { ExpressionDatasourceUID, ExpressionQueryType, expressionTypes } from 'app/features/expressions/types'; import { ExpressionDatasourceUID, ExpressionQueryType, expressionTypes } from 'app/features/expressions/types';
import { useDispatch } from 'app/types'; import { useDispatch } from 'app/types';
@ -435,8 +435,8 @@ export const QueryAndExpressionsStep = ({ editingExistingRule, onDataChange }: P
onClickSwitch={onClickSwitch} onClickSwitch={onClickSwitch}
/> />
{/* Expression Queries */} {/* Expression Queries */}
<H5>Expressions</H5> <Text element="h5">Expressions</Text>
<div className={styles.mutedText}>Manipulate data returned from queries with math and other operations.</div> <div className={styles.mutedText}>Manipulate data returned from queries with math and other operations</div>
<ExpressionsEditor <ExpressionsEditor
queries={queries} queries={queries}
panelData={queryPreviewData} panelData={queryPreviewData}

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { Stack } from '@grafana/experimental'; import { Stack } from '@grafana/experimental';
import { Alert, Button, Icon, LoadingPlaceholder, Tab, TabContent, TabsBar } from '@grafana/ui'; import { Alert, Button, Icon, LoadingPlaceholder, Tab, TabContent, TabsBar } from '@grafana/ui';
import { H1, Span } from '@grafana/ui/src/unstable'; import { Text } from '@grafana/ui/src/unstable';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { GrafanaAlertState } from 'app/types/unified-alerting-dto'; import { GrafanaAlertState } from 'app/types/unified-alerting-dto';
@ -129,18 +129,18 @@ interface BreadcrumbProps {
const BreadCrumb = ({ folder, evaluationGroup }: BreadcrumbProps) => ( const BreadCrumb = ({ folder, evaluationGroup }: BreadcrumbProps) => (
<Stack alignItems="center" gap={0.5}> <Stack alignItems="center" gap={0.5}>
<Span color="secondary"> <Text color="secondary">
<Icon name="folder" /> <Icon name="folder" />
</Span> </Text>
<Span variant="body" color="primary"> <Text variant="body" color="primary">
{folder} {folder}
</Span> </Text>
<Span variant="body" color="secondary"> <Text variant="body" color="secondary">
<Icon name="angle-right" /> <Icon name="angle-right" />
</Span> </Text>
<Span variant="body" color="primary"> <Text variant="body" color="primary">
{evaluationGroup} {evaluationGroup}
</Span> </Text>
</Stack> </Stack>
); );
@ -154,9 +154,9 @@ const Title = ({ name, state }: TitleProps) => (
<Stack alignItems={'center'} gap={1}> <Stack alignItems={'center'} gap={1}>
<AlertStateDot size="md" state={state} /> <AlertStateDot size="md" state={state} />
{/* <Button variant="secondary" fill="outline" icon="angle-left" /> */} {/* <Button variant="secondary" fill="outline" icon="angle-left" /> */}
<H1 variant="h2" weight="bold"> <Text element="h1" variant="h2" weight="bold">
{name} {name}
</H1> </Text>
{/* <Badge color="red" text={state} icon="exclamation-circle" /> */} {/* <Badge color="red" text={state} icon="exclamation-circle" /> */}
</Stack> </Stack>
</header> </header>
@ -167,9 +167,9 @@ interface SummaryProps {
} }
const Summary = ({ text }: SummaryProps) => ( const Summary = ({ text }: SummaryProps) => (
<Span variant="body" color="secondary"> <Text variant="body" color="secondary">
{text} {text}
</Span> </Text>
); );
export default RuleViewer; export default RuleViewer;

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { Space } from '@grafana/experimental'; import { Space } from '@grafana/experimental';
import { ConfirmModal } from '@grafana/ui'; import { ConfirmModal } from '@grafana/ui';
import { P } from '@grafana/ui/src/unstable'; import { Text } from '@grafana/ui/src/unstable';
import { Trans, t } from 'app/core/internationalization'; import { Trans, t } from 'app/core/internationalization';
import { DashboardTreeSelection } from '../../types'; import { DashboardTreeSelection } from '../../types';
@ -33,11 +33,11 @@ export const DeleteModal = ({ onConfirm, onDismiss, selectedItems, ...props }: P
<ConfirmModal <ConfirmModal
body={ body={
<> <>
<P> <Text element="p">
<Trans i18nKey="browse-dashboards.action.delete-modal-text"> <Trans i18nKey="browse-dashboards.action.delete-modal-text">
This action will delete the following content: This action will delete the following content:
</Trans> </Trans>
</P> </Text>
<DescendantCount selectedItems={selectedItems} /> <DescendantCount selectedItems={selectedItems} />
<Space v={2} /> <Space v={2} />
</> </>

View File

@ -2,7 +2,7 @@ import React from 'react';
import Skeleton from 'react-loading-skeleton'; import Skeleton from 'react-loading-skeleton';
import { Alert } from '@grafana/ui'; import { Alert } from '@grafana/ui';
import { P } from '@grafana/ui/src/unstable'; import { Text } from '@grafana/ui/src/unstable';
import { useGetAffectedItemsQuery } from '../../api/browseDashboardsAPI'; import { useGetAffectedItemsQuery } from '../../api/browseDashboardsAPI';
import { DashboardTreeSelection } from '../../types'; import { DashboardTreeSelection } from '../../types';
@ -18,10 +18,10 @@ export const DescendantCount = ({ selectedItems }: Props) => {
return ( return (
<> <>
<P color="secondary"> <Text element="p" color="secondary">
{data && buildBreakdownString(data.folder, data.dashboard, data.libraryPanel, data.alertRule)} {data && buildBreakdownString(data.folder, data.dashboard, data.libraryPanel, data.alertRule)}
{(isFetching || isLoading) && <Skeleton width={200} />} {(isFetching || isLoading) && <Skeleton width={200} />}
</P> </Text>
{error && <Alert severity="error" title="Unable to retrieve descendant information" />} {error && <Alert severity="error" title="Unable to retrieve descendant information" />}
</> </>
); );

View File

@ -3,7 +3,7 @@ import React, { useState } from 'react';
import { Space } from '@grafana/experimental'; import { Space } from '@grafana/experimental';
import { config } from '@grafana/runtime'; import { config } from '@grafana/runtime';
import { Alert, Button, Field, Modal } from '@grafana/ui'; import { Alert, Button, Field, Modal } from '@grafana/ui';
import { P } from '@grafana/ui/src/unstable'; import { Text } from '@grafana/ui/src/unstable';
import { NestedFolderPicker } from 'app/core/components/NestedFolderPicker/NestedFolderPicker'; import { NestedFolderPicker } from 'app/core/components/NestedFolderPicker/NestedFolderPicker';
import { FolderChange } from 'app/core/components/NestedFolderPicker/types'; import { FolderChange } from 'app/core/components/NestedFolderPicker/types';
import { FolderPicker } from 'app/core/components/Select/FolderPicker'; import { FolderPicker } from 'app/core/components/Select/FolderPicker';
@ -51,9 +51,9 @@ export const MoveModal = ({ onConfirm, onDismiss, selectedItems, ...props }: Pro
/> />
)} )}
<P> <Text element="p">
<Trans i18nKey="browse-dashboards.action.move-modal-text">This action will move the following content:</Trans> <Trans i18nKey="browse-dashboards.action.move-modal-text">This action will move the following content:</Trans>
</P> </Text>
<DescendantCount selectedItems={selectedItems} /> <DescendantCount selectedItems={selectedItems} />

View File

@ -7,7 +7,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime'; import { reportInteraction } from '@grafana/runtime';
import { Icon, IconButton, Link, Spinner, useStyles2 } from '@grafana/ui'; import { Icon, IconButton, Link, Spinner, useStyles2 } from '@grafana/ui';
import { getSvgSize } from '@grafana/ui/src/components/Icon/utils'; import { getSvgSize } from '@grafana/ui/src/components/Icon/utils';
import { Span } from '@grafana/ui/src/unstable'; import { Text } from '@grafana/ui/src/unstable';
import { getIconForKind } from 'app/features/search/service/utils'; import { getIconForKind } from 'app/features/search/service/utils';
import { useChildrenByParentUIDState } from '../state'; import { useChildrenByParentUIDState } from '../state';
@ -36,9 +36,9 @@ export function NameCell({ row: { original: data }, onFolderClick }: NameCellPro
<span className={styles.folderButtonSpacer} /> <span className={styles.folderButtonSpacer} />
{item.uiKind === 'empty-folder' ? ( {item.uiKind === 'empty-folder' ? (
<em className={styles.emptyText}> <em className={styles.emptyText}>
<Span variant="body" color="secondary" truncate> <Text variant="body" color="secondary" truncate>
No items No items
</Span> </Text>
</em> </em>
) : ( ) : (
<Skeleton width={200} /> <Skeleton width={200} />
@ -66,7 +66,7 @@ export function NameCell({ row: { original: data }, onFolderClick }: NameCellPro
)} )}
<div className={styles.iconNameContainer}> <div className={styles.iconNameContainer}>
{isLoading ? <Spinner size={ICON_SIZE} /> : <Icon size={ICON_SIZE} name={iconName} />} {isLoading ? <Spinner size={ICON_SIZE} /> : <Icon size={ICON_SIZE} name={iconName} />}
<Span variant="body" truncate> <Text variant="body" truncate>
{item.url ? ( {item.url ? (
<Link <Link
onClick={() => { onClick={() => {
@ -80,7 +80,7 @@ export function NameCell({ row: { original: data }, onFolderClick }: NameCellPro
) : ( ) : (
item.title item.title
)} )}
</Span> </Text>
</div> </div>
</> </>
); );

View File

@ -5,7 +5,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { config, locationService, reportInteraction } from '@grafana/runtime'; import { config, locationService, reportInteraction } from '@grafana/runtime';
import { Button, useStyles2 } from '@grafana/ui'; import { Button, useStyles2 } from '@grafana/ui';
import { H1, H3, P } from '@grafana/ui/src/unstable'; import { Text } from '@grafana/ui/src/unstable';
import { Trans } from 'app/core/internationalization'; import { Trans } from 'app/core/internationalization';
import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardModel } from 'app/features/dashboard/state';
import { onAddLibraryPanel, onCreateNewPanel, onCreateNewRow } from 'app/features/dashboard/utils/dashboard'; import { onAddLibraryPanel, onCreateNewPanel, onCreateNewRow } from 'app/features/dashboard/utils/dashboard';
@ -28,19 +28,19 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
<div className={cx(styles.centeredContent, styles.wrapper)}> <div className={cx(styles.centeredContent, styles.wrapper)}>
<div className={cx(styles.containerBox, styles.centeredContent, styles.visualizationContainer)}> <div className={cx(styles.containerBox, styles.centeredContent, styles.visualizationContainer)}>
<div className={styles.headerBig}> <div className={styles.headerBig}>
<H1 textAlignment="center" weight="medium"> <Text element="h1" textAlignment="center" weight="medium">
<Trans i18nKey="dashboard.empty.add-visualization-header"> <Trans i18nKey="dashboard.empty.add-visualization-header">
Start your new dashboard by adding a visualization Start your new dashboard by adding a visualization
</Trans> </Trans>
</H1> </Text>
</div> </div>
<div className={styles.bodyBig}> <div className={styles.bodyBig}>
<P textAlignment="center" color="secondary"> <Text element="p" textAlignment="center" color="secondary">
<Trans i18nKey="dashboard.empty.add-visualization-body"> <Trans i18nKey="dashboard.empty.add-visualization-body">
Select a data source and then query and visualize your data with charts, stats and tables or create Select a data source and then query and visualize your data with charts, stats and tables or create
lists, markdowns and other widgets. lists, markdowns and other widgets.
</Trans> </Trans>
</P> </Text>
</div> </div>
<Button <Button
size="lg" size="lg"
@ -61,14 +61,14 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
{config.featureToggles.vizAndWidgetSplit && ( {config.featureToggles.vizAndWidgetSplit && (
<div className={cx(styles.containerBox, styles.centeredContent, styles.widgetContainer)}> <div className={cx(styles.containerBox, styles.centeredContent, styles.widgetContainer)}>
<div className={styles.headerSmall}> <div className={styles.headerSmall}>
<H3 textAlignment="center" weight="medium"> <Text element="h3" textAlignment="center" weight="medium">
<Trans i18nKey="dashboard.empty.add-widget-header">Add a widget</Trans> <Trans i18nKey="dashboard.empty.add-widget-header">Add a widget</Trans>
</H3> </Text>
</div> </div>
<div className={styles.bodySmall}> <div className={styles.bodySmall}>
<P textAlignment="center" color="secondary"> <Text element="p" textAlignment="center" color="secondary">
<Trans i18nKey="dashboard.empty.add-widget-body">Create lists, markdowns and other widgets</Trans> <Trans i18nKey="dashboard.empty.add-widget-body">Create lists, markdowns and other widgets</Trans>
</P> </Text>
</div> </div>
<Button <Button
icon="plus" icon="plus"
@ -86,16 +86,16 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
)} )}
<div className={cx(styles.containerBox, styles.centeredContent, styles.rowContainer)}> <div className={cx(styles.containerBox, styles.centeredContent, styles.rowContainer)}>
<div className={styles.headerSmall}> <div className={styles.headerSmall}>
<H3 textAlignment="center" weight="medium"> <Text element="h3" textAlignment="center" weight="medium">
<Trans i18nKey="dashboard.empty.add-row-header">Add a row</Trans> <Trans i18nKey="dashboard.empty.add-row-header">Add a row</Trans>
</H3> </Text>
</div> </div>
<div className={styles.bodySmall}> <div className={styles.bodySmall}>
<P textAlignment="center" color="secondary"> <Text element="p" textAlignment="center" color="secondary">
<Trans i18nKey="dashboard.empty.add-row-body"> <Trans i18nKey="dashboard.empty.add-row-body">
Group your visualizations into expandable sections. Group your visualizations into expandable sections.
</Trans> </Trans>
</P> </Text>
</div> </div>
<Button <Button
icon="plus" icon="plus"
@ -112,16 +112,16 @@ const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
</div> </div>
<div className={cx(styles.containerBox, styles.centeredContent, styles.libraryContainer)}> <div className={cx(styles.containerBox, styles.centeredContent, styles.libraryContainer)}>
<div className={styles.headerSmall}> <div className={styles.headerSmall}>
<H3 textAlignment="center" weight="medium"> <Text element="h3" textAlignment="center" weight="medium">
<Trans i18nKey="dashboard.empty.add-import-header">Import panel</Trans> <Trans i18nKey="dashboard.empty.add-import-header">Import panel</Trans>
</H3> </Text>
</div> </div>
<div className={styles.bodySmall}> <div className={styles.bodySmall}>
<P textAlignment="center" color="secondary"> <Text element="p" textAlignment="center" color="secondary">
<Trans i18nKey="dashboard.empty.add-import-body"> <Trans i18nKey="dashboard.empty.add-import-body">
Import visualizations that are shared with other dashboards. Import visualizations that are shared with other dashboards.
</Trans> </Trans>
</P> </Text>
</div> </div>
<Button <Button
icon="plus" icon="plus"

View File

@ -12,7 +12,7 @@ import {
} from '@grafana/data'; } from '@grafana/data';
import { config, getDataSourceSrv } from '@grafana/runtime'; import { config, getDataSourceSrv } from '@grafana/runtime';
import { Checkbox, Icon, IconName, TagList } from '@grafana/ui'; import { Checkbox, Icon, IconName, TagList } from '@grafana/ui';
import { Span } from '@grafana/ui/src/unstable'; import { Text } from '@grafana/ui/src/unstable';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import { t } from 'app/core/internationalization'; import { t } from 'app/core/internationalization';
import { PluginIconName } from 'app/features/plugins/admin/types'; import { PluginIconName } from 'app/features/plugins/admin/types';
@ -179,9 +179,9 @@ export const generateColumns = (
return info ? ( return info ? (
<a key={p} href={info.url} className={styles.locationItem}> <a key={p} href={info.url} className={styles.locationItem}>
<Icon name={getIconForKind(info.kind)} /> <Icon name={getIconForKind(info.kind)} />
<Span variant="body" truncate> <Text variant="body" truncate>
{info.name} {info.name}
</Span> </Text>
</a> </a>
) : ( ) : (
<span key={p}>{p}</span> <span key={p}>{p}</span>

View File

@ -5,7 +5,7 @@ import { Link } from 'react-router-dom';
import { SIGV4ConnectionConfig } from '@grafana/aws-sdk'; import { SIGV4ConnectionConfig } from '@grafana/aws-sdk';
import { DataSourcePluginOptionsEditorProps, SelectableValue } from '@grafana/data'; import { DataSourcePluginOptionsEditorProps, SelectableValue } from '@grafana/data';
import { DataSourceHttpSettings, InlineField, InlineFormLabel, InlineSwitch, Select } from '@grafana/ui'; import { DataSourceHttpSettings, InlineField, InlineFormLabel, InlineSwitch, Select } from '@grafana/ui';
import { Span } from '@grafana/ui/src/unstable'; import { Text } from '@grafana/ui/src/unstable';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from './types'; import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from './types';
@ -76,9 +76,9 @@ export const ConfigEditor = (props: Props) => {
</InlineField> </InlineField>
</div> </div>
{options.jsonData.handleGrafanaManagedAlerts && ( {options.jsonData.handleGrafanaManagedAlerts && (
<Span variant="bodySmall" color="secondary"> <Text variant="bodySmall" color="secondary">
Make sure to enable the alert forwarding on the <Link to="/alerting/admin">admin page</Link>. Make sure to enable the alert forwarding on the <Link to="/alerting/admin">admin page</Link>.
</Span> </Text>
)} )}
</div> </div>
<DataSourceHttpSettings <DataSourceHttpSettings