-
Notifications
You must be signed in to change notification settings - Fork 842
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[CSS-in-JS] Add logicalShorthandCSS
util + lint rule
#6429
Conversation
// Construct a regex to find shorthands with more than one value | ||
// Shorthands with only 1 value set (applied to all 4 sides) are fine | ||
const logicalShorthandsRegexes = _shorthands.map((property) => { | ||
const start = `${property}:\\s*`; // e.g. `margin: ` | ||
const interpolatedVar = '\\${[^}]+}'; // e.g. `${euiTheme.something}` | ||
const staticValue = '[^\\s(!};]'; // e.g. `20px` - any value that doesn't have a space, !keyword, } interpolation, or semicolon | ||
const value = `(${interpolatedVar}|${staticValue})+`; // Values can be variable or static | ||
const end = ';'; // Ensure that the match ends with a semicolon | ||
|
||
return `(${start}(${value}\\s+)+${value}${end})`; // Only match multiple values (spaces between values) | ||
}); | ||
const logicalShorthandsRegex = logicalShorthandsRegexes.join('|'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just want to mention a couple things: I'm aware this is a moderately insane regex, so apologies for that. I tried to make it easier to read by splitting up the various parts but I'm not 100% sure I succeeded 🥲
I will say breaking it up like this does make it a little difficult to easily copy/paste into (e.g.) an online regex tester. For future me's records, my recommendation is doing this:
- Add
console.log(logicalShorthandsRegex);
below line 27 - Run
yarn jest scripts/eslint-plugin/css_logical_properties.test.js
and copy the console log output - Paste just one property regex into your regex tool (rather than all joined regexes), like so:
- Debug/play around with the various portions accordingly to try and solve whatever false positive or negative may be occurring
- Update Jest unit tests after to ensure said false positive/negative is or isn't caught by the linter
const templateContents = node.quasis || []; | ||
templateContents.forEach((quasi) => { | ||
const stringLiteral = quasi?.value?.raw; | ||
if (!stringLiteral) return; | ||
const stringLiteral = context.getSourceCode().getText(node); | ||
const content = stringLiteral.replace(/^`|`$/g, ''); // Strip starting/ending backtick |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just want to quickly highlight why this change to context.getSourceCode().getText(node)
(docs) was necessary:
If getText()
isn't used, AST unfortunately breaks up node.quasis
between each ${interpolatedVar}
usage, meaning a basic padding: 0 ${euiTheme.size.s};
line isn't correctly caught/linted.
Here's an example of how AST breaks down template literal quasis which you can see yourself via AST Explorer:
For the purposes of our lint rule for EUI, since we don't necessarily care what the var interpolates to[1], just that we want to check if there's more than one var, so getting the flat/non-interpolated text is the solution. It also incidentally saves us from another forEach
iteration so is possibly more performant in any case.
[1] This is because our vars are generally pretty safe... although if someone does something like const foo = '0 1 2 3'; padding: ${foo};
our linter would not catch this 🤔 Hopefully we just don't do that kind of shenanigans though.
Preview documentation changes for this PR: https://eui.elastic.co/pr_6429/ |
1 similar comment
Preview documentation changes for this PR: https://eui.elastic.co/pr_6429/ |
929b747
to
7611e97
Compare
While QAing the updated components, I realized that my logical CSS properties for |
- which automatically handles converting CSS shorthands for 4 box sides to 2 separate logical properties - see codeblock comment for more info - a single property/keyword does not yet exist, but when one is added the CSS spec we can flip this util over
- single values are ok; no need to make them logical if they're the same on all sides - no `fix` option for this sadly, since the extra logic would be overly complex (either have to handle newlines/indentations, or have to handle a util import, both of which feel overly complex) - note: `context.getSourceCode().getText(node)` is needed here over iterating through quasi's, because AST breaks up the template literal by vars found in within the literal, whereas we really only want to evaluate the exact text of variable usage itself - this actually also saves us from another iteration forEach so is unintentionally a slight perf boost(?)
…to the single util
- requires a slightly different syntax from margin/padding/inset
me: i hate math also me: i'm gonna spend several hours doing what basically amounts to light geometry and matrices
- noticed while grepping for shorthand properties
- was triggering lint rule
+ fix other example title
Preview documentation changes for this PR: https://eui.elastic.co/pr_6429/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for adding the background into this PR. Out of curiosity, was this broken and we didn't realize it?
I pulled the branch and played around the the regex that was created in an online editor. The regex is crazy, but your solution is very elegant!
With the introduction of logicalBorderRadiusCSS
, should we add some documentation to the Borders page like we did with Size?
switch (values.length) { | ||
case 1: | ||
// If it's the same value all around, no need to use logical properties | ||
return `${property}: ${value};`; | ||
case 2: | ||
verticalBlockValue = values[0]; | ||
horizontalInlineValue = values[1]; | ||
break; | ||
case 3: | ||
verticalBlockValue = `${values[0]} ${values[2]}`; | ||
horizontalInlineValue = values[1]; | ||
break; | ||
case 4: | ||
default: | ||
verticalBlockValue = `${values[0]} ${values[2]}`; | ||
horizontalInlineValue = `${values[3]} ${values[1]}`; // Note: left (4th value) comes before right (2nd value) | ||
break; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love this. It feels very self-explanatory when reading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woohoo, very satisfying to know! Also just to quickly address one of your top level q's in this thread:
Out of curiosity, was this broken and we didn't realize it?
Our logical CSS work has been more of a speculative enhancement. There isn't (yet) a specific need to support direction
or writing-mode
in Kibana or Elastic, but since it's easier for EUI to set the standard and other apps and teams follow, we're kind of making it up as we go.
With the introduction of logicalBorderRadiusCSS, should we add some documentation to the Borders page like we did with Size?
The logicalShorthandCSS
actually supports border radius (just without the ignoreZeroes
) flag, so I didn't know if I needed extra documentation for this - I mostly wanted the ignoreZeroes
option for our own EUI css where we know we're not overriding existing border-radius 🤔
I'll think about this more and circle back on it! To be honest, I'm hoping we can eventually remove that special logic totally if W3 comes out with a more handy CSS spec where the API remains the same except for a keyword.
const logicalShorthandsRegexes = _shorthands.map((property) => { | ||
const start = `${property}:\\s*`; // e.g. `margin: ` | ||
const interpolatedVar = '\\${[^}]+}'; // e.g. `${euiTheme.something}` | ||
const staticValue = '[^\\s(!};]'; // e.g. `20px` - any value that doesn't have a space, !keyword, } interpolation, or semicolon | ||
const value = `(${interpolatedVar}|${staticValue})+`; // Values can be variable or static | ||
const end = ';'; // Ensure that the match ends with a semicolon | ||
|
||
return `(${start}(${value}\\s+)+${value}${end})`; // Only match multiple values (spaces between values) | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧠 Played around with the margin
and scroll-margin
regex patterns in an online playground and tried to come up with various patterns. From what I tested, the regex seems solid to me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Woohoo! I'm sure it's not bulletproof, but it should hopefully capture 90%+ of common use cases, and we can continue to tweak (or line-disable if needed) it as we go on.
eui@70.4.0 ⏩ eui@71.0.0 --- ## [`71.0.0`](https://github.com/elastic/eui/tree/v71.0.0) - Implemented new `EuiRange` and `EuiDualRange` designs where the `levels` are now on top of the tracks ([#6092](elastic/eui#6092)) - Added `discuss` and `dotInCircle` glyphs to `EuiIcon` ([#6434](elastic/eui#6434)) - Added `article` glyph to `EuiIcon` ([#6437](elastic/eui#6437)) - Changed the `EuiProvider` usage warnings to not rely on development mode. ([#6451](elastic/eui#6451)) **Breaking changes** - `EuiDualRange` now explicitly requires both `min` and `max` via props types, to match `EuiRange` ([#6092](elastic/eui#6092)) - `EuiRange` and `EuiDualRange`'s `compressed` size no longer impacts track or level sizes, but continues to compress tick and input sizes. ([#6092](elastic/eui#6092)) - Removed all variables for the following components from EUI's theme JSON files: ([#6443](elastic/eui#6443)) - `euiCollapsibleNav*` - `euiColorPicker*` - `euiContextMenu*` - `euiControlBar*` - `euiDataGrid* `(except for z-indices and cell padding sizes) - `euiDatePicker*` - `euiSuperDatePicker*` - `euiDragAndDrop*` - `euiEuiEmptyPrompt*` - `euiFilePicker*` - `euiRange*` - `euiHeaderLinks*` - `euiKeyPad*` - `euiMarkdownEditor*` - `euiResizable*` - `euiSelectable*` - `euiSideNav*` - `euiStep*` - `euiSuggest*` - `euiTable*` (except for color variables) - `euiTooltip*` - `euiButtonFontWeight`, `euiButtonDefaultTransparency`, and `euiButtonMinWidth` - If you were importing any of the above removed JSON variables, we strongly recommend using generic color or sizing variables from `useEuiTheme()` instead. ([#6443](elastic/eui#6443)) **CSS-in-JS conversions** - Converted `EuiRange` and `EuiDualRange` to Emotion; Removed `$euiRangeThumbRadius` ([#6092](elastic/eui#6092)) - Added a new `logicalStyles` utility that automatically converts all non-logical properties in a `style` object to their corresponding logical properties ([#6426](elastic/eui#6426)) - Added a new `logicalShorthandCSS` utility that automatically converts `margin`, `padding`, and other 4-sided shorthands to their corresponding logical properties ([#6429](elastic/eui#6429)) - Added a new `logicalBorderRadiusCSS` utility that automatically converts `border-radius` to corresponding logical properties ([#6429](elastic/eui#6429)) Co-authored-by: Constance Chen <constance.chen@elastic.co> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>
Summary
Unfortunately, shorthand properties that describe boxes such as
margin
andpadding
do not currently automatically respond to logical changes in display direction (see w3c/csswg-drafts#1282).Until an official spec is created by W3C, this utility is a stopgap measure to convert (e.g.)
margin
shorthands tomargin-inline
andmargin-block
respectively.Example usage within Emotion (intentionally similar in usage to
logicalCSS()
):becomes
I've also added a lint rule for this, which flags shorthand usage with multiple values. Single values (same value applied to all 4 sides) do not need to be converted to a logical property, and both the lint rule and utility intelligently detect this.
Note: there is no
--fix
behavior currently as I deemed it too unnecessarily complex to try and automate a fix - devs will have to manually import and calllogicalShorthandCSS
. Should W3C implement a single-line shorthand keyword or property, this will become much easier to do at that point, so I would strongly suggest waiting for that.I strongly recommend following along by commit with whitespace changes disabled.
QA
Checked the following components for CSS output and visual smoke test:
General checklist