@c-rex/components 0.3.0-build.35 → 0.3.0-build.38

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 (50) hide show
  1. package/package.json +32 -36
  2. package/src/article/article-action-bar.tsx +12 -2
  3. package/src/{check-article-lang.tsx → article/check-article-lang.tsx} +1 -1
  4. package/src/article/render-article-highlight.tsx +108 -0
  5. package/src/article/render-article.tsx +28 -0
  6. package/src/autocomplete.tsx +7 -25
  7. package/src/blog/blog-author-card.tsx +116 -0
  8. package/src/carousel/carousel.tsx +5 -2
  9. package/src/carousel/information-unit-carousel-item.tsx +1 -1
  10. package/src/content-unavailable.tsx +20 -0
  11. package/src/directoryNodes/directory-tree-context.tsx +9 -4
  12. package/src/documents/description-preview.tsx +14 -4
  13. package/src/documents/result-list-item.tsx +40 -46
  14. package/src/favorites/__tests__/favorites-hydration.test.tsx +245 -0
  15. package/src/favorites/bookmark-button.tsx +38 -20
  16. package/src/favorites/favorite-button.tsx +23 -24
  17. package/src/favorites/favorites-context.tsx +287 -0
  18. package/src/icons/file-icon.tsx +9 -26
  19. package/src/info/information-unit-metadata-grid-client.tsx +21 -21
  20. package/src/navbar/navbar.tsx +16 -30
  21. package/src/navbar/settings.tsx +1 -1
  22. package/src/page-wrapper.tsx +3 -3
  23. package/src/renditions/html-client.tsx +8 -6
  24. package/src/renditions/html.tsx +3 -1
  25. package/src/restriction-menu/restriction-menu-item.tsx +48 -58
  26. package/src/restriction-menu/restriction-selection-command-menu.tsx +445 -0
  27. package/src/restriction-menu/restriction-selection-menu.tsx +5 -7
  28. package/src/restriction-menu/taxonomy-restriction-command-menu.tsx +111 -0
  29. package/src/restriction-menu/taxonomy-restriction-menu.tsx +19 -12
  30. package/src/results/filter-navbar.tsx +81 -76
  31. package/src/results/filter-sidebar/context.tsx +32 -0
  32. package/src/results/filter-sidebar/index.tsx +40 -35
  33. package/src/results/generic/search-results-client.tsx +5 -4
  34. package/src/results/generic/table-result-list.tsx +16 -16
  35. package/src/results/information-unit-search-results-card-list.tsx +4 -1
  36. package/src/results/information-unit-search-results-cards.tsx +169 -69
  37. package/src/results/pagination.tsx +43 -40
  38. package/src/search-input.tsx +4 -2
  39. package/src/toc/toc-breadcrumb.tsx +1 -1
  40. package/src/toc/toc-browse-controls.tsx +2 -2
  41. package/src/toc/toc-tree-panel.tsx +19 -16
  42. package/src/article/article-content.tsx +0 -19
  43. package/src/breadcrumb.tsx +0 -124
  44. package/src/directoryNodes/tree-of-content.tsx +0 -68
  45. package/src/render-article.tsx +0 -75
  46. package/src/restriction-menu/restriction-menu-container.tsx +0 -4
  47. package/src/restriction-menu/restriction-menu.tsx +0 -4
  48. package/src/stores/__tests__/favorites-store.test.ts +0 -54
  49. package/src/stores/favorites-store.ts +0 -163
  50. /package/src/{render-article.module.css → article/render-article.module.css} +0 -0
@@ -38,47 +38,30 @@ export const RestrictionNavigationItem: FC<Props> = ({
38
38
  currentRestrict: restrict,
39
39
  });
40
40
 
