@c-rex/components 0.1.2 → 0.1.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c-rex/components",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "files": [
5
5
  "src"
6
6
  ],
@@ -49,6 +49,10 @@
49
49
  "types": "./src/result-view/table.tsx",
50
50
  "import": "./src/result-view/table.tsx"
51
51
  },
52
+ "./dropdown-menu": {
53
+ "types": "./src/result-view/dropdown-menu.tsx",
54
+ "import": "./src/result-view/dropdown-menu.tsx"
55
+ },
52
56
  "./sidebar": {
53
57
  "types": "./src/sidebar.tsx",
54
58
  "import": "./src/sidebar.tsx"
@@ -56,6 +60,18 @@
56
60
  "./dialog-filter": {
57
61
  "types": "./src/dialog-filter.tsx",
58
62
  "import": "./src/dialog-filter.tsx"
63
+ },
64
+ "./check-article-lang": {
65
+ "types": "./src/check-article-lang.tsx",
66
+ "import": "./src/check-article-lang.tsx"
67
+ },
68
+ "./render-article": {
69
+ "types": "./src/render-article.tsx",
70
+ "import": "./src/render-article.tsx"
71
+ },
72
+ "./page-wrapper": {
73
+ "types": "./src/page-wrapper.tsx",
74
+ "import": "./src/page-wrapper.tsx"
59
75
  }
60
76
  },
61
77
  "scripts": {
@@ -102,6 +118,7 @@
102
118
  "nuqs": "^2.4.3",
103
119
  "react": "^18",
104
120
  "react-dom": "^18",
121
+ "react-icons": "^5.5.0",
105
122
  "tailwindcss-animate": "^1.0.7"
106
123
  },
