Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/__docs__/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@
"moment": "^2.30.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-window": "^2.2.3",
"semver": "^7.7.2",
"uuid": "^11.1.0",
"webpack-merge": "^6.0.1"
Expand Down
141 changes: 27 additions & 114 deletions packages/__docs__/src/Icons/IconsGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@
* SOFTWARE.
*/

import { useState, memo, useCallback, useMemo, useRef, useEffect } from 'react'
import { useState, memo, useCallback, useMemo, useRef } from 'react'
import type { ChangeEvent } from 'react'
import { Grid } from 'react-window'

import { Heading } from '@instructure/ui-heading'
import { TextInput } from '@instructure/ui-text-input'
Expand All @@ -41,12 +40,9 @@ import { LucideIcons, CustomIcons } from '@instructure/ui-icons'
import { XInstUIIcon } from '@instructure/ui-icons'
import { Flex } from '@instructure/ui-flex'

type IconSource = 'lucide' | 'custom'

type IconInfo = {
name: string
component: React.ComponentType<any>
source: IconSource
importPath: string
}

Expand All @@ -60,13 +56,11 @@ const allIcons: IconInfo[] = [
...Object.entries(LucideIcons).map(([name, component]) => ({
name,
component: component as React.ComponentType<any>,
source: 'lucide' as const,
importPath: '@instructure/ui-icons'
})),
...Object.entries(CustomIcons).map(([name, component]) => ({
name,
component: component as React.ComponentType<any>,
source: 'custom' as const,
importPath: '@instructure/ui-icons'
}))
]
Expand Down Expand Up @@ -95,10 +89,9 @@ const IconTile = memo(
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
width: '100%',
padding: '0.5rem',
boxSizing: 'border-box',
overflow: 'hidden'
minWidth: '14em',
flexBasis: '14em',
flexGrow: 1,
}}
>
<div
Expand Down Expand Up @@ -162,43 +155,28 @@ const IconTile = memo(
)
IconTile.displayName = 'IconTile'

const TILE_HEIGHT = 180

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

// Helper function to determine column count based on window width
function getColumnCountForWidth(width: number): number {
if (width < 600) return 1
if (width < 900) return 2
return 3
}

const IconsGallery = () => {
const [searchQuery, setSearchQuery] = useState<string>('')
const [searchInput, setSearchInput] = useState<string>('')
const [selectedIcon, setSelectedIcon] = useState<IconInfo | null>(null)
const [rtl, setRtl] = useState<boolean>(false)
const [containerWidth, setContainerWidth] = useState<number>(() =>
typeof window !== 'undefined' ? window.innerWidth : 900
)
const searchTimeoutId = useRef<NodeJS.Timeout | null>(null)
const resizeTimeoutId = useRef<NodeJS.Timeout | null>(null)
const containerRef = useRef<HTMLDivElement>(null)

// Debounced search
const handleSearchChange = useCallback((_e: ChangeEvent, value: string) => {
setSearchInput(value)
const handleSearchChange = (_e: ChangeEvent, value: string) => {
// Instant update when extending query (typing adds characters)
if (value.startsWith(searchQuery)) {
setSearchQuery(value)
return
}

// Debounce when deleting (reveals more icons = heavier re-render)
if (searchTimeoutId.current) {
clearTimeout(searchTimeoutId.current)
}

searchTimeoutId.current = setTimeout(() => {
setSearchQuery(value)
}, 300)
}, [])
}, 500)
}

