Skip to content

Commit

Permalink
feat: break down JSX into multiple components
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremy0x committed May 4, 2023
1 parent ad59895 commit 41e98c9
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 226 deletions.
29 changes: 29 additions & 0 deletions src/components/DefinitionExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
interface DefinitionExamplesProps {
definition: string;
exampleUsage: string;
index: number;
}

const DefinitionExample = (props: DefinitionExamplesProps) => {
const { definition, exampleUsage, index } = props;

return (
<>
{/* definition */}
<p>
<span className='text-lg font-normal sm:text-xl'>
{index + 1}. {definition}
</span>
</p>

{/* example usage */}
{exampleUsage && (
<p className='text-base font-light text-gray-400 sm:text-lg'>
{exampleUsage}
</p>
)}
</>
);
};

export default DefinitionExample;
238 changes: 12 additions & 226 deletions src/components/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import 'animate.css';
import FormField from './FormField';
import { WordDetails } from './types';
import { ChangeEvent, FormEvent, MouseEventHandler, useState } from 'react';
import { ErrorStatus, LoadingStatus, NotFoundStatus } from './FetchStatus';
import RenderStatus from './RenderStatus';
import SearchResultsHeader from './SearchResultsHeader';
import SearchedWordDetails from './SearchedWordDetails';

/**
* A component for searching and displaying dictionary definitions for words.
Expand Down Expand Up @@ -77,6 +78,11 @@ const Main = (): JSX.Element => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};

/**
* Plays the audio associated with the clicked button element and sets the isPlaying state to true for 2 seconds.
* @param event The mouse event triggered by clicking the button element.
* @returns {void}
*/
const playPhoneticAudio: MouseEventHandler<HTMLButtonElement> = (
event: React.MouseEvent<HTMLButtonElement>
) => {
Expand All @@ -98,33 +104,6 @@ const Main = (): JSX.Element => {
}, 2000);
};

/**
* Returns a truncated version of a word if it exceeds 10 characters.
* @param {WordDetails} wordData - The word data object.
* @param {string} wordData.word - The word to be shortened.
* @returns {string} The formatted word.
*/
const handleWordLength = (wordData: WordDetails): string => {
return wordData.word
? wordData.word.length > 10
? `${wordData.word.slice(0, 10)}...`
: wordData.word
: '';
};

/**
* Returns a shortened version of a URL if it exceeds 40 characters.
* @param {WordDetails} wordData - The word data object.
* @returns {string} The formatted URL.
*/
const handleUrlLength = (wordData: WordDetails): string => {
return wordData.sourceUrls
? wordData.sourceUrls[0].length > 40
? `${wordData.sourceUrls[0].slice(0, 40)}...`
: wordData.sourceUrls[0]
: '';
};

