@moraby/app-launcher 0.1.8 → 2.0.1

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
@@ -21,6 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AppLauncher: () => AppLauncher,
24
+ AppSettings: () => AppSettings,
25
+ LocalAppLauncher: () => LocalAppLauncher,
24
26
  default: () => AppLauncher,
25
27
  getIcon: () => getIcon,
26
28
  iconMap: () => iconMap
@@ -247,9 +249,526 @@ function AppLauncher({
247
249
  ] })
248
250
  ] });
249
251
  }
252
+
253
+ // src/AppSettings.tsx
254
+ var import_react4 = require("react");
255
+ var import_fa2 = require("react-icons/fa");
256
+
257
+ // src/AddAppForm.tsx
258
+ var import_react3 = require("react");
259
+
260
+ // src/IconPicker.tsx
261
+ var import_react2 = require("react");
262
+ var import_jsx_runtime2 = require("react/jsx-runtime");
263
+ var allIcons = Object.keys(iconMap);
264
+ function IconPicker({ selectedIcon, onSelect }) {
265
+ const [searchTerm, setSearchTerm] = (0, import_react2.useState)("");
266
+ const filteredIcons = allIcons.filter(
267
+ (name) => name.toLowerCase().includes(searchTerm.toLowerCase())
268
+ );
269
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "app-launcher-icon-picker", children: [
270
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
271
+ "input",
272
+ {
273
+ type: "text",
274
+ placeholder: "Search icons...",
275
+ value: searchTerm,
276
+ onChange: (e) => setSearchTerm(e.target.value),
277
+ className: "app-launcher-icon-picker__search"
278
+ }
279
+ ),
280
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "app-launcher-icon-picker__grid", children: filteredIcons.map((iconName) => {
281
+ const Icon = iconMap[iconName];
282
+ const isSelected = selectedIcon === iconName;
283
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
284
+ "button",
285
+ {
286
+ className: `app-launcher-icon-picker__button ${isSelected ? "app-launcher-icon-picker__button--selected" : ""}`,
287
+ onClick: () => onSelect(iconName),
288
+ title: iconName,
289
+ type: "button",
290
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { className: "app-launcher-icon-picker__icon" })
291
+ },
292
+ iconName
293
+ );
294
+ }) }),
295
+ filteredIcons.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "app-launcher-icon-picker__empty", children: "No icons found" })
296
+ ] });
297
+ }
298
+
299
+ // src/AddAppForm.tsx
300
+ var import_jsx_runtime3 = require("react/jsx-runtime");
301
+ var presetColors = [
302
+ "#4285F4",
303
+ // Blue
304
+ "#EA4335",
305
+ // Red
306
+ "#FBBC04",
307
+ // Yellow
308
+ "#34A853",
309
+ // Green
310
+ "#FF6D01",
311
+ // Orange
312
+ "#46BDC6",
313
+ // Teal
314
+ "#7B1FA2",
315
+ // Purple
316
+ "#E91E63",
317
+ // Pink
318
+ "#607D8B",
319
+ // Gray
320
+ "#795548"
321
+ // Brown
322
+ ];
323
+ function AddAppForm({
324
+ initialData,
325
+ onSubmit,
326
+ onCancel,
327
+ submitLabel = "Add App"
328
+ }) {
329
+ const [name, setName] = (0, import_react3.useState)(initialData?.name || "");
330
+ const [url, setUrl] = (0, import_react3.useState)(initialData?.url || "");
331
+ const initialIcon = initialData?.icon || "FaRocket";
332
+ const isInitialCustom = initialIcon.startsWith("/") || initialIcon.startsWith("http");
333
+ const [iconName, setIconName] = (0, import_react3.useState)(isInitialCustom ? "FaRocket" : initialIcon);
334
+ const [customIconUrl, setCustomIconUrl] = (0, import_react3.useState)(isInitialCustom ? initialIcon : "");
335
+ const [iconMode, setIconMode] = (0, import_react3.useState)(isInitialCustom ? "custom" : "picker");
336
+ const [color, setColor] = (0, import_react3.useState)(initialData?.color || "#4285F4");
337
+ const [description, setDescription] = (0, import_react3.useState)(initialData?.description || "");
338
+ const [errors, setErrors] = (0, import_react3.useState)({});
339
+ const SelectedIcon = iconMap[iconName] || iconMap["FaRocket"];
340
+ const validate = () => {
341
+ const newErrors = {};
342
+ if (!name.trim()) {
343
+ newErrors.name = "Name is required";
344
+ }
345
+ if (!url.trim()) {
346
+ newErrors.url = "URL is required";
347
+ } else if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("/")) {
348
+ newErrors.url = "URL must start with http://, https://, or /";
349
+ }
350
+ if (iconMode === "custom" && customIconUrl.trim()) {
351
+ if (!customIconUrl.startsWith("http://") && !customIconUrl.startsWith("https://") && !customIconUrl.startsWith("/")) {
352
+ newErrors.customIconUrl = "Icon URL must start with http://, https://, or /";
353
+ }
354
+ }
355
+ setErrors(newErrors);
356
+ return Object.keys(newErrors).length === 0;
357
+ };
358
+ const handleSubmit = (e) => {
359
+ e.preventDefault();
360
+ if (!validate()) return;
361
+ const finalIcon = iconMode === "custom" && customIconUrl.trim() ? customIconUrl.trim() : iconName;
362
+ onSubmit({
363
+ name: name.trim(),
364
+ url: url.trim(),
365
+ icon: finalIcon,
366
+ color,
367
+ description: description.trim() || void 0
368
+ });
369
+ };
370
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { onSubmit: handleSubmit, className: "app-launcher-form", children: [
371
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "app-launcher-form__preview", children: [
372
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "app-launcher-form__preview-icon", style: { backgroundColor: color + "20" }, children: iconMode === "custom" && customIconUrl ? customIconUrl.endsWith(".svg") ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
373
+ "div",
374
+ {
375
+ className: "app-launcher-form__preview-svg",
376
+ style: {
377
+ maskImage: `url(${customIconUrl})`,
378
+ WebkitMaskImage: `url(${customIconUrl})`,
379
+ backgroundColor: color === "transparent" ? "#5f6368" : color
380
+ }
381
+ }
382
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
383
+ "img",
384
+ {
385
+ src: customIconUrl,
386
+ alt: "Icon preview",
387
+ className: "app-launcher-form__preview-img"
388
+ }
389
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SelectedIcon, { style: { color, fontSize: 32 } }) }),
390
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "app-launcher-form__preview-name", children: name || "App Name" })
391
+ ] }),
392
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "app-launcher-form__field", children: [
393
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { htmlFor: "app-name", className: "app-launcher-form__label", children: "Name *" }),
394
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
395
+ "input",
396
+ {
397
+ id: "app-name",
398
+ type: "text",
399
+ value: name,
400
+ onChange: (e) => setName(e.target.value),
401
+ placeholder: "My App",
402
+ className: `app-launcher-form__input ${errors.name ? "app-launcher-form__input--error" : ""}`
403
+ }
404
+ ),
405
+ errors.name && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "app-launcher-form__error", children: errors.name })
406
+ ] }),
407
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "app-launcher-form__field", children: [
408
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { htmlFor: "app-url", className: "app-launcher-form__label", children: "URL *" }),
409
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
410
+ "input",
411
+ {
412
+ id: "app-url",
413
+ type: "text",
414
+ value: url,
415
+ onChange: (e) => setUrl(e.target.value),
416
+ placeholder: "https://myapp.com or /dashboard",
417
+ className: `app-launcher-form__input ${errors.url ? "app-launcher-form__input--error" : ""}`
418
+ }
419
+ ),
420
+ errors.url && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "app-launcher-form__error", children: errors.url })
421
+ ] }),
422
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "app-launcher-form__field", children: [
423
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { htmlFor: "app-description", className: "app-launcher-form__label", children: "Description" }),
424
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
425
+ "input",
426
+ {
427
+ id: "app-description",
428
+ type: "text",
429
+ value: description,
430
+ onChange: (e) => setDescription(e.target.value),
431
+ placeholder: "Optional description (shown on hover)",
432
+ className: "app-launcher-form__input"
433
+ }
434
+ )
435
+ ] }),
436
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "app-launcher-form__field", children: [
437
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "app-launcher-form__label", children: "Color" }),
438
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "app-launcher-form__color-picker", children: [
439
+ presetColors.map((presetColor) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
440
+ "button",
441
+ {
442
+ type: "button",
443
+ className: `app-launcher-form__color-button ${color === presetColor ? "app-launcher-form__color-button--selected" : ""}`,
444
+ style: { backgroundColor: presetColor },
445
+ onClick: () => setColor(presetColor),
446
+ title: presetColor
447
+ },
448
+ presetColor
449
+ )),
450
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
451
+ "input",
452
+ {
453
+ type: "color",
454
+ value: color,
455
+ onChange: (e) => setColor(e.target.value),
456
+ className: "app-launcher-form__color-input",
457
+ title: "Custom color"
458
+ }
459
+ )
460
+ ] })
461
+ ] }),
462
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "app-launcher-form__field", children: [
463
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { className: "app-launcher-form__label", children: "Icon" }),
464
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "app-launcher-form__icon-mode", children: [
465
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
466
+ "button",
467
+ {
468
+ type: "button",
469
+ className: `app-launcher-form__mode-button ${iconMode === "picker" ? "app-launcher-form__mode-button--active" : ""}`,
470
+ onClick: () => setIconMode("picker"),
471
+ children: "Icon Picker"
472
+ }
473
+ ),
474
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
475
+ "button",
476
+ {
477
+ type: "button",
478
+ className: `app-launcher-form__mode-button ${iconMode === "custom" ? "app-launcher-form__mode-button--active" : ""}`,
479
+ onClick: () => setIconMode("custom"),
480
+ children: "Custom URL"
481
+ }
482
+ )
483
+ ] }),
484
+ iconMode === "picker" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IconPicker, { selectedIcon: iconName, onSelect: setIconName }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "app-launcher-form__custom-icon", children: [
485
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
486
+ "input",
487
+ {
488
+ id: "app-custom-icon",
489
+ type: "text",
490
+ value: customIconUrl,
491
+ onChange: (e) => setCustomIconUrl(e.target.value),
492
+ placeholder: "/icons/my-icon.svg or https://cdn.example.com/icon.svg",
493
+ className: `app-launcher-form__input ${errors.customIconUrl ? "app-launcher-form__input--error" : ""}`
494
+ }
495
+ ),
496
+ errors.customIconUrl && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "app-launcher-form__error", children: errors.customIconUrl }),
497
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "app-launcher-form__hint", children: "Enter a path from your public folder (e.g., /icons/logo.svg) or a full URL" })
498
+ ] })
499
+ ] }),
500
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "app-launcher-form__actions", children: [
501
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
502
+ "button",
503
+ {
504
+ type: "button",
505
+ onClick: onCancel,
506
+ className: "app-launcher-form__button app-launcher-form__button--cancel",
507
+ children: "Cancel"
508
+ }
509
+ ),
510
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
511
+ "button",
512
+ {
513
+ type: "submit",
514
+ className: "app-launcher-form__button app-launcher-form__button--submit",
515
+ children: submitLabel
516
+ }
517
+ )
518
+ ] })
519
+ ] });
520
+ }
521
+
522
+ // src/AppSettings.tsx
523
+ var import_jsx_runtime4 = require("react/jsx-runtime");
524
+ function AppSettings({
525
+ isOpen,
526
+ onClose,
527
+ apps,
528
+ defaultApps = [],
529
+ onAdd,
530
+ onUpdate,
531
+ onDelete
532
+ }) {
533
+ const [showAddForm, setShowAddForm] = (0, import_react4.useState)(false);
534
+ const [editingApp, setEditingApp] = (0, import_react4.useState)(null);
535
+ if (!isOpen) return null;
536
+ const handleAddSubmit = (data) => {
537
+ onAdd(data);
538
+ setShowAddForm(false);
539
+ };
540
+ const handleEditSubmit = (data) => {
541
+ if (editingApp) {
542
+ onUpdate(editingApp.id, data);
543
+ setEditingApp(null);
544
+ }
545
+ };
546
+ const handleDelete = (id) => {
547
+ if (confirm("Are you sure you want to delete this app?")) {
548
+ onDelete(id);
549
+ }
550
+ };
551
+ const handleExportConfig = () => {
552
+ const exportedApps = [
553
+ // User apps first (they appear at top)
554
+ ...apps,
555
+ // Then default apps
556
+ ...defaultApps.filter((da) => !apps.some((ua) => ua.id === da.id))
557
+ // Avoid duplicates if any
558
+ ];
559
+ const config = {
560
+ version: "1.0",
561
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
562
+ apps: exportedApps
563
+ };
564
+ const blob = new Blob([JSON.stringify(config, null, 2)], { type: "application/json" });
565
+ const url = URL.createObjectURL(blob);
566
+ const a = document.createElement("a");
567
+ a.href = url;
568
+ a.download = "app-launcher-config.json";
569
+ document.body.appendChild(a);
570
+ a.click();
571
+ document.body.removeChild(a);
572
+ URL.revokeObjectURL(url);
573
+ };
574
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "app-settings__overlay", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "app-settings__modal", onClick: (e) => e.stopPropagation(), children: [
575
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "app-settings__header", children: [
576
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { className: "app-settings__title", children: showAddForm ? "Add New App" : editingApp ? "Edit App" : "Settings" }),
577
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "app-settings__close-button", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_fa2.FaTimes, {}) })
578
+ ] }),
579
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "app-settings__content", children: showAddForm ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
580
+ AddAppForm,
581
+ {
582
+ onSubmit: handleAddSubmit,
583
+ onCancel: () => setShowAddForm(false),
584
+ submitLabel: "Add App"
585
+ }
586
+ ) : editingApp ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
587
+ AddAppForm,
588
+ {
589
+ initialData: editingApp,
590
+ onSubmit: handleEditSubmit,
591
+ onCancel: () => setEditingApp(null),
592
+ submitLabel: "Save Changes"
593
+ }
594
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
595
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("button", { className: "app-settings__add-button", onClick: () => setShowAddForm(true), children: [
596
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_fa2.FaPlus, { className: "app-settings__add-icon" }),
597
+ "Add New App"
598
+ ] }),
599
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "app-settings__list", children: [
600
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "app-settings__section-title", children: "My Custom Apps" }),
601
+ apps.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "app-settings__empty-message", children: 'No custom apps yet. Click "Add New App" to get started.' }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "app-settings__grid", children: apps.map((app) => {
602
+ const isCustom = app.icon.startsWith("/") || app.icon.startsWith("http");
603
+ const Icon = !isCustom ? iconMap[app.icon] || iconMap["FaRocket"] : null;
604
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "app-settings__card", children: [
605
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "app-settings__card-info", children: [
606
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
607
+ "div",
608
+ {
609
+ className: "app-settings__card-icon",
610
+ style: { backgroundColor: app.color + "20" },
611
+ children: isCustom ? app.icon.endsWith(".svg") ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
612
+ "div",
613
+ {
614
+ style: {
615
+ width: 20,
616
+ height: 20,
617
+ maskImage: `url(${app.icon})`,
618
+ WebkitMaskImage: `url(${app.icon})`,
619
+ maskSize: "contain",
620
+ maskRepeat: "no-repeat",
621
+ maskPosition: "center",
622
+ WebkitMaskSize: "contain",
623
+ WebkitMaskRepeat: "no-repeat",
624
+ WebkitMaskPosition: "center",
625
+ backgroundColor: app.color === "transparent" ? "#5f6368" : app.color
626
+ }
627
+ }
628
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
629
+ "img",
630
+ {
631
+ src: app.icon,
632
+ alt: app.name,
633
+ style: { width: 20, height: 20, objectFit: "contain" }
634
+ }
635
+ ) : Icon && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Icon, { style: { color: app.color } })
636
+ }
637
+ ),
638
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "app-settings__card-details", children: [
639
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "app-settings__card-name", children: app.name }),
640
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "app-settings__card-url", children: app.url })
641
+ ] })
642
+ ] }),
643
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "app-settings__card-actions", children: [
644
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
645
+ "button",
646
+ {
647
+ className: "app-settings__action-button",
648
+ onClick: () => setEditingApp(app),
649
+ title: "Edit",
650
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_fa2.FaEdit, {})
651
+ }
652
+ ),
653
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
654
+ "button",
655
+ {
656
+ className: "app-settings__action-button app-settings__action-button--delete",
657
+ onClick: () => handleDelete(app.id),
658
+ title: "Delete",
659
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_fa2.FaTrash, {})
660
+ }
661
+ )
662
+ ] })
663
+ ] }, app.id);
664
+ }) })
665
+ ] }),
666
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("button", { className: "app-settings__export-button", onClick: handleExportConfig, children: [
667
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_fa2.FaDownload, { className: "app-settings__export-icon" }),
668
+ "Export Configuration"
669
+ ] }),
670
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "app-settings__info", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: 'Custom apps are saved in your browser. Use "Export Configuration" to share with other apps.' }) })
671
+ ] }) })
672
+ ] }) });
673
+ }
674
+
675
+ // src/LocalAppLauncher.tsx
676
+ var import_react5 = require("react");
677
+ var import_fa3 = require("react-icons/fa");
678
+ var import_jsx_runtime5 = require("react/jsx-runtime");
679
+ var STORAGE_KEY = "app-launcher-user-apps";
680
+ function LocalAppLauncher({
681
+ defaultApps = [],
682
+ className,
683
+ mergeDefaultApps = true
684
+ }) {
685
+ const [showSettings, setShowSettings] = (0, import_react5.useState)(false);
686
+ const [userApps, setUserApps] = (0, import_react5.useState)([]);
687
+ const [isLoaded, setIsLoaded] = (0, import_react5.useState)(false);
688
+ (0, import_react5.useEffect)(() => {
689
+ if (typeof window !== "undefined") {
690
+ try {
691
+ const stored = localStorage.getItem(STORAGE_KEY);
692
+ if (stored) {
693
+ setUserApps(JSON.parse(stored));
694
+ }
695
+ } catch (e) {
696
+ console.error("Failed to load apps from localStorage", e);
697
+ }
698
+ setIsLoaded(true);
699
+ }
700
+ }, []);
701
+ const saveApps = (apps) => {
702
+ setUserApps(apps);
703
+ if (typeof window !== "undefined") {
704
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(apps));
705
+ }
706
+ };
707
+ const handleAddApp = (data) => {
708
+ const newApp = {
709
+ ...data,
710
+ id: `custom-${Date.now()}`
711
+ };
712
+ saveApps([...userApps, newApp]);
713
+ };
714
+ const handleUpdateApp = (id, updates) => {
715
+ saveApps(userApps.map((app) => app.id === id ? { ...app, ...updates } : app));
716
+ };
717
+ const handleDeleteApp = (id) => {
718
+ saveApps(userApps.filter((app) => app.id !== id));
719
+ };
720
+ const displayApps = isLoaded ? mergeDefaultApps ? [...userApps, ...defaultApps.filter((da) => !userApps.some((ua) => ua.id === da.id))] : userApps.length > 0 ? userApps : defaultApps : [];
721
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
722
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
723
+ AppLauncher,
724
+ {
725
+ apps: displayApps,
726
+ className,
727
+ renderFooter: () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
728
+ "button",
729
+ {
730
+ className: "app-launcher__footer-button",
731
+ style: {
732
+ display: "flex",
733
+ alignItems: "center",
734
+ gap: "8px",
735
+ width: "100%",
736
+ justifyContent: "center",
737
+ border: "none",
738
+ background: "transparent",
739
+ cursor: "pointer",
740
+ color: "var(--al-text-secondary)",
741
+ fontSize: "14px",
742
+ fontWeight: 500
743
+ },
744
+ onClick: () => setShowSettings(true),
745
+ children: [
746
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_fa3.FaCog, { style: { fontSize: "16px" } }),
747
+ "Settings"
748
+ ]
749
+ }
750
+ )
751
+ }
752
+ ),
753
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
754
+ AppSettings,
755
+ {
756
+ isOpen: showSettings,
757
+ onClose: () => setShowSettings(false),
758
+ apps: userApps,
759
+ defaultApps,
760
+ onAdd: handleAddApp,
761
+ onUpdate: handleUpdateApp,
762
+ onDelete: handleDeleteApp
763
+ }
764
+ )
765
+ ] });
766
+ }
250
767
  // Annotate the CommonJS export names for ESM import in node:
251
768
  0 && (module.exports = {
252
769
  AppLauncher,
770
+ AppSettings,
771
+ LocalAppLauncher,
253
772
  getIcon,
254
773
  iconMap
255
774
  });