@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.
- package/README.md +73 -73
- package/package.json +250 -235
- package/src/article/article-action-bar.tsx +110 -110
- package/src/article/article-content.tsx +18 -46
- package/src/autocomplete.tsx +201 -201
- package/src/breadcrumb.tsx +124 -124
- package/src/carousel/carousel.tsx +353 -352
- package/src/check-article-lang.tsx +47 -43
- package/src/directoryNodes/directory-tree-context.tsx +388 -0
- package/src/directoryNodes/tree-of-content.tsx +68 -67
- package/src/documents/result-list.tsx +124 -127
- package/src/favorites/bookmark-button.tsx +97 -79
- package/src/favorites/favorite-button.tsx +137 -74
- package/src/footer/footer-shell.tsx +52 -0
- package/src/footer/footer.tsx +7 -0
- package/src/footer/legal-links-block.tsx +25 -0
- package/src/footer/organization-contact-block.tsx +94 -0
- package/src/footer/social-links-block.tsx +38 -0
- package/src/footer/types.ts +10 -0
- package/src/footer/vcard-footer.tsx +72 -0
- package/src/generated/client-components.tsx +1366 -1350
- package/src/generated/create-client-request.tsx +116 -113
- package/src/generated/create-server-request.tsx +70 -61
- package/src/generated/create-suggestions-request.tsx +55 -55
- package/src/generated/server-components.tsx +1056 -1056
- package/src/generated/suggestions.tsx +302 -299
- package/src/icons/file-icon.tsx +8 -8
- package/src/icons/flag-icon.tsx +15 -15
- package/src/icons/loading.tsx +11 -11
- package/src/icons/social-icon.tsx +24 -0
- package/src/info/info-card.tsx +43 -0
- package/src/info/{info-table.tsx → information-unit-metadata-grid.tsx} +157 -146
- package/src/info/shared.tsx +49 -25
- package/src/navbar/language-switcher/content-language-switch.tsx +92 -92
- package/src/navbar/language-switcher/shared.tsx +33 -33
- package/src/navbar/language-switcher/ui-language-switch.tsx +37 -38
- package/src/navbar/navbar.tsx +157 -148
- package/src/navbar/settings.tsx +62 -62
- package/src/navbar/sign-in-out-btns.tsx +35 -35
- package/src/navbar/user-menu.tsx +60 -60
- package/src/page-wrapper.tsx +54 -31
- package/src/render-article.module.css +155 -0
- package/src/render-article.tsx +75 -68
- package/src/renditions/file-download.tsx +83 -83
- package/src/renditions/html.tsx +64 -64
- package/src/renditions/image/container.tsx +54 -54
- package/src/renditions/image/rendition.tsx +55 -55
- package/src/restriction-menu/restriction-menu-container.tsx +117 -53
- package/src/restriction-menu/restriction-menu-item.tsx +155 -147
- package/src/restriction-menu/restriction-menu.tsx +341 -157
- package/src/results/dialog-filter.tsx +166 -166
- package/src/results/empty.tsx +15 -15
- package/src/results/filter-navbar.tsx +294 -261
- package/src/results/filter-sidebar/__tests__/utils.test.ts +129 -0
- package/src/results/filter-sidebar/index.tsx +270 -126
- package/src/results/filter-sidebar/utils.ts +196 -164
- package/src/results/generic/table-result-list.tsx +97 -99
- package/src/results/{table-with-images.tsx → information-unit-search-results-card-list.tsx} +125 -127
- package/src/results/{cards.tsx → information-unit-search-results-cards.tsx} +99 -99
- package/src/results/{table.tsx → information-unit-search-results-table.tsx} +104 -104
- package/src/results/pagination.tsx +81 -81
- package/src/results/summary.ts +30 -0
- package/src/results/utils.ts +54 -47
- package/src/search-input.tsx +70 -70
- package/src/share-button.tsx +49 -49
- package/src/stores/favorites-store.ts +88 -88
- package/src/stores/highlight-store.ts +15 -15
- package/src/stores/language-store.ts +14 -43
- package/src/stores/restriction-store.ts +11 -11
- package/src/stores/search-settings-store.ts +68 -64
- package/src/article/article-action-bar.analysis.md +0 -15
- package/src/article/article-action-bar.stories.tsx +0 -15
- package/src/article/article-content.analysis.md +0 -15
- package/src/article/article-content.stories.tsx +0 -21
- package/src/autocomplete.analysis.md +0 -17
- package/src/breadcrumb.analysis.md +0 -15
- package/src/carousel/carousel.analysis.md +0 -17
- package/src/check-article-lang.analysis.md +0 -15
- package/src/directoryNodes/tree-of-content.analysis.md +0 -14
- package/src/directoryNodes/tree-of-content.stories.tsx +0 -22
- package/src/documents/result-list.analysis.md +0 -14
- package/src/documents/result-list.stories.tsx +0 -19
- package/src/favorites/bookmark-button.analysis.md +0 -17
- package/src/favorites/bookmark-button.stories.tsx +0 -19
- package/src/favorites/favorite-button.analysis.md +0 -18
- package/src/favorites/favorite-button.stories.tsx +0 -22
- package/src/icons/file-icon.analysis.md +0 -14
- package/src/icons/file-icon.stories.tsx +0 -19
- package/src/icons/flag-icon.analysis.md +0 -14
- package/src/icons/flag-icon.stories.tsx +0 -25
- package/src/icons/loading.analysis.md +0 -14
- package/src/icons/loading.stories.tsx +0 -21
- package/src/info/info-table.analysis.md +0 -15
- package/src/info/shared.analysis.md +0 -14
- package/src/info/stories/info-table.stories.tsx +0 -31
- package/src/info/stories/shared.stories.tsx +0 -24
- package/src/navbar/language-switcher/content-language-switch.analysis.md +0 -15
- package/src/navbar/language-switcher/shared.analysis.md +0 -14
- package/src/navbar/language-switcher/ui-language-switch.analysis.md +0 -15
- package/src/navbar/navbar.analysis.md +0 -14
- package/src/navbar/settings.analysis.md +0 -14
- package/src/navbar/sign-in-out-btns.analysis.md +0 -14
- package/src/navbar/stories/navbar.stories.tsx +0 -31
- package/src/navbar/stories/settings.stories.tsx +0 -15
- package/src/navbar/stories/sign-in-out-btns.stories.tsx +0 -15
- package/src/navbar/stories/user-menu.stories.tsx +0 -20
- package/src/navbar/user-menu.analysis.md +0 -14
- package/src/page-wrapper.analysis.md +0 -14
- package/src/render-article.analysis.md +0 -15
- package/src/renditions/file-download.analysis.md +0 -14
- package/src/renditions/file-download.stories.tsx +0 -19
- package/src/renditions/html.analysis.md +0 -17
- package/src/renditions/html.stories.tsx +0 -19
- package/src/renditions/image/container.analysis.md +0 -15
- package/src/renditions/image/container.stories.tsx +0 -19
- package/src/renditions/image/rendition.analysis.md +0 -14
- package/src/renditions/image/rendition.stories.tsx +0 -19
- package/src/restriction-menu/restriction-menu-container.analysis.md +0 -14
- package/src/restriction-menu/restriction-menu-item.analysis.md +0 -14
- package/src/restriction-menu/restriction-menu.analysis.md +0 -17
- package/src/results/analysis/cards.analysis.md +0 -14
- package/src/results/analysis/dialog-filter.analysis.md +0 -17
- package/src/results/analysis/empty.analysis.md +0 -14
- package/src/results/analysis/filter-navbar.analysis.md +0 -16
- package/src/results/analysis/pagination.analysis.md +0 -14
- package/src/results/analysis/table-with-images.analysis.md +0 -15
- package/src/results/analysis/table.analysis.md +0 -15
- package/src/results/filter-sidebar/index.analysis.md +0 -14
- package/src/results/generic/table-result-list.analysis.md +0 -15
- package/src/results/generic/table-result-list.stories.tsx +0 -21
- package/src/results/stories/cards.stories.tsx +0 -66
- package/src/results/stories/dialog-filter.stories.tsx +0 -20
- package/src/results/stories/empty.stories.tsx +0 -25
- package/src/results/stories/filter-navbar.stories.tsx +0 -19
- package/src/results/stories/filter-sidebar.stories.tsx +0 -20
- package/src/results/stories/pagination.stories.tsx +0 -24
- package/src/results/stories/table-with-images.stories.tsx +0 -19
- package/src/results/stories/table.stories.tsx +0 -78
- package/src/search-input.analysis.md +0 -15
- package/src/share-button.analysis.md +0 -19
- package/src/stories/autocomplete.stories.tsx +0 -20
- package/src/stories/breadcrumb.stories.tsx +0 -93
- package/src/stories/check-article-lang.stories.tsx +0 -22
- package/src/stories/render-article.stories.tsx +0 -19
- package/src/stories/search-input.stories.tsx +0 -21
- 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,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} © {year}
|
|
67
|
+
</>
|
|
68
|
+
}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
};
|