@c-rex/components 0.1.38 → 0.1.39

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.
Files changed (71) hide show
  1. package/README.md +73 -73
  2. package/package.json +250 -218
  3. package/src/article/article-action-bar.tsx +110 -110
  4. package/src/article/article-content.tsx +18 -46
  5. package/src/autocomplete.tsx +201 -201
  6. package/src/breadcrumb.tsx +124 -124
  7. package/src/carousel/carousel.tsx +353 -353
  8. package/src/check-article-lang.tsx +47 -47
  9. package/src/directoryNodes/directory-tree-context.tsx +388 -0
  10. package/src/directoryNodes/tree-of-content.tsx +68 -67
  11. package/src/documents/result-list.tsx +124 -127
  12. package/src/favorites/bookmark-button.tsx +97 -94
  13. package/src/favorites/favorite-button.tsx +137 -120
  14. package/src/footer/footer-shell.tsx +52 -0
  15. package/src/footer/footer.tsx +7 -0
  16. package/src/footer/legal-links-block.tsx +25 -0
  17. package/src/footer/organization-contact-block.tsx +94 -0
  18. package/src/footer/social-links-block.tsx +38 -0
  19. package/src/footer/types.ts +10 -0
  20. package/src/footer/vcard-footer.tsx +72 -0
  21. package/src/generated/client-components.tsx +1366 -1350
  22. package/src/generated/create-client-request.tsx +116 -113
  23. package/src/generated/create-server-request.tsx +70 -61
  24. package/src/generated/create-suggestions-request.tsx +55 -55
  25. package/src/generated/server-components.tsx +1056 -1056
  26. package/src/generated/suggestions.tsx +302 -299
  27. package/src/icons/file-icon.tsx +8 -8
  28. package/src/icons/flag-icon.tsx +15 -15
  29. package/src/icons/loading.tsx +11 -11
  30. package/src/icons/social-icon.tsx +24 -0
  31. package/src/info/info-card.tsx +43 -0
  32. package/src/info/{info-table.tsx → information-unit-metadata-grid.tsx} +157 -168
  33. package/src/info/shared.tsx +49 -25
  34. package/src/navbar/language-switcher/content-language-switch.tsx +92 -92
  35. package/src/navbar/language-switcher/shared.tsx +33 -33
  36. package/src/navbar/language-switcher/ui-language-switch.tsx +37 -37
  37. package/src/navbar/navbar.tsx +157 -152
  38. package/src/navbar/settings.tsx +62 -62
  39. package/src/navbar/sign-in-out-btns.tsx +35 -35
  40. package/src/navbar/user-menu.tsx +60 -60
  41. package/src/page-wrapper.tsx +54 -31
  42. package/src/render-article.module.css +155 -0
  43. package/src/render-article.tsx +75 -68
  44. package/src/renditions/file-download.tsx +83 -83
  45. package/src/renditions/html.tsx +64 -64
  46. package/src/renditions/image/container.tsx +54 -54
  47. package/src/renditions/image/rendition.tsx +55 -55
  48. package/src/restriction-menu/restriction-menu-container.tsx +117 -53
  49. package/src/restriction-menu/restriction-menu-item.tsx +155 -147
  50. package/src/restriction-menu/restriction-menu.tsx +341 -156
  51. package/src/results/dialog-filter.tsx +166 -166
  52. package/src/results/empty.tsx +15 -15
  53. package/src/results/filter-navbar.tsx +294 -261
  54. package/src/results/filter-sidebar/__tests__/utils.test.ts +129 -0
  55. package/src/results/filter-sidebar/index.tsx +270 -126
  56. package/src/results/filter-sidebar/utils.ts +196 -164
  57. package/src/results/generic/table-result-list.tsx +97 -99
  58. package/src/results/{table-with-images.tsx → information-unit-search-results-card-list.tsx} +125 -127
  59. package/src/results/{cards.tsx → information-unit-search-results-cards.tsx} +99 -99
  60. package/src/results/{table.tsx → information-unit-search-results-table.tsx} +104 -104
  61. package/src/results/pagination.tsx +81 -81
  62. package/src/results/summary.ts +30 -0
  63. package/src/results/utils.ts +54 -54
  64. package/src/search-input.tsx +70 -70
  65. package/src/share-button.tsx +49 -49
  66. package/src/stores/favorites-store.ts +88 -88
  67. package/src/stores/highlight-store.ts +15 -15
  68. package/src/stores/language-store.ts +14 -14
  69. package/src/stores/restriction-store.ts +11 -11
  70. package/src/stores/search-settings-store.ts +68 -64
  71. package/src/info/set-available-versions.tsx +0 -19
