@c-rex/components 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +78 -62
- package/src/article/article-action-bar.tsx +89 -0
- package/src/article/article-content.tsx +55 -0
- package/src/autocomplete.tsx +55 -50
- package/src/breadcrumb.tsx +3 -1
- package/src/directoryNodes/tree-of-content.tsx +49 -0
- package/src/{bookmark-button.tsx → favorites/bookmark-button.tsx} +12 -3
- package/src/{favorite-button.tsx → favorites/favorite-button.tsx} +1 -1
- package/src/generated/client-components.tsx +1350 -0
- package/src/generated/create-client-request.tsx +105 -0
- package/src/generated/create-server-request.tsx +61 -0
- package/src/generated/create-suggestions-request.tsx +56 -0
- package/src/generated/server-components.tsx +1056 -0
- package/src/generated/suggestions.tsx +299 -0
- package/src/info/bookmark.tsx +51 -0
- package/src/info/info-table.tsx +127 -60
- package/src/info/shared.tsx +1 -1
- package/src/navbar/language-switcher/shared.tsx +1 -1
- package/src/navbar/navbar.tsx +1 -1
- package/src/{stories → navbar/stories}/navbar.stories.tsx +1 -1
- package/src/page-wrapper.tsx +1 -1
- package/src/renditions/file-download.tsx +84 -0
- package/src/renditions/html.tsx +55 -0
- package/src/renditions/image/container.tsx +52 -0
- package/src/renditions/image/rendition.tsx +61 -0
- package/src/{dialog-filter.tsx → results/dialog-filter.tsx} +22 -23
- package/src/results/filter-navbar.tsx +241 -0
- package/src/results/filter-sidebar/index.tsx +125 -0
- package/src/results/filter-sidebar/utils.ts +164 -0
- package/src/{pagination.tsx → results/pagination.tsx} +12 -10
- package/src/results/result-container.tsx +70 -0
- package/src/{stories/blog-view.stories.tsx → results/stories/cards.stories.tsx} +1 -1
- package/src/{stories/table-view.stories.tsx → results/stories/table.stories.tsx} +1 -1
- package/src/results/table-with-images.tsx +140 -0
- package/src/{result-view → results}/table.tsx +1 -2
- package/src/results/utils.ts +67 -0
- package/src/{navbar/search-input.tsx → search-input.tsx} +9 -6
- package/src/share-button.tsx +49 -0
- package/src/stores/search-settings-store.ts +1 -1
- package/src/blur-image.tsx +0 -23
- package/src/left-sidebar.tsx +0 -90
- package/src/result-list.tsx +0 -43
- package/src/result-view/table-with-images.tsx +0 -199
- package/src/right-sidebar.tsx +0 -70
- package/src/search-modal.tsx +0 -140
- package/src/stories/blur-image.stories.tsx +0 -51
- package/src/stories/sidebar.stories.tsx +0 -94
- /package/src/{file-icon.tsx → icons/file-icon.tsx} +0 -0
- /package/src/{flag.tsx → icons/flag-icon.tsx} +0 -0
- /package/src/{loading.tsx → icons/loading.tsx} +0 -0
- /package/src/{result-view/blog.tsx → results/cards.tsx} +0 -0
- /package/src/{empty.tsx → results/empty.tsx} +0 -0
- /package/src/{stories → results/stories}/empty.stories.tsx +0 -0
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
import React, { FC, useState } from "react";
|
|
4
4
|
import { FileCheck, FileX, Search } from "lucide-react";
|
|
5
5
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@c-rex/ui/tooltip";
|
|
6
|
-
import { AutoComplete } from "../autocomplete";
|
|
7
6
|
import { cn } from "@c-rex/utils";
|
|
7
|
+
import { InformationUnitsSuggestions } from "./generated/suggestions";
|
|
8
|
+
import { useQueryState } from "nuqs";
|
|
8
9
|
|
|
9
10
|
type PlacedOn = "NAVBAR" | "BODY"
|
|
10
11
|
type Props = {
|
|
@@ -14,6 +15,7 @@ type Props = {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export const SearchInput: FC<Props> = ({ showInput, showPkgFilter, placedOn = "NAVBAR" }) => {
|
|
18
|
+
const [pkg, setPkg] = useQueryState("package");
|
|
17
19
|
const [checked, setChecked] = useState<boolean>(true);
|
|
18
20
|
|
|
19
21
|
if (!showInput) return null
|
|
@@ -25,11 +27,12 @@ export const SearchInput: FC<Props> = ({ showInput, showPkgFilter, placedOn = "N
|
|
|
25
27
|
)}>
|
|
26
28
|
<Search className="shrink-0 opacity-50" />
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
{/*
|
|
31
|
+
Add scope=pkgID if checked is true
|
|
32
|
+
*/}
|
|
33
|
+
<InformationUnitsSuggestions embedded onSelectParams={
|
|
34
|
+
(pkg && checked) ? [{ key: "packages", value: pkg }] : []
|
|
35
|
+
} />
|
|
33
36
|
|
|
34
37
|
{showPkgFilter && <>
|
|
35
38
|
<TooltipProvider>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Button } from "@c-rex/ui/button";
|
|
5
|
+
import { Share2 } from "lucide-react"
|
|
6
|
+
|
|
7
|
+
export const ShareButton = () => {
|
|
8
|
+
const [copied, setCopied] = useState(false);
|
|
9
|
+
const [shareData, setShareData] = useState({
|
|
10
|
+
title: '',
|
|
11
|
+
url: '',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
setShareData({
|
|
16
|
+
title: document.title,
|
|
17
|
+
url: window.location.href,
|
|
18
|
+
});
|
|
19
|
+
}, []);
|
|
20
|
+
|
|
21
|
+
const handleShare = async () => {
|
|
22
|
+
if (navigator.share) {
|
|
23
|
+
try {
|
|
24
|
+
await navigator.share(shareData);
|
|
25
|
+
console.log('Compartilhado com sucesso');
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.log('Erro ao compartilhar:', err);
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
navigator.clipboard.writeText(shareData.url);
|
|
31
|
+
setCopied(true);
|
|
32
|
+
setTimeout(() => setCopied(false), 2000);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div style={{ fontFamily: 'sans-serif' }}>
|
|
38
|
+
<Button
|
|
39
|
+
variant="ghost"
|
|
40
|
+
size="icon"
|
|
41
|
+
onClick={handleShare}
|
|
42
|
+
>
|
|
43
|
+
<Share2 />
|
|
44
|
+
</Button>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
package/src/blur-image.tsx
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React, { useState } from "react";
|
|
4
|
-
import type { ComponentProps } from "react";
|
|
5
|
-
import Image from "next/image";
|
|
6
|
-
import { cn } from "@c-rex/utils";
|
|
7
|
-
|
|
8
|
-
export const BlurImage = (props: ComponentProps<typeof Image>) => {
|
|
9
|
-
const [isLoading, setLoading] = useState(true);
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
<Image
|
|
13
|
-
{...props}
|
|
14
|
-
alt={props.alt}
|
|
15
|
-
className={cn(
|
|
16
|
-
props.className,
|
|
17
|
-
"duration-500 ease-in-out",
|
|
18
|
-
isLoading ? "blur-sm" : "blur-0",
|
|
19
|
-
)}
|
|
20
|
-
onLoad={() => setLoading(false)}
|
|
21
|
-
/>
|
|
22
|
-
);
|
|
23
|
-
}
|
package/src/left-sidebar.tsx
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React, { ComponentProps, JSX } from "react";
|
|
4
|
-
import {
|
|
5
|
-
Sidebar,
|
|
6
|
-
SidebarContent,
|
|
7
|
-
SidebarGroup,
|
|
8
|
-
SidebarMenu,
|
|
9
|
-
SidebarMenuButton,
|
|
10
|
-
SidebarMenuItem,
|
|
11
|
-
SidebarMenuSub,
|
|
12
|
-
SidebarMenuSubButton,
|
|
13
|
-
SidebarMenuSubItem,
|
|
14
|
-
} from "@c-rex/ui/sidebar";
|
|
15
|
-
import { Skeleton } from "@c-rex/ui/skeleton";
|
|
16
|
-
import { TreeOfContent } from "@c-rex/interfaces";
|
|
17
|
-
import { cn } from "@c-rex/utils";
|
|
18
|
-
|
|
19
|
-
interface SidebarProps extends ComponentProps<typeof Sidebar> {
|
|
20
|
-
data: TreeOfContent[];
|
|
21
|
-
loading?: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function LeftSidebar({ data, loading, className, lang, ...props }: SidebarProps) {
|
|
25
|
-
|
|
26
|
-
const tableOfContentGroup = (): JSX.Element | null => {
|
|
27
|
-
if (loading) {
|
|
28
|
-
return (
|
|
29
|
-
<SidebarGroup className="pt-4">
|
|
30
|
-
<Skeleton className="w-auto h-10 mb-2" />
|
|
31
|
-
<Skeleton className="w-auto h-10 mb-2" />
|
|
32
|
-
<Skeleton className="w-auto h-10 mb-2 ml-8" />
|
|
33
|
-
<Skeleton className="w-auto h-10 mb-2 ml-8" />
|
|
34
|
-
<Skeleton className="w-auto h-10 mb-2 ml-8" />
|
|
35
|
-
<Skeleton className="w-auto h-10 mb-2 ml-8" />
|
|
36
|
-
<Skeleton className="w-auto h-10 mb-2" />
|
|
37
|
-
</SidebarGroup>
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (data.length == 0) return null;
|
|
42
|
-
|
|
43
|
-
const langProp = {} as any
|
|
44
|
-
|
|
45
|
-
if (lang) {
|
|
46
|
-
langProp.lang = lang
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<SidebarGroup {...langProp}>
|
|
51
|
-
<SidebarMenu>
|
|
52
|
-
{data.map((item) => (
|
|
53
|
-
<SidebarMenuItem key={item.id}>
|
|
54
|
-
<SidebarMenuButton asChild isActive={item.active}>
|
|
55
|
-
<a href={item.link} title={item.label}>
|
|
56
|
-
{item.label}
|
|
57
|
-
</a>
|
|
58
|
-
</SidebarMenuButton>
|
|
59
|
-
|
|
60
|
-
{item.children?.length ? (
|
|
61
|
-
<SidebarMenuSub>
|
|
62
|
-
{item.children.map((item) => (
|
|
63
|
-
<SidebarMenuSubItem key={item.label}>
|
|
64
|
-
<SidebarMenuSubButton
|
|
65
|
-
asChild
|
|
66
|
-
isActive={item.active}
|
|
67
|
-
>
|
|
68
|
-
<a href={item.link} title={item.label}>
|
|
69
|
-
{item.label}
|
|
70
|
-
</a>
|
|
71
|
-
</SidebarMenuSubButton>
|
|
72
|
-
</SidebarMenuSubItem>
|
|
73
|
-
))}
|
|
74
|
-
</SidebarMenuSub>
|
|
75
|
-
) : null}
|
|
76
|
-
</SidebarMenuItem>
|
|
77
|
-
))}
|
|
78
|
-
</SidebarMenu>
|
|
79
|
-
</SidebarGroup>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<Sidebar className={cn(className)} {...props}>
|
|
85
|
-
<SidebarContent>
|
|
86
|
-
{tableOfContentGroup()}
|
|
87
|
-
</SidebarContent>
|
|
88
|
-
</Sidebar>
|
|
89
|
-
);
|
|
90
|
-
}
|
package/src/result-list.tsx
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import React, { FC, ReactNode } from "react";
|
|
2
|
-
import { DefaultPageInfo, informationUnitsResponseItem, TopicsResponseItem, } from "@c-rex/interfaces";
|
|
3
|
-
import { Empty } from "./empty";
|
|
4
|
-
import BlogView from './result-view/blog';
|
|
5
|
-
import TableView from './result-view/table';
|
|
6
|
-
import { Pagination } from "./pagination";
|
|
7
|
-
import { useAppConfig } from "@c-rex/contexts/config-provider";
|
|
8
|
-
import { ResultViewStyles } from "@c-rex/types";
|
|
9
|
-
import TableWithImageView from "./result-view/table-with-images";
|
|
10
|
-
|
|
11
|
-
interface ResultListProps {
|
|
12
|
-
items: informationUnitsResponseItem[] | TopicsResponseItem[];
|
|
13
|
-
pagination: DefaultPageInfo;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const ResultList: FC<ResultListProps> = ({ items, pagination }: ResultListProps) => {
|
|
17
|
-
const { configs } = useAppConfig()
|
|
18
|
-
|
|
19
|
-
const listComponent: Record<ResultViewStyles, ReactNode> = {
|
|
20
|
-
"cards": <BlogView items={items as TopicsResponseItem[]} />,
|
|
21
|
-
"table": <TableView items={items as informationUnitsResponseItem[]} />,
|
|
22
|
-
"table-with-images": <TableWithImageView items={items as informationUnitsResponseItem[]} />,
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (!(configs.results.resultViewStyle in listComponent)) {
|
|
26
|
-
throw new Error(`Unsupported result view style: ${configs.results.resultViewStyle}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (items.length === 0) {
|
|
30
|
-
return <Empty />;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<>
|
|
35
|
-
{listComponent[configs.results.resultViewStyle as ResultViewStyles]}
|
|
36
|
-
|
|
37
|
-
<Pagination
|
|
38
|
-
totalPages={pagination.pageCount}
|
|
39
|
-
currentPage={pagination.pageNumber}
|
|
40
|
-
/>
|
|
41
|
-
</>
|
|
42
|
-
);
|
|
43
|
-
};
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import { FC, useEffect, useState } from "react";
|
|
2
|
-
import { informationUnitsResponseItem } from "@c-rex/interfaces";
|
|
3
|
-
import { CloudDownload, Eye, FileStack, ImageOff } from "lucide-react";
|
|
4
|
-
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@c-rex/ui/dropdown-menu";
|
|
5
|
-
import { cn } from "@c-rex/utils";
|
|
6
|
-
import { useQueryState } from "nuqs";
|
|
7
|
-
import { Skeleton } from "@c-rex/ui/skeleton";
|
|
8
|
-
import { FavoriteButton } from "../favorite-button";
|
|
9
|
-
import { Button } from "@c-rex/ui/button";
|
|
10
|
-
import { Tooltip, TooltipContent, TooltipTrigger } from "@c-rex/ui/tooltip";
|
|
11
|
-
import { Favorite, ResultTypes } from "@c-rex/types";
|
|
12
|
-
import { FileIcon } from "../file-icon";
|
|
13
|
-
import { useFavoritesStore } from "../stores/favorites-store";
|
|
14
|
-
import { BookmarkButton } from "../bookmark-button";
|
|
15
|
-
import { FaRegBookmark } from "react-icons/fa6";
|
|
16
|
-
|
|
17
|
-
interface TableWithImageViewProps {
|
|
18
|
-
items: informationUnitsResponseItem[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const Image: FC<{ id: string }> = ({ id }) => {
|
|
22
|
-
const [imageUrl, setImageUrl] = useState("");
|
|
23
|
-
const [loading, setLoading] = useState(true);
|
|
24
|
-
const [error, setError] = useState(false);
|
|
25
|
-
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
let ignore = false;
|
|
28
|
-
|
|
29
|
-
async function fetchImage() {
|
|
30
|
-
try {
|
|
31
|
-
const res = await fetch(`https://picsum.photos/v2/list?limit=1`);
|
|
32
|
-
|
|
33
|
-
{/*
|
|
34
|
-
TODO: talvez vamos receber mais de uma imagem, mas por enquanto pegamos apenas a primeira na lista
|
|
35
|
-
*/}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const data = await res.json();
|
|
39
|
-
if (!ignore) {
|
|
40
|
-
setImageUrl(data[0].download_url);
|
|
41
|
-
setLoading(false);
|
|
42
|
-
}
|
|
43
|
-
} catch (err) {
|
|
44
|
-
setError(true);
|
|
45
|
-
console.error("Error loading image", err);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
fetchImage();
|
|
50
|
-
return () => {
|
|
51
|
-
ignore = true
|
|
52
|
-
};
|
|
53
|
-
}, [id]);
|
|
54
|
-
|
|
55
|
-
if (error) {
|
|
56
|
-
return <ImageOff className="h-16 w-16 text-muted" />;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<div className="h-16 w-16 flex items-center">
|
|
61
|
-
{loading ? (
|
|
62
|
-
<Skeleton className="h-16 w-16" />
|
|
63
|
-
) : (
|
|
64
|
-
<img
|
|
65
|
-
src={imageUrl}
|
|
66
|
-
loading="lazy"
|
|
67
|
-
onLoad={() => setLoading(false)}
|
|
68
|
-
/>
|
|
69
|
-
)}
|
|
70
|
-
</div>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const TableWithImageView: FC<TableWithImageViewProps> = ({ items }) => {
|
|
75
|
-
const [query] = useQueryState("search");
|
|
76
|
-
|
|
77
|
-
const favoritesDocuments = useFavoritesStore((state) => state.documents);
|
|
78
|
-
|
|
79
|
-
return (
|
|
80
|
-
<div className="mb-6">
|
|
81
|
-
{items.map((item, index) => {
|
|
82
|
-
const isFavoriteDocument = Object.keys(favoritesDocuments).some((docId) => docId === item.shortId);
|
|
83
|
-
let topicList: Favorite[] = []
|
|
84
|
-
|
|
85
|
-
if (isFavoriteDocument && favoritesDocuments[item.shortId]) {
|
|
86
|
-
topicList = favoritesDocuments[item.shortId]?.topics || []
|
|
87
|
-
}
|
|
88
|
-
return (
|
|
89
|
-
<div
|
|
90
|
-
key={index}
|
|
91
|
-
className={cn(
|
|
92
|
-
"min-h-12 flex flex-wrap items-center border px-4 py-2 gap-2 rounded mb-2",
|
|
93
|
-
`c-rex-result-item c-rex-result-${item.type.toLowerCase()}`,
|
|
94
|
-
item.disabled && "c-rex-result-item-disabled",
|
|
95
|
-
)}
|
|
96
|
-
>
|
|
97
|
-
|
|
98
|
-
<div className="h-16 w-16 flex items-center">
|
|
99
|
-
<Image id={item.shortId} />
|
|
100
|
-
</div>
|
|
101
|
-
|
|
102
|
-
<div className="flex-1 p-2 flex flex-col">
|
|
103
|
-
<span className=" text-sm text-muted-foreground">
|
|
104
|
-
{item.language.split("-")[0]?.toUpperCase()}
|
|
105
|
-
</span>
|
|
106
|
-
|
|
107
|
-
<span className="text-lg font-medium">
|
|
108
|
-
{item.disabled ? (
|
|
109
|
-
item.title
|
|
110
|
-
) : (
|
|
111
|
-
<a className="hover:underline" href={`${item.link}?q=${query}`}>{item.title}</a>
|
|
112
|
-
)}
|
|
113
|
-
</span>
|
|
114
|
-
|
|
115
|
-
<span className="text-sm">
|
|
116
|
-
{item.type}
|
|
117
|
-
</span>
|
|
118
|
-
<span className="text-sm">
|
|
119
|
-
short description here {/*
|
|
120
|
-
TODO: get abstract if has the value key on it. Same behavior on class props
|
|
121
|
-
|
|
122
|
-
abstract and description are not being returned by the API yet
|
|
123
|
-
*/}
|
|
124
|
-
</span>
|
|
125
|
-
</div>
|
|
126
|
-
|
|
127
|
-
<div className="flex flex-col p-2 ml-auto justify-center">
|
|
128
|
-
<span className="text-end text-sm text-muted-foreground mb-2">
|
|
129
|
-
{item.revision}
|
|
130
|
-
</span>
|
|
131
|
-
<div className="flex gap-2">
|
|
132
|
-
{Object.keys(item.files).map((fileKey, index) => {
|
|
133
|
-
if (!item.files[fileKey]) return null
|
|
134
|
-
|
|
135
|
-
return (
|
|
136
|
-
<DropdownMenu key={index}>
|
|
137
|
-
<DropdownMenuTrigger>
|
|
138
|
-
<Button variant="ghost" size="icon">
|
|
139
|
-
<FileIcon extension={fileKey} />
|
|
140
|
-
</Button>
|
|
141
|
-
</DropdownMenuTrigger>
|
|
142
|
-
<DropdownMenuContent>
|
|
143
|
-
<DropdownMenuItem>
|
|
144
|
-
<a href={item.files[fileKey].view} target="_blank" rel="noreferrer" className="flex items-center">
|
|
145
|
-
<Eye className="mr-2" /> Open {/*TODO: use i18n functions*/}
|
|
146
|
-
</a>
|
|
147
|
-
</DropdownMenuItem>
|
|
148
|
-
<DropdownMenuItem>
|
|
149
|
-
<a href={item.files[fileKey].download} target="_blank" rel="noreferrer" className="flex items-center">
|
|
150
|
-
<CloudDownload className="mr-2" /> Download {/*TODO: use i18n functions*/}
|
|
151
|
-
</a>
|
|
152
|
-
</DropdownMenuItem>
|
|
153
|
-
</DropdownMenuContent>
|
|
154
|
-
</DropdownMenu>
|
|
155
|
-
)
|
|
156
|
-
})}
|
|
157
|
-
|
|
158
|
-
{(topicList && topicList.length > 0) && (
|
|
159
|
-
<BookmarkButton
|
|
160
|
-
markersList={topicList.filter(item => item.label.length > 0)}
|
|
161
|
-
trigger={
|
|
162
|
-
<Button variant="ghost" size="icon" className="relative">
|
|
163
|
-
<FaRegBookmark className="!w-5 color-primary" />
|
|
164
|
-
<span
|
|
165
|
-
className="absolute -top-[10px] -right-[10px] min-w-5 min-h-5 bg-primary text-white rounded-full"
|
|
166
|
-
>
|
|
167
|
-
{topicList.length}
|
|
168
|
-
</span>
|
|
169
|
-
</Button>
|
|
170
|
-
}
|
|
171
|
-
/>
|
|
172
|
-
)}
|
|
173
|
-
|
|
174
|
-
<FavoriteButton id={item.shortId} type={item.type as ResultTypes} label={item.title} />
|
|
175
|
-
|
|
176
|
-
{item.multipleVersions.length > 1 && (
|
|
177
|
-
<Tooltip>
|
|
178
|
-
<TooltipTrigger asChild>
|
|
179
|
-
<Button variant="ghost" size="icon">
|
|
180
|
-
<FileStack />
|
|
181
|
-
</Button>
|
|
182
|
-
</TooltipTrigger>
|
|
183
|
-
<TooltipContent>
|
|
184
|
-
|
|
185
|
-
Available in: {item.multipleVersions.join(", ")}
|
|
186
|
-
</TooltipContent>
|
|
187
|
-
</Tooltip>
|
|
188
|
-
)}
|
|
189
|
-
</div>
|
|
190
|
-
|
|
191
|
-
</div>
|
|
192
|
-
</div>
|
|
193
|
-
)
|
|
194
|
-
})}
|
|
195
|
-
</div>
|
|
196
|
-
);
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
export default TableWithImageView;
|
package/src/right-sidebar.tsx
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React, { ComponentProps } from "react";
|
|
4
|
-
import {
|
|
5
|
-
Sidebar,
|
|
6
|
-
SidebarContent,
|
|
7
|
-
} from "@c-rex/ui/sidebar";
|
|
8
|
-
import { useTranslations } from "next-intl";
|
|
9
|
-
import { articleInfoItemType, DocumentsType, Favorite } from "@c-rex/types";
|
|
10
|
-
import { InfoTable } from "./info/info-table";
|
|
11
|
-
import { AvailableVersionsInterface } from "@c-rex/interfaces";
|
|
12
|
-
import { useFavoritesStore } from "./stores/favorites-store";
|
|
13
|
-
|
|
14
|
-
interface SidebarProps extends ComponentProps<typeof Sidebar> {
|
|
15
|
-
articleInfo: articleInfoItemType[],
|
|
16
|
-
documentInfo: articleInfoItemType[],
|
|
17
|
-
files: DocumentsType,
|
|
18
|
-
articleAvailableVersions: AvailableVersionsInterface[],
|
|
19
|
-
documentAvailableVersions: AvailableVersionsInterface[]
|
|
20
|
-
documentId: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function RightSidebar({
|
|
24
|
-
articleInfo,
|
|
25
|
-
documentInfo,
|
|
26
|
-
files,
|
|
27
|
-
articleAvailableVersions,
|
|
28
|
-
documentAvailableVersions,
|
|
29
|
-
documentId,
|
|
30
|
-
...props
|
|
31
|
-
}: SidebarProps) {
|
|
32
|
-
const t = useTranslations();
|
|
33
|
-
const favoritesDocuments = useFavoritesStore((state) => state.documents);
|
|
34
|
-
const isFavoriteDocument = Object.keys(favoritesDocuments).some((docId) => docId === documentId);
|
|
35
|
-
let favoritesList: Favorite[] = []
|
|
36
|
-
|
|
37
|
-
if (Object.keys(favoritesDocuments).some(docId => docId === documentId)) {
|
|
38
|
-
favoritesList = favoritesDocuments[documentId]?.topics || []
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<Sidebar {...props}>
|
|
43
|
-
<SidebarContent className="pt-4 pr-4 pb-10">
|
|
44
|
-
{(articleInfo.length > 0 || documentInfo.length > 0) && (
|
|
45
|
-
<>
|
|
46
|
-
|
|
47
|
-
{documentInfo.length > 0 && (
|
|
48
|
-
<InfoTable
|
|
49
|
-
title={t("aboutDocument")}
|
|
50
|
-
items={documentInfo}
|
|
51
|
-
files={files}
|
|
52
|
-
availableVersions={documentAvailableVersions}
|
|
53
|
-
markersList={isFavoriteDocument ? favoritesList : []}
|
|
54
|
-
/>
|
|
55
|
-
)}
|
|
56
|
-
|
|
57
|
-
{articleInfo.length > 0 && (
|
|
58
|
-
<InfoTable
|
|
59
|
-
title={t("aboutArticle")}
|
|
60
|
-
items={articleInfo}
|
|
61
|
-
availableVersions={articleAvailableVersions}
|
|
62
|
-
/>
|
|
63
|
-
)}
|
|
64
|
-
|
|
65
|
-
</>
|
|
66
|
-
)}
|
|
67
|
-
</SidebarContent>
|
|
68
|
-
</Sidebar>
|
|
69
|
-
);
|
|
70
|
-
}
|
package/src/search-modal.tsx
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import React, { FC, useEffect, useState } from "react"
|
|
4
|
-
import { Button } from "@c-rex/ui/button"
|
|
5
|
-
import {
|
|
6
|
-
Dialog,
|
|
7
|
-
DialogClose,
|
|
8
|
-
DialogContent,
|
|
9
|
-
DialogFooter,
|
|
10
|
-
DialogHeader,
|
|
11
|
-
DialogTitle,
|
|
12
|
-
DialogTrigger,
|
|
13
|
-
} from "@c-rex/ui/dialog"
|
|
14
|
-
import { parseAsBoolean, parseAsInteger, parseAsString, useQueryStates } from "nuqs"
|
|
15
|
-
import { useTranslations } from "next-intl"
|
|
16
|
-
import { WILD_CARD_OPTIONS } from "@c-rex/constants"
|
|
17
|
-
import { call } from "@c-rex/utils"
|
|
18
|
-
import { Input } from "@c-rex/ui/input"
|
|
19
|
-
import { Label } from "@c-rex/ui/label"
|
|
20
|
-
import { Checkbox } from "@c-rex/ui/checkbox"
|
|
21
|
-
import { Toggle } from "@c-rex/ui/toggle"
|
|
22
|
-
import { useLanguageStore } from "./stores/language-store"
|
|
23
|
-
|
|
24
|
-
interface SearchModalProps {
|
|
25
|
-
trigger: React.ReactNode;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const SearchModal: FC<SearchModalProps> = ({ trigger }) => {
|
|
29
|
-
const t = useTranslations("filter");
|
|
30
|
-
const contentLang = useLanguageStore(state => state.contentLang);
|
|
31
|
-
const [value, setValue] = useState<string>("");
|
|
32
|
-
const [loading, setLoading] = useState<boolean>(false);
|
|
33
|
-
const [suggestions, setSuggestions] = useState<string[]>([]);
|
|
34
|
-
|
|
35
|
-
const [params, setParams] = useQueryStates({
|
|
36
|
-
language: parseAsString,
|
|
37
|
-
page: parseAsInteger,
|
|
38
|
-
wildcard: parseAsString,
|
|
39
|
-
operator: parseAsString,
|
|
40
|
-
search: parseAsString,
|
|
41
|
-
packages: parseAsString,
|
|
42
|
-
tags: parseAsBoolean,
|
|
43
|
-
like: parseAsBoolean,
|
|
44
|
-
}, {
|
|
45
|
-
history: 'push',
|
|
46
|
-
shallow: false,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const onSearch = (value: string): Promise<string[]> => {
|
|
51
|
-
return call<string[]>("InformationUnitsService.getSuggestions", { query: value, language: contentLang });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
const debounceFetch = setTimeout(() => {
|
|
57
|
-
if (value) {
|
|
58
|
-
setLoading(true)
|
|
59
|
-
onSearch(value).then(suggestions => {
|
|
60
|
-
setSuggestions(suggestions)
|
|
61
|
-
setLoading(false)
|
|
62
|
-
});
|
|
63
|
-
} else {
|
|
64
|
-
setSuggestions([]);
|
|
65
|
-
}
|
|
66
|
-
}, 300);
|
|
67
|
-
|
|
68
|
-
return () => clearTimeout(debounceFetch);
|
|
69
|
-
}, [value]);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const apply = () => {
|
|
73
|
-
setParams({
|
|
74
|
-
search: value,
|
|
75
|
-
operator: "OR",
|
|
76
|
-
page: 1,
|
|
77
|
-
language: contentLang,
|
|
78
|
-
wildcard: WILD_CARD_OPTIONS.BOTH,
|
|
79
|
-
tags: false,
|
|
80
|
-
like: false,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<Dialog>
|
|
86
|
-
<DialogTrigger asChild>
|
|
87
|
-
{trigger}
|
|
88
|
-
</DialogTrigger>
|
|
89
|
-
<DialogContent>
|
|
90
|
-
<DialogHeader>
|
|
91
|
-
<DialogTitle>Search</DialogTitle>
|
|
92
|
-
</DialogHeader>
|
|
93
|
-
<Input value={value} autoFocus onChange={(e) => setValue(e.target.value)} />
|
|
94
|
-
|
|
95
|
-
{loading ? (
|
|
96
|
-
<div className="flex items-center justify-center py-4">
|
|
97
|
-
<div className="animate-spin rounded-full h-6 w-6 border-2 border-gray-300 border-t-gray-950" />
|
|
98
|
-
</div>
|
|
99
|
-
) : (
|
|
100
|
-
<div>
|
|
101
|
-
{suggestions.map((option) => (
|
|
102
|
-
<Toggle
|
|
103
|
-
key={option}
|
|
104
|
-
value={option}
|
|
105
|
-
className="m-0"
|
|
106
|
-
onClick={(inputValue) => {
|
|
107
|
-
setValue(`"${inputValue}"`);
|
|
108
|
-
}}
|
|
109
|
-
>
|
|
110
|
-
{option}
|
|
111
|
-
</Toggle>
|
|
112
|
-
))}
|
|
113
|
-
</div>
|
|
114
|
-
)}
|
|
115
|
-
|
|
116
|
-
<Label className="hover:bg-accent/50 flex items-start gap-3 rounded-lg border p-3">
|
|
117
|
-
<Checkbox
|
|
118
|
-
id="toggle-2"
|
|
119
|
-
defaultChecked
|
|
120
|
-
className="data-[state=checked]:border-blue-600 data-[state=checked]:bg-blue-600 data-[state=checked]:text-white dark:data-[state=checked]:border-blue-700 dark:data-[state=checked]:bg-blue-700"
|
|
121
|
-
/>
|
|
122
|
-
<div className="font-normal">
|
|
123
|
-
<span className="text-sm leading-none font-medium">
|
|
124
|
-
Search only on this document
|
|
125
|
-
</span>
|
|
126
|
-
<p className="text-muted-foreground text-sm">
|
|
127
|
-
You can enable or disable notifications at any time.
|
|
128
|
-
</p>
|
|
129
|
-
</div>
|
|
130
|
-
</Label>
|
|
131
|
-
|
|
132
|
-
<DialogFooter className="pt-2">
|
|
133
|
-
<DialogClose asChild>
|
|
134
|
-
<Button onClick={apply}>{t("apply")}</Button>
|
|
135
|
-
</DialogClose>
|
|
136
|
-
</DialogFooter>
|
|
137
|
-
</DialogContent>
|
|
138
|
-
</Dialog >
|
|
139
|
-
)
|
|
140
|
-
}
|