Angular 11 - Add icons with different styles to a custom button component - javascript

In my Angular 11 App I've implemented a custom button component.
For styling I use TailwindCSS and TailwindUI.
The button can have multiple colors(red, blue, gray, etc) and also different sizes (xs, sm, md, lg, xl).
I want to create a variant for these buttons: a button with a leading icon, as shown here:
https://tailwindui.com/components/application-ui/elements/buttons#component-8a4b7248253ad4c9ee892c655d7ff5ec.
For the icons, I use the following library:
https://ng-heroicons.dimaslz.dev/
An icon is a component, like: <mail-solid-icon></mail-solid-icon>, <bookmark-solid-icon></bookmark-solid-icon> etc.
Because of the different sizes (xs, sm, md, lg, xl) of the button, I need to add custom Tailwind classes to the icon. For example:
<app-button [size]="'xs'">
<mail-solid-iconclass="-ml-0.5 mr-2" [size]="16"></mail-solid-icon>
Button text
</app-button>
<app-button [size]="'xl'">
<bookmark-solid-icon class="-ml-1 mr-3 h-5 w-5" [size]="20"></bookmark-solid-icon>
Button text
Desired result:
I want to be able to provide only the icon component and then, in button's component class, to add the classes -ml-0.5 mr-2, or -ml-1 mr-3 h-5 w-5; as well as the size property.
Example of use in a template:
<app-button [size]="'xl'">
<bookmark-solid-icon></bookmark-solid-icon>
Button text
</app-button>
Output:
<app-button [size]="'xl'">
<bookmark-solid-icon class="-ml-1 mr-3 h-5 w-5" [size]="20"></bookmark-solid-icon>
Button text
</app-button>
I've tried to use a custom directive and get it using #ContentChild, but I can't add the classes to it.
Thanks!
Stackblitz example:
https://stackblitz.com/edit/angular-ivy-6gpcby?file=src%2Fapp%2Fbutton%2Fbutton.component.ts

This is the sample code that may help you to achieve what you want to do: https://stackblitz.com/edit/angular-ivy-xymefd?file=src%2Fapp%2Fbutton%2Fbutton.component.ts
What I changed from your code are:
Add ButtonIconDirective to AppModule declarations.
Use appButtonIcon instead of docsButtonIcon.
Inject ElementRef into ButtonIconDirective. (We will use ElementRef in ButtonComponent).
Append a template variable onto <bookmark-solid-icon> to get its component instance.
Rewrite ngAfterContentInit in ButtonComponent method to update size and add new CSS classes at runtime:
public ngAfterContentInit(): void {
console.log( 'this.icon: ', !!this.icon);
if (this.icon) {
// reference to ng-heroicons component source code
this.icon.style = '';
this.icon.size = 50;
this.icon.renderStyle();
this.renderer.addClass(this.iconi.er.nativeElement, '-ml-1');
this.renderer.addClass(this.iconi.er.nativeElement, 'mr-3');
this.renderer.addClass(this.iconi.er.nativeElement, 'h-5');
this.renderer.addClass(this.iconi.er.nativeElement, 'w-5');
console.log(this.icon.style);
}
}

Related

Design system: styles override using TailwindCSS

