diff --git a/.eslintrc b/.eslintrc index e9395efcef4..3a4a80b8ae7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,8 +5,7 @@ "rules": { "no-only-tests/no-only-tests": "error", "react/prop-types": "off", - "@emotion/jsx-import": "error", - "@emotion/pkg-renaming": "error" + "@emotion/jsx-import": "error" }, "overrides": [ { @@ -15,6 +14,14 @@ "react-hooks/rules-of-hooks": "off", "react-hooks/exhaustive-deps": "off" } + }, + { + "files": ["packages/grafana-ui/src/components/ThemeDemos/**/*.{ts,tsx}"], + "rules": { + "@emotion/jsx-import": "off", + "react/jsx-uses-react": "off", + "react/react-in-jsx-scope": "off" + } } ] } diff --git a/packages/grafana-data/src/themes/createPalette.ts b/packages/grafana-data/src/themes/createPalette.ts index 78c560f7eb5..035553003af 100644 --- a/packages/grafana-data/src/themes/createPalette.ts +++ b/packages/grafana-data/src/themes/createPalette.ts @@ -111,7 +111,7 @@ class DarkPalette implements ThemePaletteBase> { border: this.border1, text: this.text.primary, disabledText: this.text.disabled, - disabledBackground: colors.gray10, + disabledBackground: 'rgba(255, 255, 255, 0.04)', }; contrastThreshold = 3; @@ -129,8 +129,8 @@ class LightPalette implements ThemePaletteBase> { }; secondary = { - main: 'rgba(0,0,0,0.2)', - contrastText: 'rgba(0, 0, 0, 0.87)', + main: 'rgba(0,0,0,0.18)', + contrastText: 'rgba(0, 0, 0, 0.75)', }; info = { @@ -170,7 +170,7 @@ class LightPalette implements ThemePaletteBase> { formComponent = { background: this.layer1, - border: this.border2, + border: this.border1, text: this.text.primary, disabledBackground: colors.gray95, disabledText: this.text.disabled, diff --git a/packages/grafana-data/src/themes/createSpacing.ts b/packages/grafana-data/src/themes/createSpacing.ts index fafb8fd5773..9953c915f09 100644 --- a/packages/grafana-data/src/themes/createSpacing.ts +++ b/packages/grafana-data/src/themes/createSpacing.ts @@ -26,6 +26,7 @@ export interface ThemeSpacing { bottom: ThemeSpacingArgument, left: ThemeSpacingArgument ): string; + gridSize: number; } /** @internal */ @@ -64,5 +65,7 @@ export function createSpacing(options: ThemeSpacingOptions = {}): ThemeSpacing { .join(' '); }; + spacing.gridSize = gridSize; + return spacing; } diff --git a/packages/grafana-data/src/themes/createTheme.test.ts b/packages/grafana-data/src/themes/createTheme.test.ts index ddd6502c46b..6dc5c22f73a 100644 --- a/packages/grafana-data/src/themes/createTheme.test.ts +++ b/packages/grafana-data/src/themes/createTheme.test.ts @@ -55,7 +55,7 @@ describe('createTheme', () => { "formComponent": Object { "background": "#0b0c0e", "border": "#2c3235", - "disabledBackground": "#141619", + "disabledBackground": "rgba(255, 255, 255, 0.04)", "disabledText": "rgba(255, 255, 255, 0.3)", "text": "rgba(255, 255, 255, 0.75)", }, diff --git a/packages/grafana-ui/.storybook/storybookTheme.ts b/packages/grafana-ui/.storybook/storybookTheme.ts index d2f23080b73..c7e90f0a965 100644 --- a/packages/grafana-ui/.storybook/storybookTheme.ts +++ b/packages/grafana-ui/.storybook/storybookTheme.ts @@ -9,7 +9,7 @@ const createTheme = (theme: GrafanaTheme) => { base: theme.name.includes('Light') ? 'light' : 'dark', colorPrimary: theme.v2.palette.primary.main, - colorSecondary: theme.v2.palette.secondary.main, + colorSecondary: theme.v2.palette.error.main, // UI appBg: theme.v2.palette.layer0, diff --git a/packages/grafana-ui/src/components/Button/Button.story.tsx b/packages/grafana-ui/src/components/Button/Button.story.tsx index a2f2befbf45..3027f76eed5 100644 --- a/packages/grafana-ui/src/components/Button/Button.story.tsx +++ b/packages/grafana-ui/src/components/Button/Button.story.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Story } from '@storybook/react'; -import { Button, ButtonProps, ButtonVariant } from './Button'; +import { allButtonVariants, Button, ButtonProps } from './Button'; import { withCenteredStory, withHorizontallyCenteredStory } from '../../utils/storybook/withCenteredStory'; import { iconOptions } from '../../utils/storybook/knobs'; import mdx from './Button.mdx'; @@ -31,18 +31,20 @@ export default { export const Variants: Story = ({ children, ...args }) => { const sizes: ComponentSize[] = ['lg', 'md', 'sm']; - const variants: ButtonVariant[] = ['primary', 'secondary', 'destructive', 'link']; return ( - {variants.map((variant) => ( + {allButtonVariants.map((variant) => ( {sizes.map((size) => ( ))} + ))} diff --git a/packages/grafana-ui/src/components/Button/Button.tsx b/packages/grafana-ui/src/components/Button/Button.tsx index 15e5755b820..b3079e0d3d1 100644 --- a/packages/grafana-ui/src/components/Button/Button.tsx +++ b/packages/grafana-ui/src/components/Button/Button.tsx @@ -1,15 +1,15 @@ import React, { AnchorHTMLAttributes, ButtonHTMLAttributes } from 'react'; -import { css, cx } from '@emotion/css'; -import tinycolor from 'tinycolor2'; +import { css, CSSObject, cx } from '@emotion/css'; import { useTheme } from '../../themes'; import { IconName } from '../../types/icon'; import { getPropertiesForButtonSize } from '../Forms/commonStyles'; -import { GrafanaTheme } from '@grafana/data'; +import { GrafanaTheme, GrafanaThemeV2, ThemePaletteColor } from '@grafana/data'; import { ComponentSize } from '../../types/size'; -import { focusCss } from '../../themes/mixins'; +import { getFocusStyles } from '../../themes/mixins'; import { Icon } from '../Icon/Icon'; export type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'link'; +export const allButtonVariants: ButtonVariant[] = ['primary', 'secondary', 'destructive', 'link']; type CommonProps = { size?: ComponentSize; @@ -29,9 +29,8 @@ export const Button = React.forwardRef( theme, size, variant, - icon, fullWidth, - children, + iconOnly: !children, }); return ( @@ -55,26 +54,13 @@ export const LinkButton = React.forwardRef( fullWidth, size, variant, - icon, - children, + iconOnly: !children, }); - const linkButtonStyles = - disabled && - cx( - disabledStyles, - css` - pointer-events: none; - ` - ); + const linkButtonStyles = cx(styles.button, { [styles.disabled]: disabled }, className); return ( - + {icon && } {children && {children}} @@ -87,62 +73,63 @@ LinkButton.displayName = 'LinkButton'; export interface StyleProps { size: ComponentSize; variant: ButtonVariant; - children?: React.ReactNode; - icon?: IconName; + iconOnly?: boolean; theme: GrafanaTheme; fullWidth?: boolean; narrow?: boolean; } -const disabledStyles = css` - cursor: not-allowed; - opacity: 0.65; - box-shadow: none; -`; - export const getButtonStyles = (props: StyleProps) => { - const { theme, variant, size, children, fullWidth } = props; - const { padding, fontSize, height } = getPropertiesForButtonSize(size, theme); - const { borderColor, variantStyles } = getPropertiesForVariant(theme, variant); - const iconOnly = !children; + const { theme, variant, size, iconOnly, fullWidth } = props; + const { height, padding, fontSize } = getPropertiesForButtonSize(size, theme.v2); + const variantStyles = getPropertiesForVariant(theme.v2, variant); + + const disabledStyles: CSSObject = { + cursor: 'not-allowed', + opacity: 0.65, + boxShadow: 'none', + background: theme.v2.palette.formComponent.disabledBackground, + border: `1px solid ${theme.v2.palette.formComponent.disabledBackground}`, + color: theme.v2.palette.text.disabled, + pointerEvents: 'none', + + '&:hover': { + background: theme.v2.palette.formComponent.disabledBackground, + color: theme.v2.palette.text.disabled, + }, + }; return { - button: css` - label: button; - display: inline-flex; - align-items: center; - font-weight: ${theme.typography.weight.semibold}; - font-family: ${theme.typography.fontFamily.sansSerif}; - font-size: ${fontSize}; - padding: 0 ${padding}px; - height: ${height}px; + button: css({ + label: 'button', + display: 'inline-flex', + alignItems: 'center', + fontSize: fontSize, + fontWeight: theme.v2.typography.fontWeightMedium, + fontFamily: theme.v2.typography.fontFamily, + padding: theme.v2.spacing(0, padding), + height: theme.v2.spacing(height), // Deduct border from line-height for perfect vertical centering on windows and linux - line-height: ${height - 2}px; - vertical-align: middle; - cursor: pointer; - border: 1px solid ${borderColor}; - border-radius: ${theme.border.radius.sm}; - ${fullWidth && - ` - flex-grow: 1; - justify-content: center; - `} - ${variantStyles} - - &[disabled], - &:disabled { - ${disabledStyles}; - } - `, + lineHeight: `${theme.v2.spacing.gridSize * height - 2}px`, + verticalAlign: 'middle', + cursor: 'pointer', + borderRadius: theme.v2.shape.borderRadius(1), + ...(fullWidth && { + flexGrow: 1, + justifyContent: 'center', + }), + ...variantStyles, + ':disabled': disabledStyles, + '&[disabled]': disabledStyles, + }), + disabled: css(disabledStyles), img: css` width: 16px; height: 16px; - margin-right: ${theme.spacing.sm}; - margin-left: -${theme.spacing.xs}; + margin: ${theme.v2.spacing(0, 1, 0, 0.5)}; `, icon: css` - margin-left: -${padding / 2}px; - margin-right: ${(iconOnly ? -padding : padding) / 2}px; + margin: ${theme.v2.spacing(0, (iconOnly ? -padding : padding) / 2, 0, -(padding / 2))}; `, content: css` display: flex; @@ -154,73 +141,50 @@ export const getButtonStyles = (props: StyleProps) => { }; }; -function getButtonVariantStyles(from: string, to: string, textColor: string, theme: GrafanaTheme) { - return css` - background: linear-gradient(180deg, ${from} 0%, ${to} 100%); - color: ${textColor}; - &:hover { - background: ${from}; - color: ${textColor}; - } +function getButtonVariantStyles(theme: GrafanaThemeV2, color: ThemePaletteColor): CSSObject { + return { + background: color.main, + color: color.contrastText, + boxShadow: theme.shadows.z1, + border: `1px solid transparent`, - &:focus { - background: ${from}; - outline: none; - ${focusCss(theme)}; - } - `; + '&:hover': { + background: theme.palette.getHoverColor(color.main), + color: color.contrastText, + }, + + '&:focus': { + ...getFocusStyles(theme), + }, + }; } -export function getPropertiesForVariant(theme: GrafanaTheme, variant: ButtonVariant) { +export function getPropertiesForVariant(theme: GrafanaThemeV2, variant: ButtonVariant) { switch (variant) { case 'secondary': - const from = theme.isLight ? theme.palette.gray7 : theme.palette.gray15; - const to = theme.isLight ? tinycolor(from).darken(5).toString() : tinycolor(from).lighten(4).toString(); - return { - borderColor: theme.isLight ? theme.palette.gray85 : theme.palette.gray25, - variantStyles: getButtonVariantStyles( - from, - to, - theme.isLight ? theme.palette.gray25 : theme.palette.gray4, - theme - ), - }; + return getButtonVariantStyles(theme, theme.palette.secondary); case 'destructive': - return { - borderColor: theme.palette.redShade, - variantStyles: getButtonVariantStyles( - theme.palette.redBase, - theme.palette.redShade, - theme.palette.white, - theme - ), - }; + return getButtonVariantStyles(theme, theme.palette.error); case 'link': return { - borderColor: 'transparent', - variantStyles: css` - background: transparent; - color: ${theme.colors.linkExternal}; + background: 'transparent', + color: theme.palette.text.link, + border: '1px solid transparent', + '&:focus': { + outline: 'none', + textDecoration: 'underline', + }, - &:focus { - outline: none; - text-decoration: underline; - } - - &:hover { - color: ${theme.colors.linkExternal}; - text-decoration: underline; - } - `, + '&:hover': { + color: theme.palette.getHoverColor(theme.palette.text.link), + textDecoration: 'underline', + }, }; case 'primary': default: - return { - borderColor: theme.colors.bgBlue1, - variantStyles: getButtonVariantStyles(theme.colors.bgBlue1, theme.colors.bgBlue2, theme.palette.white, theme), - }; + return getButtonVariantStyles(theme, theme.palette.primary); } } diff --git a/packages/grafana-ui/src/components/Button/ToolbarButton.tsx b/packages/grafana-ui/src/components/Button/ToolbarButton.tsx index 0a260a1d855..3727d80ff03 100644 --- a/packages/grafana-ui/src/components/Button/ToolbarButton.tsx +++ b/packages/grafana-ui/src/components/Button/ToolbarButton.tsx @@ -104,8 +104,8 @@ function renderIcon(icon: IconName | React.ReactNode) { } const getStyles = (theme: GrafanaTheme) => { - const primaryVariant = getPropertiesForVariant(theme, 'primary'); - const destructiveVariant = getPropertiesForVariant(theme, 'destructive'); + const primaryVariant = getPropertiesForVariant(theme.v2, 'primary'); + const destructiveVariant = getPropertiesForVariant(theme.v2, 'destructive'); return { button: css` @@ -151,14 +151,8 @@ const getStyles = (theme: GrafanaTheme) => { background: ${styleMixins.hoverColor(theme.colors.bg1, theme)}; } `, - primary: css` - border-color: ${primaryVariant.borderColor}; - ${primaryVariant.variantStyles} - `, - destructive: css` - border-color: ${destructiveVariant.borderColor}; - ${destructiveVariant.variantStyles} - `, + primary: css(primaryVariant), + destructive: css(destructiveVariant), narrow: css` padding: 0 ${theme.spacing.xs}; `, diff --git a/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx b/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx index 8f41f8c7cba..b293fe69747 100644 --- a/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx +++ b/packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx @@ -3,7 +3,7 @@ import usePrevious from 'react-use/lib/usePrevious'; import { DataLinkSuggestions } from './DataLinkSuggestions'; import { makeValue, ThemeContext } from '../../index'; import { SelectionReference } from './SelectionReference'; -import { getFormStyles, Portal } from '../index'; +import { Portal } from '../index'; // @ts-ignore import Prism, { Grammar, LanguageMap } from 'prismjs'; @@ -17,6 +17,7 @@ import { SlatePrism } from '../../slate-plugins'; import { SCHEMA } from '../../utils/slate'; import { stylesFactory } from '../../themes'; import { DataLinkBuiltInVars, GrafanaTheme, VariableOrigin, VariableSuggestion } from '@grafana/data'; +import { getInputStyles } from '../Input/Input'; const modulo = (a: number, n: number) => a - n * Math.floor(a / n); @@ -44,7 +45,7 @@ const plugins = [ ]; const getStyles = stylesFactory((theme: GrafanaTheme) => ({ - input: getFormStyles(theme, { variant: 'primary', size: 'md', invalid: false }).input.input, + input: getInputStyles({ theme, invalid: false }).input, editor: css` .token.builtInVariable { color: ${theme.palette.queryGreen}; diff --git a/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx b/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx index 6c257ecf3da..ae1c5580675 100644 --- a/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx +++ b/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx @@ -1,9 +1,10 @@ import React, { FC, FormEvent, useCallback, useState } from 'react'; import { GrafanaTheme } from '@grafana/data'; import { css, cx } from '@emotion/css'; -import { getFormStyles, Icon } from '../index'; +import { Icon } from '../index'; import { stylesFactory, useTheme } from '../../themes'; import { ComponentSize } from '../../types/size'; +import { getButtonStyles } from '../Button'; export interface Props { /** Callback function to handle uploaded file */ @@ -75,17 +76,13 @@ export const FileUpload: FC = ({ }; const getStyles = stylesFactory((theme: GrafanaTheme, size: ComponentSize) => { - const buttonFormStyle = getFormStyles(theme, { variant: 'primary', invalid: false, size }).button.button; + const buttonStyles = getButtonStyles({ theme, variant: 'primary', size, iconOnly: false }); return { fileUpload: css` display: none; `, - button: css` - ${buttonFormStyle} - `, - icon: css` - margin-right: ${theme.spacing.xs}; - `, + button: buttonStyles.button, + icon: buttonStyles.icon, fileName: css` margin-left: ${theme.spacing.xs}; `, diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx index 983daeab1aa..7271e1ba8bf 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButton.tsx @@ -19,7 +19,7 @@ export interface RadioButtonProps { } const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButtonSize, fullWidth?: boolean) => { - const { fontSize, height, padding } = getPropertiesForButtonSize(size, theme); + const { fontSize, height, padding } = getPropertiesForButtonSize(size, theme.v2); const c = theme.palette; const textColor = theme.colors.textSemiWeak; @@ -64,11 +64,11 @@ const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme, size: RadioButt display: inline-block; position: relative; font-size: ${fontSize}; - height: ${height}px; + height: ${theme.v2.spacing(height)}; // Deduct border from line-height for perfect vertical centering on windows and linux - line-height: ${height - 2}px; + line-height: ${theme.v2.spacing.gridSize * height - 2}px; color: ${textColor}; - padding: 0 ${padding}px; + padding: ${theme.v2.spacing(0, padding)}; margin-left: -1px; border-radius: ${theme.border.radius.sm}; border: ${border}; diff --git a/packages/grafana-ui/src/components/Forms/commonStyles.ts b/packages/grafana-ui/src/components/Forms/commonStyles.ts index f7b69039a84..a7d2f254b17 100644 --- a/packages/grafana-ui/src/components/Forms/commonStyles.ts +++ b/packages/grafana-ui/src/components/Forms/commonStyles.ts @@ -1,5 +1,5 @@ import { css } from '@emotion/css'; -import { GrafanaTheme } from '@grafana/data'; +import { GrafanaTheme, GrafanaThemeV2 } from '@grafana/data'; import { focusCss } from '../../themes/mixins'; import { ComponentSize } from '../../types/size'; @@ -88,29 +88,27 @@ export const inputSizesPixels = (size: string) => { } }; -export function getPropertiesForButtonSize(size: ComponentSize, theme: GrafanaTheme) { - const { typography, height, spacing } = theme; - +export function getPropertiesForButtonSize(size: ComponentSize, theme: GrafanaThemeV2) { switch (size) { case 'sm': return { - padding: spacing.base, - fontSize: typography.size.sm, - height: height.sm, + padding: 1, + fontSize: theme.typography.size.sm, + height: theme.components.height.sm, }; case 'lg': return { - padding: spacing.base * 3, - fontSize: typography.size.lg, - height: height.lg, + padding: 3, + fontSize: theme.typography.size.lg, + height: theme.components.height.lg, }; case 'md': default: return { - padding: spacing.base * 2, - fontSize: typography.size.md, - height: height.md, + padding: 2, + fontSize: theme.typography.size.md, + height: theme.components.height.md, }; } } diff --git a/packages/grafana-ui/src/components/Forms/getFormStyles.ts b/packages/grafana-ui/src/components/Forms/getFormStyles.ts index 70363739072..938f16f8f6f 100644 --- a/packages/grafana-ui/src/components/Forms/getFormStyles.ts +++ b/packages/grafana-ui/src/components/Forms/getFormStyles.ts @@ -8,8 +8,11 @@ import { ComponentSize } from '../../types/size'; import { getInputStyles } from '../Input/Input'; import { getCheckboxStyles } from './Checkbox'; +/** @deprecated */ export const getFormStyles = stylesFactory( (theme: GrafanaTheme, options: { variant: ButtonVariant; size: ComponentSize; invalid: boolean }) => { + console.warn('getFormStyles is deprecated'); + return { label: getLabelStyles(theme), legend: getLegendStyles(theme), diff --git a/packages/grafana-ui/src/components/ThemeDemos/EmotionPerfTest.tsx b/packages/grafana-ui/src/components/ThemeDemos/EmotionPerfTest.tsx new file mode 100644 index 00000000000..b9e2bdd39c7 --- /dev/null +++ b/packages/grafana-ui/src/components/ThemeDemos/EmotionPerfTest.tsx @@ -0,0 +1,195 @@ +/** @jsxImportSource @emotion/react */ + +import { Profiler, ProfilerOnRenderCallback, useState, FC } from 'react'; +import { GrafanaTheme } from '@grafana/data'; +import { css, cx } from '@emotion/css'; +import { useStyles, useTheme } from '../../themes'; +import { Button } from '../Button'; +import { VerticalGroup } from '../Layout/Layout'; +import classnames from 'classnames'; + +export function EmotionPerfTest() { + console.log('process.env.NODE_ENV', process.env.NODE_ENV); + + return ( + +
Emotion performance tests
+ + + + + + + +
+ ); +} + +export const TestScenario: FC<{ name: string; Component: FC }> = ({ name, Component }) => { + const [render, setRender] = useState(0); + + return ( +
+ + {render > 0 && {renderManyComponents(Component)}} +
+ ); +}; + +TestScenario.displayName = 'TestScenario'; + +function renderManyComponents(Component: FC) { + const elements: React.ReactNode[] = []; + + for (let i = 0; i < 5000; i++) { + elements.push(); + } + + return
{elements}
; +} + +interface TestComponentProps { + index: number; +} + +function UseStylesNoCX({ index }: TestComponentProps) { + const styles = useStyles(getStyles); + return ( +
+
{index}
+
+ ); +} + +function UseStylesWithConditionalCX({ index }: TestComponentProps) { + const styles = useStyles(getStyles); + const mainStyles = cx(styles.main, { [styles.large]: index > 10, [styles.disabed]: index % 10 === 0 }); + + return ( +
+
{index}
+
+ ); +} + +function UseStylesWithConditionalClassNames({ index }: TestComponentProps) { + const styles = useStyles(getStyles); + const mainStyles = classnames(styles.main, { [styles.large]: index > 10, [styles.disabed]: index % 10 === 0 }); + + return ( +
+
{index}
+
+ ); +} + +function UseStylesWithCSSProp({ index }: TestComponentProps) { + const styles = useStyles(getStylesObjects); + + return ( +
+
{index}
+
+ ); +} + +function UseStylesWithConditionalCSS({ index }: TestComponentProps) { + const styles = useStyles(getStylesObjects); + const mainStyles = [styles.main, index > 10 && styles.large, index % 10 === 0 && styles.disabed]; + return ( +
+
{index}
+
+ ); +} + +function InlineEmotionCSS({ index }: TestComponentProps) { + const theme = useTheme(); + const styles = getStyles(theme); + + return ( +
+
{index}
+
+ ); +} + +function NoStyles({ index }: TestComponentProps) { + return ( +
+
{index}
+
+ ); +} + +function MeasureRender({ children, id }: { children: React.ReactNode; id: string }) { + const onRender: ProfilerOnRenderCallback = ( + id: string, + phase: 'mount' | 'update', + actualDuration: number, + baseDuration: number, + startTime: number, + commitTime: number + ) => { + console.log('Profile ' + id, actualDuration); + }; + + return ( + + {children} + + ); +} + +const getStyles = (theme: GrafanaTheme) => { + return { + main: css(getStylesObjectMain(theme)), + large: css({ + fontSize: '20px', + color: 'red', + }), + disabed: css({ + fontSize: '10px', + color: 'gray', + }), + child: css(getStylesObjectChild(theme)), + }; +}; + +const getStylesObjects = (theme: GrafanaTheme) => { + return { + main: getStylesObjectMain(theme), + large: { + fontSize: '20px', + color: 'red', + }, + disabed: { + fontSize: '10px', + color: 'gray', + }, + child: getStylesObjectChild(theme), + }; +}; + +function getStylesObjectMain(theme: GrafanaTheme): any { + return { + background: 'blue', + border: '1px solid red', + color: 'white', + padding: theme.v2.spacing(1), + shadow: theme.v2.shadows.z1, + ':hover': { + background: theme.colors.bg1, + }, + }; +} + +function getStylesObjectChild(theme: GrafanaTheme): any { + return { + padding: '2px', + fontSize: '10px', + boxShadow: 'none', + textAlign: 'center', + textDecoration: 'none', + }; +} diff --git a/packages/grafana-ui/src/components/ThemeDemos/NewThemeDemo.story.tsx b/packages/grafana-ui/src/components/ThemeDemos/NewThemeDemo.story.tsx index b7085b91652..7f5ef2f2cad 100644 --- a/packages/grafana-ui/src/components/ThemeDemos/NewThemeDemo.story.tsx +++ b/packages/grafana-ui/src/components/ThemeDemos/NewThemeDemo.story.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { EmotionPerfTest } from './EmotionPerfTest'; import { NewThemeDemo as NewThemeDemoComponent } from './NewThemeDemo'; export default { @@ -16,3 +17,7 @@ export default { export const NewThemeDemo = () => { return ; }; + +export const PerfTest = () => { + return ; +}; diff --git a/packages/grafana-ui/src/components/ThemeDemos/NewThemeDemo.tsx b/packages/grafana-ui/src/components/ThemeDemos/NewThemeDemo.tsx index cf5ec8849d9..bc071ba81fd 100644 --- a/packages/grafana-ui/src/components/ThemeDemos/NewThemeDemo.tsx +++ b/packages/grafana-ui/src/components/ThemeDemos/NewThemeDemo.tsx @@ -9,7 +9,7 @@ import { Field } from '../Forms/Field'; import { Input } from '../Input/Input'; import { RadioButtonGroup } from '../Forms/RadioButtonGroup/RadioButtonGroup'; import { Switch } from '../Switch/Switch'; -import { Button, ButtonVariant } from '../Button'; +import { allButtonVariants, Button } from '../Button'; interface DemoBoxProps { bg?: string; @@ -50,7 +50,6 @@ export const NewThemeDemo = () => { const [boolValue, setBoolValue] = useState(false); const oldTheme = useTheme(); const t = oldTheme.v2; - const variants: ButtonVariant[] = ['primary', 'secondary', 'destructive', 'link']; const richColors = [ t.palette.primary, @@ -151,13 +150,13 @@ export const NewThemeDemo = () => { - {variants.map((variant) => ( + {allButtonVariants.map((variant) => ( ))} diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 026151acf64..ecb262f3b61 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -227,3 +227,4 @@ export { BarChartOptions, BarStackingMode, BarValueVisibility, BarChartFieldConf export { TimelineOptions, TimelineFieldConfig } from './Timeline/types'; export { GraphNGLegendEvent, GraphNGLegendEventMode } from './GraphNG/types'; export * from './NodeGraph'; +export { EmotionPerfTest } from './ThemeDemos/EmotionPerfTest'; diff --git a/packages/grafana-ui/src/themes/mixins.ts b/packages/grafana-ui/src/themes/mixins.ts index f09be644839..77fb8eaa39c 100644 --- a/packages/grafana-ui/src/themes/mixins.ts +++ b/packages/grafana-ui/src/themes/mixins.ts @@ -1,4 +1,5 @@ -import { GrafanaTheme } from '@grafana/data'; +import { CSSObject } from '@emotion/css'; +import { GrafanaTheme, GrafanaThemeV2 } from '@grafana/data'; import tinycolor from 'tinycolor2'; export function cardChrome(theme: GrafanaTheme): string { @@ -44,3 +45,12 @@ export const focusCss = (theme: GrafanaTheme) => ` box-shadow: 0 0 0 2px ${theme.colors.bodyBg}, 0 0 0px 4px ${theme.colors.formFocusOutline}; transition: all 0.2s cubic-bezier(0.19, 1, 0.22, 1); `; + +export function getFocusStyles(theme: GrafanaThemeV2): CSSObject { + return { + outline: '2px dotted transparent', + outlineOffset: '2px', + boxShadow: `0 0 0 2px ${theme.palette.layer0}, 0 0 0px 4px ${theme.palette.primary.border}`, + transition: `all 0.2s cubic-bezier(0.19, 1, 0.22, 1)`, + }; +} diff --git a/public/app/features/sandbox/BenchmarksPage.tsx b/public/app/features/sandbox/BenchmarksPage.tsx new file mode 100644 index 00000000000..ace7740745d --- /dev/null +++ b/public/app/features/sandbox/BenchmarksPage.tsx @@ -0,0 +1,12 @@ +import { EmotionPerfTest, VerticalGroup } from '@grafana/ui'; +import React, { FC } from 'react'; + +export const BenchmarksPage: FC = () => { + return ( + + + + ); +}; + +export default BenchmarksPage; diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index e6627fdcc44..8868d7b4991 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -441,6 +441,12 @@ export function getAppRoutes(): RouteDescriptor[] { () => import(/* webpackChunkName: "PlaylistEditPage"*/ 'app/features/playlist/PlaylistEditPage') ), }, + { + path: '/sandbox/benchmarks', + component: SafeDynamicImport( + () => import(/* webpackChunkName: "BenchmarksPage"*/ 'app/features/sandbox/BenchmarksPage') + ), + }, ...extraRoutes, { path: '/*',