Stirling-PDF/frontend/src/components/shared/TextInput.tsx
Anthony Stirling 73deece29e
V2 Replace Google Fonts icons with locally bundled Iconify icons (#4283)
# Description of Changes

This PR refactors the frontend icon system to remove reliance on
@mui/icons-material and the Google Material Symbols webfont.

🔄 Changes

Introduced a new LocalIcon component powered by Iconify.
Added scripts/generate-icons.js to:
Scan the codebase for used icons.
Extract only required Material Symbols from
@iconify-json/material-symbols.
Generate a minimized JSON bundle and TypeScript types.
Updated .gitignore to exclude generated icon files.
Replaced all <span className="material-symbols-rounded"> and MUI icon
imports with <LocalIcon> usage.
Removed material-symbols CSS import and related font dependency.
Updated tsconfig.json to support JSON imports.
Added prebuild/predev hooks to auto-generate the icons.

 Benefits

No more 5MB+ Google webfont download → reduces initial page load size.
Smaller install footprint → no giant @mui/icons-material dependency.
Only ships the icons we actually use, cutting bundle size further.
Type-safe icons via auto-generated MaterialSymbolIcon union type.

Note most MUI not included in this update since they are low priority
due to small SVG sizing (don't grab whole bundle)


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: a <a>
2025-08-25 16:07:55 +01:00

106 lines
2.9 KiB
TypeScript

import React, { forwardRef } from 'react';
import { useMantineColorScheme } from '@mantine/core';
import LocalIcon from './LocalIcon';
import styles from './textInput/TextInput.module.css';
/**
* Props for the TextInput component
*/
export interface TextInputProps {
/** The input value (required) */
value: string;
/** Callback when input value changes (required) */
onChange: (value: string) => void;
/** Placeholder text */
placeholder?: string;
/** Optional left icon */
icon?: React.ReactNode;
/** Whether to show the clear button (default: true) */
showClearButton?: boolean;
/** Custom clear handler (defaults to setting value to empty string) */
onClear?: () => void;
/** Additional CSS classes */
className?: string;
/** Additional inline styles */
style?: React.CSSProperties;
/** HTML autocomplete attribute (default: 'off') */
autoComplete?: string;
/** Whether the input is disabled (default: false) */
disabled?: boolean;
/** Whether the input is read-only (default: false) */
readOnly?: boolean;
/** Accessibility label */
'aria-label'?: string;
}
export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(({
value,
onChange,
placeholder,
icon,
showClearButton = true,
onClear,
className = '',
style,
autoComplete = 'off',
disabled = false,
readOnly = false,
'aria-label': ariaLabel,
...props
}, ref) => {
const { colorScheme } = useMantineColorScheme();
const handleClear = () => {
if (onClear) {
onClear();
} else {
onChange('');
}
};
const shouldShowClearButton = showClearButton && value.trim().length > 0 && !disabled && !readOnly;
return (
<div className={`${styles.container} ${className}`} style={style}>
{icon && (
<span
className={styles.icon}
style={{ color: colorScheme === 'dark' ? '#FFFFFF' : '#6B7382' }}
>
{icon}
</span>
)}
<input
ref={ref}
type="text"
placeholder={placeholder}
value={value}
onChange={(e) => onChange(e.currentTarget.value)}
autoComplete={autoComplete}
className={styles.input}
disabled={disabled}
readOnly={readOnly}
aria-label={ariaLabel}
style={{
backgroundColor: colorScheme === 'dark' ? '#4B525A' : '#FFFFFF',
color: colorScheme === 'dark' ? '#FFFFFF' : '#6B7382',
paddingRight: shouldShowClearButton ? '40px' : '12px',
paddingLeft: icon ? '40px' : '12px',
}}
{...props}
/>
{shouldShowClearButton && (
<button
type="button"
className={styles.clearButton}
onClick={handleClear}
style={{ color: colorScheme === 'dark' ? '#FFFFFF' : '#6B7382' }}
aria-label="Clear input"
>
<LocalIcon icon="close-rounded" width="1.25rem" height="1.25rem" />
</button>
)}
</div>
);
});