Nexus-Mods / NexusMods.App

Home of the development of the Nexus Mods App
https://nexus-mods.github.io/NexusMods.App/
GNU General Public License v3.0
865 stars 43 forks source link

DRAFT: Create a Button component that matches Figma #1876

Open insomnious opened 1 month ago

insomnious commented 1 month ago

Spike

Proposal

In Figma, Design has now a matrix of button variations that include numerous styles, types, fills, sizes, icons etc. Designs for other parts of the app already use or will be using these buttons and there is a sort of undocumented conversion that is happening between what is in Figma and what is currently in the app whenever UI is being built.

image

In #1833 was a quick stop gap to update the appearance of the buttons to match Figma, but not the structure or the long term benefit of doing this 'properly'. This was done by changing some of the original styles in the App without worrying about the naming and everything else to match Figma.

This is basically adding to the tech debt and we are better off staying inline with Design as quickly as we can without making things more hacky.

I'd like to propose that we build a Button component (we need a name, StandardButton?) that will that will account for all of the button variations that are in Figma through a mix of properties and classes.

We could do this all with classes and we could also do it all with properties but I think there is a natural separation between what uses classes (more style related things like size, fill, type) and properties (show label, what icons are visible, what icon for what position etc.)

Proof of concept

I worked backwards from what feels right when it comes to using a component, such as dropping <NewButton> into a axaml and without adding anything, it should look like the default button in Figma with a single line of code. We can then add properties and classes to get the necessary variation.

Please note: I've not matched paddings\margins\spacing etc to Figma.

Properties

code example
<controls:NewButton /> image
<controls:NewButton Text="Lovely" /> image
<controls:NewButton Text="Right Icon" ShowIcon="Right" /> image
<controls:NewButton Text="Left Icon" ShowIcon="Left" LeftIcon="{x:Static icons:IconValues.Steam}" /> image
<controls:NewButton Text="Both Icons" ShowIcon="Both" /> image
<controls:NewButton ShowIcon="Left" LeftIcon="{x:Static icons:IconValues.Visibility}" ShowLabel="False" /> image

Classes

Classes should be used for the more obvious styles, such as colors, sizes, types etc.

We default to type primary, fill none, size md

code example
<controls:NewButton Classes="secondary" /> image
<controls:NewButton Classes="secondary strong" /> image
<controls:NewButton Classes="secondary strong sm" ShowIcon="Right" /> image
<controls:NewButton Classes="tertiary" /> image
<controls:NewButton Classes="tertiary" ShowIcon="Right" /> image

How was it done

This proof of concept was completed by creating a new TemplatedControl (NewButton.axaml) with custom properties and default values in the code-behind. A new control theme was then created for this control (NewButtonControlTheme.axaml) which contains the actual change in template (see snippet below) as well as all the default styles that use our existing resources. This could then be overidden further using styles if we ever needed to.

<ControlTemplate>
    <Button>
        <panels:FlexPanel Direction="Row" AlignContent="Center">
            <icons:UnifiedIcon Name="PART_LeftIcon" />
            <TextBlock Text="{TemplateBinding Text}" Name="PART_Label" />
            <icons:UnifiedIcon Name="PART_RightIcon" />
        </panels:FlexPanel>
    </Button>
</ControlTemplate>

What to do

Impact

Supporting Information

Figma

Al12rs commented 1 month ago

Some notes for the meeting:

insomnious commented 1 month ago

Task list following meeting:

insomnious commented 4 weeks ago

Completed some research and expanded proof of concept based on chat with Al

Example code

This is swapping original Button code with NewButton to make sure implementation works as expected.

<controls:NewButton Grid.Column="0" x:Name="ViewGameButton"
    VisibleIcon="Left" 
    LeftIcon="{x:Static icons:IconValues.Done}"
    Type="Tertiary" 
    Fill="Weak"  
    Text="{x:Static resources:Language.GameWidget__View}"
    HorizontalAlignment="Stretch"
    />

<controls:NewButton Grid.Column="2" x:Name="RemoveGameButton"
    VisibleIcon="Left" 
    LeftIcon="{x:Static icons:IconValues.DeleteForever}"
    Type="Tertiary" 
    Fill="Weak" 
    ShowLabel="False"
    />

image

<controls:NewButton Type="Secondary" Fill="Strong" Text="Play" VisibleIcon="Left" LeftIcon="{x:Static icons:IconValues.Done}" />

image

Original Button content

As per Al's request, we can leverage the typical use of Button and it's ContentPresentation. Currently if we have Content, it renders that instead of based on what added properties we have.

<controls:NewButton>
  <StackPanel>
    <TextBlock>Test 1</TextBlock>
        <StackPanel Orientation="Horizontal" Background="Chocolate">
            <icons:UnifiedIcon Value="{x:Static icons:IconValues.Done}"></icons:UnifiedIcon>
            <icons:UnifiedIcon Value="{x:Static icons:IconValues.Done}"></icons:UnifiedIcon>
            <icons:UnifiedIcon Value="{x:Static icons:IconValues.Done}"></icons:UnifiedIcon>
        </StackPanel>
        <TextBlock Background="DarkRed">Test 2</TextBlock>
    <TextBlock>Test 3</TextBlock>
  </StackPanel>
</controls:NewButton>

image

Full reference (ish)

Screenshot of working DesignView to show and test the styles and properties (NewButtonControlTheme.axaml)

image

Hover and pressed states

https://github.com/user-attachments/assets/1683e78c-b1e1-430b-951d-d999035dfe78

Some disabled states

image

Al12rs commented 3 weeks ago

@insomnious For when you get back, we apparently don't need the classes or pseudo classes, we can actually use Property states as style selectors: https://docs.avaloniaui.net/docs/reference/styles/style-selector-syntax#by-property-match <Style Selector="Button[IsDefault=true]">