@c-rex/components 0.3.0-build.28 → 0.3.0-build.30
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 +34 -2
- package/src/carousel/carousel.tsx +70 -74
- package/src/carousel/information-unit-carousel-item.tsx +85 -0
- package/src/directoryNodes/directory-tree-context.tsx +3 -1
- package/src/documents/description-preview.tsx +132 -0
- package/src/documents/result-list-item.tsx +256 -0
- package/src/documents/result-list.tsx +13 -132
- package/src/info/information-unit-fragment-ids.ts +28 -0
- package/src/info/information-unit-metadata-grid-client.tsx +368 -0
- package/src/info/information-unit-preview-image.tsx +77 -0
- package/src/page-wrapper.tsx +5 -5
- package/src/renditions/file-download.tsx +5 -3
- package/src/renditions/html-client.tsx +99 -0
- package/src/renditions/image/container.tsx +19 -13
- package/src/renditions/image/rendition.tsx +6 -1
- package/src/restriction-menu/restriction-menu-container.tsx +4 -117
- package/src/restriction-menu/restriction-menu.tsx +4 -381
- package/src/restriction-menu/restriction-selection-menu.tsx +383 -0
- package/src/restriction-menu/taxonomy-restriction-menu.tsx +114 -0
- package/src/results/generic/search-results-client.tsx +258 -0
- package/src/results/generic/table-result-list.tsx +5 -3
|
@@ -1,117 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import * as ComponentOptions from "../generated/client-components";
|
|
6
|
-
import { Skeleton } from "@c-rex/ui/skeleton";
|
|
7
|
-
import { RestrictionMenu } from "./restriction-menu";
|
|
8
|
-
|
|
9
|
-
type GenericRequestData = {
|
|
10
|
-
items?: DomainEntityModel[];
|
|
11
|
-
pageInfo?: {
|
|
12
|
-
totalItemCount?: number;
|
|
13
|
-
};
|
|
14
|
-
} | null | undefined;
|
|
15
|
-
type GenericQueryParams = Record<string, unknown> & { Restrict?: string[]; PageSize?: number; Fields?: string[] };
|
|
16
|
-
type GenericRequestProps = {
|
|
17
|
-
queryParams?: GenericQueryParams;
|
|
18
|
-
children: (props: { data?: GenericRequestData; isLoading?: boolean }) => ReactNode;
|
|
19
|
-
};
|
|
20
|
-
type RestrictionMenuFetchMode = "all" | "deferred";
|
|
21
|
-
|
|
22
|
-
type Props = {
|
|
23
|
-
restrictField: string
|
|
24
|
-
navigationMenuListClassName?: string
|
|
25
|
-
itemsToRender?: number
|
|
26
|
-
requestType: keyof typeof ComponentOptions
|
|
27
|
-
onlyUsedEntries?: boolean
|
|
28
|
-
enableHierarchy?: boolean
|
|
29
|
-
fetchMode?: RestrictionMenuFetchMode
|
|
30
|
-
showAllWhenEmpty?: boolean
|
|
31
|
-
queryParams?: GenericQueryParams
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const RestrictionMenuContainer: FC<Props> = ({
|
|
35
|
-
queryParams,
|
|
36
|
-
restrictField,
|
|
37
|
-
itemsToRender = 7,
|
|
38
|
-
requestType,
|
|
39
|
-
onlyUsedEntries = true,
|
|
40
|
-
enableHierarchy = false,
|
|
41
|
-
fetchMode = "deferred",
|
|
42
|
-
showAllWhenEmpty = true,
|
|
43
|
-
navigationMenuListClassName = "items-center justify-between flex-row",
|
|
44
|
-
}) => {
|
|
45
|
-
const [loadAll, setLoadAll] = useState(false);
|
|
46
|
-
const RequestComponent = ComponentOptions[requestType] as unknown as FC<GenericRequestProps>;
|
|
47
|
-
const queryRestrict = queryParams?.Restrict || [];
|
|
48
|
-
const restrict = onlyUsedEntries ? ["hasInformationUnits=true", ...queryRestrict] : queryRestrict;
|
|
49
|
-
const explicitPageSize =
|
|
50
|
-
Number.isFinite(Number(queryParams?.PageSize)) && Number(queryParams?.PageSize) > 0
|
|
51
|
-
? Number(queryParams?.PageSize)
|
|
52
|
-
: undefined;
|
|
53
|
-
const resolvedPageSize = useMemo(() => {
|
|
54
|
-
if (explicitPageSize) return explicitPageSize;
|
|
55
|
-
// TODO(UI/Gabriel): `deferred` currently behaves like "initial subset + load all on first More click".
|
|
56
|
-
// Keep this behavior for now; planned follow-up is incremental loading from inside the opened More dropdown.
|
|
57
|
-
if (fetchMode === "deferred" && !loadAll) return Math.max(itemsToRender, 1);
|
|
58
|
-
return 100;
|
|
59
|
-
}, [explicitPageSize, fetchMode, itemsToRender, loadAll]);
|
|
60
|
-
const requestedFields = Array.isArray(queryParams?.Fields) ? queryParams.Fields : undefined;
|
|
61
|
-
const resolvedFields = useMemo(() => {
|
|
62
|
-
const baseFields = requestedFields && requestedFields.length > 0 ? requestedFields : ["labels"];
|
|
63
|
-
if (!enableHierarchy) return baseFields;
|
|
64
|
-
const withParents = new Set(baseFields);
|
|
65
|
-
withParents.add("parents");
|
|
66
|
-
return Array.from(withParents);
|
|
67
|
-
}, [enableHierarchy, requestedFields]);
|
|
68
|
-
|
|
69
|
-
return (
|
|
70
|
-
<RequestComponent
|
|
71
|
-
queryParams={{
|
|
72
|
-
...queryParams,
|
|
73
|
-
Fields: resolvedFields,
|
|
74
|
-
Links: true,
|
|
75
|
-
Sort: ["labels.value"],
|
|
76
|
-
PageSize: resolvedPageSize,
|
|
77
|
-
Restrict: restrict,
|
|
78
|
-
}}
|
|
79
|
-
>
|
|
80
|
-
{({ data, isLoading }) => {
|
|
81
|
-
|
|
82
|
-
if (isLoading) return (
|
|
83
|
-
<div className="flex justify-between">
|
|
84
|
-
<Skeleton className="w-12 h-9 rounded-full" />
|
|
85
|
-
{Array(itemsToRender).fill(0).map((_, index) => (
|
|
86
|
-
<Skeleton key={`skeleton-${index}`} className="w-28 h-9 rounded-full" />
|
|
87
|
-
))}
|
|
88
|
-
<Skeleton className="w-20 h-9 rounded-full" />
|
|
89
|
-
</div>
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
if (!data) return null;
|
|
93
|
-
|
|
94
|
-
const itemCount = data.items?.length || 0;
|
|
95
|
-
const totalItemCount = data.pageInfo?.totalItemCount;
|
|
96
|
-
const hasMoreFromServer = typeof totalItemCount === "number" ? totalItemCount > itemCount : false;
|
|
97
|
-
const hasMoreItems = hasMoreFromServer || (explicitPageSize ? false : (fetchMode === "deferred" && !loadAll && itemCount >= resolvedPageSize));
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<RestrictionMenu
|
|
101
|
-
restrictField={restrictField}
|
|
102
|
-
items={data.items || []}
|
|
103
|
-
enableHierarchy={enableHierarchy}
|
|
104
|
-
hasMoreItems={hasMoreItems}
|
|
105
|
-
showAllWhenEmpty={showAllWhenEmpty}
|
|
106
|
-
onRequestMore={() => {
|
|
107
|
-
if (fetchMode === "deferred" && !explicitPageSize) {
|
|
108
|
-
setLoadAll(true);
|
|
109
|
-
}
|
|
110
|
-
}}
|
|
111
|
-
navigationMenuListClassName={navigationMenuListClassName}
|
|
112
|
-
/>
|
|
113
|
-
)
|
|
114
|
-
}}
|
|
115
|
-
</RequestComponent>
|
|
116
|
-
);
|
|
117
|
-
};
|
|
1
|
+
export {
|
|
2
|
+
TaxonomyRestrictionMenu as RestrictionMenuContainer,
|
|
3
|
+
type TaxonomyRestrictionMenuProps as RestrictionMenuContainerProps,
|
|
4
|
+
} from "./taxonomy-restriction-menu";
|
|
@@ -1,381 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
NavigationMenu,
|
|
6
|
-
NavigationMenuList,
|
|
7
|
-
NavigationMenuItem,
|
|
8
|
-
NavigationMenuTrigger,
|
|
9
|
-
NavigationMenuContent,
|
|
10
|
-
} from "@c-rex/ui/navigation-menu";
|
|
11
|
-
import { RestrictionDropdownItem, RestrictionNavigationItem } from "./restriction-menu-item";
|
|
12
|
-
import { parseAsString, useQueryStates } from "nuqs";
|
|
13
|
-
import { DomainEntityModel, ObjectRefModel } from "@c-rex/interfaces";
|
|
14
|
-
import { useLocale, useTranslations } from 'next-intl'
|
|
15
|
-
import { cn, getLabelByLang } from "@c-rex/utils";
|
|
16
|
-
import { useRestrictionStore } from "../stores/restriction-store";
|
|
17
|
-
import { useBreakpoint } from "@c-rex/ui/hooks";
|
|
18
|
-
import { DEVICE_OPTIONS } from "@c-rex/constants";
|
|
19
|
-
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
type Props = {
|
|
23
|
-
restrictField: string
|
|
24
|
-
navigationMenuListClassName?: string
|
|
25
|
-
items: DomainEntityModel[],
|
|
26
|
-
enableHierarchy?: boolean,
|
|
27
|
-
hasMoreItems?: boolean,
|
|
28
|
-
showAllWhenEmpty?: boolean,
|
|
29
|
-
onRequestMore?: () => void,
|
|
30
|
-
itemsByRow?: {
|
|
31
|
-
[DEVICE_OPTIONS.MOBILE]: number,
|
|
32
|
-
[DEVICE_OPTIONS.TABLET]: number,
|
|
33
|
-
[DEVICE_OPTIONS.DESKTOP]: number,
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
type RestrictionTreeNode = {
|
|
38
|
-
item: DomainEntityModel;
|
|
39
|
-
children: RestrictionTreeNode[];
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const hasSelectedDescendant = (node: RestrictionTreeNode, selectedShortIds: Set<string>): boolean => {
|
|
43
|
-
const shortId = node.item.shortId;
|
|
44
|
-
if (shortId && selectedShortIds.has(shortId)) {
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return node.children.some((child) => hasSelectedDescendant(child, selectedShortIds));
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const getNodeKeys = (item: DomainEntityModel): string[] => {
|
|
52
|
-
const keys = new Set<string>();
|
|
53
|
-
if (item.shortId) keys.add(`short:${item.shortId}`);
|
|
54
|
-
if (item.id) keys.add(`id:${item.id}`);
|
|
55
|
-
return Array.from(keys);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const extractParentKeys = (item: DomainEntityModel): string[] => {
|
|
59
|
-
const withParents = item as DomainEntityModel & { parents?: ObjectRefModel[] | null };
|
|
60
|
-
return (withParents.parents || [])
|
|
61
|
-
.flatMap((parent) => {
|
|
62
|
-
const keys: string[] = [];
|
|
63
|
-
if (parent.shortId) keys.push(`short:${parent.shortId}`);
|
|
64
|
-
if (parent.id) keys.push(`id:${parent.id}`);
|
|
65
|
-
return keys;
|
|
66
|
-
});
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const buildRestrictionTree = (items: DomainEntityModel[]): RestrictionTreeNode[] => {
|
|
70
|
-
const nodes = new Map<string, RestrictionTreeNode>();
|
|
71
|
-
items.forEach((item) => {
|
|
72
|
-
const keys = getNodeKeys(item);
|
|
73
|
-
if (keys.length === 0) return;
|
|
74
|
-
const node: RestrictionTreeNode = { item, children: [] };
|
|
75
|
-
keys.forEach((key) => nodes.set(key, node));
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
const roots: RestrictionTreeNode[] = [];
|
|
79
|
-
const linked = new Set<RestrictionTreeNode>();
|
|
80
|
-
const rootSet = new Set<RestrictionTreeNode>();
|
|
81
|
-
|
|
82
|
-
items.forEach((item) => {
|
|
83
|
-
const [firstKey] = getNodeKeys(item);
|
|
84
|
-
if (!firstKey) return;
|
|
85
|
-
const node = nodes.get(firstKey);
|
|
86
|
-
if (!node) return;
|
|
87
|
-
|
|
88
|
-
const parentKeys = extractParentKeys(item);
|
|
89
|
-
const parentNode = parentKeys
|
|
90
|
-
.map((parentKey) => nodes.get(parentKey))
|
|
91
|
-
.find((candidate) => candidate !== undefined);
|
|
92
|
-
|
|
93
|
-
if (parentNode) {
|
|
94
|
-
if (!parentNode.children.includes(node)) {
|
|
95
|
-
parentNode.children.push(node);
|
|
96
|
-
}
|
|
97
|
-
linked.add(node);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
rootSet.add(node);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
rootSet.forEach((root) => {
|
|
105
|
-
if (!linked.has(root) || root.item.shortId) {
|
|
106
|
-
roots.push(root);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
return roots;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
export const RestrictionMenu: FC<Props> = ({
|
|
114
|
-
items,
|
|
115
|
-
restrictField,
|
|
116
|
-
enableHierarchy = false,
|
|
117
|
-
hasMoreItems = false,
|
|
118
|
-
showAllWhenEmpty = true,
|
|
119
|
-
onRequestMore,
|
|
120
|
-
navigationMenuListClassName = "items-center justify-between flex-row",
|
|
121
|
-
itemsByRow = {
|
|
122
|
-
[DEVICE_OPTIONS.MOBILE]: 2,
|
|
123
|
-
[DEVICE_OPTIONS.TABLET]: 5,
|
|
124
|
-
[DEVICE_OPTIONS.DESKTOP]: 7,
|
|
125
|
-
}
|
|
126
|
-
}) => {
|
|
127
|
-
const t = useTranslations();
|
|
128
|
-
const setRestrictionList = useRestrictionStore((state) => state.setRestrictionList);
|
|
129
|
-
|
|
130
|
-
const [params] = useQueryStates({
|
|
131
|
-
restrict: parseAsString,
|
|
132
|
-
}, {
|
|
133
|
-
history: 'push',
|
|
134
|
-
shallow: false,
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const restrictionValues = useMemo(() => params.restrict?.split(`${restrictField}=`)[1]?.split(",") || [], [params.restrict, restrictField]);
|
|
138
|
-
const selectedRestrictionIds = useMemo(() => new Set(restrictionValues), [restrictionValues]);
|
|
139
|
-
|
|
140
|
-
const uiLang = useLocale();
|
|
141
|
-
const lang = uiLang?.split("-")[0] ?? "";
|
|
142
|
-
|
|
143
|
-
useEffect(() => {
|
|
144
|
-
const map = new Map<string, string>();
|
|
145
|
-
items.forEach((item) => {
|
|
146
|
-
const label = getLabelByLang(item.labels, lang);
|
|
147
|
-
if (item.shortId && label) {
|
|
148
|
-
map.set(item.shortId, label);
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
setRestrictionList(map);
|
|
152
|
-
}, [items, lang, setRestrictionList]);
|
|
153
|
-
|
|
154
|
-
const sortedItems = useMemo(() => {
|
|
155
|
-
//if shortId it is on the restrictionValues, it should be on top of the list, otherwise keep the original order
|
|
156
|
-
|
|
157
|
-
const sorted = [...items].sort((a, b) => {
|
|
158
|
-
const aShortId = a.shortId || "";
|
|
159
|
-
const bShortId = b.shortId || "";
|
|
160
|
-
|
|
161
|
-
const aIndex = restrictionValues.indexOf(aShortId);
|
|
162
|
-
const bIndex = restrictionValues.indexOf(bShortId);
|
|
163
|
-
|
|
164
|
-
if (aIndex === -1 && bIndex === -1) {
|
|
165
|
-
return 0; // keep original order if neither is in restrictionValues
|
|
166
|
-
}
|
|
167
|
-
if (aIndex === -1) {
|
|
168
|
-
return 1; // a goes after b
|
|
169
|
-
}
|
|
170
|
-
if (bIndex === -1) {
|
|
171
|
-
return -1; // a goes before b
|
|
172
|
-
}
|
|
173
|
-
return aIndex - bIndex; // sort by index in restrictionValues
|
|
174
|
-
});
|
|
175
|
-
return sorted;
|
|
176
|
-
}, [items, restrictionValues]);
|
|
177
|
-
|
|
178
|
-
const hierarchyRoots = useMemo(() => {
|
|
179
|
-
if (!enableHierarchy) return [];
|
|
180
|
-
return buildRestrictionTree(sortedItems);
|
|
181
|
-
}, [enableHierarchy, sortedItems]);
|
|
182
|
-
|
|
183
|
-
const device = useBreakpoint();
|
|
184
|
-
const [visibleCount, setVisibleCount] = useState(0);
|
|
185
|
-
const [visibleItems, setVisibleItems] = useState(sortedItems.slice(0, visibleCount));
|
|
186
|
-
const [hiddenItems, setHiddenItems] = useState(sortedItems.slice(visibleCount));
|
|
187
|
-
|
|
188
|
-
useEffect(() => {
|
|
189
|
-
if (device == null) return
|
|
190
|
-
setVisibleCount(itemsByRow[device as keyof typeof DEVICE_OPTIONS] as number);
|
|
191
|
-
|
|
192
|
-
}, [device, itemsByRow]);
|
|
193
|
-
|
|
194
|
-
useEffect(() => {
|
|
195
|
-
if (visibleCount == 0) return;
|
|
196
|
-
|
|
197
|
-
if (enableHierarchy) {
|
|
198
|
-
const roots = hierarchyRoots.map((node) => node.item);
|
|
199
|
-
setVisibleItems(roots.slice(0, visibleCount));
|
|
200
|
-
setHiddenItems(roots.slice(visibleCount));
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
setVisibleItems(sortedItems.slice(0, visibleCount));
|
|
205
|
-
setHiddenItems(sortedItems.slice(visibleCount));
|
|
206
|
-
}, [enableHierarchy, hierarchyRoots, sortedItems, visibleCount]);
|
|
207
|
-
|
|
208
|
-
const visibleHierarchyRoots = useMemo(() => {
|
|
209
|
-
if (!enableHierarchy) return [];
|
|
210
|
-
const visibleRootShortIds = new Set(visibleItems.map((item) => item.shortId).filter(Boolean));
|
|
211
|
-
return hierarchyRoots.filter((root) => root.item.shortId && visibleRootShortIds.has(root.item.shortId));
|
|
212
|
-
}, [enableHierarchy, hierarchyRoots, visibleItems]);
|
|
213
|
-
|
|
214
|
-
const hiddenHierarchyRoots = useMemo(() => {
|
|
215
|
-
if (!enableHierarchy) return [];
|
|
216
|
-
const hiddenRootShortIds = new Set(hiddenItems.map((item) => item.shortId).filter(Boolean));
|
|
217
|
-
return hierarchyRoots.filter((root) => root.item.shortId && hiddenRootShortIds.has(root.item.shortId));
|
|
218
|
-
}, [enableHierarchy, hierarchyRoots, hiddenItems]);
|
|
219
|
-
|
|
220
|
-
const [expandedNodes, setExpandedNodes] = useState<Record<string, boolean>>({});
|
|
221
|
-
const toggleExpanded = (shortId: string) => {
|
|
222
|
-
setExpandedNodes((prev) => ({ ...prev, [shortId]: !prev[shortId] }));
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
const renderTreeNode = (node: RestrictionTreeNode, depth = 0) => {
|
|
226
|
-
const shortId = node.item.shortId || "";
|
|
227
|
-
const hasChildren = node.children.length > 0;
|
|
228
|
-
const isRootNode = depth === 0;
|
|
229
|
-
const isExpanded = isRootNode || expandedNodes[shortId] === true;
|
|
230
|
-
const isSelected = restrictionValues.includes(shortId);
|
|
231
|
-
const hasActiveDescendant = hasChildren && node.children.some((child) => hasSelectedDescendant(child, selectedRestrictionIds));
|
|
232
|
-
const shouldHighlightBranch = hasActiveDescendant && !isSelected;
|
|
233
|
-
|
|
234
|
-
return (
|
|
235
|
-
<li key={`tree-node-${shortId}`} className="flex flex-col w-full">
|
|
236
|
-
<div className="flex items-center gap-1">
|
|
237
|
-
{hasChildren && !isRootNode ? (
|
|
238
|
-
<button
|
|
239
|
-
type="button"
|
|
240
|
-
onClick={() => toggleExpanded(shortId)}
|
|
241
|
-
className="shrink-0 rounded p-1 hover:bg-muted"
|
|
242
|
-
aria-label={isExpanded ? "Collapse" : "Expand"}
|
|
243
|
-
>
|
|
244
|
-
{isExpanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
|
|
245
|
-
</button>
|
|
246
|
-
) : (
|
|
247
|
-
<span className="inline-block w-6" />
|
|
248
|
-
)}
|
|
249
|
-
<div
|
|
250
|
-
style={{ marginLeft: depth * 12 }}
|
|
251
|
-
className={cn(
|
|
252
|
-
"w-full rounded-xl transition-colors",
|
|
253
|
-
shouldHighlightBranch && "bg-primary/5 ring-1 ring-primary/10"
|
|
254
|
-
)}
|
|
255
|
-
>
|
|
256
|
-
<RestrictionDropdownItem
|
|
257
|
-
shortId={shortId}
|
|
258
|
-
restrictField={restrictField}
|
|
259
|
-
label={getLabelByLang(node.item.labels, lang)}
|
|
260
|
-
selected={isSelected}
|
|
261
|
-
/>
|
|
262
|
-
</div>
|
|
263
|
-
{shouldHighlightBranch ? (
|
|
264
|
-
<span
|
|
265
|
-
aria-hidden="true"
|
|
266
|
-
className="ml-1 h-2.5 w-2.5 shrink-0 rounded-full bg-primary/55"
|
|
267
|
-
/>
|
|
268
|
-
) : null}
|
|
269
|
-
</div>
|
|
270
|
-
{hasChildren && isExpanded && (
|
|
271
|
-
<ul className="pl-3">
|
|
272
|
-
{node.children.map((child) => renderTreeNode(child, depth + 1))}
|
|
273
|
-
</ul>
|
|
274
|
-
)}
|
|
275
|
-
</li>
|
|
276
|
-
);
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
return (
|
|
280
|
-
<NavigationMenu viewport={false} className="max-w-full w-full c-rex-restriction-menu">
|
|
281
|
-
<NavigationMenuList className={cn("w-full", navigationMenuListClassName)}>
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
{(showAllWhenEmpty || items.length > 0) && (
|
|
285
|
-
<RestrictionNavigationItem
|
|
286
|
-
removeRestrictParam
|
|
287
|
-
label={t('all')}
|
|
288
|
-
selected={restrictionValues.length === 0}
|
|
289
|
-
/>
|
|
290
|
-
)}
|
|
291
|
-
|
|
292
|
-
{!enableHierarchy && visibleItems.map((item) => (
|
|
293
|
-
<RestrictionNavigationItem
|
|
294
|
-
key={item.shortId}
|
|
295
|
-
shortId={item.shortId!}
|
|
296
|
-
restrictField={restrictField as string}
|
|
297
|
-
label={getLabelByLang(item.labels, lang)}
|
|
298
|
-
selected={restrictionValues.includes(item.shortId!)}
|
|
299
|
-
/>
|
|
300
|
-
))}
|
|
301
|
-
|
|
302
|
-
{enableHierarchy && visibleHierarchyRoots.map((rootNode) => {
|
|
303
|
-
const shortId = rootNode.item.shortId || "";
|
|
304
|
-
const hasChildren = rootNode.children.length > 0;
|
|
305
|
-
const label = getLabelByLang(rootNode.item.labels, lang);
|
|
306
|
-
const rootSelected = restrictionValues.includes(shortId);
|
|
307
|
-
const hasActiveDescendant = hasChildren && hasSelectedDescendant(rootNode, selectedRestrictionIds);
|
|
308
|
-
const shouldHighlightBranch = hasActiveDescendant && !rootSelected;
|
|
309
|
-
|
|
310
|
-
if (!hasChildren) {
|
|
311
|
-
return (
|
|
312
|
-
<RestrictionNavigationItem
|
|
313
|
-
key={shortId}
|
|
314
|
-
shortId={shortId}
|
|
315
|
-
restrictField={restrictField}
|
|
316
|
-
label={label}
|
|
317
|
-
selected={restrictionValues.includes(shortId)}
|
|
318
|
-
/>
|
|
319
|
-
);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return (
|
|
323
|
-
<NavigationMenuItem key={`root-dropdown-${shortId}`}>
|
|
324
|
-
<NavigationMenuTrigger
|
|
325
|
-
className={cn(
|
|
326
|
-
shouldHighlightBranch && "border border-primary/20 bg-primary/5 text-primary shadow-sm"
|
|
327
|
-
)}
|
|
328
|
-
>
|
|
329
|
-
<span className="inline-flex items-center gap-2">
|
|
330
|
-
{label}
|
|
331
|
-
{shouldHighlightBranch ? (
|
|
332
|
-
<span
|
|
333
|
-
aria-hidden="true"
|
|
334
|
-
className="h-2 w-2 rounded-full bg-primary/55"
|
|
335
|
-
/>
|
|
336
|
-
) : null}
|
|
337
|
-
</span>
|
|
338
|
-
</NavigationMenuTrigger>
|
|
339
|
-
<NavigationMenuContent className="w-96 sm:w-[420px] md:w-[520px]">
|
|
340
|
-
<ul className="grid gap-1">
|
|
341
|
-
{renderTreeNode(rootNode, 0)}
|
|
342
|
-
</ul>
|
|
343
|
-
</NavigationMenuContent>
|
|
344
|
-
</NavigationMenuItem>
|
|
345
|
-
);
|
|
346
|
-
})}
|
|
347
|
-
|
|
348
|
-
{(enableHierarchy ? hiddenHierarchyRoots.length > 0 : hiddenItems.length > 0) || hasMoreItems ? (
|
|
349
|
-
<NavigationMenuItem>
|
|
350
|
-
<NavigationMenuTrigger onClick={onRequestMore}>
|
|
351
|
-
{t('more')}
|
|
352
|
-
</NavigationMenuTrigger>
|
|
353
|
-
<NavigationMenuContent className="w-96 sm:w-[] md:w-[700px] lg:w-[65rem]">
|
|
354
|
-
{enableHierarchy ? (
|
|
355
|
-
<ul className="grid gap-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3">
|
|
356
|
-
{hiddenHierarchyRoots.map((node) => renderTreeNode(node, 0))}
|
|
357
|
-
</ul>
|
|
358
|
-
) : (
|
|
359
|
-
<ul className="grid gap-1 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4">
|
|
360
|
-
{hiddenItems.map((item) => (
|
|
361
|
-
<li
|
|
362
|
-
key={item.shortId}
|
|
363
|
-
className="flex items-center"
|
|
364
|
-
>
|
|
365
|
-
<RestrictionDropdownItem
|
|
366
|
-
shortId={item.shortId!}
|
|
367
|
-
restrictField={restrictField as string}
|
|
368
|
-
label={getLabelByLang(item.labels, lang)}
|
|
369
|
-
selected={restrictionValues.includes(item.shortId!)}
|
|
370
|
-
/>
|
|
371
|
-
</li>
|
|
372
|
-
))}
|
|
373
|
-
</ul>
|
|
374
|
-
)}
|
|
375
|
-
</NavigationMenuContent>
|
|
376
|
-
</NavigationMenuItem>
|
|
377
|
-
) : null}
|
|
378
|
-
</NavigationMenuList>
|
|
379
|
-
</NavigationMenu>
|
|
380
|
-
);
|
|
381
|
-
};
|
|
1
|
+
export {
|
|
2
|
+
RestrictionSelectionMenu as RestrictionMenu,
|
|
3
|
+
type RestrictionSelectionMenuProps as RestrictionMenuProps,
|
|
4
|
+
} from "./restriction-selection-menu";
|