ant-design-blazor/ant-design-blazor

Ant Design v5 preparation

ldsenow opened this issue Β· 41 comments

Hi guys,

Are we looking into the upcoming AntD v5 for Blazor?

ant-design/ant-design#33862

It is dropping the less and use css-in-js. It is better for MFE.

Yes, we need to follow up antd 5.0 after the AntDesign Blazor 1.0 release later this year.

But we need to implement css in c# first, do you have any ideas?

I may have some ideas to do the similar thing without css in C#. Let me give it a go and share the repo for you guys to review.

Great, we can implement the FloatButton as a test.

Great, we can implement the FloatButton as a test.

Somehow the website's FloatButton is broken at the moment, so I just created a simple repo (https://github.com/ldsenow/BlazorCssIsolation) with a Button component to demonstrate the potential capabilities.

It is a .net 7 solution but I think it can be run under .net 6.

Look good to me. But it looks like we need a lot of work to synchronize style from design tokens. What do you think?

I will try to see if I can extract from somewhere else and turn that into our css assets somehow. Fingers crossed.

While I'm looking into AntD react's repo, I really love these comments. Designers are respected, haha.
image

πŸ˜‚ Maybe we can use some tool for converting ts to c#

https://github.com/mono/TsToCSharp
https://github.com/hez2010/TypedocConverter

πŸ˜‚ Maybe we can use some tool for converting ts to c#

https://github.com/mono/TsToCSharp https://github.com/hez2010/TypedocConverter

I guess we can create a project which uses the antd react's npm pkg to export something like e.g. a json file, a css/less file etc., somehow, the blazor end can use it. I will give it a go and see if it works.

Yes, we can use Github Actions to synchronize. Look forward to your good news!

Hi @ldsenow , antd 5.0 have been GA. Do you think we can follow them?

Have been busy last week at work. The react version use React hook context to inject the tokens. I can't find a better way other than using react testing lib to simulate react's environment. BTW, do we need just need the tokens or we also need the values? I guess we want to derive the values based on a primary, right?

I think we need the token and algorithm.

I was looking at the algorithm over the weekend. I can help with that. I guess we dont want to introduce the whole tinycolor right? Re: tokens, i cant find an easy to extract them other than using a converter. I am worried about the maintenance going forward.

Just to share this repo, it inspires me while I'm looking for the solution under the existing blazor Infrastructure.

https://github.com/vanilla-extract-css/vanilla-extract

Yes, we should use converter and shell scripts to synchronize the tokens, instead of manual maintenance.

I have written part of the code of the color palette in feat/color branch before, but some color conversion parts were not implemented, and the test results were wrong. Help is needed for this work.

I am able to extract the base tokens from ts and having a bit of difficulties with getting component tokens. However, i think it is good enough to start applying the algorithms. I have updated the repo with the token extraction, please have a look if this helps. https://github.com/ldsenow/BlazorCssIsolation

This work has made great progress, it helps a lot! Look forward to it being applied to components!

Just to give an update. I spent not much over the weekend due to the World Cup, but i did have some progress on this and hope to ship something out for early review before i may go down to a wrong path.

Great, and I'm looking forward to seeing the implementation soon, so we can move to antd5 and have a lot of the topic issues resolved.

@ldsenow I'm excited to see a lot of updates in your repo.

Sorry for being slow on this. It could have been a bigger progress however antd react changed some props to internal. So i don't have access on those any more and finding a work around. I have mostly ported their algorithms (certainly refactor is required at the end). I will squeeze some time this weekend if I don't get distracted by the world cup.

Hello @ldsenow , have you made any progress recently?

Hello @ldsenow , have you made any progress recently?

Hello, happy new year! I was in holiday and wasn't allowed to have screen time during the break. The bad news is I didnt do anything but the kind of good news is i will continue shortly.

Happy new year, wish you a happy holiday!

Any news?

Any news?

More or less I have the default theme algorithm is ready, dark and compact ones are on the way. Theme variables are generated by the algorithm. As you see in the below screenshot.
image

Nested config provider is supported. So the component can have it own override without effecting other components.
image

