88 List ,
99 Text ,
1010 VStack ,
11+ type HeadingProps ,
1112 type HTMLChakraProps ,
1213} from '@chakra-ui/react' ;
1314import { MDXProvider } from '@mdx-js/react' ;
@@ -17,15 +18,24 @@ import {
1718 useContext ,
1819 useEffect ,
1920 useMemo ,
21+ useRef ,
22+ useState ,
2023 type MouseEvent ,
2124 type PropsWithChildren ,
2225 type ReactNode ,
2326} from 'react' ;
2427import { NAVIGATE_URL } from 'storybook/internal/core-events' ;
2528import type { DocsContextProps } from 'storybook/internal/types' ;
2629import { styled , ThemeProvider } from 'storybook/theming' ;
30+ import { useCopyToClipboard } from 'usehooks-ts' ;
2731
28- import { Table } from '../../src' ;
32+ import {
33+ ArrowUpRightIcon ,
34+ ClipboardIcon ,
35+ IconButton ,
36+ Table ,
37+ Tooltip ,
38+ } from '../../src' ;
2939import { storybookTheme } from '../storybookTheme' ;
3040import { StorybookThemeProvider } from '../StorybookThemeProvider' ;
3141import { getThemes } from '../themes' ;
@@ -60,7 +70,7 @@ const StorybookOverrides = styled.div`
6070 border: 1px solid;
6171 border-radius: var(--teleport-radii-md);
6272
63- & > :first-child {
73+ & > :first-of-type {
6474 margin-top: 0;
6575 }
6676
@@ -76,6 +86,15 @@ const StorybookOverrides = styled.div`
7686 .octicon {
7787 fill: var(--teleport-colors-interactive-solid-accent-default);
7888 }
89+
90+ a {
91+ color: var(--teleport-colors-alpha-800);
92+ text-decoration: underline;
93+
94+ &:hover {
95+ color: var(--teleport-colors-text-main);
96+ }
97+ }
7998 }
8099
81100 .markdown-alert-tip {
@@ -122,6 +141,8 @@ const StorybookOverrides = styled.div`
122141
123142export function DocsLink ( props : HTMLChakraProps < 'a' > ) {
124143 const context = useContext ( DocsContext ) ;
144+ const url = props . href ?? '' ;
145+ const isExternal = url . startsWith ( 'http://' ) || url . startsWith ( 'https://' ) ;
125146
126147 function handleClick ( event : MouseEvent ) {
127148 const LEFT_BUTTON = 0 ;
@@ -134,16 +155,99 @@ export function DocsLink(props: HTMLChakraProps<'a'>) {
134155
135156 if ( isLeftClick ) {
136157 event . preventDefault ( ) ;
137- context . channel . emit (
138- NAVIGATE_URL ,
139- event . currentTarget . getAttribute ( 'href' ) ?? ''
140- ) ;
158+ context . channel . emit ( NAVIGATE_URL , url ) ;
141159 }
142160 }
143161
162+ if ( isExternal ) {
163+ const { children, ...rest } = props ;
164+
165+ return (
166+ < Link { ...rest } target = "_blank" >
167+ { children }
168+
169+ < ArrowUpRightIcon />
170+ </ Link >
171+ ) ;
172+ }
173+
144174 return < Link { ...props } onClick = { handleClick } /> ;
145175}
146176
177+ function HeadingWrapper ( { id, mt, mb, my, children, ...props } : HeadingProps ) {
178+ const [ copied , setCopied ] = useState ( false ) ;
179+ const [ , copy ] = useCopyToClipboard ( ) ;
180+
181+ const timeoutRef = useRef < number | null > ( null ) ;
182+
183+ const location = new URL ( window . location . href ) ;
184+ const docId = location . searchParams . get ( 'id' ) ?? '' ;
185+ const idValue = id ?? '' ;
186+ const url = location . origin + '/?path=/docs/' + docId + '#' + idValue ;
187+
188+ async function handleCopy ( ) {
189+ await copy ( url ) ;
190+
191+ setCopied ( true ) ;
192+
193+ if ( timeoutRef . current ) {
194+ window . clearTimeout ( timeoutRef . current ) ;
195+ }
196+
197+ timeoutRef . current = window . setTimeout ( ( ) => {
198+ setCopied ( false ) ;
199+ } , 1000 ) ;
200+ }
201+
202+ useEffect ( ( ) => {
203+ return ( ) => {
204+ if ( timeoutRef . current ) {
205+ window . clearTimeout ( timeoutRef . current ) ;
206+ }
207+ } ;
208+ } , [ ] ) ;
209+
210+ return (
211+ < Box
212+ display = "flex"
213+ alignItems = "center"
214+ ml = "-32px"
215+ mt = { mt }
216+ my = { my }
217+ mb = { mb }
218+ className = "group"
219+ >
220+ < Tooltip
221+ content = { copied ? 'Copied' : 'Copy link to clipboard' }
222+ openDelay = { 0 }
223+ positioning = { {
224+ placement : 'top' ,
225+ } }
226+ closeDelay = { 0 }
227+ closeOnClick = { false }
228+ >
229+ < IconButton
230+ onClick = { ( ) => {
231+ void handleCopy ( ) ;
232+ } }
233+ size = "sm"
234+ fill = "minimal"
235+ intent = "neutral"
236+ mr = "8px"
237+ opacity = { 0 }
238+ _groupHover = { { opacity : 1 } }
239+ >
240+ < ClipboardIcon />
241+ </ IconButton >
242+ </ Tooltip >
243+
244+ < Heading { ...props } id = { id } cursor = "pointer" >
245+ < a href = { url } > { children } </ a >
246+ </ Heading >
247+ </ Box >
248+ ) ;
249+ }
250+
147251export function DocsContainerWrapper ( {
148252 children,
149253 context,
@@ -161,33 +265,55 @@ export function DocsContainerWrapper({
161265 useEffect ( ( ) => {
162266 let timeout : number | null = null ;
163267
164- try {
165- const url = new URL ( window . parent . location . toString ( ) ) ;
166-
167- if ( url . hash ) {
168- const element = document . getElementById (
169- decodeURIComponent ( url . hash . substring ( 1 ) )
170- ) ;
171-
172- if ( element ) {
173- timeout = window . setTimeout ( ( ) => {
174- element . scrollIntoView ( {
175- behavior : 'smooth' ,
176- block : 'start' ,
177- inline : 'nearest' ,
178- } ) ;
179- } , 200 ) ;
268+ function scrollToHash ( ms : number ) {
269+ if ( timeout ) {
270+ window . clearTimeout ( timeout ) ;
271+ timeout = null ;
272+ }
273+
274+ try {
275+ const url = new URL ( window . parent . location . toString ( ) ) ;
276+
277+ if ( url . hash ) {
278+ const element = document . getElementById (
279+ decodeURIComponent ( url . hash . substring ( 1 ) )
280+ ) ;
281+
282+ if ( element ) {
283+ timeout = window . setTimeout ( ( ) => {
284+ const elementPosition =
285+ element . getBoundingClientRect ( ) . top + window . pageYOffset ;
286+ const offsetPosition = elementPosition - 20 ;
287+
288+ window . scrollTo ( {
289+ top : offsetPosition ,
290+ behavior : 'smooth' ,
291+ } ) ;
292+ } , ms ) ;
293+ }
180294 }
295+ } catch {
296+ // pass
181297 }
182- } catch {
183- // pass
184298 }
185299
300+ scrollToHash ( 200 ) ;
301+
302+ function handleChange ( ) {
303+ scrollToHash ( 0 ) ;
304+ }
305+
306+ window . parent . addEventListener ( 'popstate' , handleChange ) ;
307+ window . parent . addEventListener ( 'hashchange' , handleChange ) ;
308+
186309 return ( ) => {
187310 if ( timeout ) {
188311 window . clearTimeout ( timeout ) ;
189312 timeout = null ;
190313 }
314+
315+ window . parent . removeEventListener ( 'popstate' , handleChange ) ;
316+ window . parent . removeEventListener ( 'hashchange' , handleChange ) ;
191317 } ;
192318 } , [ ] ) ;
193319
@@ -197,18 +323,32 @@ export function DocsContainerWrapper({
197323 < MDXProvider
198324 components = { {
199325 h1 : props => (
200- < Heading { ...props } as = "h1" mt = { 3 } mb = { 4 } size = "3xl" />
326+ < HeadingWrapper { ...props } as = "h1" mt = { 3 } mb = { 4 } size = "3xl" />
201327 ) ,
202328 h2 : props => (
203- < Heading { ...props } as = "h2" mt = { 6 } mb = { 4 } size = "2xl" />
329+ < HeadingWrapper { ...props } as = "h2" mt = { 6 } mb = { 4 } size = "2xl" />
330+ ) ,
331+ h3 : props => (
332+ < HeadingWrapper { ...props } as = "h3" mt = { 3 } mb = { 3 } size = "xl" />
333+ ) ,
334+ h4 : props => (
335+ < HeadingWrapper
336+ { ...props }
337+ as = "h4"
338+ mt = { 4 }
339+ mb = { 2 }
340+ size = "lg"
341+ fontSize = "16px"
342+ />
343+ ) ,
344+ h5 : props => (
345+ < HeadingWrapper { ...props } as = "h5" mt = { 4 } mb = { 0 } size = "md" />
204346 ) ,
205- h3 : props => < Heading { ...props } as = "h3" mt = { 3 } mb = { 3 } size = "xl" /> ,
206- h4 : props => < Heading { ...props } as = "h4" mt = { 2 } mb = { 2 } size = "md" /> ,
207- h5 : props => < Heading { ...props } as = "h5" size = "sm" /> ,
208- h6 : props => < Heading { ...props } as = "h6" size = "xs" /> ,
347+ h6 : props => < HeadingWrapper { ...props } as = "h6" size = "sm" /> ,
209348 p : props => < Text { ...props } mb = { 2 } /> ,
210- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access
211- pre : props => < CodeBlock text = { props . children . props . children } /> ,
349+ em : props => < Text as = "em" fontStyle = "italic" { ...props } /> ,
350+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
351+ pre : props => < CodeBlock { ...props . children . props } /> ,
212352 ul : props => < List . Root mt = { 4 } mb = { 6 } pl = { 4 } { ...props } /> ,
213353 li : props => < List . Item pl = { 1 } { ...props } /> ,
214354 a : props => < DocsLink { ...props } /> ,
0 commit comments