Skip to content

Commit 5aad195

Browse files
authored
fix(hooks): Tweak styling hooks automated fallback extraction (#4904)
1 parent 819245d commit 5aad195

File tree

7 files changed

+170
-50
lines changed

7 files changed

+170
-50
lines changed

.storybook/scss/ui/index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ html {
3232
td {
3333
padding-top: 0.375rem;
3434
padding-bottom: 0.375rem;
35+
white-space: normal;
3536
}
3637

3738
.hooks-table__section {

scripts/__tests__/var-extract.spec.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ describe('extractVarsFromCSS', () => {
2626
expect(actual[styleHookName].fallbackValue).toEqual('');
2727
});
2828

29+
it('extracts vars from malformed var function value with no fallback', () => {
30+
const actual = extractVarsFromCSS(
31+
'.foo { background-color: var(--sds-c-icon-color-background,); }'
32+
);
33+
34+
const styleHookName = Object.keys(actual)[0];
35+
36+
expect(styleHookName).toEqual('--sds-c-icon-color-background');
37+
expect(actual[styleHookName].fallbackValue).toEqual('');
38+
});
39+
2940
it('extracts vars only from allow pattern', () => {
3041
const actual = extractVarsFromCSS(
3142
`.foo {
@@ -63,4 +74,30 @@ describe('extractVarsFromCSS', () => {
6374
expect(actual[styleHookName1].fallbackValue).toEqual('1rem');
6475
expect(actual[styleHookName2].category).toEqual('Color');
6576
});
77+
78+
it('extracts vars from nested var functions value with fallback', () => {
79+
const actual = extractVarsFromCSS(
80+
'.foo { background-color: var(--slds-c-icon-color-background, var(--sds-c-icon-color-background, transparent)); }'
81+
);
82+
83+
const styleHookName = Object.keys(actual)[0];
84+
85+
expect(styleHookName).toEqual('--slds-c-icon-color-background');
86+
expect(actual[styleHookName].fallbackValue).toEqual('transparent');
87+
expect(actual[styleHookName].category).toEqual('Color');
88+
expect(actual[styleHookName].valueType).toEqual('Color');
89+
});
90+
91+
it('extracts vars from deeply nested var functions value with fallback', () => {
92+
const actual = extractVarsFromCSS(
93+
'.foo { background-color: var(--slds-c-icon-color-background, var(--sds-c-icon-color-background, var(--sds-g-icon-color, transparent))); }'
94+
);
95+
96+
const styleHookName = Object.keys(actual)[0];
97+
98+
expect(styleHookName).toEqual('--slds-c-icon-color-background');
99+
expect(actual[styleHookName].fallbackValue).toEqual('transparent');
100+
expect(actual[styleHookName].category).toEqual('Color');
101+
expect(actual[styleHookName].valueType).toEqual('Color');
102+
});
66103
});

scripts/var-extract.js

Lines changed: 92 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,20 @@ const extractVarsFromSLDS = (props = {}) => {
2929
// getting list of components and utilities to parse
3030
const componentList = fs
3131
.readdirSync('./.generated/css/components')
32-
.map(name => `./.generated/css/components/${name}/**/index.css`);
32+
.map((name) => `./.generated/css/components/${name}/**/index.css`);
3333
const utilityList = fs
3434
.readdirSync('./.generated/css/utilities')
35-
.map(name => `./.generated/css/utilities/${name}/**/index.css`);
35+
.map((name) => `./.generated/css/utilities/${name}/**/index.css`);
3636

3737
// parse through all variants of each component / util
38-
componentList.concat(utilityList).map(fileGlob => {
38+
componentList.concat(utilityList).map((fileGlob) => {
3939
const cssFiles = glob.sync(fileGlob);
4040
let varsData = {};
4141

42-
cssFiles.map(filename => {
42+
cssFiles.map((filename) => {
4343
const cssContent = readFileSync(filename).toString();
4444
const fileVars = extractVarsFromCSS(cssContent, {
45-
allowPattern: varsAllowPattern
45+
allowPattern: varsAllowPattern,
4646
});
4747

4848
if (Object.keys(fileVars).length > 0) {
@@ -73,12 +73,12 @@ const extractVarsFromSLDS = (props = {}) => {
7373
*/
7474
const extractVarsFromCSS = (cssContent, options = {}) => {
7575
const ast = css.parse(cssContent);
76-
const rules = ast.stylesheet.rules.filter(rule => rule.type === 'rule');
76+
const rules = ast.stylesheet.rules.filter((rule) => rule.type === 'rule');
7777
const { allowPattern } = options;
7878
let list = {};
7979

80-
rules.map(rule => {
81-
const filtered = rule.declarations.filter(dec => {
80+
rules.map((rule) => {
81+
const filtered = rule.declarations.filter((dec) => {
8282
if (!dec.value) return false;
8383

8484
// match on var values that are not custom props
@@ -89,49 +89,102 @@ const extractVarsFromCSS = (cssContent, options = {}) => {
8989
return false;
9090
});
9191

92-
if (filtered.length > 0) {
93-
const vars = rule.declarations
94-
.filter(dec => (dec.value ? dec.value.match(/var\(/) : false))
95-
.map(dec => {
96-
const cssVar = dec.value.match(/(--.*?)[,|)]/)[1];
97-
const fallbackMatch = dec.value.match(/,\s(.*)\)/);
98-
const fallback = fallbackMatch ? fallbackMatch[1] : '';
99-
100-
return {
101-
[cssVar]: fallback
102-
};
103-
});
104-
105-
vars.forEach(cssVar => {
106-
const varName = Object.keys(cssVar)[0];
107-
if (varName) {
108-
const categories = Object.keys(propTypes);
109-
110-
const matchedCategory = categories.find(option => {
111-
if (varName.includes(option)) {
112-
return option;
113-
}
114-
});
92+
///
93+
// is this a CSS variable
94+
function isCssVarClause(value) {
95+
// if it DOES contain `var(` it is a CSS variable clause
96+
return !!value.includes('var(');
97+
}
11598

116-
list[varName] = { fallbackValue: cssVar[varName] };
99+
// is this a CSS variable reassignment
100+
function isCssVarReassignment(value) {
101+
// if it DOES NOT contain a comma it is a reassignment
102+
return !value.includes(',');
103+
}
104+
105+
// regex:
106+
// var\( -> find something that starts with `var(`
107+
// (?=\S*['-]*) -> positive lookahead, zero or more non-space characters and zero or more ' or - characters
108+
// ([a-zA-Z'-]+) -> match group 1, one or more alpha characters
109+
// \s*,\s* -> zero or more spaces, a comma, zero or more spaces
110+
// (.+) -> match group 2, one or more of any character
111+
// \) -> a closing parenthesis
112+
const varRegex = /var\((?=\S*['-]*)([a-zA-Z'-]+)\s*[,]?\s*(.*)\)/;
113+
114+
/**
115+
* Recursively drill into a CSS value to find the fallback value
116+
*
117+
* @param {string} - CSS property's value
118+
* @returns {object} - hookName, name of the hook; fallback, value of the fallback
119+
*/
120+
function findFallbackRecursively(value) {
121+
// get the fallback value from the CSS variable clause
122+
let regex = varRegex;
123+
let matches = value.match(regex);
124+
125+
if (matches) {
126+
let fallbackValue = matches[2];
127+
128+
// if we have another CSS variable clause then keep drilling in
129+
if (isCssVarClause(fallbackValue)) {
130+
return findFallbackRecursively(fallbackValue);
131+
} else {
132+
return matches[2].trim();
133+
}
134+
}
117135

118-
if (matchedCategory) {
119-
list[varName].category = propTypes[matchedCategory].type;
120-
list[varName].valueType = propTypes[
121-
matchedCategory
122-
].valueTypes.join('');
136+
// if we have a reassignment
137+
if (isCssVarReassignment(value)) {
138+
return null;
139+
}
140+
141+
return value;
142+
}
143+
///
144+
145+
if (filtered.length > 0) {
146+
filtered.forEach((decl) => {
147+
if (decl.value) {
148+
// search for a `var(...)` value
149+
let matches = decl.value.match(varRegex);
150+
// if we found a match then extract the hook name, if no hook then return null
151+
let hookName = matches ? matches[1] : null;
152+
// recursively search for the fallback value
153+
let fallback = findFallbackRecursively(decl.value);
154+
155+
// if we have a hook then generate an object to add to the list of results
156+
if (hookName) {
157+
// retrieve the category names from the JSON config
158+
const categories = Object.keys(propTypes);
159+
160+
// do we have a category match? If so return it
161+
const matchedCategory = categories.find((option) => {
162+
if (hookName.includes(option)) {
163+
return option;
164+
}
165+
});
166+
167+
// add the fallback value to the hook's object in the list of results
168+
list[hookName] = { fallbackValue: fallback };
169+
170+
// if a category was found add the metadata to the hook's object in the list of results
171+
if (matchedCategory) {
172+
list[hookName].category = propTypes[matchedCategory].type;
173+
list[hookName].valueType =
174+
propTypes[matchedCategory].valueTypes.join(', ');
175+
}
123176
}
124177
}
125178
});
126179
}
127180
});
128181

129182
return allowPattern
130-
? filterObject(list, key => RegExp(allowPattern).test(key))
183+
? filterObject(list, (key) => RegExp(allowPattern).test(key))
131184
: list;
132185
};
133186

134187
module.exports = {
135188
extractVarsFromCSS,
136-
extractVarsFromSLDS
189+
extractVarsFromSLDS,
137190
};

scripts/var-metadata.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,25 @@ export const propTypes = {
5050
shadow: {
5151
type: 'Box Shadow',
5252
valueTypes: ['String']
53+
},
54+
'text-color': {
55+
type: 'Color',
56+
valueTypes: ['Color']
57+
},
58+
'text-align': {
59+
type: 'Text',
60+
valueTypes: ['String']
61+
},
62+
'image-background': {
63+
type: 'Image',
64+
valueTypes: ['Image']
65+
},
66+
'size-background': {
67+
type: 'Image',
68+
valueTypes: ['String', 'Dimension']
69+
},
70+
'position-zindex': {
71+
type: 'Position',
72+
valueTypes: ['Integer']
5373
}
5474
};

shared/components/StylingHooksTable.jsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,15 @@ class StylingHooksTable extends Component {
100100
ii === categoryVars.length - 1
101101
})}
102102
>
103-
{ii === 0 ? (
103+
{ii === 0 && (
104104
<th
105105
className="hooks-table__col-category"
106106
scope="rowgroup"
107107
rowSpan={categoryVars.length}
108108
>
109109
{category}
110110
</th>
111-
) : null}
111+
)}
112112
<td>
113113
<div className="slds-grid">
114114
<Copy
@@ -124,15 +124,19 @@ class StylingHooksTable extends Component {
124124
</div>
125125
</td>
126126
<td>
127-
{varData.types.map((type, x) => {
127+
{varData.types.map((type, x, arr) => {
128128
const formattedType = formatType(type);
129129
return (
130-
<a
131-
key={`${category}-${i}-${ii}-${x}`}
132-
href={`https://developer.mozilla.org/en-US/docs/Web/CSS/${formattedType}`}
133-
>
134-
{type}
135-
</a>
130+
<>
131+
<a
132+
key={`${category}-${i}-${ii}-${x}`}
133+
href={`https://developer.mozilla.org/en-US/docs/Web/CSS/${formattedType}`}
134+
>
135+
{type}
136+
</a>
137+
{/* Proper comma separation when there is more than 1 valueType */}
138+
{(arr.length > 1 && x <= 0) && (', ')}
139+
</>
136140
);
137141
})}
138142
</td>

ui/components/rich-text-editor/RELEASENOTES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
# Rich Text Editor Release Notes
44

5+
## 2.17.0
6+
7+
### Added
8+
- Added `slds` as the default namespace with `sds` fallbacks for Styling Hooks.
9+
510
## 2.16.0
611

712
### Changed

ui/components/rich-text-editor/base/_index.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,8 @@
186186
*/
187187
.slds-rich-text-area__content {
188188
overflow-y: auto;
189-
min-height: var(--sds-c-textarea-sizing-min-height, $size-xx-small);
190-
max-height: var(--sds-c-textarea-sizing-max-height, $size-small);
189+
min-height: var(--slds-c-textarea-sizing-min-height, var(--sds-c-textarea-sizing-min-height, $size-xx-small));
190+
max-height: var(--slds-c-textarea-sizing-max-height, var(--sds-c-textarea-sizing-max-height, $size-small));
191191
padding: $spacing-medium;
192192
background-color: $color-background-input;
193193
}

0 commit comments

Comments
 (0)