Files
stack/packages/ui/src/components/Input.tsx
T
Jason Woltje f47dd8bc92 feat: add domains, ideas, layouts, widgets API modules
- Add DomainsModule with full CRUD, search, and activity logging
- Add IdeasModule with quick capture endpoint
- Add LayoutsModule for user dashboard layouts
- Add WidgetsModule for widget definitions (read-only)
- Update ActivityService with domain/idea logging methods
- Register all new modules in AppModule
2026-01-29 13:47:03 -06:00

59 lines
1.7 KiB
TypeScript

import type { InputHTMLAttributes } from "react";
export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size"> {
label?: string;
error?: string;
helperText?: string;
fullWidth?: boolean;
}
export function Input({
label,
error,
helperText,
fullWidth = false,
className = "",
id,
...props
}: InputProps) {
const inputId = id || `input-${Math.random().toString(36).substr(2, 9)}`;
const errorId = error ? `${inputId}-error` : undefined;
const helperId = helperText ? `${inputId}-helper` : undefined;
const baseStyles = "px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors";
const widthStyles = fullWidth ? "w-full" : "";
const errorStyles = error ? "border-red-500 focus:ring-red-500" : "border-gray-300";
const combinedClassName = [baseStyles, widthStyles, errorStyles, className].filter(Boolean).join(" ");
return (
<div className={fullWidth ? "w-full" : ""}>
{label && (
<label
htmlFor={inputId}
className="block text-sm font-medium text-gray-700 mb-1"
>
{label}
</label>
)}
<input
id={inputId}
className={combinedClassName}
aria-invalid={error ? "true" : "false"}
aria-describedby={[errorId, helperId].filter(Boolean).join(" ") || undefined}
{...props}
/>
{error && (
<p id={errorId} className="mt-1 text-sm text-red-600" role="alert">
{error}
</p>
)}
{helperText && !error && (
<p id={helperId} className="mt-1 text-sm text-gray-500">
{helperText}
</p>
)}
</div>
);
}