-
Notifications
You must be signed in to change notification settings - Fork 1
/
TextBubble.tsx
118 lines (114 loc) · 2.81 KB
/
TextBubble.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import {
FunctionComponent,
h,
useCallback,
useMemo,
useState,
} from "./deps/preact.tsx";
import { ID, toId } from "./id.ts";
import { Page, PageProps } from "./Page.tsx";
import { Bubble } from "./storage.ts";
import { BubbleOperators, Source } from "./useBubbles.ts";
import { useTheme } from "./useTheme.ts";
export interface TextBubble extends BubbleOperators {
pages: [Bubble, ...Bubble[]];
whiteList: Set<string>;
delay: number;
noIndent?: boolean;
source: Source;
prefetch: (project: string, title: string) => void;
onClick?: h.JSX.MouseEventHandler<HTMLDivElement>;
}
export const TextBubble: FunctionComponent<TextBubble> = (
{ pages, onClick, source, whiteList, ...rest },
) => {
const [activeTab, setActiveTab] = useState(
toId(pages[0].project, pages[0].titleLc),
);
const pageStyle = useMemo(() => ({
top: `${source.position.top}px`,
maxWidth: `${source.position.maxWidth}px`,
...("left" in source.position
? {
left: `${source.position.left}px`,
}
: {
right: `${source.position.right}px`,
}),
}), [source.position]);
return (
<div
className="text-bubble"
style={pageStyle}
onClick={onClick}
>
{pages.length > 1 &&
(
<div role="tablist">
{pages.map((page) => (
<Tab
key={toId(page.project, page.titleLc)}
project={page.project}
titleLc={page.titleLc}
selected={activeTab ===
toId(page.project, page.titleLc)}
tabSelector={setActiveTab}
/>
))}
</div>
)}
{pages.map((page) => (
<TabPanel
key={toId(page.project, page.titleLc)}
selected={activeTab ===
toId(page.project, page.titleLc)}
{...page}
title={page.lines[0].text}
hash={source.hash}
linkTo={source.linkTo}
whiteList={whiteList}
{...rest}
/>
))}
</div>
);
};
const Tab: FunctionComponent<
{
project: string;
titleLc: string;
selected: boolean;
tabSelector: (id: ID) => void;
}
> = ({ project, titleLc, tabSelector, selected }) => {
const handleClick = useCallback(() => tabSelector(toId(project, titleLc)), [
project,
titleLc,
]);
const theme = useTheme(project);
return (
<button
role="tab"
aria-selected={selected}
data-theme={theme}
tabIndex={-1}
onClick={handleClick}
>
{project}
</button>
);
};
const TabPanel: FunctionComponent<{ selected: boolean } & PageProps> = (
{ selected, ...rest },
) => {
const theme = useTheme(rest.project);
return (
<div
role="tabpanel"
data-theme={theme}
hidden={!selected}
>
<Page {...rest} />
</div>
);
};