@papernote/ui 1.10.4 → 1.10.6

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.esm.js CHANGED
@@ -2161,6 +2161,286 @@ const validationMessageColors = {
2161
2161
  warning: 'text-warning-600',
2162
2162
  };
2163
2163
 
2164
+ /**
2165
+ * Get the current UTC offset for a timezone
2166
+ */
2167
+ function getTimezoneOffset(timezone) {
2168
+ try {
2169
+ const now = new Date();
2170
+ const formatter = new Intl.DateTimeFormat('en-US', {
2171
+ timeZone: timezone,
2172
+ timeZoneName: 'longOffset',
2173
+ });
2174
+ const parts = formatter.formatToParts(now);
2175
+ const tzPart = parts.find(p => p.type === 'timeZoneName');
2176
+ if (tzPart?.value) {
2177
+ // Extract offset from format like "GMT-05:00" or "GMT+05:30"
2178
+ const match = tzPart.value.match(/GMT([+-]\d{2}:\d{2})/);
2179
+ if (match) {
2180
+ const offsetStr = match[1];
2181
+ const [hours, minutes] = offsetStr.split(':').map(Number);
2182
+ const sign = offsetStr.startsWith('-') ? -1 : 1;
2183
+ const totalMinutes = sign * (Math.abs(hours) * 60 + minutes);
2184
+ return {
2185
+ offset: `UTC${offsetStr}`,
2186
+ offsetMinutes: totalMinutes,
2187
+ };
2188
+ }
2189
+ // Handle "GMT" (UTC+00:00)
2190
+ if (tzPart.value === 'GMT') {
2191
+ return { offset: 'UTC+00:00', offsetMinutes: 0 };
2192
+ }
2193
+ }
2194
+ // Fallback: calculate manually
2195
+ const utcDate = new Date(now.toLocaleString('en-US', { timeZone: 'UTC' }));
2196
+ const tzDate = new Date(now.toLocaleString('en-US', { timeZone: timezone }));
2197
+ const diffMinutes = (tzDate.getTime() - utcDate.getTime()) / 60000;
2198
+ const hours = Math.floor(Math.abs(diffMinutes) / 60);
2199
+ const mins = Math.abs(diffMinutes) % 60;
2200
+ const sign = diffMinutes >= 0 ? '+' : '-';
2201
+ const offset = `UTC${sign}${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}`;
2202
+ return { offset, offsetMinutes: diffMinutes };
2203
+ }
2204
+ catch {
2205
+ return { offset: 'UTC+00:00', offsetMinutes: 0 };
2206
+ }
2207
+ }
2208
+ /**
2209
+ * Comprehensive list of common timezones organized by region
2210
+ */
2211
+ const TIMEZONE_DATA = {
2212
+ Africa: [
2213
+ 'Africa/Cairo',
2214
+ 'Africa/Casablanca',
2215
+ 'Africa/Johannesburg',
2216
+ 'Africa/Lagos',
2217
+ 'Africa/Nairobi',
2218
+ 'Africa/Tunis',
2219
+ ],
2220
+ America: [
2221
+ 'America/Anchorage',
2222
+ 'America/Argentina/Buenos_Aires',
2223
+ 'America/Bogota',
2224
+ 'America/Caracas',
2225
+ 'America/Chicago',
2226
+ 'America/Denver',
2227
+ 'America/Halifax',
2228
+ 'America/Lima',
2229
+ 'America/Los_Angeles',
2230
+ 'America/Mexico_City',
2231
+ 'America/New_York',
2232
+ 'America/Phoenix',
2233
+ 'America/Santiago',
2234
+ 'America/Sao_Paulo',
2235
+ 'America/Toronto',
2236
+ 'America/Vancouver',
2237
+ ],
2238
+ Antarctica: [
2239
+ 'Antarctica/McMurdo',
2240
+ 'Antarctica/Palmer',
2241
+ 'Antarctica/Syowa',
2242
+ ],
2243
+ Asia: [
2244
+ 'Asia/Bangkok',
2245
+ 'Asia/Colombo',
2246
+ 'Asia/Dhaka',
2247
+ 'Asia/Dubai',
2248
+ 'Asia/Hong_Kong',
2249
+ 'Asia/Jakarta',
2250
+ 'Asia/Jerusalem',
2251
+ 'Asia/Karachi',
2252
+ 'Asia/Kathmandu',
2253
+ 'Asia/Kolkata',
2254
+ 'Asia/Kuala_Lumpur',
2255
+ 'Asia/Manila',
2256
+ 'Asia/Qatar',
2257
+ 'Asia/Riyadh',
2258
+ 'Asia/Seoul',
2259
+ 'Asia/Shanghai',
2260
+ 'Asia/Singapore',
2261
+ 'Asia/Taipei',
2262
+ 'Asia/Tehran',
2263
+ 'Asia/Tokyo',
2264
+ ],
2265
+ Atlantic: [
2266
+ 'Atlantic/Azores',
2267
+ 'Atlantic/Bermuda',
2268
+ 'Atlantic/Canary',
2269
+ 'Atlantic/Cape_Verde',
2270
+ 'Atlantic/Reykjavik',
2271
+ ],
2272
+ Australia: [
2273
+ 'Australia/Adelaide',
2274
+ 'Australia/Brisbane',
2275
+ 'Australia/Darwin',
2276
+ 'Australia/Hobart',
2277
+ 'Australia/Melbourne',
2278
+ 'Australia/Perth',
2279
+ 'Australia/Sydney',
2280
+ ],
2281
+ Europe: [
2282
+ 'Europe/Amsterdam',
2283
+ 'Europe/Athens',
2284
+ 'Europe/Berlin',
2285
+ 'Europe/Brussels',
2286
+ 'Europe/Bucharest',
2287
+ 'Europe/Budapest',
2288
+ 'Europe/Copenhagen',
2289
+ 'Europe/Dublin',
2290
+ 'Europe/Helsinki',
2291
+ 'Europe/Istanbul',
2292
+ 'Europe/Lisbon',
2293
+ 'Europe/London',
2294
+ 'Europe/Madrid',
2295
+ 'Europe/Moscow',
2296
+ 'Europe/Oslo',
2297
+ 'Europe/Paris',
2298
+ 'Europe/Prague',
2299
+ 'Europe/Rome',
2300
+ 'Europe/Stockholm',
2301
+ 'Europe/Vienna',
2302
+ 'Europe/Warsaw',
2303
+ 'Europe/Zurich',
2304
+ ],
2305
+ Indian: [
2306
+ 'Indian/Maldives',
2307
+ 'Indian/Mauritius',
2308
+ ],
2309
+ Pacific: [
2310
+ 'Pacific/Auckland',
2311
+ 'Pacific/Fiji',
2312
+ 'Pacific/Guam',
2313
+ 'Pacific/Honolulu',
2314
+ 'Pacific/Noumea',
2315
+ 'Pacific/Pago_Pago',
2316
+ 'Pacific/Tahiti',
2317
+ ],
2318
+ };
2319
+ /**
2320
+ * Region display names
2321
+ */
2322
+ const REGION_LABELS = {
2323
+ Africa: 'Africa',
2324
+ America: 'Americas',
2325
+ Antarctica: 'Antarctica',
2326
+ Asia: 'Asia',
2327
+ Atlantic: 'Atlantic',
2328
+ Australia: 'Australia',
2329
+ Europe: 'Europe',
2330
+ Indian: 'Indian Ocean',
2331
+ Pacific: 'Pacific',
2332
+ };
2333
+ /**
2334
+ * Get the user's local timezone
2335
+ */
2336
+ function getLocalTimezone() {
2337
+ try {
2338
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
2339
+ }
2340
+ catch {
2341
+ return 'UTC';
2342
+ }
2343
+ }
2344
+ /**
2345
+ * Check if a timezone string is valid
2346
+ */
2347
+ function isValidTimezone(timezone) {
2348
+ try {
2349
+ Intl.DateTimeFormat(undefined, { timeZone: timezone });
2350
+ return true;
2351
+ }
2352
+ catch {
2353
+ return false;
2354
+ }
2355
+ }
2356
+ /**
2357
+ * TimezoneSelector - A searchable dropdown for selecting timezones
2358
+ *
2359
+ * Provides a user-friendly way to select from IANA timezones, organized by
2360
+ * geographic region with UTC offset display.
2361
+ *
2362
+ * @example Basic usage
2363
+ * ```tsx
2364
+ * const [timezone, setTimezone] = useState('America/New_York');
2365
+ *
2366
+ * <TimezoneSelector
2367
+ * label="Select Timezone"
2368
+ * value={timezone}
2369
+ * onChange={setTimezone}
2370
+ * />
2371
+ * ```
2372
+ *
2373
+ * @example With local timezone detection
2374
+ * ```tsx
2375
+ * import { TimezoneSelector, getLocalTimezone } from 'notebook-ui';
2376
+ *
2377
+ * const [timezone, setTimezone] = useState(getLocalTimezone());
2378
+ *
2379
+ * <TimezoneSelector
2380
+ * value={timezone}
2381
+ * onChange={setTimezone}
2382
+ * includeUTC
2383
+ * clearable
2384
+ * />
2385
+ * ```
2386
+ *
2387
+ * @example Filter to specific regions
2388
+ * ```tsx
2389
+ * <TimezoneSelector
2390
+ * value={timezone}
2391
+ * onChange={setTimezone}
2392
+ * regions={['America', 'Europe']}
2393
+ * placeholder="Select US or European timezone..."
2394
+ * />
2395
+ * ```
2396
+ */
2397
+ const TimezoneSelector = React__default.forwardRef(({ value, onChange, label, helperText, error, disabled = false, placeholder = 'Select timezone...', clearable = false, loading = false, size = 'md', includeUTC = true, regions, showOffset = true, }, ref) => {
2398
+ // Build timezone options grouped by region
2399
+ const groups = useMemo(() => {
2400
+ const result = [];
2401
+ // Add UTC as a special option at the top
2402
+ if (includeUTC) {
2403
+ result.push({
2404
+ label: 'Universal',
2405
+ options: [
2406
+ {
2407
+ value: 'UTC',
2408
+ label: showOffset ? 'UTC (UTC+00:00)' : 'UTC',
2409
+ },
2410
+ ],
2411
+ });
2412
+ }
2413
+ // Determine which regions to include
2414
+ const activeRegions = regions || Object.keys(TIMEZONE_DATA);
2415
+ // Build groups for each region
2416
+ for (const region of activeRegions) {
2417
+ const timezones = TIMEZONE_DATA[region];
2418
+ if (!timezones)
2419
+ continue;
2420
+ // Build options with offset info and sort by offset
2421
+ const options = timezones
2422
+ .map(tz => {
2423
+ const { offset, offsetMinutes } = getTimezoneOffset(tz);
2424
+ const city = tz.split('/').pop()?.replace(/_/g, ' ') || tz;
2425
+ return {
2426
+ value: tz,
2427
+ label: showOffset ? `${city} (${offset})` : city,
2428
+ offsetMinutes,
2429
+ };
2430
+ })
2431
+ .sort((a, b) => a.offsetMinutes - b.offsetMinutes)
2432
+ .map(({ value, label }) => ({ value, label }));
2433
+ result.push({
2434
+ label: REGION_LABELS[region],
2435
+ options,
2436
+ });
2437
+ }
2438
+ return result;
2439
+ }, [includeUTC, regions, showOffset]);
2440
+ return (jsx(Select, { ref: ref, groups: groups, value: value, onChange: onChange, label: label, helperText: helperText, error: error, disabled: disabled, placeholder: placeholder, clearable: clearable, loading: loading, size: size, searchable: true, virtualized: false }));
2441
+ });
2442
+ TimezoneSelector.displayName = 'TimezoneSelector';
2443
+
2164
2444
  /**
2165
2445
  * Combobox component - searchable select with typeahead and custom value support.
2166
2446
  *
@@ -3066,7 +3346,7 @@ const toastStyles = {
3066
3346
  icon: jsx(Info, { className: "h-5 w-5 text-primary-600" }),
3067
3347
  },
3068
3348
  };
3069
- function Toast({ id, type, title, message, duration = 5000, onClose }) {
3349
+ function Toast({ id, type, title, message, duration = 5000, onClose, action }) {
3070
3350
  const [isExiting, setIsExiting] = useState(false);
3071
3351
  const styles = toastStyles[type];
3072
3352
  const handleClose = useCallback(() => {
@@ -3075,13 +3355,19 @@ function Toast({ id, type, title, message, duration = 5000, onClose }) {
3075
3355
  onClose(id);
3076
3356
  }, 300); // Match animation duration
3077
3357
  }, [id, onClose]);
3358
+ const handleAction = useCallback(() => {
3359
+ if (action) {
3360
+ action.onClick();
3361
+ handleClose();
3362
+ }
3363
+ }, [action, handleClose]);
3078
3364
  useEffect(() => {
3079
3365
  const timer = setTimeout(() => {
3080
3366
  handleClose();
3081
3367
  }, duration);
3082
3368
  return () => clearTimeout(timer);
3083
3369
  }, [duration, handleClose]);
3084
- return (jsx("div", { className: `${styles.bg} ${styles.border} bg-subtle-grain rounded-lg shadow-lg p-4 min-w-[320px] max-w-md transition-all duration-300 ${isExiting ? 'opacity-0 translate-x-full' : 'opacity-100 translate-x-0 animate-slide-in-right'}`, children: jsxs("div", { className: "flex items-start gap-3", children: [jsx("div", { className: "flex-shrink-0 mt-0.5", children: styles.icon }), jsxs("div", { className: "flex-1 min-w-0", children: [jsx("h4", { className: "text-sm font-medium text-ink-900 mb-1", children: title }), jsx("p", { className: "text-sm text-ink-600", children: message })] }), jsx("button", { onClick: handleClose, className: "flex-shrink-0 text-ink-400 hover:text-ink-600 transition-colors", "aria-label": "Close notification", children: jsx(X, { className: "h-4 w-4" }) })] }) }));
3370
+ return (jsx("div", { className: `${styles.bg} ${styles.border} bg-subtle-grain rounded-lg shadow-lg p-4 min-w-[320px] max-w-md transition-all duration-300 ${isExiting ? 'opacity-0 translate-x-full' : 'opacity-100 translate-x-0 animate-slide-in-right'}`, children: jsxs("div", { className: "flex items-start gap-3", children: [jsx("div", { className: "flex-shrink-0 mt-0.5", children: styles.icon }), jsxs("div", { className: "flex-1 min-w-0", children: [jsx("h4", { className: "text-sm font-medium text-ink-900 mb-1", children: title }), jsx("p", { className: "text-sm text-ink-600", children: message }), action && (jsx("button", { onClick: handleAction, className: "mt-2 text-sm font-medium text-accent-600 hover:text-accent-700 transition-colors", children: action.label }))] }), jsx("button", { onClick: handleClose, className: "flex-shrink-0 text-ink-400 hover:text-ink-600 transition-colors", "aria-label": "Close notification", children: jsx(X, { className: "h-4 w-4" }) })] }) }));
3085
3371
  }
3086
3372
  const positionStyles = {
3087
3373
  'top-right': 'top-20 right-6',
@@ -7816,6 +8102,289 @@ function SwipeableCard({ children, onSwipeRight, onSwipeLeft, rightAction = {
7816
8102
  }, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, onMouseDown: handleMouseDown, role: "button", "aria-label": `Swipeable card. ${onSwipeRight ? `Swipe right to ${rightAction.label}.` : ''} ${onSwipeLeft ? `Swipe left to ${leftAction.label}.` : ''}`, tabIndex: disabled ? -1 : 0, children: children })] }));
7817
8103
  }
7818
8104
 
8105
+ // Color classes for action backgrounds
8106
+ const getColorClass = (color) => {
8107
+ const colorMap = {
8108
+ destructive: 'bg-error-500',
8109
+ warning: 'bg-warning-500',
8110
+ success: 'bg-success-500',
8111
+ primary: 'bg-accent-500',
8112
+ };
8113
+ return colorMap[color] || color;
8114
+ };
8115
+ /**
8116
+ * SwipeableListItem - List item with swipe-to-action functionality
8117
+ *
8118
+ * Designed for mobile workflows with keyboard accessibility:
8119
+ * - Swipe right to approve/confirm
8120
+ * - Swipe left to dismiss/delete
8121
+ * - Arrow keys for keyboard navigation
8122
+ * - Async callback support with loading state
8123
+ *
8124
+ * @example
8125
+ * ```tsx
8126
+ * <SwipeableListItem
8127
+ * onSwipeRight={() => handleApprove()}
8128
+ * onSwipeLeft={() => handleDismiss()}
8129
+ * rightAction={{
8130
+ * icon: Check,
8131
+ * color: 'success',
8132
+ * label: 'Approve'
8133
+ * }}
8134
+ * leftAction={{
8135
+ * icon: X,
8136
+ * color: 'destructive',
8137
+ * label: 'Dismiss'
8138
+ * }}
8139
+ * >
8140
+ * <div className="p-4">List item content</div>
8141
+ * </SwipeableListItem>
8142
+ * ```
8143
+ */
8144
+ function SwipeableListItem({ children, onSwipeRight, onSwipeLeft, rightAction, leftAction, swipeThreshold = 100, disabled = false, className = '', }) {
8145
+ const containerRef = useRef(null);
8146
+ const [isDragging, setIsDragging] = useState(false);
8147
+ const [offsetX, setOffsetX] = useState(0);
8148
+ const [isTriggered, setIsTriggered] = useState(null);
8149
+ const [isLoading, setIsLoading] = useState(false);
8150
+ const [keyboardDirection, setKeyboardDirection] = useState(null);
8151
+ const startX = useRef(0);
8152
+ const startY = useRef(0);
8153
+ const isHorizontalSwipe = useRef(null);
8154
+ // Trigger haptic feedback
8155
+ const triggerHaptic = useCallback((style = 'medium') => {
8156
+ if ('vibrate' in navigator) {
8157
+ const patterns = {
8158
+ light: 10,
8159
+ medium: 25,
8160
+ heavy: [50, 30, 50],
8161
+ };
8162
+ navigator.vibrate(patterns[style]);
8163
+ }
8164
+ }, []);
8165
+ // Execute action with async support
8166
+ const executeAction = useCallback(async (direction) => {
8167
+ const handler = direction === 'right' ? onSwipeRight : onSwipeLeft;
8168
+ if (!handler)
8169
+ return;
8170
+ setIsLoading(true);
8171
+ triggerHaptic('heavy');
8172
+ // Animate out
8173
+ const slideDistance = direction === 'right' ? window.innerWidth : -window.innerWidth;
8174
+ setOffsetX(slideDistance);
8175
+ try {
8176
+ await handler();
8177
+ }
8178
+ finally {
8179
+ // Reset state after animation
8180
+ setTimeout(() => {
8181
+ setOffsetX(0);
8182
+ setIsTriggered(null);
8183
+ setIsLoading(false);
8184
+ setKeyboardDirection(null);
8185
+ }, 200);
8186
+ }
8187
+ }, [onSwipeRight, onSwipeLeft, triggerHaptic]);
8188
+ // Handle drag start
8189
+ const handleDragStart = useCallback((clientX, clientY) => {
8190
+ if (disabled || isLoading)
8191
+ return;
8192
+ setIsDragging(true);
8193
+ startX.current = clientX;
8194
+ startY.current = clientY;
8195
+ isHorizontalSwipe.current = null;
8196
+ }, [disabled, isLoading]);
8197
+ // Handle drag move
8198
+ const handleDragMove = useCallback((clientX, clientY) => {
8199
+ if (!isDragging || disabled || isLoading)
8200
+ return;
8201
+ const deltaX = clientX - startX.current;
8202
+ const deltaY = clientY - startY.current;
8203
+ // Determine if this is a horizontal swipe on first significant movement
8204
+ if (isHorizontalSwipe.current === null) {
8205
+ const absDeltaX = Math.abs(deltaX);
8206
+ const absDeltaY = Math.abs(deltaY);
8207
+ if (absDeltaX > 10 || absDeltaY > 10) {
8208
+ isHorizontalSwipe.current = absDeltaX > absDeltaY;
8209
+ }
8210
+ }
8211
+ // Only process horizontal swipes
8212
+ if (isHorizontalSwipe.current !== true)
8213
+ return;
8214
+ // Check if we should allow this direction
8215
+ const canSwipeRight = onSwipeRight !== undefined && rightAction !== undefined;
8216
+ const canSwipeLeft = onSwipeLeft !== undefined && leftAction !== undefined;
8217
+ let newOffset = deltaX;
8218
+ // Limit swipe direction based on available actions
8219
+ if (!canSwipeRight && deltaX > 0)
8220
+ newOffset = 0;
8221
+ if (!canSwipeLeft && deltaX < 0)
8222
+ newOffset = 0;
8223
+ // Add resistance when exceeding threshold
8224
+ const maxSwipe = swipeThreshold * 1.5;
8225
+ if (Math.abs(newOffset) > swipeThreshold) {
8226
+ const overflow = Math.abs(newOffset) - swipeThreshold;
8227
+ const resistance = overflow * 0.3;
8228
+ newOffset = newOffset > 0
8229
+ ? swipeThreshold + resistance
8230
+ : -(swipeThreshold + resistance);
8231
+ newOffset = Math.max(-maxSwipe, Math.min(maxSwipe, newOffset));
8232
+ }
8233
+ setOffsetX(newOffset);
8234
+ // Check for threshold crossing and trigger haptic
8235
+ const newTriggered = Math.abs(newOffset) >= swipeThreshold
8236
+ ? (newOffset > 0 ? 'right' : 'left')
8237
+ : null;
8238
+ if (newTriggered !== isTriggered) {
8239
+ if (newTriggered) {
8240
+ triggerHaptic('medium');
8241
+ }
8242
+ setIsTriggered(newTriggered);
8243
+ }
8244
+ }, [isDragging, disabled, isLoading, onSwipeRight, onSwipeLeft, rightAction, leftAction, swipeThreshold, isTriggered, triggerHaptic]);
8245
+ // Handle drag end
8246
+ const handleDragEnd = useCallback(() => {
8247
+ if (!isDragging)
8248
+ return;
8249
+ setIsDragging(false);
8250
+ // Check if action should be triggered
8251
+ if (Math.abs(offsetX) >= swipeThreshold) {
8252
+ if (offsetX > 0 && onSwipeRight && rightAction) {
8253
+ executeAction('right');
8254
+ return;
8255
+ }
8256
+ else if (offsetX < 0 && onSwipeLeft && leftAction) {
8257
+ executeAction('left');
8258
+ return;
8259
+ }
8260
+ }
8261
+ // Snap back
8262
+ setOffsetX(0);
8263
+ setIsTriggered(null);
8264
+ }, [isDragging, offsetX, swipeThreshold, onSwipeRight, onSwipeLeft, rightAction, leftAction, executeAction]);
8265
+ // Touch event handlers
8266
+ const handleTouchStart = (e) => {
8267
+ handleDragStart(e.touches[0].clientX, e.touches[0].clientY);
8268
+ };
8269
+ const handleTouchMove = (e) => {
8270
+ handleDragMove(e.touches[0].clientX, e.touches[0].clientY);
8271
+ // Prevent vertical scroll if horizontal swipe
8272
+ if (isHorizontalSwipe.current === true) {
8273
+ e.preventDefault();
8274
+ }
8275
+ };
8276
+ const handleTouchEnd = () => {
8277
+ handleDragEnd();
8278
+ };
8279
+ // Mouse event handlers (for desktop testing)
8280
+ const handleMouseDown = (e) => {
8281
+ handleDragStart(e.clientX, e.clientY);
8282
+ };
8283
+ useEffect(() => {
8284
+ if (!isDragging)
8285
+ return;
8286
+ const handleMouseMove = (e) => {
8287
+ handleDragMove(e.clientX, e.clientY);
8288
+ };
8289
+ const handleMouseUp = () => {
8290
+ handleDragEnd();
8291
+ };
8292
+ document.addEventListener('mousemove', handleMouseMove);
8293
+ document.addEventListener('mouseup', handleMouseUp);
8294
+ return () => {
8295
+ document.removeEventListener('mousemove', handleMouseMove);
8296
+ document.removeEventListener('mouseup', handleMouseUp);
8297
+ };
8298
+ }, [isDragging, handleDragMove, handleDragEnd]);
8299
+ // Keyboard event handlers
8300
+ const handleKeyDown = useCallback((e) => {
8301
+ if (disabled || isLoading)
8302
+ return;
8303
+ const canSwipeRight = onSwipeRight !== undefined && rightAction !== undefined;
8304
+ const canSwipeLeft = onSwipeLeft !== undefined && leftAction !== undefined;
8305
+ switch (e.key) {
8306
+ case 'ArrowRight':
8307
+ if (canSwipeRight) {
8308
+ e.preventDefault();
8309
+ setKeyboardDirection('right');
8310
+ setOffsetX(swipeThreshold);
8311
+ setIsTriggered('right');
8312
+ triggerHaptic('medium');
8313
+ }
8314
+ break;
8315
+ case 'ArrowLeft':
8316
+ if (canSwipeLeft) {
8317
+ e.preventDefault();
8318
+ setKeyboardDirection('left');
8319
+ setOffsetX(-swipeThreshold);
8320
+ setIsTriggered('left');
8321
+ triggerHaptic('medium');
8322
+ }
8323
+ break;
8324
+ case 'Enter':
8325
+ if (keyboardDirection) {
8326
+ e.preventDefault();
8327
+ executeAction(keyboardDirection);
8328
+ }
8329
+ break;
8330
+ case 'Escape':
8331
+ if (keyboardDirection) {
8332
+ e.preventDefault();
8333
+ setKeyboardDirection(null);
8334
+ setOffsetX(0);
8335
+ setIsTriggered(null);
8336
+ }
8337
+ break;
8338
+ }
8339
+ }, [disabled, isLoading, onSwipeRight, onSwipeLeft, rightAction, leftAction, swipeThreshold, keyboardDirection, executeAction, triggerHaptic]);
8340
+ // Reset keyboard state on blur
8341
+ const handleBlur = useCallback(() => {
8342
+ if (keyboardDirection) {
8343
+ setKeyboardDirection(null);
8344
+ setOffsetX(0);
8345
+ setIsTriggered(null);
8346
+ }
8347
+ }, [keyboardDirection]);
8348
+ // Calculate action opacity based on swipe distance
8349
+ const rightActionOpacity = offsetX > 0 ? Math.min(1, offsetX / swipeThreshold) : 0;
8350
+ const leftActionOpacity = offsetX < 0 ? Math.min(1, Math.abs(offsetX) / swipeThreshold) : 0;
8351
+ // Build aria-label
8352
+ const ariaLabel = [
8353
+ 'Swipeable list item.',
8354
+ rightAction && onSwipeRight ? `Swipe right or press Arrow Right to ${rightAction.label}.` : '',
8355
+ leftAction && onSwipeLeft ? `Swipe left or press Arrow Left to ${leftAction.label}.` : '',
8356
+ keyboardDirection ? `Press Enter to confirm or Escape to cancel.` : '',
8357
+ ].filter(Boolean).join(' ');
8358
+ return (jsxs("div", { ref: containerRef, className: `relative overflow-hidden ${className}`, children: [rightAction && onSwipeRight && (jsx("div", { className: `
8359
+ absolute inset-y-0 left-0 flex items-center justify-start pl-6
8360
+ ${getColorClass(rightAction.color)}
8361
+ transition-opacity duration-100
8362
+ `, style: {
8363
+ opacity: rightActionOpacity,
8364
+ width: Math.abs(offsetX) + 20,
8365
+ }, "aria-hidden": "true", children: jsx("div", { className: `
8366
+ text-white transform transition-transform duration-200
8367
+ ${isTriggered === 'right' ? 'scale-125' : 'scale-100'}
8368
+ `, children: isLoading && isTriggered === 'right' ? (jsx(Loader2, { className: "h-6 w-6 animate-spin" })) : (jsx(rightAction.icon, { className: "h-6 w-6" })) }) })), leftAction && onSwipeLeft && (jsx("div", { className: `
8369
+ absolute inset-y-0 right-0 flex items-center justify-end pr-6
8370
+ ${getColorClass(leftAction.color)}
8371
+ transition-opacity duration-100
8372
+ `, style: {
8373
+ opacity: leftActionOpacity,
8374
+ width: Math.abs(offsetX) + 20,
8375
+ }, "aria-hidden": "true", children: jsx("div", { className: `
8376
+ text-white transform transition-transform duration-200
8377
+ ${isTriggered === 'left' ? 'scale-125' : 'scale-100'}
8378
+ `, children: isLoading && isTriggered === 'left' ? (jsx(Loader2, { className: "h-6 w-6 animate-spin" })) : (jsx(leftAction.icon, { className: "h-6 w-6" })) }) })), jsx("div", { className: `
8379
+ relative bg-white
8380
+ ${isDragging ? '' : 'transition-transform duration-200 ease-out'}
8381
+ ${disabled ? 'opacity-50 pointer-events-none' : ''}
8382
+ ${keyboardDirection ? 'ring-2 ring-accent-500 ring-inset' : ''}
8383
+ `, style: {
8384
+ transform: `translateX(${offsetX}px)`,
8385
+ }, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, onMouseDown: handleMouseDown, onKeyDown: handleKeyDown, onBlur: handleBlur, role: "button", "aria-label": ariaLabel, tabIndex: disabled ? -1 : 0, children: children })] }));
8386
+ }
8387
+
7819
8388
  /**
7820
8389
  * NotificationBanner - Dismissible banner for important alerts
7821
8390
  *
@@ -11510,14 +12079,17 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
11510
12079
  const columnKey = String(column.key);
11511
12080
  const dynamicWidth = columnWidths[columnKey];
11512
12081
  return (jsx("col", { style: getColumnStyle(column, dynamicWidth) }, index));
11513
- })] }), jsx("thead", { className: `bg-paper-100 sticky top-0 z-10 ${headerClassName}`, children: jsxs("tr", { className: "table-header-row", children: [selectable && (jsx("th", { className: `sticky left-0 bg-paper-100 ${currentDensity.header} border-b ${borderColor} z-20 w-12 ${bordered ? `border ${borderColor}` : ''}`, children: jsx("input", { type: "checkbox", checked: selectedRowsSet.size === data.length && data.length > 0, onChange: handleSelectAll, className: "w-4 h-4 text-accent-600 border-paper-300 rounded focus:ring-accent-400", "aria-label": "Select all rows" }) })), ((expandable || expandedRowConfig) && showExpandChevron) && (jsx("th", { className: `sticky left-0 bg-paper-100 px-2 ${currentDensity.header} border-b ${borderColor} z-19 w-10 ${bordered ? `border ${borderColor}` : ''}` })), allActions.length > 0 && (jsx("th", { className: `sticky left-0 bg-paper-100 ${currentDensity.header} text-center text-xs font-medium text-ink-700 uppercase tracking-wider border-b ${borderColor} z-20 ${bordered ? `border ${borderColor}` : ''}`, style: { width: '28px', padding: 0 } })), visibleColumns.map((column) => {
12082
+ })] }), jsx("thead", { className: `bg-paper-100 sticky top-0 z-10 ${headerClassName}`, children: jsxs("tr", { className: "table-header-row", children: [selectable && (jsx("th", { className: `sticky left-0 bg-paper-100 ${currentDensity.header} border-b ${borderColor} z-20 w-12 ${bordered ? `border ${borderColor}` : ''}`, children: jsx("input", { type: "checkbox", checked: selectedRowsSet.size === data.length && data.length > 0, onChange: handleSelectAll, className: "w-4 h-4 text-accent-600 border-paper-300 rounded focus:ring-accent-400", "aria-label": "Select all rows" }) })), ((expandable || expandedRowConfig) && showExpandChevron) && (jsx("th", { className: `sticky left-0 bg-paper-100 px-2 ${currentDensity.header} border-b ${borderColor} z-19 w-10 ${bordered ? `border ${borderColor}` : ''}` })), allActions.length > 0 && (jsx("th", { className: `sticky left-0 bg-paper-100 text-center text-xs font-medium text-ink-700 uppercase tracking-wider border-b ${borderColor} z-20 ${bordered ? `border ${borderColor}` : ''}`, style: { width: '28px', padding: '0' } })), visibleColumns.map((column, colIdx) => {
11514
12083
  const columnKey = String(column.key);
11515
12084
  const dynamicWidth = columnWidths[columnKey];
11516
12085
  const thRef = useRef(null);
11517
12086
  const isDragging = draggingColumn === columnKey;
11518
12087
  const isDragOver = dragOverColumn === columnKey;
12088
+ // Reduce left padding on first column when there are action buttons (match body cells)
12089
+ const isFirstColumn = colIdx === 0;
12090
+ const headerPaddingClass = isFirstColumn && allActions.length > 0 ? 'pl-3' : '';
11519
12091
  return (jsxs("th", { ref: thRef, draggable: reorderable, onDragStart: (e) => reorderable && handleDragStart(e, columnKey), onDragOver: (e) => reorderable && handleDragOver(e, columnKey), onDragEnd: handleDragEnd, onDrop: (e) => reorderable && handleDrop(e, columnKey), className: `
11520
- ${currentDensity.header} text-left border-b ${borderColor} ${bordered ? `border ${borderColor}` : ''} relative
12092
+ ${currentDensity.header} ${headerPaddingClass} text-left border-b ${borderColor} ${bordered ? `border ${borderColor}` : ''} relative
11521
12093
  ${reorderable ? 'cursor-move' : ''}
11522
12094
  ${isDragging ? 'opacity-50' : ''}
11523
12095
  ${isDragOver ? 'bg-accent-100' : ''}
@@ -58193,5 +58765,5 @@ function Responsive({ mobile, tablet, desktop, }) {
58193
58765
  return jsx(Fragment, { children: mobile || tablet || desktop });
58194
58766
  }
58195
58767
 
58196
- export { Accordion, ActionBar, ActionBarCenter, ActionBarLeft, ActionBarRight, ActionButton, AdminModal, Alert, AlertDialog, AppLayout, Autocomplete, Avatar, BREAKPOINTS, Badge, BottomNavigation, BottomNavigationSpacer, BottomSheet, BottomSheetActions, BottomSheetContent, BottomSheetHeader, Box, Breadcrumbs, Button, ButtonGroup, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CardView, Carousel, Checkbox, CheckboxList, Chip, ChipGroup, Collapsible, ColorPicker, Combobox, ComingSoon, CommandPalette, CompactStat, ConfirmDialog, ContextMenu, ControlBar, CurrencyDisplay, CurrencyInput, Dashboard, DashboardContent, DashboardHeader, DataGrid, DataTable, DataTableCardView, DateDisplay, DatePicker, DateRangePicker, DateTimePicker, DesktopOnly, Drawer, DrawerFooter, DropZone, Dropdown, DropdownTrigger, EmptyState, ErrorBoundary, ExpandablePanel, ExpandablePanelContainer, ExpandablePanelSpacer, ExpandableRowButton, ExpandableToolbar, ExpandedRowEditForm, ExportButton, FORMULA_CATEGORIES, FORMULA_DEFINITIONS, FORMULA_NAMES, FieldArray, FileUpload, FilterBar, FilterControls, FilterStatusBanner, FloatingActionButton, Form, FormContext, FormControl, FormWizard, Grid, GridItem, Hide, HorizontalScroll, HoverCard, InfiniteScroll, Input, KanbanBoard, Layout, Loading, LoadingOverlay, Logo, MarkdownEditor, MaskedInput, Menu, MenuDivider, MobileHeader, MobileHeaderSpacer, MobileLayout, MobileOnly, MobileProvider, Modal, ModalFooter, MultiSelect, NotificationBanner, NotificationBar, NotificationBell, NotificationIndicator, NumberInput, Page, PageHeader, PageLayout, PageNavigation, Pagination, PasswordInput, Popover, Progress, PullToRefresh, QueryTransparency, RadioGroup, Rating, Responsive, RichTextEditor, SearchBar, SearchableList, Select, Separator, Show, Sidebar, SidebarGroup, Skeleton, SkeletonCard$1 as SkeletonCard, SkeletonTable, Slider, Spreadsheet, SpreadsheetReport, Stack, StatCard, StatItem, StatsCardGrid, StatsGrid, StatusBadge, StatusBar, StepIndicator, Stepper, SwipeActions, SwipeableCard, Switch, Tabs, TabsContent, TabsList, TabsRoot, TabsTrigger, Text, Textarea, ThemeToggle, TimePicker, Timeline, Toast, ToastContainer, Tooltip, Transfer, TreeView, TwoColumnContent, UserProfileButton, addErrorMessage, addInfoMessage, addSuccessMessage, addWarningMessage, calculateColumnWidth, createActionsSection, createFiltersSection, createMultiSheetExcel, createPageControlsSection, createQueryDetailsSection, exportDataTableToExcel, exportToExcel, formatStatisticValue, formatStatistics, getFormula, getFormulasByCategory, loadColumnOrder, loadColumnWidths, reorderArray, saveColumnOrder, saveColumnWidths, searchFormulas, statusManager, useBreadcrumbReset, useBreakpoint, useBreakpointValue, useColumnReorder, useColumnResize, useCommandPalette, useConfirmDialog, useFABScroll, useFormContext, useIsDesktop, useIsMobile, useIsTablet, useIsTouchDevice, useMediaQuery, useMobileContext, useOrientation, usePrefersMobile, useResponsiveCallback, useSafeAreaInsets, useViewportSize, withMobileContext };
58768
+ export { Accordion, ActionBar, ActionBarCenter, ActionBarLeft, ActionBarRight, ActionButton, AdminModal, Alert, AlertDialog, AppLayout, Autocomplete, Avatar, BREAKPOINTS, Badge, BottomNavigation, BottomNavigationSpacer, BottomSheet, BottomSheetActions, BottomSheetContent, BottomSheetHeader, Box, Breadcrumbs, Button, ButtonGroup, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CardView, Carousel, Checkbox, CheckboxList, Chip, ChipGroup, Collapsible, ColorPicker, Combobox, ComingSoon, CommandPalette, CompactStat, ConfirmDialog, ContextMenu, ControlBar, CurrencyDisplay, CurrencyInput, Dashboard, DashboardContent, DashboardHeader, DataGrid, DataTable, DataTableCardView, DateDisplay, DatePicker, DateRangePicker, DateTimePicker, DesktopOnly, Drawer, DrawerFooter, DropZone, Dropdown, DropdownTrigger, EmptyState, ErrorBoundary, ExpandablePanel, ExpandablePanelContainer, ExpandablePanelSpacer, ExpandableRowButton, ExpandableToolbar, ExpandedRowEditForm, ExportButton, FORMULA_CATEGORIES, FORMULA_DEFINITIONS, FORMULA_NAMES, FieldArray, FileUpload, FilterBar, FilterControls, FilterStatusBanner, FloatingActionButton, Form, FormContext, FormControl, FormWizard, Grid, GridItem, Hide, HorizontalScroll, HoverCard, InfiniteScroll, Input, KanbanBoard, Layout, Loading, LoadingOverlay, Logo, MarkdownEditor, MaskedInput, Menu, MenuDivider, MobileHeader, MobileHeaderSpacer, MobileLayout, MobileOnly, MobileProvider, Modal, ModalFooter, MultiSelect, NotificationBanner, NotificationBar, NotificationBell, NotificationIndicator, NumberInput, Page, PageHeader, PageLayout, PageNavigation, Pagination, PasswordInput, Popover, Progress, PullToRefresh, QueryTransparency, RadioGroup, Rating, Responsive, RichTextEditor, SearchBar, SearchableList, Select, Separator, Show, Sidebar, SidebarGroup, Skeleton, SkeletonCard$1 as SkeletonCard, SkeletonTable, Slider, Spreadsheet, SpreadsheetReport, Stack, StatCard, StatItem, StatsCardGrid, StatsGrid, StatusBadge, StatusBar, StepIndicator, Stepper, SwipeActions, SwipeableCard, SwipeableListItem, Switch, Tabs, TabsContent, TabsList, TabsRoot, TabsTrigger, Text, Textarea, ThemeToggle, TimePicker, Timeline, TimezoneSelector, Toast, ToastContainer, Tooltip, Transfer, TreeView, TwoColumnContent, UserProfileButton, addErrorMessage, addInfoMessage, addSuccessMessage, addWarningMessage, calculateColumnWidth, createActionsSection, createFiltersSection, createMultiSheetExcel, createPageControlsSection, createQueryDetailsSection, exportDataTableToExcel, exportToExcel, formatStatisticValue, formatStatistics, getFormula, getFormulasByCategory, getLocalTimezone, isValidTimezone, loadColumnOrder, loadColumnWidths, reorderArray, saveColumnOrder, saveColumnWidths, searchFormulas, statusManager, useBreadcrumbReset, useBreakpoint, useBreakpointValue, useColumnReorder, useColumnResize, useCommandPalette, useConfirmDialog, useFABScroll, useFormContext, useIsDesktop, useIsMobile, useIsTablet, useIsTouchDevice, useMediaQuery, useMobileContext, useOrientation, usePrefersMobile, useResponsiveCallback, useSafeAreaInsets, useViewportSize, withMobileContext };
58197
58769
  //# sourceMappingURL=index.esm.js.map