Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/documentation/Documentation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { routes } from './utils/routes'
import { Home, NoMatch } from './pages'
import { Layout } from './components'
import { Box } from '@chakra-ui/react'
import { FlashNotificationGlobal } from '@/organisms/Alerts/FlashNotification'

export const Documentation = (): JSX.Element => {
React.useEffect(() => {
Expand All @@ -17,7 +16,6 @@ export const Documentation = (): JSX.Element => {

return (
<Box w="100%" overflowX="hidden">
<FlashNotificationGlobal />
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
Expand Down
12 changes: 10 additions & 2 deletions src/documentation/components/FlashNotificationDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,24 @@ export default function FlashNotificationDemo({
state,
message,
maxContent,
width,
}: IFlashNotificationProps): JSX.Element {
const { show, active, config } = useFlashNotification({
state: state,
message: message,
maxContent: maxContent,
})
width,
}) as { show: boolean; active: () => void; config: IFlashNotificationProps }
return (
<Box>
<Button onClick={active}>{state}</Button>
<FlashNotification {...config} show={show} />
<FlashNotification
show={show}
state={config.state}
message={config.message}
maxContent={config.maxContent}
width={config.width}
/>
</Box>
)
}
57 changes: 41 additions & 16 deletions src/documentation/pages/Organisms/FlashNotification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,43 @@ export const ViewFlashNotification = (): JSX.Element => {
<i>(Ej: Problemas de conexión, fallos al guardar o cargar datos, etc.)</i>
</MyText>
<MyText>
Tiempo de permanencia en pantalla: Se recomienda <b>no exceder las 25 palabras</b> para
alertas flash para una correcta lectura de la información.
</MyText>
<MyText>
Según la cantidad de la palabras la duración será la siguiente:{' '}
<b>Tiempo de permanencia y auto-cerrado:</b>
<br />
La duración de la notificación depende de su estado y la longitud del mensaje (basado en una
velocidad de lectura de 150 palabras por minuto):
<li>
<b>De 1 a 5 palabras:</b> 3 segundos.
</li>{' '}
<b>Estado Success:</b> Se cierra automáticamente tras el tiempo calculado para su lectura
(mínimo 3 segundos).
</li>
<li>
<b>De 11 a 25 palabras:</b> 6 segundos.
</li>{' '}
<br />
<b>Otros Estados (Error, Warning, Info):</b> Permanecen visibles de forma persistente y
requieren que el usuario las cierre manualmente mediante la "X" integrada.
</li>
</MyText>

<MyTitle>Ancho del Contenido y Centrado</MyTitle>
<MyText>
Mediante la propiedad <b>maxContent</b>, la notificación tomará exactamente el ancho de su
contenido y se mantendrá perfectamente centrada en la pantalla. Alternativamente, puedes
pasar un ancho fijo mediante <b>width</b>.
</MyText>
<ListComponent>
<FlashNotificationDemo
maxContent
state="success"
message="Notificación ajustada al ancho del contenido"
/>
<FlashNotificationDemo
state="info"
message="Notificación con ancho fijo de 600px"
width="600px"
/>
</ListComponent>

<MyTitle>Implementación</MyTitle>
<MyText>
El componente de FlashNotification se implementa en conjunto con el hook
useFlashNotification
useFlashNotification:
</MyText>
<Code text="import { FlashNotification, useFlashNotification } from '@eclass/ui-kit'" />

Expand All @@ -40,15 +63,17 @@ export const ViewFlashNotification = (): JSX.Element => {
state="info"
message="<b>¡Grupo creado!</b><br />Tu grupo ha sido creado. Ahora puedes invitar a tus compañeros."
/>
<FlashNotificationDemo state="success" message="Mensaje de éxito" />
<FlashNotificationDemo state="error" message="Mensaje de error" />
<FlashNotificationDemo state="warning" message="Mensaje de advertencia" />
<FlashNotificationDemo state="success" message="Mensaje de éxito (Auto-cerrado)" />
<FlashNotificationDemo state="error" message="Mensaje de error (Persistente)" />
<FlashNotificationDemo state="warning" message="Mensaje de advertencia (Persistente)" />
</ListComponent>
<Code
text="// Se define la constante que llama a useNotificationFlash y contiene el estado y mensaje
const { show, active, config } = useFlashNotification({
state: 'info',
message: 'Mensaje informativo',
state: 'info',
message: 'Mensaje informativo',
maxContent: true, // Se ajusta al ancho del texto
width: '400px', // Opcional: anula maxContent si se provee
})
// Se pasa la función active al elemento que activará la notificación
<Button onClick={ () => {active()} } > {state} </Button>
Expand Down
6 changes: 4 additions & 2 deletions src/organisms/Alerts/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function Alert({
endTextLink,
onClickLink,
sx,
width,
}: IAlertProps): JSX.Element {
const [isMobile] = useMediaQuery('(max-width: 425px)')

Expand All @@ -64,8 +65,9 @@ export function Alert({
gap={!isFlash ? '16px' : undefined}
justifyContent={!isMobile ? 'space-between' : undefined}
margin={m}
width={maxContent ? 'max-content' : fullWidth ? '100%' : 'fit-content'}
maxWidth={fullWidth ? 'none' : '796px'}
mx={isFlash ? 'auto' : undefined}
width={width ?? (maxContent ? 'max-content' : fullWidth ? '100%' : 'fit-content')}
maxWidth={fullWidth ? 'none' : width ?? maxContent ? 'none' : '796px'}
p="1rem"
pr={canDismiss ? '1.75rem' : '1rem'}
position="relative"
Expand Down
54 changes: 25 additions & 29 deletions src/organisms/Alerts/FlashNotification.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useRef } from 'react'
import { Portal } from '@chakra-ui/react'
import { useCallback, useEffect } from 'react'
import { toast, Toaster } from 'react-hot-toast'

import { IFlashNotificationProps } from './types.d'
Expand All @@ -19,14 +20,14 @@ import { Alert } from './Alert'
* @example Componente FlashNotification recibiendo argumentos
* <FlashNotification {...config} show={show} />
*/

export function FlashNotification({
message,
state,
show,
maxContent,
}: IFlashNotificationProps): null {
const hasShownRef = useRef(false)

m,
width,
}: IFlashNotificationProps): JSX.Element {
const showToast = useCallback(() => {
toast(
(t) => (
Expand All @@ -35,42 +36,37 @@ export function FlashNotification({
state={state}
canDismiss
onClick={() => toast.dismiss(t.id)}
maxContent={maxContent}
width={width}
m={m}
>
{message}
</Alert>
),
{
duration: state === 'success' ? handleTime(message) : Infinity,
id: alertStates[state].id,
duration: handleTime(message),
}
)
}, [message, state, maxContent])
}, [message, state, width, m])

useEffect(() => {
if (show && !hasShownRef.current) {
if (show) {
showToast()
hasShownRef.current = true
}

if (!show) {
hasShownRef.current = false
}
}, [show, showToast])

return null
return (
<Portal>
<Toaster
position="top-center"
toastOptions={{
className: 'toastContainer',
style: {
background: 'transparent',
boxShadow: 'none',
},
}}
/>
</Portal>
)
}

export const FlashNotificationGlobal = (): JSX.Element => (
<Toaster
position="top-center"
toastOptions={{
className: 'toastContainer',
style: {
background: 'transparent',
boxShadow: 'none',
zIndex: 1500,
},
}}
/>
)
2 changes: 2 additions & 0 deletions src/organisms/Alerts/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export interface IAlertProps {
endTextLink?: string
onClickLink?: () => void
maxContent?: boolean
width?: string
sx?: CSSObject
}

Expand All @@ -76,4 +77,5 @@ export interface IFlashNotificationProps {
state: TState
show?: boolean
maxContent?: boolean
width?: string
}
25 changes: 11 additions & 14 deletions src/organisms/Alerts/utils/handleTime.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
/* Método que cuenta la cantidad de palabras del mensaje de alerta o notificación
y según eso calcula el tiempo que durará en pantalla */

export const handleTime = (message: string): number => {
// Se recibe el mensaje y se retorna la cantidad de palabras
function countWords(input: string): number {
const wordCount = input.match(/(\w+)/g)?.length ?? 0
return wordCount
}
// Por defecto la duración es de 3seg, si el mensaje tiene más de 5 palabras, se cambia a 6
let time = 3000
if (message && countWords(message) > 5) {
time = 6000
}
return time
if (!message) return 3000

const words = message.trim().split(/\s+/).length
const wordsPerMinute = 150
const baseMinutes = words / wordsPerMinute

// Convertimos a milisegundos: minutos * 60 segundos * 1000 ms
const totalMs = Math.round(baseMinutes * 60 * 1000)

// Aseguramos un mínimo de 3 segundos para mensajes muy cortos
return Math.max(3000, totalMs)
}
7 changes: 5 additions & 2 deletions src/organisms/Alerts/utils/useFlashNotification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const useFlashNotification = ({
state,
message,
maxContent,
width,
}: IFlashNotificationProps): any => {
// Estado que maneja si la notificación debe mostrarse.
const [show, setShow] = useState(false)
Expand All @@ -31,12 +32,13 @@ export const useFlashNotification = ({
useEffect(() => {
// Si la notificación se está mostrando, se determina el tiempo tras el cual se ocultará.
if (show) {
const duration = state === 'success' ? handleTime(message) : 1000
const timeOut = setTimeout(() => {
setShow(false)
}, handleTime(message))
}, duration)
return () => clearTimeout(timeOut)
}
}, [message, show])
}, [message, show, state])

// Función que activa la notificación
const active = (): any => {
Expand All @@ -53,6 +55,7 @@ export const useFlashNotification = ({
state,
message,
maxContent,
width,
},
}
}