1- import { useEffect } from 'react'
1+ import { useEffect , useState } from 'react'
22import type { Meta , StoryObj } from '@storybook/react'
33import { AiModelIcon , CopilotIcon , ShieldCheckIcon } from '@primer/octicons-react'
44import { useTranslation } from 'react-i18next'
@@ -9,7 +9,6 @@ import renderUI2 from '../../fixtures/images/copilot-vscode-agent-mode-2.png'
99import renderUI3 from '../../fixtures/images/copilot-vscode-agent-mode-3.png'
1010import placeholderBg from '../../fixtures/images/dither-bg-landscape-green.png'
1111import posterImage from '../../fixtures/images/example-poster.png'
12- import { useWindowSize } from '../../hooks/useWindowSize'
1312import { VideoPlayer } from '../../VideoPlayer'
1413
1514const meta = {
@@ -24,29 +23,37 @@ export default meta
2423
2524type Story = StoryObj < typeof RiverBreakoutTabs >
2625
26+ function SharedDitherBackdrop ( ) {
27+ const [ shown , setShown ] = useState ( false )
28+
29+ useEffect ( ( ) => {
30+ const raf = requestAnimationFrame ( ( ) => setShown ( true ) )
31+ return ( ) => cancelAnimationFrame ( raf )
32+ } , [ ] )
33+
34+ return (
35+ < div
36+ style = { {
37+ position : 'absolute' ,
38+ inset : 0 ,
39+ backgroundImage : `url(${ placeholderBg } )` ,
40+ backgroundSize : 'cover' ,
41+ backgroundPosition : 'center' ,
42+ opacity : shown ? 1 : 0 ,
43+ transition : 'opacity 1.5s ease-out' ,
44+ } }
45+ />
46+ )
47+ }
48+
2749export const WithImages : Story = {
2850 name : 'With images' ,
2951 render : function WithImagesRender ( ) {
3052 const { t} = useTranslation ( 'RiverBreakoutTabs' )
31- const { isLarge} = useWindowSize ( )
32-
33- useEffect ( ( ) => {
34- const visuals = Array . from ( document . querySelectorAll ( '[data-river-tabs-dither-bg]' ) ) . filter (
35- ( value ) : value is HTMLDivElement => value instanceof HTMLDivElement ,
36- )
37-
38- const cleanups = visuals . map ( visualElement => TempFadeInBackgroundEffect ( visualElement , placeholderBg , 500 ) )
39-
40- return ( ) => {
41- for ( const cleanup of cleanups ) {
42- cleanup ( )
43- }
44- }
45- } , [ isLarge ] )
4653
4754 return (
4855 < Section >
49- < RiverBreakoutTabs >
56+ < RiverBreakoutTabs backgroundVisual = { < SharedDitherBackdrop /> } imagePosition = "block-end" >
5057 < RiverBreakoutTabs . A11yHeading > { t ( 'with_images_a11y_heading' ) } </ RiverBreakoutTabs . A11yHeading >
5158
5259 < RiverBreakoutTabs . Item >
@@ -56,7 +63,7 @@ export const WithImages: Story = {
5663 < Text > { t ( 'with_images_item_plan_body' ) } </ Text >
5764 < Link href = "https://github.com/features/copilot" > { t ( 'with_images_item_plan_link' ) } </ Link >
5865 </ RiverBreakoutTabs . Content >
59- < RiverBreakoutTabs . Visual data-river-tabs-dither-bg imageBackgroundColor = "subtle" >
66+ < RiverBreakoutTabs . Visual >
6067 < Image src = { renderUI1 } alt = { t ( 'alt_ai_plan_editor' ) } />
6168 </ RiverBreakoutTabs . Visual >
6269 </ RiverBreakoutTabs . Item >
@@ -68,7 +75,7 @@ export const WithImages: Story = {
6875 < Text > { t ( 'with_images_item_refine_body' ) } </ Text >
6976 < Link href = "https://github.com/features/copilot/chat" > { t ( 'with_images_item_refine_link' ) } </ Link >
7077 </ RiverBreakoutTabs . Content >
71- < RiverBreakoutTabs . Visual data-river-tabs-dither-bg imageBackgroundColor = "subtle" >
78+ < RiverBreakoutTabs . Visual >
7279 < Image src = { renderUI2 } alt = { t ( 'alt_chat_guiding_edits' ) } />
7380 </ RiverBreakoutTabs . Visual >
7481 </ RiverBreakoutTabs . Item >
@@ -80,7 +87,7 @@ export const WithImages: Story = {
8087 < Text > { t ( 'with_images_item_merge_body' ) } </ Text >
8188 < Link href = "https://github.com/features/copilot/plans" > { t ( 'with_images_item_merge_link' ) } </ Link >
8289 </ RiverBreakoutTabs . Content >
83- < RiverBreakoutTabs . Visual data-river-tabs-dither-bg imageBackgroundColor = "subtle" >
90+ < RiverBreakoutTabs . Visual >
8491 < Image src = { renderUI3 } alt = { t ( 'alt_checks_before_merge' ) } />
8592 </ RiverBreakoutTabs . Visual >
8693 </ RiverBreakoutTabs . Item >
@@ -94,25 +101,10 @@ export const WithVideos: Story = {
94101 name : 'With videos' ,
95102 render : function WithVideosRender ( ) {
96103 const { t} = useTranslation ( 'RiverBreakoutTabs' )
97- const { isLarge} = useWindowSize ( )
98-
99- useEffect ( ( ) => {
100- const visuals = Array . from ( document . querySelectorAll ( '[data-river-tabs-dither-bg]' ) ) . filter (
101- ( value ) : value is HTMLDivElement => value instanceof HTMLDivElement ,
102- )
103-
104- const cleanups = visuals . map ( visualElement => TempFadeInBackgroundEffect ( visualElement , placeholderBg , 500 ) )
105-
106- return ( ) => {
107- for ( const cleanup of cleanups ) {
108- cleanup ( )
109- }
110- }
111- } , [ isLarge ] )
112104
113105 return (
114106 < Section >
115- < RiverBreakoutTabs >
107+ < RiverBreakoutTabs backgroundVisual = { < SharedDitherBackdrop /> } imagePosition = "block-end" >
116108 < RiverBreakoutTabs . A11yHeading > { t ( 'with_videos_a11y_heading' ) } </ RiverBreakoutTabs . A11yHeading >
117109
118110 < RiverBreakoutTabs . Item >
@@ -122,7 +114,7 @@ export const WithVideos: Story = {
122114 < Text > { t ( 'with_videos_item_backlog_body' ) } </ Text >
123115 < Link href = "https://github.com/features/copilot" > { t ( 'with_videos_item_backlog_link' ) } </ Link >
124116 </ RiverBreakoutTabs . Content >
125- < RiverBreakoutTabs . Visual data-river-tabs-dither-bg imageBackgroundColor = "subtle" >
117+ < RiverBreakoutTabs . Visual >
126118 < VideoPlayer title = { t ( 'video_title_planning' ) } poster = { posterImage } style = { { height : '100%' } } >
127119 < VideoPlayer . Source src = "./example.mp4" type = "video/mp4" />
128120 < VideoPlayer . Track src = "./example.vtt" default />
@@ -137,7 +129,7 @@ export const WithVideos: Story = {
137129 < Text > { t ( 'with_videos_item_workflow_body' ) } </ Text >
138130 < Link href = "https://github.com/features/copilot/chat" > { t ( 'with_videos_item_workflow_link' ) } </ Link >
139131 </ RiverBreakoutTabs . Content >
140- < RiverBreakoutTabs . Visual data-river-tabs-dither-bg imageBackgroundColor = "subtle" >
132+ < RiverBreakoutTabs . Visual >
141133 < VideoPlayer title = { t ( 'video_title_coding' ) } poster = { posterImage } style = { { height : '100%' } } >
142134 < VideoPlayer . Source src = "./example.mp4" type = "video/mp4" />
143135 < VideoPlayer . Track src = "./example.vtt" default />
@@ -152,7 +144,7 @@ export const WithVideos: Story = {
152144 < Text > { t ( 'with_videos_item_confidence_body' ) } </ Text >
153145 < Link href = "https://github.com/features/copilot/plans" > { t ( 'with_videos_item_confidence_link' ) } </ Link >
154146 </ RiverBreakoutTabs . Content >
155- < RiverBreakoutTabs . Visual data-river-tabs-dither-bg imageBackgroundColor = "subtle" >
147+ < RiverBreakoutTabs . Visual >
156148 < VideoPlayer title = { t ( 'video_title_merge_confidence' ) } poster = { posterImage } style = { { height : '100%' } } >
157149 < VideoPlayer . Source src = "./example.mp4" type = "video/mp4" />
158150 < VideoPlayer . Track src = "./example.vtt" default />
@@ -165,66 +157,16 @@ export const WithVideos: Story = {
165157 } ,
166158}
167159
168- /**
169- * This helper mirrors the temporary fade-in background approach used in Hero and River examples.
170- */
171- function TempFadeInBackgroundEffect ( element : HTMLDivElement , backgroundImageUrl : string , delay = 100 ) {
172- element . style . position = 'relative'
173-
174- const bgDiv = document . createElement ( 'div' )
175- bgDiv . style . cssText = `
176- position: absolute;
177- inset: 0;
178- background-image: url(${ backgroundImageUrl } );
179- background-size: cover;
180- background-position: center;
181- opacity: 0;
182- transition: opacity 1.5s ease-out;
183- z-index: 0;
184- `
185- element . insertBefore ( bgDiv , element . firstChild )
186-
187- const children = element . children
188- for ( let i = 1 ; i < children . length ; i ++ ) {
189- ; ( children [ i ] as HTMLElement ) . style . position = 'relative'
190- ; ( children [ i ] as HTMLElement ) . style . zIndex = '1'
191- }
192-
193- const timer = setTimeout ( ( ) => {
194- bgDiv . style . opacity = '1'
195- } , delay )
196-
197- return ( ) => {
198- clearTimeout ( timer )
199- bgDiv . remove ( )
200- }
201- }
202-
203160export const WithRivers : Story = {
204161 name : 'With rivers' ,
205162 render : function WithRiversRender ( ) {
206163 const { t} = useTranslation ( 'RiverBreakoutTabs' )
207- const { isLarge} = useWindowSize ( )
208-
209- useEffect ( ( ) => {
210- const visuals = Array . from ( document . querySelectorAll ( '[data-river-tabs-dither-bg]' ) ) . filter (
211- ( value ) : value is HTMLDivElement => value instanceof HTMLDivElement ,
212- )
213-
214- const cleanups = visuals . map ( visualElement => TempFadeInBackgroundEffect ( visualElement , placeholderBg , 500 ) )
215-
216- return ( ) => {
217- for ( const cleanup of cleanups ) {
218- cleanup ( )
219- }
220- }
221- } , [ isLarge ] )
222164
223165 return (
224166 < Section >
225167 < Stack padding = "none" gap = { 64 } >
226168 < River variant = "gridline" >
227- < River . Visual data-river-tabs-dither-bg imageBackgroundColor = "subtle" >
169+ < River . Visual imageBackgroundColor = "subtle" >
228170 < Image src = { renderUI1 } alt = { t ( 'with_rivers_top_visual_alt' ) } />
229171 </ River . Visual >
230172 < River . Content >
@@ -233,7 +175,7 @@ export const WithRivers: Story = {
233175 </ River . Content >
234176 </ River >
235177
236- < RiverBreakoutTabs >
178+ < RiverBreakoutTabs backgroundVisual = { < SharedDitherBackdrop /> } imagePosition = "block-end" >
237179 < RiverBreakoutTabs . A11yHeading > { t ( 'with_images_a11y_heading' ) } </ RiverBreakoutTabs . A11yHeading >
238180
239181 < RiverBreakoutTabs . Item >
@@ -243,7 +185,7 @@ export const WithRivers: Story = {
243185 < Text > { t ( 'with_images_item_plan_body' ) } </ Text >
244186 < Link href = "https://github.com/features/copilot" > { t ( 'with_images_item_plan_link' ) } </ Link >
245187 </ RiverBreakoutTabs . Content >
246- < RiverBreakoutTabs . Visual data-river-tabs-dither-bg imageBackgroundColor = "subtle" >
188+ < RiverBreakoutTabs . Visual >
247189 < Image src = { renderUI1 } alt = { t ( 'alt_ai_plan_editor' ) } />
248190 </ RiverBreakoutTabs . Visual >
249191 </ RiverBreakoutTabs . Item >
@@ -255,7 +197,7 @@ export const WithRivers: Story = {
255197 < Text > { t ( 'with_images_item_refine_body' ) } </ Text >
256198 < Link href = "https://github.com/features/copilot/chat" > { t ( 'with_images_item_refine_link' ) } </ Link >
257199 </ RiverBreakoutTabs . Content >
258- < RiverBreakoutTabs . Visual data-river-tabs-dither-bg imageBackgroundColor = "subtle" >
200+ < RiverBreakoutTabs . Visual >
259201 < Image src = { renderUI2 } alt = { t ( 'alt_chat_guiding_edits' ) } />
260202 </ RiverBreakoutTabs . Visual >
261203 </ RiverBreakoutTabs . Item >
@@ -267,14 +209,14 @@ export const WithRivers: Story = {
267209 < Text > { t ( 'with_images_item_merge_body' ) } </ Text >
268210 < Link href = "https://github.com/features/copilot/plans" > { t ( 'with_images_item_merge_link' ) } </ Link >
269211 </ RiverBreakoutTabs . Content >
270- < RiverBreakoutTabs . Visual data-river-tabs-dither-bg imageBackgroundColor = "subtle" >
212+ < RiverBreakoutTabs . Visual >
271213 < Image src = { renderUI3 } alt = { t ( 'alt_checks_before_merge' ) } />
272214 </ RiverBreakoutTabs . Visual >
273215 </ RiverBreakoutTabs . Item >
274216 </ RiverBreakoutTabs >
275217
276218 < River variant = "gridline" >
277- < River . Visual data-river-tabs-dither-bg imageBackgroundColor = "subtle" >
219+ < River . Visual imageBackgroundColor = "subtle" >
278220 < Image src = { renderUI3 } alt = { t ( 'with_rivers_bottom_visual_alt' ) } />
279221 </ River . Visual >
280222 < River . Content >
0 commit comments