Using ClojureScript, how to customize the ValueLabel in slider component of material-ui? - material-ui

In here, there is an example showing how to do the customization of ValueLabel by using javascript.
import React from "react";
import { withStyles, makeStyles } from "#material-ui/core/styles";
import Typography from "#material-ui/core/Typography";
import Slider from "#material-ui/core/Slider";
import ValueLabel from "#material-ui/core/Slider/ValueLabel";
const StyledValueLabel = withStyles({
offset: {
top: -28,
left: props => props.index === 0 ? "calc(-50% + -20px)" : "calc(-50% + 12px)"
},
circle: {
transform: props => props.index === 0 ? "rotate(-90deg)" : "rotate(0deg)"
},
label: {
transform: props => props.index === 0 ? "rotate(90deg)" : "rotate(0deg)"
}
})(ValueLabel);
const useStyles = makeStyles({
root: {
width: 300
}
});
const MySlider = withStyles({
root: {
color: "#3880ff",
height: 2,
padding: "15px 0"
},
track: {
height: 4
},
thumb: {
background: "transparent",
"&:focus,&:hover,&$active": {
boxShadow: "inherit"
}
},
rail: {
height: 4,
opacity: 0.5,
backgroundColor: "#bfbfbf"
},
mark: {
backgroundColor: "#bfbfbf",
height: 8,
width: 1,
marginTop: -2
}
})(Slider);
function valuetext(value) {
return `${value}°C`;
}
export default function RangeSlider() {
const classes = useStyles();
const [value] = React.useState([31, 37]);
return (
<div className={classes.root}>
<Typography id="range-slider" gutterBottom>
Temperature range
</Typography>
<MySlider
defaultValue={value}
valueLabelDisplay="on"
ValueLabelComponent={StyledValueLabel}
aria-labelledby="range-slider"
getAriaValueText={valuetext}
/>
</div>
);
}
I tried to changed it to the ClojureScript version, but it did not work.
(ns XXXX
(:require [reagent-material-ui.components :as mui]
[reagent.core :as reagent]
[reagent-material-ui.styles :as styles]
["#material-ui/core/Slider/ValueLabel" :as js-vl]))
...
(let [vlb (styles/with-styles {:circle {:width 40 :height 40}}
(reagent/adapt-react-class (.-default js-vl)))]
[mui/slider
{:valuelabelcomponent vlb}])
I got console error like:
Warning: Invalid value for prop `valuelabelcomponent` on <span> tag. Either remove it from the element, or pass a string or number value to keep it in the DOM. For details, see https://reactjs.org/link/attribute-behavior
at span
at eval (http://localhost:5555/ui/cljs-runtime/module$node_modules$$material_ui$core$esm$Slider$Slider.js:16:209)
at WithStyles(ForwardRef) (http://localhost:5555/ui/cljs-runtime/module$node_modules$$material_ui$styles$withStyles$withStyles.js:4:435)
at div

First of all, I think this type of question is better suited for the Clojureverse forum because you have a better chance of reaching the Clojure community there. Also, I think there are ways to improve your original question. For example: explain what exactly you were trying to do in your example but didn't work? (Sorry for picking on this but it really doesn't make sense to me to expect 30 lines of JSX and 4 lines of Clojure to work the same way. They must be different.)
Issues
To have a complete answer, let's start with the issues I noticed in your Clojure snippet:
(The warnings aren't errors. However they might be closely related to errors. 😉)
The capitalization of the props. The correct keyword for ValueLabelComponent props is :ValueLabelComponet. Reagent is a thin wrapper around React and it converts react names into Clojure keywords directly without munging the names.
The usage of reagent-material-ui.styles/with-styles. From its doc-string:
Takes a styles-generating function or a styles object.
Returns a higher-order component that wraps another component and adds a :classes prop to it.
Note: input component has to take all its props (including children) in a single map.
The difference between React component and Reagent component. Here's a great read from the reagent documentation. Although it's kind-of deceivable how similar they are, they aren't. Especially when you are doing a lot of interop with an externed React library.
Example to customize ValueLabel for MaterialUI Slider in ClojureScript
I put together this example illustrating how to put the pieces together without solving everything for you, assuming your goal is to translate every bit of the JSX snippet above. I hope this is good enough for you to work out the rest.
Pay extra attention to the distinction between React component and Reagent component.
(ns example.core
(:require
[reagent-material-ui.core.slider :refer [slider]]
[reagent-material-ui.styles :as styles]
[reagent.core :as r]
[reagent.dom :as rdom]
["#material-ui/core/Slider/ValueLabel" :as MuiValueLabel]))
(def mui-value-label
(r/adapt-react-class (or (.-default MuiValueLabel)
(.-ValueLabel MuiValueLabel))))
(def with-my-styles
(styles/with-styles {:offset {:top 50 :left 50}}))
(defn styled-value-label
[props]
[(with-my-styles mui-value-label) props])
(defn main []
[slider
{:defaultValue [31 37]
:valueLabelDisplay "on"
:ValueLabelComponent (r/reactify-component styled-value-label)}])
;; Something that renders the component like:
;; (rdom/render [main] (js/document.getElementById "app"))
Result
The value labels are offset by 50px down and 50px right:
Feel free to comment on my answer so I can make edits to it.
Cheers!

Related

Can I use an `sx` prop in a `styled` component

So my question really isn't should I, but rather can I.
If I opt to use styled components for my more common components then I style them once and export.
const MyBlueButton = styled('button')({
backgroundColor: 'blue'
})
Great export that and I have a blue button.
I can also use sx
const MyBlueButton = (props) => {
return <button sx={{backgroundColor: 'blue'}}>{props.children}</button>
}
My question is can I have both, one where I've already made my common component but then want to customize it just a bit unique for one use.
'components/buttons.jsx':
export const MyBlueButton = styled('button')({
backgroundColor: 'blue'
})
--------
'FooBar.jsx':
import {MyBlueButton} from 'components/buttons'
const FooBar = (props) => {
return (
<div>
<p>Some text</p>
<MyBlueButton sx={{fontSize: '20px'}}>Large Blue Button</MyBlueButton>
</div>
)
}
I didn't find anything stating that you couldn't do it. I'm sure you quite possibly can, but can you, and what would be the expected order of css properties?
If I have a styled component with a matching css property in the sx prop would sx win since it's closer to the render? Should I have to worry about injection order with the StyledEngineProvider?
I'm really just hoping I can use a healthy mix of both styled components and one off sx modifications.
I went ahead and made a codesand box to test my idea and it does work to combine both styled wrappers and sx props.
Again probably not for everyday use, but it's nice to know it is possible.
CodeSandbox: https://codesandbox.io/s/keen-roman-s6i7l?file=/src/App.tsx
I have been into such issue;
the way I had to implement the sx props to be passed before passing the component to styled engine was neglecting the props:
const AutoComplete = withThemeProvider(styled(Autocomplete)(autoCompleteStyles));
and when to use it, it gets neglected
<Autocomplete sx={neglectedProps}>

Can't figure out how to style material ui datagrid

I'm trying to style material-ui DataGrid component to justify the content in the cells. I am reading the material ui docs about styling but I don't seem to doing it correct and frankly find the docs on styling very confusing.
The doc here: https://material-ui.com/customization/components/#overriding-styles-with-classes implies I should be able to do something like this:
const StyledDataGrid = withStyles({
cellCenter: {
justifyContent: "center",
},
})(DataGrid);
<div style={{ height: 300, width: '100%' }}>
<StyledDataGrid rows={rows} columns={columns} />
</div>
However, when I do this, I don't see the style being added to the MuiDataGrid-cellCenter DOM element. Attaching a screenshot which shows the element classes. In the inspector I see that the style isn't being added (and if I add it manually I get the desired results). Am I not using the withStyles function correctly?
So after a bit more messing around, I believe the issue is that the DataGrid component does not support the classes property (which it seems most of the material ui components do). I believe the withStyles usage about is shorthand for passing the classes via the classes prop. Since the prop isn't listed in the API https://material-ui.com/api/data-grid/ I'm assuming this is why it isn't working. I confirmed that I can get the styles working by using a combination of the className parameter with descendant selection.
If someone determines I'm wrong and there is a way to get withStyles working on this component please comment.
const useStyles = makeStyles({
root: {
"& .MuiDataGrid-cellCenter": {
justifyContent: "center"
}
}
});
...
export default function X() {
const classes = useStyles();
return (
...
<DataGrid className={classes.root} checkboxSelection={true} rows={rows} columns={columns} />
...
)
}
ALTERNATIVE SOLUTION: (for others with similar issues)
If you are working within a class and cannot use hooks...
<div>
<DataGrid
rows={rows}
columns={columns}
sx={{
'&.MuiDataGrid-root .MuiDataGrid-cell:focus': {
outline: 'none',
},
}}
/>
</div>

StencilJS component with shadow dom enabled does not generate the helper CSS classes for dynamically added elements on IE11/Edge

I've created a new project using the stencil component starter. Inside my component I'm using an external JS nouislider, which injects HTML elements into my div (this.slider ref):
...
componentDidLoad() {
noUiSlider.create(this.slider, {
start: [20, 80],
range: {
'min': 0,
'max': 100
}
})
}
...
I've copied the slider's CSS into my-component.css and rewrote everything with :host selectors for the shadow dom:
:host(.my-component) .noUi-target {
position: relative;
direction: ltr
}
Everything works fine on Chrome/Firefox but the slider styles are not working on IE11/Edge because Stencil appends a helper sc-my-component class to every element that I have inside the render method and generates CSS rules like so:
.my-component.sc-my-component-h .noUi-target.sc-my-component {
position: relative;
direction: ltr
}
but the injected nouislider child HTML elements don't have the helper classes on them. I have an ugly fix for this case atm:
...
componentDidLoad() {
noUiSlider.create(this.slider, {
start: [20, 80],
range: {
'min': 0,
'max': 100
}
})
this.slider.querySelectorAll('div').forEach((child)=>{
child.classList.add('sc-my-component')
})
}
...
I'm appending the helper classes after the slider is created (the slider generates child divs only). Is there a better way to tell Stencil that I'm injecting elements inside lifecycle methods and that it needs to recognize those elements when CSS rules are being generated?
This is not an answer to your question, nevertheless this could also be interesting for you:
We are currently working on the same topic (StencilJS, shadow: true, noUiSlider) and encountered the problem, that the slider's touch events are not working correctly in shadowDOM on mobile devices. We found a solution for this and already created a PR (https://github.com/leongersen/noUiSlider/pull/1060).
I too had problems using nouislider in StencilJS but just managed to make it work.
my-slider.scss
#import '~nouislider/distribute/nouislider.css';
:host {
padding: 50px 30px;
display: block;
}
my-slider.tsx
import { Component, h, Prop, Event, EventEmitter } from '#stencil/core';
import noUiSlider from "nouislider";
#Component({
tag: 'skim-slider',
styleUrl: 'skim-slider.scss',
shadow: true
})
export class SkimSlider {
#Prop() min!: number;
#Prop() max!: number;
private slider: HTMLElement;
#Event() update: EventEmitter;
componentDidLoad() {
const slider = noUiSlider.create(this.slider, {
start: [this.min, this.max],
tooltips: [true, true],
range: {
'min': this.min,
'max': this.max
}
});
slider.on('change', (value) => this.update.emit(value));
}
render() {
return (
<div ref={e => this.slider = e}></div>
);
}
}
The trick that did it for me was 'display: block'

