@c-rex/components 0.1.29 → 0.1.31
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 +13 -1
- package/src/article/article-action-bar.tsx +31 -9
- package/src/autocomplete.tsx +5 -2
- package/src/carousel/carousel.tsx +17 -9
- package/src/documents/result-list.tsx +1 -2
- package/src/info/info-table.tsx +1 -1
- package/src/info/shared.tsx +1 -2
- package/src/navbar/language-switcher/content-language-switch.tsx +9 -3
- package/src/navbar/language-switcher/ui-language-switch.tsx +1 -1
- package/src/navbar/navbar.tsx +83 -50
- package/src/navbar/settings.tsx +6 -1
- package/src/page-wrapper.tsx +11 -1
- package/src/restriction-menu/restriction-menu-container.tsx +53 -0
- package/src/restriction-menu/restriction-menu-item.tsx +103 -48
- package/src/restriction-menu/restriction-menu.tsx +126 -126
- package/src/results/dialog-filter.tsx +3 -2
- package/src/results/filter-navbar.tsx +25 -2
- package/src/results/filter-sidebar/index.tsx +1 -0
- package/src/results/utils.ts +1 -21
- package/src/search-input.tsx +4 -12
- package/src/stores/restriction-store.ts +11 -0
- package/src/stores/search-settings-store.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c-rex/components",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"files": [
|
|
5
5
|
"src"
|
|
6
6
|
],
|
|
@@ -164,6 +164,18 @@
|
|
|
164
164
|
"./carousel": {
|
|
165
165
|
"types": "./src/carousel/carousel.tsx",
|
|
166
166
|
"import": "./src/carousel/carousel.tsx"
|
|
167
|
+
},
|
|
168
|
+
"./restriction-menu": {
|
|
169
|
+
"types": "./src/restriction-menu/restriction-menu.tsx",
|
|
170
|
+
"import": "./src/restriction-menu/restriction-menu.tsx"
|
|
171
|
+
},
|
|
172
|
+
"./restriction-menu-item": {
|
|
173
|
+
"types": "./src/restriction-menu/restriction-menu-item.tsx",
|
|
174
|
+
"import": "./src/restriction-menu/restriction-menu-item.tsx"
|
|
175
|
+
},
|
|
176
|
+
"./search-input": {
|
|
177
|
+
"types": "./src/search-input.tsx",
|
|
178
|
+
"import": "./src/search-input.tsx"
|
|
167
179
|
}
|
|
168
180
|
},
|
|
169
181
|
"scripts": {
|
|
@@ -27,7 +27,7 @@ export const ArticleActionBar: FC = () => {
|
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
29
|
<>
|
|
30
|
-
<div className="w-
|
|
30
|
+
<div className="w-auto justify-between bg-primary text-primary-foreground rounded-full shadow-lg flex sticky bottom-4 p-2 float-right gap-2 transition-all duration-300">
|
|
31
31
|
{enableHighlight && (
|
|
32
32
|
|
|
33
33
|
<>
|
|
@@ -48,29 +48,45 @@ export const ArticleActionBar: FC = () => {
|
|
|
48
48
|
placeholder={t("search")}
|
|
49
49
|
className={cn(
|
|
50
50
|
"border border-gray-300 left-12 transition-all duration-300 rounded-full h-9 bg-secondary text-secondary-foreground focus:outline-none focus:ring-2 focus:ring-blue-500",
|
|
51
|
-
open ? "
|
|
51
|
+
open ? "flex flex-1 opacity-100 px-2 mr-2" : "w-0 opacity-0 px-0"
|
|
52
52
|
)}
|
|
53
53
|
/>
|
|
54
54
|
|
|
55
|
-
<Button
|
|
56
|
-
variant="ghost" size="icon" rounded="full" onClick={() => setOpen(!open)}
|
|
57
|
-
>
|
|
55
|
+
<Button variant="ghost" size="icon" rounded="full" onClick={() => setOpen(!open)}>
|
|
58
56
|
{open ? <X className="w-5 h-5" /> : <Search className="w-5 h-5" />}
|
|
59
57
|
</Button>
|
|
60
58
|
|
|
61
59
|
</div>
|
|
62
60
|
|
|
63
|
-
<Button
|
|
61
|
+
<Button
|
|
62
|
+
variant="ghost"
|
|
63
|
+
size="icon"
|
|
64
|
+
rounded="full"
|
|
65
|
+
onClick={prev}
|
|
66
|
+
className={cn(open && "hidden sm:inline-flex")}
|
|
67
|
+
>
|
|
64
68
|
<ArrowBigLeft />
|
|
65
69
|
</Button>
|
|
66
|
-
<Button variant="ghost" size="icon" rounded="full" onClick={next}>
|
|
67
70
|
|
|
71
|
+
<Button
|
|
72
|
+
variant="ghost"
|
|
73
|
+
size="icon"
|
|
74
|
+
rounded="full"
|
|
75
|
+
onClick={next}
|
|
76
|
+
className={cn(open && "hidden sm:inline-flex")}
|
|
77
|
+
>
|
|
68
78
|
<ArrowBigRight />
|
|
69
79
|
</Button>
|
|
70
80
|
</>
|
|
71
81
|
)}
|
|
72
82
|
|
|
73
|
-
<Button
|
|
83
|
+
<Button
|
|
84
|
+
variant="ghost"
|
|
85
|
+
size="icon"
|
|
86
|
+
rounded="full"
|
|
87
|
+
onClick={() => toggleHighlight(!enableHighlight)}
|
|
88
|
+
className={cn("group", open && "hidden sm:inline-flex")}
|
|
89
|
+
>
|
|
74
90
|
{enableHighlight ?
|
|
75
91
|
<FileSearchIcon /> :
|
|
76
92
|
<div className="relative inline-block">
|
|
@@ -80,7 +96,13 @@ export const ArticleActionBar: FC = () => {
|
|
|
80
96
|
}
|
|
81
97
|
</Button>
|
|
82
98
|
|
|
83
|
-
<Button
|
|
99
|
+
<Button
|
|
100
|
+
variant="ghost"
|
|
101
|
+
size="icon"
|
|
102
|
+
rounded="full"
|
|
103
|
+
onClick={rightSidebar.toggleSidebar}
|
|
104
|
+
className={cn(open && "hidden sm:inline-flex")}
|
|
105
|
+
>
|
|
84
106
|
<PanelRight />
|
|
85
107
|
</Button>
|
|
86
108
|
</div>
|
package/src/autocomplete.tsx
CHANGED
|
@@ -17,6 +17,7 @@ export type AutoCompleteProps = {
|
|
|
17
17
|
queryParams?: SuggestionQueryParams
|
|
18
18
|
onSelectParams?: { key: string, value: string }[];
|
|
19
19
|
onSelectPath: string
|
|
20
|
+
inputClass?: string
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
export const AutoComplete = ({
|
|
@@ -25,7 +26,8 @@ export const AutoComplete = ({
|
|
|
25
26
|
endpoint,
|
|
26
27
|
queryParams,
|
|
27
28
|
onSelectParams,
|
|
28
|
-
onSelectPath = "/"
|
|
29
|
+
onSelectPath = "/",
|
|
30
|
+
inputClass = "",
|
|
29
31
|
}: AutoCompleteProps) => {
|
|
30
32
|
const t = useTranslations();
|
|
31
33
|
const [pkg] = useQueryState("package");
|
|
@@ -59,7 +61,7 @@ export const AutoComplete = ({
|
|
|
59
61
|
params.set('search', value);
|
|
60
62
|
params.set("page", "1")
|
|
61
63
|
params.set("operator", searchSettings.operator)
|
|
62
|
-
params.set("language", searchSettings.language)
|
|
64
|
+
if (searchSettings.language) params.set("language", searchSettings.language)
|
|
63
65
|
params.set("wildcard", searchSettings.wildcard)
|
|
64
66
|
params.set("like", searchSettings.like.toString())
|
|
65
67
|
|
|
@@ -105,6 +107,7 @@ export const AutoComplete = ({
|
|
|
105
107
|
<div className="relative flex-1" ref={containerRef}>
|
|
106
108
|
<Input
|
|
107
109
|
variant={embedded ? "embedded" : undefined}
|
|
110
|
+
className={inputClass}
|
|
108
111
|
type="text"
|
|
109
112
|
placeholder={t("search")}
|
|
110
113
|
value={query}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
createContext,
|
|
10
10
|
useContext,
|
|
11
11
|
} from "react";
|
|
12
|
-
import { cn, getLanguage, getTitle, getType } from "@c-rex/utils";
|
|
12
|
+
import { cn, formatDateToLocale, getLanguage, getTitle, getType } from "@c-rex/utils";
|
|
13
13
|
import { Button } from "@c-rex/ui/button";
|
|
14
14
|
import { ArrowLeft, ArrowRight } from "lucide-react";
|
|
15
15
|
import { Skeleton } from "@c-rex/ui/skeleton";
|
|
@@ -21,6 +21,7 @@ import { Card } from "@c-rex/ui/card";
|
|
|
21
21
|
import { Badge } from "@c-rex/ui/badge";
|
|
22
22
|
import { Flag } from "../icons/flag-icon"
|
|
23
23
|
import { Empty } from "../results/empty";
|
|
24
|
+
import { useLocale, useTranslations } from "next-intl";
|
|
24
25
|
|
|
25
26
|
// Types
|
|
26
27
|
|
|
@@ -66,7 +67,7 @@ type Props = {
|
|
|
66
67
|
responsive?: ResponsiveSetting[];
|
|
67
68
|
indicators?: boolean;
|
|
68
69
|
showImages?: boolean;
|
|
69
|
-
|
|
70
|
+
carouselItemComponent?: FC<{ item: CommonItemsModel; showImages: boolean }>;
|
|
70
71
|
serviceType: keyof typeof ServiceOptions;
|
|
71
72
|
queryParams?: Record<string, any>;
|
|
72
73
|
loadByPages?: boolean;
|
|
@@ -80,13 +81,13 @@ export const Carousel: FC<Props> = ({
|
|
|
80
81
|
indicators = false,
|
|
81
82
|
autoplaySpeed = 3000,
|
|
82
83
|
showImages = false,
|
|
83
|
-
|
|
84
|
+
carouselItemComponent,
|
|
84
85
|
serviceType,
|
|
85
86
|
queryParams = {},
|
|
86
87
|
loadByPages = false,
|
|
87
88
|
}) => {
|
|
88
89
|
const service = ServiceOptions[serviceType] as typeof documentsGetAllClientService;
|
|
89
|
-
const
|
|
90
|
+
const RenderComponent = carouselItemComponent || DefaultRenderCarouselItem;
|
|
90
91
|
|
|
91
92
|
// Responsive
|
|
92
93
|
const [slidesToShow, setSlidesToShow] = useState(responsive[0]?.slidesToShow || 1);
|
|
@@ -239,7 +240,7 @@ export const Carousel: FC<Props> = ({
|
|
|
239
240
|
className={`flex-shrink-0 flex-grow-0 flex justify-center`}
|
|
240
241
|
style={{ width: `${100 / slidesToShow}%` }}
|
|
241
242
|
>
|
|
242
|
-
{
|
|
243
|
+
<RenderComponent item={item} showImages={showImages} />
|
|
243
244
|
</div>
|
|
244
245
|
))
|
|
245
246
|
) : null}
|
|
@@ -314,14 +315,21 @@ const CarouselIndicators: FC<{ className?: string }> = ({ className }) => {
|
|
|
314
315
|
);
|
|
315
316
|
};
|
|
316
317
|
|
|
317
|
-
const
|
|
318
|
+
const DefaultRenderCarouselItem: FC<{ item: CommonItemsModel; showImages: boolean }> = ({ item, showImages }) => {
|
|
319
|
+
const locale = useLocale();
|
|
320
|
+
const t = useTranslations("itemTypes");
|
|
321
|
+
|
|
322
|
+
const date = formatDateToLocale(item.created!, locale);
|
|
318
323
|
const title = getTitle(item.titles, item.labels);
|
|
319
324
|
const itemType = getType(item.class);
|
|
320
325
|
const language = getLanguage(item.languages)
|
|
321
326
|
const countryCode = language.split("-")[1] || "";
|
|
327
|
+
|
|
322
328
|
return (
|
|
323
329
|
<div className="p-2 flex flex-1">
|
|
324
|
-
<Card className="p-4 flex-1 justify-between">
|
|
330
|
+
<Card className="p-4 flex-1 justify-between relative">
|
|
331
|
+
<Badge className="absolute -top-2 -right-2">{t(itemType.toLowerCase())}</Badge>
|
|
332
|
+
|
|
325
333
|
{showImages && (
|
|
326
334
|
<ImageRenditionContainer
|
|
327
335
|
itemShortId={item.shortId!}
|
|
@@ -333,12 +341,12 @@ const defaultRenderCarouselItem = (item: CommonItemsModel, showImages: boolean)
|
|
|
333
341
|
<span className="text-lg font-semibold">{title}</span>
|
|
334
342
|
|
|
335
343
|
<div className="flex justify-between w-full">
|
|
336
|
-
<Badge>{itemType}</Badge>
|
|
337
344
|
<span className="w-8 block border">
|
|
338
345
|
<Flag countryCode={countryCode} />
|
|
339
346
|
</span>
|
|
347
|
+
<span className="text-gray-400">{date || item.revision}</span>
|
|
340
348
|
</div>
|
|
341
349
|
</Card>
|
|
342
350
|
</div>
|
|
343
351
|
);
|
|
344
|
-
}
|
|
352
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FC } from "react";
|
|
2
2
|
import { CommonItemsModel } from "@c-rex/interfaces";
|
|
3
3
|
import { FileStack } from "lucide-react";
|
|
4
|
-
import { cn, getType, getTitle, getVersions, getLanguage } from "@c-rex/utils";
|
|
4
|
+
import { cn, getType, getTitle, getVersions, getLanguage, generateQueryParams } from "@c-rex/utils";
|
|
5
5
|
import { Button } from "@c-rex/ui/button";
|
|
6
6
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@c-rex/ui/tooltip";
|
|
7
7
|
import { RESULT_TYPES } from "@c-rex/constants";
|
|
@@ -10,7 +10,6 @@ import { FavoriteButton } from "@c-rex/components/favorite-button";
|
|
|
10
10
|
import { ImageRenditionContainer } from "../renditions/image/container";
|
|
11
11
|
import { BookmarkButton } from "../favorites/bookmark-button";
|
|
12
12
|
import { HtmlRendition } from "../renditions/html";
|
|
13
|
-
import { generateQueryParams } from "../../../utils/src/params";
|
|
14
13
|
import { QueryParams } from "@c-rex/types";
|
|
15
14
|
|
|
16
15
|
interface Props {
|
package/src/info/info-table.tsx
CHANGED
|
@@ -59,7 +59,7 @@ export const InfoTable: FC<Props> = async ({
|
|
|
59
59
|
{processDataToLabelValuePairs(data).map((item, index) => (
|
|
60
60
|
<TableRow key={index} className="min-h-12">
|
|
61
61
|
<TableCell className="font-medium w-28 pl-4">
|
|
62
|
-
<h4 className="text-sm font-medium">{t(item.label)}</h4>
|
|
62
|
+
<h4 className="text-sm font-medium capitalize">{t(item.label)}</h4>
|
|
63
63
|
</TableCell>
|
|
64
64
|
<TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
|
|
65
65
|
{renderValue(item, EN_LANG)}
|
package/src/info/shared.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
2
|
import { articleInfoItemType } from "@c-rex/types";
|
|
3
3
|
import { formatDateToLocale, isDate, isLanguage } from "@c-rex/utils";
|
|
4
4
|
import { Flag } from "../icons/flag-icon";
|
|
@@ -13,7 +13,6 @@ export const renderValue = (item: articleInfoItemType, uiLang: string): ReactNod
|
|
|
13
13
|
<span className="w-8 block">
|
|
14
14
|
<Flag countryCode={countryCode} />
|
|
15
15
|
</span>
|
|
16
|
-
|
|
17
16
|
);
|
|
18
17
|
}
|
|
19
18
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
3
|
import { startTransition } from "react";
|
|
4
4
|
import { SharedLanguageSwitch } from "./shared";
|
|
@@ -10,9 +10,13 @@ import { toast } from "sonner"
|
|
|
10
10
|
import { useLanguageStore } from "../../stores/language-store";
|
|
11
11
|
import { useSearchSettingsStore } from "../../stores/search-settings-store";
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
type Props = {
|
|
14
|
+
contentLangDefault: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const ContentLanguageSwitch = ({ contentLangDefault }: Props) => {
|
|
14
18
|
const t = useTranslations();
|
|
15
|
-
const contentLang = useSearchSettingsStore.getState().language;
|
|
19
|
+
const contentLang = useSearchSettingsStore.getState().language || contentLangDefault;
|
|
16
20
|
const updatePreferences = useSearchSettingsStore.getState().updatePreferences;
|
|
17
21
|
|
|
18
22
|
const { availableVersions } = useAppConfig()
|
|
@@ -45,6 +49,8 @@ export const ContentLanguageSwitch = () => {
|
|
|
45
49
|
setContentLanguage(locale)
|
|
46
50
|
}
|
|
47
51
|
|
|
52
|
+
//TODO en: needs to be fixed as it's not working
|
|
53
|
+
|
|
48
54
|
const currentPath = window.location.pathname;
|
|
49
55
|
const isTopicOrBlogOrDocument = (
|
|
50
56
|
currentPath.includes(TOPICS_TYPE_AND_LINK) ||
|
package/src/navbar/navbar.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FC } from "react";
|
|
1
|
+
import { ComponentProps, FC } from "react";
|
|
2
2
|
import Link from "next/link";
|
|
3
3
|
import { SignInBtn } from "./sign-in-out-btns";
|
|
4
4
|
import { getServerSession } from "next-auth";
|
|
@@ -7,21 +7,22 @@ import { UserMenu } from "./user-menu";
|
|
|
7
7
|
import { SearchInput } from "../search-input";
|
|
8
8
|
import { CrexSDK } from "@c-rex/core/sdk";
|
|
9
9
|
import * as AutocompleteOptions from "../generated/suggestions";
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
10
|
+
import { cn } from "@c-rex/utils";
|
|
11
|
+
import { getTranslations } from "next-intl/server";
|
|
12
|
+
import { Button } from "@c-rex/ui/button";
|
|
13
|
+
import { DropdownHoverItem } from "@c-rex/ui/dropdown-hover-item";
|
|
14
|
+
import { Menu } from "lucide-react";
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
type NavBarProps = {
|
|
14
17
|
title?: string;
|
|
15
18
|
showInput: boolean;
|
|
16
19
|
showPkgFilter: boolean;
|
|
17
20
|
onSelectPath?: string;
|
|
18
|
-
showMenu?: boolean
|
|
19
|
-
showRestrictMenu?: boolean
|
|
20
|
-
restrictionType?: keyof typeof ComponentOptions;
|
|
21
21
|
autocompleteType?: keyof typeof AutocompleteOptions;
|
|
22
22
|
//these two props are only used when showPkgFilter is false and has some values to override the default behavior
|
|
23
23
|
alternativeAutocompleteType?: keyof typeof AutocompleteOptions;
|
|
24
24
|
alternativeOnSelectPath?: string;
|
|
25
|
+
showMenu?: boolean;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export const NavBar: FC<NavBarProps> = async ({
|
|
@@ -29,11 +30,10 @@ export const NavBar: FC<NavBarProps> = async ({
|
|
|
29
30
|
showInput,
|
|
30
31
|
autocompleteType,
|
|
31
32
|
onSelectPath,
|
|
32
|
-
restrictionType,
|
|
33
33
|
showMenu = false,
|
|
34
|
-
showRestrictMenu = false,
|
|
35
34
|
...props
|
|
36
35
|
}) => {
|
|
36
|
+
const t = await getTranslations();
|
|
37
37
|
const sdk = new CrexSDK();
|
|
38
38
|
const clientConfigs = sdk.getClientConfig();
|
|
39
39
|
const serverConfigs = sdk.getServerConfig();
|
|
@@ -49,62 +49,95 @@ export const NavBar: FC<NavBarProps> = async ({
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
return (
|
|
52
|
-
<header className="sticky flex flex-col
|
|
53
|
-
<div
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
<header className="sticky flex flex-col top-0 z-40 w-full p-4 backdrop-blur-xl transition-all bg-transparent border-b gap-2">
|
|
53
|
+
<div className="w-full flex items-center justify-between gap-2">
|
|
54
|
+
<div
|
|
55
|
+
className={cn(
|
|
56
|
+
"flex items-center gap-4",
|
|
57
|
+
title && "lg:w-[calc(16rem-16px)]"
|
|
58
|
+
)}
|
|
59
|
+
>
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
<Link href="/"
|
|
62
|
+
>
|
|
59
63
|
<img
|
|
60
64
|
src="/img/logo.png"
|
|
61
65
|
alt="C-rex Logo"
|
|
62
|
-
className="
|
|
66
|
+
className="h-14"
|
|
63
67
|
/>
|
|
64
68
|
</Link>
|
|
65
69
|
|
|
66
|
-
{
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
{showMenu && (
|
|
71
|
+
<DropdownHoverItem
|
|
72
|
+
label={
|
|
73
|
+
<Button variant="outline" rounded="full" size="icon">
|
|
74
|
+
<Menu className="size-4" />
|
|
75
|
+
</Button>
|
|
76
|
+
}
|
|
77
|
+
>
|
|
78
|
+
<Button asChild variant="link">
|
|
79
|
+
<Link href="/documents">
|
|
80
|
+
{t('navigation.documents')}
|
|
81
|
+
</Link>
|
|
82
|
+
</Button>
|
|
83
|
+
<Button asChild variant="link">
|
|
84
|
+
<Link href="/topics">
|
|
85
|
+
{t('navigation.topics')}
|
|
86
|
+
</Link>
|
|
87
|
+
</Button>
|
|
88
|
+
<Button asChild variant="link">
|
|
89
|
+
<Link href="/fragments">
|
|
90
|
+
{t('navigation.fragments')}
|
|
91
|
+
</Link>
|
|
92
|
+
</Button>
|
|
93
|
+
<Button asChild variant="link">
|
|
94
|
+
<Link href="/packages">
|
|
95
|
+
{t('navigation.packages')}
|
|
96
|
+
</Link>
|
|
97
|
+
</Button>
|
|
98
|
+
<Button asChild variant="link">
|
|
99
|
+
<Link href="/information-units">
|
|
100
|
+
{t('navigation.informationUnits')}
|
|
101
|
+
</Link>
|
|
102
|
+
</Button>
|
|
103
|
+
</DropdownHoverItem>
|
|
70
104
|
)}
|
|
71
105
|
</div>
|
|
72
106
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<div className="flex items-center gap-2 absolute sm:static top-4 h-14 sm:h-min right-4">
|
|
107
|
+
{title && (
|
|
108
|
+
<div className="flex-1 hidden md:flex md:justify-center lg:justify-start">
|
|
109
|
+
<h1 className="md:text-2xl lg:text-3xl font-bold tracking-tight text-balance">{title}</h1>
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
79
112
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
113
|
+
<div className="flex gap-2">
|
|
114
|
+
{willShowInput &&
|
|
115
|
+
<div className="hidden sm:flex flex-1 items-center px-3 border rounded-full h-8 c-rex-search-bar">
|
|
116
|
+
<SearchInput autocompleteType={autocompleteType} onSelectPath={onSelectPath} {...props} />
|
|
117
|
+
</div>
|
|
118
|
+
}
|
|
83
119
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
120
|
+
{clientConfigs.OIDC.userEnabled && (
|
|
121
|
+
<>
|
|
122
|
+
{session ? (
|
|
123
|
+
<UserMenu session={session} OIDCEndPoint={serverConfigs.OIDC.user.issuer} />
|
|
124
|
+
) : (
|
|
125
|
+
<SignInBtn />
|
|
126
|
+
)}
|
|
127
|
+
</>
|
|
128
|
+
)}
|
|
93
129
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
</div>
|
|
130
|
+
{clientConfigs.languageSwitcher.enabled && (
|
|
131
|
+
<SettingsMenu />
|
|
132
|
+
)}
|
|
98
133
|
</div>
|
|
99
|
-
|
|
100
|
-
{title && (
|
|
101
|
-
<div className="flex w-full sm:hidden items-center">
|
|
102
|
-
<h1 className="mt-4 sm:mt-0 sm:px-4 text-xl sm:text-2xl md:text-3xl font-bold tracking-tight text-balance">
|
|
103
|
-
{title}
|
|
104
|
-
</h1>
|
|
105
|
-
</div>
|
|
106
|
-
)}
|
|
107
134
|
</div>
|
|
135
|
+
|
|
136
|
+
{title && (
|
|
137
|
+
<div className="flex-1 flex justify-center md:hidden">
|
|
138
|
+
<h1 className="text-2xl font-bold tracking-tight text-balance">{title}</h1>
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
108
141
|
</header>
|
|
109
142
|
);
|
|
110
143
|
};
|
package/src/navbar/settings.tsx
CHANGED
|
@@ -14,9 +14,12 @@ import { useTranslations } from "next-intl";
|
|
|
14
14
|
import { FC } from 'react';
|
|
15
15
|
import { ContentLanguageSwitch } from "./language-switcher/content-language-switch";
|
|
16
16
|
import { UILanguageSwitch } from "./language-switcher/ui-language-switch";
|
|
17
|
+
import { CrexSDK } from "@c-rex/core/sdk";
|
|
17
18
|
|
|
18
19
|
export const SettingsMenu: FC = () => {
|
|
19
20
|
const t = useTranslations();
|
|
21
|
+
const sdk = new CrexSDK();
|
|
22
|
+
const clientConfigs = sdk.getClientConfig();
|
|
20
23
|
|
|
21
24
|
return (
|
|
22
25
|
<DropdownMenu>
|
|
@@ -34,7 +37,9 @@ export const SettingsMenu: FC = () => {
|
|
|
34
37
|
|
|
35
38
|
<DropdownMenuPortal>
|
|
36
39
|
<DropdownMenuSubContent>
|
|
37
|
-
<ContentLanguageSwitch
|
|
40
|
+
<ContentLanguageSwitch
|
|
41
|
+
contentLangDefault={clientConfigs.languageSwitcher.default}
|
|
42
|
+
/>
|
|
38
43
|
</DropdownMenuSubContent>
|
|
39
44
|
</DropdownMenuPortal>
|
|
40
45
|
</DropdownMenuSub>
|
package/src/page-wrapper.tsx
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
import { ComponentProps, ReactNode } from "react";
|
|
2
2
|
import { NavBar } from './navbar/navbar';
|
|
3
3
|
import { MultiSidebarProvider } from "@c-rex/ui/sidebar";
|
|
4
|
+
import { RestrictionMenuContainer } from "./restriction-menu/restriction-menu-container";
|
|
4
5
|
|
|
5
6
|
type Props = {
|
|
6
7
|
children: ReactNode;
|
|
7
|
-
|
|
8
|
+
showRestrictMenu?: boolean
|
|
9
|
+
} & Partial<ComponentProps<typeof NavBar>> & Partial<ComponentProps<typeof RestrictionMenuContainer>>;
|
|
8
10
|
|
|
9
11
|
export const PageWrapper = ({
|
|
10
12
|
children,
|
|
11
13
|
showInput = false,
|
|
12
14
|
showPkgFilter = false,
|
|
15
|
+
showRestrictMenu = false,
|
|
13
16
|
...props
|
|
14
17
|
}: Props) => {
|
|
15
18
|
return (
|
|
16
19
|
<MultiSidebarProvider>
|
|
17
20
|
<NavBar showInput={showInput} showPkgFilter={showPkgFilter} {...props} />
|
|
21
|
+
|
|
22
|
+
{showRestrictMenu && (
|
|
23
|
+
<div className="flex-1 container pt-6">
|
|
24
|
+
<RestrictionMenuContainer {...props} />
|
|
25
|
+
</div>
|
|
26
|
+
)}
|
|
27
|
+
|
|
18
28
|
{children}
|
|
19
29
|
</MultiSidebarProvider>
|
|
20
30
|
);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ComponentProps, FC } from "react"
|
|
4
|
+
import * as ComponentOptions from "../generated/client-components";
|
|
5
|
+
import { InformationSubjectsGetAllClient } from "../generated/client-components";
|
|
6
|
+
import { Skeleton } from "@c-rex/ui/skeleton";
|
|
7
|
+
import { RestrictionMenu } from "./restriction-menu";
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
restrictField?: string
|
|
11
|
+
navigationMenuListClassName?: string
|
|
12
|
+
itemsToRender?: number
|
|
13
|
+
requestType?: keyof typeof ComponentOptions
|
|
14
|
+
} & Partial<Omit<ComponentProps<typeof InformationSubjectsGetAllClient>, ("render" | "children" | "pathParams")>>;
|
|
15
|
+
|
|
16
|
+
export const RestrictionMenuContainer: FC<Props> = ({
|
|
17
|
+
queryParams,
|
|
18
|
+
restrictField = "informationSubjects",
|
|
19
|
+
itemsToRender = 7,
|
|
20
|
+
requestType = "InformationSubjectsGetAllClient",
|
|
21
|
+
navigationMenuListClassName = "items-center justify-between flex-row",
|
|
22
|
+
}) => {
|
|
23
|
+
const RequestComponent = ComponentOptions[requestType] as unknown as FC<ComponentProps<typeof InformationSubjectsGetAllClient>>;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<RequestComponent
|
|
27
|
+
queryParams={{
|
|
28
|
+
Fields: ["labels"],
|
|
29
|
+
PageSize: 100,
|
|
30
|
+
Links: true,
|
|
31
|
+
Sort: ["labels.value"],
|
|
32
|
+
...queryParams
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
{({ data, isLoading }) => {
|
|
36
|
+
|
|
37
|
+
if (isLoading) return (
|
|
38
|
+
<div className="flex justify-between">
|
|
39
|
+
<Skeleton className="w-12 h-9 rounded-full" />
|
|
40
|
+
{Array(itemsToRender).fill(0).map((_, index) => (
|
|
41
|
+
<Skeleton key={index} className="w-28 h-9 rounded-full" />
|
|
42
|
+
))}
|
|
43
|
+
<Skeleton className="w-20 h-9 rounded-full" />
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if (!data) return null;
|
|
48
|
+
|
|
49
|
+
return <RestrictionMenu restrictField={restrictField} items={data.items} navigationMenuListClassName={navigationMenuListClassName} />
|
|
50
|
+
}}
|
|
51
|
+
</RequestComponent>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { FC } from "react";
|
|
4
|
-
import Link from "next/link";
|
|
5
4
|
import { Button } from "@c-rex/ui/button";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { NavigationMenuItem } from "@c-rex/ui/navigation-menu";
|
|
6
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@c-rex/ui/tooltip";
|
|
7
|
+
import { useQueryState } from "nuqs";
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
type Props = {
|
|
@@ -22,51 +22,52 @@ export const RestrictionNavigationItem: FC<Props> = ({
|
|
|
22
22
|
removeRestrictParam = false,
|
|
23
23
|
selected = false,
|
|
24
24
|
}) => {
|
|
25
|
+
const [restrict, setRestrict] = useQueryState("restrict", {
|
|
26
|
+
shallow: false,
|
|
27
|
+
history: "push",
|
|
28
|
+
});
|
|
25
29
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
const { restrictionValue, shouldRemoveRestrictParam } = getRestrictionValue({
|
|
31
|
+
shortId,
|
|
32
|
+
restrictField,
|
|
33
|
+
removeRestrictParam,
|
|
34
|
+
selected,
|
|
35
|
+
currentRestrict: restrict,
|
|
36
|
+
});
|
|
30
37
|
|
|
31
|
-
|
|
38
|
+
const labelStyle = {
|
|
39
|
+
display: 'inline-block',
|
|
40
|
+
maxWidth: 100,
|
|
41
|
+
overflow: 'hidden',
|
|
42
|
+
textOverflow: 'ellipsis',
|
|
43
|
+
whiteSpace: 'nowrap',
|
|
44
|
+
verticalAlign: 'middle',
|
|
45
|
+
} as React.CSSProperties;
|
|
32
46
|
|
|
33
|
-
|
|
34
|
-
if (selected) {
|
|
35
|
-
const restrictionsLength = currentRestrict.split(",").length;
|
|
36
|
-
//if there is only one restriction, we can remove the whole restrict param
|
|
37
|
-
if (restrictionsLength === 1) {
|
|
38
|
-
removeRestrictParam = true;
|
|
39
|
-
} else {
|
|
40
|
-
restrictParam = currentRestrict.replace(`${shortId}`, "").replace(",,", ",").replace(/(^,)|(,$)/g, "");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
} else {
|
|
44
|
-
restrictParam = `${currentRestrict},${shortId}`;
|
|
45
|
-
}
|
|
46
|
-
} else {
|
|
47
|
-
restrictParam = `${restrictField}=${shortId}`;
|
|
48
|
-
}
|
|
47
|
+
const needsTooltip = label.length > 12;
|
|
49
48
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
49
|
+
const labelNode = needsTooltip ? (
|
|
50
|
+
<TooltipProvider delayDuration={300}>
|
|
51
|
+
<Tooltip>
|
|
52
|
+
<TooltipTrigger asChild>
|
|
53
|
+
<span style={labelStyle}>{label}</span>
|
|
54
|
+
</TooltipTrigger>
|
|
55
|
+
<TooltipContent>{label}</TooltipContent>
|
|
56
|
+
</Tooltip>
|
|
57
|
+
</TooltipProvider>
|
|
58
|
+
) : (
|
|
59
|
+
<span style={labelStyle}>{label}</span>
|
|
60
|
+
);
|
|
63
61
|
|
|
64
62
|
return (
|
|
65
63
|
<NavigationMenuItem key={shortId} asChild>
|
|
66
|
-
<Button
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
<Button
|
|
65
|
+
variant={selected ? "default" : "outline"}
|
|
66
|
+
rounded="full"
|
|
67
|
+
onClick={() => shouldRemoveRestrictParam ? setRestrict(null) : setRestrict(restrictionValue)}
|
|
68
|
+
className="cursor-pointer"
|
|
69
|
+
>
|
|
70
|
+
{labelNode}
|
|
70
71
|
</Button>
|
|
71
72
|
</NavigationMenuItem>
|
|
72
73
|
);
|
|
@@ -77,13 +78,67 @@ export const RestrictionDropdownItem: FC<Props> = ({
|
|
|
77
78
|
label,
|
|
78
79
|
restrictField,
|
|
79
80
|
}) => {
|
|
80
|
-
const
|
|
81
|
-
|
|
81
|
+
const [restrict, setRestrict] = useQueryState("restrict", {
|
|
82
|
+
shallow: false,
|
|
83
|
+
history: "push",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const { restrictionValue } = getRestrictionValue({
|
|
87
|
+
shortId,
|
|
88
|
+
restrictField,
|
|
89
|
+
currentRestrict: restrict,
|
|
90
|
+
});
|
|
91
|
+
|
|
82
92
|
return (
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
<Button
|
|
94
|
+
variant="ghost"
|
|
95
|
+
onClick={() => setRestrict(restrictionValue)}
|
|
96
|
+
className="text-left text-wrap !h-auto w-full !justify-start cursor-pointer"
|
|
97
|
+
>
|
|
98
|
+
{label}
|
|
99
|
+
</Button>
|
|
88
100
|
);
|
|
89
101
|
};
|
|
102
|
+
|
|
103
|
+
function getRestrictionValue({
|
|
104
|
+
shortId,
|
|
105
|
+
restrictField,
|
|
106
|
+
removeRestrictParam = false,
|
|
107
|
+
selected = false,
|
|
108
|
+
|
|
109
|
+
currentRestrict,
|
|
110
|
+
}: {
|
|
111
|
+
shortId?: string;
|
|
112
|
+
restrictField?: string;
|
|
113
|
+
removeRestrictParam?: boolean;
|
|
114
|
+
selected?: boolean;
|
|
115
|
+
currentRestrict: string | null;
|
|
116
|
+
}): { restrictionValue: string | null; shouldRemoveRestrictParam: boolean } {
|
|
117
|
+
let restrictParam = "";
|
|
118
|
+
let shouldRemoveRestrictParam = removeRestrictParam;
|
|
119
|
+
|
|
120
|
+
if (currentRestrict) {
|
|
121
|
+
if (selected) {
|
|
122
|
+
const restrictionsLength = currentRestrict.split(",").length;
|
|
123
|
+
//if there is only one restriction, we can remove the whole restrict param
|
|
124
|
+
if (restrictionsLength === 1) {
|
|
125
|
+
shouldRemoveRestrictParam = true;
|
|
126
|
+
} else {
|
|
127
|
+
restrictParam = currentRestrict.replace(`${shortId}`, "").replace(",,", ",").replace(/(^,)|(,$)/g, "");
|
|
128
|
+
|
|
129
|
+
// Remove the restrict param if nothing remains after '='
|
|
130
|
+
if (/^[^=]+=$/.test(restrictParam)) {
|
|
131
|
+
shouldRemoveRestrictParam = true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
restrictParam = `${currentRestrict},${shortId}`;
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
restrictParam = `${restrictField}=${shortId}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const restrictionValue = shouldRemoveRestrictParam ? null : restrictParam
|
|
142
|
+
|
|
143
|
+
return { restrictionValue, shouldRemoveRestrictParam };
|
|
144
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import * as ComponentOptions from "../generated/client-components";
|
|
3
|
+
import { FC, use, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"
|
|
5
4
|
import {
|
|
6
5
|
NavigationMenu,
|
|
7
6
|
NavigationMenuList,
|
|
@@ -13,145 +12,146 @@ import { Button } from "@c-rex/ui/button";
|
|
|
13
12
|
import { ChevronDown, Menu } from "lucide-react";
|
|
14
13
|
import { DropdownHoverItem } from "@c-rex/ui/dropdown-hover-item";
|
|
15
14
|
import { RestrictionDropdownItem, RestrictionNavigationItem } from "./restriction-menu-item";
|
|
16
|
-
import { InformationSubjectsGetAllClient } from "../generated/client-components";
|
|
17
15
|
import { parseAsString, useQueryStates } from "nuqs";
|
|
18
|
-
import {
|
|
16
|
+
import { InformationSubjectModel } from "@c-rex/interfaces";
|
|
17
|
+
import { useTranslations } from 'next-intl'
|
|
18
|
+
import { cn, getLabelByLang } from "@c-rex/utils";
|
|
19
|
+
import { useRestrictionStore } from "../stores/restriction-store";
|
|
20
|
+
import { useLanguageStore } from "../stores/language-store";
|
|
21
|
+
import { useBreakpoint } from "@c-rex/ui/hooks";
|
|
22
|
+
import { DEVICE_OPTIONS } from "@c-rex/constants";
|
|
19
23
|
|
|
20
24
|
|
|
21
25
|
type Props = {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
restrictField: string
|
|
27
|
+
navigationMenuListClassName?: string
|
|
28
|
+
items: InformationSubjectModel[],
|
|
29
|
+
itemsByRow?: {
|
|
30
|
+
[DEVICE_OPTIONS.MOBILE]: number,
|
|
31
|
+
[DEVICE_OPTIONS.TABLET]: number,
|
|
32
|
+
[DEVICE_OPTIONS.DESKTOP]: number,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
28
35
|
|
|
29
36
|
export const RestrictionMenu: FC<Props> = ({
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
items,
|
|
38
|
+
restrictField,
|
|
39
|
+
navigationMenuListClassName = "items-center justify-between flex-row",
|
|
40
|
+
itemsByRow = {
|
|
41
|
+
[DEVICE_OPTIONS.MOBILE]: 2,
|
|
42
|
+
[DEVICE_OPTIONS.TABLET]: 5,
|
|
43
|
+
[DEVICE_OPTIONS.DESKTOP]: 7,
|
|
44
|
+
}
|
|
36
45
|
}) => {
|
|
46
|
+
const t = useTranslations();
|
|
47
|
+
const setRestrictionList = useRestrictionStore((state) => state.setRestrictionList);
|
|
37
48
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
const [params, setParams] = useQueryStates({
|
|
49
|
+
const [params] = useQueryStates({
|
|
41
50
|
restrict: parseAsString,
|
|
42
51
|
}, {
|
|
43
52
|
history: 'push',
|
|
44
53
|
shallow: false,
|
|
45
54
|
});
|
|
46
55
|
|
|
47
|
-
const
|
|
48
|
-
|
|
56
|
+
const restrictionValues = useMemo(() => params.restrict?.split(`${restrictField}=`)[1]?.split(",") || [], [params.restrict, restrictField]);
|
|
57
|
+
|
|
58
|
+
const uiLang = useLanguageStore.getState().uiLang;
|
|
59
|
+
const lang = uiLang?.split("-")[0] ?? "";
|
|
60
|
+
|
|
61
|
+
useMemo(() => {
|
|
62
|
+
const map = new Map<string, string>();
|
|
63
|
+
items.forEach((item) => {
|
|
64
|
+
const label = getLabelByLang(item.labels, lang);
|
|
65
|
+
if (item.shortId && label) {
|
|
66
|
+
map.set(item.shortId, label);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
setRestrictionList(map);
|
|
70
|
+
}, [items, lang, setRestrictionList]);
|
|
71
|
+
|
|
72
|
+
const sortedItems = useMemo(() => {
|
|
73
|
+
//if shortId it is on the restrictionValues, it should be on top of the list, otherwise keep the original order
|
|
74
|
+
|
|
75
|
+
const sorted = [...items].sort((a, b) => {
|
|
76
|
+
const aShortId = a.shortId || "";
|
|
77
|
+
const bShortId = b.shortId || "";
|
|
78
|
+
|
|
79
|
+
const aIndex = restrictionValues.indexOf(aShortId);
|
|
80
|
+
const bIndex = restrictionValues.indexOf(bShortId);
|
|
81
|
+
|
|
82
|
+
if (aIndex === -1 && bIndex === -1) {
|
|
83
|
+
return 0; // keep original order if neither is in restrictionValues
|
|
84
|
+
}
|
|
85
|
+
if (aIndex === -1) {
|
|
86
|
+
return 1; // a goes after b
|
|
87
|
+
}
|
|
88
|
+
if (bIndex === -1) {
|
|
89
|
+
return -1; // a goes before b
|
|
90
|
+
}
|
|
91
|
+
return aIndex - bIndex; // sort by index in restrictionValues
|
|
92
|
+
});
|
|
93
|
+
return sorted;
|
|
94
|
+
}, [items, restrictionValues]);
|
|
95
|
+
|
|
96
|
+
const device = useBreakpoint();
|
|
97
|
+
const [visibleCount, setVisibleCount] = useState(0);
|
|
98
|
+
const [visibleItems, setVisibleItems] = useState(sortedItems.slice(0, visibleCount));
|
|
99
|
+
const [hiddenItems, setHiddenItems] = useState(sortedItems.slice(visibleCount));
|
|
100
|
+
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (device == null) return
|
|
103
|
+
setVisibleCount(itemsByRow[device as keyof typeof DEVICE_OPTIONS] as number);
|
|
104
|
+
|
|
105
|
+
}, [device, itemsByRow]);
|
|
106
|
+
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (visibleCount == 0) return;
|
|
109
|
+
|
|
110
|
+
setVisibleItems(sortedItems.slice(0, visibleCount));
|
|
111
|
+
setHiddenItems(sortedItems.slice(visibleCount));
|
|
112
|
+
}, [sortedItems, visibleCount]);
|
|
49
113
|
|
|
50
114
|
return (
|
|
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
|
-
if (isLoading) return (
|
|
94
|
-
<>
|
|
95
|
-
<Skeleton className="w-12 h-9 rounded-full" />
|
|
96
|
-
{Array(itemsToRender).fill(0).map((_, index) => (
|
|
97
|
-
<Skeleton key={index} className="w-28 h-9 rounded-full" />
|
|
98
|
-
))}
|
|
99
|
-
<Skeleton className="w-20 h-9 rounded-full" />
|
|
100
|
-
</>
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
if (!data) return null;
|
|
104
|
-
|
|
105
|
-
const slicedItems = data.items.slice(0, itemsToRender);
|
|
106
|
-
const restItems = data.items.slice(itemsToRender);
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<>
|
|
110
|
-
<RestrictionNavigationItem
|
|
111
|
-
removeRestrictParam
|
|
112
|
-
label="All"
|
|
113
|
-
selected={restrictionValues.length === 0}
|
|
114
|
-
/>
|
|
115
|
-
|
|
116
|
-
{slicedItems.map((subject: any) => (
|
|
117
|
-
<RestrictionNavigationItem
|
|
118
|
-
key={subject.shortId}
|
|
119
|
-
shortId={subject.shortId!}
|
|
120
|
-
restrictField={restrictField as string}
|
|
121
|
-
label={subject.labels?.[0]?.value as string}
|
|
122
|
-
selected={restrictionValues.includes(subject.shortId!)}
|
|
123
|
-
/>
|
|
124
|
-
))}
|
|
125
|
-
|
|
126
|
-
<NavigationMenuItem>
|
|
127
|
-
<DropdownHoverItem
|
|
128
|
-
label={
|
|
129
|
-
<Button variant="outline" rounded="full">
|
|
130
|
-
More <ChevronDown className="size-4" />
|
|
131
|
-
</Button>
|
|
132
|
-
}
|
|
133
|
-
>
|
|
134
|
-
{restItems.map((subject: any) => (
|
|
135
|
-
<RestrictionDropdownItem
|
|
136
|
-
key={subject.shortId}
|
|
137
|
-
shortId={subject.shortId!}
|
|
138
|
-
restrictField={restrictField as string}
|
|
139
|
-
label={subject.labels?.[0]?.value as string}
|
|
140
|
-
selected={restrictionValues.includes(subject.shortId!)}
|
|
141
|
-
/>
|
|
142
|
-
))}
|
|
143
|
-
|
|
144
|
-
</DropdownHoverItem>
|
|
145
|
-
</NavigationMenuItem>
|
|
146
|
-
|
|
147
|
-
</>
|
|
148
|
-
);
|
|
149
|
-
}}
|
|
150
|
-
</RequestComponent>
|
|
151
|
-
)}
|
|
152
|
-
|
|
153
|
-
</NavigationMenuList>
|
|
154
|
-
</NavigationMenu>
|
|
155
|
-
</div>
|
|
115
|
+
<NavigationMenu className="max-w-full w-full c-rex-restriction-menu">
|
|
116
|
+
<NavigationMenuList className={cn("w-full", navigationMenuListClassName)}>
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
<RestrictionNavigationItem
|
|
120
|
+
removeRestrictParam
|
|
121
|
+
label="All"
|
|
122
|
+
selected={restrictionValues.length === 0}
|
|
123
|
+
/>
|
|
124
|
+
|
|
125
|
+
{visibleItems.map((item) => (
|
|
126
|
+
<RestrictionNavigationItem
|
|
127
|
+
key={item.shortId}
|
|
128
|
+
shortId={item.shortId!}
|
|
129
|
+
restrictField={restrictField as string}
|
|
130
|
+
label={getLabelByLang(item.labels, lang)}
|
|
131
|
+
selected={restrictionValues.includes(item.shortId!)}
|
|
132
|
+
/>
|
|
133
|
+
))}
|
|
134
|
+
|
|
135
|
+
<NavigationMenuItem>
|
|
136
|
+
<DropdownHoverItem
|
|
137
|
+
label={
|
|
138
|
+
<Button variant="outline" rounded="full">
|
|
139
|
+
More <ChevronDown className="size-4" />
|
|
140
|
+
</Button>
|
|
141
|
+
}
|
|
142
|
+
>
|
|
143
|
+
{hiddenItems.map((item) => (
|
|
144
|
+
<RestrictionDropdownItem
|
|
145
|
+
key={item.shortId}
|
|
146
|
+
shortId={item.shortId!}
|
|
147
|
+
restrictField={restrictField as string}
|
|
148
|
+
label={getLabelByLang(item.labels, lang)}
|
|
149
|
+
selected={restrictionValues.includes(item.shortId!)}
|
|
150
|
+
/>
|
|
151
|
+
))}
|
|
152
|
+
</DropdownHoverItem>
|
|
153
|
+
</NavigationMenuItem>
|
|
154
|
+
</NavigationMenuList>
|
|
155
|
+
</NavigationMenu>
|
|
156
156
|
);
|
|
157
157
|
};
|
|
@@ -34,7 +34,7 @@ export const DialogFilter: FC<Props> = ({ trigger }) => {
|
|
|
34
34
|
const [like, setLike] = useState<boolean>(useSearchSettingsStore.getState().like);
|
|
35
35
|
const [operator, setOperator] = useState<string>(useSearchSettingsStore.getState().operator);
|
|
36
36
|
const [wildcard, setWildcard] = useState<string>(useSearchSettingsStore.getState().wildcard);
|
|
37
|
-
const [language, setLanguage] = useState<string>(searchLanguage);
|
|
37
|
+
const [language, setLanguage] = useState<string>(searchLanguage || '');
|
|
38
38
|
|
|
39
39
|
const [params, setParams] = useQueryStates({
|
|
40
40
|
search: parseAsString,
|
|
@@ -66,7 +66,8 @@ export const DialogFilter: FC<Props> = ({ trigger }) => {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
const reload = () => {
|
|
69
|
-
|
|
69
|
+
const language = useSearchSettingsStore.getState().language || '';
|
|
70
|
+
if (language) setLanguage(language);
|
|
70
71
|
setWildcard(useSearchSettingsStore.getState().wildcard);
|
|
71
72
|
setOperator(useSearchSettingsStore.getState().operator);
|
|
72
73
|
setLike(useSearchSettingsStore.getState().like);
|
|
@@ -10,6 +10,7 @@ import { OPERATOR_OPTIONS } from "@c-rex/constants";
|
|
|
10
10
|
import { Tags } from "@c-rex/interfaces";
|
|
11
11
|
import { memoizeFilteredTags } from "./filter-sidebar/utils";
|
|
12
12
|
import { useSearchSettingsStore } from "../stores/search-settings-store";
|
|
13
|
+
import { useRestrictionStore } from "../stores/restriction-store";
|
|
13
14
|
|
|
14
15
|
type filterModel = {
|
|
15
16
|
key: string
|
|
@@ -25,8 +26,9 @@ type FilterNavbarProps = {
|
|
|
25
26
|
|
|
26
27
|
export const FilterNavbar: FC<FilterNavbarProps> = ({ tags }) => {
|
|
27
28
|
const t = useTranslations()
|
|
29
|
+
const restrictionList = useRestrictionStore((state) => state.restrictionList);
|
|
28
30
|
const [params, setParams] = useQueryStates({
|
|
29
|
-
language: parseAsString.withDefault(useSearchSettingsStore.getState().language),
|
|
31
|
+
language: parseAsString.withDefault(useSearchSettingsStore.getState().language || ''),
|
|
30
32
|
page: parseAsInteger.withDefault(1),
|
|
31
33
|
wildcard: parseAsString.withDefault(useSearchSettingsStore.getState().wildcard),
|
|
32
34
|
operator: parseAsString.withDefault(useSearchSettingsStore.getState().operator),
|
|
@@ -106,6 +108,27 @@ export const FilterNavbar: FC<FilterNavbarProps> = ({ tags }) => {
|
|
|
106
108
|
})
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
if (params.restrict !== null) {
|
|
112
|
+
const splittedParam = params.restrict.split("=")
|
|
113
|
+
const key = splittedParam[0]
|
|
114
|
+
const shortIdList = splittedParam[1]?.split(",") || []
|
|
115
|
+
|
|
116
|
+
shortIdList.forEach((item, index) => {
|
|
117
|
+
const defaultValue = [...shortIdList]
|
|
118
|
+
defaultValue.splice(index, 1)
|
|
119
|
+
|
|
120
|
+
const value = defaultValue.length == 0 ? null : `${key}=${defaultValue.join(",")}`
|
|
121
|
+
|
|
122
|
+
filters.push({
|
|
123
|
+
key: "restrict",
|
|
124
|
+
name: t(`filter.tags.${key}`),
|
|
125
|
+
value: restrictionList.get(item) || item,
|
|
126
|
+
removable: true,
|
|
127
|
+
default: value
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
109
132
|
if (params.packages !== null) {
|
|
110
133
|
let label = params.packages
|
|
111
134
|
|
|
@@ -127,7 +150,7 @@ export const FilterNavbar: FC<FilterNavbarProps> = ({ tags }) => {
|
|
|
127
150
|
}
|
|
128
151
|
|
|
129
152
|
Object.keys(params)
|
|
130
|
-
.filter(item => !["page", "search", "language", "operator", "like", "wildcard", "filter", "packages"].includes(item))
|
|
153
|
+
.filter(item => !["page", "search", "language", "operator", "like", "wildcard", "filter", "restrict", "packages"].includes(item))
|
|
131
154
|
.forEach(item => {
|
|
132
155
|
if (params[item as keyof typeof params] === null || params[item as keyof typeof params] === undefined) return;
|
|
133
156
|
const value = params[item as keyof typeof params] as string
|
package/src/results/utils.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RenditionModel } from "@c-rex/interfaces";
|
|
2
2
|
import { DocumentsType } from "@c-rex/types";
|
|
3
3
|
|
|
4
4
|
export const getFileRenditions = ({ renditions }: { renditions: RenditionModel[] }): DocumentsType => {
|
|
@@ -45,23 +45,3 @@ export const getFileRenditions = ({ renditions }: { renditions: RenditionModel[]
|
|
|
45
45
|
return result
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
export const getDescription = (descriptions?: LiteralModel[] | null, abstracts?: LiteralModel[] | null): string => {
|
|
50
|
-
const desc = descriptions?.[0]?.value || abstracts?.[0]?.value;
|
|
51
|
-
return desc || "";
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export const getClassLabel = (classObj?: { labels?: LiteralModel[] | null }): string => {
|
|
55
|
-
return classObj?.labels?.[0]?.value || "Document";
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export const getVersions = (versionOf?: ObjectRefModel) => {
|
|
59
|
-
let versionList: string[] = []
|
|
60
|
-
if (versionOf && versionOf.labels) {
|
|
61
|
-
const aux = versionOf.labels.map(item => item.language!)
|
|
62
|
-
versionList = aux.filter((value) => value !== undefined);
|
|
63
|
-
}
|
|
64
|
-
return versionList;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
|
package/src/search-input.tsx
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { FC, useEffect, 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 { cn } from "@c-rex/utils";
|
|
7
6
|
import * as AutocompleteOptions from "./generated/suggestions";
|
|
8
7
|
import { useQueryState } from "nuqs";
|
|
9
8
|
|
|
@@ -18,17 +17,14 @@ type Props = {
|
|
|
18
17
|
alternativeOnSelectPath?: string;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
20
|
export const SearchInput: FC<Props> = ({
|
|
24
21
|
showPkgFilter,
|
|
25
22
|
autocompleteType,
|
|
26
23
|
onSelectPath,
|
|
27
|
-
placedOn = "NAVBAR",
|
|
28
24
|
alternativeAutocompleteType,
|
|
29
25
|
alternativeOnSelectPath
|
|
30
26
|
}) => {
|
|
31
|
-
const [pkg
|
|
27
|
+
const [pkg] = useQueryState("package");
|
|
32
28
|
const [checked, setChecked] = useState<boolean>(true);
|
|
33
29
|
const [autocompleteComponentName, setAutocompleteComponentName] = useState<keyof typeof AutocompleteOptions>(autocompleteType);
|
|
34
30
|
|
|
@@ -45,10 +41,7 @@ export const SearchInput: FC<Props> = ({
|
|
|
45
41
|
|
|
46
42
|
|
|
47
43
|
return (
|
|
48
|
-
|
|
49
|
-
placedOn === "NAVBAR" ? "hidden lg:flex" : "sm:flex-none sm:w-60 md:w-72 lg:w-full lg:hidden flex",
|
|
50
|
-
"flex-1 items-center px-3 border rounded-full h-8 c-rex-search-bar"
|
|
51
|
-
)}>
|
|
44
|
+
<>
|
|
52
45
|
<Search className="shrink-0 opacity-50" />
|
|
53
46
|
|
|
54
47
|
{/* Add scope=pkgID if checked is true */}
|
|
@@ -73,7 +66,6 @@ export const SearchInput: FC<Props> = ({
|
|
|
73
66
|
</Tooltip>
|
|
74
67
|
</TooltipProvider>
|
|
75
68
|
</>}
|
|
76
|
-
|
|
77
|
-
</div>
|
|
69
|
+
</>
|
|
78
70
|
);
|
|
79
71
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
|
|
3
|
+
type RestrictionStore = {
|
|
4
|
+
restrictionList: Map<string, string>;
|
|
5
|
+
setRestrictionList: (restrictionList: Map<string, string>) => void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const useRestrictionStore = create<RestrictionStore>((set) => ({
|
|
9
|
+
restrictionList: new Map(),
|
|
10
|
+
setRestrictionList: (restrictionList) => set({ restrictionList }),
|
|
11
|
+
}));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OPERATOR_OPTIONS, WILD_CARD_OPTIONS } from "@c-rex/constants";
|
|
2
2
|
import { OperatorType, WildCardType } from "@c-rex/types";
|
|
3
3
|
import { create } from "zustand";
|
|
4
4
|
import { persist, createJSONStorage, StateStorage } from "zustand/middleware";
|
|
@@ -21,7 +21,7 @@ const cookieStorage: StateStorage = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export type SearchSettingsState = {
|
|
24
|
-
language
|
|
24
|
+
language?: string,
|
|
25
25
|
wildcard: WildCardType,
|
|
26
26
|
operator: OperatorType,
|
|
27
27
|
like: boolean,
|
|
@@ -32,7 +32,7 @@ export type SearchSettingsStore = SearchSettingsState & {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export const defaultSearchSettings: SearchSettingsState = {
|
|
35
|
-
language:
|
|
35
|
+
//language: DEFAULT_LANG,
|
|
36
36
|
wildcard: WILD_CARD_OPTIONS.BOTH,
|
|
37
37
|
operator: OPERATOR_OPTIONS.OR,
|
|
38
38
|
like: false,
|