1111 */
1212
1313import { css , html , LitElement } from 'lit' ;
14- import { addons , type Channel } from '@storybook/manager-api' ;
14+ import { addons } from '@storybook/manager-api' ;
1515import { STORY_CHANGED } from '@storybook/core-events' ;
1616
1717// Import Spectrum Web Components
1818import '@spectrum-web-components/switch/sp-switch.js' ;
1919import '@spectrum-web-components/theme/sp-theme.js' ;
2020import '@spectrum-web-components/theme/src/spectrum-two/themes-core-tokens.js' ;
21+ import '@spectrum-web-components/textfield/sp-textfield.js' ;
22+ import '@spectrum-web-components/help-text/sp-help-text.js' ;
23+ import '@spectrum-web-components/field-label/sp-field-label.js' ;
2124
2225import ScreenReader from '../screen-reader/screenReader.js' ;
2326
@@ -35,59 +38,42 @@ export class ScreenReaderPanel extends LitElement {
3538 text : { type : Boolean } ,
3639 isActive : { type : Boolean } ,
3740 screenReaderText : { type : String } ,
41+ themeColor : { type : String } ,
3842 } ;
3943
4044 // Use 'declare' to avoid class field definition overriding Lit's reactive properties
4145 declare voice : boolean ;
4246 declare text : boolean ;
4347 declare isActive : boolean ;
4448 declare screenReaderText : string ;
49+ declare themeColor : 'light' | 'dark' ;
4550
4651 private screenReader : ScreenReader | null = null ;
47- private channel : Channel | null = null ;
52+ private channel : ReturnType < typeof addons . getChannel > | null = null ;
53+ private themeMediaQuery : MediaQueryList | null = null ;
4854
4955 static override styles = css `
5056 :host {
5157 display: block;
5258 padding: 16px;
53- font-family:
54- -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
55- sans-serif;
5659 }
5760
5861 .toggle-row {
5962 display: flex;
6063 align-items: center;
6164 margin-bottom: 12px;
62- gap: 8px;
6365 }
6466
65- .toggle-label {
66- font-size: 14px;
67- color: var(--spectrum-global-color-gray-800, #4b4b4b);
67+ .output-section {
68+ margin-top: 16px;
6869 }
6970
70- .text-output {
71- font-size: 14px;
72- border-radius: 6px;
73- border: 1px solid var(--spectrum-global-color-gray-300, #e0e0e0);
74- padding: 12px;
75- margin-top: 12px;
76- background: var(--spectrum-global-color-gray-100, #f5f5f5);
77- min-height: 60px;
78- max-height: 200px;
79- overflow-y: auto;
80- }
81-
82- .status-text {
83- font-size: 12px;
84- color: var(--spectrum-global-color-gray-600, #6e6e6e);
85- margin: 12px 0 0 0;
86- font-style: italic;
71+ sp-textfield {
72+ width: 100%;
8773 }
8874
89- .placeholder {
90- color: var(--spectrum-global-color-gray-500, #959595) ;
75+ sp-help-text {
76+ margin-top: 12px ;
9177 }
9278 ` ;
9379
@@ -98,9 +84,41 @@ export class ScreenReaderPanel extends LitElement {
9884 this . text = false ;
9985 this . isActive = false ;
10086 this . screenReaderText = '' ;
87+ this . themeColor = this . detectTheme ( ) ;
10188 // Bind event handlers
10289 this . handleTextChange = this . handleTextChange . bind ( this ) ;
10390 this . handleStoryChange = this . handleStoryChange . bind ( this ) ;
91+ this . handleThemeChange = this . handleThemeChange . bind ( this ) ;
92+ }
93+
94+ private detectTheme ( ) : 'light' | 'dark' {
95+ // Detect theme by checking Storybook's actual background color
96+ // This works for both explicit themes (1st-gen) and auto themes (2nd-gen)
97+ const body = document . body ;
98+ const computedStyle = getComputedStyle ( body ) ;
99+ const bgColor = computedStyle . backgroundColor ;
100+
101+ // Parse RGB values
102+ const rgbMatch = bgColor . match ( / r g b \( ( \d + ) , \s * ( \d + ) , \s * ( \d + ) \) / ) ;
103+ if ( rgbMatch ) {
104+ const [ , r , g , b ] = rgbMatch . map ( Number ) ;
105+ // Calculate relative luminance
106+ const luminance = ( 0.299 * r + 0.587 * g + 0.114 * b ) / 255 ;
107+
108+ // If background is dark (luminance < 0.5), use dark theme
109+ return luminance < 0.5 ? 'dark' : 'light' ;
110+ }
111+
112+ // Fallback to system preference
113+ if ( window . matchMedia ( '(prefers-color-scheme: dark)' ) . matches ) {
114+ return 'dark' ;
115+ }
116+
117+ return 'light' ;
118+ }
119+
120+ private handleThemeChange ( ) : void {
121+ this . themeColor = this . detectTheme ( ) ;
104122 }
105123
106124 override connectedCallback ( ) : void {
@@ -115,6 +133,12 @@ export class ScreenReaderPanel extends LitElement {
115133 // Listen for story changes via Storybook API
116134 this . channel = addons . getChannel ( ) ;
117135 this . channel . on ( STORY_CHANGED , this . handleStoryChange ) ;
136+
137+ // Listen for system theme changes (for auto-theme Storybooks like 2nd-gen)
138+ this . themeMediaQuery = window . matchMedia (
139+ '(prefers-color-scheme: dark)'
140+ ) ;
141+ this . themeMediaQuery . addEventListener ( 'change' , this . handleThemeChange ) ;
118142 }
119143
120144 override disconnectedCallback ( ) : void {
@@ -129,6 +153,13 @@ export class ScreenReaderPanel extends LitElement {
129153 this . channel . off ( STORY_CHANGED , this . handleStoryChange ) ;
130154 }
131155
156+ if ( this . themeMediaQuery ) {
157+ this . themeMediaQuery . removeEventListener (
158+ 'change' ,
159+ this . handleThemeChange
160+ ) ;
161+ }
162+
132163 this . stopScreenReader ( ) ;
133164 }
134165
@@ -216,7 +247,11 @@ export class ScreenReaderPanel extends LitElement {
216247
217248 override render ( ) {
218249 return html `
219- < sp-theme scale ="medium " color ="light " system ="spectrum-two ">
250+ < sp-theme
251+ scale ="medium "
252+ color =${ this . themeColor }
253+ system ="spectrum-two"
254+ >
220255 < div class ="toggle-row ">
221256 < sp-switch
222257 ?checked =${ this . voice }
@@ -237,22 +272,27 @@ export class ScreenReaderPanel extends LitElement {
237272
238273 ${ this . text
239274 ? html `
240- < div class ="text-output ">
241- ${ this . screenReaderText ||
242- html `
243- < span class ="placeholder ">
244- Navigate to hear announcements...
245- </ span >
246- ` }
275+ < div class ="output-section ">
276+ < sp-field-label for ="screen-reader-output ">
277+ Screen reader output
278+ </ sp-field-label >
279+ < sp-textfield
280+ id ="screen-reader-output "
281+ multiline
282+ readonly
283+ rows ="1 "
284+ placeholder ="Navigate to hear announcements... "
285+ .value =${ this . screenReaderText }
286+ > </ sp-textfield >
247287 </ div >
248288 `
249289 : '' }
250290 ${ this . isActive
251291 ? html `
252- < p class =" status- text" >
292+ < sp-help- text>
253293 Use Tab or arrow keys to navigate. Focus changes
254294 will be announced.
255- </ p >
295+ </ sp-help-text >
256296 `
257297 : '' }
258298 </ sp-theme >
0 commit comments