Claude commited on
Commit
5607582
·
unverified ·
1 Parent(s): b14070b

feat(frontend): Sprint R2 — retro Home / Desktop

Browse files

Transform the Home page into a retro desktop environment:

- RetroMenuBar at top with IIIF Studio branding + search + admin
- Dithered gray background (bg-retro-dither)
- Corpus list in a RetroWindow with selection highlighting (navy blue)
- Profile-based icon glyphs for each corpus type
- Manuscripts sub-window appears on corpus with multiple manuscripts
- Bottom dock with RetroIcon grid for quick corpus access
- Loading/error states displayed in centered RetroWindow dialogs

Restyle SearchBar: inset well input, navy-blue hover on results.
Restyle AdminNav: uses RetroButton component.
Remove RetroDemo page (no longer needed).

Build passes (tsc + vite).

https://claude.ai/code/session_01WWohTtw2CxGRawmpH1tyrY

frontend/src/App.tsx CHANGED
@@ -3,14 +3,12 @@ import Admin from './pages/Admin.tsx'
3
  import Editor from './pages/Editor.tsx'
4
  import Home from './pages/Home.tsx'
5
  import Reader from './pages/Reader.tsx'
6
- import RetroDemo from './pages/RetroDemo.tsx'
7
 
8
  type View =
9
  | { name: 'home' }
10
  | { name: 'reader'; manuscriptId: string; profileId: string }
11
  | { name: 'admin' }
12
  | { name: 'editor'; pageId: string }
13
- | { name: 'retro-demo' }
14
 
15
  export default function App() {
16
  const [view, setView] = useState<View>({ name: 'home' })
@@ -39,10 +37,6 @@ export default function App() {
39
  )
40
  }
41
 