/**
* Closes the alert that shows when a word pronunciation is unavailable
* @returns {void}
Expand All @@ -143,20 +122,14 @@ const Main = (): JSX.Element => {
onSubmit={handleSubmit}
/>

{status == 'fetching' && <LoadingStatus />}

{status == 'error' && <ErrorStatus />}
<RenderStatus status={status} userInput={userInput} />

{status == 'no definitions' && <NotFoundStatus userInput={userInput} />}

{/* start: search result */}
{status == 'definition found' && (
<div className='animate__animated animate__fadeIn mx-auto mt-16 flex w-full flex-col justify-center'>
{searchResult.map((wordData: WordDetails, index: number) => (
<div key={index} className='w-full'>
{index === 0 && (
<div className='flex w-full flex-col'>
{/* start: results header */}
<SearchResultsHeader
wordData={wordData}
isPlaying={isPlaying}
Expand All @@ -168,204 +141,17 @@ const Main = (): JSX.Element => {
wordData.phonetics[1]?.audio
}
/>
{/* end: results header */}

{/* start: word details */}
<div className='mx-2 sm:mx-4 lg:mx-8'>
<div className='flex flex-col border-r border-l border-b border-t border-gray-500 border-t-gray-400 border-opacity-50 md:flex-row'>
<div className='search-results-left'>
{wordData.meanings.map((meaning, index: number) => (
<div
key={index}
className='mb-16 flex flex-col border-b border-gray-700 sm:mb-0 sm:flex-row'
>
{/* part of speech */}
<div className='border-t-0 border-b border-t-gray-700 border-b-gray-400 border-opacity-50 py-4 pl-4 text-left text-xl font-light sm:w-40 sm:border-t sm:border-b-0 sm:pl-0 sm:pr-10 sm:text-right'>
{meaning.partOfSpeech}
</div>
<div className='w-full'>
{meaning.definitions.map(
(definition, index: number) => (
<div
key={index}
className='flex flex-col gap-2 border-r border-l border-t border-gray-700 border-opacity-50 p-4'
>
{/* definition */}
<p>
<span className='text-lg font-normal sm:text-xl'>
{index + 1}. {definition.definition}
</span>
</p>
{/* example usage */}
{definition.example && (
<p className='text-base font-light text-gray-400 sm:text-lg'>
{definition.example}
</p>
)}
{/* definition specific synonyms and antonyms */}
{definition.synonyms.length > 0 && (
// synonyms //
<div className='mt-2 flex flex-col gap-1 sm:flex-row sm:gap-2'>
<p className='font-light text-gray-400'>
synonyms
</p>
<p>
{definition.synonyms.map(
(synonym, index: number) => (
<span key={index}>
<span
className='cursor-pointer font-light text-blue-400 transition-all hover:text-white focus:text-gray-400'
onClick={() =>
handleWordClick(synonym)
}
>
{synonym}
</span>
{index !==
meaning.synonyms.length -
1 && <span>, </span>}
</span>
)
)}
</p>
</div>
)}
{definition.antonyms.length > 0 && (
// antonyms //
<div className='flex flex-col gap-1 sm:flex-row sm:gap-2'>
<p className='font-light text-gray-400'>
antonyms
</p>
<p>
{definition.antonyms.map(
(antonym, index: number) => (
<span key={index}>
<span
className='cursor-pointer font-light text-blue-400 transition-all hover:text-white focus:text-gray-400'
onClick={() =>
handleWordClick(antonym)
}
>
{antonym}
</span>
{index !==
meaning.antonyms.length -
1 && <span>, </span>}
</span>
)
)}
</p>
</div>
)}
</div>
)
)}
{/* start: part of speech specific synonyms and antonyms */}
{(meaning.synonyms.length > 0 ||
meaning.antonyms.length > 0) && (
<div className='flex w-full flex-col gap-4 border border-b-0 border-r-gray-700 border-l-gray-700 border-t-gray-500 border-opacity-50 p-4'>
{/* synonyms */}
{meaning.synonyms.length > 0 && (
<div>
<div className='flex flex-col gap-1 sm:flex-row sm:gap-2'>
<p className='font-light text-gray-400'>
synonyms
</p>
<p>
{meaning.synonyms.map(
(synonym, index: number) => (
<span key={index}>
<span
className='cursor-pointer font-light text-blue-400 transition-all hover:text-white focus:text-gray-400'
onClick={() =>
handleWordClick(synonym)
}
>
{synonym}
</span>
{index !==
meaning.synonyms.length -
1 && <span>, </span>}
</span>
)
)}
</p>
</div>
</div>
)}
{/* antonyms */}
{meaning.antonyms.length > 0 && (
<div>
<div className='flex flex-col gap-1 sm:flex-row sm:gap-2'>
<p className='font-light text-gray-400'>
antonyms
</p>
<p>
{meaning.antonyms.map(
(antonym, index: number) => (
<span key={index}>
<span
className='cursor-pointer font-light text-blue-400 transition-all hover:text-white focus:text-gray-400'
onClick={() =>
handleWordClick(antonym)
}
>
{antonym}
</span>
{index !==
meaning.antonyms.length -
1 && <span>, </span>}
</span>
)
)}
</p>
</div>
</div>
)}
</div>
)}
{/* end: part of speech specific synonyms and antonyms */}
</div>
</div>
))}
</div>

{/* start: source & license */}
<div className='search-results-right flex flex-col border-b border-gray-700'>
<div className='border-t-0 border-l border-r border-b border-gray-700 border-opacity-50 p-4 sm:border'>
<p className='mb-2 text-xl'>Source URL</p>
<a
href={wordData.sourceUrls[0]}
target='_blank'
rel='noopener noreferrer'
className='break-all font-light text-blue-400 transition-all hover:text-white focus:text-gray-400'
>
{handleUrlLength(wordData)}
</a>
</div>
<div className='border-b-0 border-l border-r border-gray-700 border-opacity-50 p-4 md:border-b'>
<p className='mb-2 text-xl'>License</p>
<a
href={wordData.license.url}
target='_blank'
rel='noopener noreferrer'
className='font-light text-blue-400 transition-all hover:text-white focus:text-gray-400'
>
{wordData.license.name}
</a>
</div>
</div>
{/* end: source & license */}
</div>
</div>
{/* end: word details */}
<SearchedWordDetails
wordData={wordData}
handleWordClick={handleWordClick}
/>
</div>
)}
</div>
))}
</div>
)}
{/* start: search result */}
</main>
);
};
Expand Down
19 changes: 19 additions & 0 deletions src/components/RenderStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ErrorStatus, LoadingStatus, NotFoundStatus } from './FetchStatus';

interface RenderStatusProps {
status: string;
userInput: string;
}

const RenderStatus = (props: RenderStatusProps) => {
const { status, userInput } = props;
return (
<>
{status == 'fetching' && <LoadingStatus />}
{status == 'error' && <ErrorStatus />}
{status == 'no definitions' && <NotFoundStatus userInput={userInput} />}
</>
);
};

export default RenderStatus;
Loading

0 comments on commit 41e98c9

Please sign in to comment.