const handleBidirectionToggle = useCallback((e: ChangeEvent<any>) => {
setRtl(e.target.checked)
Expand All @@ -214,78 +192,19 @@ const IconsGallery = () => {

const filteredIcons = useMemo(() => {
if (!searchQuery) return allIcons
return allIcons.filter((icon) =>
icon.name.toLowerCase().includes(searchQuery.toLowerCase())
)
const query = searchQuery.toLowerCase()
return allIcons.filter((icon) => icon.name.toLowerCase().includes(query))
}, [searchQuery])

// Update container width on resize with debouncing
useEffect(() => {
const updateWidth = () => {
if (containerRef.current) {
setContainerWidth(containerRef.current.offsetWidth)
} else {
setContainerWidth(window.innerWidth)
}
}

const handleResize = () => {
if (resizeTimeoutId.current) {
clearTimeout(resizeTimeoutId.current)
}

resizeTimeoutId.current = setTimeout(() => {
updateWidth()
}, 150)
}

// Initial measurement
updateWidth()

window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
if (resizeTimeoutId.current) {
clearTimeout(resizeTimeoutId.current)
}
}
}, [])

// Calculate column count and tile width based on container width
const columnCount = getColumnCountForWidth(containerWidth)
const tileWidth = Math.floor(containerWidth / columnCount)
const gridWidth = tileWidth * columnCount
const rowCount = Math.ceil(filteredIcons.length / columnCount)
const gridHeight = rowCount * TILE_HEIGHT

// Memoized cell renderer to prevent unnecessary re-renders
const CellRenderer = useCallback(
({ columnIndex, rowIndex, style }: any) => {
const index = rowIndex * columnCount + columnIndex
if (index >= filteredIcons.length) {
return <div style={style} />
}

const icon = filteredIcons[index]
return (
<div style={{ ...style, overflow: 'hidden' }}>
<IconTile icon={icon} rtl={rtl} onClick={handleIconClick} />
</div>
)
},
[columnCount, filteredIcons, rtl, handleIconClick]
)

return (
<div css={{ overflowX: 'hidden' }}>
<div>
<FormFieldGroup
layout="columns"
colSpacing="small"
description={<ScreenReaderContent>Filter Icons</ScreenReaderContent>}
>
<TextInput
placeholder="Filter icons..."
value={searchInput}
onChange={handleSearchChange}
renderLabel={<ScreenReaderContent>Icon Name</ScreenReaderContent>}
/>
Expand All @@ -302,28 +221,22 @@ const IconsGallery = () => {
</FormFieldGroup>

<div
ref={containerRef}
css={{
display: 'flex',
flexWrap: 'wrap',
margin: '0 auto',
paddingTop: '1rem',
width: '100%',
maxWidth: '1200px'
gap: '1rem',
}}
>
<Grid
key={`grid-${columnCount}`}
cellComponent={CellRenderer}
cellProps={EMPTY_CELL_PROPS}
columnCount={columnCount}
columnWidth={tileWidth}
rowCount={rowCount}
rowHeight={TILE_HEIGHT}
style={{
height: `${gridHeight}px`,
width: `${gridWidth}px`,
overflowX: 'hidden'
}}
/>
{filteredIcons.map((icon) => (
<IconTile
key={icon.name}
icon={icon}
rtl={rtl}
onClick={handleIconClick}
/>
))}
</div>

{selectedIcon && (
Expand Down
2 changes: 1 addition & 1 deletion packages/__docs__/src/Icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const IconsGallery = lazy(() => import('./IconsGallery'))

const IconsPage = () => {
return (
<View as="div" padding="medium">
<View as="div">
<Suspense
fallback={
<div
Expand Down
55 changes: 12 additions & 43 deletions packages/__docs__/src/LegacyIcons/LegacyIconsGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

import { useState, useRef, memo, useCallback, useMemo } from 'react'
import type { ChangeEvent, SyntheticEvent } from 'react'
import { Grid } from 'react-window'

import { InlineSVG } from '@instructure/ui-svg-images'
import { Heading } from '@instructure/ui-heading'
Expand Down Expand Up @@ -178,14 +177,6 @@ const LegacyIconTile = memo(
)
LegacyIconTile.displayName = 'LegacyIconTile'

const TILE_WIDTH = 240
const TILE_HEIGHT = 180
const COLUMN_COUNT = 4
const GRID_WIDTH = TILE_WIDTH * COLUMN_COUNT

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

const LegacyIconsGallery = ({ glyphs }: LegacyIconsGalleryProps) => {
const [selectedFormat, setSelectedFormat] = useState<Format>('react')
Expand Down Expand Up @@ -243,8 +234,6 @@ const LegacyIconsGallery = ({ glyphs }: LegacyIconsGalleryProps) => {
)
}, [glyphs, searchQuery])

const rowCount = Math.ceil(filteredGlyphs.length / COLUMN_COUNT)

return (
<div css={{ overflowX: 'hidden' }}>
<FormFieldGroup
Expand Down Expand Up @@ -297,42 +286,22 @@ const LegacyIconsGallery = ({ glyphs }: LegacyIconsGalleryProps) => {

<div
css={{
display: 'flex',
flexWrap: 'wrap',
margin: '0 auto',
paddingTop: '1rem',
width: '100%',
maxWidth: `${GRID_WIDTH}px`
gap: '1rem',
}}
>
<Grid
cellComponent={({ columnIndex, rowIndex, style }) => {
const index = rowIndex * COLUMN_COUNT + columnIndex
if (index >= filteredGlyphs.length) {
return <div style={style} />
}

const glyph = filteredGlyphs[index]
return (
<div style={style}>
<LegacyIconTile
glyph={glyph}
format={selectedFormat}
rtl={rtl}
onClick={handleIconClick}
/>
</div>
)
}}
cellProps={EMPTY_CELL_PROPS}
columnCount={COLUMN_COUNT}
columnWidth={TILE_WIDTH}
rowCount={rowCount}
rowHeight={TILE_HEIGHT}
style={{
height: '600px',
width: `${GRID_WIDTH}px`,
overflowX: 'hidden'
}}
/>
{filteredGlyphs.map((glyph) => (
<LegacyIconTile
key={glyph.glyphName}
glyph={glyph}
format={selectedFormat}
rtl={rtl}
onClick={handleIconClick}
/>
))}
</div>
{selectedGlyph && (
<Modal
Expand Down
37 changes: 0 additions & 37 deletions packages/ui-icons/icons.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,42 +45,6 @@ const react = {
componentBaseName: 'Icon'
}

const deprecated = {
/* [icon name]: [icon name to use instead]
/* e.g. 'arrow-up': 'arrow' */
'discussion-x': 'x',
'copy-course': 'copy',
'discussion-reply-dark': 'more',
'discussion-reply': 'more',
'discussion-search': 'search',
'search-address-book': 'search',
'rss-add': 'add',
'user-add': 'add',
'materials-required-light': 'materials-required',
'mature-light': 'mature',
'note-dark': 'note',
'note-light': 'note',
'icon-reply-2': 'icon-reply',
'icon-replied': 'icon-reply',
instructure: 'instructure-logo',
'settings-2': 'settings',
'twitter-boxed': 'twitter',
'arrow-left': 'arrow-start',
'arrow-open-left': 'arrow-open-start',
'arrow-open-right': 'arrow-open-end',
'arrow-right': 'arrow-end',
'expand-left': 'expand-start',
'mini-arrow-left': 'mini-arrow-start',
'mini-arrow-right': 'mini-arrow-end',
'move-left': 'move-start',
'move-right': 'move-end',
'text-left': 'text-start',
'text-right': 'text-end',
'toggle-left': 'toggle-start',
'toggle-right': 'toggle-end',
unpublish: 'unpublished'
}

const bidirectional = [
'address-book',
'annotate',
Expand Down Expand Up @@ -171,6 +135,5 @@ module.exports = {
svg,
fonts,
react,
deprecated,
bidirectional
}
Loading
Loading