Office UI Fabric - How to apply css styles to existing components - reactjs

I'm using the provided components and every time I need to change a component style I wonder what's the proper way to do it.
Lets say I need to change the IconButton background color when it's disabled.
https://codepen.io/elsl/pen/KrQQdV
If I provide a theme, how am I supposed to know which palette/semanticColor is used by that component?
const iconsTheme = Fabric.createTheme({
semanticColors: {
disabledBackground: "#ff9933"
}
});
<Fabric.IconButton
iconProps={{iconName:'ChevronRight'}}
disabled
theme={iconsTheme}
/>
If I provide an IButtonStyles, how am I supposed to know that the property name is "rootDisabled.backgroundColor"?
const iconButtonStyles: IButtonStyles = {
rootDisabled: {
backgroundColor: "#ff0000",
}
};
<Fabric.IconButton
iconProps={{iconName:'CalculatorEqualTo'}}
disabled
styles={iconButtonStyles}
/>
For both these options, I had to dig into the component's source code on github to find out.
Is this the expected/correct way?
If so, between creating a Theme or an IStyle which would be the ideal/best practice?

Theme vs IStyles
I would say, use a Theme if you want all Fabric components to have the same customization.
Use the styles property if you just want to customize that specific component (or that one specific instance of the component).
How to discover the styling hooks if using IStyles
There are four ways that comes to mind.
Look at the documentation (e.g. https://developer.microsoft.com/en-us/fabric#/components/dropdown, look at the IDropdownStyles interface)
(screenshot)
Utilize IntelliSense if you're using an editor like Visual Studio Code, which automatically enumerates the IComponentStyles and provides documentation if any.
Inspecting the DOM often provides hints (the hook areas usually look like {area}-{number} so root-33 for instance where the "area" name is root.
Read the source code.
Unfortunately for option 1 and option 2, Fabric React isn't super consistent with the IComponentStyles documentation so not all components have equally descriptive comments and in those cases, you may need to fallback to option 3 and option 4.

Related

Should I test all component props?

For example, I have component like this:
const Button = ({borderColor, children}) => {
return (
<button style={{borderColor: borderColor ? `border: 1px solid ${borderColor}` : null}}>
{children}
</button>
)
}
Main goals of this component are:
pass children prop further
handle custom border color
With children - it's usual situation. But with css I don't know, should I test it, or not.
Some articles about RTL tell that we don't have to test css. But
in my opinion, in this case, our css prop is important for end user
(e.g. some other developer who will use this component) and this css
should be tested. What's the right way?
Testing a React Application should be about behaviors. That is, how the application behaves when controlled by an end user. That's one of the guiding principles of react-testing-library, and one I agree with.
Therefore, going by this statement you can do two different things:
Test the complete behavior that causes the border color to change. That is Integration Testing
Type check the component to guarantee that you cannot pass an incorrect value for borderColor. That is Unit Testing
In my opinion with this use case, testing anything else would be testing React itself or, as the other answer noted, you ability to write a correct css string. The later can be tested with your own eyes anyway and isn't likely to change
Type checking example
Giving you an example for integration testing is hard to do without knowing your complete use case. As for type checking you have two options:
Using PropTypes
Switching to Typescript
Note: These options aren't equivalent. Proptypes is a library that checks the props at runtime with a developement build. They are ignored with a production build. Typescript on the other hand is a complete compiler, that runs during the build. The correct solution depends on your setup
With PropTypes
You can use PropTypes.oneOf :
Button.propTypes = {
// Edit as needed
borderColor: PropTypes.oneOf(["red", ,blue"])
}
Typescript
interface ButtonProps {
borderColor: "blue" | "red";
}
const Button: React.FunctionComponent<ButtonProps> = ({borderColor, children}) => {...}
Potentially misreading your question, but maybe you're only asking because "is this string a valid CSS color" feels like a silly or annoying detail to test, even though you're aware of the problems that not writing tests will have on other developers who will use this component.
Is this a silly thing to test? I agree conditionally.
IMO this is far better solved using Typescript to enforce constraints on what form the string can take. Some ideas here: TypeScript - is it possible validate string types based on pattern matching or even length?
A better thing to test is probably a functionality question like "does this component genuinely create a button wrapping the children with the 1px solid borderColor that we asked for?
In that case, yes I'd write a test for that functionality.

Avoid magic strings when working with Chakra Ui

A problem, I keep running into with Chakra Ui are "magic strings". Let's look at an example of muted text:
function Example() {
const color = useColorModeValue("gray.400", "gray.200");
return (
<Text color={color} />
)
}
What's the problem with this code: In my app, I want to share the muted color between many components. However, defining it explicitly as a string means that I have to remember that muted text has a value of "gray.400". If I have another component that wants to use muted text, I have to copy the string "gray.400" to all other components. I will end up with lots of strings that make it really hard to change things across the entire app. I explored two solutions so far:
Solution 1 - TextStyles API: Chakra comes with a textStyles API out of the box but this doesn't work well for more complicated situations (what if I want to have a hover and active state with different colors?).
Solution 2 - Create a global object: I've created a hookuseConsistantStyles() that returns a theme-like object with values, e. g.: {"borderLight": "gray.200"}. However, this feels like I'm fighting the library.
I'd really love to have a better solution since I keep running into this.
This is probably late but for any new onlookers, since Styled System v1.17.0, chakra ui has semantic tokens. The changelog and docs have some details but the important bit is:
Semantic tokens provide the ability to create css variables which can change with a CSS condition.
CSS conditions would be states like dark mode or hover etc.
In this case, you would define a semantic token like so (with a better token name):
const customTheme = extendTheme({
semanticTokens: {
colors: {
nicelyNamedToken: {
default: 'gray.400',
_dark: 'gray.200',
},
},
},
})
import the theme however you currently do, and then anywhere the theme is present, you should be able to just use the token
function Example() {
return (
<Text color='nicelyNamedToken' />
)
}
I don't think Solution 2 is actually that bad -- but I agree that it feels like everything should be accessible since it already exists.
If you're using a chakra theme, you can actually import the theme (i.e: import { theme } from "#chakra-ui/react"
And then you can start accessing things off of that object, like colors (t is the theme import)
Note: you still have to 'know' the keys on the object, but this is at least an existing, consistent dictionary that you can reference.
I have not tested this with different color themes, etc.

Customizing children components' styles through parent with JSS

I'm currently working on a project which has all of its styles declared in JSS. One of the "benefits" highlighted on many articles and the library docs is that it encapsulates styles. However I really am having a hard time customizing them, specially when it comes to styling that depends on the component's surrounding context (by context I mean parent elements, siblings, etc.).
Consider the following styles exported along a component called FieldDescriptor via the withStyles HOC:
info: {
fontFamily: theme.typography.fontFamily.light,
fontSize: "12px",
padding: "0 24px 8px 24px",
letterSpacing: 0.3,
},
This class will be found as FieldDescriptor-info-xxx on the element having that class. Now suppose that this component is child to another one that attempts to target the error class. You could target that class with something like [class*=FieldDescriptor-error] (personally I already consider this a very unclean approach) and it will work only on a development environment.
On production, classes will become unique (e.g. jss-xxx) and selectors like the one above will no longer be useful. My question is, what is the cleanest or "correct" approach to customizing component styles like this in JSS? I am either missing something really obvious or perhaps facing the limitations of JSS.
I am looking forward to solutions that do not require more tooling or code bloating, that would really miss the purpose of adopting JSS in the first place.
You can find an example using both withStyles and useStyles here
Try to think of a component as of a black box that intentionally hides implementation details from the outside world.
With this model, only component itself is responsible for it's presentation, so you need to ask that component to change something. In React world you do that most of the time by passing props to that component.
Inside of that component you can go multiple ways, combining the predefined classes depending on the props (preferred because static) or using function rules/values which let you access the props and define the styles per component instance.

Any cost to using `makeStyles` in a component that will be iterated (like a list item)?

I've got a component that's only ever going to be rendered as part of a list. Let's call it MyListItem. The easiest thing from a coding perspective is to localize the const useStyles = makeStyles(...) right inside MyListItem. But in that case, if there are 100 list items, then I'm potentially invoking useStyles 100 times.
Normally, I'd just say screw it and put the useStyles in the parent (e.g. MyList) and then pass classes down to MyListItem. That way, useStyles is only computed once, rather than 100 times.
However, MyListItem could get invoked by more than 1 parent (e.g. MyListOne, MyListTwo). That means that any parent that wants to invoke MyListItem needs to contain the styles for MyListItem, which is less than ideal.
So here's the question: is Material-UI smart at all about detecting that the same useStyles is being invoked again and again? Will it really write out 100 versions of those styles, or will it collapse all those down at runtime? My guess is the answer is "no", but I figured it was worth asking.
Thanks!
Material-UI is definitely smart in this regard. If it wasn't, you would have the same problem with Material-UI's components such as ListItem. So long as makeStyles is being called at the top-level (which is the typical pattern) such that you are always using the exact same useStyles function, you are fine. A component that uses styles has more overhead than one that doesn't, but it won't duplicate the styles in the DOM.
The smarts in the library can be found here: https://github.com/mui-org/material-ui/blob/v4.9.13/packages/material-ui-styles/src/makeStyles/makeStyles.js#L56
let sheetManager = multiKeyStore.get(stylesOptions.sheetsManager, stylesCreator, theme);
This is the cache lookup for a particular style sheet. stylesOptions.sheetsManager will always be the same unless you are customizing it (very uncommon) for part of your component hierarchy using StylesProvider. stylesCreator is the argument to makeStyles -- i.e. the declaration of your styles. So for the same makeStyles call and the same theme, it will find the same styles and avoid re-generating them.
You can easily verify this is all working as intended by looking at the <style> elements in the <head> of your document via developer tools. The image below is from this page which is a sandbox for this other StackOverflow answer.
You can see in the image style elements for the different Material-UI components used in the page (e.g. MuiSvgIcon, MuiListItem, etc.). You can also see one generated from makeStyles and one generated from withStyles. The withStyles case is similar to what you are describing -- it is styles for a ListItem component that is used multiple times in the page, but the styles only occur once in the document.

How to exclude global styles in a React App?

I am using Material UI for building my React Project.
However there is a component which has to be embedded to a different site. Meaning, I am providing the production build of this component to embed it to a different site.
My React app's css is getting overridden by the global styles defined in that website.
I don't want this behaviour. Is there any way I can isolate the css of my react app and the global css of the other website.
I saw this question but the solutions didn't help me.
If iframes and Web Components are out of the question, the only remaining option is CSS resets.
Create a CSS class and a series of rules that reset the styles of any elements that occur inside that class.
.my-reset {
/* Global resets (e.g. font family) */
}
.my-reset p {
/* Reset paragraph styles */
}
.my-reset label {
/* Reset label styles */
}
/* etc. */
Apply that class to the root-level component:
function MyApp() {
<div className="my-reset">
{ /* app goes here */ }
</div>
}
There are plenty of CSS reset libraries out there. Since you have no control over global styles, you're going to have to be heavy handed with the resets. So if it's at all possible, make your component embeddable as an iframe.
I see multiple solutions to this problem
Use !important in those styles possible.
Use id to give styling instead of class, as id has higher presidence.
If you give more specific styling to the elements then the build file css will override the outer site's css, i.e like if we write our css like .parent#child this is more specific styling and it will override the wrapper site's css.
Check this out https://stuffandnonsense.co.uk/archives/css_specificity_wars.html
There's another sort of scrappy solution that you could use in the case where you don't need the old table style and the new Material-UI tables on the same HTML page.
If you own the site that you are trying to embed the React app in (i.e., you have control over the new environment's CSS), another thing you could do to reset the CSS styles for the components in your app is to remove the classes that are overwriting your styles.
For example, if you're using Material-UI tables and your existing table styles are affecting the render, you could put the existing table styles into a separate CSS file that you only import when you render your existing tables, on another HTML page.

Resources