42
- if (view.name === 'retro-demo') {
43
- return <RetroDemo onBack={() => setView({ name: 'home' })} />
44
- }
45
-
46
  return (
47
  <Home
48
  onOpenManuscript={(manuscriptId, profileId) =>
 
3
  import Editor from './pages/Editor.tsx'
4
  import Home from './pages/Home.tsx'
5
  import Reader from './pages/Reader.tsx'
 
6
 
7
  type View =
8
  | { name: 'home' }
9
  | { name: 'reader'; manuscriptId: string; profileId: string }
10
  | { name: 'admin' }
11
  | { name: 'editor'; pageId: string }
 
12
 
13
  export default function App() {
14
  const [view, setView] = useState<View>({ name: 'home' })
 
37
  )
38
  }
39
 
 
 
 
 
40
  return (
41
  <Home
42
  onOpenManuscript={(manuscriptId, profileId) =>
frontend/src/components/AdminNav.tsx CHANGED
@@ -1,14 +1,13 @@
 
 
1
  interface AdminNavProps {
2
  onClick: () => void
3
  }
4
 
5
  export default function AdminNav({ onClick }: AdminNavProps) {
6
  return (
7
- <button
8
- onClick={onClick}
9
- className="text-stone-400 hover:text-stone-100 text-sm transition-colors"
10
- >
11
- Administration
12
- </button>
13
  )
14
  }
 
1
+ import { RetroButton } from './retro'
2
+
3
  interface AdminNavProps {
4
  onClick: () => void
5
  }
6
 
7
  export default function AdminNav({ onClick }: AdminNavProps) {
8
  return (
9
+ <RetroButton size="sm" onClick={onClick}>
10
+ Admin
11
+ </RetroButton>
 
 
 
12
  )
13
  }
frontend/src/components/SearchBar.tsx CHANGED
@@ -41,7 +41,6 @@ export default function SearchBar({ onSelectResult }: Props) {
41
  }
42
  }, [query, runSearch])
43
 
44
- // Close dropdown on outside click
45
  useEffect(() => {
46
  const handler = (e: MouseEvent) => {
47
  if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
@@ -53,27 +52,45 @@ export default function SearchBar({ onSelectResult }: Props) {
53
  }, [])
54
 
55
  return (
56
- <div ref={containerRef} className="relative w-72">
57
  <div className="relative">
58
  <input
59
  type="search"
60
  value={query}
61
  onChange={(e) => setQuery(e.target.value)}
62
  onFocus={() => results.length > 0 && setOpen(true)}
63
- placeholder="Rechercher dans les manuscrits…"
64
- className="w-full bg-stone-800 text-stone-100 placeholder-stone-500 text-sm px-3 py-1.5 pr-8 rounded-md border border-stone-700 focus:outline-none focus:border-amber-500 focus:ring-1 focus:ring-amber-500"
 
 
 
 
 
 
 
65
  />
66
  {loading && (
67
- <span className="absolute right-2.5 top-1/2 -translate-y-1/2 text-stone-400 text-xs">
68
-
69
  </span>
70
  )}
71
  </div>
72
 
73
  {open && (
74
- <div className="absolute top-full left-0 right-0 mt-1 bg-white border border-stone-200 rounded-lg shadow-xl z-50 max-h-80 overflow-y-auto">
 
 
 
 
 
 
 
 
 
75
  {results.length === 0 ? (
76
- <div className="px-4 py-3 text-sm text-stone-400 italic">Aucun résultat.</div>
 
 
77
  ) : (
78
  <ul>
79
  {results.map((r) => (
@@ -83,17 +100,20 @@ export default function SearchBar({ onSelectResult }: Props) {
83
  setOpen(false)
84
  onSelectResult?.(r)
85
  }}
86
- className="w-full text-left px-4 py-3 hover:bg-amber-50 border-b border-stone-100 last:border-0 transition-colors"
 
 
 
 
 
87
  >
88
  <div className="flex items-center justify-between gap-2">
89
- <span className="font-medium text-stone-800 text-sm">
90
- {r.folio_label}
91
- </span>
92
- <span className="text-xs text-stone-400 shrink-0">
93
- score : {r.score}
94
  </span>
95
  </div>
96
- <div className="text-xs text-stone-500 mt-0.5 truncate">
97
  {r.excerpt}
98
  </div>
99
  </button>
 
41
  }
42
  }, [query, runSearch])
43
 
 
44
  useEffect(() => {
45
  const handler = (e: MouseEvent) => {
46
  if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
 
52
  }, [])
53
 
54
  return (
55
+ <div ref={containerRef} className="relative w-64">
56
  <div className="relative">
57
  <input
58
  type="search"
59
  value={query}
60
  onChange={(e) => setQuery(e.target.value)}
61
  onFocus={() => results.length > 0 && setOpen(true)}
62
+ placeholder="Rechercher..."
63
+ className="
64
+ w-full px-2 py-[2px]
65
+ text-retro-sm font-retro
66
+ bg-retro-white text-retro-black
67
+ border border-retro-black
68
+ shadow-retro-well
69
+ placeholder:text-retro-darkgray
70
+ "
71
  />
72
  {loading && (
73
+ <span className="absolute right-2 top-1/2 -translate-y-1/2 text-retro-darkgray text-retro-xs">
74
+ ...
75
  </span>
76
  )}
77
  </div>
78
 
79
  {open && (
80
+ <div
81
+ className="
82
+ absolute top-full left-0 right-0 mt-[2px]
83
+ bg-retro-white
84
+ border-retro border-retro-black
85
+ shadow-retro-lg
86
+ z-50 max-h-60
87
+ overflow-y-auto retro-scroll
88
+ "
89
+ >
90
  {results.length === 0 ? (
91
+ <div className="px-2 py-2 text-retro-xs text-retro-darkgray">
92
+ Aucun resultat.
93
+ </div>
94
  ) : (
95
  <ul>
96
  {results.map((r) => (
 
100
  setOpen(false)
101
  onSelectResult?.(r)
102
  }}
103
+ className="
104
+ w-full text-left px-2 py-[3px]
105
+ text-retro-sm font-retro
106
+ hover:bg-retro-select hover:text-retro-select-text
107
+ border-b border-retro-gray last:border-0
108
+ "
109
  >
110
  <div className="flex items-center justify-between gap-2">
111
+ <span className="font-bold">{r.folio_label}</span>
112
+ <span className="text-retro-xs opacity-60">
113
+ {r.score}
 
 
114
  </span>
115
  </div>
116
+ <div className="text-retro-xs truncate opacity-70">
117
  {r.excerpt}
118
  </div>
119
  </button>
frontend/src/pages/Home.tsx CHANGED
@@ -1,6 +1,7 @@
1
  import { useEffect, useState } from 'react'
2
  import AdminNav from '../components/AdminNav.tsx'
3
  import SearchBar from '../components/SearchBar.tsx'
 
4
  import {
5
  fetchCorpora,
6
  fetchManuscripts,
@@ -8,6 +9,14 @@ import {
8
  type Manuscript,
9
  } from '../lib/api.ts'
10
 
 
 
 
 
 
 
 
 
11
  interface Props {
12
  onOpenManuscript: (manuscriptId: string, profileId: string) => void
13
  onOpenPage?: (pageId: string) => void
@@ -20,6 +29,7 @@ export default function Home({ onOpenManuscript, onOpenPage, onAdmin }: Props) {
20
  const [error, setError] = useState<string | null>(null)
21
  const [manuscripts, setManuscripts] = useState<Record<string, Manuscript[]>>({})
22
  const [expanding, setExpanding] = useState<string | null>(null)
 
23
 
24
  useEffect(() => {
25
  fetchCorpora()
@@ -29,7 +39,8 @@ export default function Home({ onOpenManuscript, onOpenPage, onAdmin }: Props) {
29
  }, [])
30
 
31
  const handleCorpusClick = async (corpus: Corpus) => {
32
- // Si déjà chargé, naviguer directement si un seul manuscrit
 
33
  const cached = manuscripts[corpus.id]
34
  if (cached) {
35
  if (cached.length === 1) onOpenManuscript(cached[0].id, corpus.profile_id)
@@ -42,95 +53,171 @@ export default function Home({ onOpenManuscript, onOpenPage, onAdmin }: Props) {
42
  setManuscripts((prev) => ({ ...prev, [corpus.id]: ms }))
43
  if (ms.length === 1) onOpenManuscript(ms[0].id, corpus.profile_id)
44
  } catch {
45
- // Échec silencieux — la liste reste vide
46
  } finally {
47
  setExpanding(null)
48
  }
49
  }
50
 
 
51
  if (loading) {
52
  return (
53
- <div className="flex items-center justify-center h-screen text-stone-500">
54
- Chargement…
 
 
 
 
55
  </div>
56
  )
57
  }
58
 
 
59
  if (error) {
60
  return (
61
- <div className="p-8 text-red-600">
62
- Erreur : {error}
 
 
 
 
 
63
  </div>
64
  )
65
  }
66
 
 
 
67
  return (
68
- <div className="min-h-screen bg-stone-50">
69
- <header className="bg-stone-900 text-stone-100 px-8 py-6 flex items-start justify-between">
70
- <div>
71
- <h1 className="text-2xl font-semibold tracking-tight">IIIF Studio</h1>
72
- <p className="text-stone-400 text-sm mt-1">
73
- Plateforme de génération d'éditions savantes augmentées
74
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  </div>
76
- <div className="flex items-center gap-4">
77
- <SearchBar onSelectResult={onOpenPage ? (r) => onOpenPage(r.page_id) : undefined} />
78
- <AdminNav onClick={onAdmin} />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  </div>
80
- </header>
81
-
82
- <main className="max-w-3xl mx-auto py-10 px-8">
83
- <h2 className="text-sm font-semibold text-stone-500 uppercase tracking-wide mb-6">
84
- Corpus disponibles
85
- </h2>
86
-
87
- {corpora.length === 0 ? (
88
- <p className="text-stone-400 text-sm">
89
- Aucun corpus enregistré. Créez-en un via{' '}
90
- <code className="bg-stone-200 px-1 rounded text-xs">POST /api/v1/corpora</code>.
91
- </p>
92
- ) : (
93
- <ul className="space-y-3">
94
- {corpora.map((corpus) => (
95
- <li key={corpus.id}>
96
- <button
97
- onClick={() => void handleCorpusClick(corpus)}
98
- className="w-full text-left bg-white border border-stone-200 rounded-lg px-6 py-4 hover:border-stone-400 hover:shadow-sm transition-all"
99
- >
100
- <div className="font-medium text-stone-900">{corpus.title}</div>
101
- <div className="text-xs text-stone-400 mt-1">
102
- Profil : {corpus.profile_id} · Slug : {corpus.slug}
103
- </div>
104
- </button>
105
-
106
- {expanding === corpus.id && (
107
- <div className="mt-2 ml-4 text-xs text-stone-400">Chargement…</div>
108
- )}
109
-
110
- {manuscripts[corpus.id] && manuscripts[corpus.id].length > 1 && (
111
- <ul className="mt-2 ml-4 space-y-1">
112
- {manuscripts[corpus.id].map((ms) => (
113
- <li key={ms.id}>
114
- <button
115
- onClick={() => onOpenManuscript(ms.id, corpus.profile_id)}
116
- className="text-sm text-stone-600 hover:text-stone-900 hover:underline text-left"
117
- >
118
- {ms.title}
119
- {ms.total_pages > 0 && (
120
- <span className="text-stone-400 ml-1">
121
- ({ms.total_pages} pages)
122
- </span>
123
- )}
124
- </button>
125
- </li>
126
- ))}
127
- </ul>
128
- )}
129
- </li>
130
- ))}
131
- </ul>
132
- )}
133
- </main>
134
  </div>
135
  )
136
  }
 
1
  import { useEffect, useState } from 'react'
2
  import AdminNav from '../components/AdminNav.tsx'
3
  import SearchBar from '../components/SearchBar.tsx'
4
+ import { RetroMenuBar, RetroWindow, RetroIcon } from '../components/retro'
5
  import {
6
  fetchCorpora,
7
  fetchManuscripts,
 
9
  type Manuscript,
10
  } from '../lib/api.ts'
11
 
12
+ /** Map profile IDs to desktop icon glyphs */
13
+ const PROFILE_GLYPHS: Record<string, string> = {
14
+ 'medieval-illuminated': '\u{1F4DC}',
15
+ 'medieval-textual': '\u{1F4D6}',
16
+ 'early-modern-print': '\u{1F5A8}',
17
+ 'modern-handwritten': '\u{270D}',
18
+ }
19
+
20
  interface Props {
21
  onOpenManuscript: (manuscriptId: string, profileId: string) => void
22
  onOpenPage?: (pageId: string) => void
 
29
  const [error, setError] = useState<string | null>(null)
30
  const [manuscripts, setManuscripts] = useState<Record<string, Manuscript[]>>({})
31
  const [expanding, setExpanding] = useState<string | null>(null)
32
+ const [selectedCorpus, setSelectedCorpus] = useState<Corpus | null>(null)
33
 
34
  useEffect(() => {
35
  fetchCorpora()
 
39
  }, [])
40
 
41
  const handleCorpusClick = async (corpus: Corpus) => {
42
+ setSelectedCorpus(corpus)
43
+
44
  const cached = manuscripts[corpus.id]
45
  if (cached) {
46
  if (cached.length === 1) onOpenManuscript(cached[0].id, corpus.profile_id)
 
53
  setManuscripts((prev) => ({ ...prev, [corpus.id]: ms }))
54
  if (ms.length === 1) onOpenManuscript(ms[0].id, corpus.profile_id)
55
  } catch {
56
+ // silent
57
  } finally {
58
  setExpanding(null)
59
  }
60
  }
61
 
62
+ // ── Loading state ───────────────────────────────────────────────────
63
  if (loading) {
64
  return (
65
+ <div className="min-h-screen bg-retro-dither flex items-center justify-center">
66
+ <RetroWindow title="IIIF Studio" className="w-72">
67
+ <div className="p-4 text-retro-sm text-center">
68
+ Chargement...
69
+ </div>
70
+ </RetroWindow>
71
  </div>
72
  )
73
  }
74
 
75
+ // ── Error state ─────────────────────────────────────────────────────
76
  if (error) {
77
  return (
78
+ <div className="min-h-screen bg-retro-dither flex items-center justify-center">
79
+ <RetroWindow title="Erreur" className="w-80">
80
+ <div className="p-4 text-retro-sm">
81
+ <p className="font-bold mb-2">Erreur de connexion</p>
82
+ <p className="text-retro-xs">{error}</p>
83
+ </div>
84
+ </RetroWindow>
85
  </div>
86
  )
87
  }
88
 
89
+ const selectedMs = selectedCorpus ? manuscripts[selectedCorpus.id] : undefined
90
+
91
  return (
92
+ <div className="min-h-screen bg-retro-dither flex flex-col">
93
+ {/* ── Menu bar (top of screen) ─────────────────────────────── */}
94
+ <RetroMenuBar
95
+ items={[
96
+ { label: 'IIIF Studio' },
97
+ { label: 'Fichier' },
98
+ { label: 'Corpus' },
99
+ { label: 'Aide' },
100
+ ]}
101
+ right={
102
+ <div className="flex items-center gap-1">
103
+ <SearchBar onSelectResult={onOpenPage ? (r) => onOpenPage(r.page_id) : undefined} />
104
+ <AdminNav onClick={onAdmin} />
105
+ </div>
106
+ }
107
+ />
108
+
109
+ {/* ── Desktop area ─────────────────────────────────────────── */}
110
+ <div className="flex-1 flex flex-col">
111
+
112
+ {/* ── Corpus list window (center) ──────────────────────── */}
113
+ <div className="flex-1 flex items-start justify-center p-6 gap-4">
114
+ <RetroWindow
115
+ title="Corpus disponibles"
116
+ statusBar={`${corpora.length} corpus enregistre${corpora.length > 1 ? 's' : ''}`}
117
+ className="w-full max-w-2xl"
118
+ scrollable
119
+ >
120
+ {corpora.length === 0 ? (
121
+ <div className="p-4 text-retro-sm text-retro-darkgray">
122
+ Aucun corpus enregistre. Creez-en un via Administration.
123
+ </div>
124
+ ) : (
125
+ <div className="divide-y divide-retro-gray">
126
+ {corpora.map((corpus) => (
127
+ <div key={corpus.id}>
128
+ <button
129
+ onClick={() => void handleCorpusClick(corpus)}
130
+ className={`
131
+ w-full text-left px-3 py-[6px] flex items-center gap-3
132
+ text-retro-sm font-retro
133
+ ${selectedCorpus?.id === corpus.id
134
+ ? 'bg-retro-select text-retro-select-text'
135
+ : 'hover:bg-retro-select hover:text-retro-select-text'
136
+ }
137
+ `}
138
+ >
139
+ <span className="text-[18px] leading-none shrink-0">
140
+ {PROFILE_GLYPHS[corpus.profile_id] || '\u{1F4C1}'}
141
+ </span>
142
+ <div className="flex-1 min-w-0">
143
+ <div className="font-bold truncate">{corpus.title}</div>
144
+ <div className={`text-retro-xs truncate ${
145
+ selectedCorpus?.id === corpus.id ? 'opacity-70' : 'text-retro-darkgray'
146
+ }`}>
147
+ {corpus.profile_id} — {corpus.slug}
148
+ </div>
149
+ </div>
150
+ </button>
151
+
152
+ {expanding === corpus.id && (
153
+ <div className="px-3 py-1 text-retro-xs text-retro-darkgray bg-retro-light">
154
+ Chargement...
155
+ </div>
156
+ )}
157
+ </div>
158
+ ))}
159
+ </div>
160
+ )}
161
+ </RetroWindow>
162
+
163
+ {/* ── Manuscripts sub-window (appears when a corpus has multiple) ── */}
164
+ {selectedMs && selectedMs.length > 1 && selectedCorpus && (
165
+ <RetroWindow
166
+ title={`Manuscrits — ${selectedCorpus.title}`}
167
+ onClose={() => setSelectedCorpus(null)}
168
+ statusBar={`${selectedMs.length} manuscrit${selectedMs.length > 1 ? 's' : ''}`}
169
+ className="w-80"
170
+ scrollable
171
+ >
172
+ <div className="divide-y divide-retro-gray">
173
+ {selectedMs.map((ms) => (
174
+ <button
175
+ key={ms.id}
176
+ onClick={() => onOpenManuscript(ms.id, selectedCorpus.profile_id)}
177
+ className="
178
+ w-full text-left px-3 py-[6px]
179
+ text-retro-sm font-retro
180
+ hover:bg-retro-select hover:text-retro-select-text
181
+ "
182
+ >
183
+ <div className="font-bold truncate">{ms.title}</div>
184
+ {ms.total_pages > 0 && (
185
+ <div className="text-retro-xs text-retro-darkgray">
186
+ {ms.total_pages} pages
187
+ </div>
188
+ )}
189
+ </button>
190
+ ))}
191
+ </div>
192
+ </RetroWindow>
193
+ )}
194
  </div>
195
+
196
+ {/* ── Desktop icons (bottom dock) ────────────────────────── */}
197
+ <div
198
+ className="
199
+ shrink-0 flex items-end justify-center gap-1 px-4 py-3
200
+ border-t-retro border-retro-black
201
+ bg-retro-gray
202
+ shadow-retro-outset
203
+ "
204
+ >
205
+ {corpora.map((corpus) => (
206
+ <RetroIcon
207
+ key={corpus.id}
208
+ glyph={PROFILE_GLYPHS[corpus.profile_id] || '\u{1F4C1}'}
209
+ label={corpus.slug}
210
+ selected={selectedCorpus?.id === corpus.id}
211
+ onClick={() => void handleCorpusClick(corpus)}
212
+ />
213
+ ))}
214
+ {corpora.length === 0 && (
215
+ <div className="text-retro-xs text-retro-darkgray py-2">
216
+ Aucun corpus
217
+ </div>
218
+ )}
219
  </div>
220
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  </div>
222
  )
223
  }
frontend/src/pages/RetroDemo.tsx DELETED
@@ -1,198 +0,0 @@
1
- import { useState } from 'react'
2
- import {
3
- RetroWindow,
4
- RetroButton,
5
- RetroMenuBar,
6
- RetroIcon,
7
- RetroCheckbox,
8
- RetroInput,
9
- RetroTextarea,
10
- RetroSelect,
11
- RetroBadge,
12
- } from '../components/retro'
13
-
14
- export default function RetroDemo({ onBack }: { onBack: () => void }) {
15
- const [checked1, setChecked1] = useState(true)
16
- const [checked2, setChecked2] = useState(false)
17
- const [selectedIcon, setSelectedIcon] = useState<string | null>(null)
18
- const [inputVal, setInputVal] = useState('')
19
- const [textareaVal, setTextareaVal] = useState('Explicit liber primus incipit secundus...')
20
- const [selectVal, setSelectVal] = useState('medieval-illuminated')
21
-
22
- return (
23
- <div className="min-h-screen bg-retro-dither">
24
- {/* ── Menu Bar ─────────────────────────────────────────── */}
25
- <RetroMenuBar
26
- items={[
27
- { label: 'IIIF Studio', onClick: onBack },
28
- { label: 'Fichier' },
29
- { label: 'Edition' },
30
- { label: 'Aide' },
31
- { label: 'Disabled', disabled: true },
32
- ]}
33
- right={
34
- <span className="text-retro-xs text-retro-darkgray px-2">
35
- Design System Demo
36
- </span>
37
- }
38
- />
39
-
40
- <div className="p-4 flex flex-col gap-4 max-w-4xl mx-auto">
41
- {/* ── Section: Buttons ──────────────────────────────── */}
42
- <RetroWindow title="RetroButton" statusBar="Boutons avec bevel 3D outset/inset">
43
- <div className="p-3 flex flex-wrap gap-2 items-center">
44
- <RetroButton>Normal</RetroButton>
45
- <RetroButton size="sm">Small</RetroButton>
46
- <RetroButton pressed>Pressed</RetroButton>
47
- <RetroButton disabled>Disabled</RetroButton>
48
- <RetroButton onClick={onBack}>Retour Accueil</RetroButton>
49
- </div>
50
- </RetroWindow>
51
-
52
- {/* ── Section: Window variants ─────────────────────── */}
53
- <div className="flex gap-4">
54
- <RetroWindow
55
- title="Fenetre active"
56
- onClose={() => {}}
57
- statusBar="Active window"
58
- className="flex-1"
59
- active
60
- >
61
- <div className="p-3 text-retro-sm">
62
- Contenu de la fenetre avec barre de titre noire.
63
- Le bouton [x] ferme la fenetre.
64
- </div>
65
- </RetroWindow>
66
-
67
- <RetroWindow
68
- title="Fenetre inactive"
69
- onClose={() => {}}
70
- statusBar="Inactive window"
71
- className="flex-1"
72
- active={false}
73
- >
74
- <div className="p-3 text-retro-sm">
75
- Une fenetre non focusee a une barre de titre grise.
76
- </div>
77
- </RetroWindow>
78
- </div>
79
-
80
- {/* ── Section: Scrollable window ───────────────────── */}
81
- <RetroWindow title="Scrollable Window" scrollable className="h-[150px]">
82
- <div className="p-3 text-retro-sm">
83
- {Array.from({ length: 20 }, (_, i) => (
84
- <p key={i}>Ligne {i + 1} — Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
85
- ))}
86
- </div>
87
- </RetroWindow>
88
-
89
- {/* ── Section: Icons ───────────────────────────────── */}
90
- <RetroWindow title="RetroIcon — Desktop Icons" statusBar="Cliquez pour selectionner">
91
- <div className="p-4 flex flex-wrap gap-2 bg-retro-dither-light">
92
- {[
93
- { glyph: '\u{1F4DC}', label: 'Beatus' },
94
- { glyph: '\u{1F4D6}', label: 'Cartulaire' },
95
- { glyph: '\u{1F4C4}', label: 'Charte' },
96
- { glyph: '\u{1F5BC}', label: 'Enluminures' },
97
- { glyph: '\u{1F4BE}', label: 'Export' },
98
- { glyph: '\u{2699}', label: 'Config' },
99
- ].map((icon) => (
100
- <RetroIcon
101
- key={icon.label}
102
- glyph={icon.glyph}
103
- label={icon.label}
104
- selected={selectedIcon === icon.label}
105
- onClick={() => setSelectedIcon(icon.label)}
106
- />
107
- ))}
108
- </div>
109
- </RetroWindow>
110
-
111
- {/* ── Section: Form controls ───────────────────────── */}
112
- <RetroWindow title="Form Controls" statusBar="Inputs, textareas, selects, checkboxes">
113
- <div className="p-3 flex flex-col gap-3">
114
- <div className="flex gap-4">
115
- <div className="flex-1">
116
- <RetroInput
117
- label="Identifiant du corpus"
118
- placeholder="ex: beatus-lat8878"
119
- value={inputVal}
120
- onChange={(e) => setInputVal(e.target.value)}
121
- />
122
- </div>
123
- <div className="flex-1">
124
- <RetroSelect
125
- label="Profil de corpus"
126
- value={selectVal}
127
- onChange={(e) => setSelectVal(e.target.value)}
128
- options={[
129
- { value: 'medieval-illuminated', label: 'Medieval Illuminated' },
130
- { value: 'medieval-textual', label: 'Medieval Textual' },
131
- { value: 'early-modern-print', label: 'Early Modern Print' },
132
- { value: 'modern-handwritten', label: 'Modern Handwritten' },
133
- ]}
134
- />
135
- </div>
136
- </div>
137
-
138
- <RetroTextarea
139
- label="Transcription diplomatique"
140
- value={textareaVal}
141
- onChange={(e) => setTextareaVal(e.target.value)}
142
- rows={4}
143
- />
144
-
145
- <div className="flex gap-4">
146
- <RetroCheckbox
147
- label="OCR diplomatique"
148
- checked={checked1}
149
- onChange={setChecked1}
150
- />
151
- <RetroCheckbox
152
- label="Iconographie"
153
- checked={checked2}
154
- onChange={setChecked2}
155
- />
156
- <RetroCheckbox
157
- label="Disabled"
158
- checked={false}
159
- onChange={() => {}}
160
- disabled
161
- />
162
- </div>
163
- </div>
164
- </RetroWindow>
165
-
166
- {/* ── Section: Badges ──────────────────────────────── */}
167
- <RetroWindow title="RetroBadge — Status Indicators">
168
- <div className="p-3 flex flex-wrap gap-2 items-center">
169
- <RetroBadge>default</RetroBadge>
170
- <RetroBadge variant="success">validated</RetroBadge>
171
- <RetroBadge variant="warning">needs_review</RetroBadge>
172
- <RetroBadge variant="error">failed</RetroBadge>
173
- <RetroBadge variant="info">machine_draft</RetroBadge>
174
- </div>
175
- </RetroWindow>
176
-
177
- {/* ── Section: Typography ──────────────────────────── */}
178
- <RetroWindow title="Typography & Utilities">
179
- <div className="p-3 flex flex-col gap-2">
180
- <p className="text-retro-2xl font-bold">Heading 2XL — IIIF Studio</p>
181
- <p className="text-retro-xl font-bold">Heading XL — Beatus de Saint-Sever</p>
182
- <p className="text-retro-lg font-semibold">Heading LG — Folio 13r</p>
183
- <p className="text-retro-base">Body base — Explicit liber primus incipit secundus</p>
184
- <p className="text-retro-sm">Body SM — Metadata and labels</p>
185
- <p className="text-retro-xs text-retro-darkgray">Caption XS — Timestamps and fine print</p>
186
- <hr className="border-retro-darkgray" />
187
- <div className="flex gap-4">
188
- <div className="p-2 bg-retro-dither text-retro-xs">bg-retro-dither</div>
189
- <div className="p-2 bg-retro-dither-dark text-retro-xs text-retro-white">bg-retro-dither-dark</div>
190
- <div className="p-2 bg-retro-dither-light text-retro-xs">bg-retro-dither-light</div>
191
- <div className="p-2 bg-retro-lines text-retro-xs">bg-retro-lines</div>
192
- </div>
193
- </div>
194
- </RetroWindow>
195
- </div>
196
- </div>
197
- )
198
- }