Component tokens/variables will be difficult to extract from Antd React. I need to implement one by one manually based on their logic. Due to this is just for POC purpose, I am confident enough that will 90% work.

@ElderJames If you have time please take a look if the approach is going to be workable / maintainable?

P.S: The link to the POC
https://github.com/ldsenow/BlazorCssIsolation

I do not want to rush anyone but what is the ETA for v5? @ElderJames

Any news?

hello there, one of our contributiors was working on an other project to achieve the v5 styles. I think we can compare this two implementations.
https://github.com/ant-design-blazor/cssincs

I'm working on porting the Antd React style to Blazor. Antd React uses the cssinjs style solution, so I provide the cssincs solution here. The following is an example of style conversion using cssincs.
Antd React Style:
Alert Component Style

import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { resetComponent } from '../../style';

export interface ComponentToken {}

type AlertToken = FullToken<'Alert'> & {
  alertIconSizeLG: number;
  alertPaddingHorizontal: number;
};

const genAlertTypeStyle = (
  bgColor: string,
  borderColor: string,
  iconColor: string,
  token: AlertToken,
  alertCls: string,
): CSSObject => ({
  backgroundColor: bgColor,
  border: `${token.lineWidth}px ${token.lineType} ${borderColor}`,
  [`${alertCls}-icon`]: {
    color: iconColor,
  },
});

export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSObject => {
  const {
    componentCls,
    motionDurationSlow: duration,
    marginXS,
    marginSM,
    fontSize,
    fontSizeLG,
    lineHeight,
    borderRadiusLG: borderRadius,
    motionEaseInOutCirc,
    alertIconSizeLG,
    colorText,
    paddingContentVerticalSM,
    alertPaddingHorizontal,
    paddingMD,
    paddingContentHorizontalLG,
  } = token;

  return {
    [componentCls]: {
      ...resetComponent(token),
      position: 'relative',
      display: 'flex',
      alignItems: 'center',
      padding: `${paddingContentVerticalSM}px ${alertPaddingHorizontal}px`, // Fixed horizontal padding here.
      wordWrap: 'break-word',
      borderRadius,

      [`&${componentCls}-rtl`]: {
        direction: 'rtl',
      },

      [`${componentCls}-content`]: {
        flex: 1,
        minWidth: 0,
      },

      [`${componentCls}-icon`]: {
        marginInlineEnd: marginXS,
        lineHeight: 0,
      },

      [`&-description`]: {
        display: 'none',
        fontSize,
        lineHeight,
      },

      '&-message': {
        color: colorText,
      },

      [`&${componentCls}-motion-leave`]: {
        overflow: 'hidden',
        opacity: 1,
        transition: `max-height ${duration} ${motionEaseInOutCirc}, opacity ${duration} ${motionEaseInOutCirc},
        padding-top ${duration} ${motionEaseInOutCirc}, padding-bottom ${duration} ${motionEaseInOutCirc},
        margin-bottom ${duration} ${motionEaseInOutCirc}`,
      },

      [`&${componentCls}-motion-leave-active`]: {
        maxHeight: 0,
        marginBottom: '0 !important',
        paddingTop: 0,
        paddingBottom: 0,
        opacity: 0,
      },
    },

    [`${componentCls}-with-description`]: {
      alignItems: 'flex-start',
      paddingInline: paddingContentHorizontalLG,
      paddingBlock: paddingMD,

      [`${componentCls}-icon`]: {
        marginInlineEnd: marginSM,
        fontSize: alertIconSizeLG,
        lineHeight: 0,
      },

      [`${componentCls}-message`]: {
        display: 'block',
        marginBottom: marginXS,
        color: colorText,
        fontSize: fontSizeLG,
      },

      [`${componentCls}-description`]: {
        display: 'block',
      },
    },

    [`${componentCls}-banner`]: {
      marginBottom: 0,
      border: '0 !important',
      borderRadius: 0,
    },
  };
};

