@c-rex/components 0.1.2 → 0.1.3
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 +14 -1
- package/src/blur-image.tsx +1 -1
- package/src/check-article-lang.tsx +43 -0
- package/src/cookies.tsx +50 -0
- package/src/dialog-filter.tsx +11 -4
- package/src/navbar/language-switcher/content-language-switch.tsx +22 -7
- package/src/navbar/language-switcher/shared.tsx +1 -1
- package/src/navbar/language-switcher/ui-language-switch.tsx +24 -15
- package/src/navbar/navbar.tsx +30 -36
- package/src/page-wrapper.tsx +16 -0
- package/src/pagination.tsx +65 -0
- package/src/result-list.tsx +19 -54
- package/src/result-view/blog.tsx +6 -8
- package/src/result-view/dropdown-menu.tsx +39 -0
- package/src/result-view/table.tsx +44 -19
- package/src/sidebar.tsx +11 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c-rex/components",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"files": [
|
|
5
5
|
"src"
|
|
6
6
|
],
|
|
@@ -56,6 +56,18 @@
|
|
|
56
56
|
"./dialog-filter": {
|
|
57
57
|
"types": "./src/dialog-filter.tsx",
|
|
58
58
|
"import": "./src/dialog-filter.tsx"
|
|
59
|
+
},
|
|
60
|
+
"./check-article-lang": {
|
|
61
|
+
"types": "./src/check-article-lang.tsx",
|
|
62
|
+
"import": "./src/check-article-lang.tsx"
|
|
63
|
+
},
|
|
64
|
+
"./cookies": {
|
|
65
|
+
"types": "./src/cookies.tsx",
|
|
66
|
+
"import": "./src/cookies.tsx"
|
|
67
|
+
},
|
|
68
|
+
"./page-wrapper": {
|
|
69
|
+
"types": "./src/page-wrapper.tsx",
|
|
70
|
+
"import": "./src/page-wrapper.tsx"
|
|
59
71
|
}
|
|
60
72
|
},
|
|
61
73
|
"scripts": {
|
|
@@ -102,6 +114,7 @@
|
|
|
102
114
|
"nuqs": "^2.4.3",
|
|
103
115
|
"react": "^18",
|
|
104
116
|
"react-dom": "^18",
|
|
117
|
+
"react-icons": "^5.5.0",
|
|
105
118
|
"tailwindcss-animate": "^1.0.7"
|
|
106
119
|
},
|
|
107
120
|
"eslintConfig": {
|
package/src/blur-image.tsx
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { SidebarAvailableVersionsInterface } from "@c-rex/interfaces";
|
|
4
|
+
import { FC, useEffect, useRef } from "react";
|
|
5
|
+
import { toast } from "sonner"
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
availableVersions: SidebarAvailableVersionsInterface[]
|
|
9
|
+
contentLanguage: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const CheckArticleLangToast: FC<Props> = ({
|
|
13
|
+
availableVersions,
|
|
14
|
+
contentLanguage,
|
|
15
|
+
}) => {
|
|
16
|
+
const hasRun = useRef(false)
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (hasRun.current) return
|
|
20
|
+
hasRun.current = true
|
|
21
|
+
|
|
22
|
+
const activeArticle = availableVersions.filter((item) => item.active)[0]
|
|
23
|
+
if (activeArticle == undefined || activeArticle.lang == contentLanguage) return
|
|
24
|
+
|
|
25
|
+
const articleAvailable = availableVersions.find((item) => item.lang === contentLanguage)
|
|
26
|
+
if (articleAvailable == undefined) return
|
|
27
|
+
|
|
28
|
+
showToast(articleAvailable.lang, articleAvailable.link)
|
|
29
|
+
}, [])
|
|
30
|
+
|
|
31
|
+
const showToast = (lang: string, link: string) => {
|
|
32
|
+
toast(`Read ${lang} version`, {
|
|
33
|
+
description: `This article is also available in ${lang}`,
|
|
34
|
+
action: {
|
|
35
|
+
label: `Open ${lang} version`,
|
|
36
|
+
onClick: () => window.location.href = link,
|
|
37
|
+
},
|
|
38
|
+
duration: 10000,
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return null
|
|
43
|
+
}
|
package/src/cookies.tsx
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { FC, useEffect } from "react";
|
|
4
|
+
import { CONTENT_LANG_KEY, UI_LANG_KEY, UI_LANG_OPTIONS } from "@c-rex/constants";
|
|
5
|
+
import { getCookieInFront } from "@c-rex/utils";
|
|
6
|
+
import { setCookie } from "@c-rex/utils/next-cookies";
|
|
7
|
+
import { ConfigInterface, LanguageAndCountries } from "@c-rex/interfaces";
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
configs: ConfigInterface;
|
|
11
|
+
availableLanguages: LanguageAndCountries[];
|
|
12
|
+
}
|
|
13
|
+
export const SetCookies: FC<Props> = ({ configs, availableLanguages }) => {
|
|
14
|
+
const manageUILanguage = async (configs: ConfigInterface): Promise<void> => {
|
|
15
|
+
const hasUILangCookie = await getCookieInFront(UI_LANG_KEY);
|
|
16
|
+
|
|
17
|
+
if (hasUILangCookie.value === null) {
|
|
18
|
+
const browserLang = navigator.language;
|
|
19
|
+
|
|
20
|
+
const locale = UI_LANG_OPTIONS.includes(browserLang.toLowerCase())
|
|
21
|
+
? browserLang
|
|
22
|
+
: configs.languageSwitcher.default;
|
|
23
|
+
|
|
24
|
+
setCookie(UI_LANG_KEY, locale);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const manageContentLanguage = async (configs: ConfigInterface, availableLanguages: LanguageAndCountries[]): Promise<void> => {
|
|
29
|
+
const hasContentLangCookie = await getCookieInFront(CONTENT_LANG_KEY);
|
|
30
|
+
|
|
31
|
+
if (hasContentLangCookie.value === null) {
|
|
32
|
+
const browserLang = navigator.language;
|
|
33
|
+
const hasLang = availableLanguages.some((item) => item.value === browserLang);
|
|
34
|
+
|
|
35
|
+
const locale = hasLang
|
|
36
|
+
? browserLang
|
|
37
|
+
: configs.languageSwitcher.default;
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
setCookie(CONTENT_LANG_KEY, locale);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
manageUILanguage(configs);
|
|
46
|
+
manageContentLanguage(configs, availableLanguages);
|
|
47
|
+
}, [])
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
};
|
package/src/dialog-filter.tsx
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "@c-rex/ui/dialog"
|
|
12
12
|
import { Checkbox } from "@c-rex/ui/checkbox"
|
|
13
13
|
import { Label } from "@c-rex/ui/label"
|
|
14
|
-
import {
|
|
14
|
+
import { parseAsInteger, parseAsString, useQueryStates } from "nuqs"
|
|
15
15
|
import { useTranslations } from "next-intl"
|
|
16
16
|
|
|
17
17
|
interface DialogFilterProps {
|
|
@@ -27,10 +27,13 @@ export const DialogFilter: FC<DialogFilterProps> = ({
|
|
|
27
27
|
}) => {
|
|
28
28
|
const t = useTranslations();
|
|
29
29
|
|
|
30
|
-
const [
|
|
30
|
+
const [params, setParams] = useQueryStates({
|
|
31
|
+
language: parseAsString,
|
|
32
|
+
page: parseAsInteger,
|
|
33
|
+
}, {
|
|
31
34
|
history: 'push',
|
|
32
35
|
shallow: false,
|
|
33
|
-
})
|
|
36
|
+
});
|
|
34
37
|
|
|
35
38
|
const [languages, setLanguages] = useState(availableLanguages.map((item: any) => {
|
|
36
39
|
const checked = startSelectedLanguages.includes(item.value)
|
|
@@ -50,7 +53,11 @@ export const DialogFilter: FC<DialogFilterProps> = ({
|
|
|
50
53
|
|
|
51
54
|
const apply = () => {
|
|
52
55
|
const selectedLanguages = languages.filter((item: any) => item.checked).map((item: any) => item.value).join(',')
|
|
53
|
-
|
|
56
|
+
|
|
57
|
+
setParams({
|
|
58
|
+
page: 1,
|
|
59
|
+
language: selectedLanguages,
|
|
60
|
+
});
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
return (
|
|
@@ -1,27 +1,42 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import React, { FC, startTransition } from "react";
|
|
3
|
+
import React, { FC, startTransition, useEffect, useState } from "react";
|
|
4
4
|
import { LanguageAndCountries } from "@c-rex/interfaces";
|
|
5
5
|
import { SharedLanguageSwitch } from "./shared";
|
|
6
6
|
import { setCookie } from "@c-rex/utils/next-cookies";
|
|
7
|
-
import { CONTENT_LANG_KEY
|
|
7
|
+
import { CONTENT_LANG_KEY } from "@c-rex/constants";
|
|
8
8
|
import { useQueryState } from "nuqs"
|
|
9
|
+
import { getCookieInFront } from "@c-rex/utils";
|
|
9
10
|
|
|
10
11
|
interface ContentLanguageSwitchProps {
|
|
11
12
|
availableLanguagesAndCountries: LanguageAndCountries[];
|
|
12
13
|
}
|
|
13
14
|
export const ContentLanguageSwitch: FC<ContentLanguageSwitchProps> = ({ availableLanguagesAndCountries }) => {
|
|
14
|
-
|
|
15
|
-
const [
|
|
15
|
+
const [selected, setSelected] = useState("");
|
|
16
|
+
const [queryLanguage, setContentLanguage] = useQueryState('language', {
|
|
16
17
|
history: 'push',
|
|
17
18
|
shallow: false,
|
|
18
|
-
defaultValue: DEFAULT_UI_LANG
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const get = async () => {
|
|
23
|
+
const uiLang = await getCookieInFront(CONTENT_LANG_KEY)
|
|
24
|
+
|
|
25
|
+
if (uiLang.value != null) {
|
|
26
|
+
setSelected(uiLang.value)
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
get()
|
|
31
|
+
}, [])
|
|
32
|
+
|
|
21
33
|
const changeContentLanguage = (locale: string) => {
|
|
22
34
|
startTransition(() => {
|
|
23
35
|
setCookie(CONTENT_LANG_KEY, locale)
|
|
24
|
-
|
|
36
|
+
|
|
37
|
+
if (queryLanguage != null) {
|
|
38
|
+
setContentLanguage(locale)
|
|
39
|
+
}
|
|
25
40
|
});
|
|
26
41
|
};
|
|
27
42
|
|
|
@@ -29,7 +44,7 @@ export const ContentLanguageSwitch: FC<ContentLanguageSwitchProps> = ({ availabl
|
|
|
29
44
|
<SharedLanguageSwitch
|
|
30
45
|
availableLanguagesAndCountries={availableLanguagesAndCountries}
|
|
31
46
|
changeLanguage={changeContentLanguage}
|
|
32
|
-
selected={
|
|
47
|
+
selected={selected}
|
|
33
48
|
/>
|
|
34
49
|
);
|
|
35
50
|
};
|
|
@@ -23,8 +23,8 @@ export const SharedLanguageSwitch: FC<SharedLanguageSwitchProps> = ({ availableL
|
|
|
23
23
|
{availableLanguagesAndCountries.map(item => {
|
|
24
24
|
return (
|
|
25
25
|
<DropdownMenuRadioItem key={item.value} value={item.value}>
|
|
26
|
-
<span>{item.lang.toUpperCase()}</span>
|
|
27
26
|
{getFlagIcon(item.country)}
|
|
27
|
+
<span>{item.lang.toUpperCase()}</span>
|
|
28
28
|
</DropdownMenuRadioItem>
|
|
29
29
|
)
|
|
30
30
|
})}
|
|
@@ -1,26 +1,35 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import React, { FC, startTransition, useEffect, useState } from "react";
|
|
4
|
-
import { LanguageAndCountries } from "@c-rex/interfaces";
|
|
5
4
|
import { SharedLanguageSwitch } from "./shared";
|
|
6
5
|
import { setCookie } from "@c-rex/utils/next-cookies";
|
|
7
|
-
import {
|
|
6
|
+
import { UI_LANG_KEY, UI_LANG_OPTIONS } from "@c-rex/constants";
|
|
7
|
+
import { getCookieInFront, getCountryCodeByLang } from "@c-rex/utils";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
export const UILanguageSwitch: FC = () => {
|
|
10
|
+
const UILanguages = UI_LANG_OPTIONS.map((lang) => {
|
|
11
|
+
|
|
12
|
+
const langCode = lang.split("-")[0] as string;
|
|
13
|
+
return {
|
|
14
|
+
value: lang,
|
|
15
|
+
lang: langCode,
|
|
16
|
+
country: getCountryCodeByLang(langCode)
|
|
17
|
+
};
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const [selected, setSelected] = useState("");
|
|
14
21
|
|
|
15
22
|
useEffect(() => {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
.split('; ')
|
|
19
|
-
.find(row => row.startsWith(`${UI_LANG_KEY}=`))
|
|
20
|
-
?.split('=')[1]
|
|
21
|
-
setSelected(uiLang as string)
|
|
22
|
-
}, [])
|
|
23
|
+
const get = async () => {
|
|
24
|
+
const uiLang = await getCookieInFront(UI_LANG_KEY)
|
|
23
25
|
|
|
26
|
+
if (uiLang.value != null) {
|
|
27
|
+
setSelected(uiLang.value.toLowerCase())
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
get()
|
|
32
|
+
}, [])
|
|
24
33
|
|
|
25
34
|
const setUILanguage = (locale: string) => {
|
|
26
35
|
startTransition(() => {
|
|
@@ -30,7 +39,7 @@ export const UILanguageSwitch: FC<UILanguageSwitchProps> = ({ availableLanguages
|
|
|
30
39
|
|
|
31
40
|
return (
|
|
32
41
|
<SharedLanguageSwitch
|
|
33
|
-
availableLanguagesAndCountries={
|
|
42
|
+
availableLanguagesAndCountries={UILanguages}
|
|
34
43
|
changeLanguage={setUILanguage}
|
|
35
44
|
selected={selected}
|
|
36
45
|
/>
|
package/src/navbar/navbar.tsx
CHANGED
|
@@ -2,9 +2,6 @@ import React, { FC } from "react";
|
|
|
2
2
|
import Link from "next/link";
|
|
3
3
|
import Image from "next/image";
|
|
4
4
|
import { SignOut, SignInBtn } from "./sign-in-out-btns";
|
|
5
|
-
import { getServerSession } from "next-auth";
|
|
6
|
-
import { getCookie } from '@c-rex/utils/next-cookies';
|
|
7
|
-
import { ConfigInterface } from "@c-rex/interfaces";
|
|
8
5
|
import {
|
|
9
6
|
DropdownMenu,
|
|
10
7
|
DropdownMenuContent,
|
|
@@ -16,47 +13,36 @@ import {
|
|
|
16
13
|
DropdownMenuSubTrigger,
|
|
17
14
|
DropdownMenuTrigger,
|
|
18
15
|
} from "@c-rex/ui/dropdown-menu"
|
|
19
|
-
import {
|
|
20
|
-
import { LanguageService } from "@c-rex/services";
|
|
21
|
-
import { Settings } from "lucide-react";
|
|
16
|
+
import { Search, Settings } from "lucide-react";
|
|
22
17
|
import { ContentLanguageSwitch } from "./language-switcher/content-language-switch";
|
|
23
18
|
import { getTranslations } from "next-intl/server";
|
|
24
|
-
import { getCountryCodeByLang } from "@c-rex/utils";
|
|
25
19
|
import { UILanguageSwitch } from "./language-switcher/ui-language-switch";
|
|
20
|
+
import { Input } from "@c-rex/ui/input";
|
|
21
|
+
import { getServerSession } from "next-auth";
|
|
22
|
+
import { getConfigs } from "@c-rex/utils/next-cookies";
|
|
23
|
+
import { LanguageService } from "@c-rex/services";
|
|
24
|
+
|
|
26
25
|
|
|
27
26
|
interface NavBarProps {
|
|
28
|
-
|
|
29
|
-
large?: boolean;
|
|
27
|
+
title: string;
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
export const NavBar: FC<NavBarProps> = async () => {
|
|
30
|
+
export const NavBar: FC<NavBarProps> = async ({ title }) => {
|
|
33
31
|
const t = await getTranslations();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
if (!jsonConfigs) {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const configs: ConfigInterface = JSON.parse(jsonConfigs);
|
|
42
|
-
const service = new LanguageService(configs.languageSwitcher.endpoint);
|
|
43
|
-
const contentLanguages = await service.getLanguagesAndCountries();
|
|
32
|
+
const configs = await getConfigs();
|
|
33
|
+
const languageService = new LanguageService(configs.languageSwitcher.endpoint);
|
|
34
|
+
const availableLanguagesAndCountries = await languageService.getLanguagesAndCountries();
|
|
44
35
|
|
|
36
|
+
let session: any;
|
|
45
37
|
if (configs.OIDC.user.enabled) {
|
|
46
38
|
session = await getServerSession();
|
|
47
39
|
}
|
|
48
40
|
|
|
49
|
-
const UILanguages = UI_LANG_OPTIONS.map((lang) => ({
|
|
50
|
-
value: lang,
|
|
51
|
-
lang: lang,
|
|
52
|
-
country: getCountryCodeByLang(lang)
|
|
53
|
-
}));
|
|
54
|
-
|
|
55
41
|
return (
|
|
56
|
-
<header className="sticky top-0 z-40 flex w-full
|
|
57
|
-
<div className="
|
|
58
|
-
<div className="flex
|
|
59
|
-
<Link href="/" className="flex items-center space-x-1.5">
|
|
42
|
+
<header className="sticky top-0 z-40 flex w-full backdrop-blur-xl transition-all bg-transparent border-b justify-center py-4">
|
|
43
|
+
<div className="w-full px-4 flex justify-between">
|
|
44
|
+
<div className="flex">
|
|
45
|
+
<Link href="/" className="flex items-center justify-center space-x-1.5 w-60">
|
|
60
46
|
<Image
|
|
61
47
|
src="/img/logo.png"
|
|
62
48
|
alt="C-rex Logo"
|
|
@@ -64,10 +50,20 @@ export const NavBar: FC<NavBarProps> = async () => {
|
|
|
64
50
|
height={50}
|
|
65
51
|
/>
|
|
66
52
|
</Link>
|
|
67
|
-
|
|
53
|
+
|
|
54
|
+
{title.length > 0 && (
|
|
55
|
+
<div className="flex items-center">
|
|
56
|
+
<h1 className="px-4 text-3xl font-bold tracking-tight text-balance">{title}</h1>
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
68
59
|
</div>
|
|
69
60
|
|
|
70
61
|
<div className="flex items-center space-x-3">
|
|
62
|
+
<div className="flex items-center px-3 border rounded-full h-8">
|
|
63
|
+
<Search className="h-4 w-4 shrink-0 opacity-50" />
|
|
64
|
+
<Input variant="embedded" placeholder="Search" />
|
|
65
|
+
</div>
|
|
66
|
+
|
|
71
67
|
{configs.OIDC.user.enabled && (
|
|
72
68
|
<>
|
|
73
69
|
{session ? (
|
|
@@ -86,7 +82,7 @@ export const NavBar: FC<NavBarProps> = async () => {
|
|
|
86
82
|
<DropdownMenuTrigger>
|
|
87
83
|
<Settings />
|
|
88
84
|
</DropdownMenuTrigger>
|
|
89
|
-
<DropdownMenuContent>
|
|
85
|
+
<DropdownMenuContent align="start" sideOffset={20} alignOffset={20}>
|
|
90
86
|
<DropdownMenuLabel>{t("accountSettings.accountSettings")}</DropdownMenuLabel>
|
|
91
87
|
<DropdownMenuSeparator />
|
|
92
88
|
|
|
@@ -98,7 +94,7 @@ export const NavBar: FC<NavBarProps> = async () => {
|
|
|
98
94
|
<DropdownMenuPortal>
|
|
99
95
|
<DropdownMenuSubContent>
|
|
100
96
|
<ContentLanguageSwitch
|
|
101
|
-
availableLanguagesAndCountries={
|
|
97
|
+
availableLanguagesAndCountries={availableLanguagesAndCountries}
|
|
102
98
|
/>
|
|
103
99
|
</DropdownMenuSubContent>
|
|
104
100
|
</DropdownMenuPortal>
|
|
@@ -112,9 +108,7 @@ export const NavBar: FC<NavBarProps> = async () => {
|
|
|
112
108
|
|
|
113
109
|
<DropdownMenuPortal>
|
|
114
110
|
<DropdownMenuSubContent>
|
|
115
|
-
<UILanguageSwitch
|
|
116
|
-
availableLanguagesAndCountries={UILanguages}
|
|
117
|
-
/>
|
|
111
|
+
<UILanguageSwitch />
|
|
118
112
|
</DropdownMenuSubContent>
|
|
119
113
|
</DropdownMenuPortal>
|
|
120
114
|
</DropdownMenuSub>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { NavBar } from './navbar/navbar';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
title: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const PageWrapper = ({ children, title }: Props) => {
|
|
10
|
+
return (
|
|
11
|
+
<>
|
|
12
|
+
<NavBar title={title} />
|
|
13
|
+
{children}
|
|
14
|
+
</>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React, { FC } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Pagination as PaginationUI,
|
|
4
|
+
PaginationContent,
|
|
5
|
+
PaginationItem,
|
|
6
|
+
PaginationLink,
|
|
7
|
+
PaginationNext,
|
|
8
|
+
PaginationPrevious,
|
|
9
|
+
} from "@c-rex/ui/pagination"
|
|
10
|
+
import { parseAsInteger, useQueryState } from "nuqs";
|
|
11
|
+
|
|
12
|
+
interface PaginationProps {
|
|
13
|
+
totalPages: number;
|
|
14
|
+
currentPage: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const Pagination: FC<PaginationProps> = ({ totalPages, currentPage }) => {
|
|
18
|
+
const disabledClass = "opacity-50 pointer-events-none";
|
|
19
|
+
|
|
20
|
+
const [_, setPage] = useQueryState('page',
|
|
21
|
+
parseAsInteger.withOptions({
|
|
22
|
+
history: 'push',
|
|
23
|
+
shallow: false,
|
|
24
|
+
})
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<PaginationUI className="py-4">
|
|
29
|
+
<PaginationContent>
|
|
30
|
+
<PaginationItem>
|
|
31
|
+
<PaginationPrevious
|
|
32
|
+
href="#"
|
|
33
|
+
className={currentPage === 1 ? disabledClass : ""}
|
|
34
|
+
onClick={() => setPage(currentPage - 1)}
|
|
35
|
+
/>
|
|
36
|
+
</PaginationItem>
|
|
37
|
+
|
|
38
|
+
{Array.from({ length: totalPages }, (_, index) => index + 1).map((page) => (
|
|
39
|
+
<PaginationItem key={page}>
|
|
40
|
+
<PaginationLink
|
|
41
|
+
href="#"
|
|
42
|
+
isActive={page === currentPage}
|
|
43
|
+
onClick={() => setPage(page)}
|
|
44
|
+
>
|
|
45
|
+
{page}
|
|
46
|
+
</PaginationLink>
|
|
47
|
+
</PaginationItem>
|
|
48
|
+
))}
|
|
49
|
+
|
|
50
|
+
{/*
|
|
51
|
+
<PaginationItem>
|
|
52
|
+
<PaginationEllipsis />
|
|
53
|
+
</PaginationItem>
|
|
54
|
+
*/}
|
|
55
|
+
<PaginationItem>
|
|
56
|
+
<PaginationNext
|
|
57
|
+
href="#"
|
|
58
|
+
onClick={() => setPage(currentPage + 1)}
|
|
59
|
+
className={currentPage === totalPages ? disabledClass : ""}
|
|
60
|
+
/>
|
|
61
|
+
</PaginationItem>
|
|
62
|
+
</PaginationContent>
|
|
63
|
+
</PaginationUI>
|
|
64
|
+
)
|
|
65
|
+
}
|
package/src/result-list.tsx
CHANGED
|
@@ -1,72 +1,37 @@
|
|
|
1
1
|
import React, { FC } from "react";
|
|
2
|
-
import { ConfigInterface,
|
|
2
|
+
import { ConfigInterface, DefaultPageInfo, informationUnitsResponseItems, } from "@c-rex/interfaces";
|
|
3
3
|
import { Empty } from "./empty";
|
|
4
4
|
import BlogView from './result-view/blog';
|
|
5
5
|
import TableView from './result-view/table';
|
|
6
|
-
import {
|
|
7
|
-
import { DEFAULT_UI_LANG, RESULT_TYPES } from "@c-rex/constants";
|
|
6
|
+
import { Pagination } from "./pagination";
|
|
8
7
|
|
|
9
8
|
interface ResultListProps {
|
|
10
|
-
items:
|
|
11
|
-
configs: ConfigInterface
|
|
9
|
+
items: informationUnitsResponseItems[];
|
|
10
|
+
configs: ConfigInterface;
|
|
11
|
+
pagination: DefaultPageInfo;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export const ResultList: FC<ResultListProps> = ({ items, configs }) => {
|
|
15
|
-
const
|
|
16
|
-
const ViewComponent = configs.resultViewStyle == "table" ? TableView : BlogView;
|
|
17
|
-
|
|
18
|
-
const getTitle = (labels: Labels[]): string => {
|
|
19
|
-
if (labels === undefined) {
|
|
20
|
-
return "";
|
|
21
|
-
}
|
|
22
|
-
return labels.map((item) => item.value).join();
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const getType = (labels: Labels[]): { localeLabel: string, comparableLabel: string } => {
|
|
26
|
-
const localeLabel = labels.find((item) => item.language === locale);
|
|
27
|
-
const comparableLabel = labels.find((item) => item.language === DEFAULT_UI_LANG);
|
|
28
|
-
|
|
29
|
-
if (!localeLabel || !comparableLabel) {
|
|
30
|
-
return {
|
|
31
|
-
localeLabel: "",
|
|
32
|
-
comparableLabel: ""
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
localeLabel: localeLabel.value,
|
|
38
|
-
comparableLabel: comparableLabel.value
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const handleByType = (type: string, shortId: string) => {
|
|
43
|
-
switch (type) {
|
|
44
|
-
case RESULT_TYPES.TOPIC:
|
|
45
|
-
window.location.href = `/topics/${shortId}`;
|
|
46
|
-
break;
|
|
47
|
-
case RESULT_TYPES.DOCUMENT:
|
|
48
|
-
//should check if its a pdf or not
|
|
49
|
-
// if pdf then open in new tab
|
|
50
|
-
// else download the file
|
|
51
|
-
window.open(`/documents/${shortId}`, '_blank');
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
14
|
+
export const ResultList: FC<ResultListProps> = ({ items, configs, pagination }: ResultListProps) => {
|
|
15
|
+
const ViewComponent = configs.results.resultViewStyle == "table" ? TableView : BlogView;
|
|
55
16
|
|
|
56
17
|
return (
|
|
57
|
-
|
|
18
|
+
<>
|
|
58
19
|
{
|
|
59
20
|
items.length == 0 ? (
|
|
60
21
|
<Empty />
|
|
61
22
|
) : (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
23
|
+
<>
|
|
24
|
+
<ViewComponent
|
|
25
|
+
items={items}
|
|
26
|
+
/>
|
|
27
|
+
|
|
28
|
+
<Pagination
|
|
29
|
+
totalPages={pagination.pageCount}
|
|
30
|
+
currentPage={pagination.pageNumber}
|
|
31
|
+
/>
|
|
32
|
+
</>
|
|
68
33
|
)
|
|
69
34
|
}
|
|
70
|
-
|
|
35
|
+
</>
|
|
71
36
|
);
|
|
72
37
|
};
|
package/src/result-view/blog.tsx
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import React, { FC } from "react";
|
|
2
2
|
import { BlogCard } from "../blog-card";
|
|
3
|
-
import {
|
|
3
|
+
import { informationUnitsResponseItems } from "@c-rex/interfaces";
|
|
4
4
|
|
|
5
5
|
interface BlogViewProps {
|
|
6
|
-
items:
|
|
7
|
-
getType: (labels: Labels[]) => { localeLabel: string, comparableLabel: string };
|
|
8
|
-
getTitle: (labels: Labels[]) => string;
|
|
6
|
+
items: informationUnitsResponseItems[];
|
|
9
7
|
}
|
|
10
8
|
|
|
11
|
-
const BlogView: FC<BlogViewProps> = ({ items
|
|
9
|
+
const BlogView: FC<BlogViewProps> = ({ items }) => {
|
|
12
10
|
|
|
13
11
|
return (
|
|
14
12
|
<div className="grid gap-8 md:grid-cols-2 md:gap-x-6 md:gap-y-10 xl:grid-cols-3">
|
|
@@ -17,14 +15,14 @@ const BlogView: FC<BlogViewProps> = ({ items, getTitle, getType }) => {
|
|
|
17
15
|
<BlogCard
|
|
18
16
|
key={index}
|
|
19
17
|
data={{
|
|
20
|
-
title:
|
|
18
|
+
title: item.title,
|
|
21
19
|
blurDataURL: "/img/blog-post-1.webp",
|
|
22
20
|
image: "/img/blog-post-1.webp",
|
|
23
|
-
description:
|
|
21
|
+
description: item.type,
|
|
24
22
|
authors: "item.authors",
|
|
25
23
|
_id: "item._id",
|
|
26
24
|
date: "item.date",
|
|
27
|
-
slug:
|
|
25
|
+
slug: item.link,
|
|
28
26
|
}}
|
|
29
27
|
/>
|
|
30
28
|
);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React, { FC, JSX } from "react";
|
|
2
|
+
import {
|
|
3
|
+
DropdownMenu as DropdownMenuComp,
|
|
4
|
+
DropdownMenuContent,
|
|
5
|
+
DropdownMenuItem,
|
|
6
|
+
DropdownMenuTrigger,
|
|
7
|
+
} from "@c-rex/ui/dropdown-menu";
|
|
8
|
+
|
|
9
|
+
interface DropdownMenuProps {
|
|
10
|
+
items: {
|
|
11
|
+
format: string;
|
|
12
|
+
link: string;
|
|
13
|
+
}[];
|
|
14
|
+
icon: JSX.Element;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const DropdownMenu: FC<DropdownMenuProps> = ({
|
|
18
|
+
items, icon
|
|
19
|
+
}) => {
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<DropdownMenuComp>
|
|
23
|
+
<DropdownMenuTrigger className="mx-2">
|
|
24
|
+
{icon}
|
|
25
|
+
</DropdownMenuTrigger>
|
|
26
|
+
<DropdownMenuContent>
|
|
27
|
+
{items.map((file, index) => {
|
|
28
|
+
return (
|
|
29
|
+
<DropdownMenuItem key={index}>
|
|
30
|
+
<a href={file.link} target="_blank" rel="noreferrer">
|
|
31
|
+
{file.format}
|
|
32
|
+
</a>
|
|
33
|
+
</DropdownMenuItem>
|
|
34
|
+
)
|
|
35
|
+
})}
|
|
36
|
+
</DropdownMenuContent>
|
|
37
|
+
</DropdownMenuComp>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -6,17 +6,16 @@ import {
|
|
|
6
6
|
TableHeader,
|
|
7
7
|
TableRow,
|
|
8
8
|
} from "@c-rex/ui/table";
|
|
9
|
-
import {
|
|
9
|
+
import { informationUnitsResponseItems } from "@c-rex/interfaces";
|
|
10
10
|
import { useTranslations } from "next-intl";
|
|
11
|
+
import { Ban, CloudDownload, CloudOff, Eye, EyeOff } from "lucide-react";
|
|
12
|
+
import { DropdownMenu } from "./dropdown-menu";
|
|
11
13
|
|
|
12
14
|
interface TableViewProps {
|
|
13
|
-
items:
|
|
14
|
-
getType: (labels: Labels[]) => { localeLabel: string, comparableLabel: string };
|
|
15
|
-
getTitle: (labels: Labels[]) => string;
|
|
16
|
-
handleByType: any;
|
|
15
|
+
items: informationUnitsResponseItems[];
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
const TableView: FC<TableViewProps> = ({ items
|
|
18
|
+
const TableView: FC<TableViewProps> = ({ items }) => {
|
|
20
19
|
const t = useTranslations("results")
|
|
21
20
|
|
|
22
21
|
return (
|
|
@@ -27,25 +26,51 @@ const TableView: FC<TableViewProps> = ({ items, getType, getTitle, handleByType
|
|
|
27
26
|
<TableCell>{t("title")}</TableCell>
|
|
28
27
|
<TableCell>{t("type")}</TableCell>
|
|
29
28
|
<TableCell>{t("language")}</TableCell>
|
|
29
|
+
<TableCell>{t("files")}</TableCell>
|
|
30
30
|
</TableRow>
|
|
31
31
|
</TableHeader>
|
|
32
32
|
<TableBody>
|
|
33
|
-
{items.map((item
|
|
34
|
-
const
|
|
35
|
-
let lang = "";
|
|
36
|
-
|
|
37
|
-
if (item.languages.length > 0) {
|
|
38
|
-
lang = item.languages[0] as string;
|
|
39
|
-
lang = lang.split("-")[0] as string;
|
|
40
|
-
}
|
|
33
|
+
{items.map((item, index) => {
|
|
34
|
+
const clazz = `h-12 c-rex_result_row c-rex_result_${item.type.toLowerCase()} ${item.disabled ? "c-rex_result_row_disabled" : ""}`
|
|
41
35
|
|
|
42
36
|
return (
|
|
43
|
-
<TableRow key={index}>
|
|
44
|
-
<TableCell className="
|
|
45
|
-
{
|
|
37
|
+
<TableRow key={index} className={clazz}>
|
|
38
|
+
<TableCell className="h-12 c-rex_result_cell">
|
|
39
|
+
{item.disabled ? (item.title) : (<a href={item.link}>{item.title}</a>)}
|
|
40
|
+
</TableCell>
|
|
41
|
+
<TableCell>{item.type}</TableCell>
|
|
42
|
+
<TableCell>{item.language}</TableCell>
|
|
43
|
+
<TableCell>
|
|
44
|
+
{(item.disabled || (item.filesToDownload.length == 0 && item.filesToOpen.length == 0)) ? (
|
|
45
|
+
<button disabled className="mx-2">
|
|
46
|
+
<Ban />
|
|
47
|
+
</button>
|
|
48
|
+
) : (
|
|
49
|
+
<>
|
|
50
|
+
{item.filesToDownload.length == 0 ? (
|
|
51
|
+
<button disabled className="mx-2">
|
|
52
|
+
<CloudOff />
|
|
53
|
+
</button>
|
|
54
|
+
) : (
|
|
55
|
+
<DropdownMenu
|
|
56
|
+
items={item.filesToDownload}
|
|
57
|
+
icon={<CloudDownload />}
|
|
58
|
+
/>
|
|
59
|
+
)}
|
|
60
|
+
|
|
61
|
+
{item.filesToOpen.length == 0 ? (
|
|
62
|
+
<button disabled className="mx-2">
|
|
63
|
+
<EyeOff />
|
|
64
|
+
</button>
|
|
65
|
+
) : (
|
|
66
|
+
<DropdownMenu
|
|
67
|
+
items={item.filesToOpen}
|
|
68
|
+
icon={<Eye />}
|
|
69
|
+
/>
|
|
70
|
+
)}
|
|
71
|
+
</>
|
|
72
|
+
)}
|
|
46
73
|
</TableCell>
|
|
47
|
-
<TableCell>{type.localeLabel}</TableCell>
|
|
48
|
-
<TableCell>{lang.toUpperCase()}</TableCell>
|
|
49
74
|
</TableRow>
|
|
50
75
|
)
|
|
51
76
|
})}
|
package/src/sidebar.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import React, { ComponentProps, JSX } from "react";
|
|
|
4
4
|
import {
|
|
5
5
|
Sidebar,
|
|
6
6
|
SidebarContent,
|
|
7
|
+
SidebarFooter,
|
|
7
8
|
SidebarGroup,
|
|
8
9
|
SidebarGroupLabel,
|
|
9
10
|
SidebarMenu,
|
|
@@ -12,12 +13,12 @@ import {
|
|
|
12
13
|
SidebarMenuSub,
|
|
13
14
|
SidebarMenuSubButton,
|
|
14
15
|
SidebarMenuSubItem,
|
|
15
|
-
SidebarRail
|
|
16
16
|
} from "@c-rex/ui/sidebar";
|
|
17
17
|
import { Skeleton } from "@c-rex/ui/skeleton";
|
|
18
18
|
import { SidebarAvailableVersionsInterface, TreeOfContent } from "@c-rex/interfaces";
|
|
19
19
|
import * as Flags from 'country-flag-icons/react/3x2';
|
|
20
20
|
import { useTranslations } from "next-intl";
|
|
21
|
+
import { cn } from "@c-rex/utils";
|
|
21
22
|
|
|
22
23
|
interface SidebarProps extends ComponentProps<typeof Sidebar> {
|
|
23
24
|
data: TreeOfContent[];
|
|
@@ -25,7 +26,7 @@ interface SidebarProps extends ComponentProps<typeof Sidebar> {
|
|
|
25
26
|
availableVersions: SidebarAvailableVersionsInterface[]
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export function AppSidebar({ data, availableVersions, loading, ...props }: SidebarProps) {
|
|
29
|
+
export function AppSidebar({ data, availableVersions, loading, className, ...props }: SidebarProps) {
|
|
29
30
|
const t = useTranslations();
|
|
30
31
|
|
|
31
32
|
const getFlagIcon = (countryCode: string): JSX.Element | null => {
|
|
@@ -54,8 +55,7 @@ export function AppSidebar({ data, availableVersions, loading, ...props }: Sideb
|
|
|
54
55
|
if (data.length == 0) return null;
|
|
55
56
|
|
|
56
57
|
return (
|
|
57
|
-
<SidebarGroup
|
|
58
|
-
<SidebarGroupLabel>{t("tableOfContent")}:</SidebarGroupLabel>
|
|
58
|
+
<SidebarGroup>
|
|
59
59
|
<SidebarMenu>
|
|
60
60
|
{data.map((item) => (
|
|
61
61
|
<SidebarMenuItem key={item.id}>
|
|
@@ -93,10 +93,10 @@ export function AppSidebar({ data, availableVersions, loading, ...props }: Sideb
|
|
|
93
93
|
if (availableVersions.length == 0) return null;
|
|
94
94
|
|
|
95
95
|
return (
|
|
96
|
-
|
|
96
|
+
<>
|
|
97
97
|
<SidebarGroupLabel>{t("availableIn")}:</SidebarGroupLabel>
|
|
98
98
|
<SidebarMenu>
|
|
99
|
-
{availableVersions.map((item
|
|
99
|
+
{availableVersions.map((item) => {
|
|
100
100
|
return (
|
|
101
101
|
<SidebarMenuItem key={item.shortId}>
|
|
102
102
|
<SidebarMenuButton asChild isActive={item.active}>
|
|
@@ -108,18 +108,19 @@ export function AppSidebar({ data, availableVersions, loading, ...props }: Sideb
|
|
|
108
108
|
)
|
|
109
109
|
})}
|
|
110
110
|
</SidebarMenu>
|
|
111
|
-
|
|
111
|
+
</>
|
|
112
112
|
)
|
|
113
113
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
return (
|
|
117
|
-
<Sidebar
|
|
117
|
+
<Sidebar className={cn(className, "pt-20")} {...props}>
|
|
118
118
|
<SidebarContent>
|
|
119
119
|
{tableOfContentGroup()}
|
|
120
|
-
{availableVersionsGroup()}
|
|
121
120
|
</SidebarContent>
|
|
122
|
-
<
|
|
121
|
+
<SidebarFooter>
|
|
122
|
+
{availableVersionsGroup()}
|
|
123
|
+
</SidebarFooter>
|
|
123
124
|
</Sidebar>
|
|
124
125
|
);
|
|
125
126
|
}
|