mirror of
https://github.com/grafana/grafana.git
synced 2025-02-20 11:48:34 -06:00
Grafana UI: Create Text component (#66932)
This commit is contained in:
parent
692bb9ed1a
commit
fe59b65f9e
@ -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"]
|
||||
],
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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';
|
||||
|
8
packages/grafana-ui/src/components/Text/Text.mdx
Normal file
8
packages/grafana-ui/src/components/Text/Text.mdx
Normal file
@ -0,0 +1,8 @@
|
||||
import { Props, ArgsTable } from '@storybook/addon-docs/blocks';
|
||||
import { Text } from './Text';
|
||||
|
||||
# Text
|
||||
|
||||
Use for showing text.
|
||||
|
||||
<ArgsTable of={Text} />
|
131
packages/grafana-ui/src/components/Text/Text.story.internal.tsx
Normal file
131
packages/grafana-ui/src/components/Text/Text.story.internal.tsx
Normal file
@ -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 (
|
||||
<VerticalGroup>
|
||||
<StoryExample name="Header, paragraph, span and legend elements">
|
||||
<H1>h1. Heading</H1>
|
||||
<H2>h2. Heading</H2>
|
||||
<H3>h3. Heading</H3>
|
||||
<H4>h4. Heading</H4>
|
||||
<H5>h5. Heading</H5>
|
||||
<H6>h6. Heading</H6>
|
||||
<P>This is a paragraph</P>
|
||||
<Legend>This is a legend</Legend>
|
||||
<Span>This is a span</Span>
|
||||
</StoryExample>
|
||||
</VerticalGroup>
|
||||
);
|
||||
};
|
||||
Example.parameters = {
|
||||
controls: {
|
||||
exclude: ['variant', 'weight', 'textAlignment', 'truncate', 'color', 'children'],
|
||||
},
|
||||
};
|
||||
|
||||
export const HeadingComponent: Story = (args) => {
|
||||
return (
|
||||
<div style={{ width: '300px' }}>
|
||||
<H1 variant={args.variant} weight={args.weight} textAlignment={args.textAlignment} {...args}>
|
||||
{args.children}
|
||||
</H1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
HeadingComponent.args = {
|
||||
variant: undefined,
|
||||
weight: 'light',
|
||||
textAlignment: 'center',
|
||||
truncate: false,
|
||||
color: 'primary',
|
||||
children: 'This is a H1 component',
|
||||
};
|
||||
|
||||
export const LegendComponent: Story = (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: Story = (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;
|
36
packages/grafana-ui/src/components/Text/Text.test.tsx
Normal file
36
packages/grafana-ui/src/components/Text/Text.test.tsx
Normal file
@ -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(<Text as={'h1'}>This is a text component</Text>);
|
||||
expect(screen.getByText('This is a text component')).toBeInTheDocument();
|
||||
});
|
||||
it('keeps the element type but changes its styles', () => {
|
||||
const customVariant: keyof ThemeTypographyVariantTypes = 'body';
|
||||
render(
|
||||
<Text as={'h1'} variant={customVariant}>
|
||||
This is a text component
|
||||
</Text>
|
||||
);
|
||||
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(
|
||||
<Text as={'h1'} color={customColor}>
|
||||
This is a text component
|
||||
</Text>
|
||||
);
|
||||
const textComponent = screen.getByRole('heading');
|
||||
expect(textComponent).toHaveStyle(`color:${theme.colors.info.text}`);
|
||||
});
|
||||
});
|
106
packages/grafana-ui/src/components/Text/Text.tsx
Normal file
106
packages/grafana-ui/src/components/Text/Text.tsx
Normal file
@ -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<HTMLElement, TextProps>(
|
||||
({ 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;
|
||||
}
|
||||
};
|
75
packages/grafana-ui/src/components/Text/TextElements.tsx
Normal file
75
packages/grafana-ui/src/components/Text/TextElements.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
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';
|
@ -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 (
|
||||
<VerticalGroup>
|
||||
<StoryExample name="Native header elements (global styles)">
|
||||
<h1>h1. Heading</h1>
|
||||
<h2>h2. Heading</h2>
|
||||
<h3>h3. Heading</h3>
|
||||
<h4>h4. Heading</h4>
|
||||
<h5>h5. Heading</h5>
|
||||
<h6>h6. Heading</h6>
|
||||
</StoryExample>
|
||||
</VerticalGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default meta;
|
@ -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 <h1>{children}</h1>;
|
||||
};
|
||||
|
||||
Typography.displayName = 'Typography';
|
1
packages/grafana-ui/src/unstable.ts
Normal file
1
packages/grafana-ui/src/unstable.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './components/Text/TextElements';
|
Loading…
Reference in New Issue
Block a user