@djangocfg/ui-nextjs 2.1.268 → 2.1.271

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-nextjs",
3
- "version": "2.1.268",
3
+ "version": "2.1.271",
4
4
  "description": "Next.js UI component library with Radix UI primitives, Tailwind CSS styling, charts, and form components",
5
5
  "keywords": [
6
6
  "ui-components",
@@ -85,11 +85,11 @@
85
85
  "check": "tsc --noEmit"
86
86
  },
87
87
  "peerDependencies": {
88
- "@djangocfg/api": "^2.1.268",
89
- "@djangocfg/i18n": "^2.1.268",
90
- "@djangocfg/nextjs": "^2.1.268",
91
- "@djangocfg/ui-core": "^2.1.268",
92
- "@djangocfg/ui-tools": "^2.1.268",
88
+ "@djangocfg/api": "^2.1.271",
89
+ "@djangocfg/i18n": "^2.1.271",
90
+ "@djangocfg/nextjs": "^2.1.271",
91
+ "@djangocfg/ui-core": "^2.1.271",
92
+ "@djangocfg/ui-tools": "^2.1.271",
93
93
  "@types/react": "^19.1.0",
94
94
  "@types/react-dom": "^19.1.0",
95
95
  "consola": "^3.4.2",
@@ -112,7 +112,7 @@
112
112
  "react-chartjs-2": "^5.3.0"
113
113
  },
