mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-26 23:04:57 +00:00
ORGANIZED INTO 4 MAIN CATEGORIES: - react/ React TypeScript components (145 components + Python bindings) - qml/ QML desktop components (104+ QML components) - python/ Python package implementations - legacy/ Utilities, contexts, and migration-in-progress code SUPPORTING FOLDERS (kept as-is): - icons/ 421 SVG icons - theming/ Material Design 3 theme system - styles/ SCSS modules and utilities - scss/ SCSS preprocessor files - docs/ Documentation files STRUCTURE IMPROVEMENTS: ✅ All code preserved (nothing deleted) ✅ Clear separation by implementation type ✅ Better navigation and discoverability ✅ Easy to find what you need ✅ Professional organization DOCUMENTATION: - Added STRUCTURE.md explaining the new layout - Updated folder organization with clear purpose - Maintained all original functionality All files reorganized while keeping full functionality intact. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
140 lines
3.8 KiB
TypeScript
140 lines
3.8 KiB
TypeScript
import React, { forwardRef, useId } from 'react'
|
|
|
|
/**
|
|
* Valid input sizes
|
|
*/
|
|
export type InputSize = 'sm' | 'md' | 'lg'
|
|
|
|
export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
|
/** Input size */
|
|
size?: InputSize
|
|
/** @deprecated Use size="sm" instead */
|
|
sm?: boolean
|
|
/** @deprecated Use size="md" instead */
|
|
md?: boolean
|
|
/** @deprecated Use size="lg" instead */
|
|
lg?: boolean
|
|
/** Error state styling */
|
|
error?: boolean
|
|
/** Error message to display */
|
|
errorMessage?: string
|
|
/** Label for the input */
|
|
label?: string
|
|
/** Helper text displayed below input */
|
|
helperText?: string
|
|
/** Full width input */
|
|
fullWidth?: boolean
|
|
/** Start adornment element */
|
|
startAdornment?: React.ReactNode
|
|
/** End adornment element */
|
|
endAdornment?: React.ReactNode
|
|
}
|
|
|
|
/**
|
|
* Get size class from props
|
|
*/
|
|
const getSizeClass = (props: InputProps): string => {
|
|
if (props.size) return `input--${props.size}`
|
|
if (props.sm) return 'input--sm'
|
|
if (props.lg) return 'input--lg'
|
|
if (props.md) return 'input--md'
|
|
return ''
|
|
}
|
|
|
|
/**
|
|
* Input component with label and error support
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <Input label="Email" type="email" placeholder="Enter email" />
|
|
* <Input error errorMessage="This field is required" />
|
|
* ```
|
|
*/
|
|
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
(props, ref) => {
|
|
const {
|
|
size,
|
|
sm,
|
|
md,
|
|
lg,
|
|
error,
|
|
errorMessage,
|
|
label,
|
|
helperText,
|
|
fullWidth,
|
|
startAdornment,
|
|
endAdornment,
|
|
className = '',
|
|
id: idProp,
|
|
'aria-describedby': ariaDescribedBy,
|
|
...restProps
|
|
} = props
|
|
|
|
const generatedId = useId()
|
|
const id = idProp ?? generatedId
|
|
const helperId = `${id}-helper`
|
|
const errorId = `${id}-error`
|
|
|
|
const classes = [
|
|
'input',
|
|
getSizeClass(props),
|
|
error ? 'input--error' : '',
|
|
fullWidth ? 'input--full-width' : '',
|
|
startAdornment ? 'input--has-start' : '',
|
|
endAdornment ? 'input--has-end' : '',
|
|
className,
|
|
].filter(Boolean).join(' ')
|
|
|
|
// Build aria-describedby
|
|
const describedByParts: string[] = []
|
|
if (ariaDescribedBy) describedByParts.push(ariaDescribedBy)
|
|
if (error && errorMessage) describedByParts.push(errorId)
|
|
if (helperText && !error) describedByParts.push(helperId)
|
|
const describedBy = describedByParts.length > 0 ? describedByParts.join(' ') : undefined
|
|
|
|
const inputElement = (
|
|
<div className={`input-wrapper ${fullWidth ? 'input-wrapper--full-width' : ''}`}>
|
|
{startAdornment && <span className="input__adornment input__adornment--start">{startAdornment}</span>}
|
|
<input
|
|
ref={ref}
|
|
id={id}
|
|
className={classes}
|
|
aria-invalid={error}
|
|
aria-describedby={describedBy}
|
|
{...restProps}
|
|
/>
|
|
{endAdornment && <span className="input__adornment input__adornment--end">{endAdornment}</span>}
|
|
</div>
|
|
)
|
|
|
|
// If no label or helper text, return just the input
|
|
if (!label && !helperText && !errorMessage) {
|
|
return inputElement
|
|
}
|
|
|
|
return (
|
|
<div className={`input-field ${fullWidth ? 'input-field--full-width' : ''}`}>
|
|
{label && (
|
|
<label htmlFor={id} className="input__label">
|
|
{label}
|
|
{restProps.required && <span className="input__required" aria-hidden="true"> *</span>}
|
|
</label>
|
|
)}
|
|
{inputElement}
|
|
{error && errorMessage && (
|
|
<span id={errorId} className="input__error-message" role="alert">
|
|
{errorMessage}
|
|
</span>
|
|
)}
|
|
{!error && helperText && (
|
|
<span id={helperId} className="input__helper-text">
|
|
{helperText}
|
|
</span>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
)
|
|
|
|
Input.displayName = 'Input'
|