@papernote/ui 1.8.3 → 1.9.2

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/index.js CHANGED
@@ -10076,6 +10076,207 @@ function NotificationIndicator({ count = 0, onClick, className = '', maxCount =
10076
10076
  return (jsxRuntime.jsxs("button", { onClick: onClick, className: `relative bg-white p-2.5 rounded-lg text-ink-400 hover:text-ink-600 hover:bg-paper-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent-400 transition-all shadow-xs dark:bg-slate-800 dark:text-slate-400 dark:hover:text-slate-100 dark:hover:bg-slate-700 ${className}`, "aria-label": "View notifications", children: [jsxRuntime.jsx(lucideReact.Bell, { className: "h-5 w-5" }), showBadge && (jsxRuntime.jsx("span", { className: `absolute -top-1 -right-1 ${variantClasses[variant]} text-white text-xs font-semibold rounded-full h-5 min-w-5 px-1 flex items-center justify-center`, children: displayCount }))] }));
10077
10077
  }
10078
10078
 
10079
+ /**
10080
+ * Format a date to a relative time string
10081
+ */
10082
+ function formatTimeAgo(date) {
10083
+ const now = new Date();
10084
+ const then = new Date(date);
10085
+ const diffMs = now.getTime() - then.getTime();
10086
+ const diffSeconds = Math.floor(diffMs / 1000);
10087
+ const diffMinutes = Math.floor(diffSeconds / 60);
10088
+ const diffHours = Math.floor(diffMinutes / 60);
10089
+ const diffDays = Math.floor(diffHours / 24);
10090
+ if (diffSeconds < 60) {
10091
+ return 'Just now';
10092
+ }
10093
+ else if (diffMinutes < 60) {
10094
+ return `${diffMinutes}m ago`;
10095
+ }
10096
+ else if (diffHours < 24) {
10097
+ return `${diffHours}h ago`;
10098
+ }
10099
+ else if (diffDays < 7) {
10100
+ return `${diffDays}d ago`;
10101
+ }
10102
+ else {
10103
+ return then.toLocaleDateString();
10104
+ }
10105
+ }
10106
+ /**
10107
+ * Map notification type to Badge variant
10108
+ */
10109
+ const typeToBadgeVariant = {
10110
+ info: 'info',
10111
+ success: 'success',
10112
+ warning: 'warning',
10113
+ error: 'error',
10114
+ };
10115
+ /**
10116
+ * Default labels for notification types
10117
+ */
10118
+ const defaultTypeLabels = {
10119
+ info: 'Info',
10120
+ success: 'Success',
10121
+ warning: 'Warning',
10122
+ error: 'Alert',
10123
+ };
10124
+ /**
10125
+ * Map dropdown position to Popover placement
10126
+ * - bottom-right: Below bell, dropdown's right edge aligns with bell
10127
+ * - bottom-left: Below bell, dropdown's left edge aligns with bell
10128
+ * - top-right: Above bell, dropdown's right edge aligns with bell
10129
+ * - top-left: Above bell, dropdown's left edge aligns with bell
10130
+ */
10131
+ function getPopoverPlacement(position) {
10132
+ switch (position) {
10133
+ case 'bottom-right':
10134
+ case 'right':
10135
+ return 'bottom-start'; // Below, extends to the right
10136
+ case 'bottom-left':
10137
+ case 'left':
10138
+ return 'bottom-end'; // Below, extends to the left
10139
+ case 'top-right':
10140
+ return 'top-start'; // Above, extends to the right
10141
+ case 'top-left':
10142
+ return 'top-end'; // Above, extends to the left
10143
+ default:
10144
+ return 'bottom-start';
10145
+ }
10146
+ }
10147
+ /**
10148
+ * NotificationBell - A bell icon with badge and dropdown for displaying notifications
10149
+ *
10150
+ * Displays a bell icon with an optional unread count badge. When clicked, shows a
10151
+ * dropdown panel with recent notifications, mark as read actions, and a link to
10152
+ * view all notifications.
10153
+ *
10154
+ * @example Basic usage (compact variant)
10155
+ * ```tsx
10156
+ * <NotificationBell
10157
+ * notifications={notifications}
10158
+ * onMarkAsRead={(id) => markRead(id)}
10159
+ * onMarkAllRead={() => markAllRead()}
10160
+ * onNotificationClick={(n) => navigate(n.actionUrl)}
10161
+ * onViewAll={() => navigate('/notifications')}
10162
+ * />
10163
+ * ```
10164
+ *
10165
+ * @example Detailed variant with labeled badges
10166
+ * ```tsx
10167
+ * <NotificationBell
10168
+ * notifications={notifications}
10169
+ * variant="detailed"
10170
+ * showUnreadInHeader
10171
+ * dropdownPosition="bottom-left"
10172
+ * />
10173
+ * ```
10174
+ */
10175
+ function NotificationBell({ notifications, unreadCount: providedUnreadCount, onMarkAsRead, onMarkAllRead, onNotificationClick, onViewAll, loading = false, dropdownPosition = 'bottom-right', maxHeight = '400px', size = 'md', emptyMessage = 'No notifications', viewAllText = 'View all notifications', disabled = false, className = '', variant = 'compact', showUnreadInHeader = false, bellStyle = 'ghost', }) {
10176
+ const [isOpen, setIsOpen] = React.useState(false);
10177
+ // Calculate unread count if not provided
10178
+ const unreadCount = React.useMemo(() => {
10179
+ if (providedUnreadCount !== undefined) {
10180
+ return providedUnreadCount;
10181
+ }
10182
+ return notifications.filter((n) => !n.isRead).length;
10183
+ }, [providedUnreadCount, notifications]);
10184
+ // Handle notification click
10185
+ const handleNotificationClick = React.useCallback((notification) => {
10186
+ onNotificationClick?.(notification);
10187
+ }, [onNotificationClick]);
10188
+ // Handle mark as read
10189
+ const handleMarkAsRead = React.useCallback((e, id) => {
10190
+ e.stopPropagation();
10191
+ onMarkAsRead?.(id);
10192
+ }, [onMarkAsRead]);
10193
+ // Handle mark all as read
10194
+ const handleMarkAllRead = React.useCallback(() => {
10195
+ onMarkAllRead?.();
10196
+ }, [onMarkAllRead]);
10197
+ // Handle view all
10198
+ const handleViewAll = React.useCallback(() => {
10199
+ onViewAll?.();
10200
+ setIsOpen(false);
10201
+ }, [onViewAll]);
10202
+ // Icon sizes based on button size
10203
+ const iconSizes = {
10204
+ sm: 'h-4 w-4',
10205
+ md: 'h-5 w-5',
10206
+ lg: 'h-6 w-6',
10207
+ };
10208
+ // Dropdown width based on size
10209
+ const dropdownWidths = {
10210
+ sm: 'w-72',
10211
+ md: 'w-80',
10212
+ lg: 'w-96',
10213
+ };
10214
+ // Outlined bell style classes
10215
+ const outlinedSizeClasses = {
10216
+ sm: 'p-2',
10217
+ md: 'p-3',
10218
+ lg: 'p-4',
10219
+ };
10220
+ // Trigger button
10221
+ const triggerButton = bellStyle === 'outlined' ? (jsxRuntime.jsxs("div", { className: "relative inline-block", children: [jsxRuntime.jsx("button", { className: `
10222
+ ${outlinedSizeClasses[size]}
10223
+ bg-white border-2 border-paper-300 rounded-xl
10224
+ hover:bg-paper-50 hover:border-paper-400
10225
+ focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent-400
10226
+ transition-all duration-200
10227
+ disabled:opacity-40 disabled:cursor-not-allowed
10228
+ ${className}
10229
+ `, disabled: disabled, "aria-label": unreadCount > 0
10230
+ ? `Notifications - ${unreadCount} unread`
10231
+ : 'Notifications', children: jsxRuntime.jsx(lucideReact.Bell, { className: `${iconSizes[size]} text-ink-600` }) }), unreadCount > 0 && (jsxRuntime.jsx("span", { className: `
10232
+ absolute -top-1 -right-1
10233
+ flex items-center justify-center
10234
+ min-w-[18px] h-[18px] px-1.5
10235
+ rounded-full text-white font-semibold text-[11px]
10236
+ bg-error-500 shadow-sm
10237
+ pointer-events-none
10238
+ `, "aria-label": `${unreadCount > 99 ? '99+' : unreadCount} notifications`, children: unreadCount > 99 ? '99+' : unreadCount }))] })) : (jsxRuntime.jsx(Button, { variant: "ghost", iconOnly: true, size: size, disabled: disabled, badge: unreadCount > 0 ? unreadCount : undefined, badgeVariant: "error", "aria-label": unreadCount > 0
10239
+ ? `Notifications - ${unreadCount} unread`
10240
+ : 'Notifications', className: className, children: jsxRuntime.jsx(lucideReact.Bell, { className: iconSizes[size] }) }));
10241
+ // Header title with optional unread count
10242
+ const headerTitle = showUnreadInHeader && unreadCount > 0
10243
+ ? `Notifications (${unreadCount} unread)`
10244
+ : 'Notifications';
10245
+ // Render compact notification item
10246
+ const renderCompactItem = (notification) => (jsxRuntime.jsxs("div", { className: "flex gap-3", children: [jsxRuntime.jsx("div", { className: "flex-shrink-0 pt-1", children: jsxRuntime.jsx(Badge, { dot: true, variant: typeToBadgeVariant[notification.type], size: "sm" }) }), jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntime.jsx(Text, { size: "sm", weight: notification.priority === 'high' ||
10247
+ notification.priority === 'urgent' ||
10248
+ !notification.isRead
10249
+ ? 'medium'
10250
+ : 'normal', truncate: true, children: notification.title }), jsxRuntime.jsx(Text, { size: "xs", color: "muted", lineClamp: 2, className: "mt-0.5", children: notification.message }), jsxRuntime.jsx(Text, { size: "xs", color: "muted", className: "mt-1", children: formatTimeAgo(notification.createdAt) })] }), !notification.isRead && onMarkAsRead && (jsxRuntime.jsx("button", { className: "flex-shrink-0 p-1 text-ink-400 hover:text-ink-600 hover:bg-paper-100 rounded transition-colors", onClick: (e) => handleMarkAsRead(e, notification.id), "aria-label": "Mark as read", title: "Mark as read", children: jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) }))] }));
10251
+ // Render detailed notification item
10252
+ const renderDetailedItem = (notification) => (jsxRuntime.jsxs("div", { className: "flex gap-3", children: [jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [jsxRuntime.jsx(Text, { size: "sm", weight: notification.priority === 'high' ||
10253
+ notification.priority === 'urgent' ||
10254
+ !notification.isRead
10255
+ ? 'semibold'
10256
+ : 'medium', className: "flex-1", children: notification.title }), jsxRuntime.jsx(Text, { size: "xs", color: "muted", className: "flex-shrink-0 whitespace-nowrap", children: formatTimeAgo(notification.createdAt) })] }), jsxRuntime.jsx(Text, { size: "xs", color: "muted", lineClamp: 2, className: "mt-1", children: notification.message }), jsxRuntime.jsx("div", { className: "mt-2", children: jsxRuntime.jsx(Badge, { variant: typeToBadgeVariant[notification.type], size: "sm", children: notification.typeLabel || defaultTypeLabels[notification.type] }) })] }), !notification.isRead && onMarkAsRead && (jsxRuntime.jsx("button", { className: "flex-shrink-0 p-1 text-ink-400 hover:text-ink-600 hover:bg-paper-100 rounded transition-colors self-center", onClick: (e) => handleMarkAsRead(e, notification.id), "aria-label": "Mark as read", title: "Mark as read", children: jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }) }))] }));
10257
+ // Dropdown content
10258
+ const dropdownContent = (jsxRuntime.jsxs("div", { className: `${dropdownWidths[size]} bg-white`, children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-paper-200", children: [jsxRuntime.jsx(Text, { weight: "semibold", size: "sm", children: headerTitle }), unreadCount > 0 && onMarkAllRead && (jsxRuntime.jsxs("button", { className: "flex items-center gap-1.5 text-xs text-ink-600 hover:text-ink-800 transition-colors", onClick: handleMarkAllRead, children: [jsxRuntime.jsx(lucideReact.Check, { className: "h-3.5 w-3.5" }), "Mark all read"] }))] }), jsxRuntime.jsx("div", { className: "overflow-y-auto", style: { maxHeight }, role: "list", "aria-label": "Notifications", children: loading ? (
10259
+ // Loading state
10260
+ jsxRuntime.jsx("div", { className: "p-4", children: jsxRuntime.jsx(Stack, { spacing: "sm", children: [1, 2, 3].map((i) => (jsxRuntime.jsxs("div", { className: "flex gap-3", children: [variant === 'compact' && (jsxRuntime.jsx(Skeleton, { className: "h-4 w-4 rounded-full flex-shrink-0" })), jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsxs("div", { className: "flex justify-between", children: [jsxRuntime.jsx(Skeleton, { className: "h-4 w-3/4 mb-2" }), variant === 'detailed' && (jsxRuntime.jsx(Skeleton, { className: "h-3 w-12" }))] }), jsxRuntime.jsx(Skeleton, { className: "h-3 w-full mb-1" }), variant === 'compact' ? (jsxRuntime.jsx(Skeleton, { className: "h-3 w-1/4" })) : (jsxRuntime.jsx(Skeleton, { className: "h-5 w-16 mt-2" }))] })] }, i))) }) })) : notifications.length === 0 ? (
10261
+ // Empty state
10262
+ jsxRuntime.jsxs("div", { className: "py-8 px-4 text-center", children: [jsxRuntime.jsx(lucideReact.Bell, { className: "h-10 w-10 text-ink-300 mx-auto mb-3" }), jsxRuntime.jsx(Text, { color: "muted", size: "sm", children: emptyMessage })] })) : (
10263
+ // Notification items
10264
+ notifications.map((notification) => (jsxRuntime.jsx("div", { role: "listitem", className: `
10265
+ px-4 py-3 border-b border-paper-100 last:border-b-0
10266
+ hover:bg-paper-50 transition-colors cursor-pointer
10267
+ ${!notification.isRead ? 'bg-primary-50/30' : ''}
10268
+ ${notification.priority === 'urgent' ? 'border-l-2 border-l-error-500' : ''}
10269
+ `, onClick: () => handleNotificationClick(notification), onKeyDown: (e) => {
10270
+ if (e.key === 'Enter' || e.key === ' ') {
10271
+ e.preventDefault();
10272
+ handleNotificationClick(notification);
10273
+ }
10274
+ }, tabIndex: 0, children: variant === 'compact'
10275
+ ? renderCompactItem(notification)
10276
+ : renderDetailedItem(notification) }, notification.id)))) }), onViewAll && notifications.length > 0 && (jsxRuntime.jsx("div", { className: "px-4 py-3 border-t border-paper-200", children: jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", fullWidth: true, onClick: handleViewAll, icon: jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-3.5 w-3.5" }), iconPosition: "right", children: viewAllText }) }))] }));
10277
+ return (jsxRuntime.jsx(Popover, { trigger: triggerButton, placement: getPopoverPlacement(dropdownPosition), triggerMode: "click", showArrow: false, offset: 4, open: isOpen, onOpenChange: setIsOpen, closeOnClickOutside: true, closeOnEscape: true, disabled: disabled, className: "p-0 overflow-hidden", children: dropdownContent }));
10278
+ }
10279
+
10079
10280
  /**
10080
10281
  * Get value from item by key path (supports nested keys like 'user.name')
10081
10282
  */
