You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							123 lines
						
					
					
						
							3.4 KiB
						
					
					
				
			
		
		
	
	
							123 lines
						
					
					
						
							3.4 KiB
						
					
					
				| 'use client';
 | |
| 
 | |
| import { useReducer } from 'react';
 | |
| import GraphemeSplitter from 'grapheme-splitter';
 | |
| import { Pause, Play } from 'lucide-react';
 | |
| import Typist from 'react-typist-component';
 | |
| import Balancer from 'react-wrap-balancer';
 | |
| 
 | |
| import { blogConfig } from '@/config';
 | |
| import { cn } from '@/lib/utils';
 | |
| 
 | |
| type TypingState = {
 | |
|   titleDone: boolean;
 | |
|   subtitleDone: boolean;
 | |
|   isPaused: boolean;
 | |
| };
 | |
| 
 | |
| type TypingAction =
 | |
|   | { type: 'togglePause' }
 | |
|   | { type: 'setDone'; payload: 'title' | 'subtitle' };
 | |
| 
 | |
| const reducer = (state: TypingState, action: TypingAction) => {
 | |
|   switch (action.type) {
 | |
|     case 'togglePause':
 | |
|       return {
 | |
|         ...state,
 | |
|         isPaused: !state.isPaused,
 | |
|       };
 | |
|     case 'setDone':
 | |
|       return {
 | |
|         ...state,
 | |
|         [`${action.payload}Done`]: true,
 | |
|       };
 | |
|   }
 | |
| };
 | |
| 
 | |
| const splitter = (str: string) => new GraphemeSplitter().splitGraphemes(str);
 | |
| 
 | |
| export function HeroSection() {
 | |
|   const [{ titleDone, subtitleDone, isPaused }, dispatch] = useReducer(
 | |
|     reducer,
 | |
|     {
 | |
|       titleDone: false,
 | |
|       subtitleDone: false,
 | |
|       isPaused: false,
 | |
|     },
 | |
|   );
 | |
| 
 | |
|   return (
 | |
|     <section
 | |
|       className={cn(
 | |
|         'flex flex-col items-center justify-center space-y-2',
 | |
|         'relative h-40 w-full rounded-md px-4 shadow-lg',
 | |
|         'bg-slate-300 dark:bg-slate-800/50',
 | |
|       )}
 | |
|     >
 | |
|       <Typist
 | |
|         typingDelay={100}
 | |
|         splitter={splitter}
 | |
|         pause={isPaused}
 | |
|         onTypingDone={() => dispatch({ type: 'setDone', payload: 'title' })}
 | |
|       >
 | |
|         <h1 className="block w-full text-center text-3xl font-bold text-slate-800 dark:text-rose-50 xs:text-4xl sm:text-5xl">
 | |
|           <Balancer>
 | |
|             Добро пожаловать
 | |
|             <span className="ml-2 inline-block origin-[70%_70%] animate-wave">
 | |
|               👋
 | |
|             </span>
 | |
|           </Balancer>
 | |
|         </h1>
 | |
|       </Typist>
 | |
|       <p className="text-center text-lg text-slate-800 dark:text-rose-50 xs:text-2xl">
 | |
|         {titleDone && (
 | |
|           <Typist
 | |
|             typingDelay={100}
 | |
|             startDelay={1000}
 | |
|             pause={isPaused}
 | |
|             onTypingDone={() => {
 | |
|               dispatch({ type: 'setDone', payload: 'subtitle' });
 | |
|             }}
 | |
|           >
 | |
|             Блог про {' '}
 | |
|           </Typist>
 | |
|         )}
 | |
|         {subtitleDone && (
 | |
|           <Typist typingDelay={100} backspaceDelay={75} pause={isPaused} loop>
 | |
|             {blogConfig.topics.map((topic) => (
 | |
|               <span key={topic} className="font-semibold">
 | |
|                 {topic}
 | |
|                 <Typist.Delay ms={1000} />
 | |
|                 <Typist.Backspace count={topic.length} />
 | |
|               </span>
 | |
|             ))}
 | |
|           </Typist>
 | |
|         )}
 | |
|       </p>
 | |
|       <button
 | |
|         className="absolute right-3 top-1"
 | |
|         onClick={() => dispatch({ type: 'togglePause' })}
 | |
|       >
 | |
|         {isPaused ? (
 | |
|           <Play
 | |
|             className={cn(
 | |
|               'h-4 w-4',
 | |
|               'text-slate-400/50 hover:text-accent',
 | |
|               'dark:text-rose-50/20 dark:hover:text-accent-dark',
 | |
|             )}
 | |
|             aria-label="Запустить анимацию"
 | |
|           />
 | |
|         ) : (
 | |
|           <Pause
 | |
|             className={cn(
 | |
|               'h-4 w-4',
 | |
|               'text-slate-400/50 hover:text-accent',
 | |
|               'dark:text-rose-50/20 dark:hover:text-accent-dark',
 | |
|             )}
 | |
|             aria-label="Остановить анимацию"
 | |
|           />
 | |
|         )}
 | |
|       </button>
 | |
|     </section>
 | |
|   );
 | |
| }
 | |
| 
 |