@c-rex/components 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -1
- package/src/autocomplete.tsx +11 -33
- package/src/dialog-filter.tsx +34 -7
- package/src/info-card.tsx +38 -0
- package/src/loading.tsx +12 -0
- package/src/navbar/language-switcher/content-language-switch.tsx +26 -3
- package/src/navbar/language-switcher/shared.tsx +11 -3
- package/src/navbar/navbar.tsx +3 -2
- package/src/navbar/search-input.tsx +16 -13
- package/src/page-wrapper.tsx +10 -3
- package/src/pagination.tsx +10 -3
- package/src/result-list.tsx +5 -3
- package/src/result-view/blog.tsx +83 -10
- package/src/search-modal.tsx +1 -2
- package/src/blog-card.tsx +0 -88
- package/src/stories/blog-card.stories.tsx +0 -46
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c-rex/components",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"files": [
|
|
5
5
|
"src"
|
|
6
6
|
],
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
"types": "./src/breadcrumb.tsx",
|
|
26
26
|
"import": "./src/breadcrumb.tsx"
|
|
27
27
|
},
|
|
28
|
+
"./pagination": {
|
|
29
|
+
"types": "./src/pagination.tsx",
|
|
30
|
+
"import": "./src/pagination.tsx"
|
|
31
|
+
},
|
|
32
|
+
"./info-card": {
|
|
33
|
+
"types": "./src/info-card.tsx",
|
|
34
|
+
"import": "./src/info-card.tsx"
|
|
35
|
+
},
|
|
28
36
|
"./empty": {
|
|
29
37
|
"types": "./src/empty.tsx",
|
|
30
38
|
"import": "./src/empty.tsx"
|
|
@@ -76,6 +84,10 @@
|
|
|
76
84
|
"./search-modal": {
|
|
77
85
|
"types": "./src/search-modal.tsx",
|
|
78
86
|
"import": "./src/search-modal.tsx"
|
|
87
|
+
},
|
|
88
|
+
"./loading": {
|
|
89
|
+
"types": "./src/loading.tsx",
|
|
90
|
+
"import": "./src/loading.tsx"
|
|
79
91
|
}
|
|
80
92
|
},
|
|
81
93
|
"scripts": {
|
package/src/autocomplete.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
import { Input } from "@c-rex/ui/input";
|
|
3
|
-
import { call, generateQueryParams
|
|
4
|
-
import {
|
|
3
|
+
import { call, generateQueryParams } from "@c-rex/utils";
|
|
4
|
+
import { WILD_CARD_OPTIONS } from "@c-rex/constants";
|
|
5
5
|
import { useTranslations } from "next-intl";
|
|
6
6
|
import { useAppConfig } from "@c-rex/contexts/config-provider";
|
|
7
7
|
import { QueryParams } from "@c-rex/types";
|
|
@@ -15,7 +15,7 @@ type Props = {
|
|
|
15
15
|
export const AutoComplete = ({ initialValue, embedded, searchByPackage }: Props) => {
|
|
16
16
|
const t = useTranslations();
|
|
17
17
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
18
|
-
const { contentLang, packageID } = useAppConfig()
|
|
18
|
+
const { articleLang, contentLang, packageID } = useAppConfig()
|
|
19
19
|
|
|
20
20
|
const [open, setOpen] = useState(false);
|
|
21
21
|
const [query, setQuery] = useState(initialValue);
|
|
@@ -23,43 +23,21 @@ export const AutoComplete = ({ initialValue, embedded, searchByPackage }: Props)
|
|
|
23
23
|
const [suggestions, setSuggestions] = useState<string[]>([]);
|
|
24
24
|
|
|
25
25
|
const onSearch = (value: string): Promise<string[]> => {
|
|
26
|
-
return call<string[]>("InformationUnitsService.getSuggestions", { query: value, language: contentLang });
|
|
26
|
+
return call<string[]>("InformationUnitsService.getSuggestions", { query: value, language: articleLang || contentLang });
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const onSelect = (value: string) => {
|
|
30
|
-
const cookie = getFromCookieString(document.cookie, CONTENT_LANG_KEY)
|
|
31
30
|
const params: QueryParams[] = [
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
value: "OR"
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
key: "page",
|
|
42
|
-
value: "1"
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
key: "language",
|
|
46
|
-
value: cookie
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
key: "wildcard",
|
|
50
|
-
value: WILD_CARD_OPTIONS.BOTH
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
key: "like",
|
|
54
|
-
value: "false"
|
|
55
|
-
},
|
|
31
|
+
{ key: "search", value: value },
|
|
32
|
+
{ key: "operator", value: "OR" },
|
|
33
|
+
{ 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" },
|
|
56
37
|
]
|
|
57
38
|
|
|
58
39
|
if (searchByPackage && packageID !== null) {
|
|
59
|
-
params.push({
|
|
60
|
-
key: "package",
|
|
61
|
-
value: packageID
|
|
62
|
-
})
|
|
40
|
+
params.push({ key: "packages", value: packageID })
|
|
63
41
|
}
|
|
64
42
|
|
|
65
43
|
const aux = generateQueryParams(params)
|
package/src/dialog-filter.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { FC, useState } from "react"
|
|
1
|
+
import React, { FC, useEffect, useState } from "react"
|
|
2
2
|
import { Button } from "@c-rex/ui/button"
|
|
3
3
|
import {
|
|
4
4
|
Dialog,
|
|
@@ -10,8 +10,6 @@ import {
|
|
|
10
10
|
DialogTrigger,
|
|
11
11
|
} from "@c-rex/ui/dialog"
|
|
12
12
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@c-rex/ui/select"
|
|
13
|
-
import { ToggleGroup, ToggleGroupItem } from "@c-rex/ui/toggle-group"
|
|
14
|
-
import { RadioGroup, RadioGroupItem } from "@c-rex/ui/radio-group"
|
|
15
13
|
import { Checkbox } from "@c-rex/ui/checkbox"
|
|
16
14
|
import { Label } from "@c-rex/ui/label"
|
|
17
15
|
import { parseAsBoolean, parseAsInteger, parseAsString, useQueryStates } from "nuqs"
|
|
@@ -20,6 +18,7 @@ import { useAppConfig } from "@c-rex/contexts/config-provider"
|
|
|
20
18
|
import { LanguageAndCountries } from "@c-rex/interfaces"
|
|
21
19
|
import { WILD_CARD_OPTIONS, OPERATOR_OPTIONS } from "@c-rex/constants"
|
|
22
20
|
import { Switch } from "@c-rex/ui/switch"
|
|
21
|
+
import { useSearchContext } from "@c-rex/contexts/search"
|
|
23
22
|
|
|
24
23
|
interface DialogFilterProps {
|
|
25
24
|
trigger: React.ReactNode;
|
|
@@ -32,12 +31,14 @@ interface Languages {
|
|
|
32
31
|
|
|
33
32
|
export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
|
|
34
33
|
const t = useTranslations("filter");
|
|
34
|
+
const { setLoading } = useSearchContext();
|
|
35
35
|
const { availableLanguagesAndCountries } = useAppConfig()
|
|
36
36
|
const [params, setParams] = useQueryStates({
|
|
37
37
|
language: parseAsString,
|
|
38
38
|
page: parseAsInteger,
|
|
39
39
|
wildcard: parseAsString,
|
|
40
40
|
operator: parseAsString,
|
|
41
|
+
filter: parseAsString,
|
|
41
42
|
like: parseAsBoolean,
|
|
42
43
|
}, {
|
|
43
44
|
history: 'push',
|
|
@@ -49,6 +50,7 @@ export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
|
|
|
49
50
|
const [operator, setOperator] = useState<string>(params.operator || OPERATOR_OPTIONS.OR);
|
|
50
51
|
const [like, setLike] = useState<boolean>(params.like || false);
|
|
51
52
|
const [wildcard, setWildcard] = useState<string>(params.wildcard || "");
|
|
53
|
+
const [disabled, setDisabled] = useState<boolean>(false)
|
|
52
54
|
const [languages, setLanguages] = useState<Languages[]>(availableLanguagesAndCountries.map((item: LanguageAndCountries) => {
|
|
53
55
|
const checked = startSelectedLanguages.includes(item.value)
|
|
54
56
|
|
|
@@ -68,7 +70,12 @@ export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
|
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
const apply = () => {
|
|
71
|
-
|
|
73
|
+
setLoading(true);
|
|
74
|
+
|
|
75
|
+
const selectedLanguages = languages
|
|
76
|
+
.filter((item: any) => item.checked)
|
|
77
|
+
.map((item: any) => item.value)
|
|
78
|
+
.join(',')
|
|
72
79
|
|
|
73
80
|
setParams({
|
|
74
81
|
page: 1,
|
|
@@ -76,9 +83,29 @@ export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
|
|
|
76
83
|
operator: operator,
|
|
77
84
|
wildcard: wildcard,
|
|
78
85
|
like: like,
|
|
86
|
+
filter: null,
|
|
79
87
|
});
|
|
80
88
|
}
|
|
81
89
|
|
|
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
|
+
|
|
82
109
|
return (
|
|
83
110
|
<Dialog>
|
|
84
111
|
<DialogTrigger asChild>
|
|
@@ -143,12 +170,12 @@ export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
|
|
|
143
170
|
</div>
|
|
144
171
|
|
|
145
172
|
<div className="grid grid-cols-2 items-center pt-2">
|
|
146
|
-
<Label className="text-right">
|
|
173
|
+
<Label htmlFor="operator" className="text-right">
|
|
147
174
|
{t("operator")}:
|
|
148
175
|
</Label>
|
|
149
176
|
|
|
150
177
|
<Switch
|
|
151
|
-
id="
|
|
178
|
+
id="operator"
|
|
152
179
|
onCheckedChange={(value) => setOperator(value ? OPERATOR_OPTIONS.AND : OPERATOR_OPTIONS.OR)}
|
|
153
180
|
checked={operator == OPERATOR_OPTIONS.AND}
|
|
154
181
|
/>
|
|
@@ -163,7 +190,7 @@ export const DialogFilter: FC<DialogFilterProps> = ({ trigger }) => {
|
|
|
163
190
|
|
|
164
191
|
<DialogFooter className="pt-2">
|
|
165
192
|
<DialogClose asChild>
|
|
166
|
-
<Button onClick={apply}>{t("apply")}</Button>
|
|
193
|
+
<Button onClick={apply} disabled={disabled}>{t("apply")}</Button>
|
|
167
194
|
</DialogClose>
|
|
168
195
|
</DialogFooter>
|
|
169
196
|
</DialogContent>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@c-rex/ui/card";
|
|
2
|
+
import { useTranslations } from "next-intl";
|
|
3
|
+
import React, { FC } from "react";
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
title: string;
|
|
7
|
+
items: {
|
|
8
|
+
label: string;
|
|
9
|
+
value: string;
|
|
10
|
+
link?: string;
|
|
11
|
+
}[]
|
|
12
|
+
}
|
|
13
|
+
export const InfoCard: FC<Props> = ({ title, items }) => {
|
|
14
|
+
const t = useTranslations();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Card>
|
|
18
|
+
<CardHeader>
|
|
19
|
+
<CardTitle className="text-lg">{title}</CardTitle>
|
|
20
|
+
</CardHeader>
|
|
21
|
+
<CardContent className="space-y-3">
|
|
22
|
+
{items.map((item, index) => (
|
|
23
|
+
<div key={index} className="border-b border-blog-divider last:border-0 pb-3 last:pb-0">
|
|
24
|
+
{item.link ? (
|
|
25
|
+
<a href={item.link}>
|
|
26
|
+
<h4 className="text-sm font-medium mb-1">{t(item.label)}</h4>
|
|
27
|
+
</a>
|
|
28
|
+
) : (
|
|
29
|
+
<h4 className="text-sm font-medium mb-1">{t(item.label)}</h4>
|
|
30
|
+
)}
|
|
31
|
+
|
|
32
|
+
<span className="text-xs text-muted-foreground">{item.value}</span>
|
|
33
|
+
</div>
|
|
34
|
+
))}
|
|
35
|
+
</CardContent>
|
|
36
|
+
</Card>
|
|
37
|
+
);
|
|
38
|
+
}
|
package/src/loading.tsx
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React, { FC } from "react";
|
|
2
|
+
|
|
3
|
+
type LoadingProps = {
|
|
4
|
+
opacity: boolean;
|
|
5
|
+
}
|
|
6
|
+
export const Loading: FC<LoadingProps> = ({ opacity }) => {
|
|
7
|
+
return (
|
|
8
|
+
<div className={`fixed inset-0 z-[100000] w-full h-full flex justify-center items-center bg-white ${opacity ? "opacity-50" : ""}`}>
|
|
9
|
+
<div className="animate-spin rounded-full h-12 w-12 border-2 border-gray-300 border-t-gray-950"></div>
|
|
10
|
+
</div>
|
|
11
|
+
);
|
|
12
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { startTransition } from "react";
|
|
4
4
|
import { SharedLanguageSwitch } from "./shared";
|
|
5
5
|
import { getFromCookieString, setCookie } from "@c-rex/utils";
|
|
6
|
-
import { CONTENT_LANG_KEY } from "@c-rex/constants";
|
|
6
|
+
import { BLOG_TYPE_AND_LINK, CONTENT_LANG_KEY, DOCUMENTS_TYPE_AND_LINK, TOPICS_TYPE_AND_LINK } from "@c-rex/constants";
|
|
7
7
|
import { useQueryState } from "nuqs"
|
|
8
8
|
import { useAppConfig } from "@c-rex/contexts/config-provider";
|
|
9
9
|
import { useTranslations } from "next-intl";
|
|
@@ -12,8 +12,22 @@ import { toast } from "sonner"
|
|
|
12
12
|
export const ContentLanguageSwitch = () => {
|
|
13
13
|
const t = useTranslations();
|
|
14
14
|
const contentLang = getFromCookieString(document.cookie, CONTENT_LANG_KEY)
|
|
15
|
-
const { availableLanguagesAndCountries, setContentLang, availableVersions } = useAppConfig()
|
|
15
|
+
const { availableLanguagesAndCountries: aux, setContentLang, availableVersions } = useAppConfig()
|
|
16
16
|
|
|
17
|
+
const availableLanguagesAndCountries = () => {
|
|
18
|
+
let result = aux.map(item => ({ ...item, link: "#" }))
|
|
19
|
+
if (availableVersions == null) return result
|
|
20
|
+
|
|
21
|
+
result = aux.map(item => {
|
|
22
|
+
const availableVersion = availableVersions.find(version => version.lang === item.value)
|
|
23
|
+
return {
|
|
24
|
+
...item,
|
|
25
|
+
link: availableVersion?.link ?? "#"
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
17
31
|
const [queryLanguage, setContentLanguage] = useQueryState('language', {
|
|
18
32
|
history: 'push',
|
|
19
33
|
shallow: false,
|
|
@@ -23,10 +37,19 @@ export const ContentLanguageSwitch = () => {
|
|
|
23
37
|
startTransition(() => {
|
|
24
38
|
setCookie(CONTENT_LANG_KEY, locale)
|
|
25
39
|
setContentLang(locale)
|
|
40
|
+
|
|
26
41
|
if (queryLanguage != null) {
|
|
27
42
|
setContentLanguage(locale)
|
|
28
43
|
}
|
|
29
44
|
|
|
45
|
+
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)
|
|
47
|
+
|
|
48
|
+
if (!isTopicOrBlogOrDocument) {
|
|
49
|
+
window.location.reload();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
30
53
|
if (availableVersions !== null) {
|
|
31
54
|
const filteredList = availableVersions.filter((item) => item.lang === locale)
|
|
32
55
|
|
|
@@ -48,7 +71,7 @@ export const ContentLanguageSwitch = () => {
|
|
|
48
71
|
|
|
49
72
|
return (
|
|
50
73
|
<SharedLanguageSwitch
|
|
51
|
-
availableLanguagesAndCountries={availableLanguagesAndCountries}
|
|
74
|
+
availableLanguagesAndCountries={availableLanguagesAndCountries()}
|
|
52
75
|
changeLanguage={changeContentLanguage}
|
|
53
76
|
selected={contentLang}
|
|
54
77
|
/>
|
|
@@ -4,7 +4,7 @@ import { LanguageAndCountries } from "@c-rex/interfaces";
|
|
|
4
4
|
import { DropdownMenuRadioGroup, DropdownMenuRadioItem } from "@c-rex/ui/dropdown-menu";
|
|
5
5
|
|
|
6
6
|
interface SharedLanguageSwitchProps {
|
|
7
|
-
availableLanguagesAndCountries: LanguageAndCountries[];
|
|
7
|
+
availableLanguagesAndCountries: (LanguageAndCountries & { link?: string })[];
|
|
8
8
|
selected: string;
|
|
9
9
|
changeLanguage: (locale: string) => void;
|
|
10
10
|
}
|
|
@@ -19,10 +19,18 @@ export const SharedLanguageSwitch: FC<SharedLanguageSwitchProps> = ({ availableL
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
return (
|
|
22
|
-
<DropdownMenuRadioGroup value={selected}
|
|
22
|
+
<DropdownMenuRadioGroup value={selected}>
|
|
23
23
|
{availableLanguagesAndCountries.map(item => {
|
|
24
24
|
return (
|
|
25
|
-
<DropdownMenuRadioItem
|
|
25
|
+
<DropdownMenuRadioItem
|
|
26
|
+
key={item.value}
|
|
27
|
+
value={item.value}
|
|
28
|
+
link={item?.link}
|
|
29
|
+
onClick={(e) => {
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
changeLanguage(item.value)
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
26
34
|
{getFlagIcon(item.country)}
|
|
27
35
|
<span>{item.lang.toUpperCase()}</span>
|
|
28
36
|
</DropdownMenuRadioItem>
|
package/src/navbar/navbar.tsx
CHANGED
|
@@ -11,9 +11,10 @@ import { SearchInput } from "./search-input";
|
|
|
11
11
|
interface NavBarProps {
|
|
12
12
|
title: string;
|
|
13
13
|
showInput: boolean;
|
|
14
|
+
showPkgFilter: boolean;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export const NavBar: FC<NavBarProps> = async ({ title, showInput }) => {
|
|
17
|
+
export const NavBar: FC<NavBarProps> = async ({ title, showInput, showPkgFilter }) => {
|
|
17
18
|
const configs = await getConfigs();
|
|
18
19
|
|
|
19
20
|
let session: any;
|
|
@@ -42,7 +43,7 @@ export const NavBar: FC<NavBarProps> = async ({ title, showInput }) => {
|
|
|
42
43
|
</div>
|
|
43
44
|
|
|
44
45
|
<div className="flex items-center space-x-3">
|
|
45
|
-
<SearchInput showInput={showInput} />
|
|
46
|
+
<SearchInput showInput={showInput} showPkgFilter={showPkgFilter} />
|
|
46
47
|
|
|
47
48
|
{configs.OIDC.user.enabled && (
|
|
48
49
|
<>
|
|
@@ -7,9 +7,10 @@ import { AutoComplete } from "../autocomplete";
|
|
|
7
7
|
|
|
8
8
|
type Props = {
|
|
9
9
|
showInput: boolean
|
|
10
|
+
showPkgFilter: boolean
|
|
10
11
|
}
|
|
11
|
-
export const SearchInput: FC<Props> = ({ showInput }) => {
|
|
12
|
-
const [checked, setChecked] = useState<boolean>(
|
|
12
|
+
export const SearchInput: FC<Props> = ({ showInput, showPkgFilter }) => {
|
|
13
|
+
const [checked, setChecked] = useState<boolean>(true);
|
|
13
14
|
|
|
14
15
|
if (!showInput) {
|
|
15
16
|
return null;
|
|
@@ -25,17 +26,19 @@ export const SearchInput: FC<Props> = ({ showInput }) => {
|
|
|
25
26
|
searchByPackage={checked}
|
|
26
27
|
/>
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
{showPkgFilter && <>
|
|
30
|
+
<TooltipProvider>
|
|
31
|
+
<Tooltip delayDuration={100}>
|
|
32
|
+
<TooltipTrigger onClick={() => setChecked(!checked)}>
|
|
33
|
+
{checked ? <FileCheck className="opacity-50" /> : <FileX className="opacity-50" />}
|
|
34
|
+
</TooltipTrigger>
|
|
35
|
+
|
|
36
|
+
<TooltipContent>
|
|
37
|
+
If checked will search only in this document
|
|
38
|
+
</TooltipContent>
|
|
39
|
+
</Tooltip>
|
|
40
|
+
</TooltipProvider>
|
|
41
|
+
</>}
|
|
39
42
|
|
|
40
43
|
</div>
|
|
41
44
|
);
|
package/src/page-wrapper.tsx
CHANGED
|
@@ -4,13 +4,20 @@ import { NavBar } from './navbar/navbar';
|
|
|
4
4
|
type Props = {
|
|
5
5
|
children: React.ReactNode;
|
|
6
6
|
title: string;
|
|
7
|
-
|
|
7
|
+
pageType: "BLOG" | "DOC" | "HOME"
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export const PageWrapper = ({ children, title,
|
|
10
|
+
export const PageWrapper = ({ children, title, pageType }: Props) => {
|
|
11
|
+
const showInput = pageType === "DOC" || pageType === "BLOG"
|
|
12
|
+
const showPkgFilter = pageType === "DOC"
|
|
13
|
+
|
|
11
14
|
return (
|
|
12
15
|
<>
|
|
13
|
-
<NavBar
|
|
16
|
+
<NavBar
|
|
17
|
+
title={title}
|
|
18
|
+
showInput={showInput}
|
|
19
|
+
showPkgFilter={showPkgFilter}
|
|
20
|
+
/>
|
|
14
21
|
{children}
|
|
15
22
|
</>
|
|
16
23
|
);
|
package/src/pagination.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
PaginationPrevious,
|
|
9
9
|
} from "@c-rex/ui/pagination"
|
|
10
10
|
import { parseAsInteger, useQueryState } from "nuqs";
|
|
11
|
+
import { useSearchContext } from "@c-rex/contexts/search";
|
|
11
12
|
|
|
12
13
|
interface PaginationProps {
|
|
13
14
|
totalPages: number;
|
|
@@ -16,6 +17,7 @@ interface PaginationProps {
|
|
|
16
17
|
|
|
17
18
|
export const Pagination: FC<PaginationProps> = ({ totalPages, currentPage }) => {
|
|
18
19
|
const disabledClass = "opacity-50 pointer-events-none";
|
|
20
|
+
const { setLoading } = useSearchContext();
|
|
19
21
|
|
|
20
22
|
const [_, setPage] = useQueryState('page',
|
|
21
23
|
parseAsInteger.withOptions({
|
|
@@ -24,6 +26,11 @@ export const Pagination: FC<PaginationProps> = ({ totalPages, currentPage }) =>
|
|
|
24
26
|
})
|
|
25
27
|
)
|
|
26
28
|
|
|
29
|
+
const onChangePage = (pageNumber: number) => {
|
|
30
|
+
setLoading(true);
|
|
31
|
+
setPage(pageNumber);
|
|
32
|
+
}
|
|
33
|
+
|
|
27
34
|
return (
|
|
28
35
|
<PaginationUI className="py-4">
|
|
29
36
|
<PaginationContent>
|
|
@@ -31,7 +38,7 @@ export const Pagination: FC<PaginationProps> = ({ totalPages, currentPage }) =>
|
|
|
31
38
|
<PaginationPrevious
|
|
32
39
|
href="#"
|
|
33
40
|
className={currentPage === 1 ? disabledClass : ""}
|
|
34
|
-
onClick={() =>
|
|
41
|
+
onClick={() => onChangePage(currentPage - 1)}
|
|
35
42
|
/>
|
|
36
43
|
</PaginationItem>
|
|
37
44
|
|
|
@@ -40,7 +47,7 @@ export const Pagination: FC<PaginationProps> = ({ totalPages, currentPage }) =>
|
|
|
40
47
|
<PaginationLink
|
|
41
48
|
href="#"
|
|
42
49
|
isActive={page === currentPage}
|
|
43
|
-
onClick={() =>
|
|
50
|
+
onClick={() => onChangePage(page)}
|
|
44
51
|
>
|
|
45
52
|
{page}
|
|
46
53
|
</PaginationLink>
|
|
@@ -55,7 +62,7 @@ export const Pagination: FC<PaginationProps> = ({ totalPages, currentPage }) =>
|
|
|
55
62
|
<PaginationItem>
|
|
56
63
|
<PaginationNext
|
|
57
64
|
href="#"
|
|
58
|
-
onClick={() =>
|
|
65
|
+
onClick={() => onChangePage(currentPage + 1)}
|
|
59
66
|
className={currentPage === totalPages ? disabledClass : ""}
|
|
60
67
|
/>
|
|
61
68
|
</PaginationItem>
|
package/src/result-list.tsx
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import React, { FC } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { 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
6
|
import { Pagination } from "./pagination";
|
|
7
|
+
import { useAppConfig } from "@c-rex/contexts/config-provider";
|
|
7
8
|
|
|
8
9
|
interface ResultListProps {
|
|
9
10
|
items: informationUnitsResponseItem[];
|
|
10
|
-
configs: ConfigInterface;
|
|
11
11
|
pagination: DefaultPageInfo;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export const ResultList: FC<ResultListProps> = ({ items,
|
|
14
|
+
export const ResultList: FC<ResultListProps> = ({ items, pagination }: ResultListProps) => {
|
|
15
|
+
const { configs } = useAppConfig()
|
|
16
|
+
|
|
15
17
|
const ViewComponent = configs.results.resultViewStyle == "table" ? TableView : BlogView;
|
|
16
18
|
|
|
17
19
|
return (
|
package/src/result-view/blog.tsx
CHANGED
|
@@ -1,20 +1,93 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { FC, useState } from "react";
|
|
4
|
+
|
|
5
|
+
import Link from "next/link";
|
|
6
|
+
import { cn } from "@c-rex/utils";
|
|
7
|
+
import { Badge } from "@c-rex/ui/badge";
|
|
8
|
+
import { useTranslations } from "next-intl";
|
|
9
|
+
import { TopicsResponseItem } from "@c-rex/interfaces";
|
|
10
|
+
import { Card } from "@c-rex/ui/card";
|
|
4
11
|
|
|
5
12
|
interface BlogViewProps {
|
|
6
|
-
items:
|
|
13
|
+
items: TopicsResponseItem[];
|
|
7
14
|
}
|
|
8
15
|
|
|
9
16
|
const BlogView: FC<BlogViewProps> = ({ items }) => {
|
|
17
|
+
const t = useTranslations("results")
|
|
18
|
+
const [isLoading, setLoading] = useState(true);
|
|
10
19
|
return (
|
|
11
|
-
<div className="grid gap-
|
|
12
|
-
{items.map((item, index) =>
|
|
20
|
+
<div className="grid gap-6 grid-cols-2 ">
|
|
21
|
+
{items.map((item, index) => (
|
|
22
|
+
|
|
23
|
+
<Card
|
|
24
|
+
key={item.shortId}
|
|
25
|
+
className={cn(
|
|
26
|
+
`blog-card relative p-0 c-rex_result_${item.type.toLowerCase()}`,
|
|
27
|
+
index == 0
|
|
28
|
+
? "col-span-2 grid grid-cols-1 gap-3 md:grid-cols-2 md:gap-6"
|
|
29
|
+
: "flex flex-col space-y-2",
|
|
30
|
+
item.disabled ? "c-rex_result_item_disabled" : ""
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
|
|
34
|
+
<div className={
|
|
35
|
+
cn(
|
|
36
|
+
"w-full overflow-hidden",
|
|
37
|
+
index == 0 ? "rounded-tl-xl rounded-bl-xl" : "rounded-t-xl"
|
|
38
|
+
)}>
|
|
39
|
+
{item.image && (
|
|
40
|
+
<img
|
|
41
|
+
src={item.image}
|
|
42
|
+
alt={item.title}
|
|
43
|
+
className={cn(
|
|
44
|
+
"size-full object-cover object-center",
|
|
45
|
+
isLoading ? "bg-gray-300 animate-pulse" : "",
|
|
46
|
+
)}
|
|
47
|
+
style={{
|
|
48
|
+
width: "100%", height: index == 0 ? "auto" : "190px"
|
|
49
|
+
}}
|
|
50
|
+
onLoad={() => setLoading(false)}
|
|
51
|
+
onError={() => setLoading(false)}
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div
|
|
58
|
+
className={cn(
|
|
59
|
+
"flex flex-1 flex-col p-4",
|
|
60
|
+
index == 0 ? "justify-around" : "justify-between",
|
|
61
|
+
)}
|
|
62
|
+
>
|
|
63
|
+
<div className="w-full">
|
|
64
|
+
<h2 className="my-1.5 line-clamp-2 font-heading text-2xl">
|
|
65
|
+
{item.title}
|
|
66
|
+
</h2>
|
|
67
|
+
|
|
68
|
+
{item.type && (
|
|
69
|
+
<Badge variant="secondary">
|
|
70
|
+
{item.type}
|
|
71
|
+
</Badge>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
<div className="mt-4 flex items-end flex-row gap-2">
|
|
75
|
+
<p className="m-0 flex-1 text-sm text-muted-foreground">{item.description?.substring(0, 100)}...</p>
|
|
76
|
+
|
|
77
|
+
{item.created && (
|
|
78
|
+
<p className="m-0 text-sm text-muted-foreground">{item.created}</p>
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
13
83
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
84
|
+
{!item.disabled && (
|
|
85
|
+
<Link href={item.link} className="absolute inset-0">
|
|
86
|
+
<span className="sr-only">{t("viewArticle")}</span>
|
|
87
|
+
</Link>
|
|
88
|
+
)}
|
|
89
|
+
</Card>
|
|
90
|
+
))}
|
|
18
91
|
</div>
|
|
19
92
|
);
|
|
20
93
|
};
|
package/src/search-modal.tsx
CHANGED
|
@@ -38,7 +38,7 @@ export const SearchModal: FC<SearchModalProps> = ({ trigger }) => {
|
|
|
38
38
|
wildcard: parseAsString,
|
|
39
39
|
operator: parseAsString,
|
|
40
40
|
search: parseAsString,
|
|
41
|
-
|
|
41
|
+
packages: parseAsString,
|
|
42
42
|
tags: parseAsBoolean,
|
|
43
43
|
like: parseAsBoolean,
|
|
44
44
|
}, {
|
|
@@ -80,7 +80,6 @@ export const SearchModal: FC<SearchModalProps> = ({ trigger }) => {
|
|
|
80
80
|
wildcard: WILD_CARD_OPTIONS.BOTH,
|
|
81
81
|
tags: false,
|
|
82
82
|
like: false,
|
|
83
|
-
package: ""
|
|
84
83
|
});
|
|
85
84
|
}
|
|
86
85
|
|
package/src/blog-card.tsx
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import Link from "next/link";
|
|
2
|
-
import { cn } from "@c-rex/utils";
|
|
3
|
-
import { BlurImage } from "./blur-image";
|
|
4
|
-
import { useTranslations } from "next-intl";
|
|
5
|
-
import { informationUnitsResponseItem } from "@c-rex/interfaces";
|
|
6
|
-
|
|
7
|
-
interface BlogCardProp {
|
|
8
|
-
item: informationUnitsResponseItem;
|
|
9
|
-
priority?: boolean;
|
|
10
|
-
horizontal?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const BlogCard = ({
|
|
14
|
-
item,
|
|
15
|
-
priority,
|
|
16
|
-
horizontal = false,
|
|
17
|
-
}: BlogCardProp) => {
|
|
18
|
-
const t = useTranslations("results")
|
|
19
|
-
const image = "/img/blog-post-1.webp"
|
|
20
|
-
const blurDataURL = "/img/blog-post-1.webp"
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<article
|
|
24
|
-
className={cn(
|
|
25
|
-
"group relative", `c-rex_result_${item.type.toLowerCase()}`,
|
|
26
|
-
horizontal
|
|
27
|
-
? "grid grid-cols-1 gap-3 md:grid-cols-2 md:gap-6"
|
|
28
|
-
: "flex flex-col space-y-2",
|
|
29
|
-
item.disabled ? "c-rex_result_item_disabled" : ""
|
|
30
|
-
)}
|
|
31
|
-
>
|
|
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
|
-
|
|
50
|
-
<div
|
|
51
|
-
className={cn(
|
|
52
|
-
"flex flex-1 flex-col",
|
|
53
|
-
horizontal ? "justify-center" : "justify-between",
|
|
54
|
-
)}
|
|
55
|
-
>
|
|
56
|
-
<div className="w-full">
|
|
57
|
-
<h2 className="my-1.5 line-clamp-2 font-heading text-2xl">
|
|
58
|
-
{item.title}
|
|
59
|
-
</h2>
|
|
60
|
-
{item.type && (
|
|
61
|
-
<p className="line-clamp-2 text-muted-foreground">
|
|
62
|
-
{item.type}
|
|
63
|
-
</p>
|
|
64
|
-
)}
|
|
65
|
-
</div>
|
|
66
|
-
{/*
|
|
67
|
-
<div className="mt-4 flex items-center space-x-3">
|
|
68
|
-
<div className="flex items-center -space-x-2">
|
|
69
|
-
{item.authors && (
|
|
70
|
-
<p className="text-sm text-muted-foreground">{item.authors}</p>
|
|
71
|
-
)}
|
|
72
|
-
</div>
|
|
73
|
-
|
|
74
|
-
{item.date && (
|
|
75
|
-
<p className="text-sm text-muted-foreground">{item.date}</p>
|
|
76
|
-
)}
|
|
77
|
-
</div>
|
|
78
|
-
*/}
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
|
-
{!item.disabled && (
|
|
82
|
-
<Link href={item.link} className="absolute inset-0">
|
|
83
|
-
<span className="sr-only">{t("viewArticle")}</span>
|
|
84
|
-
</Link>
|
|
85
|
-
)}
|
|
86
|
-
</article>
|
|
87
|
-
);
|
|
88
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
import { BlogCard } from '../blog-card';
|
|
3
|
-
|
|
4
|
-
const meta: Meta = {
|
|
5
|
-
title: 'Components/BlogCard',
|
|
6
|
-
component: BlogCard,
|
|
7
|
-
tags: ['autodocs',],
|
|
8
|
-
parameters: {
|
|
9
|
-
layout: 'centered',
|
|
10
|
-
},
|
|
11
|
-
argTypes: {
|
|
12
|
-
data: { control: 'object', description: 'The data to be displayed in the card' },
|
|
13
|
-
priority: { control: 'boolean', description: 'Whether the image should be prioritized', type: 'boolean' },
|
|
14
|
-
horizontal: { control: 'boolean' },
|
|
15
|
-
},
|
|
16
|
-
} satisfies Meta<typeof BlogCard>;
|
|
17
|
-
|
|
18
|
-
export default meta;
|
|
19
|
-
type Story = StoryObj<typeof meta>;
|
|
20
|
-
|
|
21
|
-
const mockBlogData = {
|
|
22
|
-
title: 'Getting Started with C-Rex Components',
|
|
23
|
-
blurDataURL: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==',
|
|
24
|
-
image: 'https://images.pexels.com/photos/31712301/pexels-photo-31712301/free-photo-of-scenic-cycling-tour-in-andernach-fields.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
|
|
25
|
-
description: 'Learn how to use C-Rex components to build modern web applications with React and Next.js.',
|
|
26
|
-
authors: 'John Doe',
|
|
27
|
-
_id: '1',
|
|
28
|
-
date: '2023-05-15',
|
|
29
|
-
slug: 'getting-started-with-c-rex-components',
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export const Horizontal: Story = {
|
|
33
|
-
args: {
|
|
34
|
-
data: mockBlogData,
|
|
35
|
-
priority: false,
|
|
36
|
-
horizontal: true,
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export const WithPriority: Story = {
|
|
41
|
-
args: {
|
|
42
|
-
data: mockBlogData,
|
|
43
|
-
priority: true,
|
|
44
|
-
horizontal: false,
|
|
45
|
-
},
|
|
46
|
-
};
|