Skip to content

Commit

Permalink
Implement keyboard shortcuts as well as fixing button errors
Browse files Browse the repository at this point in the history
  • Loading branch information
markwitt1 committed Apr 22, 2024
1 parent b6a9d8c commit 70fcd64
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 42 deletions.
1 change: 1 addition & 0 deletions app/[chapter]/[subchapter]/quiz/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export default function QuizPage({
return (
<StartScreen
onClickStart={() => {
console.log('quiz_started');
posthog.capture('quiz_started', {
questionsCount: questions.length,
});
Expand Down
4 changes: 3 additions & 1 deletion components/button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { cn } from '@/lib/utils';

type ButtonProps = React.HTMLProps<HTMLButtonElement>;
type ButtonProps = React.ComponentPropsWithoutRef<'button'> & {
onClick?: () => void;
};

const Button = (props: ButtonProps) => {
return (
Expand Down
6 changes: 3 additions & 3 deletions components/faqAccordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function AccordionItem({ title, content, initiallyOpen = false }: any) {
<h2>
<button
type='button'
className='flex items-center justify-between w-full p-5 font-medium rtl:text-right text-gray-500 border border-b-0 border-gray-200 first:rounded-t-xl focus:ring-4 focus:ring-gray-200 hover:bg-gray-100 gap-3'
className='flex w-full items-center justify-between gap-3 border border-b-0 border-gray-200 p-5 font-medium text-gray-500 first:rounded-t-xl hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 rtl:text-right'
onClick={() => setIsOpen(!isOpen)}
aria-expanded={isOpen}
aria-controls={`accordion-body-${title
Expand All @@ -19,7 +19,7 @@ function AccordionItem({ title, content, initiallyOpen = false }: any) {
<span className='flex items-center'>{title}</span>
<svg
data-accordion-icon
className={`w-3 h-3 shrink-0 transition-transform ${
className={`h-3 w-3 shrink-0 transition-transform ${
isOpen ? 'rotate-180' : ''
}`}
aria-hidden='true'
Expand All @@ -38,7 +38,7 @@ function AccordionItem({ title, content, initiallyOpen = false }: any) {
</button>
</h2>
<div
className={`p-5 border border-b-0 border-gray-200 max-w-[1280px] mb-2 text-gray-500 ${
className={`mb-2 max-w-[1280px] border border-b-0 border-gray-200 p-5 text-gray-500 ${
isOpen ? '' : 'hidden'
}`}
>
Expand Down
14 changes: 11 additions & 3 deletions components/option.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import classNames from 'classnames';
import React from 'react';
import { useHotkeys } from 'react-hotkeys-hook';

export interface OptionProps extends React.HTMLProps<HTMLButtonElement> {
index?: number;
Expand All @@ -8,7 +9,8 @@ export interface OptionProps extends React.HTMLProps<HTMLButtonElement> {
isSelected: boolean;
isCorrect: boolean;
showSolution: boolean;
onClick: () => void;
onSelect: () => void;
keyboardShortcut: string;
}

const Option: React.FC<OptionProps> = ({
Expand All @@ -18,7 +20,8 @@ const Option: React.FC<OptionProps> = ({
isSelected,
isCorrect,
showSolution,
onClick,
onSelect,
keyboardShortcut,
...props
}) => {
const optionClasses = classNames(
Expand All @@ -34,11 +37,16 @@ const Option: React.FC<OptionProps> = ({
}
);

useHotkeys(keyboardShortcut, () => {
onSelect();
(document.activeElement as HTMLElement)?.blur();
});

return (
<button
onClick={onSelect}
{...props}
type='button'
onClick={onClick}
className={optionClasses}
>
<div className='flex items-center gap-3 align-middle'>
Expand Down
3 changes: 2 additions & 1 deletion components/question/optionsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ export const OptionsList = ({
return (
<Option
key={option}
keyboardShortcut={(optionIndex + 1).toString()}
isSelected={isSelected}
isCorrect={correctIndices.includes(optionIndex)}
showSolution={showSolution}
label={option}
disabled={showSolution}
onClick={() => onOptionClick(optionIndex)}
onSelect={() => onOptionClick(optionIndex)}
/>
);
})}
Expand Down
40 changes: 24 additions & 16 deletions components/question/question.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { SolutionDisplay } from './solutionDisplay';
import { shuffleOptionsWithCorrectIndices } from '@/lib/utils';
import NoSSR from '../NoSSR';
import { usePostHog } from 'posthog-js/react';
import { useHotkeys } from 'react-hotkeys-hook';
import { KeyboardKeys } from '../quiz/keyboardKeys';

type ExerciseProps = {
question: Question;
Expand Down Expand Up @@ -71,6 +73,25 @@ const QuestionComponent: React.FC<ExerciseProps> = ({

const posthog = usePostHog();

const handleAnswerCheck = () => {
const isFirstTry = retriesCounter === 0;
posthog.capture('question_answered', {
questionIndex: questionIndexToShow,
isCorrect,
isFirstTry,
selectedOptionIndices: Array.from(selectedOptionIndices),
correctIndices: shuffledQuestion.correctIndices,
retriesCount: retriesCounter,
maxRetries,
randomizeOptions,
});

onAnswerCheck(isCorrect, isFirstTry);
setRetriesCounter((prev) => prev + 1);
};

useHotkeys('enter', handleAnswerCheck);

return (
<div className='mb-6 max-w-7xl rounded-lg border border-gray-200 bg-white p-6'>
{questionIndexToShow !== undefined && (
Expand Down Expand Up @@ -122,28 +143,15 @@ const QuestionComponent: React.FC<ExerciseProps> = ({
<button
type='button'
disabled={selectedOptionIndices.size === 0}
onClick={() => {
const isFirstTry = retriesCounter === 0;
posthog.capture('question_answered', {
questionIndex: questionIndexToShow,
isCorrect,
isFirstTry,
selectedOptionIndices: Array.from(selectedOptionIndices),
correctIndices: shuffledQuestion.correctIndices,
retriesCount: retriesCounter,
maxRetries,
randomizeOptions,
});

onAnswerCheck(isCorrect, isFirstTry);
setRetriesCounter((prev) => prev + 1);
}}
onClick={() => handleAnswerCheck()}
className='mb-2 me-2 rounded-lg bg-green-600 px-5 py-2.5 text-sm font-semibold text-white hover:opacity-90 disabled:cursor-not-allowed disabled:bg-gray-200'
>
Check answer
</button>
)}

<KeyboardKeys questionsAmount={question.options.length} />

{nextQuestionButton}
</div>
</div>
Expand Down
20 changes: 10 additions & 10 deletions components/quiz/keyboardKeys.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
export function KeyboardKeys() {
export function KeyboardKeys({ questionsAmount }: { questionsAmount: number }) {
return (
<p className='text-gray-500 hidden lg:block'>
<p className='hidden text-sm text-gray-500 lg:block'>
You can use keys{' '}
<kbd className='px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg'>
<kbd className='rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5 text-xs font-semibold text-gray-800'>
1
</kbd>{' '}
to{' '}
<kbd className='px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg'>
7
<kbd className='rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5 text-xs font-semibold text-gray-800'>
{questionsAmount}
</kbd>
{' or '}
<kbd className='inline-flex items-center px-2 py-1.5 text-gray-800 bg-gray-100 border border-gray-200 rounded-lg'>
<kbd className='inline-flex items-center rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5 text-gray-800'>
<svg
className='w-2.5 h-2.5'
className='h-2.5 w-2.5'
aria-hidden='true'
xmlns='http://www.w3.org/2000/svg'
fill='currentColor'
Expand All @@ -22,9 +22,9 @@ export function KeyboardKeys() {
</svg>
<span className='sr-only'>Arrow key up</span>
</kbd>{' '}
<kbd className='inline-flex items-center px-2 py-1.5 text-gray-800 bg-gray-100 border border-gray-200 rounded-lg'>
<kbd className='inline-flex items-center rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5 text-gray-800'>
<svg
className='w-2.5 h-2.5'
className='h-2.5 w-2.5'
aria-hidden='true'
xmlns='http://www.w3.org/2000/svg'
fill='currentColor'
Expand All @@ -35,7 +35,7 @@ export function KeyboardKeys() {
<span className='sr-only'>Arrow key down</span>
</kbd>
{' + '}
<kbd className='px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg'>
<kbd className='rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5 text-xs font-semibold text-gray-800'>
Enter
</kbd>{' '}
to navigate the quiz.
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,19 @@
"next": "14.1.3",
"posthog-js": "^1.121.4",
"posthog-node": "^4.0.0",
"react": "^18",
"react": "^18.2.0",
"react-canvas-confetti": "^2.0.7",
"react-dom": "^18",
"react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.5.0",
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^15.0.2",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.0.1",
Expand Down
21 changes: 17 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 70fcd64

Please sign in to comment.