@@ -1,68 +1,75 @@
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";
8
-
9
- type Props = {
10
- htmlContent: string;
11
- contentLang?: string;
12
- };
13
-
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
-
63
- return (
64
- <main ref={containerRef} lang={contentLang} className="pb-4 pr-4 min-h-[70vh]">
65
- {highlightedContent}
66
- </main>
67
- );
68
- };
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";
8
+ import styles from "./render-article.module.css";
9
+
10
+ type Props = {
11
+ htmlContent: string;
12
+ contentLang?: string;
13
+ };
14
+
15
+ export const RenderArticle = ({ htmlContent, contentLang }: Props) => {
16
+ const [query] = useQueryState("q");
17
+ const containerRef = useRef<HTMLElement | null>(null);
18
+ const enableHighlight = useHighlightStore((state) => state.enable);
19
+ const { registerContainer } = useHighlight();
20
+
21
+ const highlightedContent = useMemo(() => {
22
+ if (!enableHighlight) return parse(htmlContent);
23
+ if (!query) return parse(htmlContent);
24
+
25
+ const terms = query
26
+ .split(/[+ ]/)
27
+ .map((t) => t.trim())
28
+ .filter(Boolean);
29
+
30
+ if (terms.length === 0) return parse(htmlContent);
31
+
32
+ const escaped = terms.map((t) =>
33
+ t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
34
+ );
35
+ const regex = new RegExp(`(${escaped.join("|")})`, "gi");
36
+
37
+ return parse(htmlContent, {
38
+ replace: (domNode: any) => {
39
+ if (domNode.type === "text") {
40
+ const parts = domNode.data.split(regex);
41
+ if (parts.length === 1) return;
42
+ return (
43
+ <>
44
+ {parts.map((part: any, i: any) =>
45
+ regex.test(part) ? (
46
+ <mark key={i} className="bg-yellow-200">
47
+ {part}
48
+ </mark>
49
+ ) : (
50
+ part
51
+ )
52
+ )}
53
+ </>
54
+ );
55
+ }
56
+ },
57
+ });
58
+ }, [htmlContent, query, enableHighlight]);
59
+
60
+ useEffect(() => {
61
+ registerContainer(containerRef.current);
62
+ }, [highlightedContent, registerContainer]);
63
+
64
+ return (
65
+ <main
66
+ ref={containerRef}
67
+ id="ids-content"
68
+ data-content-scope="dita"
69
+ lang={contentLang}
70
+ className={`ids-content ids-content--dita-ot pb-4 pr-4 ${styles.idsContent}`}
71
+ >
72
+ {highlightedContent}
73
+ </main>
74
+ );
75
+ };
@@ -1,84 +1,84 @@
1
- import { FC } from "react";
2
- import { RenditionModel } from "@c-rex/interfaces";
3
- import {
4
- DropdownMenu,
5
- DropdownMenuContent,
6
- DropdownMenuItem,
7
- DropdownMenuTrigger
8
- } from "@c-rex/ui/dropdown-menu";
9
- import { Button } from "@c-rex/ui/button";
10
- import { FileIcon } from "../icons/file-icon";
11
- import { CloudDownload, Eye } from "lucide-react";
12
-
13
-
14
- interface FileDownloadDropdown {
15
- renditions: RenditionModel[] | null | undefined;
16
- }
17
-
18
- export const FileDownloadDropdown: FC<FileDownloadDropdown> = async ({ renditions }) => {
19
-
20
- if (renditions == null || renditions.length == 0) return null;
21
-
22
- const result: {
23
- [key: string]: {
24
- view: string;
25
- download: string;
26
- }
27
- } = {}
28
-
29
- renditions.forEach((item) => {
30
- const key = item.format!
31
-
32
- if (result[key] == undefined) {
33
- result[key] = {
34
- view: "",
35
- download: ""
36
- }
37
- }
38
- const download = item.links?.filter((link) => link.rel == "download")[0]?.href
39
- const view = item.links?.filter((link) => link.rel == "view")[0]?.href
40
-
41
- if (download != null && download != undefined) {
42
- result[key].download = download
43
- }
44
-
45
- if (view != null && view != undefined) {
46
- result[key].view = view
47
- }
48
- })
49
-
50
- return (
51
- <>
52
- {Object.keys(result).map((fileKey, idx) => {
53
- const file = result[fileKey];
54
- if (!file) return null;
55
-
56
- return (
57
- <DropdownMenu key={idx}>
58
- <DropdownMenuTrigger asChild>
59
- <Button variant="ghost" size="icon">
60
- <FileIcon extension={fileKey} />
61
- </Button>
62
- </DropdownMenuTrigger>
63
- <DropdownMenuContent>
64
- {file.view && (
65
- <DropdownMenuItem asChild>
66
- <a href={file.view} target="_blank" rel="noreferrer" className="flex items-center">
67
- <Eye className="mr-2" /> Open
68
- </a>
69
- </DropdownMenuItem>
70
- )}
71
- {file.download && (
72
- <DropdownMenuItem asChild>
73
- <a href={file.download} target="_blank" rel="noreferrer" className="flex items-center">
74
- <CloudDownload className="mr-2" /> Download
75
- </a>
76
- </DropdownMenuItem>
77
- )}
78
- </DropdownMenuContent>
79
- </DropdownMenu>
80
- );
81
- })}
82
- </>
83
- )
1
+ import { FC } from "react";
2
+ import { RenditionModel } from "@c-rex/interfaces";
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuTrigger
8
+ } from "@c-rex/ui/dropdown-menu";
9
+ import { Button } from "@c-rex/ui/button";
10
+ import { FileIcon } from "../icons/file-icon";
11
+ import { CloudDownload, Eye } from "lucide-react";
12
+
13
+
14
+ interface FileDownloadDropdown {
15
+ renditions: RenditionModel[] | null | undefined;
16
+ }
17
+
18
+ export const FileDownloadDropdown: FC<FileDownloadDropdown> = async ({ renditions }) => {
19
+
20
+ if (renditions == null || renditions.length == 0) return null;
21
+
22
+ const result: {
23
+ [key: string]: {
24
+ view: string;
25
+ download: string;
26
+ }
27
+ } = {}
28
+
29
+ renditions.forEach((item) => {
30
+ const key = item.format!
31
+
32
+ if (result[key] == undefined) {
33
+ result[key] = {
34
+ view: "",
35
+ download: ""
36
+ }
37
+ }
38
+ const download = item.links?.filter((link) => link.rel == "download")[0]?.href
39
+ const view = item.links?.filter((link) => link.rel == "view")[0]?.href
40
+
41
+ if (download != null && download != undefined) {
42
+ result[key].download = download
43
+ }
44
+
45
+ if (view != null && view != undefined) {
46
+ result[key].view = view
47
+ }
48
+ })
49
+
50
+ return (
51
+ <>
52
+ {Object.keys(result).map((fileKey, idx) => {
53
+ const file = result[fileKey];
54
+ if (!file) return null;
55
+
56
+ return (
57
+ <DropdownMenu key={idx}>
58
+ <DropdownMenuTrigger asChild>
59
+ <Button variant="ghost" size="icon">
60
+ <FileIcon extension={fileKey} />
61
+ </Button>
62
+ </DropdownMenuTrigger>
63
+ <DropdownMenuContent>
64
+ {file.view && (
65
+ <DropdownMenuItem asChild>
66
+ <a href={file.view} target="_blank" rel="noreferrer" className="flex items-center">
67
+ <Eye className="mr-2" /> Open
68
+ </a>
69
+ </DropdownMenuItem>
70
+ )}
71
+ {file.download && (
72
+ <DropdownMenuItem asChild>
73
+ <a href={file.download} target="_blank" rel="noreferrer" className="flex items-center">
74
+ <CloudDownload className="mr-2" /> Download
75
+ </a>
76
+ </DropdownMenuItem>
77
+ )}
78
+ </DropdownMenuContent>
79
+ </DropdownMenu>
80
+ );
81
+ })}
82
+ </>
83
+ )
84
84
  }
