Platform conditional statements in stylesheets (react-native) - reactjs

For minor platform specific code you can use the Platform module to execute some platform dependent code. As detailed in the docs here:
https://facebook.github.io/react-native/docs/platform-specific-code.html
There is an example of how to use it in stylesheets
var styles = StyleSheet.create({
height: (Platform.OS === 'ios') ? 200 : 100,
});
I would like to do something similar but a simple if statement to decide whether or not to use a style, for example one that is for one platform only.
Here is an example:
var styles = StyleSheet.create({
textInputStyle: {
if (Platform.OS === 'android') {
textAlignVertical:'top' // android only style
}
}
});~
This is syntactically incorrect, what's the correct code to achieve this. I would like to avoid having two separate style sheets for each Platform as it seems unnecessary when it's only 1 or 2 fields that are different.

I believe this is what you are looking for:
var styles = StyleSheet.create({
textInputStyle: {
...Platform.select({
ios: {
//
},
android: {
//
},
}),
}
})
The link you provided shows the above code as an example. (v0.59)

Please have a look on react-native-extended-stylesheet that supports media queries allowing you to define styles for particular platform / screen.
For example:
import EStyleSheet from 'react-native-extended-stylesheet';
...
render() {
return (
<View style={styles.column}></View>
);
}
...
const styles = EStyleSheet.create({
'#media ios': {
column: {
width: '80%'
}
},
'#media android': {
column: {
width: '90%'
}
}
});
You can also use it for particular style items:
const styles = EStyleSheet.create({
column: {
'#media ios': {
width: '80%'
},
'#media android': {
width: '90%'
}
}
});

One way to achieve is to have both different styles and then apply it dynamically in render. For ex:
render(){
let platformStyle = Platform.OS === 'ios' ? styles.iosStyle: styles.androidStyle;
return (<View style={platformStyle}>
.....
</View>);
}
.....
const styles = StyleSheet.create({
iosStyle: {
},
androidStyle: {
}
});

I had the same problem with medium-sized RN app than you have.
Currently, I have external stylesheets for both iOS and Android (Layout.ios.js and Layout.android.js) which I import to components. This is meant for basic styling of components and it's not as hard to maintain as it sounds.
There are several minor issues on Android (e.g. lineHeight in some cases causes red screen of death). And that's why I had to implement this approach and I've been happy with it.
Besides most of the components share common styling, so external stylesheet works perfectly in that case too.
But in cases I have only minor difference, I do it locally, e.g.
header: {
marginTop: (Platform.OS === 'ios') ? 20 : 15
}
I've tried to look for alternative approaches, but at the moment this seems to be the way of doing it.
In addition, textAlignVertical is just ignored by iOS, so you don't need to wrap it with Platform check.

Related

How to change color of month/year label of material-ui datepicker in react using createMuiTheme?