@@ -11092,44 +11293,52 @@ function getAugmentedNamespace(n) {
11092
11293
  * (A1, A1:C5, ...)
11093
11294
  */
11094
11295
 
11095
- let Collection$3 = class Collection {
11296
+ var collection;
11297
+ var hasRequiredCollection;
11298
+
11299
+ function requireCollection () {
11300
+ if (hasRequiredCollection) return collection;
11301
+ hasRequiredCollection = 1;
11302
+ class Collection {
11096
11303
 
11097
- constructor(data, refs) {
11098
- if (data == null && refs == null) {
11099
- this._data = [];
11100
- this._refs = [];
11101
- } else {
11102
- if (data.length !== refs.length)
11103
- throw Error('Collection: data length should match references length.');
11104
- this._data = data;
11105
- this._refs = refs;
11106
- }
11107
- }
11304
+ constructor(data, refs) {
11305
+ if (data == null && refs == null) {
11306
+ this._data = [];
11307
+ this._refs = [];
11308
+ } else {
11309
+ if (data.length !== refs.length)
11310
+ throw Error('Collection: data length should match references length.');
11311
+ this._data = data;
11312
+ this._refs = refs;
11313
+ }
11314
+ }
11108
11315
 
11109
- get data() {
11110
- return this._data;
11111
- }
11316
+ get data() {
11317
+ return this._data;
11318
+ }
11112
11319
 
11113
- get refs() {
11114
- return this._refs;
11115
- }
11320
+ get refs() {
11321
+ return this._refs;
11322
+ }
11116
11323
 
11117
- get length() {
11118
- return this._data.length;
11119
- }
11324
+ get length() {
11325
+ return this._data.length;
11326
+ }
11120
11327
 
11121
- /**
11122
- * Add data and references to this collection.
11123
- * @param {{}} obj - data
11124
- * @param {{}} ref - reference
11125
- */
11126
- add(obj, ref) {
11127
- this._data.push(obj);
11128
- this._refs.push(ref);
11129
- }
11130
- };
11328
+ /**
11329
+ * Add data and references to this collection.
11330
+ * @param {{}} obj - data
11331
+ * @param {{}} ref - reference
11332
+ */
11333
+ add(obj, ref) {
11334
+ this._data.push(obj);
11335
+ this._refs.push(ref);
11336
+ }
11337
+ }
11131
11338
 
11132
- var collection = Collection$3;
11339
+ collection = Collection;
11340
+ return collection;
11341
+ }
11133
11342
 
11134
11343
  var helpers;
11135
11344
  var hasRequiredHelpers;
@@ -11138,7 +11347,7 @@ function requireHelpers () {
11138
11347
  if (hasRequiredHelpers) return helpers;
11139
11348
  hasRequiredHelpers = 1;
11140
11349
  const FormulaError = requireError();
11141
- const Collection = collection;
11350
+ const Collection = requireCollection();
11142
11351
 
11143
11352
  const Types = {
11144
11353
  NUMBER: 0,
@@ -20792,7 +21001,7 @@ var engineering = EngineeringFunctions;
20792
21001
 
20793
21002
  const FormulaError$b = requireError();
20794
21003
  const {FormulaHelpers: FormulaHelpers$8, Types: Types$6, WildCard, Address: Address$3} = requireHelpers();
20795
- const Collection$2 = collection;
21004
+ const Collection$2 = requireCollection();
20796
21005
  const H$5 = FormulaHelpers$8;
20797
21006
 
20798
21007
  const ReferenceFunctions$1 = {
@@ -32420,7 +32629,7 @@ var parsing = {
32420
32629
  const FormulaError$4 = requireError();
32421
32630
  const {Address: Address$1} = requireHelpers();
32422
32631
  const {Prefix: Prefix$1, Postfix: Postfix$1, Infix: Infix$1, Operators: Operators$1} = operators;
32423
- const Collection$1 = collection;
32632
+ const Collection$1 = requireCollection();
32424
32633
  const MAX_ROW$1 = 1048576, MAX_COLUMN$1 = 16384;
32425
32634
  const {NotAllInputParsedException} = require$$4;
32426
32635
 
@@ -33182,7 +33391,7 @@ var hooks$1 = {
33182
33391
  const FormulaError$2 = requireError();
33183
33392
  const {FormulaHelpers: FormulaHelpers$1, Types, Address} = requireHelpers();
33184
33393
  const {Prefix, Postfix, Infix, Operators} = operators;
33185
- const Collection = collection;
33394
+ const Collection = requireCollection();
33186
33395
  const MAX_ROW = 1048576, MAX_COLUMN = 16384;
33187
33396
 
33188
33397
  let Utils$1 = class Utils {
@@ -57799,6 +58008,7 @@ exports.ModalFooter = ModalFooter;
57799
58008
  exports.MultiSelect = MultiSelect;
57800
58009
  exports.NotificationBanner = NotificationBanner;
57801
58010
  exports.NotificationBar = NotificationBar;
58011
+ exports.NotificationBell = NotificationBell;
57802
58012
  exports.NotificationIndicator = NotificationIndicator;
57803
58013
  exports.NumberInput = NumberInput;
57804
58014
  exports.Page = Page;