114
114
  "devDependencies": {
115
- "@djangocfg/typescript-config": "^2.1.268",
115
+ "@djangocfg/typescript-config": "^2.1.271",
116
116
  "@radix-ui/react-dropdown-menu": "^2.1.16",
117
117
  "@radix-ui/react-slot": "^1.2.4",
118
118
  "@types/node": "^24.7.2",
@@ -1,65 +1,74 @@
1
1
  import { Link } from '../lib/navigation';
2
2
  import React from 'react';
3
3
 
4
- // cn is available for future use
5
- // import { cn } from '@djangocfg/ui-core/lib';
6
-
7
4
  import {
8
5
  Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage,
9
6
  BreadcrumbSeparator
10
7
  } from './breadcrumb';
11
8
 
12
9
  export interface BreadcrumbItem {
13
- /** Display text for the breadcrumb */
14
10
  label: string;
15
- /** URL to navigate to. If not provided, item will be rendered as current page */
16
11
  href?: string;
17
- /** Whether this item is the current page (will be rendered as BreadcrumbPage) */
12
+ /**
13
+ * Force render as current page (non-interactive, aria-current="page").
14
+ * By default only the last item without href is treated as current page.
15
+ */
18
16
  isCurrentPage?: boolean;
19
- /** Optional icon to display before the label */
20
17
  icon?: React.ReactNode;
21
18
  }
22
19
 
23
20
  export interface BreadcrumbNavigationProps {
24
- /** Array of breadcrumb items */
25
21
  items: BreadcrumbItem[];
26
- /** Custom separator between items */
27
22
  separator?: React.ReactNode;
28
- /** Maximum number of items to show before collapsing with ellipsis */
23
+ /**
24
+ * Max visible items (excluding the ellipsis slot) before collapsing.
25
+ * Must be >= 2. Defaults to 5.
26
+ */
29
27
  maxItems?: number;
30
- /** Custom className for the breadcrumb container */
31
28
  className?: string;
32
- /** Whether to use Next.js Link component for navigation */
33
- useNextLink?: boolean;
34
29
  }
35
30
 
31
+ type DisplayEntry =
32
+ | { type: 'item'; item: BreadcrumbItem; originalIndex: number }
33
+ | { type: 'ellipsis' };
34
+
36
35
  export const BreadcrumbNavigation: React.FC<BreadcrumbNavigationProps> = ({
37
36
  items,
38
37
  separator,
39
38
  maxItems = 5,
40
39
  className,
41
- useNextLink = true,
42
40
  }) => {
43
- // Handle empty or single item cases
44
- if (!items || items.length === 0) {
45
- return null;
46
- }
47
-
48
- // Determine which items to show based on maxItems
49
- const shouldCollapse = items.length > maxItems;
50
- let displayItems: BreadcrumbItem[] = items;
51
-
52
- if (shouldCollapse && items.length > 0) {
53
- // Show first item, ellipsis, and last few items
54
- if(items.length > 0) {
55
- const firstItem = items[0]!;
56
- const lastItems = items.slice(-(maxItems - 2)).filter(Boolean);
57
- displayItems = [firstItem, ...lastItems];
41
+ if (!items || items.length === 0) return null;
42
+
43
+ const clampedMax = Math.max(2, maxItems);
44
+
45
+ // Build the list of entries to render
46
+ const entries: DisplayEntry[] = (() => {
47
+ if (items.length <= clampedMax) {
48
+ return items.map((item, i) => ({ type: 'item' as const, item, originalIndex: i }));
58
49
  }
59
- }
50
+ // Always show first item, ellipsis, then last (clampedMax - 2) items
51
+ // e.g. maxItems=4 → show 1 + ellipsis + 2 last = 4 visible items + ellipsis
52
+ const tailCount = Math.max(1, clampedMax - 2);
53
+ const tail = items.slice(-tailCount).map((item, i) => ({
54
+ type: 'item' as const,
55
+ item,
56
+ originalIndex: items.length - tailCount + i,
57
+ }));
58
+ return [
59
+ { type: 'item' as const, item: items[0]!, originalIndex: 0 },
60
+ { type: 'ellipsis' as const },
61
+ ...tail,
62
+ ];
63
+ })();
60
64
 
61
- const renderBreadcrumbItem = (item: BreadcrumbItem, index: number, isLast: boolean) => {
62
- const isCurrentPage = item.isCurrentPage || isLast || !item.href;
65
+ const resolveIsCurrentPage = (item: BreadcrumbItem, originalIndex: number): boolean => {
66
+ if (item.isCurrentPage !== undefined) return item.isCurrentPage;
67
+ return originalIndex === items.length - 1;
68
+ };
69
+
70
+ const renderItem = (item: BreadcrumbItem, originalIndex: number) => {
71
+ const current = resolveIsCurrentPage(item, originalIndex);
63
72
 
64
73
  const content = (
65
74
  <>
@@ -68,23 +77,23 @@ export const BreadcrumbNavigation: React.FC<BreadcrumbNavigationProps> = ({
68
77
  </>
69
78
  );
70
79
 
71
- if (isCurrentPage) {
80
+ if (current) {
72
81
  return (
73
- <BreadcrumbItem key={`${item.label}-${index}`}>
82
+ <BreadcrumbItem key={`item-${originalIndex}`}>
74
83
  <BreadcrumbPage>{content}</BreadcrumbPage>
75
84
  </BreadcrumbItem>
76
85
  );
77
86
  }
78
87
 
79
88
  return (
80
- <BreadcrumbItem key={`${item.label}-${index}`}>
81
- <BreadcrumbLink asChild={useNextLink}>
82
- {useNextLink ? (
83
- <Link href={item.href!}>{content}</Link>
84
- ) : (
85
- <a href={item.href!}>{content}</a>
86
- )}
87
- </BreadcrumbLink>
89
+ <BreadcrumbItem key={`item-${originalIndex}`}>
90
+ {item.href ? (
91
+ <BreadcrumbLink href={item.href}>
92
+ <Link href={item.href}>{content}</Link>
93
+ </BreadcrumbLink>
94
+ ) : (
95
+ <BreadcrumbLink>{content}</BreadcrumbLink>
96
+ )}
88
97
  </BreadcrumbItem>
89
98
  );
90
99
  };
@@ -92,28 +101,24 @@ export const BreadcrumbNavigation: React.FC<BreadcrumbNavigationProps> = ({
92
101
  return (
93
102
  <Breadcrumb className={className}>
94
103
  <BreadcrumbList>
95
- {displayItems.map((item, index) => {
96
- const isLast = index === displayItems.length - 1;
97
- const isFirst = index === 0;
98
-
99
- // Show ellipsis after first item if we collapsed items
100
- if (shouldCollapse && isFirst && displayItems.length > 2) {
104
+ {entries.map((entry, displayIndex) => {
105
+ const isFirst = displayIndex === 0;
106
+
107
+ if (entry.type === 'ellipsis') {
101
108
  return (
102
- <React.Fragment key={`fragment-${index}`}>
103
- {renderBreadcrumbItem(item, index, isLast)}
109
+ <React.Fragment key="ellipsis">
104
110
  <BreadcrumbSeparator>{separator}</BreadcrumbSeparator>
105
111
  <BreadcrumbItem>
106
112
  <BreadcrumbEllipsis />
107
113
  </BreadcrumbItem>
108
- {!isLast && <BreadcrumbSeparator>{separator}</BreadcrumbSeparator>}
109
114
  </React.Fragment>
110
115
  );
111
116
  }
112
117
 
113
118
  return (
114
- <React.Fragment key={`fragment-${index}`}>
115
- {renderBreadcrumbItem(item, index, isLast)}
116
- {!isLast && <BreadcrumbSeparator>{separator}</BreadcrumbSeparator>}
119
+ <React.Fragment key={`fragment-${entry.originalIndex}`}>
120
+ {!isFirst && <BreadcrumbSeparator>{separator}</BreadcrumbSeparator>}
121
+ {renderItem(entry.item, entry.originalIndex)}
117
122
  </React.Fragment>
118
123
  );
119
124
  })}