jmix icon indicating copy to clipboard operation
jmix copied to clipboard

Ability to easily override default used icons

Open glebfox opened this issue 6 months ago • 2 comments

Currently, actions and some UI components hard code used icons, e.g.:

@Override
protected void initAction() {
    ...
    this.icon = ComponentUtils.convertToIcon(VaadinIcon.PENCIL);
}

In order to override used icons, you must to override every action just to change a single line of code. It'd be helpful to have application property class that stored all defaults, same as we have for shortcuts.


Upd: application properties are not applicable for the KIT module. Need to think something better. Upd: FontIcon looks promising

glebfox avatar Sep 04 '25 15:09 glebfox

Solution

New Icon enum

JmixFontIcon - represents a collection of font icons used within the application. Contains all Vaadin icons, Lumo icons (missing in Vaadin icons) and Jmix specific icons.

Each JmixFontIcon constant represents a FontIcon component with the specified icon class names.

For example:

  • JmixFontIcon#CHECK creates FontIcon with iconClassNames: jmix-font-icon and jmix-font-icon-check. Result HTML element: <vaadin-icon icon-class="jmix-font-icon jmix-font-icon-ok" class="jmix-font-icon jmix-font-icon-ok" icon=""></vaadin-icon>

New bean

The new Icons bean introduced - central interface to provide UI components representing icons.

Icons#get(java.lang.String) - returns a UI Component representing an icon specified by its name. This method attempts to create a Component for the given icon name. If the icon name contains : delimiter then a new Icon is created using icon collection and icon name values. Otherwise, it will attempt to locate and create the icon using predefined icon sets.

Examples:

  • icons.get("CHECK") - searches for JmixFontIcon constant with the same name and creates a corresponding icon. In this case: JmixFontIcon#create creates a FontIcon with iconClassNames: jmix-font-icon and jmix-font-icon-check.
  • icons.get("vaadin:check") - creates an com.vaadin.flow.component.icon.Icon using icon collection and icon name values. In this case: new Icon("vaadin", "check")

XML - the icon attribute handling

The value of the icons attribute is parsed by the Icons bean.

How to override

1. Override styles

Almost each JmixFontIcon is declared with two CSS classes and one CSS variable.

  • .jmix-font-icon - common for all icons and defines the default font-family - "Vaadin-Icons" (stored in the --jmix-font-icon-font-family variable)
  • .jmix-font-icon-<icon-name> - sets the content of the :before pseudo-element equal to corresponding CSS variable.
  • --jmix-font-icon-<icon-name> - stores the icon value

For example:

html {
    --jmix-font-icon-user: "user";
}

.jmix-font-icon {
    font-family: var(--jmix-font-icon-font-family);
}

.jmix-font-icon-user:before {
    content: var(--jmix-font-icon-user);
}

Currently, there are three lumo icons that additionally declares .jmix-font-icon.jmix-font-icon-lumo CSS class which changes font-family value:

.jmix-font-icon.jmix-font-icon-lumo {
    font-family: lumo-icons;
}

There are three ways to override styles:

  1. Override a single icon with an icon from the same icon font. For example, for the edit action:
html {
    --jmix-font-icon-edit-action: var(--jmix-font-icon-edit);
}
  1. Override a single icon with an icon from a different icon font:
.jmix-font-icon-refresh {
    font-family: "lumo-icons";
}

.jmix-font-icon-refresh:before {
    content: var(--lumo-icons-reload);
}
  1. Override icon font entirely:
html {
    --jmix-font-icon-font-family: "Font Awesome 7 Free"

    --jmix-font-icon-create-action: "\+";
    --jmix-font-icon-edit-action: "\f303";
    ...
    --jmix-font-icon-user: "\f007";
}

2. Override the Icons bean

In case the desired icon set doesn't provide the a font version or you strongly want to use SVG icons, you need to override the Icons bean and provide a custom mapping for the icon names.

For example:

@Primary
@org.springframework.stereotype.Component("demo_AppIcons")
public class AppIcons extends IconsImpl {

    @Override
    protected Component createIconByName(String iconName) {
        return switch (iconName) {
            case "USER" -> FontawesomeSolidIcon.USER.create();
            case "CALENDAR_DAYS" -> FontawesomeSolidIcon.CALENDAR_DAYS.create();
            case "SUN_O" -> FontawesomeSolidIcon.SUN.create();
            default -> super.createIconByName(iconName);
        };
    }
}

Breaking Changes

As mentioned above, the icon attribute is parsed by the Icons bean which means that values without collection name is converted to FontIcon component instead of Icon. This may lead to compilation errors or class cast exceptions in case Icon is used in code.

For example:

  • icon="CHECK" - converted to FontIcon
  • icon="vaadin:check" - converted to Icon

Because of that deprecated methods that works with Icon won't return a value since FontIcon isn't Icon. For example:

Having the following action definition:

<action id="iconAttributeAction" text="Action" icon="CHECK"/>

Before:

icon="CHECK" is converted to new Icon("vaadin", "check"), as a result the deprecated Action#getIcon() method that returns Icon can return a value.

Now:

icon="CHECK" is converted to FontIcon, as a result Action#getIcon() returns null and only Action#getIconComponent() returns the actual value.

glebfox avatar Nov 25 '25 12:11 glebfox

For QA: wait for https://github.com/jmix-framework/jmix/issues/4910

glebfox avatar Nov 25 '25 13:11 glebfox