Subject bar under the header in Quizzes and Lessons, synced between tabs; bidi fix for Latin math in RTL text
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
BookOpen, Brain, Compass, Grid3x3, LineChart, Play, Repeat, Sigma, Sparkles, TrendingUp,
|
||||
BarChart3, BookOpen, Brain, Compass, Grid3x3, LineChart, Network, Play, Repeat, Sigma, Sparkles, TrendingUp,
|
||||
type LucideIcon,
|
||||
} from 'lucide-react';
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
* مجموعهی کوچک در باندل است تا کلِ کتابخانهی آیکن بار نشود.
|
||||
*/
|
||||
const ICONS: Record<string, LucideIcon> = {
|
||||
Grid3x3, TrendingUp, LineChart, Sigma, Compass, Repeat, BookOpen, Sparkles, Brain, Play,
|
||||
Grid3x3, TrendingUp, LineChart, Sigma, Compass, Repeat, BookOpen, Sparkles, Brain, Play, BarChart3, Network,
|
||||
};
|
||||
|
||||
const FALLBACK: LucideIcon[] = [Grid3x3, TrendingUp, LineChart, Sigma, Compass, Repeat, BookOpen, Sparkles, Brain];
|
||||
|
||||
55
src/components/SubjectBar.tsx
Normal file
55
src/components/SubjectBar.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useAppData } from '@/state/AppData';
|
||||
import { onWhite } from '@/lib/color';
|
||||
import { iconFor } from '@/components/NodeIcon';
|
||||
|
||||
/**
|
||||
* نوارِ نازکِ کتابها (ریاضی / هندسه / حسابان / آمار و احتمال / گسسته) زیرِ سربرگِ «آزمونها» و «درسها».
|
||||
* انتخاب در AppData نگه داشته میشود و هر دو تب از همان `home` میخوانند، پس جابهجایی در یکی در دیگری
|
||||
* هم دیده میشود. فقط وقتی پایه بیش از یک کتاب دارد نشان داده میشود؛ پایههای دیگر هیچ تغییری نمیبینند.
|
||||
*
|
||||
* دکمهها عرضِ نوار را پر میکنند (پایهی ۱۰ دو کتاب، ۱۱ و ۱۲ سه کتاب دارند)؛ اگر جا کم بود اسکرولِ افقی میشود.
|
||||
*
|
||||
* بیرونِ ناحیهی اسکرول است تا با پایینرفتنِ نقشه گم نشود و با سربرگِ چسبانِ فصلها همپوشانی نکند.
|
||||
*/
|
||||
export function SubjectBar() {
|
||||
const { home, subjectKey, selectSubject } = useAppData();
|
||||
const subjects = home?.subjects ?? [];
|
||||
const activeRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// کتابِ فعال در دید بماند وقتی پنج دکمه از عرضِ گوشی بیشتر است.
|
||||
activeRef.current?.scrollIntoView({ inline: 'center', block: 'nearest' });
|
||||
}, [subjectKey]);
|
||||
|
||||
if (subjects.length < 2) return null;
|
||||
|
||||
return (
|
||||
<nav aria-label="کتابها" className="flex-shrink-0 border-b border-line bg-white">
|
||||
<div role="tablist" className="no-scrollbar flex gap-2 overflow-x-auto px-4 py-2">
|
||||
{subjects.map((subject, index) => {
|
||||
const active = subject.key === subjectKey;
|
||||
const Icon = iconFor(subject.icon, index);
|
||||
const color = onWhite(subject.color || '#4C5FD5');
|
||||
return (
|
||||
<button
|
||||
key={subject.key}
|
||||
ref={active ? activeRef : undefined}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={active}
|
||||
onClick={() => !active && selectSubject(subject.key)}
|
||||
className={`flex h-10 min-w-fit flex-1 items-center justify-center gap-1.5 whitespace-nowrap rounded-full border px-3.5 text-[13px] font-black transition active:scale-95 ${
|
||||
active ? 'border-transparent text-white shadow-sm' : 'border-line bg-white text-body'
|
||||
}`}
|
||||
style={active ? { backgroundColor: color, boxShadow: `0 3px 0 ${color}66` } : undefined}
|
||||
>
|
||||
<Icon size={16} strokeWidth={2.4} style={active ? undefined : { color }} />
|
||||
{subject.title}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user