@axzydev/axzy_ui_system 1.5.1 → 1.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/dist/index.cjs +24 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +23 -6
- package/dist/index.js.map +1 -1
- package/dist/layered.css +1 -1
- package/package.json +4 -2
- package/src/components/atoms/button/button.props.ts +3 -3
- package/src/components/atoms/button/button.tsx +27 -7
- package/src/sandbox/i18n.ts +2 -0
- package/src/showcases/DataShowcases.tsx +41 -0
- package/src/showcases/FeedbackShowcases.tsx +82 -0
- package/src/showcases/FormShowcases.tsx +320 -0
- package/src/showcases/LayoutPrimitivesShowcases.tsx +110 -0
- package/src/showcases/NavigationShowcases.tsx +94 -0
- package/src/showcases/PageShowcases.tsx +62 -0
- package/src/showcases/StructureShowcases.tsx +48 -0
- package/src/types/button.types.ts +19 -1
|
@@ -158,6 +158,14 @@ export const ButtonShowcase = () => {
|
|
|
158
158
|
<ITButton label={t("showcase.forms.sync")} icon={<FaSync />} variant="text" />
|
|
159
159
|
</div>
|
|
160
160
|
</div>
|
|
161
|
+
<div>
|
|
162
|
+
<h3 className="mb-3 w-fit border-l-2 border-primary-700 pl-2.5 font-mono text-[11px] font-medium uppercase tracking-[0.14em] text-slate-700 dark:border-primary-500 dark:text-slate-200">{t("showcase.forms.galleryIconOnly")}</h3>
|
|
163
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
164
|
+
<ITButton variant="icon-only" size="sm" color="primary" icon={<FaEdit />} ariaLabel={t("showcase.common.edit")} />
|
|
165
|
+
<ITButton variant="icon-only" size="md" color="success" icon={<FaSave />} ariaLabel={t("showcase.common.save")} />
|
|
166
|
+
<ITButton variant="icon-only" size="lg" color="danger" icon={<FaTrash />} ariaLabel={t("showcase.common.delete")} />
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
161
169
|
<div>
|
|
162
170
|
<h3 className="mb-3 w-fit border-l-2 border-primary-700 pl-2.5 font-mono text-[11px] font-medium uppercase tracking-[0.14em] text-slate-700 dark:border-primary-500 dark:text-slate-200">{t("showcase.forms.galleryVariants")}</h3>
|
|
163
171
|
<div className="flex flex-wrap gap-3">
|
|
@@ -183,6 +191,36 @@ export const ButtonShowcase = () => {
|
|
|
183
191
|
</div>
|
|
184
192
|
</div>
|
|
185
193
|
}
|
|
194
|
+
doc={{
|
|
195
|
+
summary:
|
|
196
|
+
"Botón de acción con colores semánticos, ocho variantes visuales, tres tamaños e icono opcional.",
|
|
197
|
+
description:
|
|
198
|
+
"La apariencia base sale de theme.button[color] y el tamaño de buttonSizes; admite label o children y mantiene sus propios estados de hover y focus.",
|
|
199
|
+
examples: [
|
|
200
|
+
'<ITButton label="Guardar" color="success" onClick={handleSave} />',
|
|
201
|
+
'<ITButton label="Sincronizar" icon={<FaSync />} variant="outlined" color="gray" size="sm" />',
|
|
202
|
+
'<ITButton variant="icon-only" color="danger" ariaLabel="Eliminar">🗑</ITButton>',
|
|
203
|
+
],
|
|
204
|
+
props: [
|
|
205
|
+
{ name: "label", type: "string", description: "Texto del botón. Se ignora si se pasa children." },
|
|
206
|
+
{ name: "children", type: "ReactNode", description: "Contenido propio; tiene prioridad sobre label e icon." },
|
|
207
|
+
{ name: "icon", type: "ReactNode", description: "Icono renderizado antes del label (no se usa con children)." },
|
|
208
|
+
{ name: "onClick", type: "() => void", description: "Handler de click del botón." },
|
|
209
|
+
{ name: "color", type: '"primary" | "secondary" | "success" | "danger" | "warning" | "info" | "purple" | "error" | "gray"', default: '"primary"', description: "Clave de color semántica del tema." },
|
|
210
|
+
{ name: "size", type: '"sm" | "md" | "lg"', default: '"md"', description: "Tamaño del botón (padding y tipografía)." },
|
|
211
|
+
{ name: "variant", type: '"filled" | "outlined" | "raised" | "rounded" | "text" | "raised-text" | "icon-only" | "link"', default: '"filled"', description: "Estilo visual del botón." },
|
|
212
|
+
{ name: "disabled", type: "boolean", default: "false", description: "Deshabilita la interacción y aplica opacidad reducida." },
|
|
213
|
+
{ name: "type", type: '"submit" | "reset" | "button"', default: '"button"', description: "Atributo type del elemento button nativo." },
|
|
214
|
+
{ name: "ariaLabel", type: "string", description: "Nombre accesible para lectores de pantalla; si falta cae en label." },
|
|
215
|
+
{ name: "title", type: "string", description: "Tooltip nativo; si falta cae en ariaLabel o label." },
|
|
216
|
+
{ name: "className", type: "string", description: "Clases CSS adicionales del botón." },
|
|
217
|
+
],
|
|
218
|
+
notes: [
|
|
219
|
+
"Con children no se renderizan label ni icon.",
|
|
220
|
+
'Usa variant="icon-only" junto a ariaLabel cuando el botón no tenga texto visible.',
|
|
221
|
+
'Con variant="icon-only" + icon (sin label) el botón es cuadrado y el icono queda centrado; el tamaño del cuadrado sale de buttonIconPadding (sm 0.5rem, md 0.625rem, lg 0.75rem).',
|
|
222
|
+
],
|
|
223
|
+
}}
|
|
186
224
|
/>
|
|
187
225
|
);
|
|
188
226
|
};
|
|
@@ -712,6 +750,55 @@ export const InputShowcase = () => {
|
|
|
712
750
|
</div>
|
|
713
751
|
) : undefined
|
|
714
752
|
}
|
|
753
|
+
doc={{
|
|
754
|
+
summary:
|
|
755
|
+
"Campo de entrada con label, validación y slots de iconos; cubre text, password, email, number (con moneda o miles), checkbox, radio y textarea.",
|
|
756
|
+
description:
|
|
757
|
+
"Comparte el tema theme.input con el resto del sistema: el error solo se muestra cuando el campo está tocado y los tipos numéricos se reformatean al perder el foco.",
|
|
758
|
+
examples: [
|
|
759
|
+
'<ITInput name="email" label="Correo" value={email} onChange={(e) => setEmail(e.target.value)} onBlur={() => {}} />',
|
|
760
|
+
'<ITInput name="monto" label="Monto" type="number" currencyFormat value={monto} onChange={onChange} onBlur={() => {}} />',
|
|
761
|
+
'<ITInput name="nota" label="Nota" type="textarea" rows={4} maxLength={200} showHintLength value={nota} onChange={onChange} onBlur={() => {}} />',
|
|
762
|
+
],
|
|
763
|
+
props: [
|
|
764
|
+
{ name: "name", type: "string", required: true, description: "Nombre del campo; se usa como id del control y del label." },
|
|
765
|
+
{ name: "type", type: '"text" | "password" | "number" | "email" | "checkbox" | "radio" | "textarea"', default: '"text"', description: "Tipo de control a renderizar." },
|
|
766
|
+
{ name: "label", type: "string", description: "Etiqueta mostrada encima del campo." },
|
|
767
|
+
{ name: "value", type: "any", description: "Valor actual del campo (controlado)." },
|
|
768
|
+
{ name: "onChange", type: "(event: any) => void", required: true, description: "Handler de cambio; recibe un evento con target.name y target.value." },
|
|
769
|
+
{ name: "onBlur", type: "(event: FocusEvent) => void", description: "Handler de blur; también dispara el formateo numérico." },
|
|
770
|
+
{ name: "placeholder", type: "string", description: "Texto de ayuda dentro del campo." },
|
|
771
|
+
{ name: "touched", type: "boolean", description: "Marca el campo como tocado; sin él se usa el blur interno para decidir cuándo mostrar el error." },
|
|
772
|
+
{ name: "error", type: "string | boolean", description: "Mensaje de error propio, o true para el mensaje genérico de campo requerido." },
|
|
773
|
+
{ name: "required", type: "boolean", default: "false", description: "Asterisco y error automático si el campo está vacío." },
|
|
774
|
+
{ name: "size", type: '"sm" | "md" | "lg"', default: '"md"', description: "Tamaño del campo." },
|
|
775
|
+
{ name: "disabled", type: "boolean", default: "false", description: "Deshabilita el campo." },
|
|
776
|
+
{ name: "readOnly", type: "boolean", default: "false", description: "Renderiza el campo en modo solo lectura." },
|
|
777
|
+
{ name: "currencyFormat", type: "boolean", default: "false", description: "Formatea el número como moneda (locale es-MX)." },
|
|
778
|
+
{ name: "decimals", type: "number", default: "currencyFormat ? 2 : 0", description: "Decimales permitidos; 0 restringe a enteros." },
|
|
779
|
+
{ name: "formatNumber", type: "boolean", default: "true", description: "Activa el formateo de miles al perder el foco." },
|
|
780
|
+
{ name: "maxLength", type: "number", description: "Longitud máxima permitida." },
|
|
781
|
+
{ name: "minLength", type: "number", description: "Longitud mínima permitida." },
|
|
782
|
+
{ name: "showHintLength", type: "boolean", default: "false", description: "Muestra el contador de caracteres (requiere minLength o maxLength)." },
|
|
783
|
+
{ name: "iconLeft", type: "ReactNode", description: "Icono a la izquierda del campo." },
|
|
784
|
+
{ name: "iconRight", type: "ReactNode", description: "Icono a la derecha del campo." },
|
|
785
|
+
{ name: "rows", type: "number", default: "4", description: "Filas visibles cuando type es textarea." },
|
|
786
|
+
{ name: "min", type: "number", description: "Valor numérico mínimo; se recorta al salir del campo." },
|
|
787
|
+
{ name: "max", type: "number", description: "Valor numérico máximo; se recorta al salir del campo." },
|
|
788
|
+
{ name: "checked", type: "boolean", description: "Estado marcado para checkbox y radio." },
|
|
789
|
+
{ name: "autoFocus", type: "boolean", default: "false", description: "Enfoca el campo al montar." },
|
|
790
|
+
{ name: "focusContent", type: "boolean", description: "Selecciona todo el contenido al hacer click." },
|
|
791
|
+
{ name: "onKeyDown", type: "(event: KeyboardEvent) => void", description: "Handler de keydown del control." },
|
|
792
|
+
{ name: "containerClassName", type: "string", description: "Clases CSS del contenedor externo." },
|
|
793
|
+
{ name: "labelClassName", type: "string", description: "Clases CSS de la etiqueta." },
|
|
794
|
+
{ name: "className", type: "string", description: "Clases CSS del control." },
|
|
795
|
+
],
|
|
796
|
+
notes: [
|
|
797
|
+
"El error solo aparece cuando el campo está tocado (touched explícito o blur interno); con required y vacío se usa el mensaje genérico del sistema.",
|
|
798
|
+
'En type="number" el control real es un input de texto con inputMode y filtrado de teclas; en blur se recorta a min/max y se reformatea.',
|
|
799
|
+
"El tipo password incluye un botón de ojo con aria-label traducido.",
|
|
800
|
+
],
|
|
801
|
+
}}
|
|
715
802
|
/>
|
|
716
803
|
</div>
|
|
717
804
|
);
|
|
@@ -772,6 +859,39 @@ export const SelectShowcase = () => {
|
|
|
772
859
|
<ITSelect name="s2" label={t("showcase.forms.selectWithError")} options={[]} error={t("showcase.forms.requiredFieldError2")} touched onChange={() => { }} />
|
|
773
860
|
</div>
|
|
774
861
|
}
|
|
862
|
+
doc={{
|
|
863
|
+
summary:
|
|
864
|
+
"Selector desplegable basado en el select nativo, estilizado con los tokens de input del sistema.",
|
|
865
|
+
description:
|
|
866
|
+
"Admite label, opción vacía, validación con touched/error/required y modo solo lectura; los colores y estados se resuelven desde theme.input.",
|
|
867
|
+
examples: [
|
|
868
|
+
'<ITSelect name="role" label="Rol" value={role} options={roles} onChange={(e) => setRole(e.target.value)} />',
|
|
869
|
+
'<ITSelect name="pais" label="País" placeholder="Elige un país" options={countries} required touched error={!pais ? "Requerido" : undefined} onChange={onChange} />',
|
|
870
|
+
],
|
|
871
|
+
props: [
|
|
872
|
+
{ name: "name", type: "string", required: true, description: "Atributo name del select nativo (también su id)." },
|
|
873
|
+
{ name: "options", type: "OptionType[]", required: true, description: "Arreglo de objetos clave-valor con value/label." },
|
|
874
|
+
{ name: "valueField", type: "string", default: '"value"', description: "Clave de cada opción usada como valor." },
|
|
875
|
+
{ name: "labelField", type: "string", default: '"label"', description: "Clave de cada opción usada como etiqueta visible." },
|
|
876
|
+
{ name: "label", type: "string", description: "Etiqueta sobre el select (vinculada por htmlFor)." },
|
|
877
|
+
{ name: "placeholder", type: "string", description: "Texto de la opción vacía; sin él se usa el mensaje traducido del sistema." },
|
|
878
|
+
{ name: "value", type: "string", description: "Valor seleccionado (controlado)." },
|
|
879
|
+
{ name: "onChange", type: "(event: ChangeEvent<HTMLSelectElement>) => void", description: "Se llama al cambiar la selección; recibe el evento nativo." },
|
|
880
|
+
{ name: "onBlur", type: "(event: FocusEvent<HTMLSelectElement>) => void", description: "Se llama al perder el foco." },
|
|
881
|
+
{ name: "size", type: '"sm" | "md" | "lg"', default: '"md"', description: "Tamaño del control." },
|
|
882
|
+
{ name: "disabled", type: "boolean", default: "false", description: "Deshabilita el select." },
|
|
883
|
+
{ name: "readOnly", type: "boolean", default: "false", description: "Muestra el valor actual como una única opción deshabilitada." },
|
|
884
|
+
{ name: "touched", type: "boolean", description: "Marca el campo como tocado para mostrar el error." },
|
|
885
|
+
{ name: "error", type: "string | boolean", description: "Mensaje de error, o true para el error por defecto." },
|
|
886
|
+
{ name: "required", type: "boolean", description: "Marca el campo como requerido (asterisco y error por defecto)." },
|
|
887
|
+
{ name: "className", type: "string", description: "Clases extra aplicadas al elemento select." },
|
|
888
|
+
],
|
|
889
|
+
notes: [
|
|
890
|
+
"Es un select nativo con appearance none y un icono absoluto: conserva el comportamiento del navegador, incluido el teclado.",
|
|
891
|
+
"La opción vacía se renderiza siempre como primera opción con value vacío.",
|
|
892
|
+
"El error solo se muestra cuando el campo está tocado (touched explícito o blur interno).",
|
|
893
|
+
],
|
|
894
|
+
}}
|
|
775
895
|
/>
|
|
776
896
|
);
|
|
777
897
|
};
|
|
@@ -879,6 +999,47 @@ export const SearchSelectShowcase = () => {
|
|
|
879
999
|
</div>
|
|
880
1000
|
</>
|
|
881
1001
|
}
|
|
1002
|
+
doc={{
|
|
1003
|
+
summary:
|
|
1004
|
+
"Combobox de selección única con búsqueda, filtrado local o remoto y plantilla de opción personalizable.",
|
|
1005
|
+
description:
|
|
1006
|
+
"En modo local filtra las options en cliente; con onSearch delega la búsqueda al consumidor (debounce de 500 ms) y muestra el estado de carga. El panel se renderiza en un portal.",
|
|
1007
|
+
examples: [
|
|
1008
|
+
'<ITSearchSelect name="country" label="País" value={country} options={countries} onChange={(value) => setCountry(value)} />',
|
|
1009
|
+
'<ITSearchSelect name="cliente" label="Cliente" options={results} onSearch={fetchClients} isLoading={loading} onChange={setClient} />',
|
|
1010
|
+
'<ITSearchSelect name="pais" label="País" value={country} options={countries} renderOption={(option) => <span>{option.flag} {option.label}</span>} onChange={setCountry} />',
|
|
1011
|
+
],
|
|
1012
|
+
props: [
|
|
1013
|
+
{ name: "name", type: "string", description: "Atributo name para integración con formularios." },
|
|
1014
|
+
{ name: "label", type: "string", description: "Etiqueta mostrada sobre el campo." },
|
|
1015
|
+
{ name: "placeholder", type: "string", description: "Texto cuando no hay valor seleccionado." },
|
|
1016
|
+
{ name: "value", type: "string | number", description: "Valor seleccionado actual." },
|
|
1017
|
+
{ name: "options", type: "ITSearchSelectOption[]", description: "Opciones para el modo local (filtrado en cliente)." },
|
|
1018
|
+
{ name: "valueField", type: "string", default: '"value"', description: "Campo usado como valor de la opción." },
|
|
1019
|
+
{ name: "labelField", type: "string", default: '"label"', description: "Campo usado como etiqueta de la opción." },
|
|
1020
|
+
{ name: "onChange", type: "(value: string | number, option?: ITSearchSelectOption) => void", description: "Se llama al cambiar el valor; recibe el valor y la opción completa." },
|
|
1021
|
+
{ name: "onBlur", type: "(e: FocusEvent) => void", description: "Se llama al perder el foco el input." },
|
|
1022
|
+
{ name: "onSearch", type: "(query: string) => void", description: "Búsqueda en servidor con debounce de 500 ms; desactiva el filtrado local." },
|
|
1023
|
+
{ name: "isLoading", type: "boolean", default: "false", description: "Muestra el spinner mientras llegan las opciones." },
|
|
1024
|
+
{ name: "noResultsMessage", type: "string", description: "Mensaje mostrado cuando no hay resultados." },
|
|
1025
|
+
{ name: "renderOption", type: "(option, state: { isSelected, searchTerm }) => ReactNode", description: "Plantilla personalizada de cada opción del desplegable." },
|
|
1026
|
+
{ name: "clearable", type: "boolean", default: "true", description: "Muestra el botón de limpiar (X) dentro del campo." },
|
|
1027
|
+
{ name: "onClear", type: "() => void", description: "Se llama al pulsar el botón de limpiar." },
|
|
1028
|
+
{ name: "size", type: '"sm" | "md" | "lg"', default: '"md"', description: "Tamaño del campo." },
|
|
1029
|
+
{ name: "disabled", type: "boolean", default: "false", description: "Deshabilita el componente." },
|
|
1030
|
+
{ name: "readOnly", type: "boolean", default: "false", description: "Modo solo lectura." },
|
|
1031
|
+
{ name: "touched", type: "boolean", description: "Indica que el campo fue interactuado (validación)." },
|
|
1032
|
+
{ name: "required", type: "boolean", description: "Marca el campo como requerido (asterisco rojo)." },
|
|
1033
|
+
{ name: "error", type: "string | boolean", description: "Mensaje de error, o true para el estado de error." },
|
|
1034
|
+
{ name: "className", type: "string", description: "Clases adicionales del contenedor." },
|
|
1035
|
+
],
|
|
1036
|
+
notes: [
|
|
1037
|
+
"Con onSearch el componente no filtra options: el consumidor debe devolver la lista ya filtrada.",
|
|
1038
|
+
"El panel vive en un portal sobre document.body, así que no se recorta en contenedores con overflow oculto.",
|
|
1039
|
+
"Los ítems del desplegable son divs clicables sin role de listbox/option ni navegación por teclado declarada.",
|
|
1040
|
+
"Al limpiar se emite onChange con valor vacío y después onClear.",
|
|
1041
|
+
],
|
|
1042
|
+
}}
|
|
882
1043
|
/>
|
|
883
1044
|
);
|
|
884
1045
|
};
|
|
@@ -955,6 +1116,42 @@ export const DatePickerShowcase = () => {
|
|
|
955
1116
|
</div>
|
|
956
1117
|
</>
|
|
957
1118
|
}
|
|
1119
|
+
doc={{
|
|
1120
|
+
summary:
|
|
1121
|
+
"Selector de fecha con modo simple y modo rango, entrada manual DD/MM/AAAA y popover de calendario.",
|
|
1122
|
+
description:
|
|
1123
|
+
"Emite el cambio como un evento con name y value en target (un Date, o una tupla [inicio, fin] en modo rango) y permite acotar los días con minDate y maxDate.",
|
|
1124
|
+
examples: [
|
|
1125
|
+
'<ITDatePicker name="fecha" label="Fecha" value={date} onChange={(e) => setDate(e.target.value)} />',
|
|
1126
|
+
'<ITDatePicker name="rango" label="Periodo" range value={range} minDate={hoy} onChange={(e) => setRange(e.target.value)} />',
|
|
1127
|
+
'<ITDatePicker name="fecha" label="Fecha" value={date} variant="success" size="sm" onChange={onChange} />',
|
|
1128
|
+
],
|
|
1129
|
+
props: [
|
|
1130
|
+
{ name: "name", type: "string", required: true, description: "Atributo name del input subyacente." },
|
|
1131
|
+
{ name: "value", type: "Date | [Date | null, Date | null]", description: "Fecha única, o tupla [inicio, fin] cuando range es true." },
|
|
1132
|
+
{ name: "onChange", type: "(event: { target: { name, value } }) => void", required: true, description: "Se dispara al cambiar la fecha; el evento lleva name y value en target." },
|
|
1133
|
+
{ name: "onBlur", type: "(event: { target: { name, value } }) => void", description: "Se dispara al perder el foco el input." },
|
|
1134
|
+
{ name: "range", type: "boolean", default: "false", description: "Activa la selección por rango; value pasa a ser la tupla inicio-fin." },
|
|
1135
|
+
{ name: "variant", type: "ColorsTypes", default: '"primary"', description: "Variante de color del sistema." },
|
|
1136
|
+
{ name: "size", type: '"sm" | "md" | "lg"', default: '"md"', description: "Tamaño del campo." },
|
|
1137
|
+
{ name: "disabled", type: "boolean", default: "false", description: "Deshabilita el date picker, incluido el popover." },
|
|
1138
|
+
{ name: "placeholder", type: "string", description: "Texto cuando no hay fecha; por defecto dd/mm/aaaa (o el equivalente de rango)." },
|
|
1139
|
+
{ name: "label", type: "string", description: "Etiqueta sobre el input." },
|
|
1140
|
+
{ name: "required", type: "boolean", description: "Marca el campo como requerido." },
|
|
1141
|
+
{ name: "touched", type: "boolean", description: "Indica visualmente que el campo fue interactuado." },
|
|
1142
|
+
{ name: "error", type: "string | boolean", description: "Mensaje de error, o true para el estilo de error por defecto." },
|
|
1143
|
+
{ name: "minDate", type: "Date", description: "Fecha mínima seleccionable." },
|
|
1144
|
+
{ name: "maxDate", type: "Date", description: "Fecha máxima seleccionable." },
|
|
1145
|
+
{ name: "className", type: "string", description: "Clases CSS adicionales del contenedor." },
|
|
1146
|
+
{ name: "calendarClassName", type: "string", description: "Clases CSS adicionales del popover del calendario." },
|
|
1147
|
+
],
|
|
1148
|
+
notes: [
|
|
1149
|
+
"El texto solo acepta dígitos (máximo 8) y se enmascara como DD/MM/AAAA; en modo simple, una fecha inválida se sustituye por hoy al salir del campo.",
|
|
1150
|
+
"En modo rango, al elegir la segunda fecha se emite el cambio y el popover se cierra; con solo la fecha de inicio el valor queda pendiente y se muestra como inicio - ...",
|
|
1151
|
+
"El calendario interno se instancia con selectionMode range/single y recibe minDate, maxDate y variant.",
|
|
1152
|
+
"Limitación conocida: al teclear a mano una fecha válida, onChange se invoca dos veces.",
|
|
1153
|
+
],
|
|
1154
|
+
}}
|
|
958
1155
|
/>
|
|
959
1156
|
);
|
|
960
1157
|
};
|
|
@@ -1014,6 +1211,37 @@ export const TimePickerShowcase = () => {
|
|
|
1014
1211
|
</div>
|
|
1015
1212
|
</>
|
|
1016
1213
|
}
|
|
1214
|
+
doc={{
|
|
1215
|
+
summary:
|
|
1216
|
+
"Campo de hora con formato 24 h y desplegable de doble columna (horas y minutos).",
|
|
1217
|
+
description:
|
|
1218
|
+
"Permite escribir a mano con autoformato HH:MM o elegir del panel; el desplegable se renderiza en un portal para no recortarse.",
|
|
1219
|
+
examples: [
|
|
1220
|
+
'<ITTimePicker name="inicio" label="Hora de inicio" value={time} onChange={(e) => setTime(e.target.value)} />',
|
|
1221
|
+
'<ITTimePicker name="fin" label="Hora de fin" value={time} color="success" size="sm" onBlur={() => {}} onChange={onChange} />',
|
|
1222
|
+
],
|
|
1223
|
+
props: [
|
|
1224
|
+
{ name: "name", type: "string", required: true, description: "Atributo name del campo y clave del evento de cambio." },
|
|
1225
|
+
{ name: "value", type: "string", description: "Hora controlada en formato HH:MM." },
|
|
1226
|
+
{ name: "label", type: "string", description: "Etiqueta mostrada sobre el input." },
|
|
1227
|
+
{ name: "placeholder", type: "string", default: '"HH:MM"', description: "Texto cuando no hay valor." },
|
|
1228
|
+
{ name: "onChange", type: "(e: any) => void", required: true, description: "Se dispara solo con una hora válida; recibe target.name y target.value." },
|
|
1229
|
+
{ name: "onBlur", type: "(e: any) => void", description: "Se llama al perder el foco el input." },
|
|
1230
|
+
{ name: "required", type: "boolean", description: "Marca el campo como requerido." },
|
|
1231
|
+
{ name: "touched", type: "boolean", description: "Indica que el campo fue interactuado." },
|
|
1232
|
+
{ name: "error", type: "string | boolean", description: "Mensaje de error, o true para mostrar el estado de error." },
|
|
1233
|
+
{ name: "disabled", type: "boolean", description: "Deshabilita el time picker." },
|
|
1234
|
+
{ name: "size", type: '"sm" | "md" | "lg"', default: '"md"', description: "Tamaño del campo." },
|
|
1235
|
+
{ name: "variant", type: '"primary" | "secondary" | "danger" | "success" | "warning" | "info" | "purple"', default: '"primary"', description: "Variante de estilo del input." },
|
|
1236
|
+
{ name: "color", type: "string", default: '"primary"', description: "Color del resaltado del desplegable y del botón de confirmación; acepta claves semánticas o un hex." },
|
|
1237
|
+
{ name: "className", type: "string", description: "Clases CSS adicionales del wrapper." },
|
|
1238
|
+
],
|
|
1239
|
+
notes: [
|
|
1240
|
+
"El input solo acepta dígitos (máximo 4) e inserta los dos puntos automáticamente: 0930 se convierte en 09:30.",
|
|
1241
|
+
"Valida el formato 24 h; mientras el texto es inválido muestra Hora inválida y no emite onChange.",
|
|
1242
|
+
"El desplegable se monta en un portal sobre document.body, con columnas de 24 horas y 60 minutos.",
|
|
1243
|
+
],
|
|
1244
|
+
}}
|
|
1017
1245
|
/>
|
|
1018
1246
|
);
|
|
1019
1247
|
};
|
|
@@ -1087,6 +1315,39 @@ export const CalendarShowcase = () => {
|
|
|
1087
1315
|
/>
|
|
1088
1316
|
</>
|
|
1089
1317
|
}
|
|
1318
|
+
doc={{
|
|
1319
|
+
summary:
|
|
1320
|
+
"Calendario y planificador con vistas de mes, semana y día, eventos, selección simple o por rango y arrastre de franjas.",
|
|
1321
|
+
description:
|
|
1322
|
+
"Con events y los callbacks de evento/franja funciona como scheduler; con onChange y selectionMode entra en modo date picker. Usa date-fns con locale es.",
|
|
1323
|
+
examples: [
|
|
1324
|
+
'<ITCalendar events={events} mode="month" onEventClick={(evt) => openEvent(evt)} />',
|
|
1325
|
+
'<ITCalendar events={events} mode="week" variant="success" onSlotClick={(date) => createEvent(date)} />',
|
|
1326
|
+
'<ITCalendar value={date} selectionMode="range" startDate={inicio} endDate={fin} onChange={setDate} />',
|
|
1327
|
+
],
|
|
1328
|
+
props: [
|
|
1329
|
+
{ name: "events", type: "CalendarEvent[]", description: "Eventos del planificador: id, title, start, end, color y data." },
|
|
1330
|
+
{ name: "mode", type: '"week" | "day" | "month"', description: "Vista inicial. Si se omite, es month cuando hay onChange y week en caso contrario." },
|
|
1331
|
+
{ name: "selectionMode", type: '"single" | "range"', default: '"single"', description: "Modo de selección del date picker." },
|
|
1332
|
+
{ name: "value", type: "Date", description: "Fecha seleccionada en modo picker." },
|
|
1333
|
+
{ name: "onChange", type: "(date: Date) => void", description: "Se llama al seleccionar una fecha en modo picker." },
|
|
1334
|
+
{ name: "startDate", type: "Date", description: "Inicio del rango seleccionado." },
|
|
1335
|
+
{ name: "endDate", type: "Date", description: "Fin del rango seleccionado." },
|
|
1336
|
+
{ name: "onEventClick", type: "(event: CalendarEvent) => void", description: "Se llama al hacer click en un evento." },
|
|
1337
|
+
{ name: "onSlotClick", type: "(date: Date) => void", description: "Se llama al hacer click en una franja vacía." },
|
|
1338
|
+
{ name: "onSlotHover", type: "(date: Date) => void", description: "Se llama cuando el cursor entra en una franja." },
|
|
1339
|
+
{ name: "onSelectRange", type: "(start: Date, end: Date) => void", description: "Se llama al confirmar un rango seleccionado por arrastre." },
|
|
1340
|
+
{ name: "minDate", type: "Date", description: "Fecha mínima seleccionable (solo aplica en la vista de mes)." },
|
|
1341
|
+
{ name: "maxDate", type: "Date", description: "Fecha máxima seleccionable (solo aplica en la vista de mes)." },
|
|
1342
|
+
{ name: "variant", type: "ColorsTypes", default: '"primary"', description: "Color de acento de la selección y del día de hoy." },
|
|
1343
|
+
{ name: "className", type: "string", description: "Clases CSS adicionales del contenedor." },
|
|
1344
|
+
],
|
|
1345
|
+
notes: [
|
|
1346
|
+
"Necesita un contenedor con altura definida: el calendario ocupa el 100% de la altura de su padre.",
|
|
1347
|
+
"Las vistas de semana y día cubren de 6:00 a 22:00 en franjas de 30 minutos; la línea de la hora actual solo aparece si el día mostrado es hoy.",
|
|
1348
|
+
"onSelectRange habilita el arrastre y suma 30 minutos al final de la franja elegida.",
|
|
1349
|
+
],
|
|
1350
|
+
}}
|
|
1090
1351
|
/>
|
|
1091
1352
|
);
|
|
1092
1353
|
};
|
|
@@ -1182,6 +1443,33 @@ export const SlideToggleShowcase = () => {
|
|
|
1182
1443
|
/>
|
|
1183
1444
|
</div>
|
|
1184
1445
|
}
|
|
1446
|
+
doc={{
|
|
1447
|
+
summary:
|
|
1448
|
+
"Interruptor estilo iOS con colores del tema, tres tamaños y modos controlado o no controlado.",
|
|
1449
|
+
description:
|
|
1450
|
+
"Expone role switch y aria-checked; funciona con isOn (controlado) o con initialState más onToggle (no controlado).",
|
|
1451
|
+
examples: [
|
|
1452
|
+
'<ITSlideToggle isOn={enabled} onToggle={setEnabled} ariaLabel="Notificaciones" />',
|
|
1453
|
+
'<ITSlideToggle initialState activeColor="primary" size="lg" label="Modo oscuro" onToggle={setDark} />',
|
|
1454
|
+
'<ITSlideToggle isOn={false} disabled size="sm" ariaLabel="No disponible" />',
|
|
1455
|
+
],
|
|
1456
|
+
props: [
|
|
1457
|
+
{ name: "isOn", type: "boolean", description: "Estado controlado; si se pasa, el valor externo manda." },
|
|
1458
|
+
{ name: "initialState", type: "boolean", default: "false", description: "Estado inicial en uso no controlado." },
|
|
1459
|
+
{ name: "onToggle", type: "(value: boolean) => void", description: "Se llama al cambiar el interruptor con el nuevo estado." },
|
|
1460
|
+
{ name: "activeColor", type: "string", default: '"success"', description: "Color activo: clave semántica del tema o valor hex." },
|
|
1461
|
+
{ name: "inactiveColor", type: "string", default: '"#9ca3af"', description: "Color inactivo: clave semántica del tema o valor hex." },
|
|
1462
|
+
{ name: "size", type: '"sm" | "md" | "lg"', default: '"md"', description: "Tamaño del interruptor." },
|
|
1463
|
+
{ name: "disabled", type: "boolean", default: "false", description: "Deshabilita el interruptor." },
|
|
1464
|
+
{ name: "label", type: "string", description: "Texto visible del interruptor, que también sirve como nombre accesible." },
|
|
1465
|
+
{ name: "ariaLabel", type: "string", description: "Nombre accesible cuando la etiqueta visible vive fuera; tiene prioridad sobre label." },
|
|
1466
|
+
{ name: "className", type: "string", description: "Clases CSS adicionales del contenedor." },
|
|
1467
|
+
],
|
|
1468
|
+
notes: [
|
|
1469
|
+
"Renderiza role switch y responde a Enter y Space; sin label ni ariaLabel el lector de pantalla no sabe qué se está activando.",
|
|
1470
|
+
"Con disabled aplica opacidad reducida y su tabIndex pasa a -1.",
|
|
1471
|
+
],
|
|
1472
|
+
}}
|
|
1185
1473
|
/>
|
|
1186
1474
|
);
|
|
1187
1475
|
};
|
|
@@ -1231,6 +1519,38 @@ export const DropfileShowcase = () => {
|
|
|
1231
1519
|
/>
|
|
1232
1520
|
</>
|
|
1233
1521
|
}
|
|
1522
|
+
doc={{
|
|
1523
|
+
summary:
|
|
1524
|
+
"Zona de carga por arrastrar y soltar con previsualización, badge de estado y flujo seleccionar-confirmar-subir.",
|
|
1525
|
+
description:
|
|
1526
|
+
"Admite tipos MIME configurables, vista previa de imágenes y dos presentaciones: la zona de drop inline o un botón que la abre dentro de un ITDialog.",
|
|
1527
|
+
examples: [
|
|
1528
|
+
'<ITDropfile onFileSelect={setFile} onSubmit={(file) => upload(file)} acceptedFileTypes={[FileTypeEnum.PDF, FileTypeEnum.XLSX]} />',
|
|
1529
|
+
'<ITDropfile onFileSelect={setFile} uploadStatus={status} onStatusChange={setStatus} showStatusBadge />',
|
|
1530
|
+
'<ITDropfile view="button" buttonLabel="Subir archivo" onFileSelect={setFile} />',
|
|
1531
|
+
],
|
|
1532
|
+
props: [
|
|
1533
|
+
{ name: "onFileSelect", type: "(file: File | null) => void", required: true, description: "Se llama al confirmar o limpiar la selección (null al cancelar)." },
|
|
1534
|
+
{ name: "onSubmit", type: "(file: File) => void", description: "Se llama al confirmar el archivo para subirlo." },
|
|
1535
|
+
{ name: "onCancel", type: "() => void", description: "Se llama cuando el usuario cancela la selección actual." },
|
|
1536
|
+
{ name: "acceptedFileTypes", type: "FileTypeEnum[]", default: "[PDF, XLS, XLSX, JPG, PNG, JPEG]", description: "Tipos MIME aceptados." },
|
|
1537
|
+
{ name: "uploadStatus", type: "UploadStatus", description: "Estado controlado; sin él el componente gestiona el suyo (pendiente, subiendo, subido, error)." },
|
|
1538
|
+
{ name: "onStatusChange", type: "(status: UploadStatus) => void", description: "Se llama en cada cambio de estado de subida." },
|
|
1539
|
+
{ name: "showStatusBadge", type: "boolean", default: "true", description: "Muestra el badge de estado sobre la previsualización." },
|
|
1540
|
+
{ name: "initialPreviewUrl", type: "string | null", description: "Previsualización inicial; si existe se asume el estado subido." },
|
|
1541
|
+
{ name: "view", type: '"drop" | "button"', default: '"drop"', description: "Presentación: zona de drop inline o botón que abre la zona en un modal." },
|
|
1542
|
+
{ name: "buttonLabel", type: "string", default: '"Subir archivo"', description: "Etiqueta del botón cuando view es button y aún no hay archivo." },
|
|
1543
|
+
{ name: "modalTitle", type: "string", description: "Título del modal; si falta usa buttonLabel." },
|
|
1544
|
+
{ name: "contentClassName", type: "string", description: "Clases adicionales del área de previsualización." },
|
|
1545
|
+
{ name: "containerClassName", type: "string", description: "Clases adicionales del contenedor externo." },
|
|
1546
|
+
],
|
|
1547
|
+
notes: [
|
|
1548
|
+
"Flujo de tres pasos: al soltar el archivo queda pendiente, al confirmar pasa a subiendo y tras una simulación de 1.5 s a subido.",
|
|
1549
|
+
"Un archivo fuera de acceptedFileTypes muestra un alert y reinicia la selección.",
|
|
1550
|
+
"Con view button, el modal se cierra y la selección se limpia 900 ms después de una subida correcta, listo para el siguiente archivo.",
|
|
1551
|
+
"La vista previa embebida de PDF está deshabilitada: los PDF muestran el icono de documento.",
|
|
1552
|
+
],
|
|
1553
|
+
}}
|
|
1234
1554
|
/>
|
|
1235
1555
|
);
|
|
1236
1556
|
};
|
|
@@ -669,6 +669,37 @@ export const FlexShowcase = () => {
|
|
|
669
669
|
title="ITFlex"
|
|
670
670
|
description={t("showcase.layoutPrimitives.flexDesc")}
|
|
671
671
|
code={buildCode()}
|
|
672
|
+
doc={{
|
|
673
|
+
summary:
|
|
674
|
+
"Contenedor flexbox completo: dirección, alineación, distribución, wrap y factores flex (grow/shrink/basis) en un solo componente.",
|
|
675
|
+
description:
|
|
676
|
+
"Es el primitivo de layout con más control; úsalo cuando ITStack no llegue (reparto por ejes, hijos que crecen o se encogen, wrap con alineación). La separación se mide en unidades de 0.25rem y se emite como `gap`.",
|
|
677
|
+
examples: [
|
|
678
|
+
'<ITFlex justify="between" align="center" gap={3}>\n <h2>Panel</h2>\n <ITButton label="Exportar" variant="outlined" />\n</ITFlex>',
|
|
679
|
+
'<ITFlex direction="column" gap={2} as="section">\n <header>Título</header>\n <main>Contenido</main>\n</ITFlex>',
|
|
680
|
+
'<ITFlex gap={2}>\n <ITFlex grow basis={56}>Contenido flexible</ITFlex>\n <span>Fijo</span>\n</ITFlex>',
|
|
681
|
+
],
|
|
682
|
+
props: [
|
|
683
|
+
{ name: "children", type: "ReactNode", description: "Elementos hijos del contenedor." },
|
|
684
|
+
{ name: "direction", type: '"row" | "column" | "row-reverse" | "column-reverse"', default: '"row"', description: "Dirección del eje principal." },
|
|
685
|
+
{ name: "align", type: '"start" | "end" | "center" | "stretch" | "baseline"', description: "Alineación en el eje transversal." },
|
|
686
|
+
{ name: "justify", type: '"start" | "end" | "center" | "between" | "around" | "evenly"', description: "Reparto de los hijos en el eje principal." },
|
|
687
|
+
{ name: "wrap", type: '"nowrap" | "wrap" | "wrap-reverse"', description: "Si los hijos saltan a la siguiente línea." },
|
|
688
|
+
{ name: "gap", type: "number", default: "0", description: "Separación entre hijos en unidades de 0.25rem." },
|
|
689
|
+
{ name: "grow", type: "boolean | number", description: "Factor flex-grow: `true` = 1, `false` = 0, un número se usa tal cual." },
|
|
690
|
+
{ name: "shrink", type: "boolean | number", description: "Factor flex-shrink: `true` = 1, `false` = 0, un número se usa tal cual." },
|
|
691
|
+
{ name: "basis", type: "string | number", description: "flex-basis: los números se multiplican por 0.25rem, los strings se usan tal cual." },
|
|
692
|
+
{ name: "className", type: "string", description: "Clases CSS adicionales." },
|
|
693
|
+
{ name: "style", type: "CSSProperties", description: "Estilos inline; se aplican después de gap/grow/shrink/basis, así que pueden sobrescribirlos." },
|
|
694
|
+
{ name: "as", type: "ElementType", default: '"div"', description: "Elemento HTML a renderizar en lugar del `div` por defecto." },
|
|
695
|
+
{ name: "onClick", type: "(e: React.MouseEvent) => void", description: "Handler de click del contenedor." },
|
|
696
|
+
],
|
|
697
|
+
notes: [
|
|
698
|
+
"gap, grow, shrink y basis se resuelven como estilos inline, no como clases Tailwind: el `style` propio tiene la última palabra.",
|
|
699
|
+
"align, justify y wrap no traen valor por defecto: si se omiten no se emite clase y valen los valores iniciales de CSS (stretch, flex-start, nowrap).",
|
|
700
|
+
"El contenedor siempre es `display: flex`; para una sola dirección con separación uniforme, ITStack es más corto.",
|
|
701
|
+
],
|
|
702
|
+
}}
|
|
672
703
|
demo={
|
|
673
704
|
<div className="w-full max-w-xl flex flex-col">
|
|
674
705
|
<div className="min-h-[360px]">
|
|
@@ -939,6 +970,37 @@ export const GridShowcase = () => {
|
|
|
939
970
|
title="ITGrid"
|
|
940
971
|
description={t("showcase.layoutPrimitives.gridDesc")}
|
|
941
972
|
code={buildCode()}
|
|
973
|
+
doc={{
|
|
974
|
+
summary:
|
|
975
|
+
"Sistema de grid CSS responsivo de 12 columnas con el patrón container/item: el contenedor fija columnas y gap, y cada item declara su span por breakpoint.",
|
|
976
|
+
description:
|
|
977
|
+
"Como `container` renderiza `display: grid` con `grid-cols-{columns}`; como `item` calcula el `col-span` base y los spans de sm/md/lg/xl. Si no se pasa ninguno de los dos, renderiza un `div` normal.",
|
|
978
|
+
examples: [
|
|
979
|
+
'<ITGrid container spacing={4}>\n <ITGrid item xs={12} md={6}>Sidebar</ITGrid>\n <ITGrid item xs={12} md={6}>Contenido</ITGrid>\n</ITGrid>',
|
|
980
|
+
'<ITGrid container columns={3} spacing={2}>\n {plans.map((plan) => (\n <ITGrid key={plan.id} item>{plan.name}</ITGrid>\n ))}\n</ITGrid>',
|
|
981
|
+
'<ITGrid container spacing={4} as="section">\n <ITGrid item xs={12} lg={8}>Gráfico</ITGrid>\n <ITGrid item xs={12} lg={4}>Actividad</ITGrid>\n</ITGrid>',
|
|
982
|
+
],
|
|
983
|
+
props: [
|
|
984
|
+
{ name: "children", type: "ReactNode", description: "Elementos hijos." },
|
|
985
|
+
{ name: "container", type: "boolean", description: "Renderiza el padre: `display: grid` + `grid-cols-{columns}`." },
|
|
986
|
+
{ name: "item", type: "boolean", description: "Renderiza un hijo con `col-span` según los breakpoints. Si `container` también está activo, gana `container`." },
|
|
987
|
+
{ name: "spacing", type: "number", default: "0", description: "Gap entre hijos en unidades de 0.25rem; solo aplica con `container` y si es mayor que 0." },
|
|
988
|
+
{ name: "columns", type: "number", default: "12", description: "Columnas del contenedor (se acota a 1–12); también limita el span de los items." },
|
|
989
|
+
{ name: "xs", type: "number", description: "Span en el breakpoint base; si falta se usa `sm` y, si tampoco hay, el item ocupa todo (`col-span-full`)." },
|
|
990
|
+
{ name: "sm", type: "number", description: "Span a partir del breakpoint `sm`." },
|
|
991
|
+
{ name: "md", type: "number", description: "Span a partir del breakpoint `md`." },
|
|
992
|
+
{ name: "lg", type: "number", description: "Span a partir del breakpoint `lg`." },
|
|
993
|
+
{ name: "xl", type: "number", description: "Span a partir del breakpoint `xl`." },
|
|
994
|
+
{ name: "className", type: "string", description: "Clases CSS adicionales." },
|
|
995
|
+
{ name: "style", type: "CSSProperties", description: "Estilos inline." },
|
|
996
|
+
{ name: "as", type: "ElementType", default: '"div"', description: "Elemento HTML a renderizar en lugar del `div` por defecto." },
|
|
997
|
+
],
|
|
998
|
+
notes: [
|
|
999
|
+
"El span base es `xs ?? sm` y, si no se define ninguno, cae en `col-span-full` (las 12 columnas).",
|
|
1000
|
+
"Los spans se redondean y se acotan entre 1 y `columns`: con 3 columnas, `md={99}` acaba en `md:col-span-3`.",
|
|
1001
|
+
"Tailwind no ve clases construidas en runtime: `grid-cols-{n}` y `col-span-{n}` están en el safelist `@source inline(...)` de src/index.css.",
|
|
1002
|
+
],
|
|
1003
|
+
}}
|
|
942
1004
|
demo={
|
|
943
1005
|
<div className="w-full max-w-xl flex flex-col">
|
|
944
1006
|
<div className="min-h-[360px]">
|
|
@@ -1351,6 +1413,30 @@ export const GridShowcase = () => {
|
|
|
1351
1413
|
title={t("showcase.layoutPrimitives.screensTitle")}
|
|
1352
1414
|
description={t("showcase.layoutPrimitives.screensSubtitle")}
|
|
1353
1415
|
code={code}
|
|
1416
|
+
doc={{
|
|
1417
|
+
summary:
|
|
1418
|
+
"Receta de pantalla: un dashboard de una columna que compone ITStack, ITFlex, ITGrid, ITStatCard, ITAvatar e ITButton para armar cabecera, KPIs y bloque de gráfico + actividad.",
|
|
1419
|
+
description:
|
|
1420
|
+
"No es un componente de la librería, sino un patrón de composición: primero la cabecera con su acción, después la fila de métricas y por último el contenido a dos columnas, apilado en móvil.",
|
|
1421
|
+
examples: [
|
|
1422
|
+
'<ITGrid container spacing={3}>\n <ITGrid item xs={12} sm={6} lg={3}>\n <ITStatCard label="Usuarios activos" value="1,245" trend="+12%" trendDirection="up" />\n </ITGrid>\n</ITGrid>',
|
|
1423
|
+
'<ITFlex gap={3} align="center">\n <ITAvatar initials="AL" size="sm" color="bg-primary-500" />\n <p>Ana López creó un reporte</p>\n</ITFlex>',
|
|
1424
|
+
],
|
|
1425
|
+
props: [
|
|
1426
|
+
{ name: "ITStack", type: "componente", description: "Columna raíz (`spacing={6}`) que separa cabecera, métricas y gráficos." },
|
|
1427
|
+
{ name: "ITFlex", type: "componente", description: "Cabecera con `justify=\"between\" align=\"center\"` y cada fila de actividad con `gap={3} align=\"center\"`." },
|
|
1428
|
+
{ name: "ITGrid (container)", type: "componente", description: "Rejilla de 12 columnas (`spacing={3}`) para la fila de KPIs y para el bloque de gráfico/actividad." },
|
|
1429
|
+
{ name: "ITGrid (item)", type: "componente", description: "Reparto responsive: `xs={12} sm={6} lg={3}` en las métricas y `xs={12} lg={8}` / `lg={4}` en el contenido." },
|
|
1430
|
+
{ name: "ITStatCard", type: "componente", description: "Cada KPI, con `label`, `value`, `trend`, `trendDirection` y `color` para el fondo." },
|
|
1431
|
+
{ name: "ITAvatar", type: "componente", description: "Iniciales del usuario en la actividad reciente, con `size=\"sm\"` y `color`." },
|
|
1432
|
+
{ name: "ITButton", type: "componente", description: "Acción de la cabecera (“Exportar”), con `variant=\"outlined\" size=\"sm\"`." },
|
|
1433
|
+
],
|
|
1434
|
+
notes: [
|
|
1435
|
+
"Es una receta, no un componente: no tiene props propias; en la tabla se documentan las piezas que la componen.",
|
|
1436
|
+
"Criterio de composición: cabecera → KPIs en 4 columnas (`lg={3}`) → contenido 8/4 (`lg={8}` + `lg={4}`); todo a ancho completo por debajo de `sm` y `lg`.",
|
|
1437
|
+
"El demo usa `demoMinAncho={780}` y textos de `useITTranslation()`; los colores salen de tokens (`bg-primary-500`), no de valores sueltos.",
|
|
1438
|
+
],
|
|
1439
|
+
}}
|
|
1354
1440
|
demo={
|
|
1355
1441
|
<div className="w-full">
|
|
1356
1442
|
<p className="mb-4 font-mono text-[11px] uppercase tracking-[0.14em] text-slate-500 dark:text-slate-400">
|
|
@@ -1462,6 +1548,30 @@ export const ScreenFormShowcase = () => {
|
|
|
1462
1548
|
title={t("showcase.layoutPrimitives.screensTitle")}
|
|
1463
1549
|
description={t("showcase.layoutPrimitives.screensSubtitle")}
|
|
1464
1550
|
code={formCode}
|
|
1551
|
+
doc={{
|
|
1552
|
+
summary:
|
|
1553
|
+
"Receta de pantalla: un formulario responsive que compone ITStack, ITGrid, ITInput, ITSelect, ITFlex e ITButton para armar encabezado, campos y barra de acciones.",
|
|
1554
|
+
description:
|
|
1555
|
+
"Tampoco es un componente: es el patrón de un formulario controlado dentro de una tarjeta. La rejilla de 12 columnas coloca los campos cortos en pareja y los largos a fila completa.",
|
|
1556
|
+
examples: [
|
|
1557
|
+
'<ITGrid container spacing={4}>\n <ITGrid item xs={12} md={6}>\n <ITInput name="nombre" label="Nombre" onChange={() => {}} />\n </ITGrid>\n <ITGrid item xs={12}>\n <ITInput name="email" label="Email" onChange={() => {}} />\n </ITGrid>\n</ITGrid>',
|
|
1558
|
+
'<ITFlex justify="end" gap={3}>\n <ITButton label="Cancelar" variant="outlined" size="sm" />\n <ITButton label="Guardar" size="sm" />\n</ITFlex>',
|
|
1559
|
+
],
|
|
1560
|
+
props: [
|
|
1561
|
+
{ name: "ITStack", type: "componente", description: "Columna raíz (`spacing={6}`) que separa el encabezado de la rejilla de campos." },
|
|
1562
|
+
{ name: "ITGrid (container)", type: "componente", description: "Rejilla de 12 columnas con `spacing={4}` para repartir los campos." },
|
|
1563
|
+
{ name: "ITGrid (item)", type: "componente", description: "`xs={12} md={6}` para los campos en pareja y `xs={12}` para los largos (email) y la fila de botones." },
|
|
1564
|
+
{ name: "ITInput", type: "componente", description: "Campos de texto controlados con `name`, `label`, `placeholder` y `onChange`." },
|
|
1565
|
+
{ name: "ITSelect", type: "componente", description: "Listas de opciones con `name`, `label` y `options` (`{ label, value }`)." },
|
|
1566
|
+
{ name: "ITFlex", type: "componente", description: "Barra de acciones alineada a la derecha (`justify=\"end\" gap={3}`)." },
|
|
1567
|
+
{ name: "ITButton", type: "componente", description: "“Cancelar” (`variant=\"outlined\"`) y “Guardar”; ambos con `size=\"sm\"`." },
|
|
1568
|
+
],
|
|
1569
|
+
notes: [
|
|
1570
|
+
"Es una receta de composición: las props de la tabla son las piezas que usa, no una API propia.",
|
|
1571
|
+
"Formulario controlado: cada ITInput/ITSelect recibe `name` y `onChange`; ITInput los exige, aunque en el ejemplo el handler sea un no-op.",
|
|
1572
|
+
"Mismo criterio responsive que el dashboard: 1 columna en móvil (`xs={12}`) y 2 a partir de `md` (`md={6}`), con las acciones a ancho completo al final.",
|
|
1573
|
+
],
|
|
1574
|
+
}}
|
|
1465
1575
|
demo={
|
|
1466
1576
|
<div className="w-full">
|
|
1467
1577
|
<p className="mb-4 font-mono text-[11px] uppercase tracking-[0.14em] text-slate-500 dark:text-slate-400">
|