27

I want to wrap up my ant-design components with styled-components, I know that this is possible (https://gist.github.com/samuelcastro/0ff7db4fd54ce2b80cd1c34a85b40c08) however I'm having troubles to do the same with TypeScript.

This is what I have so far:

import { Button as AntButton } from 'antd';
import { ButtonProps } from 'antd/lib/button/button';
import styledComponents from 'styled-components';

interface IButtonProps extends ButtonProps {
   customProp: string;
}

export const Button = styledComponents<IButtonProps>(AntButton)`
  // any custom style here
`;

As you can see I'm defining my ant-design button with as any in order to make it work, otherwise I get some incompatible types like:

Argument of type 'typeof Button' is not assignable to parameter of
type 'ComponentType<IButtonProps>'.

Type 'typeof Button' is not assignable to type
'StatelessComponent<IButtonProps>'.

Types of property 'propTypes' are incompatible.

 Property 'customProp' is missing in type '{ 
    type: Requireable<string>; 
    shape: Requireable<string>; 
    size: Requireable<string>; 
    htmlType: Requireable<string>; 
    onClick: ...
    etc
 }

Thank you.

Solution:

import { Button as AntButton } from 'antd';
import { NativeButtonProps } from 'antd/lib/button/button';
import * as React from 'react';
import styledComponents from 'styled-components';

export const Button = styledComponents<NativeButtonProps>(props => <AntButton {...props} />)`
    // custom-props
`;
Samuel Castro
  • 291
  • 1
  • 3
  • 5
  • It would be helpful if you could be a bit more specific - what exactly are the issues you're facing? The more info you can give (preferably showing your code, too), the more people will be able to help :) – Joe Clay Oct 08 '18 at 15:13
  • 1
    @JoeClay Thanks added more info. – Samuel Castro Oct 09 '18 at 15:41
  • Please add your imports to the question. I'm guessing `import styledComponents from "styled-components"` and `import { Button as AntButton } from "antd"` but I can't guess where `IButtonProps` comes from. – Matt McCutchen Oct 10 '18 at 01:43
  • @MattMcCutchen Added imports. Thanks! – Samuel Castro Oct 10 '18 at 14:46
  • Just tried: `export const Button = styledComponents(AntButton)` and it seems to be working fine, I just don't know what does the second type parameter of `styledComponents` mean. – Samuel Castro Oct 10 '18 at 14:53
  • The solution you posted works also with `react-emotion`. Thank you!! – Yuri Jan 25 '19 at 21:02

5 Answers5

16

I have found this ancient question and try to solve in an easy way:

import React from 'react';
import styled from 'styled-components';
import { Card } from 'antd';
import { CardProps } from 'antd/lib/card';

export const NewCard: React.FunctionComponent<CardProps> = styled(Card)`
  margin-bottom: 24px;
`;

without render props :D

if you just need wrap a component as function component, that's all right. But you will lose the properties of class component such as Card.Meta.

There is a workaround:

import React from 'react';
import styled from 'styled-components';
import { Card } from 'antd';
import { CardProps } from 'antd/lib/card';

export const NewCard: typeof Card = styled(Card)<CardProps>`
  margin-bottom: 24px;
` as any;

Everything (maybe... XD) works as original Antd Component ;)

XuToTo
  • 161
  • 1
  • 4
6

The above solutions didn't work for me, this solved it though.

const Button = styled((props: NativeButtonProps) => <AntButton {...props} />)``;
Webber
  • 941
  • 11
  • 26
4

my code is here. and it is work.


import React from 'react';
import { Button as AntButton } from 'antd';
import { ButtonProps } from 'antd/lib/button/button';
import styled from 'styled-components';

const Container = styled.div`
  font-size: 20px;
`;


const Button: React.FunctionComponent<ButtonProps> = styled(AntButton)`
  margin-top: 24px;
  margin-left: 30px;
`;

export default function Home() {
  return (
    <Container>
      Hello World
      <Button type="primary">test</Button>
    </Container>
  );
}



3

The root of the problem seems to be that styled-components expects the inner component (AntButton) to accept all the props in the specified interface (IButtonProps), but AntButton does not accept customProp. To fix this, follow the last example in this section of the documentation and use a stateless function component to remove customProp before calling AntButton.

export const Button = styledComponents<IButtonProps>(
  ({ customProp, ...rest }) => <AntButton {...rest} />)`
  // any custom style here
`;
Matt McCutchen
  • 28,856
  • 2
  • 68
  • 75
  • Thanks Matt, this worked just fine, however now when I try to use my new Button component with like: ` – Samuel Castro Oct 11 '18 at 16:23
  • 1
    Ok I fixed this problem importing `NativeButtonProps` instead of `ButtonProp` from antd. – Samuel Castro Oct 11 '18 at 16:39
1

index.tsx (Button Component)

import { Button as AntButton } from 'antd'
import { NativeButtonProps } from 'antd/lib/button/button'
import 'antd/lib/button/style/css'
import * as React from 'react'
import styledComponents from 'styled-components'
import * as colours from '../colours'

const getColour = (props: any) =>
  props.status === 'green'
    ? colours.STATUS_GREEN
    : props.status === 'red'
      ? colours.STATUS_RED
      : props.type === 'primary'
        ? colours.PRIMARY
        : colours.WHITE

export interface ButtonProps extends NativeButtonProps {
  status?: string
}

export default styledComponents((props: ButtonProps) => <AntButton {...props} />)`
  &:focus,
  &:hover
  & {
    background-color: ${getColour};
    border-color: ${getColour};
  }
`
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.5.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.5.2/umd/react-dom.production.min.js"></script>

import React from 'react'
import Button, { ButtonProps } from './index'

interface ButtonAsyncSingleSuccessProps extends ButtonProps {
  clickFunc: any, // (...args: any[]) => Promise<any>
  labelLoading: string,
  labelReady: string,
  labelSuccess: string,
}

interface ButtonAsyncSingleSuccessState {
  label: string,
  loading: boolean,
  status: string
}

export default class ButtonAsyncSingleSuccess extends React.Component<
  ButtonAsyncSingleSuccessProps,
  ButtonAsyncSingleSuccessState
> {
  constructor (props: any) {
    super(props)
    this.state = {
      label: props.labelReady,
      loading: false,
      status: ''
    }
  }
  public clickHandler (event: any) {
    const { labelLoading, labelReady, labelSuccess, clickFunc } = this.props
    this.setState({
      label: labelLoading,
      loading: true,
      status: ''
    })
    clickFunc(event)
      .then(() => {
        this.setState({
          label: labelSuccess,
          loading: false,
          status: 'green'
        })
      })
      .catch(() => {
        this.setState({
          label: labelReady,
          loading: false,
          status: 'red'
        })
      })
  }
  public render () {
    const {
      labelLoading,
      labelReady,
      labelSuccess,
      clickFunc,
      ...props
    } = this.props
    const { label, loading, status } = this.state
    if (status === 'red') {
      setTimeout(() => this.setState({ status: '' }), 1000) // flash red
    }
    return (
      <Button
        {...props}
        loading={loading}
        status={status}
        onClick={(e) => this.clickHandler(e)}
      >
        {label}
      </Button>
    )
  }
}
Alvin Smith
  • 547
  • 5
  • 9