Skip to content

Commit 916c8a9

Browse files
committed
Advance TypeDoc doc rendering
1 parent 8600665 commit 916c8a9

File tree

35 files changed

+1287
-312
lines changed

35 files changed

+1287
-312
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"start": "env NODE_ENV=development ts-node -r tsconfig-paths/register devServer.ts"
1515
},
1616
"dependencies": {
17-
"@date-fns/docs": "0.21.1",
17+
"@date-fns/docs": "0.22.0",
1818
"@sentry/browser": "^5.30.0",
1919
"@sentry/tracing": "^5.30.0",
2020
"@switcher/preact": "2.3.0",

src/ui/components/DocHeader/styles.css.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ import { style } from '@vanilla-extract/css'
22

33
export const header = style({
44
fontFamily: 'monospace',
5+
display: 'flex',
6+
justifyContent: 'space-between',
7+
alignItems: 'center',
58
})

src/ui/components/Drop/index.tsx

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import classNames from 'classnames'
2+
import isEqual from 'lodash/isEqual'
3+
import { ComponentChildren, h, JSX, RefObject } from 'preact'
4+
import { useCallback, useEffect, useState } from 'preact/hooks'
5+
6+
import * as styles from './styles.css'
7+
8+
export const DropInner = Inner
9+
10+
export interface DropArea {
11+
x: number
12+
y: number
13+
width: number
14+
height: number
15+
}
16+
17+
export type DropPosition = 'above' | 'below' | 'left' | 'right'
18+
19+
export interface DropProps {
20+
children: ComponentChildren
21+
area: DropArea
22+
ghost?: boolean
23+
innerRef?: RefObject<HTMLDivElement | undefined>
24+
onClick?: JSX.MouseEventHandler<any>
25+
preferPosition?: DropPosition
26+
}
27+
28+
/**
29+
* Floating popup, used for dropdown menus, tooltips, etc.
30+
*/
31+
export function Drop({
32+
area,
33+
children,
34+
ghost,
35+
innerRef,
36+
onClick,
37+
preferPosition,
38+
}: DropProps) {
39+
const [trianglePosition, setTrianglePosition] = useState<TrianglePosition>(
40+
'below'
41+
)
42+
43+
const [wrapper, setWrapper] = useState<HTMLDivElement | undefined>(undefined)
44+
const [wrapperArea, setWrapperArea] = useState<DropArea | undefined>(
45+
undefined
46+
)
47+
48+
useEffect(() => {
49+
if (!wrapper || !wrapperArea?.width) return
50+
51+
const windowWidth = window.innerWidth
52+
const windowHeight = window.innerHeight
53+
const width = wrapperArea.width
54+
const height = wrapperArea.height
55+
56+
wrapper.style.opacity = '1'
57+
58+
let verticalLeft = area.x + area.width / 2 - width / 2
59+
if (verticalLeft < 0) verticalLeft = 0
60+
else if (verticalLeft + width > windowWidth)
61+
verticalLeft = windowWidth - width
62+
63+
let horizontalTop = area.y + area.height / 2 - height / 2
64+
if (horizontalTop < 0) horizontalTop = 0
65+
else if (horizontalTop + height > windowHeight)
66+
horizontalTop = windowHeight - height
67+
68+
const positioning = {
69+
below: () => {
70+
const belowTop = area.y + area.height
71+
if (belowTop + height < windowHeight) {
72+
wrapper.style.top = `${belowTop}px`
73+
wrapper.style.left = `${verticalLeft}px`
74+
setTrianglePosition('above')
75+
return true
76+
}
77+
},
78+
79+
above: () => {
80+
const aboveTop = area.y - height
81+
if (aboveTop > 0) {
82+
wrapper.style.top = `${aboveTop}px`
83+
wrapper.style.left = `${verticalLeft}px`
84+
setTrianglePosition('below')
85+
return true
86+
}
87+
},
88+
89+
left: () => {
90+
const left = area.x - width
91+
if (left > 0) {
92+
wrapper.style.top = `${horizontalTop}px`
93+
wrapper.style.left = `${left}px`
94+
setTrianglePosition('right')
95+
return true
96+
}
97+
},
98+
99+
right: () => {
100+
const right = area.x + area.width
101+
if (right + width < windowWidth) {
102+
wrapper.style.top = `${horizontalTop}px`
103+
wrapper.style.left = `${right}px`
104+
setTrianglePosition('left')
105+
return true
106+
}
107+
},
108+
}
109+
110+
if (preferPosition === 'left') {
111+
circlePositioning([positioning.left, positioning.right])
112+
} else if (preferPosition === 'right') {
113+
circlePositioning([positioning.right, positioning.left])
114+
} else if (preferPosition === 'above') {
115+
circlePositioning([positioning.above, positioning.below])
116+
} else {
117+
circlePositioning([positioning.below, positioning.above])
118+
}
119+
}, [
120+
trianglePosition,
121+
wrapper,
122+
wrapperArea?.width,
123+
wrapperArea?.height,
124+
area.x,
125+
area.width,
126+
area.y,
127+
area.height,
128+
preferPosition,
129+
])
130+
131+
const wrapperCallback = useCallback(
132+
(el: HTMLDivElement | null) => {
133+
if (!el) return
134+
if (innerRef) innerRef.current = el
135+
setWrapper(el)
136+
},
137+
[innerRef]
138+
)
139+
140+
useEffect(() => {
141+
const newArea = wrapper?.getBoundingClientRect()
142+
if (!isEqual(newArea, wrapperArea)) setWrapperArea(newArea)
143+
})
144+
145+
return (
146+
<div
147+
class={classNames(
148+
styles.drop,
149+
ghost && styles.ghost,
150+
['above', 'below'].includes(trianglePosition) && styles.vertical
151+
)}
152+
ref={wrapperCallback}
153+
onClick={onClick}
154+
>
155+
{(trianglePosition === 'above' || trianglePosition === 'left') && (
156+
<Triangle position={trianglePosition} />
157+
)}
158+
159+
<Inner>
160+
{typeof children === 'string' ? (
161+
<div class={styles.string}>{children}</div>
162+
) : (
163+
children
164+
)}
165+
</Inner>
166+
167+
{(trianglePosition === 'below' || trianglePosition === 'right') && (
168+
<Triangle position={trianglePosition} />
169+
)}
170+
</div>
171+
)
172+
}
173+
174+
function circlePositioning(fns: Array<() => void | boolean>) {
175+
for (let i = 0; i < fns.length; i++) {
176+
if (fns[i]?.()) return
177+
}
178+
}
179+
180+
export type TrianglePosition = 'above' | 'below' | 'left' | 'right'
181+
182+
interface TriangleProps {
183+
position: TrianglePosition
184+
}
185+
186+
function Triangle(position: TriangleProps) {
187+
return (
188+
<div
189+
class={classNames(styles.triangle, styles.position[position.position])}
190+
></div>
191+
)
192+
}
193+
194+
interface InnerProps {
195+
children: ComponentChildren
196+
}
197+
198+
function Inner({ children }: InnerProps) {
199+
return <div class={classNames(styles.inner)}>{children} </div>
200+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { style, styleVariants } from '@vanilla-extract/css'
2+
3+
export const drop = style({
4+
position: 'fixed',
5+
opacity: 0,
6+
transition: 'opacity 150ms',
7+
zIndex: 20,
8+
maxWidth: '30rem',
9+
display: 'flex',
10+
alignItems: 'center',
11+
})
12+
13+
export const vertical = style({
14+
flexDirection: 'column',
15+
})
16+
17+
export const ghost = style({
18+
pointerEvents: 'none',
19+
})
20+
21+
export const triangle = style({
22+
background: 'var(--color-tooltip-canvas)',
23+
width: '0.75rem',
24+
height: '0.5rem',
25+
position: 'relative',
26+
marginBottom: '-1px',
27+
})
28+
29+
export const position = styleVariants({
30+
above: {
31+
clipPath: 'polygon(50% 0%, 0% 100%, 100% 100%)',
32+
},
33+
34+
below: {
35+
clipPath: 'polygon(50% 100%, 0 0, 100% 0)',
36+
},
37+
38+
left: {
39+
clipPath: 'polygon(0 50%, 100% 100%, 100% 0)',
40+
},
41+
42+
right: {
43+
clipPath: 'polygon(0 0, 0 100%, 100% 50%)',
44+
},
45+
})
46+
47+
export const inner = style({
48+
background: 'var(--color-tooltip-canvas)',
49+
color: 'var(--color-tooltip-ink)',
50+
borderRadius: '3px',
51+
overflow: 'hidden',
52+
textAlign: 'center',
53+
})
54+
55+
export const string = style({
56+
padding: '0.5rem',
57+
})
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { match } from 'assert'
2+
import { FunctionComponent, h } from 'preact'
3+
import { useEffect, useRef } from 'preact/hooks'
4+
import * as styles from './styles.css'
5+
6+
interface IdHightlightProps {
7+
id: string | undefined
8+
match?: (id: string, hash: string) => boolean
9+
}
10+
11+
export const IdHightlight: FunctionComponent<IdHightlightProps> = ({
12+
id,
13+
children,
14+
match,
15+
}) => {
16+
const spanRef = useRef<HTMLSpanElement>(null)
17+
const stopAnimation = useRef<() => void>()
18+
19+
useEffect(() => {
20+
if (!id || spanRef.current === null) return
21+
22+
const skip = match
23+
? !match(id, location.hash)
24+
: location.hash.slice(1) !== id
25+
if (skip) return
26+
27+
stopAnimation.current?.()
28+
29+
const span = spanRef.current
30+
31+
!isInViewport(span) && span.scrollIntoView({ behavior: 'smooth' })
32+
33+
const cbOut = () => {
34+
span.removeEventListener('transitionend', cbOut)
35+
36+
span.classList.remove(styles.highlightIn)
37+
span.classList.remove(styles.highlightOut)
38+
39+
stopAnimation.current = undefined
40+
}
41+
42+
const cbIn = () => {
43+
span.removeEventListener('transitionend', cbIn)
44+
45+
span.addEventListener('transitionend', cbOut)
46+
span.classList.add(styles.highlightOut)
47+
}
48+
49+
stopAnimation.current = () => {
50+
span.removeEventListener('transitionend', cbIn)
51+
cbOut()
52+
stopAnimation.current = undefined
53+
}
54+
55+
spanRef.current.addEventListener('transitionend', cbIn)
56+
span.classList.add(styles.highlightIn)
57+
}, [id, location.hash, spanRef.current])
58+
59+
return (
60+
<span ref={spanRef} class={styles.text}>
61+
{children}
62+
</span>
63+
)
64+
}
65+
66+
function isInViewport(element: HTMLElement): boolean {
67+
const rect = element.getBoundingClientRect()
68+
return (
69+
rect.top >= 0 &&
70+
rect.left >= 0 &&
71+
rect.bottom <=
72+
(window.innerHeight || document.documentElement.clientHeight) &&
73+
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
74+
)
75+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { style } from '@vanilla-extract/css'
2+
3+
export const text = style({
4+
backgroundColor: '#fffe2500',
5+
})
6+
7+
export const highlightIn = style({
8+
backgroundColor: '#fffe25',
9+
transition: 'background-color 0.25s ease-out',
10+
})
11+
12+
export const highlightOut = style({
13+
backgroundColor: '#fffe2500',
14+
transition: 'background-color 2.5s ease-out',
15+
})
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Fragment, FunctionComponent, h } from 'preact'
2+
3+
interface MissingProps {
4+
data?: unknown
5+
message?: string
6+
}
7+
8+
export const Missing: FunctionComponent<MissingProps> = ({ message, data }) => (
9+
<div
10+
onClick={(e) => {
11+
if (e.shiftKey) console.log(data)
12+
}}
13+
>
14+
<div>
15+
{message ? message + ' ' : ''}If you see this,{' '}
16+
<a href="https://twitter.com/kossnocorp">ping me</a>.
17+
</div>
18+
</div>
19+
)

0 commit comments

Comments
 (0)