Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .nx/version-plans/version-plan-1784663569372.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
eslint-plugin-gamut: minor
gamut-styles: major
variance: minor
gamut: major
---

Adding new zIndex scale to Gamut. Inclues a new eslint rule for avoiding raw z-index values. Allows variance scales to include raw values if need be.
2 changes: 2 additions & 0 deletions packages/eslint-plugin-gamut/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import gamutImportPaths from './gamut-import-paths';
import noCssStandalone from './no-css-standalone';
import noInlineStyle from './no-inline-style';
import noKbdElement from './no-kbd-element';
import noRawZIndex from './no-raw-z-index';
import preferThemed from './prefer-themed';
import recommended from './recommended';

Expand All @@ -10,6 +11,7 @@ const rules = {
'no-css-standalone': noCssStandalone,
'no-inline-style': noInlineStyle,
'no-kbd-element': noKbdElement,
'no-raw-z-index': noRawZIndex,
'prefer-themed': preferThemed,
};

Expand Down
55 changes: 55 additions & 0 deletions packages/eslint-plugin-gamut/src/no-raw-z-index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ESLintUtils } from '@typescript-eslint/utils';

import rule from './no-raw-z-index';

const ruleTester = new ESLintUtils.RuleTester({
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
});

ruleTester.run('no-raw-z-index', rule, {
valid: [
// Semantic tokens are the expected usage.
`const styles = { zIndex: zIndexes.modal };`,
`<Box zIndex={zIndexes.stickyHeader} />;`,
// Arithmetic on a token is allowed (e.g. Tip's shadow).
`const styles = { zIndex: zIndexes.foreground - 2 };`,
`<Box zIndex={zIndexes.modal + 5} />;`,
// Variables / non-literal expressions are not flagged.
`<Box zIndex={zIndex} />;`,
`const styles = { zIndex };`,
// Unrelated properties.
`const styles = { padding: 0 };`,
`<Box top={0} />;`,
],
invalid: [
{
code: `const styles = { zIndex: 1 };`,
errors: [{ messageId: 'noRawZIndex' }],
},
{
code: `const styles = { zIndex: 0 };`,
errors: [{ messageId: 'noRawZIndex' }],
},
{
code: `const styles = { zIndex: -1 };`,
errors: [{ messageId: 'noRawZIndex' }],
},
{
code: `const styles = { 'z-index': 100 };`,
errors: [{ messageId: 'noRawZIndex' }],
},
{
code: `<Box zIndex={2} />;`,
errors: [{ messageId: 'noRawZIndex' }],
},
{
code: `<Box zIndex={-1} />;`,
errors: [{ messageId: 'noRawZIndex' }],
},
],
});
62 changes: 62 additions & 0 deletions packages/eslint-plugin-gamut/src/no-raw-z-index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils';

import { createRule } from './createRule';

/**
* True for a numeric literal, including a negated one like `-1`.
*/
const isNumericLiteral = (node: TSESTree.Node | null | undefined): boolean => {
if (!node) return false;
if (node.type === AST_NODE_TYPES.Literal && typeof node.value === 'number') {
return true;
}
return (
node.type === AST_NODE_TYPES.UnaryExpression &&
(node.operator === '-' || node.operator === '+') &&
isNumericLiteral(node.argument)
);
};

const isZIndexKey = (key: TSESTree.Node): boolean =>
(key.type === AST_NODE_TYPES.Identifier && key.name === 'zIndex') ||
(key.type === AST_NODE_TYPES.Literal && key.value === 'zIndex') ||
(key.type === AST_NODE_TYPES.Literal && key.value === 'z-index');

export default createRule({
create(context) {
return {
// Style objects: `{ zIndex: 1 }` / `{ 'z-index': 1 }`
Property(node) {
if (isZIndexKey(node.key) && isNumericLiteral(node.value)) {
context.report({ messageId: 'noRawZIndex', node: node.value });
}
},
// JSX props: `<Box zIndex={1} />`
JSXAttribute(node) {
if (
node.name.type === AST_NODE_TYPES.JSXIdentifier &&
node.name.name === 'zIndex' &&
node.value?.type === AST_NODE_TYPES.JSXExpressionContainer &&
isNumericLiteral(node.value.expression as TSESTree.Node)
) {
context.report({ messageId: 'noRawZIndex', node: node.value });
}
},
};
},
defaultOptions: [],
meta: {
docs: {
description:
'Discourage raw numeric z-index values which can lead to z-index stacking issues and encourage usage of semantic tokens from the `zIndexes` scale.',
recommended: 'error',
},
messages: {
noRawZIndex:
'Semantic tokens from the `zIndexes` scale (e.g. `zIndexes.modal`) are recommendedinstead of a raw z-index number. For a deliberate in-between value, disable this rule inline with a justifying comment.',
},
type: 'suggestion',
schema: [],
},
name: 'no-raw-z-index',
});
1 change: 1 addition & 0 deletions packages/eslint-plugin-gamut/src/recommended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export default {
rules: {
'gamut/no-css-standalone': 'error',
'gamut/no-inline-style': 'error',
'gamut/no-raw-z-index': 'error',
'gamut/prefer-themed': 'off',
'gamut/gamut-import-paths': 'error',
},
Expand Down
Loading
Loading