41
- const labelStyle = {
42
- display: 'inline-block',
43
- maxWidth: 100,
44
- overflow: 'hidden',
45
- textOverflow: 'ellipsis',
46
- whiteSpace: 'nowrap',
47
- verticalAlign: 'middle',
48
- } as React.CSSProperties;
49
-
50
- const needsTooltip = label.length > 12;
51
-
52
- const labelNode = needsTooltip ? (
53
- <TooltipProvider delayDuration={300}>
54
- <Tooltip>
55
- <TooltipTrigger asChild>
56
- <span style={labelStyle}>{label}</span>
57
- </TooltipTrigger>
58
- <TooltipContent>{label}</TooltipContent>
59
- </Tooltip>
60
- </TooltipProvider>
61
- ) : (
62
- <span style={labelStyle}>{label}</span>
63
- );
64
-
65
41
  return (
66
42
  <NavigationMenuItem key={shortId} asChild>
67
- <Button
68
- variant={selected ? "default" : "outline"}
69
- rounded="full"
70
- onClick={() => {
71
- startSearchNavigation();
72
- if (shouldRemoveRestrictParam) {
73
- setRestrict(null);
74
- } else {
75
- setRestrict(restrictionValue);
76
- }
77
- }}
78
- className="cursor-pointer"
79
- >
80
- {labelNode}
81
- </Button>
43
+ <TooltipProvider delayDuration={300}>
44
+ <Tooltip>
45
+ <TooltipTrigger asChild>
46
+ <Button
47
+ variant={selected ? "default" : "outline"}
48
+ rounded="full"
49
+ onClick={() => {
50
+ startSearchNavigation();
51
+ if (shouldRemoveRestrictParam) {
52
+ setRestrict(null);
53
+ } else {
54
+ setRestrict(restrictionValue);
55
+ }
56
+ }}
57
+ className="cursor-pointer"
58
+ >
59
+ {label}
60
+ </Button>
61
+ </TooltipTrigger>
62
+ <TooltipContent>{label}</TooltipContent>
63
+ </Tooltip>
64
+ </TooltipProvider>
82
65
  </NavigationMenuItem>
83
66
  );
84
67
  };
@@ -104,25 +87,32 @@ export const RestrictionDropdownItem: FC<Props> = ({
104
87
  });
105
88
 
106
89
  return (
107
- <Button
108
- variant={selected ? "default" : "ghost"}
109
- onClick={() => {
110
- if (onClick) {
111
- onClick();
112
- return;
113
- }
114
- startSearchNavigation();
115
- if (shouldRemoveRestrictParam) {
116
- setRestrict(null);
117
- } else {
118
- setRestrict(restrictionValue);
119
- }
120
- }}
121
- rounded="full"
122
- className="text-left text-wrap !h-auto min-h-10 w-full !justify-start cursor-pointer"
123
- >
124
- {label}
125
- </Button>
90
+ <TooltipProvider delayDuration={300}>
91
+ <Tooltip>
92
+ <TooltipTrigger asChild>
93
+ <Button
94
+ variant={selected ? "default" : "ghost"}
95
+ onClick={() => {
96
+ if (onClick) {
97
+ onClick();
98
+ return;
99
+ }
100
+ startSearchNavigation();
101
+ if (shouldRemoveRestrictParam) {
102
+ setRestrict(null);
103
+ } else {
104
+ setRestrict(restrictionValue);
105
+ }
106
+ }}
107
+ rounded="full"
108
+ className="text-left text-wrap !h-auto min-h-10 w-full !justify-start cursor-pointer"
109
+ >
110
+ {label}
111
+ </Button>
112
+ </TooltipTrigger>
113
+ <TooltipContent>{label}</TooltipContent>
114
+ </Tooltip>
115
+ </TooltipProvider>
126
116
  );
127
117
  };
128
118
 
