Skip to content

Conversation

@joyenjoyer
Copy link
Contributor

@joyenjoyer joyenjoyer commented Nov 18, 2025

feat(ui-icons-lucide): Add Lucide Icons Package

Overview

This commit introduces a new @instructure/ui-icons-lucide package, providing 1,500+ modern icons from the Lucide icon library with full InstUI theming integration. This replaces the deprecated legacy icon system with a more flexible, maintainable solution built on a pure API.

What's Added

New Package: ui-icons-lucide

  • 1,500+ Lucide icons wrapped with InstUI theming support
  • wrapLucideIcon HOC for custom icon wrapping with InstUI features
  • Icon mapping (mapping.json) for migrating from legacy InstUI icons to Lucide
  • TypeScript support with full type definitions

Key Features

  1. Dual API Support

    • Semantic InstUI props: size="lg", color="successColor"
    • Native Lucide props: size={24}, color="#ff0000"
    • Props are composable and override each other intelligently
  2. InstUI Theming Integration

    • Full support for InstUI theme tokens (60+ semantic color options)
    • Semantic sizing: xs, sm, md, lg, xl, 2xl
    • Semantic stroke widths with theme-based values
  3. RTL Support

    • Automatic horizontal flipping for bidirectional icons in RTL contexts
    • Configurable via bidirectional prop (default: true)
  4. Rotation & Positioning

    • Support for 0°, 90°, 180°, 270° rotation
    • Inline or block display modes
    • Works correctly with RTL transformations
  5. Accessibility

    • Support for title and description props
    • Automatic role="img" and aria-label when title is provided
    • Falls back to aria-hidden="true" for decorative icons

Documentation Gallery Updates

  • Tabbed interface separating Lucide icons (new) from Legacy icons (deprecated)
  • Virtualized grid rendering using react-window for optimal performance with 1,500+ icons
  • Real-time search with debounced filtering
  • RTL preview toggle to test bidirectional behavior
  • Modal code examples showing usage for each icon
  • 4-column responsive layout with proper overflow handling

Migration Support

  • mapping.json provides direct mappings from legacy InstUI icons to Lucide equivalents
  • Enables automated migration via codemods (to be developed)
  • Clear deprecation warnings in Legacy Icons gallery

Technical Implementation

Architecture

  • Wrapper Pattern: Each Lucide icon is wrapped via wrapLucideIcon() HOC
  • Theme-Aware: Uses useTheme() to access InstUI theme tokens
  • Style Generation: Emotion-based styles with theme overrides support
  • Ref Forwarding: InstUI elementRef pattern for DOM access

Build System

  • generateIndex.ts script auto-generates exports for all 1,500+ icons
  • TypeScript project references for optimal build performance
  • Tree-shakeable exports for minimal bundle size

Testing Plan

Manual Testing

1. Icon Gallery Functionality

  • Navigate to the Icons page in the documentation
  • Verify both tabs are visible: "Lucide Icons (New)" and "Legacy Icons (Deprecated)"
  • Switch between tabs and confirm instant switching (galleries remain mounted)

2. Icon Usage Testing

import { Plus, ArrowLeft, Check, X } from '@instructure/ui-icons-lucide'

// Test semantic sizing
<Plus size="xs" />
<Plus size="sm" />
<Plus size="md" />
<Plus size="lg" />
<Plus size="xl" />
<Plus size="2xl" />

// Test numeric sizing
<Plus size={16} />
<Plus size={24} />
<Plus size={32} />

// Test semantic colors
<Check color="successColor" />
<X color="errorColor" />
<Plus color="actionPrimaryBaseColor" />

// Test custom colors
<Plus color="#ff0000" />
<Plus color="rgb(0, 255, 0)" />

// Test rotation
<ArrowLeft rotate="0" />
<ArrowLeft rotate="90" />
<ArrowLeft rotate="180" />
<ArrowLeft rotate="270" />

