I have a chat window on my app and I want to update the size of an image in this chat window when the chat window is less than a certain width. Is there a way I can update the css style or class based on the width?
I'm using typescript and have the value of my cat window passed in:
#Input()
public chatWidth: number;
In my html, I was attempting to do something like this where I would apply a css class if the chatWidth property was less than 400:
<img *ngIf="upsell?.image?.url; let url" [src]="url" ng-class="{bigger-img : chatWidth < 400}">
However, this doesn't work and I don't seem to even get an error in my console
Use
<img *ngIf="upsell?.image?.url; let url" [src]="url" [ngClass]="{'bigger-img': chatWidth < 400}">
More info here on ngClass.
UPDATE
You can, I believe, wrap the condition in a 'method' that returns a boolean defined in your respective component and call it in the template instead of directly declaring it in the template. Here, in your case,
in component.ts,
checkChatWidth(): boolean {
return this.chatWidth < 400;
}
then, in your template,
<img *ngIf="upsell?.image?.url; let url" [src]="url" [ngClass]="{'bigger-img': checkChatWidth()}">
You have to take care of the possible 'null' checks within your 'method' that may arise due to not having a value for the 'chatWidth' input property based on your code setup.
Related
I have an application where I'm using Material UI and its theme provider (using JSS).
I'm now incorporating fullcalendar-react, which isn't really a fully fledged React library - it's just a thin React component wrapper around the original fullcalendar code.
That is to say, that I don't have access to things like render props to control how it styles its elements.
It does however, give you access to the DOM elements directly, via a callback that is called when it renders them (eg. the eventRender method).
Here's a basic demo sandbox.
Now what I'm wanting to do is make Full Calendar components (eg, the buttons) share the same look and feel as the rest of my application.
One way to do this, is that I could manually override all of the styles by looking at the class names it's using and implementing the style accordingly.
Or - I could implement a Bootstrap theme - as suggested in their documentation.
But the problem with either of these solutions, is that that:
It would be a lot of work
I would have synchronisation problems, if I made changes to my MUI theme and forgot to update the calendar theme they would look different.
What I would like to do is either:
Magically convert the MUI theme to a Bootstrap theme.
Or create a mapping between MUI class names and the calendar class names, something like:
.fc-button = .MuiButtonBase-root.MuiButton-root.MuiButton-contained
.fc-button-primary= .MuiButton-containedPrimary
I wouldn't mind having to massage the selectors etc to make it work (ie. For example - MUI Buttons have two internal spans, whereas Full Calendar have just one). It's mostly about when I change the theme - don't want to have to change it in two places.
Using something like Sass with its #extend syntax would is what I have in mind. I could create the full-calendar CSS with Sass easily enough - but how would Sass get access to the MuiTheme?
Perhaps I could take the opposite approach - tell MUI 'Hey these class names here should be styled like these MUI classes'.
Any concrete suggestions on how I would solve this?
Here is my suggestion (obviously, it's not straight forward). Take the styles from the MUI theme and generate style tag based on it using react-helmet. To do it event nicely, I created a "wrapper" component that do the map. I implemented only the primary rule but it can be extended to all the others.
This way, any change you will do in the theme will affect the mapped selectors too.
import React from "react";
import { Helmet } from "react-helmet";
export function MuiAdapter({ theme }) {
if (!theme.palette) {
return <></>;
}
return (
<Helmet>
<style type="text/css">{`
.fc-button-primary {
background: ${theme.palette.primary.main}
}
/* more styles go here */
`}</style>
</Helmet>
);
}
And the use of the adapter
<MuiAdapter theme={theme} />
Working demo: https://codesandbox.io/s/reverent-mccarthy-3o856
You could create a mapping between MUI class names and the calendar class names by going through ref's. It's possible that this is not what some would call "best practice"...but it's a solution :). Note that I updated your component from a functional component to a class component, but you could accomplish this with hooks in a functional component.
Add refs
Add a ref to the MUI element you want to set as a reference, in your case the Button.
<Button
color="primary"
variant="contained"
ref={x => {
this.primaryBtn = x;
}}
>
And a ref to a wrapping div around the component you want to map to. You can't add it directly to the component since that wouldn't give us access to children.
<div
ref={x => {
this.fullCal = x;
}}
>
<FullCalendar
...
/>
</div>
Map classes
From componentDidMount() add whatever logic you need to target the correct DOM node (for your case, I added logic for type and matchingClass). Then run that logic on all FullCalendar DOM nodes and replace the classList on any that match.
componentDidMount() {
this.updatePrimaryBtns();
}
updatePrimaryBtns = () => {
const children = Array.from(this.fullCal.children);
// Options
const type = "BUTTON";
const matchingClass = "fc-button-primary";
this.mapClassToElem(children, type, matchingClass);
};
mapClassToElem = (arr, type, matchingClass) => {
arr.forEach(elem => {
const { tagName, classList } = elem;
// Check for match
if (tagName === type && Array.from(classList).includes(matchingClass)) {
elem.classList = this.primaryBtn.classList.value;
}
// Run on any children
const next = elem.children;
if (next.length > 0) {
this.mapClassToElem(Array.from(next), type, matchingClass);
}
});
};
This is maybe a little heavy handed, but it meets your future proof requirement for when you updated update Material UI. It would also allow you to alter the classList as you pass it to an element, which has obvious benefits.
Caveats
If the 'mapped-to' component (FullCalendar) updated classes on the elements you target (like if it added .is-selected to a current button) or adds new buttons after mounting then you'd have to figure out a way to track the relevant changes and rerun the logic.
I should also mention that (obviously) altering classes might have unintended consequences like a breaking UI and you'll have to figure out how to fix them.
Here's the working sandbox: https://codesandbox.io/s/determined-frog-3loyf
I'm trying to generate classes with Stylus {block} insertions while on an iteration with that code:
// Blocks
flexbox =
display flex
inline-flexbox =
display inline-flex
// Properties collection
props = {
'flexbox': 'flex',
'inline-flexbox': 'inline-flex'
}
// Generating classes
for kProp, vProp in props
.u-{vProp}
{kProp}
{kProp} is supposed to output {flexbox} and {inline-flexbox} but I guess there's some kind of syntax conflict between calling a Stylus {block} and calling the iteration variable.
So basically with this code, I got no output at all.
I also tried to escape the { } but no luck.
Does anyone know a workaround?
No proper solution but some workarounds I found:
You can replace {blocks} by extending $placeholders but be aware that with Stylus you can't extend inside a Media Query (it just ignore the MQ part)
You can simply replace {blocks} by mixins() (and it works inside Media Queries) which is the one I chose.
So basically now my code looks like this:
// Blocks
flexbox()
display flex
inline-flexbox()
display inline-flex
// Properties collection
props = {
'flexbox': 'flex',
'inline-flexbox': 'inline-flex'
}
// Generating classes
for kProp, vProp in props
.u-{vProp}
{kProp}()
I am trying to conditionally set a classname based on a boolean variable. I have set the variable within the parent component:
public alteredSidebar: boolean;
And this is my though process behind conditionally defining which class it is:
<div [class]="alteredSidebar == false ? 'secondsidenav' : 'sidenav'" id="rollup-sidenav" [ngClass]="{ 'greyout': greyOut }"></div>
I have defined 2 classes with css file, one called secondsidenav and another called sidenav. Wherever I set the boolean as false, I would like the classname to equal the secondsidenav and where it is not false it be sidenav. Here is an instance of where I am using it and I am expecting the class to be set to 'secondsidenav':
<app-rollup [data]="rollupData" (resetCalled)="onRollupReset()" (entrySelected)="onRollupSelectEvent($event)" [alteredSidebar]="false">
</app-rollup>
However I am getting the following error: "Can't bind to 'alteredSidebar' since it isn't a known property of 'app-rollup'."
use #Input() decorator on it
#Input()
public alteredSidebar: boolean;
As #junk said, you should use the #Input() decorator in the app-rollup component and I'd like to add, do not mix [className] with ngClass, this might not be related to your problem but it gets really buggy if you're using a reactive property to programatically add or remove a class, just pick one and try not to mix them it will also make the code more consistent. Oh, and the correct syntax is [className] you're probably confusing it with [class.foo]="expresion" which will apply the 'foo' class if the expression is true
Personally I'd do something like this, also sticking with one approach is considered a best practice
<div id="rollup-sidenav" [ngClass]="{
'greyout': greyOut,
'secondsidenav': !alteredSidebar,
'sidenav': alteredSidebar
}">
</div>
Hope that helps, let me know if it doesn't!
#Berheet.. try this
assign the value alteredSidebar in the component itself and pass it like below
parentComponent.ts
public alteredSidebar: boolean = false;
parentComponent.html
<app-rollup [data]="rollupData" (resetCalled)="onRollupReset()" (entrySelected)="onRollupSelectEvent($event)" [alteredSidebar]="alteredSidebar">
</app-rollup>
Add Input in child component
childComponent.ts
#Input() alteredSidebar: boolean;
childComponent.html
<div [class]="alteredSidebar == false ? 'secondsidenav' : 'sidenav'" id="rollup-sidenav" [ngClass]="{ 'greyout': greyOut }"></div>
I'd suggest to simply set the div-tags class property conditionally as follows
[class]="alteredSidebar ? 'sidenav' : 'secondsidenav'"
This way you get a more readable condition ? positive case : negative case flow and you don't need to compare your variable to anything.
I am using a REACT BIG CALENDAR and I want to get access to the css values in one of my functions.
I created a style component and override the library
const StyledCalendar = styled(Calendar);
Now for example there is a div inside of the Calendar with the class = "hello",
How would I access the css values of "hello" in a function? Similar to property lookup say in stylus.
I have tried window.getComputedStyle(elem, null).getPropertyValue("width") but this gives the css of the parent component.
If you know the class name, you should be able to select that and give that element to getComputedStyle instead of giving it StyledCalendar. Something like:
const childElement = document.getElementsByClassName('hello')[0];
const childWidth = getComputedStyle(childElement).getPropertyValue('width');
(this assumes that there's only one element with the class 'hello' on the page, otherwise you'll have to figure out where the one you want is in the node list that's returned by getElementsByClassName)
You can do it using simple string interpolation, just need to be sure that className is being passed to Calendar's root element.
Like this:
const StyledCalendar = styled(Calendar)`
.hello {
color: red;
}
`
Calendar component
const Calendar = props => (
// I don't know exact how this library is structured
// but need to have this root element to be with className from props
// if it's possible to pass it like this then you can do it in this way
<div className={props.className}>
...
<span className="hello"> Hello </span>
...
</div>
)
See more here.
I'm running into the following issue which I can't think of a way around:
I have a the following application structure:
AppComponent (access to Redux store)
||
CardStackComponent
||
CardComponent (many cards in a stack)
each CardComponent can have three states: accepted, rejected, or neither, represented by three classes: card--accepted, card--rejected, and card--in-stack.
There are buttons the user can click to accept or reject the topmost CardComponent, which bubbles an event up to AppComponent. AppComponent then uses a reducer to update the value of an accepted and rejected property on the Object that is used to render the CardComponent. In CardComponent I have three #HostBinding decorators which are used to add/remove classes from the CardComponent host element.
#HostBinding('class.card--accepted') accepted: boolean = false;
#HostBinding('class.card--rejected') rejected: boolean = false;
#HostBinding('class.card--in-stack') inStack: boolean = true;
the classes are being added/removed as expected as I hit the buttons to accept/reject the cards, however, there's one strange issue: To allow the cards to stack, I'm using CSS to position the top three cards absolutely on top of eachother, and hide any others. This works perfectly and is achieved using :nth-child
card--in-stack {
&:nth-child(1) {...}
&:nth-child(2) {...}
&:nth-child(3) {...}
&:nth-child(n+4) {...}
}
however, when the top card is accepted, the class card--in-stack is removed and the class card--accepted is added. This means that what was the second card in the stack (card--in-stack:nth-child(2)) should now be card--in-stack:nth-child(1), the one below it should now be card--in-stack:nth-child(2), and so on. Inspecting the second card in the stack (the one under the card that was just accepted), it still has the card--in-stack:nth-child(2) styles being applied, even through it is the first element on the page with class card--in-stack. Is there a way to get the CSS to respect :nth-child when changing classes on a component using the #HostBinding decorator?