Skip to content

Commit

Permalink
media-prefers-color-scheme
Browse files Browse the repository at this point in the history
  • Loading branch information
YozhikM committed Nov 9, 2018
1 parent d5ea611 commit c9abaef
Show file tree
Hide file tree
Showing 6 changed files with 387 additions and 88 deletions.
26 changes: 13 additions & 13 deletions README.md
Expand Up @@ -20,22 +20,21 @@ Please refer to [stylelint docs](http://stylelint.io/user-guide/) for the detail
- ⭐️ - the mark of recommended rules.
- ✒️ - the mark of fixable rules.


| | Rule ID | Description |
| :--- | :----------------------------------------------------------------------------------------- | :---------------------------------------------------------------------- |
| | [content-property-no-static-value](./src/rules/content-property-no-static-value/README.md) | Disallow unaccessible CSS generated content in pseudo-elements |
| | [font-size-is-readable](./src/rules/font-size-is-readable/README.md) | Disallow font sizes less than `15px` |
| | [line-height-is-vertical-rhythmed](./src/rules/line-height-is-vertical-rhythmed/README.md) | Disallow not vertical rhythmed `line-height` |
| | Rule ID | Description |
| :---- | :----------------------------------------------------------------------------------------- | :---------------------------------------------------------------------- |
| | [content-property-no-static-value](./src/rules/content-property-no-static-value/README.md) | Disallow unaccessible CSS generated content in pseudo-elements |
| | [font-size-is-readable](./src/rules/font-size-is-readable/README.md) | Disallow font sizes less than `15px` |
| | [line-height-is-vertical-rhythmed](./src/rules/line-height-is-vertical-rhythmed/README.md) | Disallow not vertical rhythmed `line-height` |
| ⭐️✒️ | [media-prefers-reduced-motion](./src/rules/media-prefers-reduced-motion/README.md) | Require certain styles if the animation or transition in media features |
| | [no-display-none](./src/rules/no-display-none/README.md) | Disallow content hiding with `display: none` property |
| | [no-obsolete-attribute](./src/rules/no-obsolete-attribute/README.md) | Disallow obsolete attribute using |
| | [no-obsolete-element](./src/rules/no-obsolete-element/README.md) | Disallow obsolete selectors using |
| | [no-spread-text](./src/rules/no-spread-text/README.md) | Require width of text in a comfortable range |
| | [media-prefers-color-scheme](./src/rules/media-prefers-color-scheme/README.md) | Require implementation of certain styles for selectors with colors. |
| | [no-display-none](./src/rules/no-display-none/README.md) | Disallow content hiding with `display: none` property |
| | [no-obsolete-attribute](./src/rules/no-obsolete-attribute/README.md) | Disallow obsolete attribute using |
| | [no-obsolete-element](./src/rules/no-obsolete-element/README.md) | Disallow obsolete selectors using |
| | [no-spread-text](./src/rules/no-spread-text/README.md) | Require width of text in a comfortable range |
| ⭐️ | [no-outline-none](./src/rules/no-outline-none/README.md) | Disallow outline clearing |
| | [no-text-align-justify](./src/rules/no-text-align-justify/README.md) | Disallow content with `text-align: justify` |
| | [no-text-align-justify](./src/rules/no-text-align-justify/README.md) | Disallow content with `text-align: justify` |
| ⭐️✒️ | [selector-pseudo-class-focus](./src/rules/selector-pseudo-class-focus/README.md) | Require or disallow a pseudo-element to the selectors with `:hover` |


## Recommended config

```json
Expand All @@ -52,7 +51,8 @@ Please refer to [stylelint docs](http://stylelint.io/user-guide/) for the detail
"a11y/no-spread-text": [true, { "severity": "warning" }],
"a11y/no-obsolete-attribute": [true, { "severity": "warning" }],
"a11y/no-obsolete-element": [true, { "severity": "warning" }],
"a11y/no-text-align-justify": [true, { "severity": "warning" }]
"a11y/no-text-align-justify": [true, { "severity": "warning" }],
"a11y/media-prefers-color-scheme": [true, { "severity": "warning" }]
}
}
```
Expand Down
2 changes: 2 additions & 0 deletions src/rules/index.js
Expand Up @@ -9,6 +9,7 @@ import noOutlineNone from './no-outline-none';
import noSpreadText from './no-spread-text';
import noTextAlignJustify from './no-text-align-justify';
import selectorPseudoClassFocus from './selector-pseudo-class-focus';
import mediaPrefersColorScheme from './media-prefers-color-scheme';

export default {
'content-property-no-static-value': contentPropertyNoStaticValue,
Expand All @@ -22,4 +23,5 @@ export default {
'no-spread-text': noSpreadText,
'no-text-align-justify': noTextAlignJustify,
'selector-pseudo-class-focus': selectorPseudoClassFocus,
'media-prefers-color-scheme': mediaPrefersColorScheme,
};
72 changes: 72 additions & 0 deletions src/rules/media-prefers-color-scheme/README.md
@@ -0,0 +1,72 @@
# media-prefers-color-scheme

Require implementation of certain styles for selectors with colors.

**Sources:**

- [Docs](https://drafts4.csswg.org/mediaqueries-5/#prefers-color-scheme)
- [less mixin](https://brehaut.net/blog/2018/a_dark_mode_less_mixin)
- [Webkit](https://trac.webkit.org/changeset/237156/webkit)
- [Safari TP](https://webkit.org/blog/8475/release-notes-for-safari-technology-preview-68)
- [Mojave](https://www.apple.com/lae/macos/mojave)

## Options

### true

The following pattern are considered violations:

```css
.foo {
color: red;
}
```

```css
.bar {
color: red;
}
.baz {
background-color: red;
}
@media screen and (prefers-color-scheme: dark) {
.baz {
background-color: white;
}
}
```

```css
.foo {
color: red;
}
@media screen and (prefers-color-scheme: dark) {
.foo {
background-color: red;
}
}
```

The following patterns are _not_ considered violations:

```css
.foo {
color: red;
}
@media screen and (prefers-color-scheme: dark) {
.foo {
color: white;
}
}
```

```css
.bar {
background-color: white;
}
@media screen and (prefers-color-scheme: dark) {
.bar {
background-color: gray;
}
}
```
56 changes: 56 additions & 0 deletions src/rules/media-prefers-color-scheme/__tests__/index.js
@@ -0,0 +1,56 @@
import rule, { messages, ruleName } from '../index';

testRule(rule, {
ruleName,
config: [true],

accept: [
{
code: 'a { }',
},
{
code:
'.foo { color: red } @media screen and (prefers-color-scheme: dark) { .foo { color: blue } }',
},
{
code:
'.bar { background-color: red } @media screen and (prefers-color-scheme: dark) { .bar { background-color: blue } }',
},
],

reject: [
{
code: 'a { color: red; }',
message: messages.expected('a'),
line: 1,
column: 4,
},
{
code:
'a { color: red; } @media screen and (prefers-color-scheme: dark) { a { background-color: red; } }',
message: messages.expected('a'),
line: 1,
column: 4,
},
{
code: '.foo { background-color: red;}',
message: messages.expected('.foo'),
line: 1,
column: 4,
},
{
code:
'.bar { color: red; } .baz { background-color: red; } @media screen and (prefers-color-scheme: dark) { .baz { color: blue; } }',
message: messages.expected('.bar'),
line: 1,
column: 4,
},
{
code:
'.foo { background-color: red; } @media screen and (prefers-color-scheme) { .foo { color: red; } }',
message: messages.expected('.foo'),
line: 1,
column: 4,
},
],
});
115 changes: 115 additions & 0 deletions src/rules/media-prefers-color-scheme/index.js
@@ -0,0 +1,115 @@
import { utils } from 'stylelint';
import isStandardSyntaxRule from 'stylelint/lib/utils/isStandardSyntaxRule';
import isStandardSyntaxSelector from 'stylelint/lib/utils/isStandardSyntaxSelector';
import isStandardSyntaxAtRule from 'stylelint/lib/utils/isStandardSyntaxAtRule';
import isCustomSelector from 'stylelint/lib/utils/isCustomSelector';

export const ruleName = 'a11y/media-prefers-color-scheme';

export const messages = utils.ruleMessages(ruleName, {
expected: selector => `Expected ${selector} is used with @media (prefers-color-scheme)`,
});
const targetProperties = ['background-color', 'color'];

function check(selector, node) {
const declarations = node.nodes;
const params = node.parent.params;
const parentNodes = node.parent.nodes;

if (!declarations) return true;

if (!isStandardSyntaxSelector(selector)) {
return true;
}

if (isCustomSelector(selector)) {
return true;
}

let currentSelector = null;

const declarationsIsMatched = declarations.some(declaration => {
const noMatchedParams = !params || params.indexOf('prefers-color-scheme') === -1;
const index = targetProperties.indexOf(declaration.prop);
currentSelector = targetProperties[index];

return index >= 0 && noMatchedParams;
});

if (!declarationsIsMatched) return true;

if (declarationsIsMatched) {
const parentMatchedNode = parentNodes.some(parentNode => {
if (!parentNode || !parentNode.nodes) return;
return parentNode.nodes.some(childrenNode => {
const childrenNodes = childrenNode.nodes;

if (
!parentNode.params ||
!Array.isArray(childrenNodes) ||
selector !== childrenNode.selector
)
return false;

const matchedChildrenNodes = childrenNodes.some(declaration => {
const index = targetProperties.indexOf(declaration.prop);
if (currentSelector !== targetProperties[index]) return false;

return index >= 0 && parentNode.params.indexOf('prefers-color-scheme') >= 0;
});

return matchedChildrenNodes;
});
});

if (!parentMatchedNode) return false;

return true;
}

return true;
}

export default function(actual) {
return (root, result) => {
const validOptions = utils.validateOptions(result, ruleName, { actual });

if (!validOptions || !actual) {
return;
}

root.walk(node => {
let selector = null;

if (node.type === 'rule') {
if (!isStandardSyntaxRule(node)) {
return;
}

selector = node.selector;
} else if (node.type === 'atrule' && node.name === 'page' && node.params) {
if (!isStandardSyntaxAtRule(node)) {
return;
}

selector = node.params;
}

if (!selector) {
return;
}

const isAccepted = check(selector, node);

if (!isAccepted) {
utils.report({
index: node.lastEach,
message: messages.expected(selector),
node,
ruleName,
result,
});
}
});
};
}

0 comments on commit c9abaef

Please sign in to comment.