diff --git a/.betterer.results b/.betterer.results
index 125dde4bc3a..4dbb503be09 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -6170,9 +6170,6 @@ exports[`no undocumented stories`] = {
"packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.story.tsx:5381": [
[0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"]
],
- "packages/grafana-ui/src/components/Typography/Typography.story.tsx:5381": [
- [0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"]
- ],
"packages/grafana-ui/src/components/VizLayout/VizLayout.story.tsx:5381": [
[0, 0, 0, "No undocumented stories are allowed, please add an .mdx file with some documentation", "5381"]
],
diff --git a/packages/grafana-data/src/themes/createTypography.ts b/packages/grafana-data/src/themes/createTypography.ts
index 5dfe3ebaf47..1473adc2bb8 100644
--- a/packages/grafana-data/src/themes/createTypography.ts
+++ b/packages/grafana-data/src/themes/createTypography.ts
@@ -5,7 +5,7 @@
import { ThemeColors } from './createColors';
/** @beta */
-export interface ThemeTypography {
+export interface ThemeTypography extends ThemeTypographyVariantTypes {
fontFamily: string;
fontFamilyMonospace: string;
fontSize: number;
@@ -17,16 +17,6 @@ export interface ThemeTypography {
// The font-size on the html element.
htmlFontSize?: number;
- h1: ThemeTypographyVariant;
- h2: ThemeTypographyVariant;
- h3: ThemeTypographyVariant;
- h4: ThemeTypographyVariant;
- h5: ThemeTypographyVariant;
- h6: ThemeTypographyVariant;
-
- body: ThemeTypographyVariant;
- bodySmall: ThemeTypographyVariant;
-
/**
* @deprecated
* from legacy old theme
@@ -152,3 +142,14 @@ export function createTypography(colors: ThemeColors, typographyInput: ThemeTypo
function round(value: number) {
return Math.round(value * 1e5) / 1e5;
}
+
+export interface ThemeTypographyVariantTypes {
+ h1: ThemeTypographyVariant;
+ h2: ThemeTypographyVariant;
+ h3: ThemeTypographyVariant;
+ h4: ThemeTypographyVariant;
+ h5: ThemeTypographyVariant;
+ h6: ThemeTypographyVariant;
+ body: ThemeTypographyVariant;
+ bodySmall: ThemeTypographyVariant;
+}
diff --git a/packages/grafana-data/src/themes/index.ts b/packages/grafana-data/src/themes/index.ts
index a0f2f9fcc9d..9fac0e68a3c 100644
--- a/packages/grafana-data/src/themes/index.ts
+++ b/packages/grafana-data/src/themes/index.ts
@@ -5,7 +5,7 @@ export type { ThemeColors } from './createColors';
export type { ThemeBreakpoints, ThemeBreakpointsKey } from './breakpoints';
export type { ThemeShadows } from './createShadows';
export type { ThemeShape } from './createShape';
-export type { ThemeTypography, ThemeTypographyVariant } from './createTypography';
+export type { ThemeTypography, ThemeTypographyVariant, ThemeTypographyVariantTypes } from './createTypography';
export type { ThemeTransitions } from './createTransitions';
export type { ThemeSpacing } from './createSpacing';
export type { ThemeZIndices } from './zIndex';
diff --git a/packages/grafana-ui/src/components/Text/Text.mdx b/packages/grafana-ui/src/components/Text/Text.mdx
new file mode 100644
index 00000000000..bbbf8abc2e8
--- /dev/null
+++ b/packages/grafana-ui/src/components/Text/Text.mdx
@@ -0,0 +1,8 @@
+import { Props, ArgsTable } from '@storybook/addon-docs/blocks';
+import { Text } from './Text';
+
+# Text
+
+Use for showing text.
+
+
diff --git a/packages/grafana-ui/src/components/Text/Text.story.internal.tsx b/packages/grafana-ui/src/components/Text/Text.story.internal.tsx
new file mode 100644
index 00000000000..54394aa47d9
--- /dev/null
+++ b/packages/grafana-ui/src/components/Text/Text.story.internal.tsx
@@ -0,0 +1,131 @@
+import { Meta, Story } from '@storybook/react';
+import React from 'react';
+
+import { StoryExample } from '../../utils/storybook/StoryExample';
+import { VerticalGroup } from '../Layout/Layout';
+
+import { Text } from './Text';
+import mdx from './Text.mdx';
+import { H1, H2, H3, H4, H5, H6, Span, P, Legend, TextModifier } from './TextElements';
+
+const meta: Meta = {
+ title: 'General/Text',
+ component: Text,
+ parameters: {
+ docs: {
+ page: mdx,
+ },
+ controls: { exclude: ['as'] },
+ },
+ argTypes: {
+ variant: { control: 'select', options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'body', 'bodySmall', undefined] },
+ weight: {
+ control: 'select',
+ options: ['bold', 'medium', 'light', 'regular', undefined],
+ },
+ color: {
+ control: 'select',
+ options: [
+ 'error',
+ 'success',
+ 'warning',
+ 'info',
+ 'primary',
+ 'secondary',
+ 'disabled',
+ 'link',
+ 'maxContrast',
+ undefined,
+ ],
+ },
+ truncate: { control: 'boolean' },
+ textAlignment: {
+ control: 'select',
+ options: ['inherit', 'initial', 'left', 'right', 'center', 'justify', undefined],
+ },
+ },
+};
+
+export const Example: Story = () => {
+ return (
+
+
+ h1. Heading
+ h2. Heading
+ h3. Heading
+ h4. Heading
+ h5. Heading
+ h6. Heading
+ This is a paragraph
+
+ This is a span
+
+
+ );
+};
+Example.parameters = {
+ controls: {
+ exclude: ['variant', 'weight', 'textAlignment', 'truncate', 'color', 'children'],
+ },
+};
+
+export const HeadingComponent: Story = (args) => {
+ return (
+
+
+ {args.children}
+
+
+ );
+};
+HeadingComponent.args = {
+ variant: undefined,
+ weight: 'light',
+ textAlignment: 'center',
+ truncate: false,
+ color: 'primary',
+ children: 'This is a H1 component',
+};
+
+export const LegendComponent: Story = (args) => {
+ return (
+
+
+
+ );
+};
+
+LegendComponent.args = {
+ variant: undefined,
+ weight: 'bold',
+ textAlignment: 'center',
+ truncate: false,
+ color: 'error',
+ children: 'This is a lengend component',
+};
+
+export const TextModifierComponent: Story = (args) => {
+ return (
+
+
+ {args.children}{' '}
+
+ {' '}
+ with a part of its text modified{' '}
+
+
+
+ );
+};
+TextModifierComponent.args = {
+ variant: undefined,
+ weight: 'light',
+ textAlignment: 'center',
+ truncate: false,
+ color: 'maxContrast',
+ children: 'This is a H6 component',
+};
+
+export default meta;
diff --git a/packages/grafana-ui/src/components/Text/Text.test.tsx b/packages/grafana-ui/src/components/Text/Text.test.tsx
new file mode 100644
index 00000000000..591211db265
--- /dev/null
+++ b/packages/grafana-ui/src/components/Text/Text.test.tsx
@@ -0,0 +1,36 @@
+import { render, screen } from '@testing-library/react';
+import React from 'react';
+
+import { createTheme, ThemeTypographyVariantTypes } from '@grafana/data';
+
+import { Text } from './Text';
+
+describe('Text', () => {
+ it('renders correctly', () => {
+ render(This is a text component);
+ expect(screen.getByText('This is a text component')).toBeInTheDocument();
+ });
+ it('keeps the element type but changes its styles', () => {
+ const customVariant: keyof ThemeTypographyVariantTypes = 'body';
+ render(
+
+ This is a text component
+
+ );
+ const theme = createTheme();
+ const textComponent = screen.getByRole('heading');
+ expect(textComponent).toBeInTheDocument();
+ expect(textComponent).toHaveStyle(`fontSize: ${theme.typography.body.fontSize}`);
+ });
+ it('has the selected colour', () => {
+ const customColor = 'info';
+ const theme = createTheme();
+ render(
+
+ This is a text component
+
+ );
+ const textComponent = screen.getByRole('heading');
+ expect(textComponent).toHaveStyle(`color:${theme.colors.info.text}`);
+ });
+});
diff --git a/packages/grafana-ui/src/components/Text/Text.tsx b/packages/grafana-ui/src/components/Text/Text.tsx
new file mode 100644
index 00000000000..fa191317f90
--- /dev/null
+++ b/packages/grafana-ui/src/components/Text/Text.tsx
@@ -0,0 +1,106 @@
+import { css } from '@emotion/css';
+import React, { createElement, CSSProperties, useCallback } from 'react';
+
+import { GrafanaTheme2, ThemeTypographyVariantTypes } from '@grafana/data';
+
+import { useStyles2 } from '../../themes';
+
+export interface TextProps {
+ /** Defines what HTML element is defined underneath */
+ as: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span' | 'p' | 'legend';
+ /** 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;
+ /** 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';
+ /** 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;
+ /** Whether to align the text to left, center or right */
+ textAlignment?: CSSProperties['textAlign'];
+ children: React.ReactNode;
+}
+
+export const Text = React.forwardRef(
+ ({ as, variant, weight, color, truncate, textAlignment, children }, ref) => {
+ const styles = useStyles2(
+ useCallback(
+ (theme) => getTextStyles(theme, variant, color, weight, truncate, textAlignment),
+ [color, textAlignment, truncate, weight, variant]
+ )
+ );
+
+ return createElement(
+ as,
+ {
+ className: styles,
+ ref,
+ },
+ children
+ );
+ }
+);
+
+Text.displayName = 'Text';
+
+const getTextStyles = (
+ theme: GrafanaTheme2,
+ variant?: keyof ThemeTypographyVariantTypes,
+ color?: TextProps['color'],
+ weight?: TextProps['weight'],
+ truncate?: TextProps['truncate'],
+ textAlignment?: TextProps['textAlignment']
+) => {
+ return css([
+ variant && {
+ ...theme.typography[variant],
+ },
+ {
+ margin: 0,
+ padding: 0,
+ },
+ color && {
+ color: customColor(color, theme),
+ },
+ weight && {
+ fontWeight: customWeight(weight, theme),
+ },
+ truncate && {
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ },
+ textAlignment && {
+ textAlign: textAlignment,
+ },
+ ]);
+};
+
+const customWeight = (weight: TextProps['weight'], theme: GrafanaTheme2): number => {
+ switch (weight) {
+ case 'bold':
+ return theme.typography.fontWeightBold;
+ case 'medium':
+ return theme.typography.fontWeightMedium;
+ case 'light':
+ return theme.typography.fontWeightLight;
+ case 'regular':
+ case undefined:
+ return theme.typography.fontWeightRegular;
+ }
+};
+
+const customColor = (color: TextProps['color'], theme: GrafanaTheme2): string | undefined => {
+ switch (color) {
+ case 'error':
+ return theme.colors.error.text;
+ case 'success':
+ return theme.colors.success.text;
+ case 'info':
+ return theme.colors.info.text;
+ case 'warning':
+ return theme.colors.warning.text;
+ default:
+ return color ? theme.colors.text[color] : undefined;
+ }
+};
diff --git a/packages/grafana-ui/src/components/Text/TextElements.tsx b/packages/grafana-ui/src/components/Text/TextElements.tsx
new file mode 100644
index 00000000000..f0569f9b376
--- /dev/null
+++ b/packages/grafana-ui/src/components/Text/TextElements.tsx
@@ -0,0 +1,75 @@
+import React from 'react';
+
+import { GrafanaTheme2 } from '@grafana/data';
+
+import { Text, TextProps } from './Text';
+
+interface TextElementsProps extends Omit {}
+
+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((props, ref) => {
+ return ;
+});
+
+H1.displayName = 'H1';
+
+export const H2 = React.forwardRef((props, ref) => {
+ return ;
+});
+
+H2.displayName = 'H2';
+
+export const H3 = React.forwardRef((props, ref) => {
+ return ;
+});
+
+H3.displayName = 'H3';
+
+export const H4 = React.forwardRef((props, ref) => {
+ return ;
+});
+
+H4.displayName = 'H4';
+
+export const H5 = React.forwardRef((props, ref) => {
+ return ;
+});
+
+H5.displayName = 'H5';
+
+export const H6 = React.forwardRef((props, ref) => {
+ return ;
+});
+
+H6.displayName = 'H6';
+
+export const P = React.forwardRef((props, ref) => {
+ return ;
+});
+
+P.displayName = 'P';
+
+export const Span = React.forwardRef((props, ref) => {
+ return ;
+});
+
+Span.displayName = 'Span';
+
+export const Legend = React.forwardRef((props, ref) => {
+ return ;
+});
+
+Legend.displayName = 'Legend';
+
+export const TextModifier = React.forwardRef((props, ref) => {
+ return ;
+});
+
+TextModifier.displayName = 'TextModifier';
diff --git a/packages/grafana-ui/src/components/Typography/Typography.story.tsx b/packages/grafana-ui/src/components/Typography/Typography.story.tsx
deleted file mode 100644
index e9f9ca6eacc..00000000000
--- a/packages/grafana-ui/src/components/Typography/Typography.story.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Meta, Story } from '@storybook/react';
-import React from 'react';
-
-import { StoryExample } from '../../utils/storybook/StoryExample';
-import { VerticalGroup } from '../Layout/Layout';
-
-import { Typography } from './Typography';
-
-const meta: Meta = {
- title: 'General/Typography',
- component: Typography,
- parameters: {
- docs: {},
- },
-};
-
-export const Typopgraphy: Story = () => {
- return (
-
-
- h1. Heading
- h2. Heading
- h3. Heading
- h4. Heading
- h5. Heading
- h6. Heading
-
-
- );
-};
-
-export default meta;
diff --git a/packages/grafana-ui/src/components/Typography/Typography.tsx b/packages/grafana-ui/src/components/Typography/Typography.tsx
deleted file mode 100644
index 90aa7d11379..00000000000
--- a/packages/grafana-ui/src/components/Typography/Typography.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-
-/** @internal */
-export interface Props {
- children: React.ReactNode;
-}
-
-/**
- * @internal
- * TODO implementation coming
- **/
-export const Typography = ({ children }: Props) => {
- return {children}
;
-};
-
-Typography.displayName = 'Typography';
diff --git a/packages/grafana-ui/src/unstable.ts b/packages/grafana-ui/src/unstable.ts
new file mode 100644
index 00000000000..8b61345b899
--- /dev/null
+++ b/packages/grafana-ui/src/unstable.ts
@@ -0,0 +1 @@
+export * from './components/Text/TextElements';