How to avoid clashes with HTML attributes without changing the prop name? - styled-components

Using styled components and TypeScript, I'd like to define a component that has a prop with the same name as an existing HTML attribute, but a different type.
For example:
import { Color } from "color";
import styled from "styled-components";
export interface BackgroundProps {
color: Color;
}
export const Background = styled.div<BackgroundProps>`
background-color: ${({ color }) => color.hex()};
`;
This declaration works just fine, but the usage cannot be compiled, because the color prop can never satisfy the type Color & string that is declared internally by styled components.
<Background color={new Color("red")} />
// ^^^^^^^^^^^^^^^^ "Does not satisfy constraint Color & string"
<Background color="red" />
// ^^^ "Does not satisfy constraint Color & string"
Styled components arrives at this declaration for the color prop because it merges BackgroundProps & HTMLDivAttributes and HTMLDivAttributes contains color?: string.
I know I could just rename the prop to something that doesn't clash, such as colorInstance or $color, but I would prefer to keep the name color and override the type from HTMLDivAttributes instead.
If we had more access to the styled components declarations, I might be able to use Omit<HTMLDivAttributes, "color"> somewhere, but I haven't found a way to do so.
Does anyone know if there's a decent way to achieve this?

Related

Drawer Component Backdrop blocking users from interacting with page will it is open

I have a Drawer Component anchored at the bottom but I would still like to interact with the page above the drawer but either I can click out of if but the drawer closes so I tried the variants persistent and permanent both didn't work they actually made it so nothing at all happens when I click out of if. I think it has something to do with the spacing or padding above, but if anyone knows how to disable that, it would be greatly appreciated.
I solved it slightly differently, by removing the "inset" CSS property of the .MuiDrawer-modal div:
.MuiDrawer-modal {
inset: unset !important;
}
Figured out my problem, I ended us having to do some height changes to the Paper component and it seemed to work the way I wanted. You can overrided the css with makeStyles method in the #material-ui/core/styles directory. I used the classes property example
// Outside the component
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles({
drawer: {
css here ...
}
})
// Inside Component
const classes = useStyles() // the make styles returns a function and calling the useStyles returns an object with the css.
// Inside return
<Drawer
anchor="bottom"
classes={{ paper: classes.drawer }}
>
Content...
</Drawer>

How to keep custom attribute in styled component?

I created a simple styled component with a custom property test, but when rendered, the test property just did not appear in the div dom, what happened?
const StyledDiv = styled.div<{
test?: boolean;
}>`
color:red;
`;
//in render function...
<StyledDiv test>it's just test</StyledDiv>
In React boolean value is not passed to the dom element in means that test or test={true} will not be available to the dom. It only supports three types of string, number and object and it will convert those to string so all you need to do is change it too test="" and it will work.
If you are looking to access the prop test in your component you need to do something like this:
const StyledDiv = styled.div`
color: ${({test}) => test ? 'red' : 'blue'};
`;

Extending styled components

I have a Field Formik's component, in order to apply custom CSS I do:
const Input = styled(Field)`
// CSS goes here
`
And use Input component, works fine. However I use exactly the same CSS in many places, so I've extracted those CSS to standalone styled-component called SuperInput
Now, how can extend style-componet? I need something like
const Input = styled(Field)`
// inlucde CSS from SuperInput component here
`
Example code.
import styled from 'styled-components'
const SuperInput = styled.input`
// CSS here
`
import { Field } from 'formik'
import { SuperInput } from 'styled_components/super_input'
const SomeFormComponent = () => (
<>
// How to use here <Field /> that has <SuperInput /> CSS applied?
</>
)
Basically you just need to spread or append inside the template literals to get it to work. you can keep the common CSS something like
import styled, { css } from "styled-components"
const commonCss = css`
background: red;
`
And can use it in your component like this:
const Input = styled(Field)`
// CSS goes here
${commonCss}
color: hotpink;
`;
const Input1 = styled(Field)`
${commonCss}
color: lightblue;
`;
This should allow you to use the common CSS in various styled components.
For more info, you can read through css styled component API
Edit:
Styled components create HOCs.
After the added superInput definition, I understand what you are trying to do. So your superInput is creating a button with the certain css properties which you are trying to reuse. In that case when you are using Field and trying to extend SuperInput which is a button doesnot make sense. Your Field component is by default a input element(text box), it can be checkbox, radio, file input also.Whatever CSS is written in superInput can be extracted the way I mentioned above and used at multiple places. The way you are trying to do is not the way styled component is designed. That's my understanding
Note : I may be wrong here about whether it is possible or not. But that's what i can say according to my awareness . Anyone Feel free to correct me if I am wrong.

