@papernote/ui 1.7.1 → 1.7.3
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/dist/components/Breadcrumbs.d.ts +68 -0
- package/dist/components/Breadcrumbs.d.ts.map +1 -1
- package/dist/components/Calendar.d.ts +3 -1
- package/dist/components/Calendar.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.d.ts +73 -3
- package/dist/index.esm.js +173 -49
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +171 -46
- package/dist/index.js.map +1 -1
- package/dist/styles.css +0 -4
- package/package.json +1 -1
- package/src/components/Breadcrumbs.tsx +176 -18
- package/src/components/Calendar.tsx +21 -10
- package/src/components/index.ts +2 -2
package/dist/styles.css
CHANGED
package/package.json
CHANGED
|
@@ -1,31 +1,192 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Link } from 'react-router-dom';
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { Link, useNavigate, useLocation, useInRouterContext } from 'react-router-dom';
|
|
3
3
|
import { ChevronRight, Home } from 'lucide-react';
|
|
4
4
|
|
|
5
|
+
/** State passed during breadcrumb navigation */
|
|
6
|
+
export interface BreadcrumbNavigationState {
|
|
7
|
+
/** Unique timestamp to detect navigation, even to the same route */
|
|
8
|
+
breadcrumbReset: number;
|
|
9
|
+
/** Identifies the source of navigation */
|
|
10
|
+
from: 'breadcrumb';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Hook to detect breadcrumb navigation and trigger callbacks.
|
|
15
|
+
* Use this in host components to reset state when a breadcrumb is clicked.
|
|
16
|
+
*
|
|
17
|
+
* Note: This hook requires React Router context. If used outside a Router,
|
|
18
|
+
* it will be a no-op (the callback will never be called).
|
|
19
|
+
*
|
|
20
|
+
* @param onReset - Callback fired when breadcrumb navigation is detected
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* function ProductsPage() {
|
|
24
|
+
* const [viewMode, setViewMode] = useState<'list' | 'detail'>('list');
|
|
25
|
+
*
|
|
26
|
+
* // Automatically reset to list view when breadcrumb is clicked
|
|
27
|
+
* useBreadcrumbReset(() => setViewMode('list'));
|
|
28
|
+
*
|
|
29
|
+
* // ... rest of component
|
|
30
|
+
* }
|
|
31
|
+
*/
|
|
32
|
+
export function useBreadcrumbReset(onReset: () => void): void {
|
|
33
|
+
const inRouter = useInRouterContext();
|
|
34
|
+
const lastResetRef = useRef<number | null>(null);
|
|
35
|
+
|
|
36
|
+
// Only use useLocation when inside Router context
|
|
37
|
+
const location = inRouter ? useLocation() : null;
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!location) return;
|
|
41
|
+
|
|
42
|
+
const state = location.state as BreadcrumbNavigationState | null;
|
|
43
|
+
|
|
44
|
+
if (state?.breadcrumbReset && state.breadcrumbReset !== lastResetRef.current) {
|
|
45
|
+
lastResetRef.current = state.breadcrumbReset;
|
|
46
|
+
onReset();
|
|
47
|
+
}
|
|
48
|
+
}, [location?.state, onReset, location]);
|
|
49
|
+
}
|
|
50
|
+
|
|
5
51
|
export interface BreadcrumbItem {
|
|
52
|
+
/** Display text for the breadcrumb */
|
|
6
53
|
label: string;
|
|
54
|
+
/** URL to navigate to. When provided, renders as a Link */
|
|
7
55
|
href?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Optional callback fired when breadcrumb is clicked.
|
|
58
|
+
* Called in addition to navigation when href is provided.
|
|
59
|
+
* Use for custom actions like analytics, state resets, etc.
|
|
60
|
+
*/
|
|
8
61
|
onClick?: () => void;
|
|
62
|
+
/** Optional icon to display before the label */
|
|
9
63
|
icon?: React.ReactNode;
|
|
10
64
|
}
|
|
11
65
|
|
|
12
66
|
export interface BreadcrumbsProps {
|
|
13
67
|
items: BreadcrumbItem[];
|
|
68
|
+
/** Whether to show the home icon link at the start. Default: true */
|
|
14
69
|
showHome?: boolean;
|
|
15
70
|
}
|
|
16
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Breadcrumbs navigation component.
|
|
74
|
+
*
|
|
75
|
+
* When a breadcrumb with href is clicked:
|
|
76
|
+
* - If navigating to a different route: standard navigation occurs
|
|
77
|
+
* - If navigating to the same route: navigation state is updated with a unique key,
|
|
78
|
+
* which can be used by host apps to detect "reset" navigation via useLocation().state
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* // Basic usage
|
|
82
|
+
* <Breadcrumbs items={[
|
|
83
|
+
* { label: 'Home', href: '/' },
|
|
84
|
+
* { label: 'Products', href: '/products' },
|
|
85
|
+
* { label: 'Widget' } // Current page (no href)
|
|
86
|
+
* ]} />
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* // Host app detecting breadcrumb navigation for state reset
|
|
90
|
+
* function ProductsPage() {
|
|
91
|
+
* const location = useLocation();
|
|
92
|
+
* const [viewMode, setViewMode] = useState<'list' | 'detail'>('list');
|
|
93
|
+
*
|
|
94
|
+
* // Reset to list view when breadcrumb navigation occurs
|
|
95
|
+
* useEffect(() => {
|
|
96
|
+
* if (location.state?.breadcrumbReset) {
|
|
97
|
+
* setViewMode('list');
|
|
98
|
+
* }
|
|
99
|
+
* }, [location.state?.breadcrumbReset]);
|
|
100
|
+
*
|
|
101
|
+
* // ... rest of component
|
|
102
|
+
* }
|
|
103
|
+
*/
|
|
17
104
|
export default function Breadcrumbs({ items, showHome = true }: BreadcrumbsProps) {
|
|
105
|
+
const inRouter = useInRouterContext();
|
|
106
|
+
|
|
107
|
+
// Only use router hooks when inside Router context
|
|
108
|
+
const navigate = inRouter ? useNavigate() : null;
|
|
109
|
+
const location = inRouter ? useLocation() : null;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Handle breadcrumb click with same-route detection.
|
|
113
|
+
* When clicking a breadcrumb that points to the current route,
|
|
114
|
+
* we navigate with state to trigger a reset in the host component.
|
|
115
|
+
*/
|
|
116
|
+
const handleBreadcrumbClick = (
|
|
117
|
+
e: React.MouseEvent,
|
|
118
|
+
href: string,
|
|
119
|
+
onClick?: () => void
|
|
120
|
+
) => {
|
|
121
|
+
// Always call onClick if provided (for custom actions)
|
|
122
|
+
onClick?.();
|
|
123
|
+
|
|
124
|
+
// If not in router context, let the browser handle navigation naturally
|
|
125
|
+
if (!navigate || !location) return;
|
|
126
|
+
|
|
127
|
+
// Check if we're navigating to the same base path
|
|
128
|
+
const targetPath = href.split('?')[0].split('#')[0];
|
|
129
|
+
const currentPath = location.pathname;
|
|
130
|
+
|
|
131
|
+
if (targetPath === currentPath) {
|
|
132
|
+
// Same route - prevent default Link behavior and use navigate with state
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
navigate(href, {
|
|
135
|
+
state: {
|
|
136
|
+
breadcrumbReset: Date.now(),
|
|
137
|
+
from: 'breadcrumb'
|
|
138
|
+
},
|
|
139
|
+
replace: true
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// Different route - let the Link handle it normally
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Helper to render a link - uses Link when in router, <a> when not
|
|
146
|
+
const renderLink = (
|
|
147
|
+
href: string,
|
|
148
|
+
children: React.ReactNode,
|
|
149
|
+
className: string,
|
|
150
|
+
onClick?: (e: React.MouseEvent) => void,
|
|
151
|
+
ariaLabel?: string
|
|
152
|
+
) => {
|
|
153
|
+
if (inRouter) {
|
|
154
|
+
return (
|
|
155
|
+
<Link
|
|
156
|
+
to={href}
|
|
157
|
+
className={className}
|
|
158
|
+
onClick={onClick}
|
|
159
|
+
aria-label={ariaLabel}
|
|
160
|
+
>
|
|
161
|
+
{children}
|
|
162
|
+
</Link>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
return (
|
|
166
|
+
<a
|
|
167
|
+
href={href}
|
|
168
|
+
className={className}
|
|
169
|
+
onClick={(e) => {
|
|
170
|
+
onClick?.(e);
|
|
171
|
+
}}
|
|
172
|
+
aria-label={ariaLabel}
|
|
173
|
+
>
|
|
174
|
+
{children}
|
|
175
|
+
</a>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
18
179
|
return (
|
|
19
180
|
<nav aria-label="Breadcrumb" className="flex items-center space-x-2 text-sm">
|
|
20
181
|
{showHome && (
|
|
21
182
|
<>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
className="
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
183
|
+
{renderLink(
|
|
184
|
+
'/',
|
|
185
|
+
<Home className="h-4 w-4" />,
|
|
186
|
+
'text-ink-500 hover:text-ink-900 transition-colors',
|
|
187
|
+
(e) => handleBreadcrumbClick(e, '/'),
|
|
188
|
+
'Home'
|
|
189
|
+
)}
|
|
29
190
|
{items.length > 0 && <ChevronRight className="h-4 w-4 text-ink-400" />}
|
|
30
191
|
</>
|
|
31
192
|
)}
|
|
@@ -53,16 +214,13 @@ export default function Breadcrumbs({ items, showHome = true }: BreadcrumbsProps
|
|
|
53
214
|
);
|
|
54
215
|
}
|
|
55
216
|
|
|
56
|
-
// Has href - render as Link
|
|
217
|
+
// Has href - render as Link (or <a> if no router) with same-route detection
|
|
57
218
|
if (item.href) {
|
|
58
|
-
return (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
>
|
|
64
|
-
{content}
|
|
65
|
-
</Link>
|
|
219
|
+
return renderLink(
|
|
220
|
+
item.href,
|
|
221
|
+
content,
|
|
222
|
+
'flex items-center gap-2 text-ink-500 hover:text-ink-900 hover:underline transition-colors',
|
|
223
|
+
(e) => handleBreadcrumbClick(e, item.href!, item.onClick)
|
|
66
224
|
);
|
|
67
225
|
}
|
|
68
226
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import React, { useState, useMemo } from 'react';
|
|
3
|
-
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
|
3
|
+
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
|
|
4
4
|
|
|
5
5
|
export interface CalendarEvent {
|
|
6
6
|
date: Date;
|
|
@@ -14,6 +14,8 @@ export interface CalendarProps {
|
|
|
14
14
|
value?: Date;
|
|
15
15
|
/** Callback when date is selected */
|
|
16
16
|
onChange?: (date: Date) => void;
|
|
17
|
+
/** Callback when displayed month changes (via navigation buttons or goToToday) */
|
|
18
|
+
onMonthChange?: (date: Date) => void;
|
|
17
19
|
/** Events to display on calendar */
|
|
18
20
|
events?: CalendarEvent[];
|
|
19
21
|
/** Callback when event marker is clicked */
|
|
@@ -41,6 +43,7 @@ export interface CalendarProps {
|
|
|
41
43
|
export default function Calendar({
|
|
42
44
|
value,
|
|
43
45
|
onChange,
|
|
46
|
+
onMonthChange,
|
|
44
47
|
events = [],
|
|
45
48
|
onEventClick,
|
|
46
49
|
rangeMode = false,
|
|
@@ -200,25 +203,35 @@ export default function Calendar({
|
|
|
200
203
|
|
|
201
204
|
// Navigate months
|
|
202
205
|
const previousMonth = () => {
|
|
203
|
-
|
|
206
|
+
const newMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1);
|
|
207
|
+
setCurrentMonth(newMonth);
|
|
208
|
+
onMonthChange?.(newMonth);
|
|
204
209
|
};
|
|
205
210
|
|
|
206
211
|
const nextMonth = () => {
|
|
207
|
-
|
|
212
|
+
const newMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1);
|
|
213
|
+
setCurrentMonth(newMonth);
|
|
214
|
+
onMonthChange?.(newMonth);
|
|
208
215
|
};
|
|
209
216
|
|
|
210
217
|
// Navigate years
|
|
211
218
|
const previousYear = () => {
|
|
212
|
-
|
|
219
|
+
const newMonth = new Date(currentMonth.getFullYear() - 1, currentMonth.getMonth(), 1);
|
|
220
|
+
setCurrentMonth(newMonth);
|
|
221
|
+
onMonthChange?.(newMonth);
|
|
213
222
|
};
|
|
214
223
|
|
|
215
224
|
const nextYear = () => {
|
|
216
|
-
|
|
225
|
+
const newMonth = new Date(currentMonth.getFullYear() + 1, currentMonth.getMonth(), 1);
|
|
226
|
+
setCurrentMonth(newMonth);
|
|
227
|
+
onMonthChange?.(newMonth);
|
|
217
228
|
};
|
|
218
229
|
|
|
219
230
|
// Go to today
|
|
220
231
|
const goToToday = () => {
|
|
221
|
-
|
|
232
|
+
const today = new Date();
|
|
233
|
+
setCurrentMonth(today);
|
|
234
|
+
onMonthChange?.(today);
|
|
222
235
|
};
|
|
223
236
|
|
|
224
237
|
// Day names
|
|
@@ -244,8 +257,7 @@ export default function Calendar({
|
|
|
244
257
|
className="p-1.5 hover:bg-paper-100 rounded transition-colors"
|
|
245
258
|
aria-label="Previous year"
|
|
246
259
|
>
|
|
247
|
-
<
|
|
248
|
-
<ChevronLeft className="h-4 w-4 text-ink-600 -ml-3" />
|
|
260
|
+
<ChevronsLeft className="h-4 w-4 text-ink-600" />
|
|
249
261
|
</button>
|
|
250
262
|
<button
|
|
251
263
|
onClick={previousMonth}
|
|
@@ -281,8 +293,7 @@ export default function Calendar({
|
|
|
281
293
|
className="p-1.5 hover:bg-paper-100 rounded transition-colors"
|
|
282
294
|
aria-label="Next year"
|
|
283
295
|
>
|
|
284
|
-
<
|
|
285
|
-
<ChevronRight className="h-4 w-4 text-ink-600 -ml-3" />
|
|
296
|
+
<ChevronsRight className="h-4 w-4 text-ink-600" />
|
|
286
297
|
</button>
|
|
287
298
|
</div>
|
|
288
299
|
</div>
|
package/src/components/index.ts
CHANGED
|
@@ -201,8 +201,8 @@ export type { ExpandablePanelProps } from './ExpandablePanel';
|
|
|
201
201
|
export { Show, Hide } from './ResponsiveUtilities';
|
|
202
202
|
|
|
203
203
|
// Navigation Components
|
|
204
|
-
export { default as Breadcrumbs } from './Breadcrumbs';
|
|
205
|
-
export type { BreadcrumbsProps, BreadcrumbItem } from './Breadcrumbs';
|
|
204
|
+
export { default as Breadcrumbs, useBreadcrumbReset } from './Breadcrumbs';
|
|
205
|
+
export type { BreadcrumbsProps, BreadcrumbItem, BreadcrumbNavigationState } from './Breadcrumbs';
|
|
206
206
|
|
|
207
207
|
export { default as Tabs } from './Tabs';
|
|
208
208
|
export type { TabsProps, Tab } from './Tabs';
|