export const genTypeStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSObject => {
  const {
    componentCls,

    colorSuccess,
    colorSuccessBorder,
    colorSuccessBg,

    colorWarning,
    colorWarningBorder,
    colorWarningBg,

    colorError,
    colorErrorBorder,
    colorErrorBg,

    colorInfo,
    colorInfoBorder,
    colorInfoBg,
  } = token;

  return {
    [componentCls]: {
      '&-success': genAlertTypeStyle(
        colorSuccessBg,
        colorSuccessBorder,
        colorSuccess,
        token,
        componentCls,
      ),
      '&-info': genAlertTypeStyle(colorInfoBg, colorInfoBorder, colorInfo, token, componentCls),
      '&-warning': genAlertTypeStyle(
        colorWarningBg,
        colorWarningBorder,
        colorWarning,
        token,
        componentCls,
      ),
      '&-error': {
        ...genAlertTypeStyle(colorErrorBg, colorErrorBorder, colorError, token, componentCls),
        [`${componentCls}-description > pre`]: {
          margin: 0,
          padding: 0,
        },
      },
    },
  };
};

export const genActionStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSObject => {
  const {
    componentCls,
    iconCls,
    motionDurationMid,
    marginXS,
    fontSizeIcon,
    colorIcon,
    colorIconHover,
  } = token;

  return {
    [componentCls]: {
      [`&-action`]: {
        marginInlineStart: marginXS,
      },

      [`${componentCls}-close-icon`]: {
        marginInlineStart: marginXS,
        padding: 0,
        overflow: 'hidden',
        fontSize: fontSizeIcon,
        lineHeight: `${fontSizeIcon}px`,
        backgroundColor: 'transparent',
        border: 'none',
        outline: 'none',
        cursor: 'pointer',

        [`${iconCls}-close`]: {
          color: colorIcon,
          transition: `color ${motionDurationMid}`,
          '&:hover': {
            color: colorIconHover,
          },
        },
      },

      '&-close-text': {
        color: colorIcon,
        transition: `color ${motionDurationMid}`,
        '&:hover': {
          color: colorIconHover,
        },
      },
    },
  };
};

export const genAlertStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSInterpolation => [
  genBaseStyle(token),
  genTypeStyle(token),
  genActionStyle(token),
];

export default genComponentStyleHook('Alert', (token) => {
  const { fontSizeHeading3 } = token;

  const alertToken = mergeToken<AlertToken>(token, {
    alertIconSizeLG: fontSizeHeading3,
    alertPaddingHorizontal: 12, // Fixed value here.
  });

  return [genAlertStyle(alertToken)];
});

Code of using the cssincs, here is blazor alert style code.

using CssInCs;
using static AntDesign.GlobalStyle;

namespace AntDesign
{
    public class AlertToken : TokenWithCommonCls
    {

        public int AlertIconSizeLG { get; set; }
        public int AlertPaddingHorizontal { get; set; }
    }

    public partial class Alert
    {
        public CSSObject GenAlertTypeStyle(
            string bgColor,
            string borderColor,
            string iconColor,
            AlertToken token,
            string alertCls) => new CSSObject()
            {
                BackgroundColor = bgColor,
                Border = $"{token.LineWidth}px {token.LineType} {borderColor}",
                ["${alertCls}-icon"] = new CSSObject()
                {
                    Color = iconColor,
                },
            };

