Conversation
0eb4db4 to
327b86c
Compare
|
I fixed some bugs regarding merging the component's variables and styles. The values that we receive from the theme are in the following format: so I introduced some helpers methods for converting these lists to one array of callables and another one for merging the callables. The variables and the styles can now be overridden when using the components by providing the variables and styles props. Let me know if you have any feedback or need some more help with this. |
418e947 to
c6298fe
Compare
|
Thanks for wanting to help. Note, I had a lot of unpushed changes that included dealing with merging themes downstream on context. I've forced pushed those to save the work and resume progress here. I will fix conflicts and continue. |
c6298fe to
4464caf
Compare
4464caf to
a7e0599
Compare
a7e0599 to
9328b1b
Compare
docs/src/index.tsx
Outdated
| style: { fontWeight: 700 }, | ||
| }, | ||
| ] | ||
| console.log('THEME', theme) |
There was a problem hiding this comment.
seems to be a debug leftover
There was a problem hiding this comment.
Thanks, branch was still in progress until right now 👍
6e27732 to
af4ed15
Compare
Codecov Report
@@ Coverage Diff @@
## master #16 +/- ##
==========================================
+ Coverage 85.97% 86.29% +0.32%
==========================================
Files 76 39 -37
Lines 1112 664 -448
Branches 228 90 -138
==========================================
- Hits 956 573 -383
+ Misses 149 88 -61
+ Partials 7 3 -4
Continue to review full report at Codecov.
|
src/components/Avatar/Avatar.tsx
Outdated
| name={icon} | ||
| color="white" | ||
| style={avatarRules.presenceIcon()} | ||
| className={classes.presenceIcon} |
There was a problem hiding this comment.
This looks like a bug, will update.
| ? callable(labelPropVariables)() | ||
| : callable(Label.variables)() | ||
| const iconVariables = callable(iconProps.variables)() || {} | ||
| const labelVariables = callable(variables)() || {} |
There was a problem hiding this comment.
This may also need careful review.
The iconProps available at this point are produced by the shorthand factory. Any variables provided on iconProps at this point will not have access to the theme values on context so they cannot be resolved before being handed to the overrides function.
This means we will not be able to hand resolved variables to components reliably, unless we also make factories aware of how to resolve variables (which I think is a very bad idea). That would require making factories access context.
There was a problem hiding this comment.
To be clear, I don't have a solution or proposal for this right now but I think this PR needs to get merged. It is too difficult to maintain merge conflict fixes if we push it out.
There was a problem hiding this comment.
yes, agree with increasing complexity to address merge conflicts - will help you with merging this PR today.
src/components/List/listVariables.ts
Outdated
| vars.contentLineHeight = siteVars.lineHeightSmall | ||
|
|
||
| return vars | ||
| } |
There was a problem hiding this comment.
Moving this particular file represents a significant change. The ListItem variables were defined in the listVariables file, making them accessible on context under List instead of ListItem. I had to rename this file to listItemVariables and expose it on context as ListItem in order for our theming contract to work (components get variables and styles by their display name).
| <RendererProvider renderer={outgoingTheme.renderer}> | ||
| <ThemeProvider theme={outgoingTheme}>{children}</ThemeProvider> | ||
| </RendererProvider> | ||
| ) |
There was a problem hiding this comment.
This is important. Each Provider now:
- consumes the theme from context
- merges it with its theme prop
- passes the merged theme downstream
This enables nested cascading themes.
| }, target) | ||
| } | ||
|
|
||
| const mergeThemes = (...themes: IThemeInput[]): IThemePrepared => { |
There was a problem hiding this comment.
This function is best reviewed by looking at the tests, mergeThemes-test.ts.
| _htmlFontSize = fontSize || getComputedFontSize() | ||
| } | ||
|
|
||
| export default rem |
| PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.func]), | ||
| ), | ||
| rtl: PropTypes.bool, | ||
| theme: PropTypes.shape({ |
There was a problem hiding this comment.
Is this required? It looks to be based on the interface.
There was a problem hiding this comment.
Currently, docs are still generated from propTypes. That needs to be moved to being generated from typings. Then we can consider removing propTypes.
The other consideration is that most consumers are not running typescript. So, I think we may end up keeping both interfaces and propTypes around. Also, some runtime checks are really handy since we can do things with values (suggest icon names, etc).
There was a problem hiding this comment.
Sorry, I meant to say "should the theme propType be .isRequired?". I wasn't talking about the propTypes themselves.
There was a problem hiding this comment.
Got it, thanks. We have a scheduled task to make a default theme. For now, it is required.
src/lib/getClasses.ts
Outdated
|
|
||
| // root, icon, etc. | ||
| const componentParts: string[] = stylesArr.reduce((acc, next) => { | ||
| return next ? _.union(acc, _.keys(next)) : acc |
There was a problem hiding this comment.
next should always be truthy because of the above call to toCompactArray, if I'm understanding this correctly.
|
|
||
| /** | ||
| * Returns a string of HTML classes. | ||
| * Renders one or many component styles (objects of component parts) to the DOM. |
There was a problem hiding this comment.
What is this line about rendering to the DOM?
| }, target) | ||
| } | ||
|
|
||
| const mergeThemes = (...themes: IThemeInput[]): IThemePrepared => { |
There was a problem hiding this comment.
Nit: I think this would be more simple if it took an array of themes.
-
It doesn't really take multiple arguments. It takes a single collection of themes. Representing this with an appropriate data type makes this obvious.
-
It becomes possible to add additional arguments if needed.
-
It makes it easier to provide a list of themes to this function. You can do
mergeThemes(...myListOfThemes), but I would ask "why?" when you can just domergeThemes(myListOfThemes). -
Least importantly, it saves you casting
argumentsto an array.
Justified myself because you asked :).
| acc.rtl = mergeRTL(acc.rtl, next.rtl) | ||
|
|
||
| // Use the correct renderer for RTL | ||
| acc.renderer = acc.rtl ? felaRtlRenderer : felaRenderer |
There was a problem hiding this comment.
Should emptyTheme have a default renderer? It looks to be possible to never set renderer if next is never truthy. Same goes for rtl.
| @@ -0,0 +1,5 @@ | |||
| const toCompactArray = <T = any>(...values: T[]): T[] => { | |||
| return [].concat(...values).filter(Boolean) | |||
There was a problem hiding this comment.
You shouldn't have to spread values here. Also, why not _.compact?
There was a problem hiding this comment.
Spread is required as each value may be a single value or an array. we need to concat each argument.
_.compact only compacts a single array, it is essentially equivalent to [].filter(Boolean).
The feature I need is:
- Cast to array
- Allow items or arrays of items
- Compact the resulting array
So:
toCompactArray(1)
//=> [1]
toCompactArray(1, null, 2)
//=> [1, 2]
toCompactArray(1, 'two', [3, 4], null, ['more', 'stuff', undefined])
// => [1, 'two', 3, 4, 'more', 'stuff']A more literal name would be something quite verbose like castToFlattenedCompactArray() since it does all of these things.
You could also create it from _.compact(_.flatten(...values)), however, given how often it is used and on hot paths I went with a minimal custom function that we can more easily optimize. Lodash does a lot of fancy checks and handling that we don't need to waste more cycles on.
Example, we can already optimize this by using reduce and skipping the second loop via filter. We can just not concat empty values into the accumulator. You could also put an early return check for values.length === 1 scenarios, etc...
There was a problem hiding this comment.
Sure, but why can't you just pass an array when you call toCompactArray? 🤔
You'd still need to handle the flattening, of course.
| componentVariables = {}, | ||
| componentStyles = {}, | ||
| rtl = false, | ||
| renderer = felaRenderer, |
There was a problem hiding this comment.
See my previous comment about missing values for rtl and renderer. It would be a benefit to overall maintainability if the provider did all of the heavy lifting, and these consumers could be more confident about the interface they receive, since they don't have to work with all the same uncertainty.
| import { IComponentPartStylesInput, ICSSInJSStyle } from '../../../../../types/theme' | ||
|
|
||
| const accordionStyles: IComponentPartStylesInput = { | ||
| root: (): ICSSInJSStyle => ({ |
There was a problem hiding this comment.
Would it perhaps be easier to type the overall styles object as [key: string]: (): ICSSInJSStyle (or whatever the dammed syntax is) so that every individual style function doesn't have to declare its type?
There was a problem hiding this comment.
Yes, did this however it doesn't provide completions inside the style object for index keys. It will only do so if you type the return value. Even that has many shortcomings. Note the const accordionStyles: IComponentPartStylesInput, which is doing exactly what you are referring to.
Since this PR is being broken down into smaller bits, we'll save the typings for later.
|
|
||
| return { | ||
| ...rules, | ||
| ...(disabled |
There was a problem hiding this comment.
Nit: may be more maintainable to break this out into another function rather than inlining it.
| @@ -0,0 +1,21 @@ | |||
| export interface IDividerVariables { | |||
| [key: string]: string | number | |||
There was a problem hiding this comment.
Why pin an unknown key to a specific type? Mostly just curious.
There was a problem hiding this comment.
Just had to pick something and this seemed reasonable. This is essentially a "first guess" at what acceptable style variable values would be. Very much open to change and iteration.
We're targeting simple and flat variable objects. I can say I don't think we should allow arrays and objects and such. The theming system is pretty robust as it is, keeping variables simple will help make themes more usable.
| @@ -1,3 +1,5 @@ | |||
| const callable = val => (typeof val !== 'function' ? () => val : val) | |||
| const callable = (possibleFunction: any) => (...args: any[]) => { | |||
| return typeof possibleFunction === 'function' ? possibleFunction(...args) : possibleFunction | |||
There was a problem hiding this comment.
Can we attach a benchmark into JSDoc?
https://jsperf.com/startdust-callable
I thought that callableRest() would be faster 😼 But, not
src/lib/renderComponent.tsx
Outdated
| const styleParam: ComponentStyleFunctionParam = { | ||
| props, | ||
| variables, | ||
| siteVariables, |
There was a problem hiding this comment.
⚠️ NOTE TO SELF
siteVariables should not be passed to style functions. This was found in the breakout work for moving rules and variables to themes/teams/components. The Text component was directly accessing site variables via an import. Instead, it needs to get these values from the textVariables.ts.
This is the only case of siteVariables being accessed in a component's style function. It should also be removed.
Apologies, this PR is necessarily large:
Relevant features...
Styles prop
The
stylesprop is now supported:Nested Providers
You can now nest Providers and themes will be merged: