@c-rex/components 0.1.37 → 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 (146) hide show
  1. package/README.md +73 -73
  2. package/package.json +250 -235
  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 -352
  8. package/src/check-article-lang.tsx +47 -43
  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 -79
  13. package/src/favorites/favorite-button.tsx +137 -74
  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 -146
  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 -38
  37. package/src/navbar/navbar.tsx +157 -148
  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 -157
  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 -47
  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 -43
  69. package/src/stores/restriction-store.ts +11 -11
  70. package/src/stores/search-settings-store.ts +68 -64
  71. package/src/article/article-action-bar.analysis.md +0 -15
  72. package/src/article/article-action-bar.stories.tsx +0 -15
  73. package/src/article/article-content.analysis.md +0 -15
  74. package/src/article/article-content.stories.tsx +0 -21
  75. package/src/autocomplete.analysis.md +0 -17
  76. package/src/breadcrumb.analysis.md +0 -15
  77. package/src/carousel/carousel.analysis.md +0 -17
  78. package/src/check-article-lang.analysis.md +0 -15
  79. package/src/directoryNodes/tree-of-content.analysis.md +0 -14
  80. package/src/directoryNodes/tree-of-content.stories.tsx +0 -22
  81. package/src/documents/result-list.analysis.md +0 -14
  82. package/src/documents/result-list.stories.tsx +0 -19
  83. package/src/favorites/bookmark-button.analysis.md +0 -17
  84. package/src/favorites/bookmark-button.stories.tsx +0 -19
  85. package/src/favorites/favorite-button.analysis.md +0 -18
  86. package/src/favorites/favorite-button.stories.tsx +0 -22
  87. package/src/icons/file-icon.analysis.md +0 -14
  88. package/src/icons/file-icon.stories.tsx +0 -19
  89. package/src/icons/flag-icon.analysis.md +0 -14
  90. package/src/icons/flag-icon.stories.tsx +0 -25
  91. package/src/icons/loading.analysis.md +0 -14
  92. package/src/icons/loading.stories.tsx +0 -21
  93. package/src/info/info-table.analysis.md +0 -15
  94. package/src/info/shared.analysis.md +0 -14
  95. package/src/info/stories/info-table.stories.tsx +0 -31
  96. package/src/info/stories/shared.stories.tsx +0 -24
  97. package/src/navbar/language-switcher/content-language-switch.analysis.md +0 -15
  98. package/src/navbar/language-switcher/shared.analysis.md +0 -14
  99. package/src/navbar/language-switcher/ui-language-switch.analysis.md +0 -15
  100. package/src/navbar/navbar.analysis.md +0 -14
  101. package/src/navbar/settings.analysis.md +0 -14
  102. package/src/navbar/sign-in-out-btns.analysis.md +0 -14
  103. package/src/navbar/stories/navbar.stories.tsx +0 -31
  104. package/src/navbar/stories/settings.stories.tsx +0 -15
  105. package/src/navbar/stories/sign-in-out-btns.stories.tsx +0 -15
  106. package/src/navbar/stories/user-menu.stories.tsx +0 -20
  107. package/src/navbar/user-menu.analysis.md +0 -14
  108. package/src/page-wrapper.analysis.md +0 -14
  109. package/src/render-article.analysis.md +0 -15
  110. package/src/renditions/file-download.analysis.md +0 -14
  111. package/src/renditions/file-download.stories.tsx +0 -19
  112. package/src/renditions/html.analysis.md +0 -17
  113. package/src/renditions/html.stories.tsx +0 -19
  114. package/src/renditions/image/container.analysis.md +0 -15
  115. package/src/renditions/image/container.stories.tsx +0 -19
  116. package/src/renditions/image/rendition.analysis.md +0 -14
  117. package/src/renditions/image/rendition.stories.tsx +0 -19
  118. package/src/restriction-menu/restriction-menu-container.analysis.md +0 -14
  119. package/src/restriction-menu/restriction-menu-item.analysis.md +0 -14
  120. package/src/restriction-menu/restriction-menu.analysis.md +0 -17
  121. package/src/results/analysis/cards.analysis.md +0 -14
  122. package/src/results/analysis/dialog-filter.analysis.md +0 -17
  123. package/src/results/analysis/empty.analysis.md +0 -14
  124. package/src/results/analysis/filter-navbar.analysis.md +0 -16
  125. package/src/results/analysis/pagination.analysis.md +0 -14
  126. package/src/results/analysis/table-with-images.analysis.md +0 -15
  127. package/src/results/analysis/table.analysis.md +0 -15
  128. package/src/results/filter-sidebar/index.analysis.md +0 -14
  129. package/src/results/generic/table-result-list.analysis.md +0 -15
  130. package/src/results/generic/table-result-list.stories.tsx +0 -21
  131. package/src/results/stories/cards.stories.tsx +0 -66
  132. package/src/results/stories/dialog-filter.stories.tsx +0 -20
  133. package/src/results/stories/empty.stories.tsx +0 -25
  134. package/src/results/stories/filter-navbar.stories.tsx +0 -19
  135. package/src/results/stories/filter-sidebar.stories.tsx +0 -20
  136. package/src/results/stories/pagination.stories.tsx +0 -24
  137. package/src/results/stories/table-with-images.stories.tsx +0 -19
  138. package/src/results/stories/table.stories.tsx +0 -78
  139. package/src/search-input.analysis.md +0 -15
  140. package/src/share-button.analysis.md +0 -19
  141. package/src/stories/autocomplete.stories.tsx +0 -20
  142. package/src/stories/breadcrumb.stories.tsx +0 -93
  143. package/src/stories/check-article-lang.stories.tsx +0 -22
  144. package/src/stories/render-article.stories.tsx +0 -19
  145. package/src/stories/search-input.stories.tsx +0 -21
  146. package/src/stories/share-button.stories.tsx +0 -15