107
124
  "eslintConfig": {
@@ -31,6 +31,10 @@ export const AutoComplete = ({
31
31
  const [suggestions, setSuggestions] = useState<string[]>([]);
32
32
  const [open, setOpen] = useState(false);
33
33
 
34
+ useEffect(() => {
35
+ setQuery(initialValue);
36
+ }, [initialValue]);
37
+
34
38
  useEffect(() => {
35
39
  const debounceFetch = setTimeout(() => {
36
40
  if (query) {
@@ -56,7 +60,7 @@ export const AutoComplete = ({
56
60
  onMouseDown={() => setOpen(false)}
57
61
  onFocus={() => setOpen(true)}
58
62
  >
59
- <Input />
63
+ <Input value={query} />
60
64
  </CommandPrimitive.Input>
61
65
  </PopoverAnchor>
62
66
 
@@ -75,36 +79,29 @@ export const AutoComplete = ({
75
79
  }}
76
80
  className="w-[--radix-popover-trigger-width] p-0"
77
81
  >
78
- <CommandList>
79
- {/*isLoading && (
80
- <CommandPrimitive.Loading>
81
- <div className="p-1">
82
- <Skeleton className="h-6 w-full" />
83
- </div>
84
- </CommandPrimitive.Loading>
85
- )*/}
86
-
87
- {suggestions.length > 0 ? (
88
- <CommandGroup>
89
- {suggestions.map((option) => (
90
- <CommandItem
91
- key={option}
92
- value={option}
93
- onMouseDown={(e) => e.preventDefault()}
94
- onSelect={(inputValue) => {
95
- setOpen(false);
96
- onSelect(inputValue ?? "");
97
- }}
98
- >
99
- <Check className="mr-2 h-4 w-4 opacity-0" />
100
- {option}
101
- </CommandItem>
102
- ))}
103
- </CommandGroup>
104
- ) : (
105
- <CommandEmpty>No suggestions.</CommandEmpty>
106
- )}
107
- </CommandList>
82
+ {query.length > 0 && (
83
+ <CommandList>
84
+ {suggestions.length > 0 ? (
85
+ <CommandGroup>
86
+ {suggestions.map((option) => (
87
+ <CommandItem
88
+ key={option}
89
+ value={option}
90
+ onMouseDown={(e) => e.preventDefault()}
91
+ onSelect={(inputValue) => {
92
+ setOpen(false);
93
+ onSelect(inputValue);
94
+ }}
95
+ >
96
+ {option}
97
+ </CommandItem>
98
+ ))}
99
+ </CommandGroup>
100
+ ) : (
101
+ <CommandEmpty>No suggestions.</CommandEmpty>
102
+ )}
103
+ </CommandList>
104
+ )}
108
105
  </PopoverContent>
109
106
  </Command>
110
107
  </Popover>
package/src/blog-card.tsx CHANGED
@@ -1,58 +1,52 @@
1
1
  import Link from "next/link";
2
- import React from "react";
3
2
  import { cn } from "@c-rex/utils";
4
3
  import { BlurImage } from "./blur-image";
5
4
  import { useTranslations } from "next-intl";
5
+ import { informationUnitsResponseItem } from "@c-rex/interfaces";
6
6
 
7
7
  interface BlogCardProp {
8
- data: {
9
- title: string;
10
- blurDataURL: string;
11
- image: string;
12
- description: string;
13
- authors: string;
14
- _id: string;
15
- date: string;
16
- slug: string;
17
- };
8
+ item: informationUnitsResponseItem;
18
9
  priority?: boolean;
19
10
  horizontal?: boolean;
20
11
  }
21
12
 
22
13
  export const BlogCard = ({
23
- data,
14
+ item,
24
15
  priority,
25
16
  horizontal = false,
26
17
  }: BlogCardProp) => {
27
18
  const t = useTranslations("results")
19
+ const image = "/img/blog-post-1.webp"
20
+ const blurDataURL = "/img/blog-post-1.webp"
28
21
 
29
22
  return (
30
23
  <article
31
24
  className={cn(
32
- "group relative",
25
+ "group relative", `c-rex_result_${item.type.toLowerCase()}`,
33
26
  horizontal
34
27
  ? "grid grid-cols-1 gap-3 md:grid-cols-2 md:gap-6"
35
28
  : "flex flex-col space-y-2",
29
+ item.disabled ? "c-rex_result_item_disabled" : ""
36
30
  )}
37
31
  >
38
- {data.image && (
39
- <div className="w-full overflow-hidden rounded-xl border">
40
- <BlurImage
41
- alt={data.title}
42
- blurDataURL={data.blurDataURL}
43
- className={cn(
44
- "size-full object-cover object-center",
45
- horizontal ? "lg:h-72" : null,
46
- )}
47
- width={800}
48
- height={400}
49
- priority={priority}
50
- placeholder="blur"
51
- src={data.image}
52
- sizes="(max-width: 768px) 750px, 600px"
53
- />
54
- </div>
55
- )}
32
+
33
+ <div className="w-full overflow-hidden rounded-xl border">
34
+ <BlurImage
35
+ alt={item.title}
36
+ blurDataURL={blurDataURL}
37
+ className={cn(
38
+ "size-full object-cover object-center",
39
+ horizontal ? "lg:h-72" : null,
40
+ )}
41
+ width={800}
42
+ height={400}
43
+ priority={priority}
44
+ placeholder="blur"
45
+ src={image}
46
+ sizes="(max-width: 768px) 750px, 600px"
47
+ />
48
+ </div>
49
+
56
50
  <div
57
51
  className={cn(
58
52
  "flex flex-1 flex-col",
@@ -61,31 +55,34 @@ export const BlogCard = ({
61
55
  >
62
56
  <div className="w-full">
63
57
  <h2 className="my-1.5 line-clamp-2 font-heading text-2xl">
64
- {data.title}
58
+ {item.title}
65
59
  </h2>
66
- {data.description && (
60
+ {item.type && (
67
61
  <p className="line-clamp-2 text-muted-foreground">
68
- {data.description}
62
+ {item.type}
69
63
  </p>
70
64
  )}
71
65
  </div>
72
66
  {/*
73
67
  <div className="mt-4 flex items-center space-x-3">
74
68
  <div className="flex items-center -space-x-2">
75
- {data.authors && (
76
- <p className="text-sm text-muted-foreground">{data.authors}</p>
69
+ {item.authors && (
70
+ <p className="text-sm text-muted-foreground">{item.authors}</p>
77
71
  )}
78
72
  </div>
79
73
 
80
- {data.date && (
81
- <p className="text-sm text-muted-foreground">{data.date}</p>
74
+ {item.date && (
75
+ <p className="text-sm text-muted-foreground">{item.date}</p>
82
76
  )}
83
77
  </div>
84
78
  */}
85
79
  </div>
86
- <Link href={data.slug} className="absolute inset-0" target="_blank">
87
- <span className="sr-only">{t("viewArticle")}</span>
88
- </Link>
80
+
81
+ {!item.disabled && (
82
+ <Link href={item.link} className="absolute inset-0" target="_blank">
83
+ <span className="sr-only">{t("viewArticle")}</span>
84
+ </Link>
85
+ )}
89
86
  </article>
90
87
  );
91
88
  };
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useState } from "react";
3
+ import React, { useState } from "react";
4
4
  import type { ComponentProps } from "react";
5
5
  import Image from "next/image";
6
6
  import { cn } from "@c-rex/utils";
@@ -13,10 +13,11 @@ import { useTranslations } from 'next-intl';
13
13
 
14
14
  interface BreadcrumbProps {
15
15
  items: TreeOfContent[];
16
+ lang: string;
16
17
  loading?: boolean;
17
18
  }
18
19
 
19
- export const Breadcrumb: FC<BreadcrumbProps> = ({ items, loading }) => {
20
+ export const Breadcrumb: FC<BreadcrumbProps> = ({ items, loading, lang }) => {
20
21
  const t = useTranslations("breadcrumbs");
21
22
  if (!items) {
22
23
  return null;
@@ -61,7 +62,7 @@ export const Breadcrumb: FC<BreadcrumbProps> = ({ items, loading }) => {
61
62
  };
62
63
 
63
64
  return (
64
- <BreadcrumbComponent>
65
+ <BreadcrumbComponent lang={lang}>
65
66
  <BreadcrumbList>
66
67
  {newItemList.map((item, index) => {
67
68
  const isLast = index === newItemList.length - 1;
@@ -0,0 +1,41 @@
1
+ "use client"
2
+
3
+ import { FC, useEffect, useRef } from "react";
4
+ import { useAppConfig } from "@c-rex/contexts/config-provider";
5
+ import { SidebarAvailableVersionsInterface } from "@c-rex/interfaces";
6
+ import { toast } from "sonner"
7
+
8
+ interface Props {
9
+ availableVersions: SidebarAvailableVersionsInterface[]
10
+ }
11
+
12
+ export const CheckArticleLangToast: FC<Props> = ({ availableVersions }) => {
13
+ const { contentLang } = useAppConfig()
14
+ const hasRun = useRef(false)
15
+
16
+ useEffect(() => {
17
+ if (hasRun.current) return
18
+ hasRun.current = true
19
+
20
+ const activeArticle = availableVersions.filter((item) => item.active)[0]
21
+ if (activeArticle == undefined || activeArticle.lang == contentLang) return
22
+
23
+ const articleAvailable = availableVersions.find((item) => item.lang === contentLang)
24
+ if (articleAvailable == undefined) return
25
+
26
+ showToast(articleAvailable.lang, articleAvailable.link)
27
+ }, [])
28
+
29
+ const showToast = (lang: string, link: string) => {
30
+ toast(`Read ${lang} version`, {
31
+ description: `This article is also available in ${lang}`,
32
+ action: {
33
+ label: `Open ${lang} version`,
34
+ onClick: () => window.location.href = link,
35
+ },
36
+ duration: 10000,
37
+ })
38
+ }
39
+
40
+ return null
41
+ }
@@ -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 { useQueryState } from "nuqs"
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 [_, setContentLanguage] = useQueryState('language', {
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
- setContentLanguage(selectedLanguages)
56
+
57
+ setParams({
58
+ page: 1,
59
+ language: selectedLanguages,
60
+ });
54
61
  }
55
62
 
56
63
  return (
@@ -1,27 +1,28 @@
1
- 'use client'
1
+ "use client"
2
2
 
3
- import React, { FC, startTransition } from "react";
4
- import { LanguageAndCountries } from "@c-rex/interfaces";
3
+ import React, { startTransition } from "react";
5
4
  import { SharedLanguageSwitch } from "./shared";
6
- import { setCookie } from "@c-rex/utils/next-cookies";
7
- import { CONTENT_LANG_KEY, DEFAULT_UI_LANG } from "@c-rex/constants";
5
+ import { getFromCookieString, setCookie } from "@c-rex/utils";
6
+ import { CONTENT_LANG_KEY } from "@c-rex/constants";
8
7
  import { useQueryState } from "nuqs"
8
+ import { useAppConfig } from "@c-rex/contexts/config-provider";
9
9
 
10
- interface ContentLanguageSwitchProps {
11
- availableLanguagesAndCountries: LanguageAndCountries[];
12
- }
13
- export const ContentLanguageSwitch: FC<ContentLanguageSwitchProps> = ({ availableLanguagesAndCountries }) => {
14
-
15
- const [contentLanguage, setContentLanguage] = useQueryState('language', {
10
+ export const ContentLanguageSwitch = () => {
11
+ const { availableLanguagesAndCountries, setContentLang } = useAppConfig()
12
+ const contentLang = getFromCookieString(document.cookie, CONTENT_LANG_KEY)
13
+ const [queryLanguage, setContentLanguage] = useQueryState('language', {
16
14
  history: 'push',
17
15
  shallow: false,
18
- defaultValue: DEFAULT_UI_LANG
19
16
  })
20
17
 
21
18
  const changeContentLanguage = (locale: string) => {
22
19
  startTransition(() => {
23
20
  setCookie(CONTENT_LANG_KEY, locale)
24
- setContentLanguage(locale)
21
+ setContentLang(locale)
22
+
23
+ if (queryLanguage != null) {
24
+ setContentLanguage(locale)
25
+ }
25
26
  });
26
27
  };
27
28
 
@@ -29,7 +30,7 @@ export const ContentLanguageSwitch: FC<ContentLanguageSwitchProps> = ({ availabl
29
30
  <SharedLanguageSwitch
30
31
  availableLanguagesAndCountries={availableLanguagesAndCountries}
31
32
  changeLanguage={changeContentLanguage}
32
- selected={contentLanguage}
33
+ selected={contentLang}
33
34
  />
34
35
  );
35
36
  };
@@ -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,38 +1,37 @@
1
1
  "use client"
2
2
 
3
- import React, { FC, startTransition, useEffect, useState } from "react";
4
- import { LanguageAndCountries } from "@c-rex/interfaces";
3
+ import React, { FC, startTransition } from "react";
5
4
  import { SharedLanguageSwitch } from "./shared";
6
- import { setCookie } from "@c-rex/utils/next-cookies";
7
- import { DEFAULT_UI_LANG, UI_LANG_KEY } from "@c-rex/constants";
8
-
9
- interface UILanguageSwitchProps {
10
- availableLanguagesAndCountries: LanguageAndCountries[];
11
- }
12
- export const UILanguageSwitch: FC<UILanguageSwitchProps> = ({ availableLanguagesAndCountries }) => {
13
- const [selected, setSelected] = useState(DEFAULT_UI_LANG);
14
-
15
- useEffect(() => {
16
- const allCookies = document.cookie
17
- const uiLang = allCookies
18
- .split('; ')
19
- .find(row => row.startsWith(`${UI_LANG_KEY}=`))
20
- ?.split('=')[1]
21
- setSelected(uiLang as string)
22
- }, [])
5
+ import { UI_LANG_KEY, UI_LANG_OPTIONS } from "@c-rex/constants";
6
+ import { getCountryCodeByLang, getFromCookieString, setCookie } from "@c-rex/utils";
7
+ import { useAppConfig } from "@c-rex/contexts/config-provider";
23
8
 
9
+ export const UILanguageSwitch: FC = () => {
10
+ const { setUiLang } = useAppConfig()
11
+ const uiLang = getFromCookieString(document.cookie, UI_LANG_KEY).toLowerCase()
12
+ const UILanguages = UI_LANG_OPTIONS.map((lang) => {
13
+ const langCode = lang.split("-")[0] as string;
14
+ return {
15
+ value: lang,
16
+ lang: langCode,
17
+ country: getCountryCodeByLang(langCode)
18
+ };
19
+ })
24
20
 
25
21
  const setUILanguage = (locale: string) => {
26
22
  startTransition(() => {
27
23
  setCookie(UI_LANG_KEY, locale)
24
+ setUiLang(locale)
25
+
26
+ window.location.reload()
28
27
  });
29
28
  }
30
29
 
31
30
  return (
32
31
  <SharedLanguageSwitch
33
- availableLanguagesAndCountries={availableLanguagesAndCountries}
32
+ availableLanguagesAndCountries={UILanguages}
34
33
  changeLanguage={setUILanguage}
35
- selected={selected}
34
+ selected={uiLang}
36
35
  />
37
36
  );
38
37
  };
@@ -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,31 @@ import {
16
13
  DropdownMenuSubTrigger,
17
14
  DropdownMenuTrigger,
18
15
  } from "@c-rex/ui/dropdown-menu"
19
- import { SDK_CONFIG_KEY, UI_LANG_OPTIONS } from "@c-rex/constants";
20
- import { LanguageService } from "@c-rex/services";
21
16
  import { 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 { getServerSession } from "next-auth";
21
+ import { getConfigs } from "@c-rex/utils/next-cookies";
26
22
 
27
23
  interface NavBarProps {
28
- scroll?: boolean;
29
- large?: boolean;
24
+ title: string;
30
25
  }
31
26
 
32
- export const NavBar: FC<NavBarProps> = async () => {
27
+ export const NavBar: FC<NavBarProps> = async ({ title }) => {
33
28
  const t = await getTranslations();
34
- let session;
35
-
36
- const jsonConfigs = await getCookie(SDK_CONFIG_KEY);
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();
29
+ const configs = getConfigs();
44
30
 
31
+ let session: any;
45
32
  if (configs.OIDC.user.enabled) {
46
33
  session = await getServerSession();
47
34
  }
48
35
 
49
- const UILanguages = UI_LANG_OPTIONS.map((lang) => ({
50
- value: lang,
51
- lang: lang,
52
- country: getCountryCodeByLang(lang)
53
- }));
54
-
55
36
  return (
56
- <header className="sticky top-0 z-40 flex w-full bg-background/60 backdrop-blur-xl transition-all bg-transparent border-b justify-center py-4">
57
- <div className="container flex justify-between">
58
- <div className="flex gap-6 md:gap-10">
59
- <Link href="/" className="flex items-center space-x-1.5">
37
+ <header className="sticky top-0 z-40 flex w-full backdrop-blur-xl transition-all bg-transparent border-b justify-center py-4">
38
+ <div className="w-full px-4 flex justify-between">
39
+ <div className="flex">
40
+ <Link href="/" className="flex items-center justify-center space-x-1.5 w-60">
60
41
  <Image
61
42
  src="/img/logo.png"
62
43
  alt="C-rex Logo"
@@ -64,10 +45,22 @@ export const NavBar: FC<NavBarProps> = async () => {
64
45
  height={50}
65
46
  />
66
47
  </Link>
67
- {configs.projectName}
48
+
49
+ {title.length > 0 && (
50
+ <div className="flex items-center">
51
+ <h1 className="px-4 text-3xl font-bold tracking-tight text-balance">{title}</h1>
52
+ </div>
53
+ )}
68
54
  </div>
69
55
 
70
56
  <div className="flex items-center space-x-3">
57
+ {/*
58
+ <div className="flex items-center px-3 border rounded-full h-8">
59
+ <Search className="h-4 w-4 shrink-0 opacity-50" />
60
+ <Input variant="embedded" placeholder="Search" />
61
+ </div>
62
+ */}
63
+
71
64
  {configs.OIDC.user.enabled && (
72
65
  <>
73
66
  {session ? (
@@ -86,7 +79,7 @@ export const NavBar: FC<NavBarProps> = async () => {
86
79
  <DropdownMenuTrigger>
87
80
  <Settings />
88
81
  </DropdownMenuTrigger>
89
- <DropdownMenuContent>
82
+ <DropdownMenuContent align="start" sideOffset={20} alignOffset={20}>
90
83
  <DropdownMenuLabel>{t("accountSettings.accountSettings")}</DropdownMenuLabel>
91
84
  <DropdownMenuSeparator />
92
85
 
@@ -97,9 +90,7 @@ export const NavBar: FC<NavBarProps> = async () => {
97
90
 
98
91
  <DropdownMenuPortal>
99
92
  <DropdownMenuSubContent>
100
- <ContentLanguageSwitch
101
- availableLanguagesAndCountries={contentLanguages}
102
- />
93
+ <ContentLanguageSwitch />
103
94
  </DropdownMenuSubContent>
104
95
  </DropdownMenuPortal>
105
96
  </DropdownMenuSub>
@@ -112,9 +103,7 @@ export const NavBar: FC<NavBarProps> = async () => {
112
103
 
113
104
  <DropdownMenuPortal>
114
105
  <DropdownMenuSubContent>
115
- <UILanguageSwitch
116
- availableLanguagesAndCountries={UILanguages}
117
- />
106
+ <UILanguageSwitch />
118
107
  </DropdownMenuSubContent>
119
108
  </DropdownMenuPortal>
120
109
  </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
+ }
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+
3
+ type Props = {
4
+ htmlContent: string,
5
+ contentLang: string
6
+ }
7
+
8
+ export const RenderArticle = ({ htmlContent, contentLang }: Props) => {
9
+ return (
10
+ <div
11
+ lang={contentLang}
12
+ className="pb-4"
13
+ dangerouslySetInnerHTML={{ __html: htmlContent }}
14
+ />
15
+ );
16
+ }
@@ -1,72 +1,37 @@
1
1
  import React, { FC } from "react";
2
- import { ConfigInterface, informationUnitsItems, Labels } from "@c-rex/interfaces";
2
+ import { ConfigInterface, DefaultPageInfo, informationUnitsResponseItem, } 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 { useLocale } from "next-intl";
7
- import { DEFAULT_UI_LANG, RESULT_TYPES } from "@c-rex/constants";
6
+ import { Pagination } from "./pagination";
8
7
 
9
8
  interface ResultListProps {
10
- items: informationUnitsItems[];
11
- configs: ConfigInterface
9
+ items: informationUnitsResponseItem[];
10
+ configs: ConfigInterface;
11
+ pagination: DefaultPageInfo;
12
12
  }
13
13
 
14
- export const ResultList: FC<ResultListProps> = ({ items, configs }) => {
15
- const locale = useLocale();
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
- <div>
18
+ <>
58
19
  {
59
20
  items.length == 0 ? (
60
21
  <Empty />
61
22
  ) : (
62
- <ViewComponent
63
- items={items}
64
- getTitle={getTitle}
65
- getType={getType}
66
- handleByType={handleByType}
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
- </div>
35
+ </>
71
36
  );
72
37
  };
@@ -1,32 +1,18 @@
1
1
  import React, { FC } from "react";
2
2
  import { BlogCard } from "../blog-card";
3
- import { informationUnitsItems, Labels } from "@c-rex/interfaces";
3
+ import { informationUnitsResponseItem } from "@c-rex/interfaces";
4
4
 
5
5
  interface BlogViewProps {
6
- items: informationUnitsItems[];
7
- getType: (labels: Labels[]) => { localeLabel: string, comparableLabel: string };
8
- getTitle: (labels: Labels[]) => string;
6
+ items: informationUnitsResponseItem[];
9
7
  }
10
8
 
11
- const BlogView: FC<BlogViewProps> = ({ items, getTitle, getType }) => {
12
-
9
+ const BlogView: FC<BlogViewProps> = ({ items }) => {
13
10
  return (
14
11
  <div className="grid gap-8 md:grid-cols-2 md:gap-x-6 md:gap-y-10 xl:grid-cols-3">
15
12
  {items.map((item, index) => {
13
+
16
14
  return (
17
- <BlogCard
18
- key={index}
19
- data={{
20
- title: getTitle(item.labels),
21
- blurDataURL: "/img/blog-post-1.webp",
22
- image: "/img/blog-post-1.webp",
23
- description: getType(item.class.labels).localeLabel,
24
- authors: "item.authors",
25
- _id: "item._id",
26
- date: "item.date",
27
- slug: `info/${item.shortId}`,
28
- }}
29
- />
15
+ <BlogCard key={index} item={item} />
30
16
  );
31
17
  })}
32
18
  </div>
@@ -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 } from "react";
1
+ import { FC } from "react";
2
2
  import {
3
3
  Table,
4
4
  TableBody,
@@ -6,19 +6,17 @@ import {
6
6
  TableHeader,
7
7
  TableRow,
8
8
  } from "@c-rex/ui/table";
9
- import { informationUnitsItems, Labels } from "@c-rex/interfaces";
9
+ import { informationUnitsResponseItem } 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: informationUnitsItems[];
14
- getType: (labels: Labels[]) => { localeLabel: string, comparableLabel: string };
15
- getTitle: (labels: Labels[]) => string;
16
- handleByType: any;
15
+ items: informationUnitsResponseItem[];
17
16
  }
18
17
 
19
- const TableView: FC<TableViewProps> = ({ items, getType, getTitle, handleByType }) => {
18
+ const TableView: FC<TableViewProps> = ({ items }) => {
20
19
  const t = useTranslations("results")
21
-
22
20
  return (
23
21
  <div className="rounded-md border">
24
22
  <Table>
@@ -27,25 +25,51 @@ const TableView: FC<TableViewProps> = ({ items, getType, getTitle, handleByType
27
25
  <TableCell>{t("title")}</TableCell>
28
26
  <TableCell>{t("type")}</TableCell>
29
27
  <TableCell>{t("language")}</TableCell>
28
+ <TableCell>{t("files")}</TableCell>
30
29
  </TableRow>
31
30
  </TableHeader>
32
31
  <TableBody>
33
- {items.map((item: informationUnitsItems, index: number) => {
34
- const type = getType(item.class.labels);
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
- }
32
+ {items.map((item, index) => {
33
+ const clazz = `h-12 c-rex_result_row c-rex_result_${item.type.toLowerCase()} ${item.disabled ? "c-rex_result_item_disabled" : ""}`
41
34
 
42
35
  return (
43
- <TableRow key={index}>
44
- <TableCell className="cursor-pointer" onClick={() => handleByType(type.comparableLabel.toUpperCase(), item.shortId)}>
45
- {getTitle(item.labels)}
36
+ <TableRow key={index} className={clazz}>
37
+ <TableCell className="h-12 c-rex_result_cell">
38
+ {item.disabled ? (item.title) : (<a href={item.link}>{item.title}</a>)}
39
+ </TableCell>
40
+ <TableCell>{item.type}</TableCell>
41
+ <TableCell>{item.language}</TableCell>
42
+ <TableCell>
43
+ {(item.disabled || (item.filesToDownload.length == 0 && item.filesToOpen.length == 0)) ? (
44
+ <button disabled className="mx-2">
45
+ <Ban />
46
+ </button>
47
+ ) : (
48
+ <>
49
+ {item.filesToDownload.length == 0 ? (
50
+ <button disabled className="mx-2">
51
+ <CloudOff />
52
+ </button>
53
+ ) : (
54
+ <DropdownMenu
55
+ items={item.filesToDownload}
56
+ icon={<CloudDownload />}
57
+ />
58
+ )}
59
+
60
+ {item.filesToOpen.length == 0 ? (
61
+ <button disabled className="mx-2">
62
+ <EyeOff />
63
+ </button>
64
+ ) : (
65
+ <DropdownMenu
66
+ items={item.filesToOpen}
67
+ icon={<Eye />}
68
+ />
69
+ )}
70
+ </>
71
+ )}
46
72
  </TableCell>
47
- <TableCell>{type.localeLabel}</TableCell>
48
- <TableCell>{lang.toUpperCase()}</TableCell>
49
73
  </TableRow>
50
74
  )
51
75
  })}
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, lang, ...props }: SidebarProps) {
29
30
  const t = useTranslations();
30
31
 
31
32
  const getFlagIcon = (countryCode: string): JSX.Element | null => {
@@ -39,7 +40,7 @@ export function AppSidebar({ data, availableVersions, loading, ...props }: Sideb
39
40
  const tableOfContentGroup = (): JSX.Element | null => {
40
41
  if (loading) {
41
42
  return (
42
- <>
43
+ <SidebarGroup className="pt-4">
43
44
  <Skeleton className="w-auto h-10 mb-2" />
44
45
  <Skeleton className="w-auto h-10 mb-2" />
45
46
  <Skeleton className="w-auto h-10 mb-2 ml-8" />
@@ -47,15 +48,20 @@ export function AppSidebar({ data, availableVersions, loading, ...props }: Sideb
47
48
  <Skeleton className="w-auto h-10 mb-2 ml-8" />
48
49
  <Skeleton className="w-auto h-10 mb-2 ml-8" />
49
50
  <Skeleton className="w-auto h-10 mb-2" />
50
- </>
51
+ </SidebarGroup>
51
52
  )
52
53
  }
53
54
 
54
55
  if (data.length == 0) return null;
55
56
 
57
+ const langProp = {} as any
58
+
59
+ if (lang) {
60
+ langProp.lang = lang
61
+ }
62
+
56
63
  return (
57
- <SidebarGroup className="my-4">
58
- <SidebarGroupLabel>{t("tableOfContent")}:</SidebarGroupLabel>
64
+ <SidebarGroup {...langProp}>
59
65
  <SidebarMenu>
60
66
  {data.map((item) => (
61
67
  <SidebarMenuItem key={item.id}>
@@ -93,10 +99,10 @@ export function AppSidebar({ data, availableVersions, loading, ...props }: Sideb
93
99
  if (availableVersions.length == 0) return null;
94
100
 
95
101
  return (
96
- <SidebarGroup>
102
+ <>
97
103
  <SidebarGroupLabel>{t("availableIn")}:</SidebarGroupLabel>
98
104
  <SidebarMenu>
99
- {availableVersions.map((item: any) => {
105
+ {availableVersions.map((item) => {
100
106
  return (
101
107
  <SidebarMenuItem key={item.shortId}>
102
108
  <SidebarMenuButton asChild isActive={item.active}>
@@ -108,18 +114,19 @@ export function AppSidebar({ data, availableVersions, loading, ...props }: Sideb
108
114
  )
109
115
  })}
110
116
  </SidebarMenu>
111
- </SidebarGroup>
117
+ </>
112
118
  )
113
119
 
114
120
  }
115
121
 
116
122
  return (
117
- <Sidebar collapsible="icon" {...props}>
123
+ <Sidebar className={cn(className, "pt-20")} {...props}>
118
124
  <SidebarContent>
119
125
  {tableOfContentGroup()}
120
- {availableVersionsGroup()}
121
126
  </SidebarContent>
122
- <SidebarRail />
127
+ <SidebarFooter>
128
+ {availableVersionsGroup()}
129
+ </SidebarFooter>
123
130
  </Sidebar>
124
131
  );
125
132
  }