Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves the diagnosis flow UI (MBTI selection and baseline survey) by:
Changes:
- Implemented common background patterns in Tailwind v4 utilities (yellow + white dots for diagnostic pages, SVG + white dots for top page)
- Enhanced MBTI selection screen with tabbed interface, carousel navigation, confirmation popup, and larger character images
- Updated BaselineSurvey to align with backend specification using new
QuestionOptiontype structure - Migrated
<img>to Next.js<Image>components for performance optimization
Reviewed changes
Copilot reviewed 6 out of 26 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
frontend/src/features/diagnosis/types/index.ts |
Added QuestionOption type and finalized quiz questions with Japanese content |
frontend/src/features/diagnosis/components/MbtiSelect.tsx |
Complete UI overhaul with tabs, carousel, animations, and confirmation dialog |
frontend/src/features/diagnosis/components/BaselineSurvey.tsx |
Updated to use new QuestionOption type structure |
frontend/src/app/page.tsx |
Migrated to Next.js Image component and applied background pattern |
frontend/src/app/globals.css |
Added Tailwind v4 utilities for common background patterns |
frontend/src/app/diagnosis/page.tsx |
Applied background pattern utility |
frontend/public/images/StartButton.png |
New start button image asset |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <motion.button | ||
| key={type.code} | ||
| type="button" | ||
| onClick={() => setSelected(type.code)} | ||
| className="relative flex flex-1 basis-0 flex-col cursor-pointer items-center justify-center overflow-hidden rounded-4xl bg-transparent outline-none ring-0" | ||
| whileHover={{ | ||
| scale: 1.2, | ||
| y: -16, | ||
| transition: { duration: 0.2 }, | ||
| }} | ||
| whileTap={{ scale: 1.02 }} | ||
| style={{ boxShadow: 'none' }} | ||
| > | ||
| <div className="relative flex min-h-0 flex-1 items-center justify-center w-full"> | ||
| <Image | ||
| src={`/images/mbti/${type.code}.png`} | ||
| alt={type.name} | ||
| width={176} | ||
| height={176} | ||
| className="max-h-44 w-auto border-0 object-contain outline-none" | ||
| /> | ||
| <div | ||
| className={`absolute bottom-0 left-0 right-0 ${GROUP_OVERLAY_COLORS[groupIndex]} py-1.5 px-2 text-center`} | ||
| > | ||
| <p className="text-sm font-bold text-white"> | ||
| {type.code} / {type.name} | ||
| </p> | ||
| </div> | ||
| </div> | ||
| </motion.button> |
There was a problem hiding this comment.
The motion.button elements for MBTI type selection should have accessible labels. Each button should include an aria-label describing what it does, such as aria-label={${type.code} ${type.name}を選択}. This helps screen reader users understand what each button represents.
| <motion.button | ||
| key={group} | ||
| type="button" | ||
| onClick={() => handleTabClick(index)} | ||
| className={`relative z-10 cursor-pointer rounded-t-lg border-4 border-b-0 border-gray-800 px-4 py-2 text-base font-semibold text-gray-800 ${ | ||
| GROUP_COLORS[index] | ||
| } ${groupIndex === index ? 'mb-[-4px]' : ''}`} | ||
| animate={{ | ||
| y: groupIndex === index ? 2 : 0, | ||
| boxShadow: | ||
| groupIndex === index | ||
| ? 'inset 0 3px 6px rgba(0,0,0,0.12)' | ||
| : 'none', | ||
| }} | ||
| transition={{ duration: 0.2 }} | ||
| > | ||
| {group} | ||
| </motion.button> |
There was a problem hiding this comment.
The tab buttons should have aria-selected attributes to indicate which tab is currently active. Add aria-selected={groupIndex === index} to improve accessibility for screen reader users navigating the tabs.
| {selectedType && ( | ||
| <div | ||
| className="fixed inset-0 z-50 flex items-center justify-center bg-black/50" | ||
| role="dialog" | ||
| aria-modal="true" | ||
| aria-labelledby="mbti-confirm-title" | ||
| > | ||
| スキップ | ||
| </button> | ||
| </div> | ||
| <div | ||
| className={`mx-4 w-full max-w-md rounded-2xl border-4 border-gray-800 p-6 shadow-xl ${ | ||
| GROUP_COLORS[ | ||
| Math.max( | ||
| 0, | ||
| MBTI_GROUPS.findIndex((g) => g === selectedType.group) | ||
| ) | ||
| ] | ||
| }`} | ||
| > | ||
| <p | ||
| id="mbti-confirm-title" | ||
| className="text-center text-lg font-bold text-gray-900" | ||
| > | ||
| あなたのMBTIは | ||
| </p> | ||
| <div className="my-4 flex justify-center"> | ||
| <Image | ||
| src={`/images/mbti/${selectedType.code}.png`} | ||
| alt={selectedType.name} | ||
| width={128} | ||
| height={128} | ||
| className="h-32 w-auto border-0 object-contain outline-none" | ||
| /> | ||
| </div> | ||
| <p className="text-center text-xl font-bold text-gray-900"> | ||
| {selectedType.code} / {selectedType.name} | ||
| </p> | ||
| <div className="mt-6 flex gap-4"> | ||
| <button | ||
| type="button" | ||
| onClick={handleReselect} | ||
| className="flex-1 cursor-pointer rounded-lg border-4 border-gray-800 bg-white px-4 py-3 font-semibold text-gray-800 transition-all duration-200 hover:scale-105 hover:bg-gray-100 hover:shadow-md active:scale-95" | ||
| > | ||
| 選び直す | ||
| </button> | ||
| <button | ||
| type="button" | ||
| onClick={handleConfirm} | ||
| className="flex-1 cursor-pointer rounded-lg border-4 border-gray-800 bg-white px-4 py-3 font-semibold text-gray-800 transition-all duration-200 hover:scale-105 hover:bg-gray-100 hover:shadow-md active:scale-95" | ||
| > | ||
| 決定 | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
The confirmation dialog should implement keyboard trap (focus management) and allow closing with the ESC key for better accessibility. Consider using a focus trap library or implementing custom keyboard event handlers to prevent focus from leaving the modal when open, and add an onKeyDown handler to close the modal when ESC is pressed.
| <button | ||
| type="button" | ||
| onClick={handleArrowPrev} | ||
| aria-label="前のジャンル" | ||
| className="h-0 w-0 shrink-0 cursor-pointer border-y-18 border-r-24 border-l-0 border-y-transparent border-r-white transition hover:border-r-gray-200 hover:scale-110" | ||
| /> | ||
|
|
||
| <div className="relative min-h-52 overflow-visible"> | ||
| <AnimatePresence mode="wait" initial={false}> | ||
| <motion.div | ||
| key={groupIndex} | ||
| initial="enter" | ||
| animate="center" | ||
| exit="exit" | ||
| variants={contentVariants} | ||
| transition={{ duration: 0.25 }} | ||
| className="absolute inset-0 flex items-center justify-center gap-1" | ||
| > | ||
| {type.code} | ||
| <span className="ml-1 font-normal text-gray-500"> | ||
| {type.name} | ||
| </span> | ||
| </button> | ||
| ))} | ||
| <div | ||
| className="flex h-full w-full items-stretch justify-center gap-2 rounded-lg bg-white px-1 py-1" | ||
| style={{ | ||
| backgroundImage: `radial-gradient(circle, ${GROUP_COLOR_HEX[groupIndex]} 2px, transparent 2px)`, | ||
| backgroundSize: '20px 20px', | ||
| }} | ||
| > | ||
| {currentTypes.map((type) => ( | ||
| <motion.button | ||
| key={type.code} | ||
| type="button" | ||
| onClick={() => setSelected(type.code)} | ||
| className="relative flex flex-1 basis-0 flex-col cursor-pointer items-center justify-center overflow-hidden rounded-4xl bg-transparent outline-none ring-0" | ||
| whileHover={{ | ||
| scale: 1.2, | ||
| y: -16, | ||
| transition: { duration: 0.2 }, | ||
| }} | ||
| whileTap={{ scale: 1.02 }} | ||
| style={{ boxShadow: 'none' }} | ||
| > | ||
| <div className="relative flex min-h-0 flex-1 items-center justify-center w-full"> | ||
| <Image | ||
| src={`/images/mbti/${type.code}.png`} | ||
| alt={type.name} | ||
| width={176} | ||
| height={176} | ||
| className="max-h-44 w-auto border-0 object-contain outline-none" | ||
| /> | ||
| <div | ||
| className={`absolute bottom-0 left-0 right-0 ${GROUP_OVERLAY_COLORS[groupIndex]} py-1.5 px-2 text-center`} | ||
| > | ||
| <p className="text-sm font-bold text-white"> | ||
| {type.code} / {type.name} | ||
| </p> | ||
| </div> | ||
| </div> | ||
| </motion.button> | ||
| ))} | ||
| </div> | ||
| </motion.div> | ||
| </AnimatePresence> | ||
| </div> | ||
|
|
||
| <button | ||
| type="button" | ||
| onClick={handleArrowNext} | ||
| aria-label="次のジャンル" | ||
| className="h-0 w-0 shrink-0 cursor-pointer border-y-18 border-l-24 border-r-0 border-y-transparent border-l-white transition hover:border-l-gray-200 hover:scale-110" | ||
| /> |
There was a problem hiding this comment.
The arrow navigation buttons (triangles created with CSS borders) may not be accessible via keyboard navigation alone. While they have proper aria-label attributes, users navigating with keyboard should also be able to use left/right arrow keys to navigate between groups. Consider adding keyboard event handlers to support arrow key navigation.
| GROUP_COLORS[ | ||
| Math.max( | ||
| 0, | ||
| MBTI_GROUPS.findIndex((g) => g === selectedType.group) | ||
| ) | ||
| ] |
There was a problem hiding this comment.
The use of Math.max(0, MBTI_GROUPS.findIndex(...)) is defensive but may hide a potential bug. If selectedType.group doesn't match any MBTI_GROUPS entry, findIndex returns -1, and this code falls back to index 0. Since selectedType should always have a valid group matching one of MBTI_GROUPS, consider adding a runtime check or assertion to catch this case during development instead of silently defaulting to index 0.
This reverts commit 8be52f4.
診断UIの改善 (feature/diagnosi-ui)
概要
診断フロー(MBTI選択・簡易性格診断)のUIを整備し、バックエンド仕様に合わせた型定義とスタイルの共通化を行いました。
変更内容
背景の共通化
globals.cssに Tailwind v4 の@utilityで共通背景を定義bg-page-pattern: 黄色ベース + 白の水玉模様(診断ページなど)bg-top-pattern: SVG背景 + 白の水玉オーバーレイ(トップページ)MBTI選択画面(MbtiSelect)
簡易性格診断(BaselineSurvey)
QuestionOption:{ value: 'A'|'B'|'C'|'D', label: string }を追加baseline_answers形式での送信に対応その他
<img>を Next.js の<Image>に置き換え(LCP・帯域の最適化)