@c-rex/components 0.1.12 → 0.1.14

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.12",
3
+ "version": "0.1.14",
4
4
  "files": [
5
5
  "src"
6
6
  ],
@@ -100,6 +100,14 @@
100
100
  "./loading": {
101
101
  "types": "./src/loading.tsx",
102
102
  "import": "./src/loading.tsx"
103
+ },
104
+ "./language-store": {
105
+ "types": "./src/stores/language-store.ts",
106
+ "import": "./src/stores/language-store.ts"
107
+ },
108
+ "./highlight-store": {
109
+ "types": "./src/stores/highlight-store.ts",
110
+ "import": "./src/stores/highlight-store.ts"
103
111
  }
104
112
  },
105
113
  "scripts": {
@@ -140,6 +148,7 @@
140
148
  "@c-rex/ui": "*",
141
149
  "@c-rex/utils": "*",
142
150
  "country-flag-icons": "^1.5.19",
151
+ "html-react-parser": "^5.2.6",
143
152
  "lucide-react": "^0.511.0",
144
153
  "next": "^14",
145
154
  "next-intl": "^4.1.0",
@@ -147,7 +156,8 @@
147
156
  "react": "^18.3.1",
148
157
  "react-dom": "^18.3.1",
149
158
  "react-icons": "^5.5.0",
150
- "tailwindcss-animate": "^1.0.7"
159
+ "tailwindcss-animate": "^1.0.7",
160
+ "zustand": "^5.0.8"
151
161
  },
