@papernote/ui 1.9.2 → 1.10.0

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
@@ -8191,11 +8191,254 @@ function Breadcrumbs({ items, showHome = true }) {
8191
8191
  })] }));
8192
8192
  }
8193
8193
 
8194
- function Tabs({ tabs, activeTab: controlledActiveTab, defaultTab, variant = 'underline', orientation = 'horizontal', size = 'md', onChange }) {
8194
+ function Badge({ children, variant = 'neutral', size = 'md', icon, onRemove, className = '', dot = false, pill = false, }) {
8195
+ const variantStyles = {
8196
+ success: 'bg-success-50 text-success-700 border-success-200',
8197
+ warning: 'bg-warning-50 text-warning-700 border-warning-200',
8198
+ error: 'bg-error-50 text-error-700 border-error-200',
8199
+ info: 'bg-primary-50 text-primary-700 border-primary-200',
8200
+ neutral: 'bg-paper-100 text-ink-700 border-paper-300',
8201
+ };
8202
+ const dotVariantStyles = {
8203
+ success: 'bg-success-500',
8204
+ warning: 'bg-warning-500',
8205
+ error: 'bg-error-500',
8206
+ info: 'bg-primary-500',
8207
+ neutral: 'bg-ink-400',
8208
+ };
8209
+ const sizeStyles = {
8210
+ sm: 'px-2 py-0.5 text-xs gap-1',
8211
+ md: 'px-3 py-1 text-xs gap-1.5',
8212
+ lg: 'px-3 py-1.5 text-sm gap-2',
8213
+ };
8214
+ // Pill variant has tighter horizontal padding and fully rounded ends
8215
+ const pillSizeStyles = {
8216
+ sm: 'px-1.5 py-0.5 text-xs gap-1',
8217
+ md: 'px-2 py-0.5 text-xs gap-1',
8218
+ lg: 'px-2.5 py-1 text-sm gap-1.5',
8219
+ };
8220
+ const dotSizeStyles = {
8221
+ sm: 'h-1.5 w-1.5',
8222
+ md: 'h-2 w-2',
8223
+ lg: 'h-2.5 w-2.5',
8224
+ };
8225
+ const iconSize = {
8226
+ sm: 'h-3 w-3',
8227
+ md: 'h-3.5 w-3.5',
8228
+ lg: 'h-4 w-4',
8229
+ };
8230
+ // Dot variant - just a colored circle
8231
+ if (dot) {
8232
+ return (jsxRuntime.jsx("span", { className: `
8233
+ inline-block rounded-full
8234
+ ${dotVariantStyles[variant]}
8235
+ ${dotSizeStyles[size]}
8236
+ ${className}
8237
+ `, "aria-label": `${variant} indicator` }));
8238
+ }
8239
+ // Regular badge
8240
+ return (jsxRuntime.jsxs("span", { className: `
8241
+ inline-flex items-center border font-medium
8242
+ ${pill ? 'rounded-full' : 'rounded-full'}
8243
+ ${variantStyles[variant]}
8244
+ ${pill ? pillSizeStyles[size] : sizeStyles[size]}
8245
+ ${className}
8246
+ `, children: [icon && jsxRuntime.jsx("span", { className: iconSize[size], children: icon }), jsxRuntime.jsx("span", { children: children }), onRemove && (jsxRuntime.jsx("button", { onClick: onRemove, className: "ml-1 hover:opacity-70 transition-opacity", "aria-label": "Remove badge", children: jsxRuntime.jsx(lucideReact.X, { className: iconSize[size] }) }))] }));
8247
+ }
8248
+
8249
+ const TabsContext = React.createContext(null);
8250
+ function useTabsContext() {
8251
+ const context = React.useContext(TabsContext);
8252
+ if (!context) {
8253
+ throw new Error('Tabs compound components must be used within a TabsRoot component');
8254
+ }
8255
+ return context;
8256
+ }
8257
+ /**
8258
+ * TabsRoot - Root component for compound tabs pattern
8259
+ *
8260
+ * @example
8261
+ * ```tsx
8262
+ * <TabsRoot defaultValue="tab1">
8263
+ * <TabsList>
8264
+ * <TabsTrigger value="tab1">Tab 1</TabsTrigger>
8265
+ * <TabsTrigger value="tab2">Tab 2</TabsTrigger>
8266
+ * </TabsList>
8267
+ * <TabsContent value="tab1">Content 1</TabsContent>
8268
+ * <TabsContent value="tab2">Content 2</TabsContent>
8269
+ * </TabsRoot>
8270
+ * ```
8271
+ */
8272
+ function TabsRoot({ children, defaultValue, value: controlledValue, onValueChange, variant = 'underline', orientation = 'horizontal', size = 'md', lazy = false, preserveState = false, className = '', }) {
8273
+ const [tabValues, setTabValues] = React.useState([]);
8274
+ const [internalValue, setInternalValue] = React.useState(defaultValue || '');
8275
+ const [visitedTabs, setVisitedTabs] = React.useState(new Set(defaultValue ? [defaultValue] : []));
8276
+ const isControlled = controlledValue !== undefined;
8277
+ const activeTab = isControlled ? controlledValue : internalValue;
8278
+ // Set initial value when tabs register
8279
+ React.useEffect(() => {
8280
+ if (!activeTab && tabValues.length > 0) {
8281
+ const firstTab = tabValues[0];
8282
+ if (isControlled) {
8283
+ onValueChange?.(firstTab);
8284
+ }
8285
+ else {
8286
+ setInternalValue(firstTab);
8287
+ }
8288
+ }
8289
+ }, [tabValues, activeTab, isControlled, onValueChange]);
8290
+ // Track visited tabs
8291
+ React.useEffect(() => {
8292
+ if (activeTab && preserveState) {
8293
+ setVisitedTabs(prev => new Set(prev).add(activeTab));
8294
+ }
8295
+ }, [activeTab, preserveState]);
8296
+ const setActiveTab = React.useCallback((value) => {
8297
+ if (!isControlled) {
8298
+ setInternalValue(value);
8299
+ }
8300
+ onValueChange?.(value);
8301
+ }, [isControlled, onValueChange]);
8302
+ const registerTab = React.useCallback((value) => {
8303
+ setTabValues(prev => prev.includes(value) ? prev : [...prev, value]);
8304
+ }, []);
8305
+ const unregisterTab = React.useCallback((value) => {
8306
+ setTabValues(prev => prev.filter(v => v !== value));
8307
+ }, []);
8308
+ const contextValue = {
8309
+ activeTab,
8310
+ setActiveTab,
8311
+ variant,
8312
+ orientation,
8313
+ size,
8314
+ lazy,
8315
+ preserveState,
8316
+ visitedTabs,
8317
+ registerTab,
8318
+ unregisterTab,
8319
+ tabValues,
8320
+ };
8321
+ // Size-specific gap classes
8322
+ const gapClasses = {
8323
+ sm: orientation === 'vertical' ? 'gap-1.5' : 'gap-4',
8324
+ md: orientation === 'vertical' ? 'gap-2' : 'gap-6',
8325
+ lg: orientation === 'vertical' ? 'gap-3' : 'gap-8',
8326
+ };
8327
+ return (jsxRuntime.jsx(TabsContext.Provider, { value: contextValue, children: jsxRuntime.jsx("div", { className: `w-full ${orientation === 'vertical' ? `flex ${gapClasses[size]}` : ''} ${className}`, children: children }) }));
8328
+ }
8329
+ /**
8330
+ * TabsList - Container for tab triggers
8331
+ */
8332
+ function TabsList({ children, className = '' }) {
8333
+ const { variant, orientation, size } = useTabsContext();
8334
+ const sizeClasses = {
8335
+ sm: {
8336
+ gap: orientation === 'vertical' ? 'gap-1.5' : 'gap-4',
8337
+ minWidth: orientation === 'vertical' ? 'min-w-[150px]' : '',
8338
+ },
8339
+ md: {
8340
+ gap: orientation === 'vertical' ? 'gap-2' : 'gap-6',
8341
+ minWidth: orientation === 'vertical' ? 'min-w-[200px]' : '',
8342
+ },
8343
+ lg: {
8344
+ gap: orientation === 'vertical' ? 'gap-3' : 'gap-8',
8345
+ minWidth: orientation === 'vertical' ? 'min-w-[250px]' : '',
8346
+ },
8347
+ };
8348
+ return (jsxRuntime.jsx("div", { className: `
8349
+ flex ${orientation === 'vertical' ? 'flex-col' : 'items-center'}
8350
+ ${variant === 'underline'
8351
+ ? orientation === 'vertical'
8352
+ ? `border-r border-paper-200 ${sizeClasses[size].gap} pr-6`
8353
+ : `border-b border-paper-200 ${sizeClasses[size].gap}`
8354
+ : `${sizeClasses[size].gap} p-1 bg-paper-50 rounded-lg`}
8355
+ ${sizeClasses[size].minWidth}
8356
+ ${className}
8357
+ `, role: "tablist", "aria-orientation": orientation, children: children }));
8358
+ }
8359
+ /**
8360
+ * TabsTrigger - Individual tab button
8361
+ */
8362
+ function TabsTrigger({ children, value, disabled = false, icon, badge, badgeVariant = 'info', className = '', }) {
8363
+ const { activeTab, setActiveTab, variant, orientation, size, registerTab, unregisterTab } = useTabsContext();
8364
+ const isActive = activeTab === value;
8365
+ // Register this tab on mount
8366
+ React.useEffect(() => {
8367
+ registerTab(value);
8368
+ return () => unregisterTab(value);
8369
+ }, [value, registerTab, unregisterTab]);
8370
+ const sizeClasses = {
8371
+ sm: { padding: 'px-3 py-1.5', text: 'text-xs', icon: 'h-3.5 w-3.5', badgeSize: 'sm' },
8372
+ md: { padding: 'px-4 py-2.5', text: 'text-sm', icon: 'h-4 w-4', badgeSize: 'sm' },
8373
+ lg: { padding: 'px-5 py-3', text: 'text-base', icon: 'h-5 w-5', badgeSize: 'md' },
8374
+ };
8375
+ return (jsxRuntime.jsxs("button", { role: "tab", "aria-selected": isActive, "aria-controls": `panel-${value}`, "aria-disabled": disabled, tabIndex: isActive ? 0 : -1, disabled: disabled, onClick: () => !disabled && setActiveTab(value), className: `
8376
+ flex items-center gap-2 ${sizeClasses[size].padding} ${sizeClasses[size].text} font-medium transition-all duration-200
8377
+ ${orientation === 'vertical' ? 'w-full justify-start' : ''}
8378
+ ${variant === 'underline'
8379
+ ? isActive
8380
+ ? orientation === 'vertical'
8381
+ ? 'text-accent-900 border-r-2 border-accent-500 -mr-[1px]'
8382
+ : 'text-accent-900 border-b-2 border-accent-500 -mb-[1px]'
8383
+ : orientation === 'vertical'
8384
+ ? 'text-ink-600 hover:text-ink-900 border-r-2 border-transparent'
8385
+ : 'text-ink-600 hover:text-ink-900 border-b-2 border-transparent'
8386
+ : isActive
8387
+ ? 'bg-white text-accent-900 rounded-md shadow-xs'
8388
+ : 'text-ink-600 hover:text-ink-900 hover:bg-white/50 rounded-md'}
8389
+ ${disabled ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer'}
8390
+ focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-500 focus-visible:ring-offset-1
8391
+ ${className}
8392
+ `, children: [icon && jsxRuntime.jsx("span", { className: `flex-shrink-0 ${sizeClasses[size].icon}`, children: icon }), jsxRuntime.jsx("span", { children: children }), badge !== undefined && (jsxRuntime.jsx(Badge, { variant: badgeVariant, size: sizeClasses[size].badgeSize, children: badge }))] }));
8393
+ }
8394
+ /**
8395
+ * TabsContent - Content panel for a tab
8396
+ */
8397
+ function TabsContent({ children, value, className = '' }) {
8398
+ const { activeTab, lazy, preserveState, visitedTabs, orientation, size } = useTabsContext();
8399
+ const isActive = activeTab === value;
8400
+ // Determine if content should be rendered
8401
+ const shouldRender = !lazy || isActive || (preserveState && visitedTabs.has(value));
8402
+ if (!shouldRender) {
8403
+ return null;
8404
+ }
8405
+ const spacingClasses = {
8406
+ sm: orientation === 'vertical' ? '' : 'mt-4',
8407
+ md: orientation === 'vertical' ? '' : 'mt-6',
8408
+ lg: orientation === 'vertical' ? '' : 'mt-8',
8409
+ };
8410
+ return (jsxRuntime.jsx("div", { id: `panel-${value}`, role: "tabpanel", "aria-labelledby": `tab-${value}`, hidden: !isActive, tabIndex: 0, className: `
8411
+ ${orientation === 'vertical' ? 'flex-1' : spacingClasses[size]}
8412
+ ${isActive ? 'animate-fade-in focus:outline-none' : 'hidden'}
8413
+ ${className}
8414
+ `, children: children }));
8415
+ }
8416
+ // =============================================================================
8417
+ // Data-Driven Component (Original API)
8418
+ // =============================================================================
8419
+ /**
8420
+ * Tabs - Data-driven tabs component
8421
+ *
8422
+ * Use this for simple use cases where tabs are defined as an array.
8423
+ * For more complex layouts, use the compound components (TabsRoot, TabsList, TabsTrigger, TabsContent).
8424
+ */
8425
+ function Tabs({ tabs, activeTab: controlledActiveTab, defaultTab, variant = 'underline', orientation = 'horizontal', size = 'md', onChange, lazy = false, preserveState = false, closeable = false, onClose, showAddButton = false, onAdd, addButtonLabel = 'Add tab', }) {
8195
8426
  const [internalActiveTab, setInternalActiveTab] = React.useState(defaultTab || tabs[0]?.id);
8427
+ const [focusedIndex, setFocusedIndex] = React.useState(-1);
8428
+ const [visitedTabs, setVisitedTabs] = React.useState(new Set([defaultTab || tabs[0]?.id]));
8429
+ const tabListRef = React.useRef(null);
8430
+ const tabRefs = React.useRef([]);
8196
8431
  // Controlled mode: use activeTab prop, Uncontrolled mode: use internal state
8197
8432
  const isControlled = controlledActiveTab !== undefined;
8198
8433
  const activeTab = isControlled ? controlledActiveTab : internalActiveTab;
8434
+ // Track visited tabs for preserveState
8435
+ React.useEffect(() => {
8436
+ if (activeTab && preserveState) {
8437
+ setVisitedTabs(prev => new Set(prev).add(activeTab));
8438
+ }
8439
+ }, [activeTab, preserveState]);
8440
+ // Get enabled tab indices for keyboard navigation
8441
+ const enabledIndices = tabs.map((tab, index) => tab.disabled ? -1 : index).filter(i => i !== -1);
8199
8442
  // Ensure the activeTab exists in the current tabs array
8200
8443
  // This handles the case where tabs array reference changes at the same time as activeTab
8201
8444
  React.useEffect(() => {
@@ -8210,66 +8453,196 @@ function Tabs({ tabs, activeTab: controlledActiveTab, defaultTab, variant = 'und
8210
8453
  }
8211
8454
  }
8212
8455
  }, [tabs, activeTab, isControlled, onChange]);
8213
- const handleTabChange = (tabId) => {
8456
+ const handleTabChange = React.useCallback((tabId) => {
8214
8457
  if (!isControlled) {
8215
8458
  setInternalActiveTab(tabId);
8216
8459
  }
8217
8460
  onChange?.(tabId);
8218
- };
8461
+ }, [isControlled, onChange]);
8462
+ // Handle tab close
8463
+ const handleClose = React.useCallback((e, tabId) => {
8464
+ e.stopPropagation();
8465
+ onClose?.(tabId);
8466
+ }, [onClose]);
8467
+ // Keyboard navigation handler
8468
+ const handleKeyDown = React.useCallback((e) => {
8469
+ const currentIndex = focusedIndex >= 0 ? focusedIndex : tabs.findIndex(t => t.id === activeTab);
8470
+ const currentEnabledPosition = enabledIndices.indexOf(currentIndex);
8471
+ let nextIndex = -1;
8472
+ let shouldPreventDefault = true;
8473
+ // Determine navigation keys based on orientation
8474
+ const prevKey = orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft';
8475
+ const nextKey = orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight';
8476
+ switch (e.key) {
8477
+ case prevKey:
8478
+ // Move to previous enabled tab
8479
+ if (currentEnabledPosition > 0) {
8480
+ nextIndex = enabledIndices[currentEnabledPosition - 1];
8481
+ }
8482
+ else {
8483
+ // Wrap to last enabled tab
8484
+ nextIndex = enabledIndices[enabledIndices.length - 1];
8485
+ }
8486
+ break;
8487
+ case nextKey:
8488
+ // Move to next enabled tab
8489
+ if (currentEnabledPosition < enabledIndices.length - 1) {
8490
+ nextIndex = enabledIndices[currentEnabledPosition + 1];
8491
+ }
8492
+ else {
8493
+ // Wrap to first enabled tab
8494
+ nextIndex = enabledIndices[0];
8495
+ }
8496
+ break;
8497
+ case 'Home':
8498
+ // Move to first enabled tab
8499
+ nextIndex = enabledIndices[0];
8500
+ break;
8501
+ case 'End':
8502
+ // Move to last enabled tab
8503
+ nextIndex = enabledIndices[enabledIndices.length - 1];
8504
+ break;
8505
+ case 'Enter':
8506
+ case ' ':
8507
+ // Activate focused tab
8508
+ if (focusedIndex >= 0 && !tabs[focusedIndex]?.disabled) {
8509
+ handleTabChange(tabs[focusedIndex].id);
8510
+ }
8511
+ break;
8512
+ case 'Delete':
8513
+ case 'Backspace':
8514
+ // Close focused tab if closeable
8515
+ if (focusedIndex >= 0) {
8516
+ const tab = tabs[focusedIndex];
8517
+ const isTabCloseable = tab.closeable !== undefined ? tab.closeable : closeable;
8518
+ if (isTabCloseable && !tab.disabled && onClose) {
8519
+ onClose(tab.id);
8520
+ }
8521
+ }
8522
+ break;
8523
+ default:
8524
+ shouldPreventDefault = false;
8525
+ }
8526
+ if (shouldPreventDefault) {
8527
+ e.preventDefault();
8528
+ }
8529
+ if (nextIndex >= 0 && nextIndex !== currentIndex) {
8530
+ setFocusedIndex(nextIndex);
8531
+ tabRefs.current[nextIndex]?.focus();
8532
+ }
8533
+ }, [focusedIndex, tabs, activeTab, enabledIndices, orientation, handleTabChange, closeable, onClose]);
8534
+ // Handle focus events
8535
+ const handleFocus = React.useCallback((index) => {
8536
+ setFocusedIndex(index);
8537
+ }, []);
8538
+ const handleBlur = React.useCallback(() => {
8539
+ setFocusedIndex(-1);
8540
+ }, []);
8219
8541
  // Size-specific classes
8220
8542
  const sizeClasses = {
8221
8543
  sm: {
8222
8544
  padding: 'px-3 py-1.5',
8223
8545
  text: 'text-xs',
8224
8546
  icon: 'h-3.5 w-3.5',
8547
+ closeIcon: 'h-3 w-3',
8225
8548
  gap: orientation === 'vertical' ? 'gap-1.5' : 'gap-4',
8226
8549
  minWidth: orientation === 'vertical' ? 'min-w-[150px]' : '',
8227
8550
  spacing: orientation === 'vertical' ? 'mt-4' : 'mt-4',
8551
+ badgeSize: 'sm',
8552
+ addPadding: 'px-2 py-1.5',
8228
8553
  },
8229
8554
  md: {
8230
8555
  padding: 'px-4 py-2.5',
8231
8556
  text: 'text-sm',
8232
8557
  icon: 'h-4 w-4',
8558
+ closeIcon: 'h-3.5 w-3.5',
8233
8559
  gap: orientation === 'vertical' ? 'gap-2' : 'gap-6',
8234
8560
  minWidth: orientation === 'vertical' ? 'min-w-[200px]' : '',
8235
8561
  spacing: orientation === 'vertical' ? 'mt-6' : 'mt-6',
8562
+ badgeSize: 'sm',
8563
+ addPadding: 'px-3 py-2.5',
8236
8564
  },
8237
8565
  lg: {
8238
8566
  padding: 'px-5 py-3',
8239
8567
  text: 'text-base',
8240
8568
  icon: 'h-5 w-5',
8569
+ closeIcon: 'h-4 w-4',
8241
8570
  gap: orientation === 'vertical' ? 'gap-3' : 'gap-8',
8242
8571
  minWidth: orientation === 'vertical' ? 'min-w-[250px]' : '',
8243
8572
  spacing: orientation === 'vertical' ? 'mt-8' : 'mt-8',
8573
+ badgeSize: 'md',
8574
+ addPadding: 'px-4 py-3',
8244
8575
  },
8245
8576
  };
8246
- return (jsxRuntime.jsxs("div", { className: `w-full ${orientation === 'vertical' ? `flex ${sizeClasses[size].gap}` : ''}`, children: [jsxRuntime.jsx("div", { className: `
8247
- flex ${orientation === 'vertical' ? 'flex-col' : ''}
8577
+ // Determine which tabs should be rendered
8578
+ const shouldRenderContent = React.useCallback((tabId) => {
8579
+ if (!lazy) {
8580
+ // Not lazy: render all tabs
8581
+ return true;
8582
+ }
8583
+ if (tabId === activeTab) {
8584
+ // Always render active tab
8585
+ return true;
8586
+ }
8587
+ if (preserveState && visitedTabs.has(tabId)) {
8588
+ // Preserve state: render previously visited tabs
8589
+ return true;
8590
+ }
8591
+ return false;
8592
+ }, [lazy, activeTab, preserveState, visitedTabs]);
8593
+ return (jsxRuntime.jsxs("div", { className: `w-full ${orientation === 'vertical' ? `flex ${sizeClasses[size].gap}` : ''}`, children: [jsxRuntime.jsxs("div", { ref: tabListRef, className: `
8594
+ flex ${orientation === 'vertical' ? 'flex-col' : 'items-center'}
8248
8595
  ${variant === 'underline'
8249
8596
  ? orientation === 'vertical'
8250
8597
  ? `border-r border-paper-200 ${sizeClasses[size].gap} pr-6`
8251
8598
  : `border-b border-paper-200 ${sizeClasses[size].gap}`
8252
8599
  : `${sizeClasses[size].gap} p-1 bg-paper-50 rounded-lg`}
8253
8600
  ${sizeClasses[size].minWidth}
8254
- `, role: "tablist", children: tabs.map((tab) => {
8255
- const isActive = activeTab === tab.id;
8256
- return (jsxRuntime.jsxs("button", { role: "tab", "aria-selected": isActive, "aria-controls": `panel-${tab.id}`, disabled: tab.disabled, onClick: () => !tab.disabled && handleTabChange(tab.id), className: `
8601
+ `, role: "tablist", "aria-orientation": orientation, onKeyDown: handleKeyDown, children: [tabs.map((tab, index) => {
8602
+ const isActive = activeTab === tab.id;
8603
+ const isTabCloseable = tab.closeable !== undefined ? tab.closeable : closeable;
8604
+ return (jsxRuntime.jsxs("button", { ref: (el) => { tabRefs.current[index] = el; }, id: `tab-${tab.id}`, role: "tab", "aria-selected": isActive, "aria-controls": `panel-${tab.id}`, "aria-disabled": tab.disabled, tabIndex: isActive ? 0 : -1, disabled: tab.disabled, onClick: () => !tab.disabled && handleTabChange(tab.id), onFocus: () => handleFocus(index), onBlur: handleBlur, className: `
8257
8605
  flex items-center gap-2 ${sizeClasses[size].padding} ${sizeClasses[size].text} font-medium transition-all duration-200
8258
8606
  ${orientation === 'vertical' ? 'w-full justify-start' : ''}
8259
8607
  ${variant === 'underline'
8260
- ? isActive
8261
- ? orientation === 'vertical'
8262
- ? 'text-accent-900 border-r-2 border-accent-500 -mr-[1px]'
8263
- : 'text-accent-900 border-b-2 border-accent-500 -mb-[1px]'
8264
- : orientation === 'vertical'
8265
- ? 'text-ink-600 hover:text-ink-900 border-r-2 border-transparent'
8266
- : 'text-ink-600 hover:text-ink-900 border-b-2 border-transparent'
8267
- : isActive
8268
- ? 'bg-white text-accent-900 rounded-md shadow-xs'
8269
- : 'text-ink-600 hover:text-ink-900 hover:bg-white/50 rounded-md'}
8608
+ ? isActive
8609
+ ? orientation === 'vertical'
8610
+ ? 'text-accent-900 border-r-2 border-accent-500 -mr-[1px]'
8611
+ : 'text-accent-900 border-b-2 border-accent-500 -mb-[1px]'
8612
+ : orientation === 'vertical'
8613
+ ? 'text-ink-600 hover:text-ink-900 border-r-2 border-transparent'
8614
+ : 'text-ink-600 hover:text-ink-900 border-b-2 border-transparent'
8615
+ : isActive
8616
+ ? 'bg-white text-accent-900 rounded-md shadow-xs'
8617
+ : 'text-ink-600 hover:text-ink-900 hover:bg-white/50 rounded-md'}
8270
8618
  ${tab.disabled ? 'opacity-40 cursor-not-allowed' : 'cursor-pointer'}
8271
- `, children: [tab.icon && jsxRuntime.jsx("span", { className: `flex-shrink-0 ${sizeClasses[size].icon}`, children: tab.icon }), jsxRuntime.jsx("span", { children: tab.label })] }, tab.id));
8272
- }) }), jsxRuntime.jsx("div", { className: `${orientation === 'vertical' ? 'flex-1' : sizeClasses[size].spacing}`, children: tabs.map((tab) => (jsxRuntime.jsx("div", { id: `panel-${tab.id}`, role: "tabpanel", "aria-labelledby": tab.id, hidden: activeTab !== tab.id, className: activeTab === tab.id ? 'animate-fade-in' : '', children: activeTab === tab.id && tab.content }, tab.id))) })] }));
8619
+ focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-500 focus-visible:ring-offset-1
8620
+ group
8621
+ `, children: [tab.icon && jsxRuntime.jsx("span", { className: `flex-shrink-0 ${sizeClasses[size].icon}`, children: tab.icon }), jsxRuntime.jsx("span", { className: isTabCloseable ? 'mr-1' : '', children: tab.label }), tab.badge !== undefined && (jsxRuntime.jsx(Badge, { variant: tab.badgeVariant || 'info', size: sizeClasses[size].badgeSize, children: tab.badge })), isTabCloseable && onClose && (jsxRuntime.jsx("span", { role: "button", "aria-label": `Close ${tab.label}`, onClick: (e) => handleClose(e, tab.id), className: `
8622
+ flex-shrink-0 p-0.5 rounded
8623
+ text-ink-400 hover:text-ink-700 hover:bg-paper-200
8624
+ opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100
8625
+ ${isActive ? 'opacity-100' : ''}
8626
+ transition-opacity duration-150
8627
+ `, children: jsxRuntime.jsx(lucideReact.X, { className: sizeClasses[size].closeIcon }) }))] }, tab.id));
8628
+ }), showAddButton && onAdd && (jsxRuntime.jsx("button", { type: "button", onClick: onAdd, "aria-label": addButtonLabel, title: addButtonLabel, className: `
8629
+ flex items-center justify-center ${sizeClasses[size].addPadding}
8630
+ text-ink-500 hover:text-ink-700 hover:bg-paper-100
8631
+ rounded transition-colors duration-150
8632
+ focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-500 focus-visible:ring-offset-1
8633
+ ${variant === 'underline'
8634
+ ? orientation === 'vertical'
8635
+ ? 'border-r-2 border-transparent'
8636
+ : 'border-b-2 border-transparent -mb-[1px]'
8637
+ : ''}
8638
+ `, children: jsxRuntime.jsx(lucideReact.Plus, { className: sizeClasses[size].icon }) }))] }), jsxRuntime.jsx("div", { className: `${orientation === 'vertical' ? 'flex-1' : sizeClasses[size].spacing}`, children: tabs.map((tab) => {
8639
+ const isActive = activeTab === tab.id;
8640
+ const shouldRender = shouldRenderContent(tab.id);
8641
+ if (!shouldRender) {
8642
+ return null;
8643
+ }
8644
+ return (jsxRuntime.jsx("div", { id: `panel-${tab.id}`, role: "tabpanel", "aria-labelledby": `tab-${tab.id}`, hidden: !isActive, tabIndex: 0, className: isActive ? 'animate-fade-in focus:outline-none' : 'hidden', children: tab.content }, tab.id));
8645
+ }) })] }));
8273
8646
  }