Why is theme undefined in styled component props?

Here I access theme by passing a callback function to the styled tag. I guess styled calls this callback function with the props as first argument. This works well.
export default function SectionHeading(props: SectionHeadingProps) {
const Heading = styled.h2`
${props => props.theme.green && `
color: green;
`}
`;
return (
<Heading>{propss.children}</Heading>
);
}
In this example I pass an expression that contains the props the component has received. Here, theme is undefined.
export default function SectionHeading(props: SectionHeadingProps) {
const Heading = styled.h2`
${props.theme.green && `
color: green;
`}
`;
return (
<Heading>{props.children}</Heading>
);
}
Why is theme undefined in the second example?
The reason is that these are different "props" and they are evaluated in different times, in the first example, the props are the props passed to the styled component, augmented with theme (provided you used <ThemeProvider .../>. In the second example, it is the props passed to your component.
The injection of the theme is done by styled-component library and only to styled components. Your component doesn't get it (because it is not a styled component).
Btw, your code has redundant nesting and creates a styled component each time it is invoked.
The way to do it is to simply define:
const SectionHeading = styled.div`
${props => (props.theme && props.theme.green && {color: 'green'})};
`;
and then:
export default SectionHeading;
Note that your sample code has a typo in the first part, you wrote {propss.children} (an extra 's').

How to create an SVG component dynamically in Angular2?

I am creating a web application which uses SVG.
I have created components consist of SVG element, and they are put into a root svg element.
They have attribute selector, because SVG/XML document tree is strict so I cannot use element selector.
And they have a template starts with svg:g tag:
#Component({
selector:'[foo]',
template: '<svg:g>...</svg:g>',
})
In the application, I want to create a component when a user press a button,
and simultaneously start dragging it.
I thought it can be achieved by creating a component dynamically using ComponentResolver:
#ViewChild('dynamicContentPlaceHolder', {read: ViewContainerRef})
protected dynamicComponentTarget: ViewContainerRef
private componentResolver: ComponentResolver
onMouseDown() {
this.componentResolver
.resolveComponent(FooComponent)
.then((factory) => {
const dynamicComponent = this.dynamicComponentTarget.createComponent(factory, 0)
const component: FooComponent = dynamicComponent.instance
const element = dynamicComponent.location.nativeElement
// add event listener to start dragging `element`.
})
}
Component is created when onMouseDown() called, but its DOM element is div, so it is illegal element in svg document and cannot be displayed.
I have tried with selector='svg:g[foo]', then g element is created, but its namespace is not for SVG (http://www.w3.org/2000/svg), but normal HTML namespace (http://www.w3.org/1999/xhtml) and its class is HTMLUnknownElement > g.
I also tried with selector='svg:svg[foo]', then svg:svg element is created and it is displayed. But svg:svg cannot move with transform attribute so this doesn't work well for my application.
How can I dynamically create svg:g element for attribute selector component?
I am using Angular2: 2.0.0-rc4.
You're right about the namespacing issues keeping the g element from rendering as svg. Unfortunately, attaching the node as an svg element is the only way to feasibly get the component to namespace properly.
However, this doesn't mean this won't work. If you add the drag functionality as a directive on the g element in the template, it will be compiled with your component, and you can offset your logic into that directive. The top level svg will be namespaced correctly, and the template will inherit this accordingly.
import {Component, Input} from '#angular/core';
#Component({
selector: 'svg:svg[customName]', // prevent this from hijacking other svg
template: '<svg:g dragDirective>...</svg:g>', // note the directive added here
style: []
})
export class GComponent {
constructor() { }
}
This may not be ideal, but until https://github.com/angular/angular/issues/10404 is resolved, there's not much of an alternative.
I am not sure that my solution is right but it works for me (even with ivy):
#Component({
...
})
class ParentComponent {
constructor(
private injector: Injector,
private appRef: ApplicationRef,
private componentFactoryResolver: ComponentFactoryResolver,
) {}
createDynamicComponent() {
let node = document.createElementNS("http://www.w3.org/2000/svg", "svg");
let factory = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
let componentRef = factory.create(this.injector, [], node);
this.appRef.attachView(componentRef.hostView);
}
}
After that you must manually append node into DOM.
Instead of trying to create your component to the view with the Component Resolver I will do this instead.
Create a object with properties which match the attributes you want to pass to your SVG Component.
Append this object to an array (ex.svgItems).
Add *ngFor="svgItem in svgItems" to the SVG component you want to create dynamically.
Hope it's clear and solve your problem.

Resources