Skip to content

Commit 71a8b10

Browse files
committed
✨ feat: restaurar barra de progresso de leitura com confetti e botão Run
- Adicionar barra de progresso de leitura que rastreia o scroll da página - Implementar celebração com confetti ao completar a leitura (95%+) - Restaurar botões Run e Refresh no CodePlayground - Instalar dependência canvas-confetti para efeitos visuais - Criar componente ReadingProgressBar com design responsivo - Integrar barra de progresso globalmente via Root component - Adicionar animação de pulso quando próximo ao final da página - Suportar tema claro e escuro na barra de progresso
1 parent 74fea59 commit 71a8b10

6 files changed

Lines changed: 190 additions & 0 deletions

File tree

package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@docusaurus/preset-classic": "^3.9.2",
2424
"@docusaurus/theme-mermaid": "^3.9.2",
2525
"@mdx-js/react": "^3.1.1",
26+
"canvas-confetti": "^1.9.4",
2627
"clsx": "^2.1.1",
2728
"prism-react-renderer": "^2.4.1",
2829
"react": "^18.3.1",

src/components/CodePlayground.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ export default function CodePlayground({
114114
showInlineErrors: true,
115115
showConsole: showConsole,
116116
showConsoleButton: showConsole,
117+
showRefreshButton: true,
118+
showRunButton: true,
117119
editorHeight: height,
118120
autorun: autorun,
119121
autoReload: true,
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import React, { useState, useEffect, useCallback, useRef } from 'react';
2+
import confetti from 'canvas-confetti';
3+
import styles from './styles.module.css';
4+
5+
/**
6+
* Barra de Progresso de Leitura
7+
*
8+
* Mostra o progresso de leitura da página atual e dispara confetti
9+
* quando o usuário completa a leitura (chega ao final da página).
10+
*/
11+
export default function ReadingProgressBar() {
12+
const [progress, setProgress] = useState(0);
13+
const hasCompletedRef = useRef(false);
14+
const confettiTimeoutRef = useRef(null);
15+
16+
const fireConfetti = useCallback(() => {
17+
const duration = 3000;
18+
const animationEnd = Date.now() + duration;
19+
const defaults = {
20+
startVelocity: 30,
21+
spread: 360,
22+
ticks: 60,
23+
zIndex: 10000,
24+
colors: ['#f7df1e', '#FFD700', '#FFA500', '#FF6347']
25+
};
26+
27+
function randomInRange(min, max) {
28+
return Math.random() * (max - min) + min;
29+
}
30+
31+
const interval = setInterval(function() {
32+
const timeLeft = animationEnd - Date.now();
33+
34+
if (timeLeft <= 0) {
35+
return clearInterval(interval);
36+
}
37+
38+
const particleCount = 50 * (timeLeft / duration);
39+
40+
confetti({
41+
...defaults,
42+
particleCount,
43+
origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 }
44+
});
45+
confetti({
46+
...defaults,
47+
particleCount,
48+
origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 }
49+
});
50+
}, 250);
51+
}, []);
52+
53+
const handleScroll = useCallback(() => {
54+
const windowHeight = window.innerHeight;
55+
const documentHeight = document.documentElement.scrollHeight;
56+
const scrollTop = window.scrollY || window.pageYOffset || document.documentElement.scrollTop;
57+
58+
// Calcular progresso
59+
const scrollableHeight = documentHeight - windowHeight;
60+
const scrollPercentage = scrollableHeight > 0
61+
? Math.min((scrollTop / scrollableHeight) * 100, 100)
62+
: 0;
63+
64+
setProgress(scrollPercentage);
65+
66+
// Disparar confetti quando completar a leitura (chegou a 95%+)
67+
if (scrollPercentage >= 95 && !hasCompletedRef.current) {
68+
hasCompletedRef.current = true;
69+
70+
// Adicionar pequeno delay para garantir que o usuário realmente chegou ao final
71+
if (confettiTimeoutRef.current) {
72+
clearTimeout(confettiTimeoutRef.current);
73+
}
74+
75+
confettiTimeoutRef.current = setTimeout(() => {
76+
fireConfetti();
77+
}, 300);
78+
}
79+
80+
// Reset se o usuário voltar para o topo
81+
if (scrollPercentage < 10) {
82+
hasCompletedRef.current = false;
83+
}
84+
}, [fireConfetti]);
85+
86+
useEffect(() => {
87+
// Adicionar listener de scroll
88+
window.addEventListener('scroll', handleScroll, { passive: true });
89+
90+
// Calcular progresso inicial
91+
handleScroll();
92+
93+
// Cleanup
94+
return () => {
95+
window.removeEventListener('scroll', handleScroll);
96+
if (confettiTimeoutRef.current) {
97+
clearTimeout(confettiTimeoutRef.current);
98+
}
99+
};
100+
}, [handleScroll]);
101+
102+
// Reset quando mudar de página
103+
useEffect(() => {
104+
hasCompletedRef.current = false;
105+
setProgress(0);
106+
}, []);
107+
108+
return (
109+
<div className={styles.progressBarContainer}>
110+
<div
111+
className={styles.progressBar}
112+
style={{ width: `${progress}%` }}
113+
role="progressbar"
114+
aria-valuenow={Math.round(progress)}
115+
aria-valuemin="0"
116+
aria-valuemax="100"
117+
aria-label={`Progresso de leitura: ${Math.round(progress)}%`}
118+
/>
119+
</div>
120+
);
121+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
.progressBarContainer {
2+
position: fixed;
3+
top: 60px; /* Abaixo da navbar do Docusaurus */
4+
left: 0;
5+
width: 100%;
6+
height: 4px;
7+
background-color: rgba(0, 0, 0, 0.1);
8+
z-index: 9999;
9+
transition: opacity 0.3s ease;
10+
}
11+
12+
html[data-theme='dark'] .progressBarContainer {
13+
background-color: rgba(255, 255, 255, 0.1);
14+
}
15+
16+
.progressBar {
17+
height: 100%;
18+
background: linear-gradient(90deg, #f7df1e 0%, #ffa500 50%, #ff6347 100%);
19+
transition: width 0.2s ease-out;
20+
box-shadow: 0 0 10px rgba(247, 223, 30, 0.5);
21+
}
22+
23+
html[data-theme='dark'] .progressBar {
24+
box-shadow: 0 0 10px rgba(247, 223, 30, 0.7);
25+
}
26+
27+
/* Animação de pulso quando chegando perto do final */
28+
@keyframes pulse {
29+
0%, 100% {
30+
box-shadow: 0 0 10px rgba(247, 223, 30, 0.5);
31+
}
32+
50% {
33+
box-shadow: 0 0 20px rgba(247, 223, 30, 0.8);
34+
}
35+
}
36+
37+
.progressBar[style*="width: 9"],
38+
.progressBar[style*="width: 100"] {
39+
animation: pulse 1.5s ease-in-out infinite;
40+
}

src/theme/Root.jsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react';
2+
import ReadingProgressBar from '@site/src/components/ReadingProgressBar';
3+
4+
/**
5+
* Root component wrapper
6+
* Adiciona a barra de progresso de leitura em todas as páginas
7+
*/
8+
export default function Root({ children }) {
9+
return (
10+
<>
11+
<ReadingProgressBar />
12+
{children}
13+
</>
14+
);
15+
}

0 commit comments

Comments
 (0)