fix(UI): manual import map for DynamicIcon to avoid huge bundle of Tabler icons

This commit is contained in:
Jake Turner 2026-04-02 19:03:39 +00:00
parent 7218b7458b
commit dc7c5ca0e0
No known key found for this signature in database
GPG Key ID: 6DCBBAE4FEAB53EB
3 changed files with 113 additions and 20 deletions

View File

@ -1,6 +1,5 @@
import * as Icons from '@tabler/icons-react'
import classNames from '~/lib/classNames' import classNames from '~/lib/classNames'
import DynamicIcon from './DynamicIcon' import DynamicIcon, { DynamicIconName } from './DynamicIcon'
import StyledButton, { StyledButtonProps } from './StyledButton' import StyledButton, { StyledButtonProps } from './StyledButton'
export type AlertProps = React.HTMLAttributes<HTMLDivElement> & { export type AlertProps = React.HTMLAttributes<HTMLDivElement> & {
@ -10,7 +9,7 @@ export type AlertProps = React.HTMLAttributes<HTMLDivElement> & {
children?: React.ReactNode children?: React.ReactNode
dismissible?: boolean dismissible?: boolean
onDismiss?: () => void onDismiss?: () => void
icon?: keyof typeof Icons icon?: DynamicIconName
variant?: 'standard' | 'bordered' | 'solid' variant?: 'standard' | 'bordered' | 'solid'
buttonProps?: StyledButtonProps buttonProps?: StyledButtonProps
} }
@ -27,7 +26,7 @@ export default function Alert({
buttonProps, buttonProps,
...props ...props
}: AlertProps) { }: AlertProps) {
const getDefaultIcon = (): keyof typeof Icons => { const getDefaultIcon = (): DynamicIconName => {
switch (type) { switch (type) {
case 'warning': case 'warning':
return 'IconAlertTriangle' return 'IconAlertTriangle'

View File

@ -1,36 +1,26 @@
import classNames from 'classnames' import classNames from 'classnames'
import * as TablerIcons from '@tabler/icons-react' import { icons } from '../lib/icons'
export type DynamicIconName = keyof typeof TablerIcons export type { DynamicIconName } from '../lib/icons'
interface DynamicIconProps { interface DynamicIconProps {
icon?: DynamicIconName icon?: keyof typeof icons
className?: string className?: string
stroke?: number stroke?: number
onClick?: () => void onClick?: () => void
} }
/**
* Renders a dynamic icon from the TablerIcons library based on the provided icon name.
* @param icon - The name of the icon to render.
* @param className - Optional additional CSS classes to apply to the icon.
* @param stroke - Optional stroke width for the icon.
* @returns A React element representing the icon, or null if no matching icon is found.
*/
const DynamicIcon: React.FC<DynamicIconProps> = ({ icon, className, stroke, onClick }) => { const DynamicIcon: React.FC<DynamicIconProps> = ({ icon, className, stroke, onClick }) => {
if (!icon) return null if (!icon) return null
const Icon = TablerIcons[icon] const Icon = icons[icon]
if (!Icon) { if (!Icon) {
console.warn(`Icon "${icon}" not found in TablerIcons.`) console.warn(`Icon "${icon}" not found in icon map.`)
return null return null
} }
return ( return <Icon className={classNames('h-5 w-5', className)} strokeWidth={stroke ?? 2} onClick={onClick} />
// @ts-ignore
<Icon className={classNames('h-5 w-5', className)} stroke={stroke || 2} onClick={onClick} />
)
} }
export default DynamicIcon export default DynamicIcon

104
admin/inertia/lib/icons.ts Normal file
View File

@ -0,0 +1,104 @@
import {
IconArrowUp,
IconBooks,
IconBrain,
IconChefHat,
IconCheck,
IconChevronLeft,
IconChevronRight,
IconCloudDownload,
IconCloudUpload,
IconCpu,
IconDatabase,
IconDownload,
IconHome,
IconLogs,
IconNotes,
IconPlayerPlay,
IconPlus,
IconRefresh,
IconRefreshAlert,
IconRobot,
IconSchool,
IconSettings,
IconTrash,
IconUpload,
IconWand,
IconWorld,
IconX,
IconAlertTriangle,
IconXboxX,
IconCircleCheck,
IconInfoCircle,
IconBug,
IconCopy,
IconServer,
IconMenu2,
IconArrowLeft,
IconArrowRight,
IconSun,
IconMoon,
IconStethoscope,
IconShieldCheck,
IconTool,
IconPlant,
IconCode,
IconMap,
} from '@tabler/icons-react'
/**
* An explicit import of used icons in the DynamicIcon component to ensure we get maximum tree-shaking
* while still providing us a nice DX with the DynamicIcon component and icon name inference.
* Only icons that are actually used by DynamicIcon should be added here. Yes, it does introduce
* some manual maintenance, but the bundle size benefits are worth it since we use a (relatively)
* very limited subset of the full Tabler Icons library.
*/
export const icons = {
IconAlertTriangle,
IconArrowLeft,
IconArrowRight,
IconArrowUp,
IconBooks,
IconBrain,
IconBug,
IconChefHat,
IconCheck,
IconChevronLeft,
IconChevronRight,
IconCircleCheck,
IconCloudDownload,
IconCloudUpload,
IconCode,
IconCopy,
IconCpu,
IconDatabase,
IconDownload,
IconHome,
IconInfoCircle,
IconLogs,
IconMap,
IconMenu2,
IconMoon,
IconNotes,
IconPlant,
IconPlayerPlay,
IconPlus,
IconRefresh,
IconRefreshAlert,
IconRobot,
IconSchool,
IconServer,
IconSettings,
IconShieldCheck,
IconStethoscope,
IconSun,
IconTool,
IconTrash,
IconUpload,
IconWand,
IconWorld,
IconX,
IconXboxX
} as const
export type DynamicIconName = keyof typeof icons