I want to change the color of the label circled here:
How would I go about doing so?
I have tried changing the color of the primary colors on the palette. Also, there does not seem to be a way to do it through CSS without affecting other components.
Here's my current code for the theme:
const calendarTheme = createMuiTheme({
overrides: {
MuiPickersDay: {
day: {
color: "#436E70",
},
daySelected: {
backgroundColor: "#436E70",
},
dayDisabled: {
color: "#436E70",
},
current: {
color: "#436E70",
},
},
},
});
Material-UI pickers v3.3.10
https://material-ui-pickers.dev/guides/css-overrides
Variant: static
Npm package used: #material-ui/pickers
Someone suggested this post: Change header color of Material-UI Date Picker
That solution is 5+ years old already,getMuiTheme is not part of v3.3.10. Also the way described in that post does not work anymore, it does not matter where in the createMuiTheme object I put
datePicker: {
color: palette.primary1Color,
textColor: palette.alternateTextColor,
calendarTextColor: palette.textColor,
selectColor: palette.primary2Color,
selectTextColor: palette.alternateTextColor,
calendarYearBackgroundColor: palette.canvasColor,
headerColor: palette.pickerHeaderColor || palette.primary1Color,
},
It does not work, it doesn't have any effects. And the documentation also doesn't bring much light to the case.
Thanks in advance.
According to the documentation from your link the datepicker has a rule (sometimes called slot) called MuiPickersCalendarHeader. This rule is used to provide styling to a <div> tag that is an ancestor to the <p> tag that contains the text you've circled in your sample image (i.e. "July 2022"). You can see how these tags are structured in the Inspector tab of the Developer Tools Window in Firefox (in the browser highlight the text "July 2022", right-click the highlighted text, then from the context menu choose Inspect). Knowing the tag structure, we can apply a CSS selector to target the <p> tag like so:
const calendarTheme = createMuiTheme({
overrides: {
MuiPickersDay: {
...
// MuiPickersCalendarHeader rule
MuiPickersCalendarHeader: {
switchHeader: {
['& > div > p']: {
// backgroundColor: lightBlue.A200,
// color: "white",
},
},
},
...
}
}
The above code is untested. If you have problems with it say so in a comment, and I'll try to test it.

Dynamically build classnames in TailwindCss

I am currently building a component library for my next project with TailwindCss, I just ran into a small issue when working on the Button component.
I'm passing in a prop like 'primary' or 'secondary' that matches a color I've specified in the tailwind.config.js then I want to assign that to the button component using Template literals like so: bg-${color}-500
<button
className={`
w-40 rounded-lg p-3 m-2 font-bold transition-all duration-100 border-2 active:scale-[0.98]
bg-${color}-500 `}
onClick={onClick}
type="button"
tabIndex={0}
>
{children}
</button>
The class name comes through in the browser just fine, it shows bg-primary-500 in the DOM, but not in the applied styles tab.
The theming is configured like so:
theme: {
extend: {
colors: {
primary: {
500: '#B76B3F',
},
secondary: {
500: '#344055',
},
},
},
},
But it doesn't apply any styling. if I just add bg-primary-500 manually it works fine.
I'm honestly just wondering if this is because of the JIT compiler not picking dynamic classnames up or if I'm doing something wrong (or this is just NOT the way to work with tailWind).
Any help is welcome, thanks in advance!
So after finding out that this way of working is not recommended and that JIT doesn't support it (Thanks to the generous commenters). I have changed the approach to a more 'config' based approach.
Basically I define a const with the basic configuration for the different props and apply those to the component. It's a bit more maintenance work but it does the job.
Here is the example of a config. (Currently without typing) and up for some better refactoring but you'll get the idea.
const buttonConfig = {
// Colors
primary: {
bgColor: 'bg-primary-500',
color: 'text-white',
outline:
'border-primary-500 text-primary-500 bg-opacity-0 hover:bg-opacity-10',
},
secondary: {
bgColor: 'bg-secondary-500',
color: 'text-white',
outline:
'border-secondary-500 text-secondary-500 bg-opacity-0 hover:bg-opacity-10',
},
// Sizes
small: 'px-3 py-2',
medium: 'px-4 py-2',
large: 'px-5 py-2',
};
Then I just apply the styling like so:
<motion.button
whileTap={{ scale: 0.98 }}
className={`
rounded-lg font-bold transition-all duration-100 border-2 focus:outline-none
${buttonConfig[size]}
${outlined && buttonConfig[color].outline}
${buttonConfig[color].bgColor} ${buttonConfig[color].color}`}
onClick={onClick}
type="button"
tabIndex={0}
>
{children}
</motion.button>
this way of writing Tailwind CSS classes is not recommended. Even JIT mode doesn't support it, to quote Tailwind CSS docs: "Tailwind doesn’t include any sort of client-side runtime, so class names need to be statically extractable at build-time, and can’t depend on any sort of arbitrary dynamic values that change on the client"
EDIT: Better implementation 2022 - https://stackoverflow.com/a/73057959/11614995
Tailwind CSS does not support dynamic class names (see here). However, there's still a way to accomplish this. I needed to use dynamically build class names in my Vue3 application. See the code example below.
Upon build tailwind scanes your application for classes that are in use and automatically purges all other classes (see here). There is however a savelist feature that you can use to exclude classes from purging - aka they will always make it to production.
I have created a sample code below, that I use in my production. It combines each color and each color shade (colorValues array).
This array of class names is passed into the safelist. Please note, that by implementing this feature you ship more css data to production as well as ship css classes you may never use.
const colors = require('./node_modules/tailwindcss/colors');
const colorSaveList = [];
const extendedColors = {};
const colorValues = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
for (const key in colors) {
// To avoid tailWind "Color deprecated" warning
if (!['lightBlue', 'warmGray', 'trueGray', 'coolGray', 'blueGray'].includes(key))
{
extendedColors[key] = colors[key];
for(const colorValue in colorValues) {
colorSaveList.push(`text-${key}-${colorValue}`);
colorSaveList.push(`bg-${key}-${colorValue}`);
}
}
}
module.exports = {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}"
],
safelist: colorSaveList,
theme: {
extend: {
colors: extendedColors
}
},
plugins: [
require('tailwind-scrollbar'),
]
}
For tailwind JIT mode or v3 that uses JIT, you have to ensure that the file where you export the object styles is included in the content option in tailwind.config.js, e.g.
content: ["./src/styles/**/*.{html,js}"],
If someone comes across in 2022 - I took A. Mrózek's answer and made a couple of tweaks to avoid deprecated warnings and an issue with iterating non-object pallettes.
const tailwindColors = require("./node_modules/tailwindcss/colors")
const colorSafeList = []
// Skip these to avoid a load of deprecated warnings when tailwind starts up
const deprecated = ["lightBlue", "warmGray", "trueGray", "coolGray", "blueGray"]
for (const colorName in tailwindColors) {
if (deprecated.includes(colorName)) {
continue
}
const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]
const pallette = tailwindColors[colorName]
if (typeof pallette === "object") {
shades.forEach((shade) => {
if (shade in pallette) {
colorSafeList.push(`text-${colorName}-${shade}`)
colorSafeList.push(`bg-${colorName}-${shade}`)
}
})
}
}
// tailwind.config.js
module.exports = {
safelist: colorSafeList,
content: ["{pages,app}/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
colors: tailwindColors,
},
},
plugins: [],
}
this might be a bit late, but for the people bumping this thread.
the simplest explaination for this is;
Dynamic Class Name does not work unless you configured Safelisting for the Dynamic class name,
BUT, Dynamic Class works fine so long as its a full tailwind class name.
its stated here
this will not work
<div class="text-{{ error ? 'red' : 'green' }}-600"></div>
but this one works
<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>
its states;
As long as you always use complete class names in your code, Tailwind
will generate all of your CSS perfectly every time.
the longer explanation;
Tailwind will scan all the files specified in module.exports.content inside tailwind.config.js and look for tailwind classes, it does not even have to be in a class attribute and can even be added in commented lines, so long as the full class name is present in that file and class name is not dynamically constructed; Tailwind will pull the styling for that class,
so in your case, all you have to do is put in the full class name inside that file for all the possible values of your dynamic class
something like this
<button className={ color === 'primary' ? 'bg-primary-500' : 'bg-secondary-500'}>
{children}
</button>
or the method I would prefer
<!-- bg-primary-500 bg-secondary-500 -->
<button className={`bg-${color}-500 `}>
{children}
</button>
here's another example, although its Vue, the idea would be the same for any JS framework
<template>
<div :class="`bg-${color}-100 border-${color}-500 text-${color}-700 border-l-4 p-4`" role="alert">
test
</div>
</template>
<script>
/* all supported classes for color props
bg-red-100 border-red-500 text-red-700
bg-orange-100 border-orange-500 text-orange-700
bg-green-100 border-green-500 text-green-700
bg-blue-100 border-blue-500 text-blue-700
*/
export default {
name: 'Alert',
props: {
color: {type: String, default: 'red'}
}
}
</script>
and the result would be this
<Alert color="red"></Alert> <!-- this will have color related styling-->
<Alert color="orange"></Alert> <!-- this will have color related styling-->
<Alert color="green"></Alert> <!-- this will have color related styling-->
<Alert color="blue"></Alert> <!-- this will have color related styling-->
<Alert color="purple"></Alert> <!-- this will NOT have color related styling as the generated classes are not pre-specified inside the file -->
Now could use safeListing
and tailwind-safelist-generator package to "pregenerate" our dynamics styles.
With tailwind-safelist-generator, you can generate a safelist.txt file for your theme based on a set of patterns.
Tailwind's JIT mode scans your codebase for class names, and generates CSS based on what it finds. If a class name is not listed explicitly, like text-${error ? 'red' : 'green'}-500, Tailwind won't discover it. To ensure these utilities are generated, you can maintain a file that lists them explicitly, like a safelist.txt file in the root of your project.
In v3 as Blessing said you can change the content array to support that.
I had this
const PokemonTypeMap = {
ghost: {
classes: "bg-purple-900 text-white",
text: "fantasma",
},
normal: {
classes: "bg-gray-500 text-white",
text: "normal",
},
dark: {
classes: "bg-black text-white",
text: "siniestro",
},
psychic: {
classes: "bg-[#fc46aa] text-white",
text: "psíquico",
},
};
function PokemonType(props) {
const pokemonType = PokemonTypeMap[props.type];
return (
<span
className={pokemonType.classes + " p-1 px-3 rounded-3xl leading-6 lowercase text-sm font-['Open_Sans'] italic"}
>
{pokemonType.text}
</span>
);
}
export default PokemonType;
something similar to your approach, then I moved the array to a JSON file, it thought was working fine, but was browser caché... so following Blessing's response, you can add .json like this
content: ["./src/**/*.{js,jsx,ts,tsx,json}"],
Finally I have this code, it's better in my view.
import PokemonTypeMap from "./pokemonTypeMap.json";
function PokemonType(props) {
const pokemonType = PokemonTypeMap[props.type];
return (
<span className={pokemonType.classes + " p-1 px-3 rounded-3xl leading-6 lowercase text-sm font-['Open_Sans']"}>
{pokemonType.text}
</span>
);
}
export default PokemonType;
Is it recommended to use dynamic class in tailwind ?
No
Using dynamic classes in tailwind-css is usually not recommended because tailwind uses tree-shaking i.e any class that wasn't declared in your source files, won't be generated in the output file.
Hence it is always recommended to use full class names
According to Tailwind-css docs
If you use string interpolation or concatenate partial class names together, Tailwind will not find them and therefore will not generate the corresponding CSS
Isn't there work around ?
Yes
As a last resort, Tailwind offers Safelisting classes.
Safelisting is a last-resort, and should only be used in situations where it’s impossible to scan certain content for class names. These situations are rare, and you should almost never need this feature.
In your example,you want to have 100 500 700 shades of colors. You can use regular expressions to include all the colors you want using pattern and specify the shades accordingly .
Note: You can force Tailwind to create variants as well:
In tailwind.config.js
module.exports = {
content: [
'./pages/**/*.{html,js}',
'./components/**/*.{html,js}',
],
safelist: [
{
pattern: /bg-(red|green|blue|orange)-(100|500|700)/, // You can display all the colors that you need
variants: ['lg', 'hover', 'focus', 'lg:hover'], // Optional
},
],
// ...
}
EXTRA: How to automate to have all tailwind colors in the safelist
const tailwindColors = require("./node_modules/tailwindcss/colors")
const colorSafeList = []
// Skip these to avoid a load of deprecated warnings when tailwind starts up
const deprecated = ["lightBlue", "warmGray", "trueGray", "coolGray", "blueGray"]
for (const colorName in tailwindColors) {
if (deprecated.includes(colorName)) {
continue
}
// Define all of your desired shades
const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]
const pallette = tailwindColors[colorName]
if (typeof pallette === "object") {
shades.forEach((shade) => {
if (shade in pallette) {
// colorSafeList.push(`text-${colorName}-${shade}`) <-- You can add different colored text as well
colorSafeList.push(`bg-${colorName}-${shade}`)
}
})
}
}
// tailwind.config.js
module.exports = {
safelist: colorSafeList, // <-- add the safelist here
content: ["{pages,app}/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
colors: tailwindColors,
},
},
plugins: [],
}
Note: I have tried to summarize the answer in all possible ways, In the combination of all possible answers.Hope it helps
I had a similar issue, instead of passing all the possible configs, I just pass the data to the style property of the HTML. This is far more efficient!
or
Pass a class name as a prop and let the user of the package write the styles to that class.
const CustomComp = ({
keyColGap = 0,
keyRowGap = 0,
className = '',
}: Props) => {
const classNameToRender = (): string => {
return `m-1 flex flex-col ${className}`.trim();
};
const rowStylesToRender = (): React.CSSProperties | undefined => {
const styles: React.CSSProperties | undefined = { gap: `${keyRowGap}rem` };
return styles;
};
const colStylesToRender = (): React.CSSProperties | undefined => {
const styles: React.CSSProperties | undefined = { gap: `${keyColGap}rem` };
return styles;
};
return (
<div className={classNameToRender()} style={rowStylesToRender()}>
{layout.map((row) => {
return (
<div
className={`flex justify-around`}
style={colStylesToRender()}
key={row}
>
/* Some Code */
</div>
);
})}
</div>
}

React: Conditionally required CSS file always included in build

For testing purposes I include a CSS file that disables animation for certain CSS class, this is used so that differencify tests do not produce spurious diffs. This CSS file is only included if certain environmental variables are set:
if (process.env.REACT_APP_BACKEND_URL === 'localhost') {
// Use a fixed clock against local backend
moment.now = () => 1558396800000;
// Disable animations when running localcd to avoid diff on visual tests
if (process.env.REACT_APP_DISABLE_ANIMATIONS === 'true') {
require('./disable-animations.css');
}
}
ReactDOM.render(<App />, document.getElementById('root'));
This works perfectly when running locally, the animations are disabled when backend is localhost and enabled when running against other backends. But for some reason the animations are also disabled in the deployed code that is built using react-scripts build. moment.now() is not overriden in the built code, so it seems that react-scripts build will include all resources passed to require() regardless of their conditionality? Is there a way to avoid that? Is there a better way to achieve this?
if (process.env.REACT_APP_DISABLE_ANIMATIONS === 'true') {
require('./disable-animations.css');
}
In the above if condition, the evaluation is dynamic, the compiler does not know this is a compile time directive, it will only know to evaluate at runtime.
If using webpack there is a way to tell the compiler this is a build time constant, an example of that is the process.env.NODE_ENV, with this the compiler will evaluate this value at build time and not runtime. It does this by replacing what's in NODE_ENV with it's value, so for example.
if (process.env.NODE_ENV !== 'production') {
require('./disable-animations.css');
}
During production the above will actually get converted to ->
if ('production' !== 'production') {
require('./disable-animations.css');
}
As such the require('./disable-animations.css'); will be excluded from build.
If you have more complicated build time constants you want to use, there is also the https://webpack.js.org/plugins/define-plugin/ , with this you can have even finer control than just development & production, eg. you might want a production build with logging enabled, etc.
All require() will add static files in the final build, whether they are there on true or false conditions. I would say that the workaround could be such that you use StyleSheet.create() instead and make CSS dynamic in there. You should be able to control any CSS property logically, and even output an empty StyleSheet object in the end, thus not including anything unrelated in the build.
From https://facebook.github.io:
const styles = StyleSheet.create({
container: {
borderRadius: 4,
borderWidth: 0.5,
borderColor: '#d6d7da',
},
title: {
fontSize: 19,
fontWeight: 'bold',
},
activeTitle: {
color: 'red',
},
});
In your case it could look like this:
const isIncluded = true;
const styles = isIncluded ? StyleSheet.create({
container: {
borderRadius: 4,
borderWidth: 0.5,
borderColor: '#d6d7da',
},
title: {
fontSize: 19,
fontWeight: 'bold',
},
activeTitle: {
color: 'red',
},
}) : null;
EDIT: While for the majority of cases this will be true, as #Keith has pointed out, "...that's not strictly true, eg.. if you did if (false) { require("something"); } the compiler will know this is dead code an will exclude it...". In other words, in the cases where compiler will be sure that this code will never be reached, require() will not be included in the build

How to apply Fabric UI Office theme to body background?

I've got a problem with theming a react website made with fabric ui
I've generated 2 themes (Night/Day mode) using the website that Fabric UI provided for generating themes. I exported them in json format.
As i want to use a toggle button to change between these two themes, those two jsons became variables in a file "Themes.json"
Problem is that, once i change the theme, the body's background which is supposed to be dark is in fact plain white
My themes
var lightCyan = {
"themePrimary": "#009486",
"themeLighterAlt": "#f1fbfa",
"themeLighter": "#c8eeea",
"themeLight": "#9cdfd8",
"themeTertiary": "#4cbfb4",
"themeSecondary": "#13a193",
"themeDarkAlt": "#008578",
"themeDark": "#007166",
"themeDarker": "#00534b",
"neutralLighterAlt": "#f8f8f8",
"neutralLighter": "#f4f4f4",
"neutralLight": "#eaeaea",
"neutralQuaternaryAlt": "#dadada",
"neutralQuaternary": "#d0d0d0",
"neutralTertiaryAlt": "#c8c8c8",
"neutralTertiary": "#595959",
"neutralSecondary": "#373737",
"neutralPrimaryAlt": "#2f2f2f",
"neutralPrimary": "#000000",
"neutralDark": "#151515",
"black": "#0b0b0b",
"white": "#ffffff"
}
var darkCyan = {
"themePrimary": "#4fedec",
"themeLighterAlt": "#030909",
"themeLighter": "#0d2626",
"themeLight": "#184747",
"themeTertiary": "#2f8e8e",
"themeSecondary": "#45d1d0",
"themeDarkAlt": "#5fefee",
"themeDark": "#77f2f1",
"themeDarker": "#99f5f4",
"neutralLighterAlt": "#151717",
"neutralLighter": "#1e2020",
"neutralLight": "#2c302f",
"neutralQuaternaryAlt": "#353939",
"neutralQuaternary": "#3d4141",
"neutralTertiaryAlt": "#5c6161",
"neutralTertiary": "#c8c8c8",
"neutralSecondary": "#d0d0d0",
"neutralPrimaryAlt": "#dadada",
"neutralPrimary": "#ffffff",
"neutralDark": "#f4f4f4",
"black": "#f8f8f8",
"white": "#0c0d0c",
"bodyBackground":"#2e2a2a"
}
export {lightCyan, darkCyan};
As you found, FluentUI does not alter the body background when you load a theme. You can fix this with one line of code, which you can add to the function where you load the theme:
document.documentElement.style.background = paletteNext === "white" ? "black" : "white";
If you are interested in my method for toggling themes, see my answer to this other question: Change theme like Fabric Web ( Default / Dark)
What works for me is setup correct ThemeProvider to Body:
<ThemeProvider
theme={appTheme}
applyTo="body"
>
and in AppTheme set correct value for semanticColors.bodyBackground
...
export const appTheme: PartialTheme = {
defaultFontStyle: { fontFamily: "Segoe UI", fontWeight: "regular" },
fonts: {
small: {
fontSize: "10px",
},
...
},
semanticColors: {
bodyBackground: "#f5f7fa",
...
},
palette: {
....
full part of rendered object can meb
return (
<ThemeProvider
theme={appTheme}
applyTo="body"
>
<Header />
<Router>
<Menu />
<BreadCrumb />
<TaskPaneMainRouterSwitch />
</Router>
</ThemeProvider>
);

TypeError: StyleSheet.create is not a function

I'm trying to create some styles for my react component like this:
var styles = StyleSheet.create({
/*Pinout*/
/*GPIO Shapre*/
gpio_outer: {
background: '#222222',
borderRadius: 5,
height: 26,
width: 26,
},
})
I'm adding it like
After compiling the code and try to run on the browser I get this from the browser console:
TypeError: StyleSheet.create is not a function
Do you know what can be happening? Thanks in advance
StyleSheet is a part of React-Native, not a part of regular ReactJS. You can however instantiate a style object and use that like you would in RN.
render() {
const style = {
margin: '0.5em',
paddingLeft: 0,
listStyle: 'none'
};
return <ul style={style}>item</ul>;
}
A quick google search also led me to find a few react js libraries that give you the abstraction you're looking for.
Radium
react-style
Good info source
You should import StyleSheet correctly from react-native. You probably haven't written it correctly. Check this one:
import { View, Text, StyleSheet } from 'react-native';
Are you requiring StyleSheet properly?
You can try to check this tutorial https://www.toptal.com/ios/cold-dive-into-react-native-a-beginners-tutorial

Resources