759 lines
14 KiB
Markdown
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*
|