@@ -0,0 +1,445 @@
1
+ "use client";
2
+
3
+ import { FC, useEffect, useMemo, useRef, useState } from "react"
4
+ import {
5
+ NavigationMenu,
6
+ NavigationMenuList,
7
+ NavigationMenuItem,
8
+ } from "@c-rex/ui/navigation-menu";
9
+ import { RestrictionNavigationItem } from "./restriction-menu-item";
10
+ import { parseAsString, useQueryState, useQueryStates } from "nuqs";
11
+ import { DomainEntityModel, ObjectRefModel } from "@c-rex/interfaces";
12
+ import { useLocale, useTranslations } from 'next-intl'
13
+ import { cn, getLabelByLang } from "@c-rex/utils";
14
+ import { useRestrictionStore } from "../stores/restriction-store";
15
+ import { useBreakpoint } from "@c-rex/ui/hooks";
16
+ import { DEVICE_OPTIONS } from "@c-rex/constants";
17
+ import { Command, CommandEmpty, CommandInput, CommandItem, CommandList } from "@c-rex/ui/command";
18
+ import { Dialog, DialogContent, DialogTitle } from "@c-rex/ui/dialog";
19
+ import { Button } from "@c-rex/ui/button";
20
+ import { Check, ChevronDown } from "lucide-react";
21
+ import { useSearchNavigationStore } from "../stores/search-navigation-store";
22
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@c-rex/ui/tooltip";
23
+
24
+ type Props = {
25
+ restrictField: string
26
+ navigationMenuListClassName?: string
27
+ items: DomainEntityModel[],
28
+ enableHierarchy?: boolean,
29
+ hasMoreItems?: boolean,
30
+ showAllWhenEmpty?: boolean,
31
+ onRequestMore?: () => void,
32
+ stripLabelPrefix?: string,
33
+ itemsByRow?: {
34
+ [DEVICE_OPTIONS.MOBILE]: number,
35
+ [DEVICE_OPTIONS.TABLET]: number,
36
+ [DEVICE_OPTIONS.DESKTOP]: number,
37
+ }
38
+ }
39
+
40
+ type RestrictionTreeNode = {
41
+ item: DomainEntityModel;
42
+ children: RestrictionTreeNode[];
43
+ };
44
+
45
+ const hasSelectedDescendant = (node: RestrictionTreeNode, selectedShortIds: Set<string>): boolean => {
46
+ const shortId = node.item.shortId;
47
+ if (shortId && selectedShortIds.has(shortId)) return true;
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 || []).flatMap((parent) => {
61
+ const keys: string[] = [];
62
+ if (parent.shortId) keys.push(`short:${parent.shortId}`);
63
+ if (parent.id) keys.push(`id:${parent.id}`);
64
+ return keys;
65
+ });
66
+ };
67
+
68
+ const buildRestrictionTree = (items: DomainEntityModel[]): RestrictionTreeNode[] => {
69
+ const nodes = new Map<string, RestrictionTreeNode>();
70
+ items.forEach((item) => {
71
+ const keys = getNodeKeys(item);
72
+ if (keys.length === 0) return;
73
+ const node: RestrictionTreeNode = { item, children: [] };
74
+ keys.forEach((key) => nodes.set(key, node));
75
+ });
76
+
77
+ const roots: RestrictionTreeNode[] = [];
78
+ const linked = new Set<RestrictionTreeNode>();
79
+ const rootSet = new Set<RestrictionTreeNode>();
80
+
81
+ items.forEach((item) => {
82
+ const [firstKey] = getNodeKeys(item);
83
+ if (!firstKey) return;
84
+ const node = nodes.get(firstKey);
85
+ if (!node) return;
86
+
87
+ const parentKeys = extractParentKeys(item);
88
+ const parentNode = parentKeys
89
+ .map((parentKey) => nodes.get(parentKey))
90
+ .find((candidate) => candidate !== undefined);
91
+
92
+ if (parentNode) {
93
+ if (!parentNode.children.includes(node)) {
94
+ parentNode.children.push(node);
95
+ }
96
+ linked.add(node);
97
+ return;
98
+ }
99
+
100
+ rootSet.add(node);
101
+ });
102
+
103
+ rootSet.forEach((root) => {
104
+ if (!linked.has(root) || root.item.shortId) {
105
+ roots.push(root);
106
+ }
107
+ });
108
+
109
+ return roots;
110
+ };
111
+
112
+ type RestrictionCommandDialogProps = {
113
+ label: string;
114
+ items: RestrictionTreeNode[];
115
+ restrictField: string;
116
+ selectedRestrictionIds: Set<string>;
117
+ lang: string;
118
+ highlighted?: boolean;
119
+ formatLabel?: (label: string) => string;
120
+ };
121
+
122
+ const RestrictionCommandDialog: FC<RestrictionCommandDialogProps> = ({
123
+ label,
124
+ items,
125
+ restrictField,
126
+ selectedRestrictionIds,
127
+ lang,
128
+ highlighted = false,
129
+ formatLabel,
130
+ }) => {
131
+ const fmt = (raw: string) => formatLabel ? formatLabel(raw) : raw;
132
+ const [open, setOpen] = useState(false);
133
+ const [search, setSearch] = useState("");
134
+ const listRef = useRef<HTMLDivElement>(null);
135
+ const [restrict, setRestrict] = useQueryState("restrict", { shallow: false, history: "push" });
136
+ const startSearchNavigation = useSearchNavigationStore((state) => state.start);
137
+ const t = useTranslations();
138
+ const device = useBreakpoint();
139
+
140
+ useEffect(() => {
141
+ if (listRef.current) {
142
+ listRef.current.scrollTop = 0;
143
+ }
144
+ }, [search]);
145
+
146
+ const filteredItems = useMemo(() => {
147
+ if (!search) return items;
148
+ const lower = search.toLowerCase();
149
+ return items.flatMap((node) => {
150
+ const groupLabel = getLabelByLang(node.item.labels, lang) ?? node.item.shortId ?? "";
151
+ const matchingChildren = node.children.filter((child) => {
152
+ const childLabel = getLabelByLang(child.item.labels, lang) ?? child.item.shortId ?? "";
153
+ return childLabel.toLowerCase().includes(lower);
154
+ });
155
+ if (groupLabel.toLowerCase().includes(lower)) {
156
+ return [node];
157
+ }
158
+ if (matchingChildren.length > 0) {
159
+ return [{ ...node, children: matchingChildren }];
160
+ }
161
+ return [];
162
+ });
163
+ }, [items, search, lang]);
164
+
165
+ const handleSelect = (shortId: string, isSelected: boolean) => {
166
+ startSearchNavigation();
167
+ if (isSelected) {
168
+ const restrictionsLength = restrict?.split(",").length ?? 0;
169
+ if (restrictionsLength <= 1) {
170
+ setRestrict(null);
171
+ } else {
172
+ const updated = restrict!
173
+ .replace(shortId, "")
174
+ .replace(",,", ",")
175
+ .replace(/(^,)|(,$)/g, "");
176
+ setRestrict(/^[^=]+=$/.test(updated) ? null : updated);
177
+ }
178
+ } else {
179
+ setRestrict(restrict ? `${restrict},${shortId}` : `${restrictField}=${shortId}`);
180
+ }
181
+ setOpen(false);
182
+ };
183
+
184
+ return (
185
+ <>
186
+ <TooltipProvider delayDuration={300}>
187
+ <Button
188
+ variant={highlighted ? "default" : "outline"}
189
+ rounded="full"
190
+ onClick={() => setOpen(true)}
191
+ className="cursor-pointer gap-1"
192
+ >
193
+ <Tooltip>
194
+ <TooltipTrigger asChild>
195
+ <span className="items-center gap-2">
196
+ {(
197
+ device == DEVICE_OPTIONS.MOBILE && label.length > 15
198
+ ) ? `${label.slice(0, 12)}...` : label}
199
+ {highlighted && (
200
+ <span aria-hidden="true" className="h-2 w-2 rounded-full bg-primary/55" />
201
+ )}
202
+ </span>
203
+ </TooltipTrigger>
204
+ <TooltipContent>{label}</TooltipContent>
205
+ </Tooltip>
206
+ <ChevronDown className="h-3 w-3 opacity-60" />
207
+ </Button>
208
+ </TooltipProvider>
209
+
210
+ <Dialog open={open} onOpenChange={setOpen}>
211
+ <DialogContent className="overflow-hidden p-0">
212
+ <DialogTitle className="sr-only">{t("search")}</DialogTitle>
213
+ <Command shouldFilter={false} className="[&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
214
+ <CommandInput placeholder={t("search")} value={search} onValueChange={setSearch} />
215
+ <CommandList ref={listRef}>
216
+ <CommandEmpty>{t("results.noResultsTitle")}</CommandEmpty>
217
+ {filteredItems.map((node) => {
218
+ const groupShortId = node.item.shortId ?? "";
219
+ const groupLabel = fmt(getLabelByLang(node.item.labels, lang) ?? groupShortId);
220
+ const groupIsSelected = selectedRestrictionIds.has(groupShortId);
221
+
222
+ return (
223
+ <>
224
+ <CommandItem
225
+ key={groupShortId}
226
+ value={groupLabel}
227
+ onSelect={() => handleSelect(groupShortId, groupIsSelected)}
228
+ className="cursor-pointer"
229
+ >
230
+ <span className={cn("flex-1", groupIsSelected && "font-medium")}>{groupLabel}</span>
231
+ {groupIsSelected && <Check className="h-4 w-4 shrink-0 text-primary" />}
232
+ </CommandItem>
233
+ {node.children.map((child) => {
234
+ const shortId = child.item.shortId ?? "";
235
+ const childLabel = fmt(getLabelByLang(child.item.labels, lang) ?? shortId);
236
+ const isSelected = selectedRestrictionIds.has(shortId);
237
+ return (
238
+ <CommandItem
239
+ key={shortId}
240
+ value={`${groupLabel}/${childLabel}`}
241
+ onSelect={() => handleSelect(shortId, isSelected)}
242
+ className="cursor-pointer !pl-8"
243
+ >
244
+ <span className={cn("flex-1", isSelected && "font-bold")}>{childLabel}</span>
245
+ {isSelected && <Check className="h-4 w-4 shrink-0 text-primary" />}
246
+ </CommandItem>
247
+ );
248
+ })}
249
+ </>
250
+ );
251
+ })}
252
+ </CommandList>
253
+ </Command>
254
+ </DialogContent>
255
+ </Dialog>
256
+ </>
257
+ );
258
+ };
259
+
260
+ export const RestrictionSelectionCommandMenu: FC<Props> = ({
261
+ items,
262
+ restrictField,
263
+ enableHierarchy = false,
264
+ hasMoreItems = false,
265
+ showAllWhenEmpty = true,
266
+ onRequestMore,
267
+ stripLabelPrefix,
268
+ navigationMenuListClassName = "items-center justify-start gap-4 flex-row",
269
+ itemsByRow = {
270
+ [DEVICE_OPTIONS.MOBILE]: 2,
271
+ [DEVICE_OPTIONS.TABLET]: 4,
272
+ [DEVICE_OPTIONS.DESKTOP]: 7,
273
+ }
274
+ }) => {
275
+ const t = useTranslations();
276
+ const setRestrictionList = useRestrictionStore((state) => state.setRestrictionList);
277
+ const formatLabel = stripLabelPrefix
278
+ ? (label: string) => label.replace(new RegExp(`^${stripLabelPrefix}`, "i"), "")
279
+ : undefined;
280
+
281
+ const [params] = useQueryStates({
282
+ restrict: parseAsString,
283
+ }, {
284
+ history: 'push',
285
+ shallow: false,
286
+ });
287
+
288
+ const restrictionValues = useMemo(
289
+ () => params.restrict?.split(`${restrictField}=`)[1]?.split(",") || [],
290
+ [params.restrict, restrictField]
291
+ );
292
+ const selectedRestrictionIds = useMemo(() => new Set(restrictionValues), [restrictionValues]);
293
+
294
+ const uiLang = useLocale();
295
+ const lang = uiLang?.split("-")[0] ?? "";
296
+
297
+ useEffect(() => {
298
+ const map = new Map<string, string>();
299
+ items.forEach((item) => {
300
+ const label = getLabelByLang(item.labels, lang);
301
+ if (item.shortId && label) {
302
+ map.set(item.shortId, label);
303
+ }
304
+ });
305
+ setRestrictionList(map);
306
+ }, [items, lang, setRestrictionList]);
307
+
308
+ const sortedItems = useMemo(() => {
309
+ return [...items].sort((a, b) => {
310
+ const aIndex = restrictionValues.indexOf(a.shortId || "");
311
+ const bIndex = restrictionValues.indexOf(b.shortId || "");
312
+ if (aIndex === -1 && bIndex === -1) return 0;
313
+ if (aIndex === -1) return 1;
314
+ if (bIndex === -1) return -1;
315
+ return aIndex - bIndex;
316
+ });
317
+ }, [items, restrictionValues]);
318
+
319
+ const hierarchyRoots = useMemo(() => {
320
+ if (!enableHierarchy) return [];
321
+ return buildRestrictionTree(sortedItems);
322
+ }, [enableHierarchy, sortedItems]);
323
+
324
+ const device = useBreakpoint();
325
+ const [visibleCount, setVisibleCount] = useState(0);
326
+ const [visibleItems, setVisibleItems] = useState(sortedItems.slice(0, visibleCount));
327
+ const [hiddenItems, setHiddenItems] = useState(sortedItems.slice(visibleCount));
328
+
329
+ useEffect(() => {
330
+ if (device == null) return;
331
+ setVisibleCount(itemsByRow[device as keyof typeof DEVICE_OPTIONS] as number);
332
+ }, [device, itemsByRow]);
333
+
334
+ useEffect(() => {
335
+ if (visibleCount === 0) return;
336
+
337
+ if (enableHierarchy) {
338
+ const roots = hierarchyRoots.map((node) => node.item);
339
+ setVisibleItems(roots.slice(0, visibleCount));
340
+ setHiddenItems(roots.slice(visibleCount));
341
+ return;
342
+ }
343
+
344
+ setVisibleItems(sortedItems.slice(0, visibleCount));
345
+ setHiddenItems(sortedItems.slice(visibleCount));
346
+ }, [enableHierarchy, hierarchyRoots, sortedItems, visibleCount]);
347
+
348
+ const visibleHierarchyRoots = useMemo(() => {
349
+ if (!enableHierarchy) return [];
350
+ const visibleRootShortIds = new Set(visibleItems.map((item) => item.shortId).filter(Boolean));
351
+ return hierarchyRoots.filter((root) => root.item.shortId && visibleRootShortIds.has(root.item.shortId));
352
+ }, [enableHierarchy, hierarchyRoots, visibleItems]);
353
+
354
+ const hiddenHierarchyRoots = useMemo(() => {
355
+ if (!enableHierarchy) return [];
356
+ const hiddenRootShortIds = new Set(hiddenItems.map((item) => item.shortId).filter(Boolean));
357
+ return hierarchyRoots.filter((root) => root.item.shortId && hiddenRootShortIds.has(root.item.shortId));
358
+ }, [enableHierarchy, hierarchyRoots, hiddenItems]);
359
+
360
+ const hasHiddenContent = (enableHierarchy ? hiddenHierarchyRoots.length > 0 : hiddenItems.length > 0) || hasMoreItems;
361
+
362
+ return (
363
+ <NavigationMenu viewport={false} className="max-w-full w-full c-rex-restriction-menu">
364
+ <NavigationMenuList className={cn("w-full", navigationMenuListClassName)}>
365
+
366
+ {(showAllWhenEmpty || items.length > 0) && (
367
+ <RestrictionNavigationItem
368
+ removeRestrictParam
369
+ label={t('all')}
370
+ selected={restrictionValues.length === 0}
371
+ />
372
+ )}
373
+
374
+ {!enableHierarchy && visibleItems.map((item) => {
375
+ const rawLabel = getLabelByLang(item.labels, lang);
376
+ const label = rawLabel && formatLabel ? formatLabel(rawLabel) : rawLabel;
377
+ return (
378
+ <RestrictionNavigationItem
379
+ key={item.shortId}
380
+ shortId={item.shortId!}
381
+ restrictField={restrictField}
382
+ label={label}
383
+ selected={restrictionValues.includes(item.shortId!)}
384
+ />
385
+ );
386
+ })}
387
+
388
+ {enableHierarchy && visibleHierarchyRoots.map((rootNode) => {
389
+ const shortId = rootNode.item.shortId || "";
390
+ const hasChildren = rootNode.children.length > 0;
391
+ const rawLabel = getLabelByLang(rootNode.item.labels, lang);
392
+ const label = rawLabel && formatLabel ? formatLabel(rawLabel) : rawLabel;
393
+ const rootSelected = restrictionValues.includes(shortId);
394
+ const hasActiveDescendant = hasChildren && hasSelectedDescendant(rootNode, selectedRestrictionIds);
395
+ const shouldHighlightBranch = hasActiveDescendant && !rootSelected;
396
+
397
+ if (!hasChildren) {
398
+ return (
399
+ <RestrictionNavigationItem
400
+ key={shortId}
401
+ shortId={shortId}
402
+ restrictField={restrictField}
403
+ label={label}
404
+ selected={rootSelected}
405
+ />
406
+ );
407
+ }
408
+
409
+ return (
410
+ <NavigationMenuItem key={`root-command-${shortId}`}>
411
+ <RestrictionCommandDialog
412
+ label={label}
413
+ items={rootNode.children}
414
+ restrictField={restrictField}
415
+ selectedRestrictionIds={selectedRestrictionIds}
416
+ lang={lang}
417
+ highlighted={shouldHighlightBranch}
418
+ formatLabel={formatLabel}
419
+ />
420
+ </NavigationMenuItem>
421
+ );
422
+ })}
423
+
424
+ {hasHiddenContent && (
425
+ <NavigationMenuItem>
426
+ <RestrictionCommandDialog
427
+ label={t('more')}
428
+ items={
429
+ enableHierarchy
430
+ ? hiddenHierarchyRoots
431
+ : hiddenItems.map((item) => ({ item, children: [] }))
432
+ }
433
+ restrictField={restrictField}
434
+ selectedRestrictionIds={selectedRestrictionIds}
435
+ lang={lang}
436
+ highlighted={false}
437
+ formatLabel={formatLabel}
438
+ />
439
+ </NavigationMenuItem>
440
+ )}
441
+
442
+ </NavigationMenuList>
443
+ </NavigationMenu>
444
+ );
445
+ };
@@ -117,10 +117,10 @@ export const RestrictionSelectionMenu: FC<Props> = ({
117
117
  hasMoreItems = false,
118
118
  showAllWhenEmpty = true,
119
119
  onRequestMore,
120
- navigationMenuListClassName = "items-center justify-between flex-row",
120
+ navigationMenuListClassName = "items-center justify-start gap-4 flex-row",
121
121
  itemsByRow = {
122
122
  [DEVICE_OPTIONS.MOBILE]: 2,
123
- [DEVICE_OPTIONS.TABLET]: 5,
123
+ [DEVICE_OPTIONS.TABLET]: 4,
124
124
  [DEVICE_OPTIONS.DESKTOP]: 7,
125
125
  }
126
126
  }) => {
@@ -277,7 +277,7 @@ export const RestrictionSelectionMenu: FC<Props> = ({
277
277
  };
278
278
 
279
279
  return (
280
- <NavigationMenu viewport={false} className="max-w-full w-full c-rex-restriction-menu">
280
+ <NavigationMenu viewport={false} className="max-w-full w-full c-rex-restriction-menu overflow-auto pb-4">
281
281
  <NavigationMenuList className={cn("w-full", navigationMenuListClassName)}>
282
282
 
283
283
 
@@ -326,7 +326,7 @@ export const RestrictionSelectionMenu: FC<Props> = ({
326
326
  shouldHighlightBranch && "border border-primary/20 bg-primary/5 text-primary shadow-sm"
327
327
  )}
328
328
  >
329
- <span className="inline-flex items-center gap-2">
329
+ <span className="max-w-40 overflow-hidden text-ellipsis items-center gap-2">
330
330
  {label}
331
331
  {shouldHighlightBranch ? (
332
332
  <span
@@ -378,6 +378,4 @@ export const RestrictionSelectionMenu: FC<Props> = ({
378
378
  </NavigationMenuList>
379
379
  </NavigationMenu>
380
380
  );
381
- };
382
-
383
- export const RestrictionMenu = RestrictionSelectionMenu;
381
+ };