1
1
import type { Command , CommandLatest } from "@cursorless/common" ;
2
+ import { useState , useEffect , useRef } from "react" ;
3
+ import clsx from "clsx" ;
2
4
3
5
export function ShikiComponent ( {
4
6
data,
7
+ debug,
5
8
} : {
6
9
data : {
7
- before : string ;
8
- during : string ;
9
- after : string ;
10
+ before : { html : string ; data : string [ ] } ;
11
+ during : { html : string ; data : string [ ] } ;
12
+ after : { html : string ; data : string [ ] } ;
10
13
command : CommandLatest | Command ;
11
14
} ;
15
+ debug ?: boolean ;
12
16
} ) {
13
17
const { spokenForm } = data . command ;
14
18
const { before, during, after } = data ;
15
19
return (
16
- < div className = "mx-16 overflow-auto p-4" >
17
- < div className = "p-8 " >
20
+ < div className = "mx-16 overflow-auto p-4 px-10 " >
21
+ < div className = "" >
18
22
< h2 className = "dark:text-stone-100" > { spokenForm } </ h2 >
19
23
20
- < div className = "m-2 border" >
21
- < Before content = { before } />
22
- < div className = "command" > { spokenForm } </ div >
23
- < During content = { during } />
24
- < After content = { after } />
25
- </ div >
24
+ { debug && (
25
+ < div className = "my-4 outline" >
26
+ < Before content = { before . html } />
27
+ < div className = "command" > { spokenForm } </ div >
28
+ < During content = { during . html } />
29
+ < After content = { after . html } />
30
+ </ div >
31
+ ) }
32
+ < Carousel >
33
+ < Before content = { before . html } />
34
+ < During content = { during . html } />
35
+ < After content = { after . html } />
36
+ </ Carousel >
26
37
</ div >
38
+
27
39
< details >
28
40
< summary > JSON</ summary >
29
41
< pre className = "max-w-xl overflow-auto" >
@@ -35,13 +47,21 @@ export function ShikiComponent({
35
47
}
36
48
37
49
const Before = ( { content } : { content : string } ) => {
38
- return < div className = "p-4" dangerouslySetInnerHTML = { { __html : content } } /> ;
50
+ return (
51
+ < div
52
+ className = "rounded-md py-4"
53
+ dangerouslySetInnerHTML = { { __html : content } }
54
+ />
55
+ ) ;
39
56
} ;
40
57
41
58
const During = ( { content } : { content : string } ) => {
42
59
if ( content ) {
43
60
return (
44
- < div className = "p-4" dangerouslySetInnerHTML = { { __html : content } } />
61
+ < div
62
+ className = "rounded-md py-4"
63
+ dangerouslySetInnerHTML = { { __html : content } }
64
+ />
45
65
) ;
46
66
}
47
67
return < > </ > ;
@@ -50,8 +70,143 @@ const During = ({ content }: { content: string }) => {
50
70
const After = ( { content } : { content : string } ) => {
51
71
return (
52
72
< div
53
- className = "flex flex-col gap-y-4 p -4"
73
+ className = "rounded-md py -4"
54
74
dangerouslySetInnerHTML = { { __html : content } }
55
75
/>
56
76
) ;
57
77
} ;
78
+
79
+ const STEP_DURATIONS = [ 1500 , 500 , 2000 ] ; // milliseconds
80
+
81
+ function Carousel ( { children } : { children : React . ReactNode [ ] } ) {
82
+ const [ activeIndex , setActiveIndex ] = useState ( 0 ) ;
83
+
84
+ const handleNext = ( ) => {
85
+ setActiveIndex ( ( prevIndex ) => ( prevIndex + 1 ) % children . length ) ;
86
+ } ;
87
+
88
+ const handlePrev = ( ) => {
89
+ setActiveIndex ( ( prevIndex ) =>
90
+ prevIndex === 0 ? children . length - 1 : prevIndex - 1 ,
91
+ ) ;
92
+ } ;
93
+
94
+ const handleIndicatorClick = ( index : number ) => {
95
+ setActiveIndex ( index ) ;
96
+ } ;
97
+
98
+ const timeoutRef = useRef < any > ( null ) ;
99
+
100
+ useEffect ( ( ) => {
101
+ const duration = STEP_DURATIONS [ activeIndex ] || 3000 ;
102
+
103
+ timeoutRef . current = setTimeout ( ( ) => {
104
+ setActiveIndex ( ( prevIndex ) => ( prevIndex + 1 ) % children . length ) ;
105
+ } , duration ) ;
106
+
107
+ return ( ) => clearTimeout ( timeoutRef . current ) ;
108
+ } , [ activeIndex , children . length ] ) ;
109
+
110
+ const CarouselItem = ( {
111
+ child,
112
+ isActive,
113
+ } : {
114
+ child : React . ReactNode ;
115
+ isActive : boolean ;
116
+ } ) => {
117
+ return (
118
+ < div
119
+ className = { clsx ( "transform transition-all duration-700 ease-in-out" , {
120
+ "block scale-100 opacity-100" : isActive ,
121
+ "hidden scale-95 opacity-0" : ! isActive ,
122
+ } ) }
123
+ data-carousel-item = { isActive ? "active" : undefined }
124
+ >
125
+ { child }
126
+ </ div >
127
+ ) ;
128
+ } ;
129
+
130
+ return (
131
+ < div
132
+ id = "default-carousel"
133
+ className = "relative w-full"
134
+ data-carousel = "slide"
135
+ >
136
+ { /* <!-- Carousel wrapper --> */ }
137
+ < div className = "relative h-56 overflow-hidden rounded-lg bg-gray-900 md:h-96" >
138
+ < div className = "absolute right-2 top-2 z-30 font-bold text-red-500" >
139
+ { activeIndex + 1 } /{ children . length }
140
+ </ div >
141
+ { children . map ( ( child , index ) => (
142
+ < CarouselItem
143
+ key = { index }
144
+ child = { child }
145
+ isActive = { index === activeIndex }
146
+ />
147
+ ) ) }
148
+ </ div >
149
+ { /* <!-- Slider indicators --> */ }
150
+ < div className = "absolute bottom-5 left-1/2 z-30 flex -translate-x-1/2 space-x-3 rtl:space-x-reverse" >
151
+ { children . map ( ( _ , index ) => (
152
+ < button
153
+ key = { index }
154
+ type = "button"
155
+ className = { clsx ( "h-3 w-3 rounded-full" , {
156
+ "bg-blue-500" : index === activeIndex ,
157
+ "bg-gray-300" : index !== activeIndex ,
158
+ } ) }
159
+ aria-current = { index === activeIndex }
160
+ aria-label = { `Slide ${ index + 1 } ` }
161
+ onClick = { ( ) => handleIndicatorClick ( index ) }
162
+ > </ button >
163
+ ) ) }
164
+ </ div >
165
+ { /* <!-- Slider controls --> */ }
166
+ < SliderButton additionalClasses = "-start-12 top-0" callback = { handlePrev } />
167
+ < SliderButton
168
+ additionalClasses = "rotate-180 -end-12 top-0"
169
+ callback = { handleNext }
170
+ />
171
+ </ div >
172
+ ) ;
173
+ }
174
+
175
+ const SliderButton = ( {
176
+ additionalClasses,
177
+ callback,
178
+ } : {
179
+ additionalClasses ?: string ;
180
+ callback : React . MouseEventHandler ;
181
+ } ) => {
182
+ return (
183
+ < button
184
+ type = "button"
185
+ className = { clsx (
186
+ "group absolute z-30 flex h-full cursor-pointer items-center justify-center px-4 focus:outline-none" ,
187
+ additionalClasses ,
188
+ ) }
189
+ onClick = { callback }
190
+ data-carousel-next
191
+ >
192
+ < span className = "inline-flex h-10 w-10 items-center justify-center rounded-full border-2 border-gray-300 bg-white text-base group-hover:bg-gray-100 group-focus:outline-none group-focus:ring-4 group-focus:ring-white dark:bg-gray-800 dark:text-white dark:group-hover:bg-gray-700 dark:group-focus:ring-white" >
193
+ < svg
194
+ className = "h-4 w-4 rtl:rotate-180"
195
+ aria-hidden = "true"
196
+ xmlns = "http://www.w3.org/2000/svg"
197
+ fill = "none"
198
+ viewBox = "0 0 6 10"
199
+ >
200
+ < path
201
+ stroke = "currentColor"
202
+ stroke-linecap = "round"
203
+ stroke-linejoin = "round"
204
+ stroke-width = "2"
205
+ d = "M5 1 1 5l4 4"
206
+ />
207
+ </ svg >
208
+ < span className = "sr-only" > Next</ span >
209
+ </ span >
210
+ </ button >
211
+ ) ;
212
+ } ;
0 commit comments