/ concept-collection / seqlab
concept-collection / seqlab
seqlab / src / examples / ExamplesModal.tsx
60 lines · 2.1 KBCodeBlameHistory
78d04f1seqlab: write and view pulseq MRI sequences in the browserJeremy Magland 1// Categorized example gallery, shown as a modal. Groups examples by their
2// catalog category and lets the user load one (which populates the editor and
3// runs it).
4import { useEffect } from 'react'
5import { CATEGORY_ORDER, type Category } from './catalog.ts'
6import type { Example } from './index.ts'
8export interface ExamplesModalProps {
9 examples: Example[]
10 onPick: (id: string) => void
11 onClose: () => void
14export default function ExamplesModal({ examples, onPick, onClose }: ExamplesModalProps) {
15 useEffect(() => {
16 const onKey = (e: KeyboardEvent) => {
17 if (e.key === 'Escape') onClose()
18 }
19 window.addEventListener('keydown', onKey)
20 return () => window.removeEventListener('keydown', onKey)
21 }, [onClose])
23 const byCategory = new Map<Category, Example[]>()
24 for (const ex of examples) {
25 const list = byCategory.get(ex.category) ?? []
26 list.push(ex)
27 byCategory.set(ex.category, list)
28 }
30 return (
31 <div className="modal-backdrop" onClick={onClose}>
32 <div className="modal" onClick={(e) => e.stopPropagation()}>
33 <div className="modal-head">
34 <h2>Examples</h2>
35 <span className="count">{examples.length} sequences from pulseq&apos;s demoSeq</span>
36 <div style={{ flex: 1 }} />
37 <button onClick={onClose}>Close ✕</button>
38 </div>
39 <div className="modal-body">
40 {CATEGORY_ORDER.filter((c) => byCategory.has(c)).map((category) => (
41 <section key={category}>
42 <h3>{category}</h3>
43 <div className="example-grid">
44 {byCategory.get(category)!.map((ex) => (
45 <button key={ex.id} className="example-card" onClick={() => onPick(ex.id)}>
46 <div className="ex-top">
47 <b>{ex.name}</b>
48 <span className="ex-time">{ex.approx}</span>
49 </div>
50 <span className="ex-desc">{ex.description}</span>
51 </button>
52 ))}
53 </div>
54 </section>
55 ))}
56 </div>
57 </div>
58 </div>
59 )