@@ -1,127 +1,124 @@
1
- import { FC } from "react";
2
- import { CommonItemsModel } from "@c-rex/interfaces";
3
- import { FileStack } from "lucide-react";
4
- import { cn, getType, getTitle, getVersions, getLanguage, generateQueryParams } from "@c-rex/utils";
5
- import { Button } from "@c-rex/ui/button";
6
- import { Tooltip, TooltipContent, TooltipTrigger } from "@c-rex/ui/tooltip";
7
- import { RESULT_TYPES } from "@c-rex/constants";
8
- import { FileDownloadDropdown } from "@c-rex/components/file-download";
9
- import { FavoriteButton } from "@c-rex/components/favorite-button";
10
- import { ImageRenditionContainer } from "../renditions/image/container";
11
- import { BookmarkButton } from "../favorites/bookmark-button";
12
- import { HtmlRendition } from "../renditions/html";
13
- import { QueryParams } from "@c-rex/types";
14
-
15
- interface Props {
16
- items: CommonItemsModel[];
17
- query?: string;
18
- imageRestrictions?: string[];
19
- showActions?: boolean;
20
- }
21
-
22
-
23
- export const DocumentsResultList: FC<Props> = ({
24
- items,
25
- query = "",
26
- imageRestrictions = [],
27
- showActions = true,
28
- }) => {
29
- return (
30
- <div className="flex-1">
31
- {items.map((item, index) => {
32
- const title = getTitle(item.titles, item.labels);
33
- const language = getLanguage(item.languages);
34
- const itemType = getType(item.class);
35
- const isDocument = itemType === RESULT_TYPES.DOCUMENT;
36
- const multipleVersions = getVersions(item.versionOf);
37
- const packageId = item.packages && item.packages.length > 0 ? item.packages[0]?.shortId : null;
38
- const queryParams: QueryParams[] = []
39
-
40
- if (packageId) {
41
- queryParams.push({
42
- key: "package",
43
- value: packageId,
44
- })
45
- }
46
-
47
- if (query.length > 0) {
48
- queryParams.push({
49
- key: "q",
50
- value: query,
51
- })
52
- }
53
-
54
- const params = generateQueryParams(queryParams)
55
- const itemLink = `/documents/${item.shortId}` + (params.length > 0 ? `?${params}` : "")
56
-
57
- return (
58
- <div
59
- key={item.shortId}
60
- className={cn(
61
- "min-h-12 flex flex-wrap items-center border px-4 py-2 gap-2 rounded",
62
- index == items.length - 1 ? "" : "mb-4",
63
- `c-rex-result-item c-rex-result-${itemType}`
64
- )}
65
- >
66
- <div className="h-16 w-16 flex items-start">
67
- <ImageRenditionContainer
68
- itemShortId={item.shortId!}
69
- imageRestrictions={imageRestrictions}
70
- emptyImageStyle="h-16 w-16"
71
- />
72
- </div>
73
-
74
- <div className="flex-1 p-2 flex flex-col">
75
- <span className="text-sm text-muted-foreground">
76
- {item.revision} - {language}
77
- </span>
78
-
79
- <span className="text-lg font-medium">
80
- <a className="hover:underline" href={itemLink}>{title}</a>
81
- </span>
82
-
83
- <span className="text-sm">
84
- {itemType}
85
- </span>
86
- <span className="text-sm">
87
- <HtmlRendition shortId={item.shortId!} />
88
- </span>
89
- </div>
90
-
91
- <div className="flex flex-col p-2 ml-auto justify-center">
92
- <div className="flex gap-2">
93
- <FileDownloadDropdown renditions={item.renditions} />
94
-
95
- <FavoriteButton
96
- id={item.shortId!}
97
- type={itemType}
98
- label={title}
99
- />
100
-
101
- {multipleVersions.length > 1 && (
102
- <Tooltip>
103
- <TooltipTrigger asChild>
104
- <Button variant="ghost" size="icon">
105
- <FileStack />
106
- </Button>
107
- </TooltipTrigger>
108
- <TooltipContent>
109
- Available in: {multipleVersions.join(", ")}
110
- </TooltipContent>
111
- </Tooltip>
112
- )}
113
-
114
- {isDocument && (
115
- <BookmarkButton
116
- shortId={item.shortId!}
117
- triggerVariant="ghost"
118
- />
119
- )}
120
- </div>
121
- </div>
122
- </div>
123
- );
124
- })}
125
- </div>
126
- );
127
- };
1
+ import { FC } from "react";
2
+ import { CommonItemsModel } from "@c-rex/interfaces";
3
+ import { FileStack } from "lucide-react";
4
+ import { cn, generateQueryParams } from "@c-rex/utils";
5
+ import { Button } from "@c-rex/ui/button";
6
+ import { Tooltip, TooltipContent, TooltipTrigger } from "@c-rex/ui/tooltip";
7
+ import { RESULT_TYPES } from "@c-rex/constants";
8
+ import { FileDownloadDropdown } from "@c-rex/components/file-download";
9
+ import { FavoriteButton } from "@c-rex/components/favorite-button";
10
+ import { ImageRenditionContainer } from "../renditions/image/container";
11
+ import { BookmarkButton } from "../favorites/bookmark-button";
12
+ import { HtmlRendition } from "../renditions/html";
13
+ import { QueryParams } from "@c-rex/types";
14
+ import { getResultItemSummary } from "../results/summary";
15
+
16
+ interface Props {
17
+ items: CommonItemsModel[];
18
+ query?: string;
19
+ imageRestrictions?: string[];
20
+ showActions?: boolean;
21
+ }
22
+
23
+
24
+ export const DocumentsResultList: FC<Props> = ({
25
+ items,
26
+ query = "",
27
+ imageRestrictions = [],
28
+ showActions = true,
29
+ }) => {
30
+ return (
31
+ <div className="flex-1">
32
+ {items.map((item, index) => {
33
+ const { title, language, itemType, multipleVersions, packageId } = getResultItemSummary(item);
34
+ const isDocument = itemType === RESULT_TYPES.DOCUMENT;
35
+ const queryParams: QueryParams[] = []
36
+
37
+ if (packageId) {
38
+ queryParams.push({
39
+ key: "package",
40
+ value: packageId,
41
+ })
42
+ }
43
+
44
+ if (query.length > 0) {
45
+ queryParams.push({
46
+ key: "q",
47
+ value: query,
48
+ })
49
+ }
50
+
51
+ const params = generateQueryParams(queryParams)
52
+ const itemLink = `/documents/${item.shortId}` + (params.length > 0 ? `?${params}` : "")
53
+
54
+ return (
55
+ <div
56
+ key={item.shortId}
57
+ className={cn(
58
+ "min-h-12 flex flex-wrap items-center border px-4 py-2 gap-2 rounded",
59
+ index == items.length - 1 ? "" : "mb-4",
60
+ `c-rex-result-item c-rex-result-${itemType}`
61
+ )}
62
+ >
63
+ <div className="h-16 w-16 flex items-start">
64
+ <ImageRenditionContainer
65
+ itemShortId={item.shortId!}
66
+ imageRestrictions={imageRestrictions}
67
+ emptyImageStyle="h-16 w-16"
68
+ />
69
+ </div>
70
+
71
+ <div className="flex-1 p-2 flex flex-col">
72
+ <span className="text-sm text-muted-foreground">
73
+ {item.revision} - {language}
74
+ </span>
75
+
76
+ <span className="text-lg font-medium">
77
+ <a className="hover:underline" href={itemLink}>{title}</a>
78
+ </span>
79
+
80
+ <span className="text-sm">
81
+ {itemType}
82
+ </span>
83
+ <span className="text-sm">
84
+ <HtmlRendition shortId={item.shortId!} />
85
+ </span>
86
+ </div>
87
+
88
+ <div className="flex flex-col p-2 ml-auto justify-center">
89
+ <div className="flex gap-2">
90
+ <FileDownloadDropdown renditions={item.renditions} />
91
+
92
+ <FavoriteButton
93
+ id={item.shortId!}
94
+ type={itemType}
95
+ label={title}
96
+ />
97
+
98
+ {multipleVersions.length > 1 && (
99
+ <Tooltip>
100
+ <TooltipTrigger asChild>
101
+ <Button variant="ghost" size="icon">
102
+ <FileStack />
103
+ </Button>
104
+ </TooltipTrigger>
105
+ <TooltipContent>
106
+ Available in: {multipleVersions.join(", ")}
107
+ </TooltipContent>
108
+ </Tooltip>
109
+ )}
110
+
111
+ {isDocument && (
112
+ <BookmarkButton
113
+ shortId={item.shortId!}
114
+ triggerVariant="ghost"
115
+ />
116
+ )}
117
+ </div>
118
+ </div>
119
+ </div>
120
+ );
121
+ })}
122
+ </div>
123
+ );
124
+ };
@@ -1,79 +1,97 @@
1
- "use client";
2
-
3
- import { ComponentProps, FC, ReactNode, useEffect, useState } from "react";
4
- import { Button, ButtonProps } from "@c-rex/ui/button";
5
- import { Trash } from "lucide-react";
6
- import { cn } from "@c-rex/utils";
7
- import Link from "next/link";
8
- import { useFavoritesStore } from "../stores/favorites-store";
9
- import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@c-rex/ui/dialog";
10
- import {
11
- Table,
12
- TableBody,
13
- TableCell,
14
- TableRow,
15
- } from "@c-rex/ui/table"
16
- import { FaRegBookmark } from "react-icons/fa6";
17
-
18
- type BookmarkProps = {
19
- shortId: string;
20
- triggerVariant?: ComponentProps<typeof Button>["variant"];
21
- }
22
-
23
- export const BookmarkButton: FC<BookmarkProps> = ({
24
- shortId,
25
- triggerVariant = "outline"
26
- }) => {
27
- const [markersList, setMarkersList] = useState<Array<{ id: string; label: string; color: string }>>([]);
28
-
29
- useEffect(() => {
30
- setMarkersList(useFavoritesStore.getState().documents[shortId]?.topics || []);
31
- }, [shortId]);
32
- return (
33
- <Dialog>
34
- <DialogTrigger asChild>
35
- <Button variant={triggerVariant} size="icon" className="relative">
36
- <FaRegBookmark className="text-primary" />
37
-
38
- {markersList.length > 0 && (
39
- <span
40
- className="absolute -top-[10px] -right-[10px] min-w-5 min-h-5 bg-primary text-white rounded-full"
41
- >
42
- {markersList.length}
43
- </span>
44
- )}
45
- </Button>
46
-
47
- </DialogTrigger>
48
- <DialogContent>
49
- <DialogHeader>
50
- <DialogTitle>Bookmarks</DialogTitle>
51
- <DialogDescription>
52
- Manage your bookmarks here
53
- </DialogDescription>
54
- </DialogHeader>
55
- <Table>
56
- <TableBody>
57
- {markersList.map((item) => (
58
- <TableRow key={item.id} className="min-h-12">
59
- <TableCell>
60
- <FaRegBookmark className={cn("w-5", `text-${item.color}`)} />
61
- </TableCell>
62
- <TableCell>
63
- <Link href={`${window.location.origin}/topics/${item.id}`}>
64
- {item.label}
65
- </Link>
66
- </TableCell>
67
- <TableCell>
68
- <Button variant="destructive" size="icon">
69
- <Trash className="w-5 hover:text-red-600 cursor-pointer" />
70
- </Button>
71
- </TableCell>
72
- </TableRow>
73
- ))}
74
- </TableBody>
75
- </Table>
76
- </DialogContent>
77
- </Dialog>
78
- )
79
- }
1
+ "use client";
2
+
3
+ import { ComponentProps, FC } from "react";
4
+ import { Button } from "@c-rex/ui/button";
5
+ import { Trash } from "lucide-react";
6
+ import { cn } from "@c-rex/utils";
7
+ import Link from "next/link";
8
+ import { useFavoritesStore } from "../stores/favorites-store";
9
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@c-rex/ui/dialog";
10
+ import { useTranslations } from "next-intl";
11
+ import {
12
+ Table,
13
+ TableBody,
14
+ TableCell,
15
+ TableRow,
16
+ } from "@c-rex/ui/table"
17
+ import { FaRegBookmark } from "react-icons/fa6";
18
+
19
+ const EMPTY_TOPICS: { id: string; label: string; color: string }[] = [];
20
+
21
+ type BookmarkProps = {
22
+ shortId: string;
23
+ linkPattern?: string;
24
+ triggerVariant?: ComponentProps<typeof Button>["variant"];
25
+ }
26
+
27
+ export const BookmarkButton: FC<BookmarkProps> = ({
28
+ shortId,
29
+ linkPattern = `/topics/{shortId}/pages`,
30
+ triggerVariant = "outline"
31
+ }) => {
32
+ const t = useTranslations("bookmarks");
33
+ const removeFavoriteTopic = useFavoritesStore((state) => state.unfavoriteTopic);
34
+ const markersList = useFavoritesStore((state) => state.documents[shortId]?.topics) ?? EMPTY_TOPICS;
35
+ return (
36
+ <Dialog>
37
+ <DialogTrigger asChild>
38
+ <Button variant={triggerVariant} size="icon" className="relative">
39
+ <FaRegBookmark className="text-primary" />
40
+
41
+ {markersList.length > 0 && (
42
+ <span
43
+ className="absolute -top-[10px] -right-[10px] min-w-5 min-h-5 bg-primary text-white rounded-full"
44
+ >
45
+ {markersList.length}
46
+ </span>
47
+ )}
48
+ </Button>
49
+
50
+ </DialogTrigger>
51
+ <DialogContent>
52
+ <DialogHeader>
53
+ <DialogTitle>{t("title")}</DialogTitle>
54
+ <DialogDescription>
55
+ {t("description")}
56
+ </DialogDescription>
57
+ </DialogHeader>
58
+ <Table>
59
+ <TableBody>
60
+ {markersList.length === 0 && (
61
+ <TableRow>
62
+ <TableCell colSpan={3} className="text-center">
63
+ {t("empty")}
64
+ </TableCell>
65
+ </TableRow>
66
+ )}
67
+
68
+ {markersList.map((item) => (
69
+ <TableRow key={item.id} className="min-h-12">
70
+ <TableCell>
71
+ <FaRegBookmark className={cn("w-5", `text-${item.color}`)} />
72
+ </TableCell>
73
+ <TableCell>
74
+ <Link
75
+ href={linkPattern.replace("{shortId}", item.id).replace("{id}", item.id)}
76
+ className="hover:underline"
77
+ >
78
+ {item.label}
79
+ </Link>
80
+ </TableCell>
81
+ <TableCell>
82
+ <Button
83
+ variant="destructive"
84
+ size="icon"
85
+ onClick={() => removeFavoriteTopic(shortId, item.id)}
86
+ >
87
+ <Trash className="w-5 hover:text-red-600 cursor-pointer" />
88
+ </Button>
89
+ </TableCell>
90
+ </TableRow>
91
+ ))}
92
+ </TableBody>
93
+ </Table>
94
+ </DialogContent>
95
+ </Dialog>
96
+ )
97
+ }
@@ -1,74 +1,137 @@
1
- "use client";
2
-
3
- import { FC, useEffect, useState } from "react";
4
- import { Button } from "@c-rex/ui/button";
5
- import { FaStar, FaRegStar } from "react-icons/fa";
6
- import { useFavoritesStore } from "../stores/favorites-store";
7
- import { MARKER_COLORS, RESULT_TYPES } from "@c-rex/constants";
8
- import { ResultTypes } from "@c-rex/types";
9
-
10
- export const FavoriteButton: FC<{ id: string, type: ResultTypes, label: string }> = ({ id, type, label }) => {
11
- const addFavoriteTopic = useFavoritesStore((state) => state.favoriteTopic);
12
- const addFavoriteDocument = useFavoritesStore((state) => state.favoriteDocument);
13
- const removeFavoriteTopic = useFavoritesStore((state) => state.unfavoriteTopic);
14
- const removeFavoriteDocument = useFavoritesStore((state) => state.unfavoriteDocument);
15
-
16
- const favoriteDocumentList = useFavoritesStore((state) => state.documents);
17
- const favoriteList = useFavoritesStore((state) => state.favorites);
18
- const isFavorite = favoriteList.find((fav) => fav.id === id);
19
- const [documentData, setDocumentData] = useState<{ id: string, label: string }>({ id, label });
20
-
21
- useEffect(() => {
22
- if (type === RESULT_TYPES.TOPIC) {
23
- getTopicDocumentData(id);
24
- }
25
- }, []);
26
-
27
- const addFavorite = async (id: string) => {
28
- if (type === RESULT_TYPES.DOCUMENT) {
29
- addFavoriteDocument(id, label);
30
- return;
31
- }
32
-
33
- const length = favoriteDocumentList[documentData.id]?.topics.length || 0;
34
- const color = MARKER_COLORS[length] || MARKER_COLORS[MARKER_COLORS.length - 1] as string;
35
-
36
- addFavoriteTopic(documentData.id, id, label, color);
37
- }
38
-
39
- const removeFavorite = (id: string) => {
40
- if (type === RESULT_TYPES.DOCUMENT) {
41
- removeFavoriteDocument(id);
42
- return;
43
- }
44
-
45
- removeFavoriteTopic(documentData.id, id);
46
- }
47
-
48
- const getTopicDocumentData = async (topicId: string): Promise<void> => {
49
-
50
- const response = await fetch(`/api/information-units/document-by-topic-id?shortId=${topicId}`, {
51
- method: "GET"
52
- });
53
-
54
- if (!response.ok) throw new Error("Failed to fetch document by topic id")
55
-
56
- const { documentId, label } = await response.json();
57
-
58
- setDocumentData({ id: documentId, label });
59
- }
60
-
61
- if (isFavorite) {
62
- return (
63
- <Button variant="ghost" size="icon" onClick={() => removeFavorite(id)}>
64
- <FaStar className="!h-5 !w-5 color-primary" />
65
- </Button>
66
- );
67
- }
68
-
69
- return (
70
- <Button variant="ghost" size="icon" onClick={() => addFavorite(id)}>
71
- <FaRegStar className="!h-5 !w-5" />
72
- </Button>
73
- )
74
- }
1
+ "use client";
2
+
3
+ import { FC, useEffect, useState } from "react";
4
+ import { Button } from "@c-rex/ui/button";
5
+ import { FaStar, FaRegStar } from "react-icons/fa";
6
+ import { useFavoritesStore } from "../stores/favorites-store";
7
+ import { MARKER_COLORS, RESULT_TYPES } from "@c-rex/constants";
8
+ import { ResultTypes } from "@c-rex/types";
9
+ import { Loader2 } from "lucide-react";
10
+ import { toast } from "sonner";
11
+
12
+ export const FavoriteButton: FC<{
13
+ id: string;
14
+ type: ResultTypes;
15
+ label: string;
16
+ topicDocumentId?: string;
17
+ topicDocumentLabel?: string;
18
+ }> = ({ id, type, label, topicDocumentId, topicDocumentLabel }) => {
19
+ const addFavoriteTopic = useFavoritesStore((state) => state.favoriteTopic);
20
+ const addFavoriteDocument = useFavoritesStore((state) => state.favoriteDocument);
21
+ const removeFavoriteTopic = useFavoritesStore((state) => state.unfavoriteTopic);
22
+ const removeFavoriteDocument = useFavoritesStore((state) => state.unfavoriteDocument);
23
+
24
+ const favoriteDocumentList = useFavoritesStore((state) => state.documents);
25
+ const favoriteList = useFavoritesStore((state) => state.favorites);
26
+ const isFavorite = favoriteList.find((fav) => fav.id === id);
27
+ const [documentData, setDocumentData] = useState<{ id: string, label: string }>({
28
+ id: topicDocumentId || id,
29
+ label: topicDocumentLabel || label,
30
+ });
31
+ const [isLoading, setIsLoading] = useState(false);
32
+
33
+ useEffect(() => {
34
+ if (type !== RESULT_TYPES.TOPIC || !topicDocumentId) return;
35
+ setDocumentData({
36
+ id: topicDocumentId,
37
+ label: topicDocumentLabel || label,
38
+ });
39
+ }, [type, topicDocumentId, topicDocumentLabel, label]);
40
+
41
+ useEffect(() => {
42
+ if (type === RESULT_TYPES.TOPIC && !topicDocumentId) {
43
+ void getTopicDocumentData(id).catch(() => {
44
+ // Lazy retry on user action.
45
+ });
46
+ }
47
+ }, [id, type, topicDocumentId]);
48
+
49
+ const ensureDocumentData = async (topicId: string): Promise<{ id: string, label: string }> => {
50
+ if (type !== RESULT_TYPES.TOPIC) {
51
+ return { id, label };
52
+ }
53
+
54
+ if (documentData.id !== topicId) {
55
+ return documentData;
56
+ }
57
+
58
+ return await getTopicDocumentData(topicId);
59
+ };
60
+
61
+ const addFavorite = async (targetId: string) => {
62
+ if (type === RESULT_TYPES.DOCUMENT) {
63
+ addFavoriteDocument(targetId, label);
64
+ return;
65
+ }
66
+
67
+ const docData = await ensureDocumentData(targetId);
68
+ const length = favoriteDocumentList[docData.id]?.topics.length || 0;
69
+ const color = MARKER_COLORS[length] || MARKER_COLORS[MARKER_COLORS.length - 1] as string;
70
+
71
+ addFavoriteTopic(docData.id, targetId, label, color);
72
+ };
73
+
74
+ const removeFavorite = async (targetId: string) => {
75
+ if (type === RESULT_TYPES.DOCUMENT) {
76
+ removeFavoriteDocument(targetId);
77
+ return;
78
+ }
79
+
80
+ const docData = await ensureDocumentData(targetId);
81
+ removeFavoriteTopic(docData.id, targetId);
82
+ };
83
+
84
+ const getTopicDocumentData = async (topicId: string): Promise<{ id: string, label: string }> => {
85
+ const response = await fetch(`/api/information-units/document-by-topic-id?shortId=${topicId}`, {
86
+ method: "GET"
87
+ });
88
+
89
+ if (!response.ok) {
90
+ throw new Error("Failed to fetch document by topic id");
91
+ }
92
+
93
+ const { documentId, label } = await response.json();
94
+ const data = { id: documentId, label };
95
+ setDocumentData(data);
96
+ return data;
97
+ };
98
+
99
+ const handleToggle = async () => {
100
+ if (isLoading) return;
101
+ setIsLoading(true);
102
+
103
+ try {
104
+ if (isFavorite) {
105
+ await removeFavorite(id);
106
+ } else {
107
+ await addFavorite(id);
108
+ }
109
+ } catch {
110
+ toast.error("Não foi possível atualizar os favoritos.");
111
+ } finally {
112
+ setIsLoading(false);
113
+ }
114
+ };
115
+
116
+ if (isFavorite) {
117
+ return (
118
+ <Button variant="ghost" size="icon" onClick={handleToggle} disabled={isLoading}>
119
+ {isLoading ? (
120
+ <Loader2 className="!h-5 !w-5 animate-spin" />
121
+ ) : (
122
+ <FaStar className="!h-5 !w-5 color-primary" />
123
+ )}
124
+ </Button>
125
+ );
126
+ }
127
+
128
+ return (
129
+ <Button variant="ghost" size="icon" onClick={handleToggle} disabled={isLoading}>
130
+ {isLoading ? (
131
+ <Loader2 className="!h-5 !w-5 animate-spin" />
132
+ ) : (
133
+ <FaRegStar className="!h-5 !w-5" />
134
+ )}
135
+ </Button>
136
+ )
137
+ }