@papernote/ui 1.10.4 → 1.10.5

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
@@ -2181,6 +2181,286 @@ const validationMessageColors = {
2181
2181
  warning: 'text-warning-600',
2182
2182
  };
2183
2183
 
2184
+ /**
2185
+ * Get the current UTC offset for a timezone
2186
+ */
2187
+ function getTimezoneOffset(timezone) {
2188
+ try {
2189
+ const now = new Date();
2190
+ const formatter = new Intl.DateTimeFormat('en-US', {
2191
+ timeZone: timezone,
2192
+ timeZoneName: 'longOffset',
2193
+ });
2194
+ const parts = formatter.formatToParts(now);
2195
+ const tzPart = parts.find(p => p.type === 'timeZoneName');
2196
+ if (tzPart?.value) {
2197
+ // Extract offset from format like "GMT-05:00" or "GMT+05:30"
2198
+ const match = tzPart.value.match(/GMT([+-]\d{2}:\d{2})/);
2199
+ if (match) {
2200
+ const offsetStr = match[1];
2201
+ const [hours, minutes] = offsetStr.split(':').map(Number);
2202
+ const sign = offsetStr.startsWith('-') ? -1 : 1;
2203
+ const totalMinutes = sign * (Math.abs(hours) * 60 + minutes);
2204
+ return {
2205
+ offset: `UTC${offsetStr}`,
2206
+ offsetMinutes: totalMinutes,
2207
+ };
2208
+ }
2209
+ // Handle "GMT" (UTC+00:00)
2210
+ if (tzPart.value === 'GMT') {
2211
+ return { offset: 'UTC+00:00', offsetMinutes: 0 };
2212
+ }
2213
+ }
2214
+ // Fallback: calculate manually
2215
+ const utcDate = new Date(now.toLocaleString('en-US', { timeZone: 'UTC' }));
2216
+ const tzDate = new Date(now.toLocaleString('en-US', { timeZone: timezone }));
2217
+ const diffMinutes = (tzDate.getTime() - utcDate.getTime()) / 60000;
2218
+ const hours = Math.floor(Math.abs(diffMinutes) / 60);
2219
+ const mins = Math.abs(diffMinutes) % 60;
2220
+ const sign = diffMinutes >= 0 ? '+' : '-';
2221
+ const offset = `UTC${sign}${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}`;
2222
+ return { offset, offsetMinutes: diffMinutes };
2223
+ }
2224
+ catch {
2225
+ return { offset: 'UTC+00:00', offsetMinutes: 0 };
2226
+ }
2227
+ }
2228
+ /**
2229
+ * Comprehensive list of common timezones organized by region
2230
+ */
2231
+ const TIMEZONE_DATA = {
2232
+ Africa: [
2233
+ 'Africa/Cairo',
2234
+ 'Africa/Casablanca',
2235
+ 'Africa/Johannesburg',
2236
+ 'Africa/Lagos',
2237
+ 'Africa/Nairobi',
2238
+ 'Africa/Tunis',
2239
+ ],
2240
+ America: [
2241
+ 'America/Anchorage',
2242
+ 'America/Argentina/Buenos_Aires',
2243
+ 'America/Bogota',
2244
+ 'America/Caracas',
2245
+ 'America/Chicago',
2246
+ 'America/Denver',
2247
+ 'America/Halifax',
2248
+ 'America/Lima',
2249
+ 'America/Los_Angeles',
2250
+ 'America/Mexico_City',
2251
+ 'America/New_York',
2252
+ 'America/Phoenix',
2253
+ 'America/Santiago',
2254
+ 'America/Sao_Paulo',
2255
+ 'America/Toronto',
2256
+ 'America/Vancouver',
2257
+ ],
2258
+ Antarctica: [
2259
+ 'Antarctica/McMurdo',
2260
+ 'Antarctica/Palmer',
2261
+ 'Antarctica/Syowa',
2262
+ ],
2263
+ Asia: [
2264
+ 'Asia/Bangkok',
2265
+ 'Asia/Colombo',
2266
+ 'Asia/Dhaka',
2267
+ 'Asia/Dubai',
2268
+ 'Asia/Hong_Kong',
2269
+ 'Asia/Jakarta',
2270
+ 'Asia/Jerusalem',
2271
+ 'Asia/Karachi',
2272
+ 'Asia/Kathmandu',
2273
+ 'Asia/Kolkata',
2274
+ 'Asia/Kuala_Lumpur',
2275
+ 'Asia/Manila',
2276
+ 'Asia/Qatar',
2277
+ 'Asia/Riyadh',
2278
+ 'Asia/Seoul',
2279
+ 'Asia/Shanghai',
2280
+ 'Asia/Singapore',
2281
+ 'Asia/Taipei',
2282
+ 'Asia/Tehran',
2283
+ 'Asia/Tokyo',
2284
+ ],
2285
+ Atlantic: [
2286
+ 'Atlantic/Azores',
2287
+ 'Atlantic/Bermuda',
2288
+ 'Atlantic/Canary',
2289
+ 'Atlantic/Cape_Verde',
2290
+ 'Atlantic/Reykjavik',
2291
+ ],
2292
+ Australia: [
2293
+ 'Australia/Adelaide',
2294
+ 'Australia/Brisbane',
2295
+ 'Australia/Darwin',
2296
+ 'Australia/Hobart',
2297
+ 'Australia/Melbourne',
2298
+ 'Australia/Perth',
2299
+ 'Australia/Sydney',
2300
+ ],
2301
+ Europe: [
2302
+ 'Europe/Amsterdam',
2303
+ 'Europe/Athens',
2304
+ 'Europe/Berlin',
2305
+ 'Europe/Brussels',
2306
+ 'Europe/Bucharest',
2307
+ 'Europe/Budapest',
2308
+ 'Europe/Copenhagen',
2309
+ 'Europe/Dublin',
2310
+ 'Europe/Helsinki',
2311
+ 'Europe/Istanbul',
2312
+ 'Europe/Lisbon',
2313
+ 'Europe/London',
2314
+ 'Europe/Madrid',
2315
+ 'Europe/Moscow',
2316
+ 'Europe/Oslo',
2317
+ 'Europe/Paris',
2318
+ 'Europe/Prague',
2319
+ 'Europe/Rome',
2320
+ 'Europe/Stockholm',
2321
+ 'Europe/Vienna',
2322
+ 'Europe/Warsaw',
2323
+ 'Europe/Zurich',
2324
+ ],
2325
+ Indian: [
2326
+ 'Indian/Maldives',
2327
+ 'Indian/Mauritius',
2328
+ ],
2329
+ Pacific: [
2330
+ 'Pacific/Auckland',
2331
+ 'Pacific/Fiji',
2332
+ 'Pacific/Guam',
2333
+ 'Pacific/Honolulu',
2334
+ 'Pacific/Noumea',
2335
+ 'Pacific/Pago_Pago',
2336
+ 'Pacific/Tahiti',
2337
+ ],
2338
+ };
2339
+ /**
2340
+ * Region display names
2341
+ */
2342
+ const REGION_LABELS = {
2343
+ Africa: 'Africa',
2344
+ America: 'Americas',
2345
+ Antarctica: 'Antarctica',
2346
+ Asia: 'Asia',
2347
+ Atlantic: 'Atlantic',
2348
+ Australia: 'Australia',
2349
+ Europe: 'Europe',
2350
+ Indian: 'Indian Ocean',
2351
+ Pacific: 'Pacific',
2352
+ };
2353
+ /**
2354
+ * Get the user's local timezone
2355
+ */
2356
+ function getLocalTimezone() {
2357
+ try {
2358
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
2359
+ }
2360
+ catch {
2361
+ return 'UTC';
2362
+ }
2363
+ }
2364
+ /**
2365
+ * Check if a timezone string is valid
2366
+ */
2367
+ function isValidTimezone(timezone) {
2368
+ try {
2369
+ Intl.DateTimeFormat(undefined, { timeZone: timezone });
2370
+ return true;
2371
+ }
2372
+ catch {
2373
+ return false;
2374
+ }
2375
+ }
2376
+ /**
2377
+ * TimezoneSelector - A searchable dropdown for selecting timezones
2378
+ *
2379
+ * Provides a user-friendly way to select from IANA timezones, organized by
2380
+ * geographic region with UTC offset display.
2381
+ *
2382
+ * @example Basic usage
2383
+ * ```tsx
2384
+ * const [timezone, setTimezone] = useState('America/New_York');
2385
+ *
2386
+ * <TimezoneSelector
2387
+ * label="Select Timezone"
2388
+ * value={timezone}
2389
+ * onChange={setTimezone}
2390
+ * />
2391
+ * ```
2392
+ *
2393
+ * @example With local timezone detection
2394
+ * ```tsx
2395
+ * import { TimezoneSelector, getLocalTimezone } from 'notebook-ui';
2396
+ *
2397
+ * const [timezone, setTimezone] = useState(getLocalTimezone());
2398
+ *
2399
+ * <TimezoneSelector
2400
+ * value={timezone}
2401
+ * onChange={setTimezone}
2402
+ * includeUTC
2403
+ * clearable
2404
+ * />
2405
+ * ```
2406
+ *
2407
+ * @example Filter to specific regions
2408
+ * ```tsx
2409
+ * <TimezoneSelector
2410
+ * value={timezone}
2411
+ * onChange={setTimezone}
2412
+ * regions={['America', 'Europe']}
2413
+ * placeholder="Select US or European timezone..."
2414
+ * />
2415
+ * ```
2416
+ */
2417
+ const TimezoneSelector = React.forwardRef(({ value, onChange, label, helperText, error, disabled = false, placeholder = 'Select timezone...', clearable = false, loading = false, size = 'md', includeUTC = true, regions, showOffset = true, }, ref) => {
2418
+ // Build timezone options grouped by region
2419
+ const groups = React.useMemo(() => {
2420
+ const result = [];
2421
+ // Add UTC as a special option at the top
2422
+ if (includeUTC) {
2423
+ result.push({
2424
+ label: 'Universal',
2425
+ options: [
2426
+ {
2427
+ value: 'UTC',
2428
+ label: showOffset ? 'UTC (UTC+00:00)' : 'UTC',
2429
+ },
2430
+ ],
2431
+ });
2432
+ }
2433
+ // Determine which regions to include
2434
+ const activeRegions = regions || Object.keys(TIMEZONE_DATA);
2435
+ // Build groups for each region
2436
+ for (const region of activeRegions) {
2437
+ const timezones = TIMEZONE_DATA[region];
2438
+ if (!timezones)
2439
+ continue;
2440
+ // Build options with offset info and sort by offset
2441
+ const options = timezones
2442
+ .map(tz => {
2443
+ const { offset, offsetMinutes } = getTimezoneOffset(tz);
2444
+ const city = tz.split('/').pop()?.replace(/_/g, ' ') || tz;
2445
+ return {
2446
+ value: tz,
2447
+ label: showOffset ? `${city} (${offset})` : city,
2448
+ offsetMinutes,
2449
+ };
2450
+ })
2451
+ .sort((a, b) => a.offsetMinutes - b.offsetMinutes)
2452
+ .map(({ value, label }) => ({ value, label }));
2453
+ result.push({
2454
+ label: REGION_LABELS[region],
2455
+ options,
2456
+ });
2457
+ }
2458
+ return result;
2459
+ }, [includeUTC, regions, showOffset]);
2460
+ return (jsxRuntime.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 }));
2461
+ });
2462
+ TimezoneSelector.displayName = 'TimezoneSelector';
2463
+
2184
2464
  /**
2185
2465
  * Combobox component - searchable select with typeahead and custom value support.
2186
2466
  *
@@ -11530,14 +11810,17 @@ mobileView = 'auto', cardConfig, cardGap = 'md', cardClassName, }) {
11530
11810
  const columnKey = String(column.key);
11531
11811
  const dynamicWidth = columnWidths[columnKey];
11532
11812
  return (jsxRuntime.jsx("col", { style: getColumnStyle(column, dynamicWidth) }, index));
11533
- })] }), jsxRuntime.jsx("thead", { className: `bg-paper-100 sticky top-0 z-10 ${headerClassName}`, children: jsxRuntime.jsxs("tr", { className: "table-header-row", children: [selectable && (jsxRuntime.jsx("th", { className: `sticky left-0 bg-paper-100 ${currentDensity.header} border-b ${borderColor} z-20 w-12 ${bordered ? `border ${borderColor}` : ''}`, children: jsxRuntime.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) && (jsxRuntime.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 && (jsxRuntime.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) => {
11813
+ })] }), jsxRuntime.jsx("thead", { className: `bg-paper-100 sticky top-0 z-10 ${headerClassName}`, children: jsxRuntime.jsxs("tr", { className: "table-header-row", children: [selectable && (jsxRuntime.jsx("th", { className: `sticky left-0 bg-paper-100 ${currentDensity.header} border-b ${borderColor} z-20 w-12 ${bordered ? `border ${borderColor}` : ''}`, children: jsxRuntime.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) && (jsxRuntime.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 && (jsxRuntime.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) => {
11534
11814
  const columnKey = String(column.key);
11535
11815
  const dynamicWidth = columnWidths[columnKey];
11536
11816
  const thRef = React.useRef(null);
11537
11817
  const isDragging = draggingColumn === columnKey;
11538
11818
  const isDragOver = dragOverColumn === columnKey;
11819
+ // Reduce left padding on first column when there are action buttons (match body cells)
11820
+ const isFirstColumn = colIdx === 0;
11821
+ const headerPaddingClass = isFirstColumn && allActions.length > 0 ? 'pl-3' : '';
11539
11822
  return (jsxRuntime.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: `
11540
- ${currentDensity.header} text-left border-b ${borderColor} ${bordered ? `border ${borderColor}` : ''} relative
11823
+ ${currentDensity.header} ${headerPaddingClass} text-left border-b ${borderColor} ${bordered ? `border ${borderColor}` : ''} relative
11541
11824
  ${reorderable ? 'cursor-move' : ''}
11542
11825
  ${isDragging ? 'opacity-50' : ''}
11543
11826
  ${isDragOver ? 'bg-accent-100' : ''}
@@ -58377,6 +58660,7 @@ exports.Textarea = Textarea;
58377
58660
  exports.ThemeToggle = ThemeToggle;
58378
58661
  exports.TimePicker = TimePicker;
58379
58662
  exports.Timeline = Timeline;
58663
+ exports.TimezoneSelector = TimezoneSelector;
58380
58664
  exports.Toast = Toast;
58381
58665
  exports.ToastContainer = ToastContainer;
58382
58666
  exports.Tooltip = Tooltip;
@@ -58400,6 +58684,8 @@ exports.formatStatisticValue = formatStatisticValue;
58400
58684
  exports.formatStatistics = formatStatistics;
58401
58685
  exports.getFormula = getFormula;
58402
58686
  exports.getFormulasByCategory = getFormulasByCategory;
58687
+ exports.getLocalTimezone = getLocalTimezone;
58688
+ exports.isValidTimezone = isValidTimezone;
58403
58689
  exports.loadColumnOrder = loadColumnOrder;
58404
58690
  exports.loadColumnWidths = loadColumnWidths;
58405
58691
  exports.reorderArray = reorderArray;