        public CSSObject GenBaseStyle(AlertToken token)
        {
            var (
                componentCls,
                duration,
                marginXS,
                marginSM,
                fontSize,
                fontSizeLG,
                lineHeight,
                borderRadius,
                motionEaseInOutCirc,
                alertIconSizeLG,
                colorText,
                paddingContentVerticalSM,
                alertPaddingHorizontal,
                paddingMD,
                paddingContentHorizontalLG
            ) = (
                token.ComponentCls,
                token.MotionDurationSlow,
                token.MarginXS,
                token.MarginSM,
                token.FontSize,
                token.FontSizeLG,
                token.LineHeight,
                token.BorderRadiusLG,
                token.MotionEaseInOutCirc,
                token.AlertIconSizeLG,
                token.ColorText,
                token.PaddingContentVerticalSM,
                token.AlertPaddingHorizontal,
                token.PaddingMD,
                token.PaddingContentHorizontalLG
            );
            return new CSSObject
            {
                [componentCls] = new CSSObject()
                {
                    ["..."] = ResetComponent(token),
                    Position = "relative",
                    Display = "flex",
                    AlignItems = "center",
                    Padding = $"{paddingContentVerticalSM}px {alertPaddingHorizontal}px", // Fixed horizontal padding here.
                    WordWrap = "break-word",
                    BorderRadius = borderRadius,

                    [$"&{componentCls}-rtl"] = new CSSObject()
                    {
                        Direction = "rtl",
                    },

                    [$"{componentCls}-content"] = new CSSObject()
                    {
                        Flex = 1,
                        MinWidth = 0,
                    },

                    [$"{componentCls}-icon"] = new CSSObject()
                    {
                        MarginInlineEnd = marginXS,
                        LineHeight = 0,
                    },

                    ["&-description"] = new CSSObject()
                    {
                        Display = "none",
                        FontSize = fontSize,
                        LineHeight = lineHeight,
                    },

                    ["&-message"] = new CSSObject()
                    {
                        Color = colorText,
                    },

                    [$"&{componentCls}-motion-leave"] = new CSSObject()
                    {
                        Overflow = "hidden",
                        Opacity = 1,
                        Transition = @$"max-height {duration} {motionEaseInOutCirc}, opacity {duration} {motionEaseInOutCirc},
                            Padding-top {duration} {motionEaseInOutCirc}, padding-bottom {duration} {motionEaseInOutCirc},
                            Margin-bottom {duration} {motionEaseInOutCirc}",
                    },

                    [$"&{componentCls}-motion-leave-active"] = new CSSObject()
                    {
                        MaxHeight = 0,
                        MarginBottom = "0 !important",
                        PaddingTop = 0,
                        PaddingBottom = 0,
                        Opacity = 0,
                    },
                },

                [$"{componentCls}-with-description"] = new CSSObject()
                {
                    AlignItems = "flex-start",
                    PaddingInline = paddingContentHorizontalLG,
                    PaddingBlock = paddingMD,

                    [$"{componentCls}-icon"] = new CSSObject()
                    {
                        MarginInlineEnd = marginSM,
                        FontSize = alertIconSizeLG,
                        LineHeight = 0,
                    },

                    [$"{componentCls}-message"] = new CSSObject()
                    {
                        Display = "block",
                        MarginBottom = marginXS,
                        Color = colorText,
                        FontSize = fontSizeLG,
                    },

                    [$"{componentCls}-description"] = new CSSObject()
                    {
                        Display = "block",
                    },
                },

                [$"{componentCls}-banner"] = new CSSObject()
                {
                    MarginBottom = 0,
                    Border = "0 !important",
                    BorderRadius = 0,
                },
            };
        }
        public CSSObject GenTypeStyle(AlertToken token)
        {
            var (
                componentCls,
                colorSuccess,
                colorSuccessBorder,
                colorSuccessBg,
                colorWarning,
                colorWarningBorder,
                colorWarningBg,
                colorError,
                colorErrorBorder,
                colorErrorBg,
                colorInfo,
                colorInfoBorder,
                colorInfoBg
            ) = (
                token.ComponentCls,
                token.ColorSuccess,
                token.ColorSuccessBorder,
                token.ColorSuccessBg,
                token.ColorWarning,
                token.ColorWarningBorder,
                token.ColorWarningBg,
                token.ColorError,
                token.ColorErrorBorder,
                token.ColorErrorBg,
                token.ColorInfo,
                token.ColorInfoBorder,
                token.ColorInfoBg
            );
            return new CSSObject
            {
                [componentCls] = new CSSObject()
                {
                    ["&-success"] = GenAlertTypeStyle(
                colorSuccessBg,
                colorSuccessBorder,
                colorSuccess,
                token,
                componentCls
              ),
                    ["&-info"] = GenAlertTypeStyle(colorInfoBg, colorInfoBorder, colorInfo, token, componentCls),
                    ["&-warning"] = GenAlertTypeStyle(
                colorWarningBg,
                colorWarningBorder,
                colorWarning,
                token,
                componentCls
              ),
                    ["&-error"] = new CSSObject()
                    {
                        ["..."] = GenAlertTypeStyle(colorErrorBg, colorErrorBorder, colorError, token, componentCls),
                        [$"{componentCls}-description > pre"] = new CSSObject()
                        {
                            Margin = 0,
                            Padding = 0,
                        },
                    },
                },
            };
        }
        public CSSObject GenActionStyle(AlertToken token)
        {
            var (
                componentCls,
                iconCls,
                motionDurationMid,
                marginXS,
                fontSizeIcon,
                colorIcon,
                colorIconHover
            ) = (
                token.ComponentCls,
                token.IconCls,
                token.MotionDurationMid,
                token.MarginXS,
                token.FontSizeIcon,
                token.ColorIcon,
                token.ColorIconHover
            );
            return new CSSObject
            {
                [componentCls] = new CSSObject()
                {
                    ["&-action"] = new CSSObject()
                    {
                        MarginInlineStart = marginXS,
                    },

                    [$"{componentCls}-close-icon"] = new CSSObject()
                    {
                        MarginInlineStart = marginXS,
                        Padding = 0,
                        Overflow = "hidden",
                        FontSize = fontSizeIcon,
                        LineHeight = $"{fontSizeIcon}px",
                        BackgroundColor = "transparent",
                        Border = "none",
                        Outline = "none",
                        Cursor = "pointer",

                        [$"{iconCls}-close"] = new CSSObject()
                        {
                            Color = colorIcon,
                            Transition = $"color {motionDurationMid}",
                            ["&:hover"] = new CSSObject()
                            {
                                Color = colorIconHover,
                            },
                        },
                    },

                    ["&-close-text"] = new CSSObject()
                    {
                        Color = colorIcon,
                        Transition = $"color {motionDurationMid}",
                        ["&:hover"] = new CSSObject()
                        {
                            Color = colorIconHover,
                        },
                    },
                },
            };
        }