8274
8647
 
8275
8648
  function Pagination({ currentPage, totalPages, onPageChange, showPageNumbers = true, maxPageNumbers = 5, showPageJump = false, }) {
@@ -8343,61 +8716,6 @@ function StepIndicator({ steps, currentStep, variant = 'horizontal', onStepClick
8343
8716
  }) }) }));
8344
8717
  }
8345
8718
 
8346
- function Badge({ children, variant = 'neutral', size = 'md', icon, onRemove, className = '', dot = false, pill = false, }) {
8347
- const variantStyles = {
8348
- success: 'bg-success-50 text-success-700 border-success-200',
8349
- warning: 'bg-warning-50 text-warning-700 border-warning-200',
8350
- error: 'bg-error-50 text-error-700 border-error-200',
8351
- info: 'bg-primary-50 text-primary-700 border-primary-200',
8352
- neutral: 'bg-paper-100 text-ink-700 border-paper-300',
8353
- };
8354
- const dotVariantStyles = {
8355
- success: 'bg-success-500',
8356
- warning: 'bg-warning-500',
8357
- error: 'bg-error-500',
8358
- info: 'bg-primary-500',
8359
- neutral: 'bg-ink-400',
8360
- };
8361
- const sizeStyles = {
8362
- sm: 'px-2 py-0.5 text-xs gap-1',
8363
- md: 'px-3 py-1 text-xs gap-1.5',
8364
- lg: 'px-3 py-1.5 text-sm gap-2',
8365
- };
8366
- // Pill variant has tighter horizontal padding and fully rounded ends
8367
- const pillSizeStyles = {
8368
- sm: 'px-1.5 py-0.5 text-xs gap-1',
8369
- md: 'px-2 py-0.5 text-xs gap-1',
8370
- lg: 'px-2.5 py-1 text-sm gap-1.5',
8371
- };
8372
- const dotSizeStyles = {
8373
- sm: 'h-1.5 w-1.5',
8374
- md: 'h-2 w-2',
8375
- lg: 'h-2.5 w-2.5',
8376
- };
8377
- const iconSize = {
8378
- sm: 'h-3 w-3',
8379
- md: 'h-3.5 w-3.5',
8380
- lg: 'h-4 w-4',
8381
- };
8382
- // Dot variant - just a colored circle
8383
- if (dot) {
8384
- return (jsxRuntime.jsx("span", { className: `
8385
- inline-block rounded-full
8386
- ${dotVariantStyles[variant]}
8387
- ${dotSizeStyles[size]}
8388
- ${className}
8389
- `, "aria-label": `${variant} indicator` }));
8390
- }
8391
- // Regular badge
8392
- return (jsxRuntime.jsxs("span", { className: `
8393
- inline-flex items-center border font-medium
8394
- ${pill ? 'rounded-full' : 'rounded-full'}
8395
- ${variantStyles[variant]}
8396
- ${pill ? pillSizeStyles[size] : sizeStyles[size]}
8397
- ${className}
8398
- `, children: [icon && jsxRuntime.jsx("span", { className: iconSize[size], children: icon }), jsxRuntime.jsx("span", { children: children }), onRemove && (jsxRuntime.jsx("button", { onClick: onRemove, className: "ml-1 hover:opacity-70 transition-opacity", "aria-label": "Remove badge", children: jsxRuntime.jsx(lucideReact.X, { className: iconSize[size] }) }))] }));
8399
- }
8400
-
8401
8719
  /**
8402
8720
  * Avatar component that displays:
8403
8721
  * - User initials in a colored circle (default)
@@ -10172,7 +10490,7 @@ function getPopoverPlacement(position) {
10172
10490
  * />
10173
10491
  * ```
10174
10492
  */
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', }) {
10493
+ 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', icon: customIcon, }) {
10176
10494
  const [isOpen, setIsOpen] = React.useState(false);