// Test RTL behavior
<div dir="rtl">
  <ArrowLeft bidirectional={true} /> {/* Should flip */}
  <ArrowLeft bidirectional={false} /> {/* Should not flip */}
</div>

// Test accessibility
<Plus title="Add item" description="Click to add a new item" />

// Test ref access
const iconRef = (el) => console.log(el)
<Plus elementRef={iconRef} />

Breaking Changes

None - this is an additive change. Legacy icons remain functional but deprecated.


This PR description was created with assistance from Claude AI

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

github-actions bot commented Nov 18, 2025

PR Preview Action v1.6.3

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

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

@joyenjoyer joyenjoyer force-pushed the add_lucide_v12 branch 2 times, most recently from 9f618d2 to f083632 Compare November 23, 2025 22:54

const CONFLICTING_NAMES = ['Infinity', 'Map']

function generateIndex() {
Copy link
Contributor Author

@joyenjoyer joyenjoyer Nov 23, 2025

Choose a reason for hiding this comment

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

If we want to update to a new Lucid version, this function has to be run to generate an updated list of icons in the index.tsx. I will add this info to the documentation (in another PR).

@@ -0,0 +1,114 @@
{
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 is an example mapping file between old and new icons. If Alex is ready with the mapping, this is going to be changed for that version.

@joyenjoyer joyenjoyer self-assigned this Nov 23, 2025
@joyenjoyer joyenjoyer marked this pull request as ready for review November 23, 2025 23:35
@@ -0,0 +1,49 @@
/*
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not needed because if you use the latest version of React-window it supplies its own types.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is still not needed

"moment": "^2.30.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-window": "^1.8.10",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please use the latest version

Copy link
Collaborator

Choose a reason for hiding this comment

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

this is still using an old version

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.

Nice work, overall I think it looks good :)
Can you also please write a list to the upgrade guide about which props are removed/changed compared to old icons?

Comment on lines 198 to 143
const accessibilityProps: Record<string, string | boolean> = {}
if (title) {
accessibilityProps['aria-label'] = description
? `${title}: ${description}`
: title
accessibilityProps['role'] = 'img'
} else {
accessibilityProps['aria-hidden'] = 'true'
accessibilityProps['role'] = 'presentation'
Copy link
Collaborator

Choose a reason for hiding this comment

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

As I see in the code the old InlineSVG added a <title> tag when the title prop was specified; description added a <description> that and connected these to the svg with aria-labelledby. Why is this here differently?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We discussed that title stays, description will be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

HTML props are spread over to the underlying <svg> -> add this to ugprade guide.

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

Comment on lines 30 to 33
color?: string
rotate?: '0' | '90' | '180' | '270'
rotate?: LucideIconWrapperProps['rotate']
bidirectional?: boolean
inline?: boolean
Copy link
Collaborator

Choose a reason for hiding this comment

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

themse need the same prop ref too, so if we change the prop, this one changes too

@@ -0,0 +1,49 @@
/*
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is still not needed

"moment": "^2.30.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-window": "^1.8.10",
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is still using an old version


// Empty object constant for cellProps to maintain referential equality
// and prevent unnecessary re-renders of all cells
const EMPTY_CELL_PROPS = {}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If an empty object without a reference is assigned to cellProps on Grid it keeps rerendering, this trick prevents it.


// Empty object constant for cellProps to maintain referential equality
// and prevent unnecessary re-renders of all cells
const EMPTY_CELL_PROPS = {}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If an empty object without a reference is assigned to cellProps on Grid it keeps rerendering, this trick prevents it.

@joyenjoyer joyenjoyer requested a review from matyasf November 25, 2025 16:42
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 now

@joyenjoyer joyenjoyer merged commit c1aba7a into v12 Nov 26, 2025
6 checks passed
@joyenjoyer joyenjoyer deleted the add_lucide_v12 branch November 26, 2025 10:27
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