How to use theme overrides with nested elements?

If I adjust the size of a button in the theme, like this:
const theme = createMuiTheme({
overrides: {
MuiButton: {
fab: {
width: 36,
height: 36,
},
},
MuiSvgIcon: {
root: {
width: 16,
},
},
},
};
Then the button and the icon appear at the size I want. However this affects all icons, whether they're inside a button or not!
How can I say that I only want the MuiSvgIcon properties to apply when the element is found inside a MuiButton element?
Well I've worked out one way to do it, but it's not ideal because it relies on the internal structure of the MuiSvgIcon. But it might serve as a starting point for someone else. Better answers are most welcome.
const theme = createMuiTheme({
overrides: {
MuiButton: {
fab: {
width: 36,
height: 36,
'& svg': {
width: 16,
},
},
},
},
};
It works by applying a style to the <svg> DOM element inside the JSX <Button variant="fab"> element.
It's not documented anywhere unfortunately, but you can use any sort of CSS selectors to help. Use the "attribute contains" pattern with the class attribute itself to target descendants:
const theme = createMuiTheme({
overrides: {
MuiButton: {
root: {
// Targets MuiSvgIcon elements that appear as descendants of MuiButton
'& [class*="MuiSvgIcon-root"]': {
width: 16
}
},
},
},
};
Note 1: Caution! Material UI minifies these classnames in prod builds by default. If you want to preserve these classnames for prod, you will need to tweak the class generator function: https://material-ui.com/customization/css-in-js/ (see dangerouslyUseGlobalCSS)
Note 2: with this pattern, occasionally you end having to use !important if there's already a competing inline style that you want to override. If you're using styled-components, you can increase the specificity by using && (see the Material UI docs).

Can't style slider (or maybe anything) in Material UI

There was an issue requesting documentation for theming which the author subsequently closed. The author found their answer. A non-programmer will probably not. At least, the non-programmer designer I'm helping doesn't even know where to start (and I still don't have a working different colored slider). This kind of documentation would be great. Even if it's just a link to the code #jdelafon found with some explanation that would suffice to answer the following specific example.
Ultimately, we want a set of sliders with each one a different color. It seems like the appropriate way to do this is with per-element inline styles.
I made a simple example here. Can you change the color of the slider? (We started down the path of breaking out to CSS, but the widget is so dynamic that this approach ends up being quite ugly.)
Slider has two different slots for theming, neither of which seems to respond to an embedded object with a selectionColor key.
Should be simple. Probably it is, but it appears to be undocumented. Otherwise it's a rad UI toolkit, thanks devs!
Take a look at this line of getMuiTheme.js. You can find there that slider can have those styles overridden:
{
trackSize: 2,
trackColor: palette.primary3Color,
trackColorSelected: palette.accent3Color,
handleSize: 12,
handleSizeDisabled: 8,
handleSizeActive: 18,
handleColorZero: palette.primary3Color,
handleFillColor: palette.alternateTextColor,
selectionColor: palette.primary1Color,
rippleColor: palette.primary1Color,
}
In material-ui you need to use MuiThemeProvider in order to use your custom theme. Taking your example:
...
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import { Slider } from 'material-ui';
const theme1 = getMuiTheme({
slider: {
selectionColor: "red",
trackSize: 20
}
})
const theme2 = getMuiTheme({
slider: {
selectionColor: "green",
trackSize: 30
}
})
const HelloWorld = () => (
<div>
...
<MuiThemeProvider muiTheme={theme1}>
<Slider defaultValue={0.5}/>
</MuiThemeProvider>
<MuiThemeProvider muiTheme={theme2}>
<Slider defaultValue={0.5}/>
</MuiThemeProvider>
</div>
);
export default HelloWorld;
Your modified webpackbin: http://www.webpackbin.com/EyEPnZ_8M
The sliderStyle you tried to use is for different styles :-) Like marginTop / marginBottom, a full list can be found here.