@c-rex/components 0.1.1 → 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 +17 -2
- package/src/blog-card.tsx +3 -0
- 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 +32 -34
- package/src/page-wrapper.tsx +16 -0
- package/src/pagination.tsx +65 -0
- package/src/result-list.tsx +21 -6
- package/src/result-view/blog.tsx +5 -11
- package/src/result-view/dropdown-menu.tsx +39 -0
- package/src/result-view/table.tsx +51 -23
- package/src/sidebar.tsx +84 -68
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,11 +56,25 @@
|
|
|
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": {
|
|
62
74
|
"storybook": "storybook dev -p 6006",
|
|
63
|
-
"build-storybook": "storybook build"
|
|
75
|
+
"build-storybook": "storybook build",
|
|
76
|
+
"lint": "eslint .",
|
|
77
|
+
"lint:fix": "eslint . --fix"
|
|
64
78
|
},
|
|
65
79
|
"devDependencies": {
|
|
66
80
|
"@c-rex/eslint-config": "*",
|
|
@@ -100,6 +114,7 @@
|
|
|
100
114
|
"nuqs": "^2.4.3",
|
|
101
115
|
"react": "^18",
|
|
102
116
|
"react-dom": "^18",
|
|
117
|
+
"react-icons": "^5.5.0",
|
|
103
118
|
"tailwindcss-animate": "^1.0.7"
|
|
104
119
|
},
|
|
105
120
|
"eslintConfig": {
|
package/src/blog-card.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Link from "next/link";
|
|
2
|
+
import React from "react";
|
|
2
3
|
import { cn } from "@c-rex/utils";
|
|
3
4
|
import { BlurImage } from "./blur-image";
|
|
4
5
|
import { useTranslations } from "next-intl";
|
|
@@ -68,6 +69,7 @@ export const BlogCard = ({
|
|
|
68
69
|
</p>
|
|
69
70
|
)}
|
|
70
71
|
</div>
|
|
72
|
+
{/*
|
|
71
73
|
<div className="mt-4 flex items-center space-x-3">
|
|
72
74
|
<div className="flex items-center -space-x-2">
|
|
73
75
|
{data.authors && (
|
|
@@ -79,6 +81,7 @@ export const BlogCard = ({
|
|
|
79
81
|
<p className="text-sm text-muted-foreground">{data.date}</p>
|
|
80
82
|
)}
|
|
81
83
|
</div>
|
|
84
|
+
*/}
|
|
82
85
|
</div>
|
|
83
86
|
<Link href={data.slug} className="absolute inset-0" target="_blank">
|
|
84
87
|
<span className="sr-only">{t("viewArticle")}</span>
|
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,43 +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
|
-
const
|
|
32
|
+
const configs = await getConfigs();
|
|
33
|
+
const languageService = new LanguageService(configs.languageSwitcher.endpoint);
|
|
34
|
+
const availableLanguagesAndCountries = await languageService.getLanguagesAndCountries();
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
|
|
36
|
+
let session: any;
|
|
37
|
+
if (configs.OIDC.user.enabled) {
|
|
38
|
+
session = await getServerSession();
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
const configs: ConfigInterface = JSON.parse(jsonConfigs);
|
|
42
|
-
const service = new LanguageService(configs.languageSwitcher.endpoint);
|
|
43
|
-
const contentLanguages = await service.getLanguagesAndCountries();
|
|
44
|
-
|
|
45
|
-
const UILanguages = UI_LANG_OPTIONS.map((lang) => ({
|
|
46
|
-
value: lang,
|
|
47
|
-
lang: lang,
|
|
48
|
-
country: getCountryCodeByLang(lang)
|
|
49
|
-
}));
|
|
50
|
-
|
|
51
41
|
return (
|
|
52
|
-
<header className="sticky top-0 z-40 flex w-full
|
|
53
|
-
<div className="
|
|
54
|
-
<div className="flex
|
|
55
|
-
<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">
|
|
56
46
|
<Image
|
|
57
47
|
src="/img/logo.png"
|
|
58
48
|
alt="C-rex Logo"
|
|
@@ -60,10 +50,20 @@ export const NavBar: FC<NavBarProps> = async () => {
|
|
|
60
50
|
height={50}
|
|
61
51
|
/>
|
|
62
52
|
</Link>
|
|
63
|
-
|
|
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
|
+
)}
|
|
64
59
|
</div>
|
|
65
60
|
|
|
66
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
|
+
|
|
67
67
|
{configs.OIDC.user.enabled && (
|
|
68
68
|
<>
|
|
69
69
|
{session ? (
|
|
@@ -82,7 +82,7 @@ export const NavBar: FC<NavBarProps> = async () => {
|
|
|
82
82
|
<DropdownMenuTrigger>
|
|
83
83
|
<Settings />
|
|
84
84
|
</DropdownMenuTrigger>
|
|
85
|
-
<DropdownMenuContent>
|
|
85
|
+
<DropdownMenuContent align="start" sideOffset={20} alignOffset={20}>
|
|
86
86
|
<DropdownMenuLabel>{t("accountSettings.accountSettings")}</DropdownMenuLabel>
|
|
87
87
|
<DropdownMenuSeparator />
|
|
88
88
|
|
|
@@ -94,7 +94,7 @@ export const NavBar: FC<NavBarProps> = async () => {
|
|
|
94
94
|
<DropdownMenuPortal>
|
|
95
95
|
<DropdownMenuSubContent>
|
|
96
96
|
<ContentLanguageSwitch
|
|
97
|
-
availableLanguagesAndCountries={
|
|
97
|
+
availableLanguagesAndCountries={availableLanguagesAndCountries}
|
|
98
98
|
/>
|
|
99
99
|
</DropdownMenuSubContent>
|
|
100
100
|
</DropdownMenuPortal>
|
|
@@ -108,9 +108,7 @@ export const NavBar: FC<NavBarProps> = async () => {
|
|
|
108
108
|
|
|
109
109
|
<DropdownMenuPortal>
|
|
110
110
|
<DropdownMenuSubContent>
|
|
111
|
-
<UILanguageSwitch
|
|
112
|
-
availableLanguagesAndCountries={UILanguages}
|
|
113
|
-
/>
|
|
111
|
+
<UILanguageSwitch />
|
|
114
112
|
</DropdownMenuSubContent>
|
|
115
113
|
</DropdownMenuPortal>
|
|
116
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,21 +1,36 @@
|
|
|
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 { Pagination } from "./pagination";
|
|
6
7
|
|
|
7
8
|
interface ResultListProps {
|
|
8
|
-
items:
|
|
9
|
-
configs: ConfigInterface
|
|
9
|
+
items: informationUnitsResponseItems[];
|
|
10
|
+
configs: ConfigInterface;
|
|
11
|
+
pagination: DefaultPageInfo;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
export const ResultList: FC<ResultListProps> = ({ items, configs }) => {
|
|
13
|
-
const ViewComponent = configs.resultViewStyle == "table" ? TableView : BlogView;
|
|
14
|
+
export const ResultList: FC<ResultListProps> = ({ items, configs, pagination }: ResultListProps) => {
|
|
15
|
+
const ViewComponent = configs.results.resultViewStyle == "table" ? TableView : BlogView;
|
|
14
16
|
|
|
15
17
|
return (
|
|
16
18
|
<>
|
|
17
19
|
{
|
|
18
|
-
items.length == 0 ?
|
|
20
|
+
items.length == 0 ? (
|
|
21
|
+
<Empty />
|
|
22
|
+
) : (
|
|
23
|
+
<>
|
|
24
|
+
<ViewComponent
|
|
25
|
+
items={items}
|
|
26
|
+
/>
|
|
27
|
+
|
|
28
|
+
<Pagination
|
|
29
|
+
totalPages={pagination.pageCount}
|
|
30
|
+
currentPage={pagination.pageNumber}
|
|
31
|
+
/>
|
|
32
|
+
</>
|
|
33
|
+
)
|
|
19
34
|
}
|
|
20
35
|
</>
|
|
21
36
|
);
|
package/src/result-view/blog.tsx
CHANGED
|
@@ -1,18 +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:
|
|
6
|
+
items: informationUnitsResponseItems[];
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
const BlogView: FC<BlogViewProps> = ({ items }) => {
|
|
10
|
-
const getTitle = (labels: Labels[]): string => {
|
|
11
|
-
if (labels === undefined) {
|
|
12
|
-
return "";
|
|
13
|
-
}
|
|
14
|
-
return labels.map((item) => item.value).join();
|
|
15
|
-
};
|
|
16
10
|
|
|
17
11
|
return (
|
|
18
12
|
<div className="grid gap-8 md:grid-cols-2 md:gap-x-6 md:gap-y-10 xl:grid-cols-3">
|
|
@@ -21,14 +15,14 @@ const BlogView: FC<BlogViewProps> = ({ items }) => {
|
|
|
21
15
|
<BlogCard
|
|
22
16
|
key={index}
|
|
23
17
|
data={{
|
|
24
|
-
title:
|
|
18
|
+
title: item.title,
|
|
25
19
|
blurDataURL: "/img/blog-post-1.webp",
|
|
26
20
|
image: "/img/blog-post-1.webp",
|
|
27
|
-
description:
|
|
21
|
+
description: item.type,
|
|
28
22
|
authors: "item.authors",
|
|
29
23
|
_id: "item._id",
|
|
30
24
|
date: "item.date",
|
|
31
|
-
slug:
|
|
25
|
+
slug: item.link,
|
|
32
26
|
}}
|
|
33
27
|
/>
|
|
34
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
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { FC
|
|
1
|
+
import React, { FC } from "react";
|
|
2
2
|
import {
|
|
3
3
|
Table,
|
|
4
4
|
TableBody,
|
|
@@ -6,46 +6,74 @@ import {
|
|
|
6
6
|
TableHeader,
|
|
7
7
|
TableRow,
|
|
8
8
|
} from "@c-rex/ui/table";
|
|
9
|
-
import {
|
|
10
|
-
import Link from "next/link";
|
|
9
|
+
import { informationUnitsResponseItems } from "@c-rex/interfaces";
|
|
11
10
|
import { useTranslations } from "next-intl";
|
|
11
|
+
import { Ban, CloudDownload, CloudOff, Eye, EyeOff } from "lucide-react";
|
|
12
|
+
import { DropdownMenu } from "./dropdown-menu";
|
|
12
13
|
|
|
13
14
|
interface TableViewProps {
|
|
14
|
-
items:
|
|
15
|
+
items: informationUnitsResponseItems[];
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
const TableView: FC<TableViewProps> = ({ items }) => {
|
|
18
19
|
const t = useTranslations("results")
|
|
19
20
|
|
|
20
|
-
const getTitle = (labels: Labels[]): string => {
|
|
21
|
-
return labels.map((item) => item.value).join();
|
|
22
|
-
};
|
|
23
|
-
const getIcons = (languages: string[]): string => {
|
|
24
|
-
return languages.map((lang) => lang.split("-")[1]).join(",");
|
|
25
|
-
};
|
|
26
|
-
|
|
27
21
|
return (
|
|
28
22
|
<div className="rounded-md border">
|
|
29
23
|
<Table>
|
|
30
24
|
<TableHeader>
|
|
31
25
|
<TableRow>
|
|
32
26
|
<TableCell>{t("title")}</TableCell>
|
|
27
|
+
<TableCell>{t("type")}</TableCell>
|
|
33
28
|
<TableCell>{t("language")}</TableCell>
|
|
29
|
+
<TableCell>{t("files")}</TableCell>
|
|
34
30
|
</TableRow>
|
|
35
31
|
</TableHeader>
|
|
36
32
|
<TableBody>
|
|
37
|
-
{items.map((item
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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" : ""}`
|
|
35
|
+
|
|
36
|
+
return (
|
|
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
|
+
)}
|
|
73
|
+
</TableCell>
|
|
74
|
+
</TableRow>
|
|
75
|
+
)
|
|
76
|
+
})}
|
|
49
77
|
</TableBody>
|
|
50
78
|
</Table>
|
|
51
79
|
</div>
|
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,20 +13,20 @@ 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
|
-
import { TreeOfContent } from "@c-rex/interfaces";
|
|
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[];
|
|
24
25
|
loading?: boolean;
|
|
25
|
-
availableVersions:
|
|
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 => {
|
|
@@ -36,75 +37,90 @@ export function AppSidebar({ data, availableVersions, loading, ...props }: Sideb
|
|
|
36
37
|
return <FlagComponent />;
|
|
37
38
|
};
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
40
|
+
const tableOfContentGroup = (): JSX.Element | null => {
|
|
41
|
+
if (loading) {
|
|
42
|
+
return (
|
|
43
|
+
<>
|
|
44
|
+
<Skeleton className="w-auto h-10 mb-2" />
|
|
45
|
+
<Skeleton className="w-auto h-10 mb-2" />
|
|
46
|
+
<Skeleton className="w-auto h-10 mb-2 ml-8" />
|
|
47
|
+
<Skeleton className="w-auto h-10 mb-2 ml-8" />
|
|
48
|
+
<Skeleton className="w-auto h-10 mb-2 ml-8" />
|
|
49
|
+
<Skeleton className="w-auto h-10 mb-2 ml-8" />
|
|
50
|
+
<Skeleton className="w-auto h-10 mb-2" />
|
|
51
|
+
</>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (data.length == 0) return null;
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<SidebarGroup>
|
|
59
|
+
<SidebarMenu>
|
|
60
|
+
{data.map((item) => (
|
|
61
|
+
<SidebarMenuItem key={item.id}>
|
|
62
|
+
<SidebarMenuButton asChild isActive={item.active}>
|
|
63
|
+
<a href={item.link} title={item.label}>
|
|
64
|
+
{item.label}
|
|
65
|
+
</a>
|
|
66
|
+
</SidebarMenuButton>
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
68
|
+
{item.children?.length ? (
|
|
69
|
+
<SidebarMenuSub>
|
|
70
|
+
{item.children.map((item) => (
|
|
71
|
+
<SidebarMenuSubItem key={item.label}>
|
|
72
|
+
<SidebarMenuSubButton
|
|
73
|
+
asChild
|
|
74
|
+
isActive={item.active}
|
|
75
|
+
>
|
|
76
|
+
<a href={item.link} title={item.label}>
|
|
77
|
+
{item.label}
|
|
78
|
+
</a>
|
|
79
|
+
</SidebarMenuSubButton>
|
|
80
|
+
</SidebarMenuSubItem>
|
|
81
|
+
))}
|
|
82
|
+
</SidebarMenuSub>
|
|
83
|
+
) : null}
|
|
84
|
+
</SidebarMenuItem>
|
|
85
|
+
))}
|
|
86
|
+
</SidebarMenu>
|
|
87
|
+
</SidebarGroup>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
87
90
|
|
|
88
|
-
|
|
89
|
-
<SidebarGroupLabel>{t("availableIn")}:</SidebarGroupLabel>
|
|
90
|
-
<SidebarMenu>
|
|
91
|
+
const availableVersionsGroup = (): JSX.Element | null => {
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
return (
|
|
93
|
+
if (availableVersions.length == 0) return null;
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
95
|
+
return (
|
|
96
|
+
<>
|
|
97
|
+
<SidebarGroupLabel>{t("availableIn")}:</SidebarGroupLabel>
|
|
98
|
+
<SidebarMenu>
|
|
99
|
+
{availableVersions.map((item) => {
|
|
100
|
+
return (
|
|
101
|
+
<SidebarMenuItem key={item.shortId}>
|
|
102
|
+
<SidebarMenuButton asChild isActive={item.active}>
|
|
103
|
+
<a href={item.link} title={item.lang}>
|
|
104
|
+
{getFlagIcon(item.country)} {item.lang}
|
|
105
|
+
</a>
|
|
106
|
+
</SidebarMenuButton>
|
|
107
|
+
</SidebarMenuItem>
|
|
108
|
+
)
|
|
109
|
+
})}
|
|
110
|
+
</SidebarMenu>
|
|
111
|
+
</>
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<Sidebar className={cn(className, "pt-20")} {...props}>
|
|
118
|
+
<SidebarContent>
|
|
119
|
+
{tableOfContentGroup()}
|
|
106
120
|
</SidebarContent>
|
|
107
|
-
<
|
|
121
|
+
<SidebarFooter>
|
|
122
|
+
{availableVersionsGroup()}
|
|
123
|
+
</SidebarFooter>
|
|
108
124
|
</Sidebar>
|
|
109
125
|
);
|
|
110
126
|
}
|