mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.48k stars 32.16k forks source link

[RFC] Restructure icons exports #42704

Open o-alexandrov opened 3 months ago

o-alexandrov commented 3 months ago

What's the problem?

This RFC looks at 2 problems with icons:

  1. for MUI's users, there's no out-of-the-box option to enforce icons' variant: Sharp, Rounded, Outlined, TwoTone, Filled
  2. too many icons; it affects performance (see related issues)

What are the requirements?

MUI's user should be able to enforce icons' variants and the number of icons shouldn't affect DX.

Proposed solution

We can tackle both problems by allowing MUI's users to have more consistent interfaces by enforcing icons' variants using ESLint's no-restricted-imports.

File structure for Material Icons:

File structure for Material Symbols:

|-- index.ts // exports everything as it is now to support the current usage
|-- /variants
   |-- /sharp // exports only Sharp variant, available as "@mui/icons-material/sharp"
      |-- /100 // exports only 100 weight, available as "@mui/icons-material/sharp/100"
         |-- /neg25 // exports only -25 grade, available as "@mui/icons-material/sharp/100/neg25"
         |-- /0 // exports only 0 grade, available as "@mui/icons-material/sharp/100/0"
         |-- /200 // exports only 200 grade, available as "@mui/icons-material/sharp/100/200"
      |-- ...other weights in separate directories 200, 300, 400, 500, 600, 700
   |-- /rounded // exports only Rounded variant, available as "@mui/icons-material/rounded"
      |-- /100 // exports only 100 weight, available as "@mui/icons-material/rounded/100"
         |-- /neg25 // exports only -25 grade, available as "@mui/icons-material/rounded/100/neg25"
         |-- /0 // exports only 0 grade, available as "@mui/icons-material/rounded/100/0"
         |-- /200 // exports only 200 grade, available as "@mui/icons-material/rounded/100/200"
      |-- ...other weights in separate directories 200, 300, 400, 500, 600, 700
   |-- /outlined // exports only Outlined variant, available as "@mui/icons-material/outlined"
      |-- /100 // exports only 100 weight, available as "@mui/icons-material/outlined/100"
         |-- /neg25 // exports only -25 grade, available as "@mui/icons-material/outlined/100/neg25"
         |-- /0 // exports only 0 grade, available as "@mui/icons-material/outlined/100/0"
         |-- /200 // exports only 200 grade, available as "@mui/icons-material/outlined/100/200"
      |-- ...other weights in separate directories 200, 300, 400, 500, 600, 700

Related issues

Search keywords: material icons, symbols, icons v2

TheOneTheOnlyJJ commented 3 months ago

Not long ago I made a suggestion that would exapnd the icons' package features and improve DX when requiring specific icons at runtime (eg. based on user input).

See the issue at #42450.

Now, thinking of it in the context of performance, splitting the icons into separate packages based on style could allow tree shakers to reduce the bundled size significantly if icons of one particular style are used. A separate import could be used for when all the icons of all styles are required.

My issue was requesting the ability to get the icons at runtime based on their style(s) and tag(s)/synonym(s) (see why I need that feature in mui/mui-x#13206). It's a shame it got closed just because there's already a hacky way to import icons at runtime in userland. The current way of doing it is also not optimised for a particular style. I'm glad this RFC has been opened, so we can have a proper discussion on the topic.

Given the very likely adoption of Material Symbols (#32846) in MUI v7, I believe an expansion of the icons' package API (like my suggestion or similar) should be considered by the team. A well thought-out API expansion in this area could bring both extended functionality and improved performance, thus covering extra use-cases (like mine from mui/mui-x#13206) and improving performance, as mentioned here.

TheOneTheOnlyJJ commented 3 months ago

I'll copy & paste the proposed API changes I made in the issues mentioned in the above reply so that they can be discussed further right here.

I would like to add an explicit example of how the utility function from the icons package I was talking about could look like:

import { getIcons } from '@mui/icons-material';
import SvgIcon from '@mui/material/SvgIcon';

// Get all icons in all styles
let allIcons: Array<SvgIcon> = getIcons();

// Get all outlined icons
let allOutlinedIcons: Array<SvgIcon> = getIcons({ styles: [ 'outlined' ] });

// Get only filled and outlined icons
let filledAndOutlinedIcons: Array<SvgIcon> = getIcons({ styles: [ 'filled', 'outlined' ] });

// Get only filled icons with the "home" tag
// Should only return these icons: https://mui.com/material-ui/material-icons/?query=home
let filledHomeIcons: Array<SvgIcon> = getIcons({ styles: [ 'filled' ], tags: { anyOf: [ 'home' ] } });

// Get all icons with the "home" tag, in all styles
let allHomeIcons: Array<SvgIcon> = getIcons({ tags: { anyOf: [ 'home' ] } });

// Get all icons that have either the "home" or the "building" tags, in all styles
let allHomeOrBuildingIcons: Array<SvgIcon> = getIcons({ tags: { anyOf: [ 'home', 'building' ] } });

// Get all icons that have both the "home" and "building" tags, in the sharp style only
let allSharpHomeAndBuildingIcons: Array<SvgIcon> = getIcons({ styles: [ 'sharp' ], tags: { allOf: [ 'home', 'building' ] } });

// Do not support allOf and anyOf at the same time
let willThrowException = getIcons({ tags: { anyOf: [ 'home' ], allOf: [ 'building' ] } });

The styles argument is optional and, if missing, every style will be included in the output. It is a string array that can have any of the following options filled, outlined, rounded, two tone and sharp. These could additionally also be exported as enums to be used in code conveniently.

The tags argument is also optional, and its filtering will apply together with the style filter. It is an object that must have either the anyOf or the allOf keys that map to string arrays. They should not be both supported at the same time, resulting in an exception being thrown. Using the argument with anyOf option will return all the icons that have at least one of the tags in the list. Using it with the allOf option will return only the icons that have all the tags from the given tag list.

With this kind of API, the Icon Picker component can pass its state as arguments to the getIcons function and fulfil my requirement. When the user would change the style option(s) or change the search word, the state would update and call the function again, and the Icon Picker would rerender to only display the new icons returned by getIcons. Having both the anyOf and the allOf tag selectors will enable this component to potentially use Chip-based sorting as well as the already discussed Radio Button/Tree View variants.

Additionally, a function that allows getting all tags for a specific icon (maybe call it getIconTags) should also be available for flexibility (to show the user the keywords/tags a specific icon can be found by), but this would require an internal ID for every icon to be passed as an argument to that function. I am not that accustomed to the internals of the icons package, so I do not know how difficult such an addition would be.

If this icons-related reply is out of topic here, feel free to move (or copy) this issue to the corresponding repository (material-ui).

Of course, this proposed API should be expanded taking into account the separation of the icons' packages based on style, to reduce the final build size through tree shaking when not all styles are required.