Skip to content

Conversation

@joyenjoyer
Copy link
Contributor

@joyenjoyer joyenjoyer commented Nov 26, 2025

Closes INSTUI-4864

Test:
Try all these icon adding options with Avatar and check whether size and color passed to the icon correctly. Also check how changing theme impact the rendered component.

renderIcon={AlarmClockInstUIIcon} // reference to icon
renderIcon={<AlarmClockInstUIIcon />} // icon as React component
renderIcon={()=><AlarmClockInstUIIcon />} // icon as React component returned by an arrow function 

@joyenjoyer joyenjoyer changed the base branch from master to v12 November 26, 2025 14:54
@github-actions
Copy link

github-actions bot commented Nov 26, 2025

PR Preview Action v1.6.3

🚀 View preview at
https://instructure.design/pr-preview/pr-2266/

Built to branch gh-pages at 2025-12-11 14:10 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@joyenjoyer joyenjoyer force-pushed the add_lucide_icons_to_avatar branch 2 times, most recently from 8b5e111 to 1c11f12 Compare December 4, 2025 10:02
@joyenjoyer joyenjoyer marked this pull request as ready for review December 4, 2025 10:04
@joyenjoyer joyenjoyer force-pushed the add_lucide_icons_to_avatar branch from 1c11f12 to f75d05f Compare December 4, 2025 12:37
<Avatar size="large" color="ai" name="AI Assistant" renderIcon={IconAiSolid} />
<Avatar size="x-large" color="ai" name="AI Assistant" renderIcon={IconAiSolid} />
<Avatar size="xx-large" color="ai" name="AI Assistant" renderIcon={IconAiSolid} />
<Avatar size="xx-small" color="ai" name="AI Assistant" renderIcon={IconAiSolid} />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has to be replaced later to the appropriate Lucide equivalent icon. (Alex is still working on the mapping)

@joyenjoyer joyenjoyer force-pushed the add_lucide_icons_to_avatar branch from f75d05f to 2bc3b92 Compare December 4, 2025 12:49
Copy link
Collaborator

@matyasf matyasf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my comments

@joyenjoyer joyenjoyer requested a review from matyasf December 5, 2025 09:24
@joyenjoyer
Copy link
Contributor Author

@matyasf I answered the comments and did changes where they were needed, please check the PR again.

