Files
stack/packages/ui/src/components/Select.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

82 lines
2.2 KiB
TypeScript

import type { SelectHTMLAttributes } from "react";
export interface SelectOption {
value: string;
label: string;
disabled?: boolean;
}
export interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, "size"> {
label?: string;
error?: string;
helperText?: string;
fullWidth?: boolean;
options: SelectOption[];
placeholder?: string;
}
export function Select({
label,
error,
helperText,
fullWidth = false,
options,
placeholder = "Select an option...",
className = "",
id,
...props
}: SelectProps) {
const selectId = id || `select-${Math.random().toString(36).substr(2, 9)}`;
const errorId = error ? `${selectId}-error` : undefined;
const helperId = helperText ? `${selectId}-helper` : undefined;
const baseStyles = "px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors bg-white";
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={selectId}
className="block text-sm font-medium text-gray-700 mb-1"
>
{label}
</label>
)}
<select
id={selectId}
className={combinedClassName}
aria-invalid={error ? "true" : "false"}
aria-describedby={[errorId, helperId].filter(Boolean).join(" ") || undefined}
{...props}
>
<option value="" disabled>
{placeholder}
</option>
{options.map((option) => (
<option
key={option.value}
value={option.value}
disabled={option.disabled}
>
{option.label}
</option>
))}
</select>
{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>
);
}