I am trying to create a Design System using ReactJS and TailwindCSS.
I created a default Button component with basic styling as follow:
import React from "react";
import classNames from "classnames";
const Button = React.forwardRef(
({ children, className = "", onClick }, ref) => {
const buttonClasses = classNames(
className,
"w-24 py-3 bg-red-500 text-white font-bold rounded-full"
);
const commonProps = {
className: buttonClasses,
onClick,
ref
};
return React.createElement(
"button",
{ ...commonProps, type: "button" },
children
);
}
);
export default Button;
I then use the Button in my page like:
import Button from "../src/components/Button";
export default function IndexPage() {
return (
<div>
<Button onClick={() => console.log("TODO")}>Vanilla Button</Button>
<div className="h-2" />
<Button
className="w-6 py-2 bg-blue-500 rounded-sm"
onClick={() => console.log("TODO")}
>
Custom Button
</Button>
</div>
);
}
This is what is displayed:
Some attributes are overridden like the background-color but some aren't (the rest).
The reason is the classes provided by TailwindCSS are written in an order where bg-blue-500 is placed after bg-red-500, therefore overriding it. On the other hand, the other classes provided in the custom button are written before the classes on the base button, therefore not overriding the styles.
This behavior is happening with TailwindCSS but might occurs with any other styling approach as far as the class order can produce this scenario.
Do you have any workaround / solution to enable this kind of customisation?
Here is a full CodeSanbox if needed.
One approach is to extract classes from your component using Tailwind's #apply in your components layer.
/* main.css */
#layer components {
.base-button {
#apply w-24 py-3 bg-red-500 text-white font-bold rounded-full;
}
}
// Button.js
const Button = React.forwardRef(({ children, className = "", onClick }, ref) => {
const buttonClasses = classNames("base-button", className);
// ...
);
This will extract the styles into the new base-button class, meaning they can easily be overwritten by the utility classes you pass to the Button component.
Another approach to create reusable React components using Tailwind is as follows..
Read this gist
https://gist.github.com/RobinMalfait/490a0560a7cfde985d435ad93f8094c5
for an excellent example.
Avoid using className as a prop. Otherwise, it'd be difficult for you to know what state your component is in. If you want to add an extra class, you can easily extend.
You need a helper for combining classname strings conditionally. Robert, the writer of this gist, shared the helper function also with us:
export function classNames(...classes: (false | null | undefined | string)[]) {
return classes.filter(Boolean).join(" ");
}
To have Tailwind CSS override material theming (or something else for that matter) one could apply !important to all tailwind utilities with configuration to module.exports.
The important option lets you control whether or not Tailwind’s utilities should be marked with !important. This can be really useful when using Tailwind with existing CSS that has high specificity selectors.
To generate utilities as !important, set the important key in your configuration options to true:
tailwind.config.js
module.exports = {
important: true
}
https://tailwindcss.com/docs/configuration#important
To solve, I recommend doing what Bootstrap does. Use a default class for your default button like:
.button {
width: 2rem;
background-color: red;
border-radius: 0.25rem;
}
Then when customizing a button you should apply classes that either come after the button class in your CSS file, or come in a different CSS file that is called after your default CSS file, or use the !important declaration.
Old answer
Use your browser developer tools to observe how your browser is loading CSS styles on an element. For example, in Chrome, right-click on the custom button and select "Inspect". A DevTools window will open and the element will be highlighted in the DOM.
On the right, you should have a Styles pane. There, you'll see a list of all the CSS styles being applied to the element. Styles with strikethroughs are being overridden by styles called by other CSS classes or inline styles.
In your case, the custom button has both the "CommonProps" classes and the classes you're adding in IndexPage. For example, both class w-6 and class w-24.
Class w-24 is overriding class w-6 because of CSS precedence. Read more about CSS precedence here. Check out rule #3 in the accepted answer. I think that's what's happening to you.
To solve, you may want to remove some classes from commonProps. Or use the !important declaration on some classes. This is the part of your design system that you need to think through. Look at how other systems like Bootstrap have done it.

Applying CSS properties based on subscribed variable. Dark mode vs light

I want to change the CSS property based on if a user has clicked on dark mode or light. I can get the a subscription going to capture the value every time the modes are switch but i want to be able to change certain property's in CSS when the different modes are clicked.
I'm using Angular 9
Any idea on how I can do this?
You can use BehaviourSubject RXJS to implement this feature. By default you can set to false and use light mode when ever User clicks the switch using next() method change the behaviour subject to true then dark etc. you have to subscribe to BehaviourSubject variable on init and handle the response.
You can have the reference at BehaviourSubject.
I personally implemented this dark mode feature in my project here is the link telivic.com the approach I have used in that site is as followed.Hope this works for you.
addDark(){
document.getElementById('nav').classList.add('dark');
document.getElementById('box').style.backgroundColor = "#35363A";
document.getElementsByTagName("BODY")[0].classList.add('dark');
document.getElementById('modal').style.backgroundColor ="bisque";
if(this.service.flag){
document.getElementById('opt').classList.add('dark');
}
document.getElementById('gen').style.color="white";
}
removeDark(){
document.getElementById('nav').classList.remove('dark');
document.getElementById('box').style.backgroundColor = "#fff";
document.getElementsByTagName("BODY")[0].classList.remove('dark');
document.getElementById('modal').style.backgroundColor ="white";
if(this.service.flag){
document.getElementById('opt').classList.remove('dark');
}
document.getElementById('gen').style.color="black";
}
/*Binded my switch to this function I have used service so that user dark/light mode preference can be processed in other pages also.*/
darkMode(){
this.dark = !this.dark;
this.userService.setMode();
if(this.dark){
this.addDark();
}else{
this.removeDark();
}
}
You can use styled-components. One example would be:
import styled from 'styled-components'
const ComponentName = styled.div`
color: ${props => props.mode === `dark` ? `white` : `black`};
`
And then in your render, do this:
<ComponentName mode={mode} />
You can add a variable in ts file,
modeType: string;
This will change as 'dark' or 'light' depending on user's selection.
In your html to change the css property use ngClass.
Or use
<div class="{{modeType == 'dark' ? 'dark-property': 'light-property'}}">
In your css file,
.dark-property{
add your "dark" css styles
}
.light-property{
add your "light" css styles
}

Font Awesome 5 icon's class binding change doesn't affect the icon

During the upgrade process of Font Awesome 5 from 4.7.0, I noticed that any class bindings I gave to an <i> tag would not function as it did before.
Imagine the following element with a class binding:
<i class.bind="iconClass"></i>
And imagine the initial value of the iconClass being 'fas fa-cog'. When changing the value of iconClass to 'fas fa-ship', the icon won't update to the newly set icon classes. It will remain a cog icon.
I believe this happens because Font Awesome 5 replaces the <i> tags with <svg> tags and doesn't copy over the class binding properly and thus not trigger an icon change.
In the following example, the bound classes are changed after two seconds to illustrate the problem, please see this GistRun for an example of the issue. See app.html and app.js for the implementation. It also contains a dirty workaround.
How can/should this behaviour be implemented?
Inspired by the answer of #huocp utilising innerhtml.
Using a custom component to solve this problem does indeed work. However, as I will only be having one icon with a class binding in my project, I have found a simpler way of doing this:
<i innerhtml="<i class='${iconClass}'></i>"></i>
A change of the iconClass value will generate a new <i> child (while replacing the old <svg> element) within the parent, after which Font Awesome 5 will convert this <i> child into an <svg>.
Any elements can be used as parent and child elements of course, I just think <i> looks short and clean, it will nest the <svg> generated by Font Awesome 5 within the <i>.
See working gistrun here: https://gist.run/?id=55559f3bd606aa854502f3ddbbcad480
Use this custom component like this.
<fa-svg icon-class.bind="iconClass"></fa-svg>
fa-svg.js
import {inject, inlineView, bindable} from 'aurelia-framework';
#inject(Element)
#inlineView("<template></template>")
export class FaSvg {
#bindable iconClass;
constructor(element) {
this.element = element;
}
iconClassChanged(newIcon) {
if (!this.live) return;
this._rebuild(newIcon);
}
attached() {
this.live = true;
this._rebuild(this.iconClass);
}
detached() {
this.live = false;
}
_rebuild(iconClass) {
this.element.innerHTML = iconClass ? `<i class="${iconClass}"></i>` : '';
}
}
I had exactly same issue before.
As you said, you were using "svg with js" (the fa5 recommended way). It does fight Aurelia since the svg version use JavaScript to replace DOM element, the binding of Aurelia getting destroyed along with old DOM element.
Switch to "web font with css" which fa5 supported perfectly. It will obey your command as fa4.

with draft.js is it possible to create a custom block span with classname

I am using draft.js, and I have everything I need working except for one thing.
I want to be able to add a custom block option that will apply a span with a custom class (e.g. content) around the selected content in the editor.
Is this possible with draft-js custom blocks?
Any good examples out there? (didn't find anything when googling)
You can do it without wrapping text to the element with a custom class. You can style selected text with method RichUtils.toggleInlineStyle. More details described here.
Look at this working example - https://jsfiddle.net/x2gsp6ju/2/
Define customStyleMap object. Keys of this object should be unique names of your custom styles and values - objects with appropriate styles.
const customStyleMap = {
redBackground: {
backgroundColor: 'red'
},
underlined: {
textDecoration: 'underline',
fontSize: 26
},
};
Pass this object to customStyleMap property of Editor component:
<Editor
placeholder="Type away :)"
editorState={this.state.editorState}
onChange={this._handleChange}
customStyleMap={customStyleMap}
/>
In this example, I apply styles for selected text after click on appropriate buttons, I call this.applyCustomSTyles method and pass style-name as first argument. In this method I generate new editorState with RichUtils.toggleInlineStyles:
applyCustomStyles = (nameOfCustomStyle) => {
this._handleChange(
RichUtils.toggleInlineStyle(
this.state.editorState,
nameOfCustomStyle
)
);
}

Styling react-toolbox elements with className

I seem to be having some issues in regards to styling the components without using a theme. I just want to change a couple of colors without needing to create a new theme per element.
In this case, I just want to change the color of the bar to a brownish color and right now I have an input class as follows:
import style from './style.scss'
const TextInput = (props) => {
<Input className={style.textInput} {...props} />
}
And in my style.scss file:
.textInput {
.bar {
background-color: #663300;
}
}
Any help would be appreciated.
ClassName doesn't work like that.
You can't pass a Css style to a className, that is wrong.
Either pass the class names you want to apply as a string (in your case I guess it would be className="textInput bar") or you can create a className with classNames library (in any case the final result will be a string).
Just make sure you're styles are included in the page that the component is going to render and react will be smart enough to render the correct css class for each component.
As you can check in here ClassName is a string

Categories