Eslint: Allow 'unset' in no-border-radius-literal lint rule (#106619)
* allow border radius of 0 * Prefer unset or initial over 0 * readme * add an autofix for 0 -> unset * replace 0 with unset * fix fixes tests * fix snapshot * Fix lint in SecretFormField * fix unused cx
This commit is contained in:
@@ -18,6 +18,8 @@ Check if border-radius theme tokens are used.
|
||||
|
||||
To improve the consistency across Grafana we encourage devs to use tokens instead of custom values. In this case, we want the `borderRadius` to use the appropriate token such as `theme.shape.radius.default`, `theme.shape.radius.pill` or `theme.shape.radius.circle`.
|
||||
|
||||
Instead of using `0` to remove a previously set border-radius, use `unset`.
|
||||
|
||||
### `no-unreduced-motion`
|
||||
|
||||
Avoid direct use of `animation*` or `transition*` properties.
|
||||
|
||||
@@ -4,6 +4,16 @@ const createRule = ESLintUtils.RuleCreator(
|
||||
(name) => `https://github.com/grafana/grafana/blob/main/packages/grafana-eslint-rules/README.md#${name}`
|
||||
);
|
||||
|
||||
const BORDER_RADIUS_PROPERTIES = [
|
||||
'borderRadius',
|
||||
'borderTopLeftRadius',
|
||||
'borderTopRightRadius',
|
||||
'borderBottomLeftRadius',
|
||||
'borderBottomRightRadius',
|
||||
];
|
||||
|
||||
const RE_ZERO_VALUE = /^0([a-zA-Z%]*)$/;
|
||||
|
||||
const borderRadiusRule = createRule({
|
||||
create(context) {
|
||||
return {
|
||||
@@ -11,13 +21,30 @@ const borderRadiusRule = createRule({
|
||||
if (
|
||||
node.type === AST_NODE_TYPES.Property &&
|
||||
node.key.type === AST_NODE_TYPES.Identifier &&
|
||||
node.key.name === 'borderRadius' &&
|
||||
BORDER_RADIUS_PROPERTIES.includes(node.key.name) &&
|
||||
node.value.type === AST_NODE_TYPES.Literal
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'borderRadiusId',
|
||||
});
|
||||
const value = node.value.value;
|
||||
|
||||
if (value === 'unset' || value === 'initial') {
|
||||
// Allow 'unset' or 'initial' to remove border radius
|
||||
return;
|
||||
} else if (value === 0 || RE_ZERO_VALUE.test(value)) {
|
||||
// Require 'unset' or 'initial' to remove border radius instead of `0` or `0px`
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'borderRadiusNoZeroValue',
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(node.value, "'unset'");
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// Otherwise, require theme tokens are used
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'borderRadiusUseTokens',
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -25,11 +52,13 @@ const borderRadiusRule = createRule({
|
||||
name: 'no-border-radius-literal',
|
||||
meta: {
|
||||
type: 'problem',
|
||||
fixable: 'code',
|
||||
docs: {
|
||||
description: 'Check if border-radius theme tokens are used',
|
||||
},
|
||||
messages: {
|
||||
borderRadiusId: 'Prefer using theme.shape.radius tokens instead of literal values.',
|
||||
borderRadiusUseTokens: 'Prefer using theme.shape.radius tokens instead of literal values.',
|
||||
borderRadiusNoZeroValue: 'Use unset or initial to remove a border radius.',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
|
||||
@@ -14,8 +14,12 @@ RuleTester.setDefaultConfig({
|
||||
},
|
||||
});
|
||||
|
||||
const expectedError = {
|
||||
messageId: 'borderRadiusId',
|
||||
const useTokenError = {
|
||||
messageId: 'borderRadiusUseTokens',
|
||||
};
|
||||
|
||||
const noZeroValueError = {
|
||||
messageId: 'borderRadiusNoZeroValue',
|
||||
};
|
||||
|
||||
const ruleTester = new RuleTester();
|
||||
@@ -31,20 +35,44 @@ ruleTester.run('eslint no-border-radius-literal', noBorderRadiusLiteral, {
|
||||
{
|
||||
code: `css({ borderRadius: theme.shape.radius.pill })`,
|
||||
},
|
||||
{
|
||||
code: `css({ borderTopLeftRadius: theme.shape.radius.pill })`,
|
||||
},
|
||||
{
|
||||
code: `css({ borderTopRightRadius: theme.shape.radius.pill })`,
|
||||
},
|
||||
{
|
||||
code: `css({ borderBottomLeftRadius: theme.shape.radius.pill })`,
|
||||
},
|
||||
{
|
||||
code: `css({ borderBottomRightRadius: theme.shape.radius.pill })`,
|
||||
},
|
||||
|
||||
// allow values to remove border radius
|
||||
{
|
||||
code: `css({ borderRadius: 'initial' })`,
|
||||
},
|
||||
{
|
||||
code: `css({ borderRadius: 'unset' })`,
|
||||
},
|
||||
],
|
||||
|
||||
invalid: [
|
||||
{
|
||||
code: `css({ borderRadius: '2px' })`,
|
||||
errors: [expectedError],
|
||||
errors: [useTokenError],
|
||||
},
|
||||
{
|
||||
code: `css({ borderRadius: 2 })`, // should error on px shorthand
|
||||
errors: [useTokenError],
|
||||
},
|
||||
{
|
||||
code: `css({ lineHeight: 1 }, { borderRadius: '2px' })`,
|
||||
errors: [expectedError],
|
||||
errors: [useTokenError],
|
||||
},
|
||||
{
|
||||
code: `css([{ lineHeight: 1 }, { borderRadius: '2px' }])`,
|
||||
errors: [expectedError],
|
||||
errors: [useTokenError],
|
||||
},
|
||||
{
|
||||
name: 'nested classes',
|
||||
@@ -56,7 +84,40 @@ css({
|
||||
},
|
||||
},
|
||||
})`,
|
||||
errors: [expectedError],
|
||||
errors: [useTokenError],
|
||||
},
|
||||
{
|
||||
code: `css({ borderTopLeftRadius: 1 })`,
|
||||
errors: [useTokenError],
|
||||
},
|
||||
{
|
||||
code: `css({ borderTopRightRadius: "2px" })`,
|
||||
errors: [useTokenError],
|
||||
},
|
||||
{
|
||||
code: `css({ borderBottomLeftRadius: 3 })`,
|
||||
errors: [useTokenError],
|
||||
},
|
||||
{
|
||||
code: `css({ borderBottomRightRadius: "4px" })`,
|
||||
errors: [useTokenError],
|
||||
},
|
||||
|
||||
// should use unset or initial to remove border radius
|
||||
{
|
||||
code: `css({ borderRadius: 0 })`,
|
||||
output: `css({ borderRadius: 'unset' })`,
|
||||
errors: [noZeroValueError],
|
||||
},
|
||||
{
|
||||
code: `css({ borderRadius: '0px' })`,
|
||||
output: `css({ borderRadius: 'unset' })`,
|
||||
errors: [noZeroValueError],
|
||||
},
|
||||
{
|
||||
code: `css({ borderRadius: "0%" })`,
|
||||
output: `css({ borderRadius: 'unset' })`,
|
||||
errors: [noZeroValueError],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -27,14 +27,14 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
borderRadius: theme.shape.radius.default,
|
||||
|
||||
'> .button-group:not(:first-child) > button, > button:not(:first-child)': {
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderTopLeftRadius: 'unset',
|
||||
borderBottomLeftRadius: 'unset',
|
||||
borderLeft: `1px solid rgba(255, 255, 255, 0.12)`,
|
||||
},
|
||||
|
||||
'> .button-group:not(:last-child) > button, > button:not(:last-child)': {
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
borderTopRightRadius: 'unset',
|
||||
borderBottomRightRadius: 'unset',
|
||||
borderRight: `1px solid rgba(0, 0, 0, 0.12)`,
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -168,8 +168,8 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }:
|
||||
'&:not(:first-child):last-child': {
|
||||
'> input': {
|
||||
borderLeft: 'none',
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderTopLeftRadius: 'unset',
|
||||
borderBottomLeftRadius: 'unset',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -177,8 +177,8 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }:
|
||||
'&:first-child:not(:last-child)': {
|
||||
'> input': {
|
||||
borderRight: 'none',
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
borderTopRightRadius: 'unset',
|
||||
borderBottomRightRadius: 'unset',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -186,10 +186,10 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }:
|
||||
'&:not(:first-child):not(:last-child)': {
|
||||
'> input': {
|
||||
borderRight: 'none',
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderTopRightRadius: 'unset',
|
||||
borderBottomRightRadius: 'unset',
|
||||
borderTopLeftRadius: 'unset',
|
||||
borderBottomLeftRadius: 'unset',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -238,20 +238,20 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }:
|
||||
position: 'relative',
|
||||
|
||||
'&:first-child': {
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
borderTopRightRadius: 'unset',
|
||||
borderBottomRightRadius: 'unset',
|
||||
'> :last-child': {
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
borderTopRightRadius: 'unset',
|
||||
borderBottomRightRadius: 'unset',
|
||||
},
|
||||
},
|
||||
|
||||
'&:last-child': {
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderTopLeftRadius: 'unset',
|
||||
borderBottomLeftRadius: 'unset',
|
||||
'> :first-child': {
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderTopLeftRadius: 'unset',
|
||||
borderBottomLeftRadius: 'unset',
|
||||
},
|
||||
},
|
||||
'> *:focus': {
|
||||
@@ -266,8 +266,8 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }:
|
||||
paddingLeft: theme.spacing(1),
|
||||
paddingRight: theme.spacing(0.5),
|
||||
borderRight: 'none',
|
||||
borderTopRightRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
borderTopRightRadius: 'unset',
|
||||
borderBottomRightRadius: 'unset',
|
||||
})
|
||||
),
|
||||
suffix: cx(
|
||||
@@ -277,8 +277,8 @@ export const getInputStyles = stylesFactory(({ theme, invalid = false, width }:
|
||||
paddingLeft: theme.spacing(1),
|
||||
paddingRight: theme.spacing(1),
|
||||
borderLeft: 'none',
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderTopLeftRadius: 'unset',
|
||||
borderBottomLeftRadius: 'unset',
|
||||
right: 0,
|
||||
})
|
||||
),
|
||||
|
||||
@@ -118,8 +118,8 @@ export class RefreshPicker extends PureComponent<Props> {
|
||||
{!noIntervalPicker && (
|
||||
<ButtonSelect
|
||||
className={css({
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderTopLeftRadius: 'unset',
|
||||
borderBottomLeftRadius: 'unset',
|
||||
})}
|
||||
value={selectedValue}
|
||||
options={options}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { omit } from 'lodash';
|
||||
import { InputHTMLAttributes } from 'react';
|
||||
import * as React from 'react';
|
||||
@@ -26,19 +25,6 @@ export interface Props extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onRe
|
||||
interactive?: boolean;
|
||||
}
|
||||
|
||||
const getSecretFormFieldStyles = () => {
|
||||
return {
|
||||
noRadiusInput: css({
|
||||
borderBottomRightRadius: '0 !important',
|
||||
borderTopRightRadius: '0 !important',
|
||||
}),
|
||||
noRadiusButton: css({
|
||||
borderBottomLeftRadius: '0 !important',
|
||||
borderTopLeftRadius: '0 !important',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Form field that has 2 states configured and not configured. If configured it will not show its contents and adds
|
||||
* a reset button that will clear the input and makes it accessible. In non configured state it behaves like normal
|
||||
@@ -58,7 +44,6 @@ export const SecretFormField = ({
|
||||
interactive,
|
||||
...inputProps
|
||||
}: Props) => {
|
||||
const styles = getSecretFormFieldStyles();
|
||||
return (
|
||||
<FormField
|
||||
label={label!}
|
||||
@@ -70,7 +55,7 @@ export const SecretFormField = ({
|
||||
<>
|
||||
<input
|
||||
type="text"
|
||||
className={cx(`gf-form-input width-${inputWidth}`, styles.noRadiusInput)}
|
||||
className={`gf-form-input width-${inputWidth}`}
|
||||
disabled={true}
|
||||
value="configured"
|
||||
{...omit(inputProps, 'value')}
|
||||
|
||||
@@ -6,6 +6,7 @@ package extensions
|
||||
import (
|
||||
_ "cloud.google.com/go/kms/apiv1"
|
||||
_ "cloud.google.com/go/kms/apiv1/kmspb"
|
||||
_ "cloud.google.com/go/spanner"
|
||||
_ "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
_ "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys"
|
||||
_ "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault"
|
||||
@@ -29,13 +30,6 @@ import (
|
||||
_ "github.com/spf13/cobra" // used by the standalone apiserver cli
|
||||
_ "github.com/stretchr/testify/require"
|
||||
_ "golang.org/x/time/rate"
|
||||
_ "k8s.io/api"
|
||||
_ "k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
_ "k8s.io/apimachinery/pkg/util/proxy"
|
||||
_ "k8s.io/kube-aggregator/pkg/apiserver/scheme"
|
||||
_ "k8s.io/kube-aggregator/pkg/generated/openapi"
|
||||
_ "k8s.io/kube-aggregator/pkg/registry/apiservice/rest"
|
||||
_ "sigs.k8s.io/randfill"
|
||||
_ "xorm.io/builder"
|
||||
|
||||
_ "github.com/grafana/authlib/authn"
|
||||
|
||||
@@ -99,8 +99,8 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
display: 'flex',
|
||||
// No border for second element (inputs) as label and input border is shared
|
||||
'> :nth-child(2)': css({
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderTopLeftRadius: 'unset',
|
||||
borderBottomLeftRadius: 'unset',
|
||||
}),
|
||||
}),
|
||||
labelWrapper: css({
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ exports[`VariableQueryEditor renders correctly 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="css-1h17wob-input-suffix"
|
||||
class="css-1ms3s8l-input-suffix"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
|
||||
Reference in New Issue
Block a user