erp-construccion/docs/06-frontend-specs/components/COMP-shared.md

759 lines
14 KiB
Markdown

# Shared Components Specification
**Version:** 1.0.0
**Fecha:** 2025-12-05
---
## UI Components
### Button
```tsx
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
loading?: boolean;
leftIcon?: ReactNode;
rightIcon?: ReactNode;
fullWidth?: boolean;
type?: 'button' | 'submit' | 'reset';
onClick?: () => void;
children: ReactNode;
}
// Uso
<Button variant="primary" size="md" loading={isLoading}>
Guardar
</Button>
```
### Card
```tsx
interface CardProps {
className?: string;
padding?: 'none' | 'sm' | 'md' | 'lg';
shadow?: 'none' | 'sm' | 'md' | 'lg';
border?: boolean;
hoverable?: boolean;
onClick?: () => void;
children: ReactNode;
}
// Variantes
<Card.Header />
<Card.Body />
<Card.Footer />
```
### Badge
```tsx
interface BadgeProps {
variant?: 'default' | 'success' | 'warning' | 'danger' | 'info';
size?: 'sm' | 'md';
dot?: boolean;
removable?: boolean;
onRemove?: () => void;
children: ReactNode;
}
// Uso
<Badge variant="success">Activo</Badge>
<Badge variant="warning" dot>Pendiente</Badge>
```
### Modal
```tsx
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title?: string;
description?: string;
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
closeOnOverlayClick?: boolean;
closeOnEscape?: boolean;
showCloseButton?: boolean;
children: ReactNode;
footer?: ReactNode;
}
// Uso
<Modal isOpen={isOpen} onClose={onClose} title="Crear Proyecto" size="lg">
<ProjectForm />
<Modal.Footer>
<Button variant="outline" onClick={onClose}>Cancelar</Button>
<Button variant="primary" onClick={onSubmit}>Crear</Button>
</Modal.Footer>
</Modal>
```
### Tabs
```tsx
interface TabsProps {
defaultValue?: string;
value?: string;
onChange?: (value: string) => void;
children: ReactNode;
}
interface TabProps {
value: string;
label: string;
icon?: ReactNode;
disabled?: boolean;
badge?: number | string;
}
// Uso
<Tabs defaultValue="general">
<Tabs.List>
<Tabs.Tab value="general" label="General" />
<Tabs.Tab value="budget" label="Presupuesto" badge={3} />
<Tabs.Tab value="schedule" label="Cronograma" />
</Tabs.List>
<Tabs.Panel value="general"><GeneralTab /></Tabs.Panel>
<Tabs.Panel value="budget"><BudgetTab /></Tabs.Panel>
<Tabs.Panel value="schedule"><ScheduleTab /></Tabs.Panel>
</Tabs>
```
### Dropdown
```tsx
interface DropdownProps {
trigger: ReactNode;
items: DropdownItem[];
align?: 'start' | 'end';
side?: 'top' | 'bottom';
}
interface DropdownItem {
label: string;
value: string;
icon?: ReactNode;
disabled?: boolean;
danger?: boolean;
onClick?: () => void;
}
// Uso
<Dropdown
trigger={<Button variant="ghost"><MoreIcon /></Button>}
items={[
{ label: 'Editar', value: 'edit', icon: <EditIcon /> },
{ label: 'Duplicar', value: 'duplicate', icon: <CopyIcon /> },
{ label: 'Eliminar', value: 'delete', icon: <TrashIcon />, danger: true },
]}
/>
```
### Progress
```tsx
interface ProgressProps {
value: number;
max?: number;
variant?: 'default' | 'success' | 'warning' | 'danger';
size?: 'sm' | 'md' | 'lg';
showLabel?: boolean;
labelFormat?: (value: number, max: number) => string;
animated?: boolean;
}
// Uso
<Progress value={75} showLabel />
<Progress value={30} variant="warning" size="lg" />
```
### Avatar
```tsx
interface AvatarProps {
src?: string;
name?: string;
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
shape?: 'circle' | 'square';
status?: 'online' | 'offline' | 'busy' | 'away';
fallback?: ReactNode;
}
// Uso
<Avatar src={user.avatar} name={user.name} size="md" status="online" />
<Avatar.Group max={3}>
<Avatar name="User 1" />
<Avatar name="User 2" />
<Avatar name="User 3" />
<Avatar name="User 4" />
</Avatar.Group>
```
### Tooltip
```tsx
interface TooltipProps {
content: ReactNode;
position?: 'top' | 'bottom' | 'left' | 'right';
delay?: number;
disabled?: boolean;
children: ReactNode;
}
// Uso
<Tooltip content="Mas informacion sobre este campo">
<InfoIcon className="text-gray-400" />
</Tooltip>
```
### Alert
```tsx
interface AlertProps {
type: 'info' | 'success' | 'warning' | 'error';
title?: string;
message: string;
dismissible?: boolean;
onDismiss?: () => void;
action?: {
label: string;
onClick: () => void;
};
}
// Uso
<Alert
type="warning"
title="Atencion"
message="El presupuesto excede el limite aprobado"
action={{ label: 'Ver detalles', onClick: () => {} }}
/>
```
---
## Form Components
### Input
```tsx
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string;
helper?: string;
error?: string;
leftAddon?: ReactNode;
rightAddon?: ReactNode;
leftIcon?: ReactNode;
rightIcon?: ReactNode;
}
// Uso
<Input
label="Nombre del proyecto"
placeholder="Ingrese nombre"
error={errors.name?.message}
helper="Maximo 200 caracteres"
/>
```
### Select
```tsx
interface SelectProps {
label?: string;
placeholder?: string;
options: SelectOption[];
value?: string | string[];
onChange?: (value: string | string[]) => void;
multiple?: boolean;
searchable?: boolean;
clearable?: boolean;
disabled?: boolean;
error?: string;
loading?: boolean;
}
interface SelectOption {
value: string;
label: string;
disabled?: boolean;
group?: string;
}
// Uso
<Select
label="Estado"
options={statusOptions}
value={status}
onChange={setStatus}
searchable
/>
```
### DatePicker
```tsx
interface DatePickerProps {
label?: string;
value?: Date | null;
onChange?: (date: Date | null) => void;
format?: string;
placeholder?: string;
minDate?: Date;
maxDate?: Date;
disabled?: boolean;
error?: string;
}
interface DateRangePickerProps {
label?: string;
value?: { start: Date | null; end: Date | null };
onChange?: (range: { start: Date | null; end: Date | null }) => void;
// ... same as DatePicker
}
// Uso
<DatePicker
label="Fecha de inicio"
value={startDate}
onChange={setStartDate}
minDate={new Date()}
/>
<DateRangePicker
label="Periodo"
value={dateRange}
onChange={setDateRange}
/>
```
### FileUpload
```tsx
interface FileUploadProps {
label?: string;
accept?: string;
maxSize?: number; // bytes
maxFiles?: number;
multiple?: boolean;
onUpload?: (files: File[]) => void;
onRemove?: (file: File) => void;
value?: File[];
error?: string;
helper?: string;
preview?: boolean;
dragAndDrop?: boolean;
}
// Uso
<FileUpload
label="Documentos"
accept=".pdf,.doc,.docx"
maxSize={10 * 1024 * 1024}
maxFiles={5}
multiple
dragAndDrop
onUpload={handleUpload}
/>
```
### CurrencyInput
```tsx
interface CurrencyInputProps {
label?: string;
value?: number;
onChange?: (value: number) => void;
currency?: string; // MXN, USD
locale?: string;
precision?: number;
min?: number;
max?: number;
error?: string;
}
// Uso
<CurrencyInput
label="Monto"
value={amount}
onChange={setAmount}
currency="MXN"
/>
```
### Form (React Hook Form Integration)
```tsx
interface FormProps<T extends FieldValues> {
onSubmit: (data: T) => void | Promise<void>;
defaultValues?: DefaultValues<T>;
schema?: ZodSchema<T>;
children: ReactNode;
}
// Uso
<Form onSubmit={handleSubmit} schema={projectSchema}>
<Form.Field name="name" label="Nombre">
<Input placeholder="Nombre del proyecto" />
</Form.Field>
<Form.Field name="status" label="Estado">
<Select options={statusOptions} />
</Form.Field>
<Form.Submit>Guardar</Form.Submit>
</Form>
```
---
## Table Components
### DataTable
```tsx
interface DataTableProps<T> {
columns: ColumnDef<T>[];
data: T[];
loading?: boolean;
emptyMessage?: string;
sortable?: boolean;
selectable?: boolean;
selectedRows?: string[];
onSelectRows?: (ids: string[]) => void;
onRowClick?: (row: T) => void;
rowActions?: (row: T) => DropdownItem[];
pagination?: PaginationProps;
stickyHeader?: boolean;
}
interface ColumnDef<T> {
id: string;
header: string | ReactNode;
accessor: keyof T | ((row: T) => any);
cell?: (value: any, row: T) => ReactNode;
sortable?: boolean;
width?: number | string;
align?: 'left' | 'center' | 'right';
}
// Uso
const columns: ColumnDef<Project>[] = [
{ id: 'code', header: 'Codigo', accessor: 'code', sortable: true },
{ id: 'name', header: 'Nombre', accessor: 'name', sortable: true },
{
id: 'status',
header: 'Estado',
accessor: 'status',
cell: (value) => <Badge variant={getVariant(value)}>{value}</Badge>
},
{
id: 'progress',
header: 'Avance',
accessor: 'progressPercentage',
cell: (value) => <Progress value={value} size="sm" />
}
];
<DataTable
columns={columns}
data={projects}
loading={isLoading}
selectable
pagination={{ total, page, limit, onChange }}
rowActions={(row) => [
{ label: 'Editar', onClick: () => handleEdit(row) },
{ label: 'Eliminar', onClick: () => handleDelete(row), danger: true }
]}
/>
```
### TableFilters
```tsx
interface TableFiltersProps {
filters: FilterConfig[];
values: Record<string, any>;
onChange: (values: Record<string, any>) => void;
onClear?: () => void;
}
interface FilterConfig {
key: string;
label: string;
type: 'text' | 'select' | 'date' | 'dateRange' | 'number';
options?: SelectOption[]; // for select
placeholder?: string;
}
// Uso
<TableFilters
filters={[
{ key: 'search', label: 'Buscar', type: 'text' },
{ key: 'status', label: 'Estado', type: 'select', options: statusOptions },
{ key: 'dateRange', label: 'Fecha', type: 'dateRange' }
]}
values={filters}
onChange={setFilters}
onClear={resetFilters}
/>
```
---
## Chart Components
### LineChart
```tsx
interface LineChartProps {
data: ChartData[];
xKey: string;
yKey: string | string[];
height?: number;
showGrid?: boolean;
showLegend?: boolean;
showTooltip?: boolean;
colors?: string[];
curve?: 'linear' | 'monotone' | 'step';
}
// Uso
<LineChart
data={progressData}
xKey="date"
yKey={['planned', 'actual']}
height={300}
showLegend
/>
```
### BarChart
```tsx
interface BarChartProps {
data: ChartData[];
xKey: string;
yKey: string | string[];
height?: number;
orientation?: 'vertical' | 'horizontal';
stacked?: boolean;
showGrid?: boolean;
showLegend?: boolean;
colors?: string[];
}
// Uso
<BarChart
data={budgetData}
xKey="category"
yKey={['budgeted', 'actual']}
height={400}
showLegend
/>
```
### PieChart
```tsx
interface PieChartProps {
data: PieData[];
nameKey: string;
valueKey: string;
height?: number;
donut?: boolean;
showLabels?: boolean;
showLegend?: boolean;
colors?: string[];
}
// Uso
<PieChart
data={statusDistribution}
nameKey="status"
valueKey="count"
donut
showLegend
/>
```
### GaugeChart
```tsx
interface GaugeChartProps {
value: number;
max?: number;
min?: number;
thresholds?: { value: number; color: string }[];
label?: string;
showValue?: boolean;
size?: 'sm' | 'md' | 'lg';
}
// Uso
<GaugeChart
value={75}
max={100}
thresholds={[
{ value: 30, color: 'red' },
{ value: 70, color: 'yellow' },
{ value: 100, color: 'green' }
]}
label="Avance Total"
showValue
/>
```
---
## Layout Components
### PageHeader
```tsx
interface PageHeaderProps {
title: string;
subtitle?: string;
breadcrumbs?: BreadcrumbItem[];
actions?: ReactNode;
tabs?: TabItem[];
activeTab?: string;
onTabChange?: (tab: string) => void;
}
// Uso
<PageHeader
title="Proyectos"
subtitle="Gestiona todos los proyectos de construccion"
breadcrumbs={[
{ label: 'Dashboard', href: '/' },
{ label: 'Proyectos' }
]}
actions={<Button>+ Nuevo Proyecto</Button>}
/>
```
### Sidebar
```tsx
interface SidebarProps {
items: SidebarItem[];
collapsed?: boolean;
onCollapse?: (collapsed: boolean) => void;
logo?: ReactNode;
footer?: ReactNode;
}
interface SidebarItem {
label: string;
href?: string;
icon?: ReactNode;
badge?: number | string;
children?: SidebarItem[];
active?: boolean;
}
```
### EmptyState
```tsx
interface EmptyStateProps {
icon?: ReactNode;
title: string;
description?: string;
action?: ReactNode;
}
// Uso
<EmptyState
icon={<FolderIcon />}
title="Sin documentos"
description="No hay documentos en esta carpeta"
action={<Button>Subir documento</Button>}
/>
```
### LoadingSpinner
```tsx
interface LoadingSpinnerProps {
size?: 'sm' | 'md' | 'lg';
fullScreen?: boolean;
message?: string;
}
// Uso
<LoadingSpinner size="lg" message="Cargando proyectos..." />
```
---
## Utility Components
### ConfirmDialog
```tsx
interface ConfirmDialogProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void | Promise<void>;
title: string;
message: string;
confirmLabel?: string;
cancelLabel?: string;
variant?: 'danger' | 'warning' | 'info';
loading?: boolean;
}
// Uso
<ConfirmDialog
isOpen={isDeleteDialogOpen}
onClose={() => setIsDeleteDialogOpen(false)}
onConfirm={handleDelete}
title="Eliminar proyecto"
message="Esta accion no se puede deshacer. Se eliminaran todos los datos asociados."
variant="danger"
confirmLabel="Eliminar"
/>
```
### Toast
```tsx
// Hook
const { toast } = useToast();
toast({
title: 'Proyecto creado',
description: 'El proyecto se ha creado exitosamente',
type: 'success',
duration: 5000
});
toast.success('Guardado exitosamente');
toast.error('Error al guardar');
toast.warning('Atencion requerida');
toast.info('Informacion importante');
```
### Skeleton
```tsx
interface SkeletonProps {
width?: number | string;
height?: number | string;
variant?: 'text' | 'rectangular' | 'circular';
animation?: 'pulse' | 'wave' | 'none';
}
// Uso
<Skeleton variant="text" width="60%" />
<Skeleton variant="rectangular" height={200} />
<Skeleton variant="circular" width={40} height={40} />
```
---
*Ultima actualizacion: 2025-12-05*