I am trying to override the default styles in a Mui component using the classes prop and passing the css into it. So far I have been unsuccessful and I'm not sure what I have wrong.
The motivation for not using makeStyles is to keep our files cleaner and Emotion is faster.
I am able to do it successfully using makeStyles like this:
import { css } from '#emotion/react';
import HelpIcon from '#material-ui/icons/Help';
import { Tooltip } from '#material-ui/core';
import ClickAwayListener from '#material-ui/core/ClickAwayListener';
import { useState } from 'react';
import { cbNeutral } from '../../src/theme/palette';
import { makeStyles } from '#material-ui/core/styles';
import PropTypes from 'prop-types';
const useStyles = makeStyles(() => ({
arrow: {
'&:before': {
border: `1px solid ${cbNeutral[900]}`
},
color: cbNeutral[1000]
},
tooltip: {
backgroundColor: cbNeutral[1000],
border: `1px solid ${cbNeutral[900]}`,
color: cbNeutral[400],
boxShadow: '0px 8px 16px 0px rgba(14, 13, 38, 0.12)',
maxWidth: '385px',
fontSize: '1.125rem',
padding: '18px 25px',
fontWeight: 400
},
root: {
fill: cbNeutral[700],
'&:hover': {
fill: cbNeutral[400],
cursor: 'pointer'
}
}
}));
const ToolTip = ({ text }) => {
const styles = useStyles();
const [open, setOpen] = useState(false);
return (
<ClickAwayListener onClickAway={() => setOpen(false)}>
<Tooltip
arrow
title={text}
placement="right-start"
open={open}
classes={{
arrow: styles.arrow,
tooltip: styles.tooltip
}}
>
<HelpIcon onClick={() => setOpen(true)} classes={{ root: styles.root }} />
</Tooltip>
</ClickAwayListener>
);
};
export default ToolTip;
ToolTip.propTypes = {
text: PropTypes.string.isRequired
};
I am trying to do it with Emotion like this:
import { css } from '#emotion/react';
import HelpIcon from '#material-ui/icons/Help';
import { Tooltip } from '#material-ui/core';
import ClickAwayListener from '#material-ui/core/ClickAwayListener';
import { useState } from 'react';
import { cbNeutral } from '../../src/theme/palette';
import { makeStyles } from '#material-ui/core/styles';
import PropTypes from 'prop-types';
const toolTipCss = css`
.arrow {
color: ${cbNeutral[1000]};
&:before {
border: 1px solid ${cbNeutral[900]};
}
}
.tooltip {
background-color: ${cbNeutral[1000]};
border: 1px solid ${cbNeutral[900]};
color: cbNeutral[400];
box-shadow: 0px 8px 16px 0px rgba(14, 13, 38, 0.12);
max-width: 385px;
font-size: 1.125rem;
padding: 18px 25px;
font-weight: 400;
}
.help-icon {
fill: ${cbNeutral[700]};
&:hover {
fill: ${cbNeutral[400]};
cursor: pointer;
}
}
`;
const ToolTip = ({ text }) => {
const [open, setOpen] = useState(false);
return (
<div css={toolTipCss}>
<ClickAwayListener onClickAway={() => setOpen(false)}>
<Tooltip
arrow
title={text}
placement="right-start"
open={open}
classes={{
arrow: 'arrow',
tooltip: 'tooltip'
}}
>
<HelpIcon onClick={() => setOpen(true)} className="help-icon" />
</Tooltip>
</ClickAwayListener>
</div>
);
};
export default ToolTip;
ToolTip.propTypes = {
text: PropTypes.string.isRequired
};
In 👆 that version where I use className="help-icon", it works as expected, but classes={{arrow: 'arrow', tooltip: 'tooltip'}} does not change the default MUI styles.
I am using these versions of MUI and Emotion
"#emotion/react": "^11.4.0",
"#material-ui/core": "^4.11.4",
I know we have it working like this in another app using older versions of MUI and emotion. I don't know if something has changed in the mean time or of I just have something wrong with my syntax. Any help would be appreciated.
Related
I want to set a default background color that will apply to all created pages in my project.
I have tried the following tutorial: https://itnext.io/next-js-with-material-ui-7a7f6485f671 but it does not work and gives this error: Error
Here are my files:
Index.tsx file:
import LoginPage from "./login";
export default function Start() {
return (<div ><LoginPage></LoginPage></div>);
}
Login.tsx file
import Typography from "#mui/material/Typography"
import React from 'react';
import { makeStyles } from '#mui/styles';
import { Button, TextField } from "#mui/material";
const useStyles = makeStyles(theme => ({
root: {
display: "inline-grid",
margin: "auto",
position:"fixed",
top: "50%",
left: "50%",
width:"30em",
height:"18em",
marginTop: "-9em",
marginLeft: "-15em",
border: "1px solid #ccc",
},
loginButton: {
border: "1px solid #ccc",
width: "30%",
height: "80%",
margin: "auto"
},
loginText: {
margin: "auto"
}
}));
export default function LoginPage() {
const classes = useStyles();
return (
<>
<div className={classes.root}>
<Typography className ={classes.loginText}>Login</Typography>
<TextField id="filled-basic" label="Email Adresse" variant="standard"></TextField>
<TextField id="filled-basic" label="Passwort" variant="standard" />
<Button className={classes.loginButton}>Login</Button>
</div>
</>
);
}
useTheme.ts file:
import { createTheme } from "#mui/material/styles";
// Create a theme instance.
const theme = createTheme ({
palette: {
primary: {
main: '#556cd6',
},
secondary: {
main: '#19857b',
},
background: {
default: '#fff',
},
},
});
export default theme;
_app.tsx and _document.tsx are the same as in the tutorial provided in the link above
That's how my folder looks like with all files in
How can I fix the error that I am getting?
I am using React Native Typescript for my. In My app there are three elements one is Map, bottom-sheet (which position is absolute) and One button. I have created one custom bottom sheet. Bottom-sheet is animated Scrollable. Inside the Bottom-sheet I used Custom search bar and under the search bar there is Flat-list where I am rendering the data. In IOS simulator I can see the FlatList card under the Button. which is really bad from UI (IOS-Image) but in Android it looks perfect Android-simulator. I want to hide the flat-List data which is coming under the button. I tried background color white and tried with height, width, bottom.But none of of work. I want to make efficient css position instead of hard code position. But I don't know how to fixed it.
I share my code in Expo-snacks
This is my custom Bottom-sheet
import React from "react";
import {
SafeAreaView,
StyleSheet,
Text,
useWindowDimensions,
TouchableOpacity,
View,
} from "react-native";
import Animated, {
Extrapolate,
interpolate,
useAnimatedGestureHandler,
useAnimatedStyle,
withTiming,
} from "react-native-reanimated";
import { PanGestureHandler } from "react-native-gesture-handler";
import styled from "styled-components/native";
interface Props {
panY: number;
children: React.ReactNode;
}
export default function BottomSheet({ panY, children }: Props) {
const { height } = useWindowDimensions();
const gestureHandler = useAnimatedGestureHandler(
{
onStart(_, context) {
context.startY = panY.value;
},
onActive(event, context) {
panY.value = context.startY + event.translationY;
},
onEnd() {
if (panY.value < -height * 0.4) {
panY.value = withTiming(-(height * 0.6));
} else {
panY.value = withTiming(0);
}
},
},
[height]
);
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateY: interpolate(panY.value, [-1, 0], [-1, 0], {
extrapolateLeft: Extrapolate.EXTEND,
extrapolateRight: Extrapolate.CLAMP,
}),
},
],
};
});
return (
<PanGestureHandler onGestureEvent={gestureHandler}>
<Animated.View
style={[styles.container, { top: height * 0.7 }, animatedStyle]}
>
{children}
</Animated.View>
</PanGestureHandler>
);
}
const styles = StyleSheet.create({
container: {
position: "absolute",
top: 0,
left: 0,
right: 0,
backgroundColor: "white",
shadowColor: "black",
shadowOffset: {
height: -6,
width: 0,
},
shadowOpacity: 0.1,
shadowRadius: 5,
borderTopEndRadius: 15,
borderTopLeftRadius: 15,
},
});
This is My App component
import React, { useState } from "react";
import {
StyleSheet,
Text,
View,
Dimensions,
useWindowDimensions,
SafeAreaView,
StatusBar,
RefreshControl,
} from "react-native";
import styled from "styled-components/native";
import MapView from "react-native-maps";
import { useSharedValue } from "react-native-reanimated";
import { FlatList } from "react-native-gesture-handler";
import BottomSheet from "./ActionSheet";
import BottomButton from "./BottomButton";
import SafeAreaWrapper from "./SafeAreaWrapper";
import SearchBar from "./SearchBar";
const { width, height } = Dimensions.get("screen");
const api =
"http://open-api.myhelsinki.fi/v1/events/?distance_filter=60.1699%2C24.9384%2C10&language_filter=en&limit=100";
const initialRegion = {
latitudeDelta: 15,
longitudeDelta: 15,
latitude: 60.1098678,
longitude: 24.7385084,
};
export default function App() {
const { width, height } = useWindowDimensions();
const [loading, setLoading] = useState(false);
const [data, setData] = React.useState([]);
const updateState = async () => {
try {
const response = await fetch(api);
const data = await response.json();
setData(data.data);
} catch (error) {
console.log("failed to catch", error);
}
};
React.useEffect(() => {
updateState();
}, []);
const y = useSharedValue(0);
return (
<SafeAreaWrapper>
<Container>
<MapContent>
<MapView style={styles.mapStyle} initialRegion={initialRegion} />
</MapContent>
<BottomSheet panY={y}>
<Content>
<SearchContainer>
<SearchBar placeholder={"write some thing"} />
</SearchContainer>
{data && data === undefined ? (
<Text>loading</Text>
) : (
<HeroFlatList
data={data}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
enabled={true}
refreshing={loading}
onRefresh={updateState}
/>
}
keyExtractor={(_, index) => index.toString()}
renderItem={({ item, index }) => {
const image = item?.description.images.map((img) => img.url);
const startDate = item?.event_dates?.starting_day;
return (
<EventContainer key={index}>
<EventImage
source={{
uri:
image[0] ||
"https://res.cloudinary.com/drewzxzgc/image/upload/v1631085536/zma1beozwbdc8zqwfhdu.jpg",
}}
/>
<DescriptionContainer>
<Title ellipsizeMode="tail" numberOfLines={1}>
{item?.name?.en}
</Title>
<DescriptionText>
{item?.description?.intro ||
"No description available"}
</DescriptionText>
<DateText>{startDate}</DateText>
</DescriptionContainer>
</EventContainer>
);
}}
/>
)}
</Content>
</BottomSheet>
<ButtonContainer>
<BottomButton
title={"Save"}
onPress={() => {
console.log("jjj");
}}
/>
</ButtonContainer>
</Container>
</SafeAreaWrapper>
);
}
const styles = StyleSheet.create({
mapStyle: {
width: width,
height: height, // I DON'T KNOW HOW TO CONVERT THEM INTO STYLED COMPONENT
},
});
const HeroFlatList = styled(FlatList).attrs({
contentContainerStyle: {
paddingTop: 20,
paddingBottom: 2000, // BAD PRACTICE
flexGrow: 1, //SEEMS LIKE IT DOES NOT WORK
},
})`
height: 2000px; // BAD PRACTICE
`;
const MapContent = styled.View`
flex: 1;
`;
const Content = styled.View`
flex: 1;
padding: 20px;
`;
const Container = styled.View`
flex: 1;
`;
const ButtonContainer = styled.View`
background-color: #fdfbfb; // My Button container
`;
const TextName = styled.Text`
color: orange;
font-weight: bold;
`;
const SearchContainer = styled.View`
margin-bottom: 5px;
`;
const Title = styled.Text`
font-size: 16px;
font-weight: 700;
margin-bottom: 5px;
`;
const DescriptionText = styled(Title)`
font-size: 14px;
opacity: 0.7;
`;
const DateText = styled(Title)`
font-size: 14px;
opacity: 0.8;
color: #0099cc;
`;
const EventImage = styled.Image`
width: 70px;
height: 70px;
border-radius: 70px;
margin-right: 20px;
`;
const DescriptionContainer = styled.View`
width: 200px;
`;
const EventContainer = styled.View`
flex-direction: row;
padding: 20px;
margin-bottom: 10px;
border-radius: 20px;
background-color: rgba(0, 0, 0, 0.8);
`;
How add custom scrollbar in material ui.?
I have search many things to add such scrollbar finally i have got this.
thank you everyone.
import React from "react";
import { Box} from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
root: {
"&::-webkit-scrollbar": {
width: 7,
},
"&::-webkit-scrollbar-track": {
boxShadow: `inset 0 0 6px rgba(0, 0, 0, 0.3)`,
},
"&::-webkit-scrollbar-thumb": {
backgroundColor: "darkgrey",
outline: `1px solid slategrey`,
},
},
}));
export default const Customscroll= (props) => {
const classes = useStyles();
return (
<Box className={classes.root}>
</Box>
);
};
What is the best way to create variants using styled components? Heres what i am currently doing.
const ButtonStyle = styled.button`
padding:8px 20px;
border:none;
outline:none;
font-weight:${props => props.theme.font.headerFontWeight};
font-size:${props => props.theme.font.headerFontSize};
display:block;
&:hover{
cursor:pointer;
}
${({ variant }) =>
variant == 'header' && css`
background-color:${props => props.theme.colors.lightblue};
color:${({ theme }) => theme.colors.white};
&:active{
background-color:${props => props.theme.colors.blue}
}
`
}
${({ variant }) =>
variant == 'white' && css`
background-color:white;
color:${({ theme }) => theme.colors.lightblue};
&:active{
color:${props => props.theme.colors.blue}
}
`
}
`;
I cannot tell if this is the standard way of doing things.
I have also been using other components as bases to create other components from while changing a few things
eg
const InnerDiv = styled(otherComponent)`
position: unset;
background-color: red;
overflow-x: hidden;
display: flex;
`;
Which is the better approach? Are there any better alternatives?
Inspired by previous solutions, I want to share what I came up with:
import styled, { css, DefaultTheme } from 'styled-components';
const variantStyles = (theme: DefaultTheme, variant = 'primary') =>
({
primary: css`
color: ${theme.colors.light};
background: ${theme.colors.primary};
border: 1px solid ${theme.colors.primary};
`,
}[variant]);
const Button = styled.button<{ variant: string }>`
padding: 1rem;
font-size: 0.875rem;
transition: all 0.3s;
cursor: pointer;
${({ theme, variant }) => variantStyles(theme, variant)}
&:active {
transform: translateY(1.5px);
}
`;
export default Button;
For now it contains only primary and its the default one, by you can add more variants by adding new object to variantStyles object
Then you can use it by passing the variant as a prop or keep the default by not passing any variant.
import { Button } from './HeroSection.styles';
<Button variant="primary">Start Learning</Button>
This is just my opinion:
I don't think we can do anything very different from what you did.
A different way that I thought, would be to create an options object to map the possibilities of the variant, like this:
const variantOptions = {
header: {
backgroundColor: theme.colors.lightblue,
color: theme.colors.white,
active: theme.colors.blue,
},
white: {
backgroundColor: "white",
color: theme.colors.lightblue,
active: theme.colors.blue,
},
};
And use it in your style component like this:
const ButtonStyle = styled.button`
padding: 8px 20px;
border: none;
outline: none;
font-weight: ${(props) => props.theme.font.headerFontWeight};
font-size: ${(props) => props.theme.font.headerFontSize};
display: block;
&:hover {
cursor: pointer;
}
${({ variant }) =>
variant &&
variantOptions[variant] &&
css`
background-color: ${variantOptions[variant].backgroundColor};
color: ${variantOptions[variant].color};
&:active {
color: ${variantOptions[variant].active};
}
`}
`;
And all of this buttons will work:
<ButtonStyle variant="*wrong*">Button</ButtonStyle>
<ButtonStyle variant="header">Button</ButtonStyle>
<ButtonStyle variant="white">Button</ButtonStyle>
<ButtonStyle>Button</ButtonStyle>
When dealing with Styled Component variants here is what I like to do to keep things organised and scalable.
If the variants are stored within the same file I am using the inheritance properties:
const DefaultButton = styled.button`
color: ${(props) => props.theme.primary};
`;
const ButtonFlashy = styled(DefaultButton)`
color: fuchsia;
`;
const ButtonDisabled = styled(DefaultButton)`
color: ${(props) => props.theme.grey};
`;
If if we are talking about a reusable components I would use this technique:
import styled from 'styled-components';
// Note that having a default class is important
const StyledCTA = ({ className = 'default', children }) => {
return <Wrapper className={className}>{children}</Wrapper>;
};
/*
* Default Button styles
*/
const Wrapper = styled.button`
color: #000;
`;
/*
* Custom Button Variant 1
*/
export const StyledCTAFushia = styled(StyledCTA)`
&& {
color: fuchsia;
}
`;
/*
* Custom Button Variant 2
*/
export const StyledCTADisabled = styled(StyledCTA)`
&& {
color: ${(props) => props.theme.colors.grey.light};
}
`;
export default StyledCTA;
Usage:
import StyledCTA, { StyledCTADisabled, StyledCTAFushia } from 'components/StyledCTA';
const Page = () => {
return (
<>
<StyledCTA>Default CTA</StyledCTA>
<StyledCTADisabled>Disable CTA</StyledCTADisabled>
<StyledCTAFushia>Fuchsia CTA</StyledCTAFushia>
</>
)
};
Read more about this in the blog posts I created on the subject here and there.
There are many ways to do this. one simple way is to use the package called Styled-components-modifiers. documentation is simple and straightforward.
https://www.npmjs.com/package/styled-components-modifiers
Simple usage example:
import { applyStyleModifiers } from 'styled-components-modifiers';
export const TEXT_MODIFIERS = {
success: () => `
color: #118D4E;
`,
warning: () => `
color: #DBC72A;
`,
error: () => `
color: #DB2A30;
`,
};
export const Heading = styled.h2`
color: #28293d;
font-weight: 600;
${applyStyleModifiers(TEXT_MODIFIERS)};
`;
In the Component - import Heading and use modifier prop to select the variants.
<Heading modifiers='success'>
Hello Buddy!!
</Heading>
Styled components are usually used with Styled system that supports variants and other nice features that enhance Styled components. In the example below Button prop variant automatically is mapped to keys of variants object:
const buttonVariant = ({ theme }) =>
variant({
variants: {
header: {
backgroundColor: theme.colors.lightblue,
color: theme.colors.white,
active: theme.colors.blue,
},
white: {
backgroundColor: 'white',
color: theme.colors.lightblue,
active: theme.colors.blue,
},
},
})
const Button = styled.button`
${(props) => buttonVariant(props)}
`
Styled System Variants: https://styled-system.com/variants
Use the variant API to apply styles to a component based on a single prop. This can be a handy way to support slight stylistic variations in button or typography components.
Import the variant function and pass variant style objects in your component definition. When defining variants inline, you can use Styled System like syntax to pick up values from your theme.
// example Button with variants
import styled from 'styled-components'
import { variant } from 'styled-system'
const Button = styled('button')(
{
appearance: 'none',
fontFamily: 'inherit',
},
variant({
variants: {
primary: {
color: 'white',
bg: 'primary',
},
secondary: {
color: 'white',
bg: 'secondary',
},
}
})
)
I'm using styled-system with styled components and have a basic case like this:
const buttonFont = {
fontFamily: "Chilanka"
};
// style a boilerplate for text
const Text = styled.div`
${typography}
${color}
`;
// button blueprint
const Button = ({ children, ...rest }) => {
return (
<Text as="button" {...buttonFont } {...rest}>
{children}
</Text>
);
};
// styled button using button
const StyledButton = styled(Button)`
color: white;
background-color: gray;
padding: 10px 20px;
border: 2px solid black;
`;
// When using "as" this component does not includes buttonFont styles
const StyledLabel = styled(StyledButton).attrs({
as: "label"
})``;
I want to create a StyledLabel which will inherit all styles from StyledButton, but change tag to label. But StyledLabel does not get the buttonFont styles when using "as" attribute.
Please see live example here: demo
I'm not sure what your end goal is, but these 2 examples worked in terms of inheritance. However, they might not help with your plan for composition:
import React from "react";
import styled, {css} from "styled-components";
import { typography, color } from "styled-system";
import ReactDOM from "react-dom";
import "./styles.css";
const buttonFont = {
fontFamily: "Chilanka"
};
const Text = styled.div`
${typography}
${color}
margin: 24px;
`;
const StyledButton = styled(Text)`
color: white;
background-color: gray;
padding: 10px 20px;
border: 2px solid black;
`;
const StyledLabel = styled(StyledButton)`
color: yellow;
`;
const __Text = styled.div`
${typography(buttonFont)}
${color}
margin: 24px;
`;
const __StyledButton = styled(__Text)`
color: white;
background-color: gray;
padding: 10px 20px;
border: 2px solid black;
`;
const __StyledLabel = styled(__StyledButton)`
color: yellow;
`;
function App() {
return (
<div className="App">
<StyledButton as="button" {...buttonFont}>styled button</StyledButton>
<StyledLabel as="label" {...buttonFont}>Does inherit styled font with "as"</StyledLabel>
<br />
<br />
<br />
<__StyledButton as="button">styled button</__StyledButton>
<__StyledLabel as="label">Does inherit styled font with "as"</__StyledLabel>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);