@@ -1,65 +1,65 @@
1
- import { FC, JSX } from "react";
2
- import * as cheerio from "cheerio"
3
- import { RenditionModel } from "@c-rex/interfaces";
4
- import { fragmentsGetAllServer } from "@c-rex/services/server-requests";
5
- import { call } from "@c-rex/utils";
6
-
7
- interface HtmlRenditionProps {
8
- htmlFormats?: string[]
9
- shortId: string,
10
- render?: (html: string) => JSX.Element,
11
- renditions?: RenditionModel[] | null
12
- }
13
-
14
- const defaultRender = (html: string) => {
15
- const $ = cheerio.load(html)
16
- const articleHtml = $("body").html() || ""
17
- return <div dangerouslySetInnerHTML={{ __html: articleHtml }} />;
18
- }
19
-
20
- export const HtmlRendition: FC<HtmlRenditionProps> = async ({
21
- shortId,
22
- htmlFormats = ["application/xhtml+xml", "application/html", "text/html"],
23
- render = defaultRender,
24
- renditions
25
- }) => {
26
- const empty = <div>No rendition available</div>;
27
-
28
- if (renditions == undefined) {
29
- const result = await fragmentsGetAllServer({
30
- Fields: ["titles", "renditions"],
31
- Embed: ["renditions"],
32
- PageSize: 1,
33
- Links: true,
34
- Restrict: [
35
- `informationUnits=${shortId}`,
36
- ...htmlFormats.map(format => `renditions.format=${format}`)
37
- ],
38
- })
39
-
40
- renditions = result.items?.[0]?.renditions;
41
- }
42
-
43
- if (renditions == null || renditions.length == 0) return empty;
44
- renditions = renditions.filter(rendition => htmlFormats.includes(rendition.format!));
45
-
46
- if (renditions.length == 0 || renditions[0] == undefined || renditions[0].links == undefined) return empty;
47
-
48
- const filteredLinks = renditions[0].links.filter((item) => item.rel == "view");
49
-
50
- if (filteredLinks.length == 0 || filteredLinks[0] == undefined || filteredLinks[0].href == undefined) return empty;
51
-
52
- const url = filteredLinks[0].href;
53
- try {
54
-
55
- const html = await fetch(url).then(res => res.text());
56
- return render(html);
57
- } catch (error) {
58
- call("CrexLogger.log", {
59
- level: "error",
60
- message: `HtmlRendition error: ${error}`
61
- });
62
-
63
- return empty;
64
- }
1
+ import { FC, JSX } from "react";
2
+ import * as cheerio from "cheerio"
3
+ import { RenditionModel } from "@c-rex/interfaces";
4
+ import { fragmentsGetAllServer } from "@c-rex/services/server-requests";
5
+ import { call } from "@c-rex/utils";
6
+
7
+ interface HtmlRenditionProps {
8
+ htmlFormats?: string[]
9
+ shortId: string,
10
+ render?: (html: string) => JSX.Element,
11
+ renditions?: RenditionModel[] | null
12
+ }
13
+
14
+ const defaultRender = (html: string) => {
15
+ const $ = cheerio.load(html)
16
+ const articleHtml = $("body").html() || ""
17
+ return <div dangerouslySetInnerHTML={{ __html: articleHtml }} />;
18
+ }
19
+
20
+ export const HtmlRendition: FC<HtmlRenditionProps> = async ({
21
+ shortId,
22
+ htmlFormats = ["application/xhtml+xml", "application/html", "text/html"],
23
+ render = defaultRender,
24
+ renditions
25
+ }) => {
26
+ const empty = <div>No rendition available</div>;
27
+
28
+ if (renditions == undefined) {
29
+ const result = await fragmentsGetAllServer({
30
+ Fields: ["titles", "renditions"],
31
+ Embed: ["renditions"],
32
+ PageSize: 1,
33
+ Links: true,
34
+ Restrict: [
35
+ `informationUnits=${shortId}`,
36
+ ...htmlFormats.map(format => `renditions.format=${format}`)
37
+ ],
38
+ })
39
+
40
+ renditions = result.items?.[0]?.renditions;
41
+ }
42
+
43
+ if (renditions == null || renditions.length == 0) return empty;
44
+ renditions = renditions.filter(rendition => htmlFormats.includes(rendition.format!));
45
+
46
+ if (renditions.length == 0 || renditions[0] == undefined || renditions[0].links == undefined) return empty;
47
+
48
+ const filteredLinks = renditions[0].links.filter((item) => item.rel == "view");
49
+
50
+ if (filteredLinks.length == 0 || filteredLinks[0] == undefined || filteredLinks[0].href == undefined) return empty;
51
+
52
+ const url = filteredLinks[0].href;
53
+ try {
54
+
55
+ const html = await fetch(url).then(res => res.text());
56
+ return render(html);
57
+ } catch (error) {
58
+ call("CrexLogger.log", {
59
+ level: "error",
60
+ message: `HtmlRendition error: ${error}`
61
+ });
62
+
63
+ return empty;
64
+ }
65
65
  }
@@ -1,55 +1,55 @@
1
- "use client"
2
-
3
- import { FC } from "react";
4
- import { cn } from "@c-rex/utils";
5
- import { FragmentsGetAllClient } from "../../generated/client-components";
6
- import { ImageRendition } from "./rendition";
7
- import { Skeleton } from "@c-rex/ui/skeleton";
8
-
9
- interface ImageContainerProps {
10
- itemShortId: string;
11
- imageRestrictions?: string[];
12
- imageFormats?: string[];
13
- emptyImageStyle?: string;
14
- skeletonStyle?: string;
15
- imageStyle?: string;
16
- }
17
-
18
- export const ImageRenditionContainer: FC<ImageContainerProps> = ({
19
- itemShortId,
20
- emptyImageStyle,
21
- imageStyle,
22
- imageFormats = ["image/svg+xml", "image/gif", "image/png", "image/jpg"],
23
- skeletonStyle
24
- }) => {
25
- const newRestrictions = [
26
- `informationUnits=${itemShortId}`,
27
- ...imageFormats.map(format => `renditions.format=${format}`)
28
- ];
29
-
30
- return (
31
- <FragmentsGetAllClient
32
- queryParams={{
33
- Fields: ["titles", "renditions"],
34
- Embed: ["renditions"],
35
- PageSize: 1,
36
- Links: true,
37
- Restrict: newRestrictions,
38
- }}
39
- >
40
- {({ data, isLoading }) => (
41
- isLoading ? (
42
- <Skeleton className={cn("w-full h-full", skeletonStyle)} />
43
- ) : (
44
- <ImageRendition
45
- key={data?.items[0]?.shortId}
46
- items={data?.items as any}
47
- formats={imageFormats}
48
- emptyImageStyle={emptyImageStyle}
49
- imageStyle={imageStyle}
50
- />
51
- )
52
- )}
53
- </FragmentsGetAllClient>
54
- )
1
+ "use client"
2
+
3
+ import { FC } from "react";
4
+ import { cn } from "@c-rex/utils";
5
+ import { FragmentsGetAllClient } from "../../generated/client-components";
6
+ import { ImageRendition } from "./rendition";
7
+ import { Skeleton } from "@c-rex/ui/skeleton";
8
+
9
+ interface ImageContainerProps {
10
+ itemShortId: string;
11
+ imageRestrictions?: string[];
12
+ imageFormats?: string[];
13
+ emptyImageStyle?: string;
14
+ skeletonStyle?: string;
15
+ imageStyle?: string;
16
+ }
17
+
18
+ export const ImageRenditionContainer: FC<ImageContainerProps> = ({
19
+ itemShortId,
20
+ emptyImageStyle,
21
+ imageStyle,
22
+ imageFormats = ["image/svg+xml", "image/gif", "image/png", "image/jpg"],
23
+ skeletonStyle
24
+ }) => {
25
+ const newRestrictions = [
26
+ `informationUnits=${itemShortId}`,
27
+ ...imageFormats.map(format => `renditions.format=${format}`)
28
+ ];
29
+
30
+ return (
31
+ <FragmentsGetAllClient
32
+ queryParams={{
33
+ Fields: ["titles", "renditions"],
34
+ Embed: ["renditions"],
35
+ PageSize: 1,
36
+ Links: true,
37
+ Restrict: newRestrictions,
38
+ }}
39
+ >
40
+ {({ data, isLoading }) => (
41
+ isLoading ? (
42
+ <Skeleton className={cn("w-full h-full", skeletonStyle)} />
43
+ ) : (
44
+ <ImageRendition
45
+ key={data?.items[0]?.shortId}
46
+ items={data?.items as any}
47
+ formats={imageFormats}
48
+ emptyImageStyle={emptyImageStyle}
49
+ imageStyle={imageStyle}
50
+ />
51
+ )
52
+ )}
53
+ </FragmentsGetAllClient>
54
+ )
55
55
  }