Material UI - Custom Select icon with custom themes - reactjs

I'm creating a custom Material UI theme for my React application :
import {createTheme, Theme, ThemeOptions, ThemeProvider, useMediaQuery} from "#mui/material";
import {Components} from '#mui/material/styles/components';
import {FunctionComponent, PropsWithChildren, useMemo} from 'react';
import {select} from './select';
import {darkColors, lightColors} from './colors';
const components: Components<Theme> = {
MuiSelect: select
}
const lightThemeOptions: ThemeOptions = {
palette: lightColors,
components
};
export const lightTheme = createTheme(lightThemeOptions);
const darkThemeOptions: ThemeOptions = {
palette: darkColors,
components
};
export const darkTheme = createTheme(darkThemeOptions);
// eslint-disable-next-line #typescript-eslint/no-empty-interface
export interface SystemThemeProps {
}
export const SystemThemeProvider: FunctionComponent<PropsWithChildren<SystemThemeProps>> = ({children}) => {
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const systemTheme = useMemo(() => prefersDarkMode ? darkTheme : lightTheme, [prefersDarkMode]);
return (
<ThemeProvider theme={systemTheme}>{children}</ThemeProvider>
);
};
And here is my select file :
import {Theme} from '#mui/material'
import {ComponentsOverrides} from '#mui/material/styles/overrides';
import {ComponentsProps} from '#mui/material/styles/props';
import {ComponentsVariants} from '#mui/material/styles/variants';
type MuiSelect = {
defaultProps?: ComponentsProps['MuiSelect'];
styleOverrides?: ComponentsOverrides<Theme>['MuiSelect'];
variants?: ComponentsVariants['MuiSelect'];
}
export const select: MuiSelect = {
styleOverrides: {
icon: ({ownerState, theme}) => ({
color: 'red'
})
}
}
So with that code, I can see the select native icon displayed with red color. So I think I've targeted the right classes to change.
However I have no idea how I can say that I don't want to use the native css but instead I want to load a JPEG file.
I guess I'll have one more class to target for the "open" state of the select.
Can you help ?

Related

Published styled-components UI library does not have access to extended theme types on the consumer side

