diff --git a/views/interactivity/src/App.tsx b/views/interactivity/src/App.tsx index 5855c4b..bf08aca 100644 --- a/views/interactivity/src/App.tsx +++ b/views/interactivity/src/App.tsx @@ -2,12 +2,52 @@ import React, { useState, useEffect } from 'react'; import './App.css'; import { DictionaryApi } from './components/api/dictionary'; import SearchBox from './components/search-box/SearchBox'; +import { SearchResultView } from './components/search-result-view/SearchResultView'; import { DictionaryEntry } from './components/types'; function App() { - const [bearerToken, setBearerToken] = useState(null); + const [bearerToken, setBearerToken] = useState('bearer-token'); // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [response, setResponse] = useState(null); + const [response, setResponse] = useState({ + dictionaryWord: { + name: 'hello', + entry: { + ipaListings: { + uk: [ + { + category: '', + ipa: '/heˈləʊ/', + audio: + 'https://dictionary.cambridge.org/media/english/uk_pron/u/ukh/ukhef/ukheft_029.mp3', + }, + ], + us: [ + { + category: '', + ipa: '/heˈloʊ/', + audio: + 'https://dictionary.cambridge.org/media/english/us_pron/h/hel/hello/hello.mp3', + }, + ], + }, + meanings: [ + { + categories: 'exclamation, noun', + entries: [ + { + meaning: 'used when meeting or greeting someone:', + examples: ["Hello, Paul. I haven't seen you for ages."], + }, + ], + }, + ], + }, + }, + accessSummary: { + totalAccess: 1, + lastAccessAt: new Date('2024-01-01T00:00:00.000Z'), + }, + }); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [errorMessage, setErrorMessage] = useState(null); @@ -24,11 +64,14 @@ function App() { const dictionaryApi = new DictionaryApi({ token: bearerToken }); return ( - + <> + + {response && } + ); } diff --git a/views/interactivity/src/components/api/dictionary.validator.ts b/views/interactivity/src/components/api/dictionary.validator.ts index fa0ec51..3b7cb7e 100644 --- a/views/interactivity/src/components/api/dictionary.validator.ts +++ b/views/interactivity/src/components/api/dictionary.validator.ts @@ -11,18 +11,18 @@ export const responseSchema = object({ }).nullable(), }); -const ipaListingSchema = object({ +export const ipaListingSchema = object({ category: string().nonNullable().defined(), ipa: string().required(), audio: string().url().required(), }); -const meaningEntrySchema = object({ +export const meaningEntrySchema = object({ meaning: string().required(), examples: array().of(string()).required(), }); -const meaningSchema = object({ +export const meaningSchema = object({ categories: string().required(), entries: array().of(meaningEntrySchema).required(), }); diff --git a/views/interactivity/src/components/general/Accordion.css b/views/interactivity/src/components/general/Accordion.css new file mode 100644 index 0000000..955783a --- /dev/null +++ b/views/interactivity/src/components/general/Accordion.css @@ -0,0 +1,55 @@ +.accordion > input[type='checkbox'] { + position: absolute; + left: -100vw; +} +.accordion .content { + overflow-y: hidden; + height: 0; + transition: height 0.3s ease; +} +.accordion > input[type='checkbox']:checked ~ .content { + height: auto; + overflow: visible; +} +.accordion label { + display: block; +} +.accordion { + margin-bottom: 1em; +} +.accordion > input[type='checkbox']:checked ~ .content { + padding: 1em; + border: 1px solid #e8e8e8; + border-top: 0; +} +.accordion .handle { + margin: 0; + font-size: 1.125em; + line-height: 1.2em; +} +.accordion label { + color: #333; + cursor: pointer; + font-weight: normal; + padding: 1em; + background: #e8e8e8; +} +.accordion label:hover, +.accordion label:focus { + background: #d8d8d8; +} +.accordion .handle label:before { + font-family: 'fontawesome'; + content: '\f054'; + display: inline-block; + margin-right: 0.625em; + font-size: 0.58em; + line-height: 1.556em; + vertical-align: middle; +} +.accordion > input[type='checkbox']:checked ~ .handle label:before { + content: '\f078'; +} +.accordion { + width: 100%; +} diff --git a/views/interactivity/src/components/general/Accordion.test.tsx b/views/interactivity/src/components/general/Accordion.test.tsx new file mode 100644 index 0000000..642429e --- /dev/null +++ b/views/interactivity/src/components/general/Accordion.test.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { Accordion } from './Accordion'; + +test('accordion should have a label and content block', async () => { + const id = 'accordion-id'; + const label = 'label'; + const Children = () =>
Hello
; + render( + + + , + ); + + expect(screen.queryByText(label)).toBeInTheDocument(); + expect(screen.queryByText('Hello')).toBeInTheDocument(); +}); diff --git a/views/interactivity/src/components/general/Accordion.tsx b/views/interactivity/src/components/general/Accordion.tsx new file mode 100644 index 0000000..0039b69 --- /dev/null +++ b/views/interactivity/src/components/general/Accordion.tsx @@ -0,0 +1,22 @@ +import React, { ReactNode } from 'react'; +import './Accordion.css'; + +export function Accordion({ + id, + label, + children, +}: { + id: string; + label: string; + children: ReactNode; +}) { + return ( +
+ +

+ +

+
{children}
+
+ ); +} diff --git a/views/interactivity/src/components/general/AudioPlayer.test.tsx b/views/interactivity/src/components/general/AudioPlayer.test.tsx new file mode 100644 index 0000000..3fde939 --- /dev/null +++ b/views/interactivity/src/components/general/AudioPlayer.test.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { AudioPlayer } from './AudioPlayer'; + +test('audio player should play audio when button is clicked', async () => { + const id = 1; + const src = 'https://domain.com/media/1.mp3'; + + render(); + + const audioPlayer: HTMLAudioElement = screen.getByTestId( + `audio-player-${id}`, + ); + jest.spyOn(audioPlayer, 'play').mockImplementation(() => Promise.resolve()); + + const button = screen.getByTestId(`audio-button-${id}`); + fireEvent.click(button); + + expect(audioPlayer.play).toHaveBeenCalled(); +}); diff --git a/views/interactivity/src/components/general/AudioPlayer.tsx b/views/interactivity/src/components/general/AudioPlayer.tsx new file mode 100644 index 0000000..f2b3f3c --- /dev/null +++ b/views/interactivity/src/components/general/AudioPlayer.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Icon } from './Icon'; + +export function AudioPlayer({ id, src }: { id: number; src: string }) { + const audioRef = React.useRef(null); + + const handlePlay = () => { + if (audioRef.current) { + audioRef.current.play(); + } + }; + + return ( + <> +
+ +
+