@joyenjoyer joyenjoyer force-pushed the add_lucide_icons_to_avatar branch 2 times, most recently from e7c9af6 to 52e6819 Compare December 9, 2025 22:08
color: iconColor
})
return (
<IconPropsProvider size={iconSize} color={iconColor}>
Copy link
Contributor Author

@joyenjoyer joyenjoyer Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed renderLucideIconWithProps since it was too complex to understand and added IconPropsProvider instead. Lucide icons consume size and color through it.

This works with all the wanted patterns:

renderIcon={AlarmClockInstUIIcon}
renderIcon={<AlarmClockInstUIIcon />}
renderIcon={()=><AlarmClockInstUIIcon />}

* When using Lucide icons, Avatar will automatically pass the appropriate size and color props based on the Avatar's size and color.
*/
renderIcon?: Renderable<{ size?: string | number; color?: string }>
renderIcon?: Renderable
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed unnecessarily complex typing

const contextProps = useIconProps()

// Merge props: direct props take precedence over context props
const finalSize = size ?? contextProps?.size
Copy link
Contributor Author

@joyenjoyer joyenjoyer Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Followed the React best practice here, component props always override context values by default.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general I would agree, but this code design explicitly wants to force props if applicable. For example we don't want to have misconfigured Avatars with incorrects sizes or colors. I'd default to context here and if the need arises, we could have a prop like "forceSize" or something later

*/

import React from 'react'
import type { IconPropsContextValue } from './IconPropsProvider'
Copy link
Contributor Author

@joyenjoyer joyenjoyer Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I followed the same file structuring I saw with DeterministicIdProvider

@joyenjoyer joyenjoyer force-pushed the add_lucide_icons_to_avatar branch from 52e6819 to 8aae67a Compare December 9, 2025 22:26
| 'inverseColor'
| 'disabledBaseColor'
| 'disabledOnColor'
| 'dark'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Designers confirmed that dark is a valid color, they will use it in the future.

@joyenjoyer joyenjoyer force-pushed the add_lucide_icons_to_avatar branch from 8aae67a to 543b72a Compare December 9, 2025 22:30
import { IconPropsProvider, useIconProps } from '../IconPropsProvider'

// Test component that exposes context values
const TestComponentWithHook = () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mocks are perfectly valid here, since these are unit tests.

Copy link
Collaborator

@matyasf matyasf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks much better now! see my comments

/**
* Icon theme color token or CSS color value
*/
color?: 'inherit' | IconColorToken | string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old icons use other values ('inherit', 'primary', 'secondary',... How do we handle the migration?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add these to the props too, ask for a mapping from design, and do it in the code in index.ts/styles.ts

Comment on lines 296 to 297
<code>color</code>: CSS color - e.g.,{' '}
<code>&quot;currentColor&quot;</code>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currentColor is not valid AFAIK, a better example would be successColor

<code>&quot;currentColor&quot;</code>
</li>
<li>
<code>strokeWidth</code>: Number - e.g., <code>2</code>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a better example is sm

</Heading>
<ul>
<li>
<code>size</code>: Number (pixels) - e.g., <code>24</code>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here a semantic example would be better

> &
InstUIIconOwnProps & {
themeOverride?: ThemeOverrideValue
} & OtherHTMLAttributes<InstUIIconOwnProps>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you could omit the props removed by passtroughProps, I think the syntax is `Omit<OtherHTMLAttributes, 'children', ...>

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g.

type LucideIconWrapperProps = Omit<LucideProps, 'size' | 'color' | 'strokeWidth' | 'rotate' | 'children' | 'style' | others> &
  InstUIIconOwnProps & {
    themeOverride?: ThemeOverrideValue
  }

"@babel/runtime": "^7.27.6",
"@instructure/emotion": "workspace:*",
"@instructure/shared-types": "workspace:*",
"@instructure/ui-icons": "workspace:*",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is ui-icons still needed?

Comment on lines +28 to +29
size?: string | number
color?: string
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These have the same type as the props with the same name from wrapLucideIcon/props.ts I think. But in this case we will have to pull in the icons package as a dependency, which might create an impossible build graph... So it might be better to move these where the icons are, we will use them with icons anyway

@joyenjoyer joyenjoyer force-pushed the add_lucide_icons_to_avatar branch from 27b73ca to 7e5aaeb Compare December 11, 2025 14:03
Copy link
Collaborator

@matyasf matyasf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!
I've found just one small visual bug: In the themeOverride prop example the examples are not aligned vertically.

Image

According to AI its because "The children elements all have display: inline-flex and vertical-align: baseline. While they all have the same height (48px), vertical-align: baseline aligns the baselines of the elements. If there is text content or different line-height values within the inline-flex items, or if the parent div has a specific line-height or font-size that interacts with the baseline, it can cause vertical misalignment"

and the fix is pretty simple, just add verticalAlign:'middle to the avatar CSS class

Copy link
Contributor

@HerrTopi HerrTopi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comments. After fixing them, I'd go through it again to check new logic.

I think for the big changes, design needs to be brought in to discuss

const contextProps = useIconProps()

// Merge props: direct props take precedence over context props
const finalSize = size ?? contextProps?.size
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general I would agree, but this code design explicitly wants to force props if applicable. For example we don't want to have misconfigured Avatars with incorrects sizes or colors. I'd default to context here and if the need arises, we could have a prop like "forceSize" or something later

}

const styles = useStyle({
componentId: 'Icon' as const,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpic but this const doesn't do anything here, remove it please so it wont clutter the code

params: {
size: semanticSize as InstUIIconOwnProps['size'],
color: colorValue,
size: finalSize as LucideIconWrapperProps['size'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cast isnt't needed

color: colorValue,
size: finalSize as LucideIconWrapperProps['size'],
strokeWidth,
color: finalColor,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason behind displayName: LucideIcon(${Icon.displayName || Icon.name}) ` aremt't we sure that displayName (or name) will be available?

Line 83. For some reason I can't comment there

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 86: const accessibilityProps: Record<string, string | boolean> = {}

| boolean is not needed

)
}

WrappedIcon.displayName = `wrapLucideIcon(${Icon.displayName || Icon.name})`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this displayname different from the one in line 83? It should be the same for consistency and theme override's sake

return WrappedIcon
}

export type { LucideIconWrapperProps, InstUIIconOwnProps }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this part. Why re-export these from here? These are already exported from their respected files.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for export { default as generateStyle } from './styles'

| 'actionDestructiveSecondaryActiveColor'

type InstUIIconOwnProps = {
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I can't comment on unchanged parts, but:
StorkeWidth is a not allowed prop, all stroke width have to be calculated from size and are not changable.
Size van not be number, only the preset tokens.
Same for color. No inherit, no string, only the presets
Ask about these from desing

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calculations and logic are tied to these things in styles.ts, so I won'r review it in depth because it needs to be drastically changed before.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants