tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
289
fork
atom
a tool for shared writing and social publishing
289
fork
atom
overview
issues
28
pulls
pipelines
styling mention with scoped to publicaiton
cozylittle.house
3 months ago
8da4f186
5961fe6f
+84
-25
2 changed files
expand all
collapse all
unified
split
components
Icons
GoBackTiny.tsx
Mention.tsx
+21
components/Icons/GoBackTiny.tsx
···
1
1
+
import { Props } from "./Props";
2
2
+
3
3
+
export const GoBackTiny = (props: Props) => {
4
4
+
return (
5
5
+
<svg
6
6
+
width="16"
7
7
+
height="16"
8
8
+
viewBox="0 0 16 16"
9
9
+
fill="none"
10
10
+
xmlns="http://www.w3.org/2000/svg"
11
11
+
>
12
12
+
<path
13
13
+
d="M7.40426 3L2.19592 8M2.19592 8L7.40426 13M2.19592 8H13.8041"
14
14
+
stroke="currentColor"
15
15
+
strokeWidth="2"
16
16
+
strokeLinecap="round"
17
17
+
strokeLinejoin="round"
18
18
+
/>
19
19
+
</svg>
20
20
+
);
21
21
+
};
+63
-25
components/Mention.tsx
···
9
9
import { ArrowRightTiny } from "components/Icons/ArrowRightTiny";
10
10
import { GoBackSmall } from "components/Icons/GoBackSmall";
11
11
import { SearchTiny } from "components/Icons/SearchTiny";
12
12
+
import { CloseTiny } from "./Icons/CloseTiny";
13
13
+
import { GoToArrow } from "./Icons/GoToArrow";
14
14
+
import { GoBackTiny } from "./Icons/GoBackTiny";
12
15
13
16
export function MentionAutocomplete(props: {
14
17
open: boolean;
···
18
21
coords: { top: number; left: number } | null;
19
22
}) {
20
23
const [searchQuery, setSearchQuery] = useState("");
24
24
+
const [noResults, setNoResults] = useState(false);
21
25
const inputRef = useRef<HTMLInputElement>(null);
22
26
const contentRef = useRef<HTMLDivElement>(null);
23
27
···
48
52
setSearchQuery("");
49
53
setScope({ type: "default" });
50
54
setSuggestionIndex(0);
55
55
+
setNoResults(false);
51
56
}
52
57
}, [props.open, setScope, setSuggestionIndex]);
58
58
+
59
59
+
// Handle timeout for showing "No results found"
60
60
+
useEffect(() => {
61
61
+
if (searchQuery && suggestions.length === 0) {
62
62
+
setNoResults(false);
63
63
+
const timer = setTimeout(() => {
64
64
+
setNoResults(true);
65
65
+
}, 2000);
66
66
+
return () => clearTimeout(timer);
67
67
+
} else {
68
68
+
setNoResults(false);
69
69
+
}
70
70
+
}, [searchQuery, suggestions.length]);
53
71
54
72
// Handle keyboard navigation
55
73
const handleKeyDown = (e: React.KeyboardEvent) => {
···
120
138
121
139
if (!props.open || !props.coords) return null;
122
140
123
123
-
const getHeader = (type: Mention["type"]) => {
141
141
+
const getHeader = (type: Mention["type"], scope?: MentionScope) => {
124
142
switch (type) {
125
143
case "did":
126
144
return "People";
127
145
case "publication":
128
146
return "Publications";
129
147
case "post":
130
130
-
return "Posts";
148
148
+
if (scope) {
149
149
+
return (
150
150
+
<ScopeHeader
151
151
+
scope={scope}
152
152
+
handleScopeChange={() => {
153
153
+
handleScopeChange({ type: "default" });
154
154
+
}}
155
155
+
/>
156
156
+
);
157
157
+
} else return "Posts";
131
158
}
132
159
};
133
160
···
164
191
max-h-(--radix-popover-content-available-height)
165
192
overflow-y-scroll`}
166
193
>
167
167
-
{/* Search input */}
168
168
-
<div className="flex items-center gap-2 px-2 py-1 border-b group-data-[side=top]/mention-menu:border-b-0 group-data-[side=top]/mention-menu:border-t border-border-light">
169
169
-
{scope.type === "publication" && (
170
170
-
<button
171
171
-
className="p-1 rounded hover:bg-accent-light text-tertiary hover:text-accent-contrast shrink-0"
172
172
-
onClick={() => handleScopeChange({ type: "default" })}
173
173
-
onMouseDown={(e) => e.preventDefault()}
174
174
-
>
175
175
-
<GoBackSmall className="w-4 h-4" />
176
176
-
</button>
177
177
-
)}
178
178
-
{scope.type === "publication" && (
179
179
-
<span className="text-sm font-bold text-secondary truncate shrink-0 max-w-[100px]">
180
180
-
{scope.name}
181
181
-
</span>
182
182
-
)}
194
194
+
{/* Dropdown Header */}
195
195
+
<div className="flex flex-col items-center gap-2 px-2 py-1 border-b group-data-[side=top]/mention-menu:border-b-0 group-data-[side=top]/mention-menu:border-t border-border-light">
183
196
<div className="flex items-center gap-1 flex-1 min-w-0">
184
197
<SearchTiny className="w-4 h-4 text-tertiary shrink-0" />
185
198
<input
186
199
ref={inputRef}
200
200
+
size={100}
187
201
type="text"
188
202
value={searchQuery}
189
203
onChange={(e) => {
···
197
211
? "Search posts..."
198
212
: "Search people & publications..."
199
213
}
200
200
-
className="flex-1 min-w-0 bg-transparent border-none outline-none text-sm placeholder:text-tertiary"
214
214
+
className="flex-1 w-full min-w-0 bg-transparent border-none outline-none text-sm placeholder:text-tertiary"
201
215
/>
202
216
</div>
203
217
</div>
204
218
{sortedSuggestions.length === 0 ? (
205
205
-
<div className="text-sm text-tertiary italic px-3 py-2">
219
219
+
<div className="text-sm text-tertiary italic px-3 py-1 text-center">
206
220
{searchQuery
207
207
-
? "No results found"
221
221
+
? noResults
222
222
+
? "No results found..."
223
223
+
: "Searching..."
208
224
: scope.type === "publication"
209
209
-
? "Type to search posts"
210
210
-
: "Type to search"}
225
225
+
? "Start typing to search posts"
226
226
+
: "Start typing to search people and publications"}
211
227
</div>
212
228
) : (
213
229
<ul className="list-none p-0 text-sm flex flex-col group-data-[side=top]/mention-menu:flex-col-reverse">
214
230
{sortedSuggestions.map((result, index) => {
215
231
const prevResult = sortedSuggestions[index - 1];
216
232
const showHeader =
217
217
-
prevResult && prevResult.type !== result.type;
233
233
+
index === 0 ||
234
234
+
(prevResult && prevResult.type !== result.type);
218
235
219
236
return (
220
237
<Fragment
···
226
243
<hr className="border-border-light mx-1 my-1" />
227
244
)}
228
245
<div className="text-xs text-tertiary font-bold pt-1 px-2">
229
229
-
{getHeader(result.type)}
246
246
+
{getHeader(result.type, scope)}
230
247
</div>
231
248
</>
232
249
)}
···
387
404
selected={props.selected}
388
405
/>
389
406
);
407
407
+
};
408
408
+
409
409
+
const ScopeHeader = (props: {
410
410
+
scope: MentionScope;
411
411
+
handleScopeChange: () => void;
412
412
+
}) => {
413
413
+
if (props.scope.type === "default") return;
414
414
+
if (props.scope.type === "publication")
415
415
+
return (
416
416
+
<button
417
417
+
className="w-full flex flex-row gap-2 pt-1 rounded text-tertiary hover:text-accent-contrast shrink-0 text-xs"
418
418
+
onClick={() => props.handleScopeChange()}
419
419
+
onMouseDown={(e) => e.preventDefault()}
420
420
+
>
421
421
+
<GoBackTiny className="shrink-0 " />
422
422
+
423
423
+
<div className="grow w-full truncate text-left">
424
424
+
Posts from {props.scope.name}
425
425
+
</div>
426
426
+
</button>
427
427
+
);
390
428
};
391
429
392
430
export type Mention =