10177
10495
  // Calculate unread count if not provided
10178
10496
  const unreadCount = React.useMemo(() => {
@@ -10217,6 +10535,8 @@ function NotificationBell({ notifications, unreadCount: providedUnreadCount, onM
10217
10535
  md: 'p-3',
10218
10536
  lg: 'p-4',
10219
10537
  };
10538
+ // Default bell icon or custom icon
10539
+ const bellIcon = customIcon || jsxRuntime.jsx(lucideReact.Bell, { className: `${iconSizes[size]} text-ink-600` });
10220
10540
  // Trigger button
10221
10541
  const triggerButton = bellStyle === 'outlined' ? (jsxRuntime.jsxs("div", { className: "relative inline-block", children: [jsxRuntime.jsx("button", { className: `
10222
10542
  ${outlinedSizeClasses[size]}
@@ -10228,16 +10548,16 @@ function NotificationBell({ notifications, unreadCount: providedUnreadCount, onM
10228
10548
  ${className}
10229
10549
  `, disabled: disabled, "aria-label": unreadCount > 0
10230
10550
  ? `Notifications - ${unreadCount} unread`
10231
- : 'Notifications', children: jsxRuntime.jsx(lucideReact.Bell, { className: `${iconSizes[size]} text-ink-600` }) }), unreadCount > 0 && (jsxRuntime.jsx("span", { className: `
10551
+ : 'Notifications', children: bellIcon }), unreadCount > 0 && (jsxRuntime.jsx("span", { className: `
10232
10552
  absolute -top-1 -right-1
10233
10553
  flex items-center justify-center
10234
10554
  min-w-[18px] h-[18px] px-1.5
10235
10555
  rounded-full text-white font-semibold text-[11px]
10236
10556
  bg-error-500 shadow-sm
10237
10557
  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
10558
+ `, "aria-label": `${unreadCount > 99 ? '99+' : unreadCount} notifications`, children: unreadCount > 99 ? '99+' : unreadCount }))] })) : (jsxRuntime.jsx(Button, { variant: "ghost", iconOnly: true, size: size, disabled: disabled, icon: bellIcon, badge: unreadCount > 0 ? unreadCount : undefined, badgeVariant: "error", "aria-label": unreadCount > 0
10239
10559
  ? `Notifications - ${unreadCount} unread`
10240
- : 'Notifications', className: className, children: jsxRuntime.jsx(lucideReact.Bell, { className: iconSizes[size] }) }));
10560
+ : 'Notifications', className: className }));
10241
10561
  // Header title with optional unread count
10242
10562
  const headerTitle = showUnreadInHeader && unreadCount > 0
10243
10563
  ? `Notifications (${unreadCount} unread)`
@@ -11293,52 +11613,44 @@ function getAugmentedNamespace(n) {
11293
11613
  * (A1, A1:C5, ...)
11294
11614
  */
11295
11615
 
11296
- var collection;
11297
- var hasRequiredCollection;
11298
-
11299
- function requireCollection () {
11300
- if (hasRequiredCollection) return collection;
11301
- hasRequiredCollection = 1;
11302
- class Collection {
11616
+ let Collection$3 = class Collection {
11303
11617
 
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
- }
11618
+ constructor(data, refs) {
11619
+ if (data == null && refs == null) {
11620
+ this._data = [];
11621
+ this._refs = [];
11622
+ } else {
11623
+ if (data.length !== refs.length)
11624
+ throw Error('Collection: data length should match references length.');
11625
+ this._data = data;
11626
+ this._refs = refs;
11627
+ }
11628
+ }
11315
11629
 
11316
- get data() {
11317
- return this._data;
11318
- }
11630
+ get data() {
11631
+ return this._data;
11632
+ }
11319
11633
 
11320
- get refs() {
11321
- return this._refs;
11322
- }
11634
+ get refs() {
11635
+ return this._refs;
11636
+ }
11323
11637
 
11324
- get length() {
11325
- return this._data.length;
11326
- }
11638
+ get length() {
11639
+ return this._data.length;
11640
+ }
11327
11641
 
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
- }
11642
+ /**
11643
+ * Add data and references to this collection.
11644
+ * @param {{}} obj - data
11645
+ * @param {{}} ref - reference
11646
+ */
11647
+ add(obj, ref) {
11648
+ this._data.push(obj);
11649
+ this._refs.push(ref);
11650
+ }
11651
+ };
11338
11652
 
11339
- collection = Collection;
11340
- return collection;
11341
- }
11653
+ var collection = Collection$3;
11342
11654
 
11343
11655
  var helpers;
11344
11656
  var hasRequiredHelpers;
@@ -11347,7 +11659,7 @@ function requireHelpers () {
11347
11659
  if (hasRequiredHelpers) return helpers;
11348
11660
  hasRequiredHelpers = 1;
11349
11661
  const FormulaError = requireError();
11350
- const Collection = requireCollection();
11662
+ const Collection = collection;
11351
11663
 
11352
11664
  const Types = {
11353
11665
  NUMBER: 0,
@@ -21001,7 +21313,7 @@ var engineering = EngineeringFunctions;
21001
21313
 
21002
21314
  const FormulaError$b = requireError();
21003
21315
  const {FormulaHelpers: FormulaHelpers$8, Types: Types$6, WildCard, Address: Address$3} = requireHelpers();
21004
- const Collection$2 = requireCollection();
21316
+ const Collection$2 = collection;
21005
21317
  const H$5 = FormulaHelpers$8;
21006
21318
 
21007
21319
  const ReferenceFunctions$1 = {
@@ -32629,7 +32941,7 @@ var parsing = {
32629
32941
  const FormulaError$4 = requireError();
32630
32942
  const {Address: Address$1} = requireHelpers();
32631
32943
  const {Prefix: Prefix$1, Postfix: Postfix$1, Infix: Infix$1, Operators: Operators$1} = operators;
32632
- const Collection$1 = requireCollection();
32944
+ const Collection$1 = collection;
32633
32945
  const MAX_ROW$1 = 1048576, MAX_COLUMN$1 = 16384;
32634
32946
  const {NotAllInputParsedException} = require$$4;
32635
32947
 
@@ -33391,7 +33703,7 @@ var hooks$1 = {
33391
33703
  const FormulaError$2 = requireError();
33392
33704
  const {FormulaHelpers: FormulaHelpers$1, Types, Address} = requireHelpers();
33393
33705
  const {Prefix, Postfix, Infix, Operators} = operators;
33394
- const Collection = requireCollection();
33706
+ const Collection = collection;
33395
33707
  const MAX_ROW = 1048576, MAX_COLUMN = 16384;
33396
33708
 
33397
33709
  let Utils$1 = class Utils {
@@ -58051,6 +58363,10 @@ exports.SwipeActions = SwipeActions;
58051
58363
  exports.SwipeableCard = SwipeableCard;
58052
58364
  exports.Switch = Switch;
58053
58365
  exports.Tabs = Tabs;
58366
+ exports.TabsContent = TabsContent;
58367
+ exports.TabsList = TabsList;
58368
+ exports.TabsRoot = TabsRoot;
58369
+ exports.TabsTrigger = TabsTrigger;
58054
58370
  exports.Text = Text;
58055
58371
  exports.Textarea = Textarea;
58056
58372
  exports.ThemeToggle = ThemeToggle;