        public CSSObject[] GenAlertStyle(AlertToken token) => new CSSObject[]{
          GenBaseStyle(token),
          GenTypeStyle(token),
          GenActionStyle(token),
        };

        protected override CSSObject[] UseStyle(GlobalToken token)
        {
            var fontSizeHeading3 = token.FontSizeHeading3;
            var alertToken = MergeToken<AlertToken>(token, new AlertToken
            {
                AlertIconSizeLG = fontSizeHeading3,
                AlertPaddingHorizontal = 12,
            });

            return GenAlertStyle(alertToken);
        }
    }
}

Yep, you can see that the codes of both are consistent. But one of the problems we face is that we can't automatically convert TS to C# using scripts.
We can only manually convert the ts code to csharp code. Since there are so many union types in TS, it cannot be converted directly.

Any thing i can help with? e.g. convert a component from ts to cs using cssincs

Any thing i can help with? e.g. convert a component from ts to cs using cssincs

I decided to use AST to automatically convert TS code to CS code. This may take a lot of time. For now, this solution is still experimental. You can continue to try other solution.

I've done part of the style migration work in the v5 branch.

Any thing i can help with? e.g. convert a component from ts to cs using cssincs

I decided to use AST to automatically convert TS code to CS code. This may take a lot of time. For now, this solution is still experimental. You can continue to try other solution.

I've done part of the style migration work in the v5 branch.

Can you describe the process you adopted today? (the longest and manual, so we can understand better so we can contribute with tools for automation or even running this most boring and costly part)

@ElderJames @yoli799480165 any update for v5?
v5ζœ‰δ»€δΉˆθΏ›ε±•ε—οΌŒ δ»Šε€©ηœ‹εˆ°5.10.2 都出ζ₯δΊ†

Is the v5 development still in progress?

@joa77 Yes, we haven't made any more progress yet. Do you have any idsa?

Hello there, we are merging antd v5 styles #3574 , please help us review together.

@ElderJames Great to see the progress and I will have a go.

Question for v5. Will there be themed css variables (:root), like accent color?
Currently, all colors are defined in its components css. In order to create custom components - it could be helpful to have something to rely on.

Hello @mkalinski93 Currently we have the css variables for antd v4, you can replace the css link with _content/AntDesign/css/ant-design-blazor.variable.css to achieve that. For v5, we would support css variables by CssInCSharp too.

Hey guys,

any news or progress about the v5?