@papernote/ui 1.7.2 → 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/styles.css CHANGED
@@ -1575,10 +1575,6 @@ input:checked + .slider:before{
1575
1575
  margin-left: -0.5rem;
1576
1576
  }
1577
1577
 
1578
- .-ml-3{
1579
- margin-left: -0.75rem;
1580
- }
1581
-
1582
1578
  .-ml-px{
1583
1579
  margin-left: -1px;
1584
1580
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papernote/ui",
3
- "version": "1.7.2",
3
+ "version": "1.7.3",
4
4
  "type": "module",
5
5
  "description": "A modern React component library with a paper notebook aesthetic - minimal, professional, and expressive",
6
6
  "main": "dist/index.js",
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useRef } from 'react';
2
- import { Link, useNavigate, useLocation } from 'react-router-dom';
2
+ import { Link, useNavigate, useLocation, useInRouterContext } from 'react-router-dom';
3
3
  import { ChevronRight, Home } from 'lucide-react';
4
4
 
5
5
  /** State passed during breadcrumb navigation */
@@ -14,6 +14,9 @@ export interface BreadcrumbNavigationState {
14
14
  * Hook to detect breadcrumb navigation and trigger callbacks.
15
15
  * Use this in host components to reset state when a breadcrumb is clicked.
16
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
+ *
17
20
  * @param onReset - Callback fired when breadcrumb navigation is detected
18
21
  *
19
22
  * @example
@@ -27,17 +30,22 @@ export interface BreadcrumbNavigationState {
27
30
  * }
28
31
  */
29
32
  export function useBreadcrumbReset(onReset: () => void): void {
30
- const location = useLocation();
33
+ const inRouter = useInRouterContext();
31
34
  const lastResetRef = useRef<number | null>(null);
35
+
36
+ // Only use useLocation when inside Router context
37
+ const location = inRouter ? useLocation() : null;
32
38
 
33
39
  useEffect(() => {
40
+ if (!location) return;
41
+
34
42
  const state = location.state as BreadcrumbNavigationState | null;
35
43
 
36
44
  if (state?.breadcrumbReset && state.breadcrumbReset !== lastResetRef.current) {
37
45
  lastResetRef.current = state.breadcrumbReset;
38
46
  onReset();
39
47
  }
40
- }, [location.state, onReset]);
48
+ }, [location?.state, onReset, location]);
41
49
  }
42
50
 
43
51
  export interface BreadcrumbItem {
@@ -94,8 +102,11 @@ export interface BreadcrumbsProps {
94
102
  * }
95
103
  */
96
104
  export default function Breadcrumbs({ items, showHome = true }: BreadcrumbsProps) {
97
- const navigate = useNavigate();
98
- const location = useLocation();
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;
99
110
 
100
111
  /**
101
112
  * Handle breadcrumb click with same-route detection.
@@ -110,6 +121,9 @@ export default function Breadcrumbs({ items, showHome = true }: BreadcrumbsProps
110
121
  // Always call onClick if provided (for custom actions)
111
122
  onClick?.();
112
123
 
124
+ // If not in router context, let the browser handle navigation naturally
125
+ if (!navigate || !location) return;
126
+
113
127
  // Check if we're navigating to the same base path
114
128
  const targetPath = href.split('?')[0].split('#')[0];
115
129
  const currentPath = location.pathname;
@@ -128,18 +142,51 @@ export default function Breadcrumbs({ items, showHome = true }: BreadcrumbsProps
128
142
  // Different route - let the Link handle it normally
129
143
  };
130
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
+
131
179
  return (
132
180
  <nav aria-label="Breadcrumb" className="flex items-center space-x-2 text-sm">
133
181
  {showHome && (
134
182
  <>
135
- <Link
136
- to="/"
137
- className="text-ink-500 hover:text-ink-900 transition-colors"
138
- aria-label="Home"
139
- onClick={(e) => handleBreadcrumbClick(e, '/')}
140
- >
141
- <Home className="h-4 w-4" />
142
- </Link>
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
+ )}
143
190
  {items.length > 0 && <ChevronRight className="h-4 w-4 text-ink-400" />}
144
191
  </>
145
192
  )}
@@ -167,16 +214,13 @@ export default function Breadcrumbs({ items, showHome = true }: BreadcrumbsProps
167
214
  );
168
215
  }
169
216
 
170
- // Has href - render as Link with same-route detection
217
+ // Has href - render as Link (or <a> if no router) with same-route detection
171
218
  if (item.href) {
172
- return (
173
- <Link
174
- to={item.href}
175
- onClick={(e) => handleBreadcrumbClick(e, item.href!, item.onClick)}
176
- className="flex items-center gap-2 text-ink-500 hover:text-ink-900 hover:underline transition-colors"
177
- >
178
- {content}
179
- </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)
180
224
  );
181
225
  }
182
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
- setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1));
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
- setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1));
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
- setCurrentMonth(new Date(currentMonth.getFullYear() - 1, currentMonth.getMonth(), 1));
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
- setCurrentMonth(new Date(currentMonth.getFullYear() + 1, currentMonth.getMonth(), 1));
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
- setCurrentMonth(new Date());
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
- <ChevronLeft className="h-4 w-4 text-ink-600" />
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
- <ChevronRight className="h-4 w-4 text-ink-600" />
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>