@@ -11,9 +11,34 @@ import { useShallow } from 'zustand/shallow';
1111import logo from './assets/logo.png' ;
1212import { LayoutPresetButtons } from './components/LayoutPresetButtons' ;
1313import { Button } from './components/ui/Button' ;
14+ import {
15+ Dialog ,
16+ DialogBody ,
17+ DialogClose ,
18+ DialogContent ,
19+ DialogDescription ,
20+ DialogFooter ,
21+ DialogHeader ,
22+ DialogOverlay ,
23+ DialogPortal ,
24+ DialogTitle ,
25+ DialogTrigger ,
26+ FormGroup ,
27+ Input ,
28+ Label ,
29+ } from './components/ui/Dialog' ;
1430import { useTheme , type ColorMode } from './context/ThemeContext' ;
31+ import { fetchHealth } from './services/health' ;
1532import { LAYOUT_PRESETS , useLayoutStore , type LayoutPreset } from './stores/layoutStore' ;
1633import { usePermissionStore } from './stores/permissionStore' ;
34+ import { getLogger } from './utils/logger' ;
35+
36+ const logger = getLogger ( 'Layout' ) ;
37+
38+ type BuildInfo = {
39+ version : string ;
40+ buildHash : string ;
41+ } ;
1742
1843const LayoutContainer = styled . div `
1944 display: flex;
@@ -44,6 +69,21 @@ const LogoContainer = styled.div`
4469 user-select: none;
4570` ;
4671
72+ const LogoButton = styled . button `
73+ display: inline-flex;
74+ align-items: center;
75+ padding: 0;
76+ border: none;
77+ background: none;
78+ cursor: pointer;
79+
80+ &:focus-visible {
81+ outline: none;
82+ box-shadow: var(--sk-focus-ring);
83+ border-radius: 8px;
84+ }
85+ ` ;
86+
4787const Logo = styled . img `
4888 height: 42px;
4989 width: auto;
@@ -190,6 +230,8 @@ const Main = styled.main`
190230` ;
191231
192232const Layout : React . FC = ( ) => {
233+ const [ buildInfo , setBuildInfo ] = React . useState < BuildInfo | null > ( null ) ;
234+ const closeButtonRef = React . useRef < HTMLButtonElement | null > ( null ) ;
193235 const { colorMode, setColorMode } = useTheme ( ) ;
194236 const role = usePermissionStore ( ( s ) => s . role ) ;
195237 const { currentPreset, setPreset } = useLayoutStore (
@@ -204,12 +246,75 @@ const Layout: React.FC = () => {
204246 'focus-canvas' ,
205247 'inspector-focus' ,
206248 ] ;
249+ const version = buildInfo ?. version ?? 'unknown' ;
250+ const buildHash = buildInfo ?. buildHash ?? 'unknown' ;
251+ const handleDialogOpenAutoFocus = React . useCallback ( ( event : Event ) => {
252+ event . preventDefault ( ) ;
253+ closeButtonRef . current ?. focus ( ) ;
254+ } , [ ] ) ;
255+
256+ React . useEffect ( ( ) => {
257+ let cancelled = false ;
258+ const controller = new AbortController ( ) ;
259+
260+ ( async ( ) => {
261+ try {
262+ const info = await fetchHealth ( controller . signal ) ;
263+ if ( ! cancelled ) {
264+ setBuildInfo ( info ) ;
265+ }
266+ } catch ( err ) {
267+ const isAbortError = err instanceof Error && err . name === 'AbortError' ;
268+ const isAbortRelated = err instanceof DOMException && err . name === 'AbortError' ;
269+ if ( ! cancelled && ! isAbortError && ! isAbortRelated ) {
270+ logger . debug ( 'Failed to load build info' , err ) ;
271+ }
272+ }
273+ } ) ( ) ;
274+
275+ return ( ) => {
276+ cancelled = true ;
277+ controller . abort ( ) ;
278+ } ;
279+ } , [ ] ) ;
207280
208281 return (
209282 < LayoutContainer >
210283 < Nav >
211284 < LogoContainer >
212- < Logo src = { logo } alt = "StreamKit" />
285+ < Dialog >
286+ < DialogTrigger asChild >
287+ < LogoButton type = "button" aria-label = "About StreamKit" >
288+ < Logo src = { logo } alt = "StreamKit" />
289+ </ LogoButton >
290+ </ DialogTrigger >
291+ < DialogPortal >
292+ < DialogOverlay />
293+ < DialogContent onOpenAutoFocus = { handleDialogOpenAutoFocus } >
294+ < DialogHeader >
295+ < DialogTitle > About StreamKit</ DialogTitle >
296+ < DialogDescription > Build info for support and debugging.</ DialogDescription >
297+ </ DialogHeader >
298+ < DialogBody >
299+ < FormGroup spacing = "compact" >
300+ < Label htmlFor = "about-version" > Version</ Label >
301+ < Input id = "about-version" readOnly value = { version } />
302+ </ FormGroup >
303+ < FormGroup spacing = "compact" >
304+ < Label htmlFor = "about-build-hash" > Build hash</ Label >
305+ < Input id = "about-build-hash" readOnly value = { buildHash } />
306+ </ FormGroup >
307+ </ DialogBody >
308+ < DialogFooter >
309+ < DialogClose asChild >
310+ < Button ref = { closeButtonRef } variant = "primary" >
311+ Close
312+ </ Button >
313+ </ DialogClose >
314+ </ DialogFooter >
315+ </ DialogContent >
316+ </ DialogPortal >
317+ </ Dialog >
213318 </ LogoContainer >
214319 < NavLinks >
215320 < StyledNavLink to = "/design" > Design</ StyledNavLink >
0 commit comments