I am creating UI library using styled-components. I am extending the DefaultTheme type to support custom themes that are needed for our use case. Both theme and theme definition is coming from a different internal package that I use. When working on the UI library components, it works correctly. The issue begins when we tried to use the theming on the consumer side.
The problem is that, it seems that types are not really extended to the consumer correctly, so without adding styled.d.ts file on the client side, it doesn't seem to understand the types of the theme. Theme get a type anyand it should be read asDefaultTheme type.
I wonder if there is any way to expose the extended types to consumer so they don't have to add this additional file on their side?
Is there anyone out there who had similar problem? Maybe you could share your findings?
Here is my setup:
Design System project:
// theme.ts
import tokens from 'my-external-package/variables.json';
import type { ThemeProps } from 'styled-components';
import type { MyThemeType } from 'my-external-package//theme';
const { light, dark } = tokens.color.theme;
export const lightTheme = {
color: light,
};
export const darkTheme = {
color: dark,
};
export const defaultTheme = lightTheme;
// styled.d.ts
import {} from 'styled-components';
import type { MyThemeType } from 'my-external-package//theme';
// extend theme
declare module 'styled-components' {
// eslint-disable-next-line #typescript-eslint/no-empty-interface
export interface DefaultTheme extends MyThemeType {}
}
// CustomThemeProvider.tsx
import React, { createContext, useState, ReactNode, useContext } from 'react';
import { ThemeProvider } from 'styled-components';
import { lightTheme, darkTheme } from './theme';
const themes = {
light: lightTheme,
dark: darkTheme,
};
type CustomThemeProviderProps = {
children: ReactNode;
defaultTheme?: keyof typeof themes;
};
const themeContext = createContext({ toggleTheme: () => {} });
const { Provider } = themeContext;
export const CustomThemeProvider = ({
children,
defaultTheme = 'light',
}: CustomThemeProviderProps) => {
const [currentTheme, setCurrentTheme] = useState(defaultTheme);
return (
<Provider
value={{
toggleTheme: () =>
setCurrentTheme((current) => current === 'light' ? 'dark' : 'light'),
}}
>
<ThemeProvider theme={themes[currentTheme]}>{children}</ThemeProvider>
</Provider>
);
};
// I also export hook over here so I can use it on the client side
export const useToggleTheme = () => {
const { toggleTheme } = useContext(themeContext);
return toggleTheme;
};
App consumer NextJs
//_app.tsx
import type { AppProps } from 'next/app';
import { CustomThemeProvider } from 'my-library-package/theming';
function MyApp({ Component, pageProps }: AppProps) {
return (
<CustomThemeProvider defaultTheme='light'>
<Component {...pageProps} />
</CustomThemeProvider>
);
}
export default MyApp;
// consumer_page.tsx
import type { NextPage } from 'next';
import { useCallback, useState } from 'react';
import styled from 'styled-components';
import tokens from 'my-external-package/variables.json';
import { useToggleTheme, Switch } from 'my-library-package';
const CustomComponent = styled.p`
color: ${({ theme }) => theme.color.feedback.success.foreground};
`;
const MyPage: NextPage = () => {
const toggleTheme = useToggleTheme();
return (
<>
<Switch onChange={toggleTheme}/>
<CustomComponent>This component have access to theme</CustomComponent>
</>
)
}
export default MyPage;
We are considering re-export utilities from styled-components with the right DefaultTheme and instruct consumers not to install styled-components
Instruct design-system consumers to create a styled.d.ts file to get the theme correctly populated.
Both of those seems rather painful. :(

extending default theme chakra ui

I want to set default borderColor to all Input components but it doesn't work
This is my theme.js file:
import { extendTheme } from "#chakra-ui/react";
const config = {
initialColorMode: "light",
useSystemColorMode: false,
};
const theme = extendTheme({
config,
components: {
Input: {
borderColor: "teal",
},
},
});
export default theme;
Input is a multi-part component. You can figure out if the component you're trying to customise is single-part or multi-part by going to the docs for that component and clicking the View theme source button at the top of the page:
How to customise the theme: Docs
How to customise single-part and multi-part components: Docs (especially, how to customise multi-part components)
So in your case you need do something like this:
index.js :
import * as React from "react";
import { render } from "react-dom";
import { ChakraProvider, extendTheme } from "#chakra-ui/react";
import App from "./App";
const Input = {
variants: {
filled: () => ({
field: {
borderColor: "teal"
}
})
}
};
const theme = extendTheme({
components: {
Input
}
});
const rootElement = document.getElementById("root");
render(
<ChakraProvider theme={theme}>
<App />
</ChakraProvider>,
rootElement
);
App.js :
import * as React from "react";
import { Input } from "#chakra-ui/react";
export default function App() {
return <Input placeholder="extra small size" variant="filled" />;
}

Proper way to use Material UI in reactjs project

I've recently started using Material UI and I'm not sure what is the proper way to use it. There are some times when I want to customize the styles of the Material UIcomponents. But for that I have to work in the ".js" file, which I think isn't appropriate.
Is there a way where one can store multiple custom styles/components of Material UI components in a different file and then import the styles/components from there?
Thanks!
There is no problem in separating the styles from the components.
I for one even import the makeStyles hook inside the styles.js.
// styles.js
import { makeStyles } from '#material-ui/core/styles';
const styles = makeStyles(theme => ({
main: {
padding: theme.spacing(2),
},
}));
export default styles;
You can import the useStyles in the component if you like that better
// Component.jsx
import React from 'react';
import styles from './styles';
const Component = () => {
const classes = styles();
return (
<WhatEverComponent classsName={classes.main}/>
);
}
LE: makeStyles in Component
// styles.js
const styles = theme => ({
main: {
padding: theme.spacing(2),
},
});
export default styles;
// Component.jsx
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import stylesObj from './styles';
const styles = makeStyles(stylesObj);
const Component = () => {
const classes = styles();
return (
<WhatEverComponent classsName={classes.main}/>
);
}
I've made also a sandbox here. You can check how both work.
I would suggest to use the first variant.

Is it a correct way to extend Material-UI components (React / Typescript)

I'm trying to extend material-ui components as my own component with custom properties. I'm using reactjs w/ typescript.
The below code is my trial :
import React from 'react';
import clsx from 'clsx';
import { makeStyles } from '#material-ui/core';
import { Theme } from '#material-ui/core/styles/createMuiTheme';
import Tabs, { TabsProps } from '#material-ui/core/Tabs';
export interface Iprops extends TabsProps {
/* how to add a variant ? */
}
const useStyles = makeStyles((theme: Theme) => ({
root: {
// styles
}
}));
export const BasicTabs = (props: Iprops) => {
const classes = useStyles(props);
if (props.variant === 'test') {
return (
<Tabs
{...props}
className={clsx(classes.root, props.className)}
/>
);
}
return (
<Tabs {...props} />
);
};
So, what Im trying to do now is return a custom styled button when the variant is 'test'.
So, my first question is
1. How to add a new variant to the button?
the second question is
2. should I pass the children like
<Tabs {...props}>{props.children}</Tabs>
all the time whenever I extend a material-ui components?
import React from 'react'
import TextField, { StandardTextFieldProps } from '#material-ui/core/TextField'
/**
* Extend properties
*/
export interface PasswordProps extends StandardTextFieldProps {
custom: string
}
/**
* Password input
* #param props Properties
*/
export const PasswordField: React.FunctionComponent<PasswordProps> = (props) => {
props = Object.assign({ type: 'password' }, props)
return (
<TextField {...props}>
{props.children}
</TextField>
)
}
For your questions:
A. Extend the props, add variants to the interface you want.
B. Yes, always like this as suggested with React official 'composition model' https://reactjs.org/docs/composition-vs-inheritance.html

material-ui-next - Dynamically set palette color

I am using "material-ui": "^1.0.0-beta.33" for my project.
What I want to do is set primary palette color dynamically inside a react component (color will be fetched from some api).
Basically I want to override below:
const theme = createMuiTheme({
palette: {
primary: "some color from api"
},
})
Is there a way to set this in componentDidMount function of any component?
Reference: https://material-ui-next.com/
I created a component that uses MuiThemeProvider and wrap my entire app around that component. Below is the structure of the component.
import React, {Component} from "react";
import {connect} from "react-redux";
import {withStyles} from 'material-ui/styles';
import * as colors from 'material-ui/colors';
import { MuiThemeProvider, createMuiTheme } from 'material-ui/styles';
import { withRouter } from 'react-router-dom';
export class ThemeWrapperComponent extends Component {
constructor(props){
super(props);
}
render(){
return (
<MuiThemeProvider theme={createMuiTheme(
{
palette: {
primary: { main: **colorFromApi** },
}
)}>
<div>
{ this.props.children }
</div>
</MuiThemeProvider>
)
}
}
export const ThemeWrapper = withRouter(connect(mapStateToProps)(ThemeWrapperComponent));
Below is how I wrapped my app around this component:
<ThemeWrapper>
<div>
<Routes/>
</div>
</ThemeWrapper>
Now, whatever colour you are sending from the api gets applied to the whole theme. More customisation can be done based on requirement.
I'm doing exactly this. Even got it working with WebMIDI using MIDI controller sliders and knobs just for fun.
The basic strategy is to use createMuiTheme and ThemeProvider and to store the theme in your application store (context, state, redux), etc.
class ThemeManager extends React.Component {
getThemeJson = () => this.props.context.themeJson || defaultThemeJson
componentDidMount () {
const themeJson = this.getThemeJson()
const theme = createMuiTheme(themeJson)
this.props.setContext({ theme, themeJson })
}
render () {
const { children, context } = this.props
const theme = context.theme
return theme
? <ThemeProvider theme={theme}>{children}</ThemeProvider>
: children
}
}
https://github.com/platform9/pf9-ui-plugin/blob/master/src/app/ThemeManager.js
and then you simply update your application's state.
handleImport = themeStr => {
const themeJson = JSON.parse(themeStr)
const theme = createMuiTheme(themeJson)
this.props.setContext({ theme, themeJson })
}
https://github.com/platform9/pf9-ui-plugin/blob/master/src/app/plugins/theme/components/ImportExportPanel.js#L17

Resources