@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
@@ -0,0 +1,52 @@
1
+ import { HTMLAttributes, ReactNode } from "react";
2
+ import { cn } from "@c-rex/utils";
3
+
4
+ type FooterShellProps = {
5
+ left: ReactNode;
6
+ middle?: ReactNode;
7
+ right?: ReactNode;
8
+ bottom?: ReactNode;
9
+ className?: HTMLAttributes<HTMLElement>["className"];
10
+ leftClassName?: HTMLAttributes<HTMLElement>["className"];
11
+ middleClassName?: HTMLAttributes<HTMLElement>["className"];
12
+ rightClassName?: HTMLAttributes<HTMLElement>["className"];
13
+ bottomClassName?: HTMLAttributes<HTMLElement>["className"];
14
+ };
15
+
16
+ /**
17
+ * Structural footer shell with left/middle/right content areas and an optional bottom row.
18
+ */
19
+ export const FooterShell = ({
20
+ left,
21
+ middle,
22
+ right,
23
+ bottom,
24
+ className,
25
+ leftClassName,
26
+ middleClassName,
27
+ rightClassName,
28
+ bottomClassName,
29
+ }: FooterShellProps) => {
30
+ return (
31
+ <footer className={cn(
32
+ "w-full bg-primary flex items-center flex-col text-primary-foreground",
33
+ className
34
+ )}>
35
+ <div className="container py-24 flex flex-row justify-between gap-8">
36
+ <div className={cn("space-y-4", leftClassName)}>{left}</div>
37
+
38
+ {middle && <div className={cn("space-y-4", middleClassName)}>{middle}</div>}
39
+
40
+ {right && <div className={cn("space-y-4 md:justify-self-end", rightClassName)}>{right}</div>}
41
+ </div>
42
+
43
+ {bottom && (
44
+ <div className={cn(bottomClassName, "w-full border-t flex justify-center p-4 text-xs border-primary-foreground")}>
45
+
46
+ {bottom}
47
+
48
+ </div>
49
+ )}
50
+ </footer>
51
+ );
52
+ };
@@ -0,0 +1,7 @@
1
+ export { VCardFooter, VCardFooter as Footer } from "./vcard-footer";
2
+ export type { VCardFooterProps } from "./vcard-footer";
3
+ export { FooterShell } from "./footer-shell";
4
+ export { OrganizationContactBlock } from "./organization-contact-block";
5
+ export { SocialLinksBlock } from "./social-links-block";
6
+ export { LegalLinksBlock } from "./legal-links-block";
7
+ export type { FooterLegalLink, FooterSocialLink } from "./types";
@@ -0,0 +1,25 @@
1
+ import Link from "next/link";
2
+ import type { FooterLegalLink } from "./types";
3
+ import { cn } from "@c-rex/utils";
4
+
5
+ type LegalLinksBlockProps = {
6
+ links: FooterLegalLink[];
7
+ className?: string;
8
+ };
9
+
10
+ /**
11
+ * Footer block for legal/imprint/privacy links.
12
+ */
13
+ export const LegalLinksBlock = ({ links, className }: LegalLinksBlockProps) => {
14
+ if (links.length === 0) return null;
15
+
16
+ return (
17
+ <div className={cn("flex gap-4 text-sm text-muted-foreground", className)}>
18
+ {links.map((link) => (
19
+ <Link key={`${link.href}-${link.label}`} href={link.href} className="hover:underline">
20
+ {link.label}
21
+ </Link>
22
+ ))}
23
+ </div>
24
+ );
25
+ };
@@ -0,0 +1,94 @@
1
+ import type { OrganizationProfile } from "@c-rex/services/vcard";
2
+ import { cn } from "@c-rex/utils";
3
+ import { getTranslations } from "next-intl/server";
4
+
5
+ type OrganizationContactBlockProps = {
6
+ profile: OrganizationProfile;
7
+ className?: string;
8
+ headerClassName?: string;
9
+ detailsClassName?: string;
10
+ showOptionalLabels?: boolean;
11
+ showLogo?: boolean;
12
+ };
13
+
14
+ /**
15
+ * Footer block that renders organization identity and contact details.
16
+ */
17
+ export const OrganizationContactBlock = async ({
18
+ profile,
19
+ className,
20
+ headerClassName,
21
+ detailsClassName,
22
+ showOptionalLabels = false,
23
+ showLogo = true,
24
+ }: OrganizationContactBlockProps) => {
25
+ const t = await getTranslations();
26
+ const shouldRenderAddressLabel = showOptionalLabels && profile.addressLines.length > 0;
27
+ const sectionAriaLabel = t("footer.contact.sectionAriaLabel", {
28
+ organizationName: profile.organizationName,
29
+ });
30
+ const logoAlt = t("footer.contact.logoAlt", {
31
+ organizationName: profile.organizationName,
32
+ });
33
+
34
+ return (
35
+ <section className={className} aria-label={sectionAriaLabel}>
36
+ <div className={cn("flex items-center gap-3", headerClassName)}>
37
+ {showLogo && (
38
+ <img
39
+ src={profile.logoSrc}
40
+ alt={logoAlt}
41
+ className="h-8 w-auto object-contain sm:h-10"
42
+ />
43
+ )}
44
+ <h3 className="text-lg font-semibold sm:text-xl">{profile.organizationName}</h3>
45
+ </div>
46
+
47
+ <div className={cn("space-y-2 text-sm", detailsClassName)}>
48
+ {shouldRenderAddressLabel && <p className="font-medium">{t("footer.contact.address")}</p>}
49
+ {profile.addressLines.length > 0 && (
50
+ <address className="not-italic">
51
+ {profile.addressLines.map((line, index) => (
52
+ <p key={`${line}-${index}`}>{line}</p>
53
+ ))}
54
+ </address>
55
+ )}
56
+
57
+ {profile.telephoneEntries.length > 0 && (
58
+ <ul className="space-y-1">
59
+ {profile.telephoneEntries.map((entry, index) => (
60
+ <li key={`${entry.value}-${index}`}>
61
+ <span className="font-medium">{entry.label || t("footer.contact.phone")}:</span>{" "}
62
+ {entry.href ? (
63
+ <a
64
+ className="underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
65
+ href={entry.href}
66
+ aria-label={`${entry.label || t("footer.contact.phone")}: ${entry.value}`}
67
+ >
68
+ {entry.value}
69
+ </a>
70
+ ) : (
71
+ <span aria-label={`${entry.label || t("footer.contact.phone")}: ${entry.value}`}>{entry.value}</span>
72
+ )}
73
+ </li>
74
+ ))}
75
+ </ul>
76
+ )}
77
+
78
+ {profile.email && (
79
+ <p>
80
+ {showOptionalLabels && <span className="font-medium">{t("footer.contact.email")}:</span>}
81
+ {showOptionalLabels && " "}
82
+ <a
83
+ className="underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
84
+ href={`mailto:${profile.email}`}
85
+ aria-label={`${t("footer.contact.email")}: ${profile.email}`}
86
+ >
87
+ {profile.email}
88
+ </a>
89
+ </p>
90
+ )}
91
+ </div>
92
+ </section>
93
+ );
94
+ };
@@ -0,0 +1,38 @@
1
+ import type { FooterSocialLink } from "./types";
2
+ import { cn } from "@c-rex/utils";
3
+ import { SocialIcon } from "../icons/social-icon";
4
+
5
+ type SocialLinksBlockProps = {
6
+ links: FooterSocialLink[];
7
+ className?: string;
8
+ ariaLabel?: string;
9
+ };
10
+
11
+ /**
12
+ * Footer block for social media links.
13
+ */
14
+ export const SocialLinksBlock = ({ links, className, ariaLabel }: SocialLinksBlockProps) => {
15
+ if (links.length === 0) return null;
16
+
17
+ return (
18
+ <nav aria-label={ariaLabel}>
19
+ <ul className={cn("flex flex-wrap gap-4 text-sm", className)}>
20
+ {links.map((link, index) => {
21
+ return (
22
+ <li key={`${link.href}-${index}`}>
23
+ <a
24
+ href={link.href}
25
+ target="_blank"
26
+ rel="noreferrer"
27
+ title={link.label}
28
+ className="underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
29
+ >
30
+ <SocialIcon label={link.label} className="size-7" />
31
+ </a>
32
+ </li>
33
+ );
34
+ })}
35
+ </ul>
36
+ </nav>
37
+ );
38
+ };
@@ -0,0 +1,10 @@
1
+ export type FooterSocialLink = {
2
+ label: string;
3
+ href: string;
4
+ };
5
+
6
+ export type FooterLegalLink = {
7
+ label: string;
8
+ href: string;
9
+ };
10
+
@@ -0,0 +1,72 @@
1
+ import { ComponentProps } from "react";
2
+ import { getOrganizationProfile } from "@c-rex/services/vcard";
3
+ import { FooterShell } from "./footer-shell";
4
+ import { OrganizationContactBlock } from "./organization-contact-block";
5
+ import { SocialLinksBlock } from "./social-links-block";
6
+ import { getTranslations } from "next-intl/server";
7
+
8
+ /**
9
+ * Footer composition that is backed by VCARD profile data plus configured social links.
10
+ */
11
+ export type VCardFooterProps = {
12
+ contactClassName?: string;
13
+ socialClassName?: string;
14
+ showOrganizationLogo?: boolean;
15
+ } & Omit<ComponentProps<typeof FooterShell>, "left" | "right" | "bottom">;
16
+
17
+ /**
18
+ * VCARD-based footer composition using organization profile and social/website blocks.
19
+ * Exposes className hooks to avoid copy/paste for common customer-specific styling changes.
20
+ */
21
+ export const VCardFooter = async ({
22
+ contactClassName,
23
+ socialClassName,
24
+ showOrganizationLogo = true,
25
+ ...props
26
+ }: VCardFooterProps = {}) => {
27
+ const t = await getTranslations();
28
+ const profile = await getOrganizationProfile();
29
+ const year = new Date().getFullYear();
30
+
31
+ return (
32
+ <FooterShell
33
+ left={
34
+ <OrganizationContactBlock
35
+ profile={profile}
36
+ className={contactClassName}
37
+ showLogo={showOrganizationLogo}
38
+ />
39
+ }
40
+ right={
41
+ <>
42
+ {profile.website && (
43
+ <p className="text-sm">
44
+ <a
45
+ href={profile.website}
46
+ target="_blank"
47
+ rel="noreferrer"
48
+ className="underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
49
+ aria-label={t("footer.website.visitAriaLabel", {
50
+ organizationName: profile.organizationName,
51
+ })}
52
+ >
53
+ {t("footer.website.visit")}
54
+ </a>
55
+ </p>
56
+ )}
57
+ <SocialLinksBlock
58
+ links={profile.socialLinks}
59
+ className={socialClassName}
60
+ ariaLabel={t("footer.social.ariaLabel")}
61
+ />
62
+ </>
63
+ }
64
+ bottom={
65
+ <>
66
+ {profile.organizationName} &copy; {year}
67
+ </>
68
+ }
69
+ {...props}
70
+ />
71
+ );
72
+ };