152
162
  "eslintConfig": {
153
163
  "extends": [
@@ -1,10 +1,11 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
2
  import { Input } from "@c-rex/ui/input";
3
3
  import { call, generateQueryParams } from "@c-rex/utils";
4
- import { WILD_CARD_OPTIONS } from "@c-rex/constants";
5
4
  import { useTranslations } from "next-intl";
6
5
  import { useAppConfig } from "@c-rex/contexts/config-provider";
7
6
  import { QueryParams } from "@c-rex/types";
7
+ import { useSearchSettingsStore } from "./stores/search-settings-store";
8
+ import { useLanguageStore } from "./stores/language-store";
8
9
 
9
10
  type Props = {
10
11
  initialValue: string;
@@ -15,7 +16,9 @@ type Props = {
15
16
  export const AutoComplete = ({ initialValue, embedded, searchByPackage }: Props) => {
16
17
  const t = useTranslations();
17
18
  const containerRef = useRef<HTMLDivElement>(null);
18
- const { articleLang, contentLang, packageID } = useAppConfig()
19
+ const contentLang = useLanguageStore.getState().contentLang;
20
+
21
+ const { articleLang, packageID } = useAppConfig()
19
22
 
20
23
  const [open, setOpen] = useState(false);
21
24
  const [query, setQuery] = useState(initialValue);
@@ -29,11 +32,13 @@ export const AutoComplete = ({ initialValue, embedded, searchByPackage }: Props)
29
32
  const onSelect = (value: string) => {
30
33
  const params: QueryParams[] = [
31
34
  { key: "search", value: value },
32
- { key: "operator", value: "OR" },
33
35
  { key: "page", value: "1" },
34
- { key: "language", value: articleLang || contentLang as string },
35
- { key: "wildcard", value: WILD_CARD_OPTIONS.BOTH },
36
- { key: "like", value: "false" },
36
+ { key: "operator", value: useSearchSettingsStore.getState().operator },
37
+
38
+ { key: "language", value: useLanguageStore.getState().contentLang },
39
+
40
+ { key: "wildcard", value: useSearchSettingsStore.getState().wildcard },
41
+ { key: "like", value: useSearchSettingsStore.getState().like.toString() },
37
42
  ]
38
43
 
39
44
  if (searchByPackage && packageID !== null) {
@@ -45,7 +50,6 @@ export const AutoComplete = ({ initialValue, embedded, searchByPackage }: Props)
45
50
  window.location.href = `${window.location.origin}/?${aux}`
46
51
  };
47
52
 
48
-
49
53
  useEffect(() => {
50
54
  setQuery(initialValue);
51
55
  }, [initialValue]);
@@ -101,7 +105,7 @@ export const AutoComplete = ({ initialValue, embedded, searchByPackage }: Props)
101
105
  />
102
106
 
103
107
  {open && (
104
- <ul className="absolute z-10 w-full bg-white border border-gray-300 rounded-lg shadow-lg max-h-60 overflow-y-auto">
108
+ <ul className="suggestions-list absolute z-10 w-full bg-white border border-gray-300 rounded-lg shadow-lg max-h-60 overflow-y-auto">
105
109
  {loading ? (
106
110
  <li>
107
111
  <div className="flex items-center justify-center py-4">
@@ -5,6 +5,7 @@ import { useAppConfig } from "@c-rex/contexts/config-provider";
5
5
  import { AvailableVersionsInterface } from "@c-rex/interfaces";
6
6
  import { toast } from "sonner"
7
7
  import { useTranslations } from "next-intl"
8
+ import { useLanguageStore } from "./stores/language-store";
8
9
 
9
10
  interface Props {
10
11
  availableVersions: AvailableVersionsInterface[]
@@ -12,10 +13,11 @@ interface Props {
12
13
 
13
14
  export const CheckArticleLangToast: FC<Props> = ({ availableVersions }) => {
14
15
  const t = useTranslations();
15
- const { contentLang, setAvailableVersions } = useAppConfig()
16
+ const { setAvailableVersions } = useAppConfig()
16
17
 
17
18
  useEffect(() => {
18
19
  setAvailableVersions(availableVersions)
20
+ const contentLang = useLanguageStore.getState().contentLang;
19
21
 
20
22
  const activeArticle = availableVersions.filter((item) => item.active)[0]
21
23
  if (activeArticle == undefined || activeArticle.lang == contentLang) return
@@ -1,4 +1,4 @@
1
- import React, { FC, useEffect, useState } from "react"
1
+ import React, { FC, useState } from "react"
2
2
  import { Button } from "@c-rex/ui/button"
3
3
  import {
4
4
  Dialog,
@@ -14,11 +14,13 @@ import { Checkbox } from "@c-rex/ui/checkbox"
14
14
  import { Label } from "@c-rex/ui/label"
15
15
  import { parseAsBoolean, parseAsInteger, parseAsString, useQueryStates } from "nuqs"
16
16
  import { useTranslations } from "next-intl"
17
- import { useAppConfig } from "@c-rex/contexts/config-provider"
18
17
  import { LanguageAndCountries } from "@c-rex/interfaces"
19
18
  import { WILD_CARD_OPTIONS, OPERATOR_OPTIONS } from "@c-rex/constants"
20
19
  import { Switch } from "@c-rex/ui/switch"
21
20
  import { useSearchContext } from "@c-rex/contexts/search"
21
+ import { useSearchSettingsStore } from "./stores/search-settings-store"
22
+ import { OperatorType, WildCardType } from "@c-rex/types"
23
+ import { useLanguageStore } from "./stores/language-store"
22
24
 
23
25
  interface DialogFilterProps {
24
26
  trigger: React.ReactNode;
@@ -32,35 +34,40 @@ interface Languages {
32
34
  export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
33
35
  const t = useTranslations("filter");
34
36
  const { setLoading } = useSearchContext();
35
- const { availableLanguagesAndCountries } = useAppConfig()
37
+
38
+ const savedLike = useSearchSettingsStore.getState().like;
39
+ const savedOperator = useSearchSettingsStore.getState().operator;
40
+ const savedWildcard = useSearchSettingsStore.getState().wildcard;
41
+ const savedLanguages = useLanguageStore.getState().contentLang;
42
+ const availableLanguagesAndCountries = useLanguageStore.getState().availableLanguages;
43
+
44
+ const [like, setLike] = useState<boolean>(savedLike);
45
+ const [operator, setOperator] = useState<string>(savedOperator);
46
+ const [wildcard, setWildcard] = useState<string>(savedWildcard);
47
+ const [languages, setLanguages] = useState<Languages[]>(
48
+ availableLanguagesAndCountries?.map((item: LanguageAndCountries) => {
49
+ const checked = savedLanguages.includes(item.value)
50
+
51
+ return {
52
+ lang: item.lang,
53
+ value: item.value,
54
+ checked,
55
+ }
56
+ })
57
+ );
58
+
36
59
  const [params, setParams] = useQueryStates({
60
+ search: parseAsString,
37
61
  language: parseAsString,
38
62
  page: parseAsInteger,
39
63
  wildcard: parseAsString,
40
64
  operator: parseAsString,
41
- filter: parseAsString,
42
65
  like: parseAsBoolean,
43
66
  }, {
44
67
  history: 'push',
45
68
  shallow: false,
46
69
  });
47
70
 
48
- const startSelectedLanguages = params.language?.split(',') || []
49
-
50
- const [operator, setOperator] = useState<string>(params.operator || OPERATOR_OPTIONS.OR);
51
- const [like, setLike] = useState<boolean>(params.like || false);
52
- const [wildcard, setWildcard] = useState<string>(params.wildcard || "");
53
- const [disabled, setDisabled] = useState<boolean>(false)
54
- const [languages, setLanguages] = useState<Languages[]>(availableLanguagesAndCountries.map((item: LanguageAndCountries) => {
55
- const checked = startSelectedLanguages.includes(item.value)
56
-
57
- return {
58
- lang: item.lang,
59
- value: item.value,
60
- checked,
61
- }
62
- }));
63
-
64
71
  const onChangeCheckbox = (item: Languages) => {
65
72
  const newLangList = languages.filter((lang) => lang.lang !== item.lang)
66
73
 
@@ -70,42 +77,29 @@ export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
70
77
  }
71
78
 
72
79
  const apply = () => {
73
- setLoading(true);
80
+ const selectedLanguages = languages.filter((item) => item.checked).map((item) => item.value)
74
81
 
75
- const selectedLanguages = languages
76
- .filter((item: any) => item.checked)
77
- .map((item: any) => item.value)
78
- .join(',')
82
+ useSearchSettingsStore.getState().updatePreferences({
83
+ like,
84
+ operator: operator as OperatorType,
85
+ wildcard: wildcard as WildCardType,
86
+ language: selectedLanguages,
87
+ });
88
+
89
+ if (params.search == null) {
90
+ return
91
+ }
79
92
 
93
+ setLoading(true);
80
94
  setParams({
81
95
  page: 1,
82
- language: selectedLanguages,
96
+ language: selectedLanguages.join(','),
83
97
  operator: operator,
84
98
  wildcard: wildcard,
85
99
  like: like,
86
- filter: null,
87
100
  });
88
101
  }
89
102
 
90
- useEffect(() => {
91
- setOperator(params.operator || OPERATOR_OPTIONS.OR);
92
- setLike(params.like || false);
93
- setWildcard(params.wildcard || "");
94
- setLanguages(availableLanguagesAndCountries.map((item: LanguageAndCountries) => {
95
- const checked = startSelectedLanguages.includes(item.value)
96
-
97
- return {
98
- lang: item.lang,
99
- value: item.value,
100
- checked,
101
- }
102
- }));
103
- }, [params])
104
-
105
- useEffect(() => {
106
- setDisabled(languages.every((item) => !item.checked));
107
- }, [languages])
108
-
109
103
  return (
110
104
  <Dialog>
111
105
  <DialogTrigger asChild>
@@ -190,7 +184,9 @@ export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
190
184
 
191
185
  <DialogFooter className="pt-2">
192
186
  <DialogClose asChild>
193
- <Button onClick={apply} disabled={disabled}>{t("apply")}</Button>
187
+ <Button onClick={apply}>
188
+ {params.search && params.search.length > 0 ? t("applyAndSearch") : t("apply")}
189
+ </Button>
194
190
  </DialogClose>
195
191
  </DialogFooter>
196
192
  </DialogContent>
@@ -1,9 +1,8 @@
1
+ import React, { FC } from "react";
1
2
  import { Card, CardContent, CardHeader, CardTitle } from "@c-rex/ui/card";
2
- import { getFromCookieString } from "@c-rex/utils";
3
3
  import { useTranslations } from "next-intl";
4
- import React, { FC } from "react";
5
4
  import { filteredItems, renderValue } from "./shared";
6
- import { UI_LANG_KEY } from "@c-rex/constants";
5
+ import { useLanguageStore } from "../stores/language-store";
7
6
 
8
7
  type Props = {
9
8
  title: string;
@@ -15,7 +14,7 @@ type Props = {
15
14
  }
16
15
  export const InfoCard: FC<Props> = ({ title, items }) => {
17
16
  const t = useTranslations();
18
- const uiLang = getFromCookieString(document.cookie, UI_LANG_KEY)
17
+ const uiLang = useLanguageStore(state => state.uiLang);
19
18
  const newItems = filteredItems(items)
20
19
 
21
20
  return (
@@ -8,8 +8,6 @@ import {
8
8
  TableRow,
9
9
  } from "@c-rex/ui/table"
10
10
  import { useTranslations } from "next-intl";
11
- import { getFromCookieString } from "@c-rex/utils";
12
- import { UI_LANG_KEY } from "@c-rex/constants";
13
11
  import { filteredItems, renderValue } from "./shared";
14
12
  import {
15
13
  DropdownMenu,
@@ -22,6 +20,7 @@ import { CloudDownload, Eye } from "lucide-react";
22
20
  import { FaFilePdf } from "react-icons/fa6";
23
21
  import { AvailableVersionsInterface } from "@c-rex/interfaces";
24
22
  import { Flag } from "../flag";
23
+ import { useLanguageStore } from "../stores/language-store";
25
24
 
26
25
  type Props = {
27
26
  title: string;
@@ -36,7 +35,7 @@ const IconsToFileExtension: Record<string, React.ReactNode> = {
36
35
 
37
36
  export const InfoTable: FC<Props> = ({ title, items, files, availableVersions }) => {
38
37
  const t = useTranslations();
39
- const uiLang = getFromCookieString(document.cookie, UI_LANG_KEY)
38
+ const uiLang = useLanguageStore(state => state.uiLang);
40
39
  const newItems = filteredItems(items)
41
40
 
42
41
  return (
@@ -48,7 +47,7 @@ export const InfoTable: FC<Props> = ({ title, items, files, availableVersions })
48
47
  <Table>
49
48
  <TableBody>
50
49
  {newItems.map((item, index) => (
51
- <TableRow key={index} className="h-12">
50
+ <TableRow key={index} className="min-h-12">
52
51
  <TableCell className="font-medium w-28 pl-4">
53
52
  {item.link ? (
54
53
  <a href={item.link} className="underline">
@@ -58,18 +57,18 @@ export const InfoTable: FC<Props> = ({ title, items, files, availableVersions })
58
57
  <h4 className="text-sm font-medium">{t(item.label)}</h4>
59
58
  )}
60
59
  </TableCell>
61
- <TableCell className="text-xs text-muted-foreground flex items-center gap-2 h-12">
60
+ <TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
62
61
  {renderValue(item, uiLang)}
63
62
  </TableCell>
64
63
  </TableRow>
65
64
  ))}
66
65
 
67
66
  {files && (
68
- <TableRow className="h-12">
67
+ <TableRow className="min-h-12">
69
68
  <TableCell className="font-medium w-28 pl-4">
70
69
  <h4 className="text-sm font-medium">{t("files")}</h4>
71
70
  </TableCell>
72
- <TableCell className="text-xs text-muted-foreground flex items-center gap-2 h-12">
71
+ <TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
73
72
 
74
73
  {Object.keys(files).map((item, index) => {
75
74
  if (!files[item]) return null
@@ -101,11 +100,11 @@ export const InfoTable: FC<Props> = ({ title, items, files, availableVersions })
101
100
  )}
102
101
 
103
102
  {availableVersions && (
104
- <TableRow className="h-12">
103
+ <TableRow className="min-h-12">
105
104
  <TableCell className="font-medium w-28 pl-4">
106
105
  <h4 className="text-sm font-medium">{t("availableIn")}</h4>
107
106
  </TableCell>
108
- <TableCell className="text-xs text-muted-foreground flex items-center gap-2 h-12">
107
+ <TableCell className="text-xs text-muted-foreground flex items-center gap-2 min-h-12">
109
108
  {availableVersions.map((item) => {
110
109
  return (
111
110
  <span className="w-8 block border" key={item.shortId}>
@@ -2,19 +2,24 @@
2
2
 
3
3
  import { startTransition } from "react";
4
4
  import { SharedLanguageSwitch } from "./shared";
5
- import { getFromCookieString, setCookie } from "@c-rex/utils";
6
- import { BLOG_TYPE_AND_LINK, CONTENT_LANG_KEY, DOCUMENTS_TYPE_AND_LINK, TOPICS_TYPE_AND_LINK } from "@c-rex/constants";
5
+ import { BLOG_TYPE_AND_LINK, DOCUMENTS_TYPE_AND_LINK, TOPICS_TYPE_AND_LINK } from "@c-rex/constants";
7
6
  import { useQueryState } from "nuqs"
8
7
  import { useAppConfig } from "@c-rex/contexts/config-provider";
9
8
  import { useTranslations } from "next-intl";
10
9
  import { toast } from "sonner"
10
+ import { useLanguageStore } from "../../stores/language-store";
11
+ import { useSearchSettingsStore } from "../../stores/search-settings-store";
11
12
 
12
13
  export const ContentLanguageSwitch = () => {
13
14
  const t = useTranslations();
14
- const contentLang = getFromCookieString(document.cookie, CONTENT_LANG_KEY)
15
- const { availableLanguagesAndCountries: aux, setContentLang, availableVersions } = useAppConfig()
15
+ const contentLang = useLanguageStore.getState().contentLang;
16
+ const setContentLang = useLanguageStore.getState().setContentLang;
17
+ const updatePreferences = useSearchSettingsStore.getState().updatePreferences;
18
+
19
+ const { availableVersions } = useAppConfig()
16
20
 
17
21
  const availableLanguagesAndCountries = () => {
22
+ const aux = useLanguageStore.getState().availableLanguages;
18
23
  let result = aux.map(item => ({ ...item, link: "#" }))
19
24
  if (availableVersions == null) return result
20
25
 
@@ -35,18 +40,22 @@ export const ContentLanguageSwitch = () => {
35
40
 
36
41
  const changeContentLanguage = (locale: string) => {
37
42
  startTransition(() => {
38
- setCookie(CONTENT_LANG_KEY, locale)
39
43
  setContentLang(locale)
44
+ updatePreferences({ language: [locale] })
40
45
 
41
46
  if (queryLanguage != null) {
42
47
  setContentLanguage(locale)
43
48
  }
44
49
 
45
50
  const currentPath = window.location.pathname;
46
- const isTopicOrBlogOrDocument = currentPath.includes(TOPICS_TYPE_AND_LINK) || currentPath.includes(BLOG_TYPE_AND_LINK) || currentPath.includes(DOCUMENTS_TYPE_AND_LINK)
51
+ const isTopicOrBlogOrDocument = (
52
+ currentPath.includes(TOPICS_TYPE_AND_LINK) ||
53
+ currentPath.includes(BLOG_TYPE_AND_LINK) ||
54
+ currentPath.includes(DOCUMENTS_TYPE_AND_LINK)
55
+ )
47
56
 
48
57
  if (!isTopicOrBlogOrDocument) {
49
- window.location.reload();
58
+ setTimeout(() => window.location.reload(), 5);
50
59
  return;
51
60
  }
52
61
 
@@ -2,13 +2,13 @@
2
2
 
3
3
  import React, { FC, startTransition } from "react";
4
4
  import { SharedLanguageSwitch } from "./shared";
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";
5
+ import { UI_LANG_OPTIONS } from "@c-rex/constants";
6
+ import { getCountryCodeByLang } from "@c-rex/utils";
7
+ import { useLanguageStore } from "../../stores/language-store";
8
8
 
9
9
  export const UILanguageSwitch: FC = () => {
10
- const { setUiLang } = useAppConfig()
11
- const uiLang = getFromCookieString(document.cookie, UI_LANG_KEY).toLowerCase()
10
+ const uiLang = useLanguageStore.getState().uiLang.toLowerCase();
11
+ const setUiLang = useLanguageStore.getState().setUiLang;
12
12
  const UILanguages = UI_LANG_OPTIONS.map((lang) => {
13
13
  const langCode = lang.split("-")[0] as string;
14
14
  return {
@@ -20,7 +20,6 @@ export const UILanguageSwitch: FC = () => {
20
20
 
21
21
  const setUILanguage = (locale: string) => {
22
22
  startTransition(() => {
23
- setCookie(UI_LANG_KEY, locale)
24
23
  setUiLang(locale)
25
24
 
26
25
  window.location.reload()
@@ -21,7 +21,7 @@ export const SearchInput: FC<Props> = ({ showInput, showPkgFilter, placedOn = "N
21
21
  return (
22
22
  <div className={cn(
23
23
  placedOn === "NAVBAR" ? "hidden lg:flex" : "sm:flex-none sm:w-60 md:w-72 lg:w-full lg:hidden flex",
24
- "flex-1 items-center px-3 border rounded-full h-8"
24
+ "flex-1 items-center px-3 border rounded-full h-8 c-rex-search-bar"
25
25
  )}>
26
26
  <Search className="h-4 w-4 shrink-0 opacity-50" />
27
27
 
@@ -1,16 +1,68 @@
1
- import React from "react";
1
+ "use client";
2
+
3
+ import { useMemo, useRef, useEffect } from "react";
4
+ import parse from "html-react-parser";
5
+ import { useQueryState } from "nuqs";
6
+ import { useHighlight } from "@c-rex/contexts/highlight-provider";
7
+ import { useHighlightStore } from "./stores/highlight-store";
2
8
 
3
9
  type Props = {
4
- htmlContent: string,
5
- contentLang: string
6
- }
10
+ htmlContent: string;
11
+ contentLang?: string;
12
+ };
7
13
 
8
14
  export const RenderArticle = ({ htmlContent, contentLang }: Props) => {
15
+ const [query] = useQueryState("q");
16
+ const containerRef = useRef<HTMLElement | null>(null);
17
+ const enableHighlight = useHighlightStore((state) => state.enable);
18
+ const { registerContainer } = useHighlight();
19
+
20
+ const highlightedContent = useMemo(() => {
21
+ if (!enableHighlight) return parse(htmlContent);
22
+ if (!query) return parse(htmlContent);
23
+
24
+ const terms = query
25
+ .split(/[+ ]/)
26
+ .map((t) => t.trim())
27
+ .filter(Boolean);
28
+
29
+ if (terms.length === 0) return parse(htmlContent);
30
+
31
+ const escaped = terms.map((t) =>
32
+ t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
33
+ );
34
+ const regex = new RegExp(`(${escaped.join("|")})`, "gi");
35
+
36
+ return parse(htmlContent, {
37
+ replace: (domNode: any) => {
38
+ if (domNode.type === "text") {
39
+ const parts = domNode.data.split(regex);
40
+ if (parts.length === 1) return;
41
+ return (
42
+ <>
43
+ {parts.map((part: any, i: any) =>
44
+ regex.test(part) ? (
45
+ <mark key={i} className="bg-yellow-200">
46
+ {part}
47
+ </mark>
48
+ ) : (
49
+ part
50
+ )
51
+ )}
52
+ </>
53
+ );
54
+ }
55
+ },
56
+ });
57
+ }, [htmlContent, query, enableHighlight]);
58
+
59
+ useEffect(() => {
60
+ registerContainer(containerRef.current);
61
+ }, [highlightedContent, registerContainer]);
62
+
9
63
  return (
10
- <main
11
- lang={contentLang}
12
- className="pb-4"
13
- dangerouslySetInnerHTML={{ __html: htmlContent }}
14
- />
64
+ <main ref={containerRef} lang={contentLang} className="pb-4">
65
+ {highlightedContent}
66
+ </main>
15
67
  );
16
- }
68
+ };
@@ -8,6 +8,7 @@ import { Badge } from "@c-rex/ui/badge";
8
8
  import { useTranslations } from "next-intl";
9
9
  import { TopicsResponseItem } from "@c-rex/interfaces";
10
10
  import { Card } from "@c-rex/ui/card";
11
+ import { useQueryState } from "nuqs";
11
12
 
12
13
  interface BlogViewProps {
13
14
  items: TopicsResponseItem[];
@@ -16,18 +17,20 @@ interface BlogViewProps {
16
17
  const BlogView: FC<BlogViewProps> = ({ items }) => {
17
18
  const t = useTranslations("results")
18
19
  const [isLoading, setLoading] = useState(true);
20
+ const [query] = useQueryState("search");
21
+
19
22
  return (
20
- <div className="grid gap-6 grid-cols-2 ">
21
- {items.map((item, index) => (
23
+ <div className="grid gap-6 grid-cols-2">
22
24
 
25
+ {items.map((item, index) => (
23
26
  <Card
24
27
  key={item.shortId}
25
28
  className={cn(
26
- `blog-card relative p-0 c-rex_result_${item.type.toLowerCase()}`,
29
+ `c-rex-result-item blog-card relative p-0 c-rex-result-${item.type.toLowerCase()}`,
27
30
  index == 0
28
31
  ? "col-span-2 grid grid-cols-1 gap-3 md:grid-cols-2 md:gap-6"
29
32
  : "flex flex-col space-y-2",
30
- item.disabled ? "c-rex_result_item_disabled" : ""
33
+ item.disabled ? "c-rex-result-item-disabled" : ""
31
34
  )}
32
35
  >
33
36
 
@@ -82,7 +85,7 @@ const BlogView: FC<BlogViewProps> = ({ items }) => {
82
85
  </div>
83
86
 
84
87
  {!item.disabled && (
85
- <Link href={item.link} className="absolute inset-0">
88
+ <Link href={`${item.link}?q=${query}`} className="absolute inset-0">
86
89
  <span className="sr-only">{t("viewArticle")}</span>
87
90
  </Link>
88
91
  )}
@@ -1,12 +1,13 @@
1
1
  import { FC } from "react";
2
2
  import { informationUnitsResponseItem } from "@c-rex/interfaces";
3
3
  import { useTranslations } from "next-intl";
4
- import { Ban, CloudDownload, Eye } from "lucide-react";
4
+ import { CloudDownload, Eye } from "lucide-react";
5
5
  import { FaFilePdf } from "react-icons/fa6";
6
6
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@c-rex/ui/dropdown-menu";
7
7
  import { cn } from "@c-rex/utils";
8
8
  import { Flag } from "../flag";
9
9
  import { Badge } from "@c-rex/ui/badge";
10
+ import { useQueryState } from "nuqs";
10
11
 
11
12
  interface TableViewProps {
12
13
  items: informationUnitsResponseItem[];
@@ -19,6 +20,8 @@ const IconsToFileExtension: Record<string, React.ReactNode> = {
19
20
 
20
21
  const TableView: FC<TableViewProps> = ({ items }) => {
21
22
  const t = useTranslations("results")
23
+ const [query] = useQueryState("search");
24
+
22
25
 
23
26
  return (
24
27
  <div className="rounded-md border mb-6 last:border-b-0">
@@ -32,15 +35,15 @@ const TableView: FC<TableViewProps> = ({ items }) => {
32
35
  {items.map((item, index) => (
33
36
  <div
34
37
  className={cn(
35
- "min-h-12 c-rex_result_row flex flex-wrap items-center",
36
- `c-rex_result_${item.type.toLowerCase()}`,
37
- item.disabled && "c-rex_result_item_disabled",
38
+ "min-h-12 c-rex-result-item flex flex-wrap items-center",
39
+ `c-rex-result-${item.type.toLowerCase()}`,
40
+ item.disabled && "c-rex-result-item-disabled",
38
41
  "border-b"
39
42
  )}
40
43
  key={index}
41
44
  >
42
45
  <div className="w-4/5 md:w-2/5 p-2">
43
- {item.disabled ? (item.title) : (<a href={item.link}>{item.title}</a>)}
46
+ {item.disabled ? (item.title) : (<a href={`${item.link}?q=${query}`}>{item.title}</a>)}
44
47
  </div>
45
48
 
46
49
  <div className="w-1/5 md:w-1/5 flex justify-center p-2">
@@ -59,7 +62,7 @@ const TableView: FC<TableViewProps> = ({ items }) => {
59
62
 
60
63
  <div className="w-1/5 flex justify-center p-2">
61
64
  {(item.disabled || (Object.keys(item.files).length === 0)) ? (
62
- <Ban />
65
+ null // remove null rendering
63
66
  ) : (
64
67
  <>
65
68
  {Object.keys(item.files).map((fileKey, index) => {
@@ -6,7 +6,6 @@ import {
6
6
  SidebarContent,
7
7
  } from "@c-rex/ui/sidebar";
8
8
  import { useTranslations } from "next-intl";
9
- import { cn } from "@c-rex/utils";
10
9
  import { articleInfoItemType, DocumentsType } from "@c-rex/types";
11
10
  import { InfoTable } from "./info/info-table";
12
11
  import { AvailableVersionsInterface } from "@c-rex/interfaces";
@@ -15,24 +14,26 @@ interface SidebarProps extends ComponentProps<typeof Sidebar> {
15
14
  articleInfo: articleInfoItemType[],
16
15
  documentInfo: articleInfoItemType[],
17
16
  files: DocumentsType,
18
- availableVersions?: AvailableVersionsInterface[]
17
+ articleAvailableVersions: AvailableVersionsInterface[],
18
+ documentAvailableVersions: AvailableVersionsInterface[]
19
19
 
20
20
  }
21
21
 
22
- export function RightSidebar({ articleInfo, documentInfo, className, files, availableVersions, ...props }: SidebarProps) {
22
+ export function RightSidebar({ articleInfo, documentInfo, files, articleAvailableVersions, documentAvailableVersions, ...props }: SidebarProps) {
23
23
  const t = useTranslations();
24
24
 
25
25
  return (
26
- <Sidebar className={cn(className)} {...props} side="right">
27
- <SidebarContent className="p-2">
26
+ <Sidebar {...props}>
27
+ <SidebarContent className="pt-4 pr-4 pb-10">
28
28
  {(articleInfo.length > 0 || documentInfo.length > 0) && (
29
- <div className=" gap-4 flex flex-col">
29
+ <>
30
30
 
31
31
  {documentInfo.length > 0 && (
32
32
  <InfoTable
33
33
  title={t("aboutDocument")}
34
34
  items={documentInfo}
35
35
  files={files}
36
+ availableVersions={documentAvailableVersions}
36
37
  />
37
38
  )}
38
39
 
@@ -40,11 +41,11 @@ export function RightSidebar({ articleInfo, documentInfo, className, files, avai
40
41
  <InfoTable
41
42
  title={t("aboutArticle")}
42
43
  items={articleInfo}
43
- availableVersions={availableVersions}
44
+ availableVersions={articleAvailableVersions}
44
45
  />
45
46
  )}
46
47
 
47
- </div>
48
+ </>
48
49
  )}
49
50
  </SidebarContent>
50
51
  </Sidebar>
@@ -13,13 +13,13 @@ import {
13
13
  } from "@c-rex/ui/dialog"
14
14
  import { parseAsBoolean, parseAsInteger, parseAsString, useQueryStates } from "nuqs"
15
15
  import { useTranslations } from "next-intl"
16
- import { CONTENT_LANG_KEY, WILD_CARD_OPTIONS } from "@c-rex/constants"
17
- import { call, getFromCookieString } from "@c-rex/utils"
16
+ import { WILD_CARD_OPTIONS } from "@c-rex/constants"
17
+ import { call } from "@c-rex/utils"
18
18
  import { Input } from "@c-rex/ui/input"
19
- import { useAppConfig } from "@c-rex/contexts/config-provider"
20
19
  import { Label } from "@c-rex/ui/label"
21
20
  import { Checkbox } from "@c-rex/ui/checkbox"
22
21
  import { Toggle } from "@c-rex/ui/toggle"
22
+ import { useLanguageStore } from "./stores/language-store"
23
23
 
24
24
  interface SearchModalProps {
25
25
  trigger: React.ReactNode;
@@ -27,11 +27,11 @@ interface SearchModalProps {
27
27
 
28
28
  export const SearchModal: FC<SearchModalProps> = ({ trigger }) => {
29
29
  const t = useTranslations("filter");
30
- const { contentLang } = useAppConfig()
31
-
32
- const [suggestions, setSuggestions] = useState<string[]>([]);
30
+ const contentLang = useLanguageStore(state => state.contentLang);
33
31
  const [value, setValue] = useState<string>("");
34
32
  const [loading, setLoading] = useState<boolean>(false);
33
+ const [suggestions, setSuggestions] = useState<string[]>([]);
34
+
35
35
  const [params, setParams] = useQueryStates({
36
36
  language: parseAsString,
37
37
  page: parseAsInteger,
@@ -70,13 +70,11 @@ export const SearchModal: FC<SearchModalProps> = ({ trigger }) => {
70
70
 
71
71
 
72
72
  const apply = () => {
73
- const cookie = getFromCookieString(document.cookie, CONTENT_LANG_KEY)
74
-
75
73
  setParams({
76
74
  search: value,
77
75
  operator: "OR",
78
76
  page: 1,
79
- language: cookie,
77
+ language: contentLang,
80
78
  wildcard: WILD_CARD_OPTIONS.BOTH,
81
79
  tags: false,
82
80
  like: false,
@@ -94,8 +92,6 @@ export const SearchModal: FC<SearchModalProps> = ({ trigger }) => {
94
92
  </DialogHeader>
95
93
  <Input value={value} autoFocus onChange={(e) => setValue(e.target.value)} />
96
94
 
97
-
98
-
99
95
  {loading ? (
100
96
  <div className="flex items-center justify-center py-4">
101
97
  <div className="animate-spin rounded-full h-6 w-6 border-2 border-gray-300 border-t-gray-950" />
@@ -0,0 +1,16 @@
1
+ import { create } from "zustand";
2
+ import { persist } from "zustand/middleware";
3
+
4
+ type HighlightStoreType = {
5
+ enable: boolean;
6
+ toggleHighlight: (v: boolean) => void;
7
+ };
8
+
9
+ export const useHighlightStore = create<HighlightStoreType>()(
10
+ persist((set) => ({
11
+ enable: false,
12
+ toggleHighlight: (v) => set({ enable: v }),
13
+ }), {
14
+ name: "c-rex-highlight-settings",
15
+ })
16
+ );
@@ -0,0 +1,59 @@
1
+ import { UI_LANG_KEY } from "@c-rex/constants";
2
+ import { AVAILABLE_CONTENT_LANG_KEY } from "@c-rex/constants";
3
+ import { CONTENT_LANG_KEY } from "@c-rex/constants";
4
+ import { LanguageAndCountries } from "@c-rex/interfaces";
5
+ import { create } from "zustand";
6
+
7
+ type LanguageStoreType = {
8
+ contentLang: string;
9
+ uiLang: string;
10
+ availableLanguages: LanguageAndCountries[];
11
+ setContentLang: (v: string) => void;
12
+ setUiLang: (v: string) => void;
13
+ setAvailableLanguages: (list: LanguageAndCountries[]) => void;
14
+ hydrate: () => void;
15
+ };
16
+
17
+ function setCookie(name: string, value: string, days = 365) {
18
+ const expires = new Date(Date.now() + days * 864e5).toUTCString();
19
+ document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/`;
20
+ }
21
+
22
+ function getCookie(name: string): string {
23
+ return document.cookie
24
+ .split('; ')
25
+ .find(row => row.startsWith(name + '='))
26
+ ?.split('=')[1] || '';
27
+ }
28
+
29
+ export const useLanguageStore = create<LanguageStoreType>((set) => ({
30
+ contentLang: "",
31
+ uiLang: "",
32
+ availableLanguages: [],
33
+ setContentLang: (v) => {
34
+ set({ contentLang: v });
35
+ setCookie(CONTENT_LANG_KEY, v);
36
+ },
37
+ setUiLang: (v) => {
38
+ set({ uiLang: v });
39
+ setCookie(UI_LANG_KEY, v);
40
+ },
41
+ setAvailableLanguages: (list) => {
42
+ set({ availableLanguages: list });
43
+ setCookie(AVAILABLE_CONTENT_LANG_KEY, JSON.stringify(list));
44
+ },
45
+ hydrate: () => {
46
+ set({
47
+ uiLang: getCookie(UI_LANG_KEY),
48
+ contentLang: getCookie(CONTENT_LANG_KEY),
49
+ availableLanguages: (() => {
50
+ const raw = getCookie(AVAILABLE_CONTENT_LANG_KEY);
51
+ try {
52
+ return raw ? JSON.parse(raw) : [];
53
+ } catch {
54
+ return [];
55
+ }
56
+ })(),
57
+ });
58
+ },
59
+ }));
@@ -0,0 +1,23 @@
1
+ import { OperatorType, WildCardType } from "@c-rex/types";
2
+ import { create } from "zustand";
3
+ import { persist } from "zustand/middleware";
4
+
5
+ type SearchSettingsStore = {
6
+ language: string[],
7
+ wildcard: WildCardType,
8
+ operator: OperatorType,
9
+ like: boolean,
10
+ updatePreferences: (settings: Partial<Omit<SearchSettingsStore, "updatePreferences">>) => void,
11
+ }
12
+
13
+ export const useSearchSettingsStore = create<SearchSettingsStore>()(
14
+ persist((set) => ({
15
+ language: [],
16
+ wildcard: "BOTH",
17
+ operator: "OR",
18
+ like: false,
19
+ updatePreferences: (settings) => set((state) => ({ ...state, ...settings })),
20
+ }), {
21
+ name: "c-rex-search-settings",
22
+ })
23
+ );