@moraby/app-launcher 0.1.3 → 0.1.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/README.md CHANGED
@@ -90,7 +90,41 @@ Export your configuration from the admin app and host it as a JSON file:
90
90
  | `className` | `string` | Custom class name for the container |
91
91
  | `onAppClick` | `(app: AppItem) => void` | Custom click handler |
92
92
 
93
- ## Available Icons
93
+ ## Icons
94
+
95
+ ### Built-in Icons (react-icons)
96
+
97
+ Use the icon name as a string:
98
+
99
+ ```json
100
+ { "icon": "FaRocket" }
101
+ ```
102
+
103
+ ### Custom Icons (URLs)
104
+
105
+ You can also use your own icons from URLs or public folder:
106
+
107
+ ```json
108
+ // From Next.js public folder
109
+ { "icon": "/icons/my-icon.svg" }
110
+
111
+ // From external URL
112
+ { "icon": "https://cdn.example.com/icon.png" }
113
+ ```
114
+
115
+ **Example with custom icon:**
116
+
117
+ ```json
118
+ {
119
+ "id": "my-app",
120
+ "name": "My App",
121
+ "url": "https://myapp.com",
122
+ "icon": "/motorcycle-icon.svg",
123
+ "color": "transparent"
124
+ }
125
+ ```
126
+
127
+ ## Available Built-in Icons
94
128
 
95
129
  The package includes 50+ icons from react-icons. Use any of these names:
96
130
 
package/dist/index.css CHANGED
@@ -91,6 +91,11 @@
91
91
  .app-launcher__item:hover .app-launcher__icon {
92
92
  transform: scale(1.1);
93
93
  }
94
+ .app-launcher__icon--custom {
95
+ width: 32px;
96
+ height: 32px;
97
+ object-fit: contain;
98
+ }
94
99
  .app-launcher__name {
95
100
  font-family:
96
101
  "Google Sans",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/styles.css"],"sourcesContent":["/* App Launcher Styles */\r\n\r\n.app-launcher {\r\n position: relative;\r\n display: inline-flex;\r\n align-items: center;\r\n}\r\n\r\n/* Trigger Button */\r\n.app-launcher__trigger {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 40px;\r\n height: 40px;\r\n border: none;\r\n background: transparent;\r\n border-radius: 50%;\r\n cursor: pointer;\r\n transition: background-color 0.2s ease;\r\n}\r\n\r\n.app-launcher__trigger:hover {\r\n background-color: rgba(0, 0, 0, 0.08);\r\n}\r\n\r\n.app-launcher__trigger:focus {\r\n outline: none;\r\n background-color: rgba(0, 0, 0, 0.12);\r\n}\r\n\r\n.app-launcher__trigger-icon {\r\n font-size: 24px;\r\n color: #5f6368;\r\n}\r\n\r\n/* Dropdown */\r\n.app-launcher__dropdown {\r\n position: absolute;\r\n top: calc(100% + 8px);\r\n right: 0;\r\n width: 336px;\r\n max-height: 70vh;\r\n overflow-y: auto;\r\n background-color: #ffffff;\r\n border-radius: 8px;\r\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1), 0 8px 40px rgba(0, 0, 0, 0.2);\r\n z-index: 9999;\r\n animation: app-launcher-slide-in 0.15s ease-out;\r\n}\r\n\r\n@keyframes app-launcher-slide-in {\r\n from {\r\n opacity: 0;\r\n transform: translateY(-8px) scale(0.98);\r\n }\r\n to {\r\n opacity: 1;\r\n transform: translateY(0) scale(1);\r\n }\r\n}\r\n\r\n/* Grid */\r\n.app-launcher__grid {\r\n display: grid;\r\n grid-template-columns: repeat(3, 1fr);\r\n gap: 4px;\r\n padding: 16px 8px;\r\n}\r\n\r\n/* Item */\r\n.app-launcher__item {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 12px 8px;\r\n border: none;\r\n background: transparent;\r\n border-radius: 8px;\r\n cursor: pointer;\r\n transition: background-color 0.15s ease;\r\n min-height: 90px;\r\n}\r\n\r\n.app-launcher__item:hover {\r\n background-color: rgba(0, 0, 0, 0.04);\r\n}\r\n\r\n.app-launcher__item:focus {\r\n outline: none;\r\n background-color: rgba(0, 0, 0, 0.08);\r\n}\r\n\r\n/* Icon */\r\n.app-launcher__icon-wrapper {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 48px;\r\n height: 48px;\r\n margin-bottom: 8px;\r\n}\r\n\r\n.app-launcher__icon {\r\n font-size: 32px;\r\n transition: transform 0.15s ease;\r\n}\r\n\r\n.app-launcher__item:hover .app-launcher__icon {\r\n transform: scale(1.1);\r\n}\r\n\r\n/* Name */\r\n.app-launcher__name {\r\n font-family: 'Google Sans', 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif;\r\n font-size: 13px;\r\n font-weight: 400;\r\n color: #3c4043;\r\n text-align: center;\r\n line-height: 1.3;\r\n max-width: 100%;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n/* Loading & Error */\r\n.app-launcher__loading,\r\n.app-launcher__error {\r\n padding: 24px;\r\n text-align: center;\r\n font-size: 14px;\r\n color: #5f6368;\r\n}\r\n\r\n.app-launcher__error {\r\n color: #d93025;\r\n}\r\n\r\n/* Scrollbar */\r\n.app-launcher__dropdown::-webkit-scrollbar {\r\n width: 8px;\r\n}\r\n\r\n.app-launcher__dropdown::-webkit-scrollbar-track {\r\n background: transparent;\r\n}\r\n\r\n.app-launcher__dropdown::-webkit-scrollbar-thumb {\r\n background-color: #dadce0;\r\n border-radius: 4px;\r\n}\r\n\r\n/* Footer (for Settings button, etc.) */\r\n.app-launcher__footer {\r\n display: flex;\r\n justify-content: center;\r\n padding: 12px 16px;\r\n border-top: 1px solid #e0e0e0;\r\n background: #fafafa;\r\n}\r\n\r\n/* Responsive */\r\n@media (max-width: 400px) {\r\n .app-launcher__dropdown {\r\n width: 280px;\r\n right: -8px;\r\n }\r\n\r\n .app-launcher__icon-wrapper {\r\n width: 40px;\r\n height: 40px;\r\n }\r\n\r\n .app-launcher__icon {\r\n font-size: 28px;\r\n }\r\n\r\n .app-launcher__name {\r\n font-size: 12px;\r\n }\r\n}\r\n"],"mappings":";AAEA,CAAC;AACC,YAAU;AACV,WAAS;AACT,eAAa;AACf;AAGA,CAAC;AACC,WAAS;AACT,eAAa;AACb,mBAAiB;AACjB,SAAO;AACP,UAAQ;AACR,UAAQ;AACR,cAAY;AACZ,iBAAe;AACf,UAAQ;AACR,cAAY,iBAAiB,KAAK;AACpC;AAEA,CAbC,qBAaqB;AACpB,oBAAkB,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAClC;AAEA,CAjBC,qBAiBqB;AACpB,WAAS;AACT,oBAAkB,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAClC;AAEA,CAAC;AACC,aAAW;AACX,SAAO;AACT;AAGA,CAAC;AACC,YAAU;AACV,OAAK,KAAK,KAAK,EAAE;AACjB,SAAO;AACP,SAAO;AACP,cAAY;AACZ,cAAY;AACZ,oBAAkB;AAClB,iBAAe;AACf,cAAY,EAAE,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACpE,WAAS;AACT,aAAW,sBAAsB,MAAM;AACzC;AAEA,WAHa;AAIX;AACE,aAAS;AACT,eAAW,WAAW,MAAM,MAAM;AACpC;AACA;AACE,aAAS;AACT,eAAW,WAAW,GAAG,MAAM;AACjC;AACF;AAGA,CAAC;AACC,WAAS;AACT,yBAAuB,OAAO,CAAC,EAAE;AACjC,OAAK;AACL,WAAS,KAAK;AAChB;AAGA,CAAC;AACC,WAAS;AACT,kBAAgB;AAChB,eAAa;AACb,mBAAiB;AACjB,WAAS,KAAK;AACd,UAAQ;AACR,cAAY;AACZ,iBAAe;AACf,UAAQ;AACR,cAAY,iBAAiB,MAAM;AACnC,cAAY;AACd;AAEA,CAdC,kBAckB;AACjB,oBAAkB,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAClC;AAEA,CAlBC,kBAkBkB;AACjB,WAAS;AACT,oBAAkB,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAClC;AAGA,CAAC;AACC,WAAS;AACT,eAAa;AACb,mBAAiB;AACjB,SAAO;AACP,UAAQ;AACR,iBAAe;AACjB;AAEA,CAAC;AACC,aAAW;AACX,cAAY,UAAU,MAAM;AAC9B;AAEA,CAtCC,kBAsCkB,OAAO,CALzB;AAMC,aAAW,MAAM;AACnB;AAGA,CAAC;AACC;AAAA,IAAa,aAAa;AAAA,IAAE,QAAQ;AAAA,IAAE,aAAa;AAAA,IAAE,kBAAkB;AAAA,IAAE;AACzE,aAAW;AACX,eAAa;AACb,SAAO;AACP,cAAY;AACZ,eAAa;AACb,aAAW;AACX,YAAU;AACV,iBAAe;AACf,eAAa;AACf;AAGA,CAAC;AACD,CAAC;AACC,WAAS;AACT,cAAY;AACZ,aAAW;AACX,SAAO;AACT;AAEA,CAPC;AAQC,SAAO;AACT;AAGA,CAxGC,sBAwGsB;AACrB,SAAO;AACT;AAEA,CA5GC,sBA4GsB;AACrB,cAAY;AACd;AAEA,CAhHC,sBAgHsB;AACrB,oBAAkB;AAClB,iBAAe;AACjB;AAGA,CAAC;AACC,WAAS;AACT,mBAAiB;AACjB,WAAS,KAAK;AACd,cAAY,IAAI,MAAM;AACtB,cAAY;AACd;AAGA,QAAO,WAAY;AACjB,GAhID;AAiIG,WAAO;AACP,WAAO;AACT;AAEA,GA3ED;AA4EG,WAAO;AACP,YAAQ;AACV;AAEA,GAvED;AAwEG,eAAW;AACb;AAEA,GAjED;AAkEG,eAAW;AACb;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/styles.css"],"sourcesContent":["/* App Launcher Styles */\r\n\r\n.app-launcher {\r\n position: relative;\r\n display: inline-flex;\r\n align-items: center;\r\n}\r\n\r\n/* Trigger Button */\r\n.app-launcher__trigger {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 40px;\r\n height: 40px;\r\n border: none;\r\n background: transparent;\r\n border-radius: 50%;\r\n cursor: pointer;\r\n transition: background-color 0.2s ease;\r\n}\r\n\r\n.app-launcher__trigger:hover {\r\n background-color: rgba(0, 0, 0, 0.08);\r\n}\r\n\r\n.app-launcher__trigger:focus {\r\n outline: none;\r\n background-color: rgba(0, 0, 0, 0.12);\r\n}\r\n\r\n.app-launcher__trigger-icon {\r\n font-size: 24px;\r\n color: #5f6368;\r\n}\r\n\r\n/* Dropdown */\r\n.app-launcher__dropdown {\r\n position: absolute;\r\n top: calc(100% + 8px);\r\n right: 0;\r\n width: 336px;\r\n max-height: 70vh;\r\n overflow-y: auto;\r\n background-color: #ffffff;\r\n border-radius: 8px;\r\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1), 0 8px 40px rgba(0, 0, 0, 0.2);\r\n z-index: 9999;\r\n animation: app-launcher-slide-in 0.15s ease-out;\r\n}\r\n\r\n@keyframes app-launcher-slide-in {\r\n from {\r\n opacity: 0;\r\n transform: translateY(-8px) scale(0.98);\r\n }\r\n to {\r\n opacity: 1;\r\n transform: translateY(0) scale(1);\r\n }\r\n}\r\n\r\n/* Grid */\r\n.app-launcher__grid {\r\n display: grid;\r\n grid-template-columns: repeat(3, 1fr);\r\n gap: 4px;\r\n padding: 16px 8px;\r\n}\r\n\r\n/* Item */\r\n.app-launcher__item {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 12px 8px;\r\n border: none;\r\n background: transparent;\r\n border-radius: 8px;\r\n cursor: pointer;\r\n transition: background-color 0.15s ease;\r\n min-height: 90px;\r\n}\r\n\r\n.app-launcher__item:hover {\r\n background-color: rgba(0, 0, 0, 0.04);\r\n}\r\n\r\n.app-launcher__item:focus {\r\n outline: none;\r\n background-color: rgba(0, 0, 0, 0.08);\r\n}\r\n\r\n/* Icon */\r\n.app-launcher__icon-wrapper {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 48px;\r\n height: 48px;\r\n margin-bottom: 8px;\r\n}\r\n\r\n.app-launcher__icon {\r\n font-size: 32px;\r\n transition: transform 0.15s ease;\r\n}\r\n\r\n.app-launcher__item:hover .app-launcher__icon {\r\n transform: scale(1.1);\r\n}\r\n\r\n/* Custom image icons */\r\n.app-launcher__icon--custom {\r\n width: 32px;\r\n height: 32px;\r\n object-fit: contain;\r\n}\r\n\r\n/* Name */\r\n.app-launcher__name {\r\n font-family: 'Google Sans', 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif;\r\n font-size: 13px;\r\n font-weight: 400;\r\n color: #3c4043;\r\n text-align: center;\r\n line-height: 1.3;\r\n max-width: 100%;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n/* Loading & Error */\r\n.app-launcher__loading,\r\n.app-launcher__error {\r\n padding: 24px;\r\n text-align: center;\r\n font-size: 14px;\r\n color: #5f6368;\r\n}\r\n\r\n.app-launcher__error {\r\n color: #d93025;\r\n}\r\n\r\n/* Scrollbar */\r\n.app-launcher__dropdown::-webkit-scrollbar {\r\n width: 8px;\r\n}\r\n\r\n.app-launcher__dropdown::-webkit-scrollbar-track {\r\n background: transparent;\r\n}\r\n\r\n.app-launcher__dropdown::-webkit-scrollbar-thumb {\r\n background-color: #dadce0;\r\n border-radius: 4px;\r\n}\r\n\r\n/* Footer (for Settings button, etc.) */\r\n.app-launcher__footer {\r\n display: flex;\r\n justify-content: center;\r\n padding: 12px 16px;\r\n border-top: 1px solid #e0e0e0;\r\n background: #fafafa;\r\n}\r\n\r\n/* Responsive */\r\n@media (max-width: 400px) {\r\n .app-launcher__dropdown {\r\n width: 280px;\r\n right: -8px;\r\n }\r\n\r\n .app-launcher__icon-wrapper {\r\n width: 40px;\r\n height: 40px;\r\n }\r\n\r\n .app-launcher__icon {\r\n font-size: 28px;\r\n }\r\n\r\n .app-launcher__name {\r\n font-size: 12px;\r\n }\r\n}\r\n"],"mappings":";AAEA,CAAC;AACC,YAAU;AACV,WAAS;AACT,eAAa;AACf;AAGA,CAAC;AACC,WAAS;AACT,eAAa;AACb,mBAAiB;AACjB,SAAO;AACP,UAAQ;AACR,UAAQ;AACR,cAAY;AACZ,iBAAe;AACf,UAAQ;AACR,cAAY,iBAAiB,KAAK;AACpC;AAEA,CAbC,qBAaqB;AACpB,oBAAkB,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAClC;AAEA,CAjBC,qBAiBqB;AACpB,WAAS;AACT,oBAAkB,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAClC;AAEA,CAAC;AACC,aAAW;AACX,SAAO;AACT;AAGA,CAAC;AACC,YAAU;AACV,OAAK,KAAK,KAAK,EAAE;AACjB,SAAO;AACP,SAAO;AACP,cAAY;AACZ,cAAY;AACZ,oBAAkB;AAClB,iBAAe;AACf,cAAY,EAAE,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACpE,WAAS;AACT,aAAW,sBAAsB,MAAM;AACzC;AAEA,WAHa;AAIX;AACE,aAAS;AACT,eAAW,WAAW,MAAM,MAAM;AACpC;AACA;AACE,aAAS;AACT,eAAW,WAAW,GAAG,MAAM;AACjC;AACF;AAGA,CAAC;AACC,WAAS;AACT,yBAAuB,OAAO,CAAC,EAAE;AACjC,OAAK;AACL,WAAS,KAAK;AAChB;AAGA,CAAC;AACC,WAAS;AACT,kBAAgB;AAChB,eAAa;AACb,mBAAiB;AACjB,WAAS,KAAK;AACd,UAAQ;AACR,cAAY;AACZ,iBAAe;AACf,UAAQ;AACR,cAAY,iBAAiB,MAAM;AACnC,cAAY;AACd;AAEA,CAdC,kBAckB;AACjB,oBAAkB,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAClC;AAEA,CAlBC,kBAkBkB;AACjB,WAAS;AACT,oBAAkB,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAClC;AAGA,CAAC;AACC,WAAS;AACT,eAAa;AACb,mBAAiB;AACjB,SAAO;AACP,UAAQ;AACR,iBAAe;AACjB;AAEA,CAAC;AACC,aAAW;AACX,cAAY,UAAU,MAAM;AAC9B;AAEA,CAtCC,kBAsCkB,OAAO,CALzB;AAMC,aAAW,MAAM;AACnB;AAGA,CAAC;AACC,SAAO;AACP,UAAQ;AACR,cAAY;AACd;AAGA,CAAC;AACC;AAAA,IAAa,aAAa;AAAA,IAAE,QAAQ;AAAA,IAAE,aAAa;AAAA,IAAE,kBAAkB;AAAA,IAAE;AACzE,aAAW;AACX,eAAa;AACb,SAAO;AACP,cAAY;AACZ,eAAa;AACb,aAAW;AACX,YAAU;AACV,iBAAe;AACf,eAAa;AACf;AAGA,CAAC;AACD,CAAC;AACC,WAAS;AACT,cAAY;AACZ,aAAW;AACX,SAAO;AACT;AAEA,CAPC;AAQC,SAAO;AACT;AAGA,CA/GC,sBA+GsB;AACrB,SAAO;AACT;AAEA,CAnHC,sBAmHsB;AACrB,cAAY;AACd;AAEA,CAvHC,sBAuHsB;AACrB,oBAAkB;AAClB,iBAAe;AACjB;AAGA,CAAC;AACC,WAAS;AACT,mBAAiB;AACjB,WAAS,KAAK;AACd,cAAY,IAAI,MAAM;AACtB,cAAY;AACd;AAGA,QAAO,WAAY;AACjB,GAvID;AAwIG,WAAO;AACP,WAAO;AACT;AAEA,GAlFD;AAmFG,WAAO;AACP,YAAQ;AACV;AAEA,GA9ED;AA+EG,eAAW;AACb;AAEA,GAjED;AAkEG,eAAW;AACb;AACF;","names":[]}
package/dist/index.d.mts CHANGED
@@ -9,6 +9,12 @@ interface AppItem {
9
9
  id: string;
10
10
  name: string;
11
11
  url: string;
12
+ /**
13
+ * Icon can be:
14
+ * - react-icons name: "FaRocket", "MdDashboard"
15
+ * - URL path: "/icons/my-icon.svg" (from public folder)
16
+ * - Full URL: "https://example.com/icon.png"
17
+ */
12
18
  icon: string;
13
19
  color: string;
14
20
  description?: string;
@@ -56,7 +62,8 @@ interface ResolvedApp {
56
62
  id: string;
57
63
  name: string;
58
64
  url: string;
59
- icon: IconType;
65
+ icon: IconType | null;
66
+ customIconUrl: string | null;
60
67
  color: string;
61
68
  description?: string;
62
69
  }
package/dist/index.d.ts CHANGED
@@ -9,6 +9,12 @@ interface AppItem {
9
9
  id: string;
10
10
  name: string;
11
11
  url: string;
12
+ /**
13
+ * Icon can be:
14
+ * - react-icons name: "FaRocket", "MdDashboard"
15
+ * - URL path: "/icons/my-icon.svg" (from public folder)
16
+ * - Full URL: "https://example.com/icon.png"
17
+ */
12
18
  icon: string;
13
19
  color: string;
14
20
  description?: string;
@@ -56,7 +62,8 @@ interface ResolvedApp {
56
62
  id: string;
57
63
  name: string;
58
64
  url: string;
59
- icon: IconType;
65
+ icon: IconType | null;
66
+ customIconUrl: string | null;
60
67
  color: string;
61
68
  description?: string;
62
69
  }
package/dist/index.js CHANGED
@@ -163,12 +163,17 @@ function AppLauncher({
163
163
  }
164
164
  return () => document.removeEventListener("keydown", handleEscape);
165
165
  }, [isOpen]);
166
+ function isCustomIconUrl(icon) {
167
+ return icon.startsWith("/") || icon.startsWith("http");
168
+ }
166
169
  function resolveApp(app) {
170
+ const isCustom = isCustomIconUrl(app.icon);
167
171
  return {
168
172
  id: app.id,
169
173
  name: app.name,
170
174
  url: app.url,
171
- icon: getIcon(app.icon),
175
+ icon: isCustom ? null : getIcon(app.icon),
176
+ customIconUrl: isCustom ? app.icon : null,
172
177
  color: app.color,
173
178
  description: app.description
174
179
  };
@@ -179,7 +184,7 @@ function AppLauncher({
179
184
  id: app.id,
180
185
  name: app.name,
181
186
  url: app.url,
182
- icon: app.icon.name || "FaRocket",
187
+ icon: app.customIconUrl || (app.icon?.name ?? "FaRocket"),
183
188
  color: app.color,
184
189
  description: app.description
185
190
  });
@@ -208,7 +213,14 @@ function AppLauncher({
208
213
  onClick: () => handleAppClick(app),
209
214
  title: app.description || app.name,
210
215
  children: [
211
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "app-launcher__icon-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(app.icon, { className: "app-launcher__icon", style: { color: app.color } }) }),
216
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "app-launcher__icon-wrapper", children: app.customIconUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
217
+ "img",
218
+ {
219
+ src: app.customIconUrl,
220
+ alt: app.name,
221
+ className: "app-launcher__icon app-launcher__icon--custom"
222
+ }
223
+ ) : app.icon ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(app.icon, { className: "app-launcher__icon", style: { color: app.color } }) : null }),
212
224
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "app-launcher__name", children: app.name })
213
225
  ]
214
226
  },
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/AppLauncher.tsx","../src/icons.ts"],"sourcesContent":["// Main component\r\nexport { AppLauncher, AppLauncher as default } from './AppLauncher';\r\n\r\n// Types\r\nexport type { \r\n AppLauncherProps, \r\n AppItem, \r\n AppLauncherConfig,\r\n ResolvedApp,\r\n} from './types';\r\n\r\n// Icons (in case consumers want to use them)\r\nexport { iconMap, getIcon } from './icons';\r\n","'use client';\r\n\r\nimport React, { useState, useRef, useEffect } from 'react';\r\nimport { IoApps } from 'react-icons/io5';\r\nimport { AppLauncherProps, AppItem, ResolvedApp, AppLauncherConfig } from './types';\r\nimport { getIcon } from './icons';\r\nimport './styles.css';\r\n\r\n/**\r\n * A Google-style app launcher component\r\n *\r\n * @example\r\n * // With config URL\r\n * <AppLauncher configUrl=\"https://example.com/apps.json\" />\r\n *\r\n * @example\r\n * // With direct apps array\r\n * <AppLauncher apps={[{ id: '1', name: 'App', url: '/app', icon: 'FaRocket', color: '#4285F4' }]} />\r\n */\r\nexport function AppLauncher({\r\n configUrl,\r\n apps: propApps,\r\n className,\r\n onAppClick,\r\n renderFooter,\r\n}: AppLauncherProps) {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [apps, setApps] = useState<ResolvedApp[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n // Resolve apps from props or fetch from URL\r\n useEffect(() => {\r\n if (propApps) {\r\n setApps(propApps.map(resolveApp));\r\n return;\r\n }\r\n\r\n if (configUrl) {\r\n setLoading(true);\r\n setError(null);\r\n\r\n fetch(configUrl)\r\n .then((res) => {\r\n if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);\r\n return res.json();\r\n })\r\n .then((config: AppLauncherConfig) => {\r\n setApps(config.apps.map(resolveApp));\r\n })\r\n .catch((err) => {\r\n setError(err.message);\r\n console.error('AppLauncher: Failed to load config', err);\r\n })\r\n .finally(() => setLoading(false));\r\n }\r\n }, [configUrl, propApps]);\r\n\r\n // Close on click outside\r\n useEffect(() => {\r\n function handleClickOutside(event: MouseEvent) {\r\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\r\n setIsOpen(false);\r\n }\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('mousedown', handleClickOutside);\r\n }\r\n return () => document.removeEventListener('mousedown', handleClickOutside);\r\n }, [isOpen]);\r\n\r\n // Close on Escape\r\n useEffect(() => {\r\n function handleEscape(event: KeyboardEvent) {\r\n if (event.key === 'Escape') setIsOpen(false);\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('keydown', handleEscape);\r\n }\r\n return () => document.removeEventListener('keydown', handleEscape);\r\n }, [isOpen]);\r\n\r\n function resolveApp(app: AppItem): ResolvedApp {\r\n return {\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: getIcon(app.icon),\r\n color: app.color,\r\n description: app.description,\r\n };\r\n }\r\n\r\n function handleAppClick(app: ResolvedApp) {\r\n if (onAppClick) {\r\n onAppClick({\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: app.icon.name || 'FaRocket',\r\n color: app.color,\r\n description: app.description,\r\n });\r\n } else {\r\n // Open in new tab\r\n window.open(app.url, '_blank', 'noopener,noreferrer');\r\n }\r\n }\r\n\r\n return (\r\n <div className={`app-launcher ${className || ''}`} ref={containerRef}>\r\n {/* Trigger Button */}\r\n <button\r\n className=\"app-launcher__trigger\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n aria-label=\"Open app launcher\"\r\n aria-expanded={isOpen}\r\n >\r\n <IoApps className=\"app-launcher__trigger-icon\" />\r\n </button>\r\n\r\n {/* Dropdown */}\r\n {isOpen && (\r\n <div className=\"app-launcher__dropdown\">\r\n {loading && <div className=\"app-launcher__loading\">Loading...</div>}\r\n\r\n {error && <div className=\"app-launcher__error\">{error}</div>}\r\n\r\n {!loading && !error && (\r\n <div className=\"app-launcher__grid\">\r\n {apps.map((app) => (\r\n <button\r\n key={app.id}\r\n className=\"app-launcher__item\"\r\n onClick={() => handleAppClick(app)}\r\n title={app.description || app.name}\r\n >\r\n <div className=\"app-launcher__icon-wrapper\">\r\n <app.icon className=\"app-launcher__icon\" style={{ color: app.color }} />\r\n </div>\r\n <span className=\"app-launcher__name\">{app.name}</span>\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n\r\n {/* Custom footer (e.g., Settings button) */}\r\n {renderFooter && <div className=\"app-launcher__footer\">{renderFooter()}</div>}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nexport default AppLauncher;\r\n","import { IconType } from 'react-icons';\r\n// prettier-ignore\r\nimport {\r\n FaGoogle, FaEnvelope, FaYoutube, FaCalendarAlt, FaMapMarkerAlt,\r\n FaFile, FaBookmark, FaTable, FaNewspaper, FaImage, FaRocket,\r\n FaHome, FaUser, FaCog, FaChartBar, FaShoppingCart, FaDatabase,\r\n FaCode, FaTerminal, FaGlobe, FaLock, FaKey, FaBell, FaHeart,\r\n FaStar, FaFolder, FaClipboard, FaCalculator, FaMusic, FaCamera,\r\n FaGamepad, FaPuzzlePiece, FaBriefcase, FaGraduationCap, FaPlane,\r\n FaCar, FaBicycle, FaUtensils, FaCoffee, FaGift,\r\n} from 'react-icons/fa';\r\nimport { FaTicketSimple } from 'react-icons/fa6';\r\nimport { GrDeliver } from 'react-icons/gr';\r\nimport { IoBusinessSharp, IoApps } from 'react-icons/io5';\r\nimport { MdOutlineSecurity, MdDashboard, MdAnalytics, MdEmail, MdWork } from 'react-icons/md';\r\nimport { SiGoogledrive, SiGooglemeet } from 'react-icons/si';\r\nimport { AiOutlineSecurityScan } from 'react-icons/ai';\r\n\r\n/**\r\n * Map of icon names to icon components\r\n */\r\n// prettier-ignore\r\nexport const iconMap: Record<string, IconType> = {\r\n // Business & Work\r\n FaBriefcase, IoBusinessSharp, MdWork, FaGraduationCap,\r\n // Security\r\n MdOutlineSecurity, FaLock, FaKey, AiOutlineSecurityScan,\r\n // Communication\r\n FaEnvelope, MdEmail, FaBell,\r\n // Media\r\n FaYoutube, FaMusic, FaCamera, FaImage, FaGamepad,\r\n // Productivity\r\n FaCalendarAlt, FaClipboard, FaCalculator, FaFolder, FaFile,\r\n FaBookmark, FaTable, FaNewspaper,\r\n // Navigation\r\n FaMapMarkerAlt, FaGlobe, FaHome,\r\n // Google\r\n FaGoogle, SiGoogledrive, SiGooglemeet,\r\n // Development\r\n FaCode, FaTerminal, FaDatabase, FaPuzzlePiece,\r\n // Analytics\r\n FaChartBar, MdDashboard, MdAnalytics,\r\n // Shopping\r\n FaShoppingCart, FaGift, FaTicketSimple,\r\n // Travel\r\n FaPlane, FaCar, FaBicycle, GrDeliver,\r\n // Food\r\n FaUtensils, FaCoffee,\r\n // General\r\n FaRocket, FaUser, FaCog, FaHeart, FaStar, IoApps,\r\n};\r\n\r\n/**\r\n * Get icon component by name\r\n */\r\nexport function getIcon(name: string): IconType {\r\n return iconMap[name] || FaRocket;\r\n}\r\n"],"mappings":"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","names":["import_io5"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/AppLauncher.tsx","../src/icons.ts"],"sourcesContent":["// Main component\r\nexport { AppLauncher, AppLauncher as default } from './AppLauncher';\r\n\r\n// Types\r\nexport type { \r\n AppLauncherProps, \r\n AppItem, \r\n AppLauncherConfig,\r\n ResolvedApp,\r\n} from './types';\r\n\r\n// Icons (in case consumers want to use them)\r\nexport { iconMap, getIcon } from './icons';\r\n","'use client';\r\n\r\nimport React, { useState, useRef, useEffect } from 'react';\r\nimport { IoApps } from 'react-icons/io5';\r\nimport { AppLauncherProps, AppItem, ResolvedApp, AppLauncherConfig } from './types';\r\nimport { getIcon } from './icons';\r\nimport './styles.css';\r\n\r\n/**\r\n * A Google-style app launcher component\r\n *\r\n * @example\r\n * // With config URL\r\n * <AppLauncher configUrl=\"https://example.com/apps.json\" />\r\n *\r\n * @example\r\n * // With direct apps array\r\n * <AppLauncher apps={[{ id: '1', name: 'App', url: '/app', icon: 'FaRocket', color: '#4285F4' }]} />\r\n */\r\nexport function AppLauncher({\r\n configUrl,\r\n apps: propApps,\r\n className,\r\n onAppClick,\r\n renderFooter,\r\n}: AppLauncherProps) {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [apps, setApps] = useState<ResolvedApp[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n // Resolve apps from props or fetch from URL\r\n useEffect(() => {\r\n if (propApps) {\r\n setApps(propApps.map(resolveApp));\r\n return;\r\n }\r\n\r\n if (configUrl) {\r\n setLoading(true);\r\n setError(null);\r\n\r\n fetch(configUrl)\r\n .then((res) => {\r\n if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);\r\n return res.json();\r\n })\r\n .then((config: AppLauncherConfig) => {\r\n setApps(config.apps.map(resolveApp));\r\n })\r\n .catch((err) => {\r\n setError(err.message);\r\n console.error('AppLauncher: Failed to load config', err);\r\n })\r\n .finally(() => setLoading(false));\r\n }\r\n }, [configUrl, propApps]);\r\n\r\n // Close on click outside\r\n useEffect(() => {\r\n function handleClickOutside(event: MouseEvent) {\r\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\r\n setIsOpen(false);\r\n }\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('mousedown', handleClickOutside);\r\n }\r\n return () => document.removeEventListener('mousedown', handleClickOutside);\r\n }, [isOpen]);\r\n\r\n // Close on Escape\r\n useEffect(() => {\r\n function handleEscape(event: KeyboardEvent) {\r\n if (event.key === 'Escape') setIsOpen(false);\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('keydown', handleEscape);\r\n }\r\n return () => document.removeEventListener('keydown', handleEscape);\r\n }, [isOpen]);\r\n\r\n // Check if icon is a custom URL (path or full URL)\r\n function isCustomIconUrl(icon: string): boolean {\r\n return icon.startsWith('/') || icon.startsWith('http');\r\n }\r\n\r\n function resolveApp(app: AppItem): ResolvedApp {\r\n const isCustom = isCustomIconUrl(app.icon);\r\n return {\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: isCustom ? null : getIcon(app.icon),\r\n customIconUrl: isCustom ? app.icon : null,\r\n color: app.color,\r\n description: app.description,\r\n };\r\n }\r\n\r\n function handleAppClick(app: ResolvedApp) {\r\n if (onAppClick) {\r\n onAppClick({\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: app.customIconUrl || (app.icon?.name ?? 'FaRocket'),\r\n color: app.color,\r\n description: app.description,\r\n });\r\n } else {\r\n // Open in new tab\r\n window.open(app.url, '_blank', 'noopener,noreferrer');\r\n }\r\n }\r\n\r\n return (\r\n <div className={`app-launcher ${className || ''}`} ref={containerRef}>\r\n {/* Trigger Button */}\r\n <button\r\n className=\"app-launcher__trigger\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n aria-label=\"Open app launcher\"\r\n aria-expanded={isOpen}\r\n >\r\n <IoApps className=\"app-launcher__trigger-icon\" />\r\n </button>\r\n\r\n {/* Dropdown */}\r\n {isOpen && (\r\n <div className=\"app-launcher__dropdown\">\r\n {loading && <div className=\"app-launcher__loading\">Loading...</div>}\r\n\r\n {error && <div className=\"app-launcher__error\">{error}</div>}\r\n\r\n {!loading && !error && (\r\n <div className=\"app-launcher__grid\">\r\n {apps.map((app) => (\r\n <button\r\n key={app.id}\r\n className=\"app-launcher__item\"\r\n onClick={() => handleAppClick(app)}\r\n title={app.description || app.name}\r\n >\r\n <div className=\"app-launcher__icon-wrapper\">\r\n {app.customIconUrl ? (\r\n <img\r\n src={app.customIconUrl}\r\n alt={app.name}\r\n className=\"app-launcher__icon app-launcher__icon--custom\"\r\n />\r\n ) : app.icon ? (\r\n <app.icon className=\"app-launcher__icon\" style={{ color: app.color }} />\r\n ) : null}\r\n </div>\r\n <span className=\"app-launcher__name\">{app.name}</span>\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n\r\n {/* Custom footer (e.g., Settings button) */}\r\n {renderFooter && <div className=\"app-launcher__footer\">{renderFooter()}</div>}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nexport default AppLauncher;\r\n","import { IconType } from 'react-icons';\r\n// prettier-ignore\r\nimport {\r\n FaGoogle, FaEnvelope, FaYoutube, FaCalendarAlt, FaMapMarkerAlt,\r\n FaFile, FaBookmark, FaTable, FaNewspaper, FaImage, FaRocket,\r\n FaHome, FaUser, FaCog, FaChartBar, FaShoppingCart, FaDatabase,\r\n FaCode, FaTerminal, FaGlobe, FaLock, FaKey, FaBell, FaHeart,\r\n FaStar, FaFolder, FaClipboard, FaCalculator, FaMusic, FaCamera,\r\n FaGamepad, FaPuzzlePiece, FaBriefcase, FaGraduationCap, FaPlane,\r\n FaCar, FaBicycle, FaUtensils, FaCoffee, FaGift,\r\n} from 'react-icons/fa';\r\nimport { FaTicketSimple } from 'react-icons/fa6';\r\nimport { GrDeliver } from 'react-icons/gr';\r\nimport { IoBusinessSharp, IoApps } from 'react-icons/io5';\r\nimport { MdOutlineSecurity, MdDashboard, MdAnalytics, MdEmail, MdWork } from 'react-icons/md';\r\nimport { SiGoogledrive, SiGooglemeet } from 'react-icons/si';\r\nimport { AiOutlineSecurityScan } from 'react-icons/ai';\r\n\r\n/**\r\n * Map of icon names to icon components\r\n */\r\n// prettier-ignore\r\nexport const iconMap: Record<string, IconType> = {\r\n // Business & Work\r\n FaBriefcase, IoBusinessSharp, MdWork, FaGraduationCap,\r\n // Security\r\n MdOutlineSecurity, FaLock, FaKey, AiOutlineSecurityScan,\r\n // Communication\r\n FaEnvelope, MdEmail, FaBell,\r\n // Media\r\n FaYoutube, FaMusic, FaCamera, FaImage, FaGamepad,\r\n // Productivity\r\n FaCalendarAlt, FaClipboard, FaCalculator, FaFolder, FaFile,\r\n FaBookmark, FaTable, FaNewspaper,\r\n // Navigation\r\n FaMapMarkerAlt, FaGlobe, FaHome,\r\n // Google\r\n FaGoogle, SiGoogledrive, SiGooglemeet,\r\n // Development\r\n FaCode, FaTerminal, FaDatabase, FaPuzzlePiece,\r\n // Analytics\r\n FaChartBar, MdDashboard, MdAnalytics,\r\n // Shopping\r\n FaShoppingCart, FaGift, FaTicketSimple,\r\n // Travel\r\n FaPlane, FaCar, FaBicycle, GrDeliver,\r\n // Food\r\n FaUtensils, FaCoffee,\r\n // General\r\n FaRocket, FaUser, FaCog, FaHeart, FaStar, IoApps,\r\n};\r\n\r\n/**\r\n * Get icon component by name\r\n */\r\nexport function getIcon(name: string): IconType {\r\n return iconMap[name] || FaRocket;\r\n}\r\n"],"mappings":"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","names":["import_io5"]}
package/dist/index.mjs CHANGED
@@ -175,12 +175,17 @@ function AppLauncher({
175
175
  }
176
176
  return () => document.removeEventListener("keydown", handleEscape);
177
177
  }, [isOpen]);
178
+ function isCustomIconUrl(icon) {
179
+ return icon.startsWith("/") || icon.startsWith("http");
180
+ }
178
181
  function resolveApp(app) {
182
+ const isCustom = isCustomIconUrl(app.icon);
179
183
  return {
180
184
  id: app.id,
181
185
  name: app.name,
182
186
  url: app.url,
183
- icon: getIcon(app.icon),
187
+ icon: isCustom ? null : getIcon(app.icon),
188
+ customIconUrl: isCustom ? app.icon : null,
184
189
  color: app.color,
185
190
  description: app.description
186
191
  };
@@ -191,7 +196,7 @@ function AppLauncher({
191
196
  id: app.id,
192
197
  name: app.name,
193
198
  url: app.url,
194
- icon: app.icon.name || "FaRocket",
199
+ icon: app.customIconUrl || (app.icon?.name ?? "FaRocket"),
195
200
  color: app.color,
196
201
  description: app.description
197
202
  });
@@ -220,7 +225,14 @@ function AppLauncher({
220
225
  onClick: () => handleAppClick(app),
221
226
  title: app.description || app.name,
222
227
  children: [
223
- /* @__PURE__ */ jsx("div", { className: "app-launcher__icon-wrapper", children: /* @__PURE__ */ jsx(app.icon, { className: "app-launcher__icon", style: { color: app.color } }) }),
228
+ /* @__PURE__ */ jsx("div", { className: "app-launcher__icon-wrapper", children: app.customIconUrl ? /* @__PURE__ */ jsx(
229
+ "img",
230
+ {
231
+ src: app.customIconUrl,
232
+ alt: app.name,
233
+ className: "app-launcher__icon app-launcher__icon--custom"
234
+ }
235
+ ) : app.icon ? /* @__PURE__ */ jsx(app.icon, { className: "app-launcher__icon", style: { color: app.color } }) : null }),
224
236
  /* @__PURE__ */ jsx("span", { className: "app-launcher__name", children: app.name })
225
237
  ]
226
238
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/AppLauncher.tsx","../src/icons.ts"],"sourcesContent":["'use client';\r\n\r\nimport React, { useState, useRef, useEffect } from 'react';\r\nimport { IoApps } from 'react-icons/io5';\r\nimport { AppLauncherProps, AppItem, ResolvedApp, AppLauncherConfig } from './types';\r\nimport { getIcon } from './icons';\r\nimport './styles.css';\r\n\r\n/**\r\n * A Google-style app launcher component\r\n *\r\n * @example\r\n * // With config URL\r\n * <AppLauncher configUrl=\"https://example.com/apps.json\" />\r\n *\r\n * @example\r\n * // With direct apps array\r\n * <AppLauncher apps={[{ id: '1', name: 'App', url: '/app', icon: 'FaRocket', color: '#4285F4' }]} />\r\n */\r\nexport function AppLauncher({\r\n configUrl,\r\n apps: propApps,\r\n className,\r\n onAppClick,\r\n renderFooter,\r\n}: AppLauncherProps) {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [apps, setApps] = useState<ResolvedApp[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n // Resolve apps from props or fetch from URL\r\n useEffect(() => {\r\n if (propApps) {\r\n setApps(propApps.map(resolveApp));\r\n return;\r\n }\r\n\r\n if (configUrl) {\r\n setLoading(true);\r\n setError(null);\r\n\r\n fetch(configUrl)\r\n .then((res) => {\r\n if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);\r\n return res.json();\r\n })\r\n .then((config: AppLauncherConfig) => {\r\n setApps(config.apps.map(resolveApp));\r\n })\r\n .catch((err) => {\r\n setError(err.message);\r\n console.error('AppLauncher: Failed to load config', err);\r\n })\r\n .finally(() => setLoading(false));\r\n }\r\n }, [configUrl, propApps]);\r\n\r\n // Close on click outside\r\n useEffect(() => {\r\n function handleClickOutside(event: MouseEvent) {\r\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\r\n setIsOpen(false);\r\n }\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('mousedown', handleClickOutside);\r\n }\r\n return () => document.removeEventListener('mousedown', handleClickOutside);\r\n }, [isOpen]);\r\n\r\n // Close on Escape\r\n useEffect(() => {\r\n function handleEscape(event: KeyboardEvent) {\r\n if (event.key === 'Escape') setIsOpen(false);\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('keydown', handleEscape);\r\n }\r\n return () => document.removeEventListener('keydown', handleEscape);\r\n }, [isOpen]);\r\n\r\n function resolveApp(app: AppItem): ResolvedApp {\r\n return {\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: getIcon(app.icon),\r\n color: app.color,\r\n description: app.description,\r\n };\r\n }\r\n\r\n function handleAppClick(app: ResolvedApp) {\r\n if (onAppClick) {\r\n onAppClick({\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: app.icon.name || 'FaRocket',\r\n color: app.color,\r\n description: app.description,\r\n });\r\n } else {\r\n // Open in new tab\r\n window.open(app.url, '_blank', 'noopener,noreferrer');\r\n }\r\n }\r\n\r\n return (\r\n <div className={`app-launcher ${className || ''}`} ref={containerRef}>\r\n {/* Trigger Button */}\r\n <button\r\n className=\"app-launcher__trigger\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n aria-label=\"Open app launcher\"\r\n aria-expanded={isOpen}\r\n >\r\n <IoApps className=\"app-launcher__trigger-icon\" />\r\n </button>\r\n\r\n {/* Dropdown */}\r\n {isOpen && (\r\n <div className=\"app-launcher__dropdown\">\r\n {loading && <div className=\"app-launcher__loading\">Loading...</div>}\r\n\r\n {error && <div className=\"app-launcher__error\">{error}</div>}\r\n\r\n {!loading && !error && (\r\n <div className=\"app-launcher__grid\">\r\n {apps.map((app) => (\r\n <button\r\n key={app.id}\r\n className=\"app-launcher__item\"\r\n onClick={() => handleAppClick(app)}\r\n title={app.description || app.name}\r\n >\r\n <div className=\"app-launcher__icon-wrapper\">\r\n <app.icon className=\"app-launcher__icon\" style={{ color: app.color }} />\r\n </div>\r\n <span className=\"app-launcher__name\">{app.name}</span>\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n\r\n {/* Custom footer (e.g., Settings button) */}\r\n {renderFooter && <div className=\"app-launcher__footer\">{renderFooter()}</div>}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nexport default AppLauncher;\r\n","import { IconType } from 'react-icons';\r\n// prettier-ignore\r\nimport {\r\n FaGoogle, FaEnvelope, FaYoutube, FaCalendarAlt, FaMapMarkerAlt,\r\n FaFile, FaBookmark, FaTable, FaNewspaper, FaImage, FaRocket,\r\n FaHome, FaUser, FaCog, FaChartBar, FaShoppingCart, FaDatabase,\r\n FaCode, FaTerminal, FaGlobe, FaLock, FaKey, FaBell, FaHeart,\r\n FaStar, FaFolder, FaClipboard, FaCalculator, FaMusic, FaCamera,\r\n FaGamepad, FaPuzzlePiece, FaBriefcase, FaGraduationCap, FaPlane,\r\n FaCar, FaBicycle, FaUtensils, FaCoffee, FaGift,\r\n} from 'react-icons/fa';\r\nimport { FaTicketSimple } from 'react-icons/fa6';\r\nimport { GrDeliver } from 'react-icons/gr';\r\nimport { IoBusinessSharp, IoApps } from 'react-icons/io5';\r\nimport { MdOutlineSecurity, MdDashboard, MdAnalytics, MdEmail, MdWork } from 'react-icons/md';\r\nimport { SiGoogledrive, SiGooglemeet } from 'react-icons/si';\r\nimport { AiOutlineSecurityScan } from 'react-icons/ai';\r\n\r\n/**\r\n * Map of icon names to icon components\r\n */\r\n// prettier-ignore\r\nexport const iconMap: Record<string, IconType> = {\r\n // Business & Work\r\n FaBriefcase, IoBusinessSharp, MdWork, FaGraduationCap,\r\n // Security\r\n MdOutlineSecurity, FaLock, FaKey, AiOutlineSecurityScan,\r\n // Communication\r\n FaEnvelope, MdEmail, FaBell,\r\n // Media\r\n FaYoutube, FaMusic, FaCamera, FaImage, FaGamepad,\r\n // Productivity\r\n FaCalendarAlt, FaClipboard, FaCalculator, FaFolder, FaFile,\r\n FaBookmark, FaTable, FaNewspaper,\r\n // Navigation\r\n FaMapMarkerAlt, FaGlobe, FaHome,\r\n // Google\r\n FaGoogle, SiGoogledrive, SiGooglemeet,\r\n // Development\r\n FaCode, FaTerminal, FaDatabase, FaPuzzlePiece,\r\n // Analytics\r\n FaChartBar, MdDashboard, MdAnalytics,\r\n // Shopping\r\n FaShoppingCart, FaGift, FaTicketSimple,\r\n // Travel\r\n FaPlane, FaCar, FaBicycle, GrDeliver,\r\n // Food\r\n FaUtensils, FaCoffee,\r\n // General\r\n FaRocket, FaUser, FaCog, FaHeart, FaStar, IoApps,\r\n};\r\n\r\n/**\r\n * Get icon component by name\r\n */\r\nexport function getIcon(name: string): IconType {\r\n return iconMap[name] || FaRocket;\r\n}\r\n"],"mappings":"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","names":["IoApps","IoApps"]}
1
+ {"version":3,"sources":["../src/AppLauncher.tsx","../src/icons.ts"],"sourcesContent":["'use client';\r\n\r\nimport React, { useState, useRef, useEffect } from 'react';\r\nimport { IoApps } from 'react-icons/io5';\r\nimport { AppLauncherProps, AppItem, ResolvedApp, AppLauncherConfig } from './types';\r\nimport { getIcon } from './icons';\r\nimport './styles.css';\r\n\r\n/**\r\n * A Google-style app launcher component\r\n *\r\n * @example\r\n * // With config URL\r\n * <AppLauncher configUrl=\"https://example.com/apps.json\" />\r\n *\r\n * @example\r\n * // With direct apps array\r\n * <AppLauncher apps={[{ id: '1', name: 'App', url: '/app', icon: 'FaRocket', color: '#4285F4' }]} />\r\n */\r\nexport function AppLauncher({\r\n configUrl,\r\n apps: propApps,\r\n className,\r\n onAppClick,\r\n renderFooter,\r\n}: AppLauncherProps) {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [apps, setApps] = useState<ResolvedApp[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n // Resolve apps from props or fetch from URL\r\n useEffect(() => {\r\n if (propApps) {\r\n setApps(propApps.map(resolveApp));\r\n return;\r\n }\r\n\r\n if (configUrl) {\r\n setLoading(true);\r\n setError(null);\r\n\r\n fetch(configUrl)\r\n .then((res) => {\r\n if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);\r\n return res.json();\r\n })\r\n .then((config: AppLauncherConfig) => {\r\n setApps(config.apps.map(resolveApp));\r\n })\r\n .catch((err) => {\r\n setError(err.message);\r\n console.error('AppLauncher: Failed to load config', err);\r\n })\r\n .finally(() => setLoading(false));\r\n }\r\n }, [configUrl, propApps]);\r\n\r\n // Close on click outside\r\n useEffect(() => {\r\n function handleClickOutside(event: MouseEvent) {\r\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\r\n setIsOpen(false);\r\n }\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('mousedown', handleClickOutside);\r\n }\r\n return () => document.removeEventListener('mousedown', handleClickOutside);\r\n }, [isOpen]);\r\n\r\n // Close on Escape\r\n useEffect(() => {\r\n function handleEscape(event: KeyboardEvent) {\r\n if (event.key === 'Escape') setIsOpen(false);\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('keydown', handleEscape);\r\n }\r\n return () => document.removeEventListener('keydown', handleEscape);\r\n }, [isOpen]);\r\n\r\n // Check if icon is a custom URL (path or full URL)\r\n function isCustomIconUrl(icon: string): boolean {\r\n return icon.startsWith('/') || icon.startsWith('http');\r\n }\r\n\r\n function resolveApp(app: AppItem): ResolvedApp {\r\n const isCustom = isCustomIconUrl(app.icon);\r\n return {\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: isCustom ? null : getIcon(app.icon),\r\n customIconUrl: isCustom ? app.icon : null,\r\n color: app.color,\r\n description: app.description,\r\n };\r\n }\r\n\r\n function handleAppClick(app: ResolvedApp) {\r\n if (onAppClick) {\r\n onAppClick({\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: app.customIconUrl || (app.icon?.name ?? 'FaRocket'),\r\n color: app.color,\r\n description: app.description,\r\n });\r\n } else {\r\n // Open in new tab\r\n window.open(app.url, '_blank', 'noopener,noreferrer');\r\n }\r\n }\r\n\r\n return (\r\n <div className={`app-launcher ${className || ''}`} ref={containerRef}>\r\n {/* Trigger Button */}\r\n <button\r\n className=\"app-launcher__trigger\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n aria-label=\"Open app launcher\"\r\n aria-expanded={isOpen}\r\n >\r\n <IoApps className=\"app-launcher__trigger-icon\" />\r\n </button>\r\n\r\n {/* Dropdown */}\r\n {isOpen && (\r\n <div className=\"app-launcher__dropdown\">\r\n {loading && <div className=\"app-launcher__loading\">Loading...</div>}\r\n\r\n {error && <div className=\"app-launcher__error\">{error}</div>}\r\n\r\n {!loading && !error && (\r\n <div className=\"app-launcher__grid\">\r\n {apps.map((app) => (\r\n <button\r\n key={app.id}\r\n className=\"app-launcher__item\"\r\n onClick={() => handleAppClick(app)}\r\n title={app.description || app.name}\r\n >\r\n <div className=\"app-launcher__icon-wrapper\">\r\n {app.customIconUrl ? (\r\n <img\r\n src={app.customIconUrl}\r\n alt={app.name}\r\n className=\"app-launcher__icon app-launcher__icon--custom\"\r\n />\r\n ) : app.icon ? (\r\n <app.icon className=\"app-launcher__icon\" style={{ color: app.color }} />\r\n ) : null}\r\n </div>\r\n <span className=\"app-launcher__name\">{app.name}</span>\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n\r\n {/* Custom footer (e.g., Settings button) */}\r\n {renderFooter && <div className=\"app-launcher__footer\">{renderFooter()}</div>}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nexport default AppLauncher;\r\n","import { IconType } from 'react-icons';\r\n// prettier-ignore\r\nimport {\r\n FaGoogle, FaEnvelope, FaYoutube, FaCalendarAlt, FaMapMarkerAlt,\r\n FaFile, FaBookmark, FaTable, FaNewspaper, FaImage, FaRocket,\r\n FaHome, FaUser, FaCog, FaChartBar, FaShoppingCart, FaDatabase,\r\n FaCode, FaTerminal, FaGlobe, FaLock, FaKey, FaBell, FaHeart,\r\n FaStar, FaFolder, FaClipboard, FaCalculator, FaMusic, FaCamera,\r\n FaGamepad, FaPuzzlePiece, FaBriefcase, FaGraduationCap, FaPlane,\r\n FaCar, FaBicycle, FaUtensils, FaCoffee, FaGift,\r\n} from 'react-icons/fa';\r\nimport { FaTicketSimple } from 'react-icons/fa6';\r\nimport { GrDeliver } from 'react-icons/gr';\r\nimport { IoBusinessSharp, IoApps } from 'react-icons/io5';\r\nimport { MdOutlineSecurity, MdDashboard, MdAnalytics, MdEmail, MdWork } from 'react-icons/md';\r\nimport { SiGoogledrive, SiGooglemeet } from 'react-icons/si';\r\nimport { AiOutlineSecurityScan } from 'react-icons/ai';\r\n\r\n/**\r\n * Map of icon names to icon components\r\n */\r\n// prettier-ignore\r\nexport const iconMap: Record<string, IconType> = {\r\n // Business & Work\r\n FaBriefcase, IoBusinessSharp, MdWork, FaGraduationCap,\r\n // Security\r\n MdOutlineSecurity, FaLock, FaKey, AiOutlineSecurityScan,\r\n // Communication\r\n FaEnvelope, MdEmail, FaBell,\r\n // Media\r\n FaYoutube, FaMusic, FaCamera, FaImage, FaGamepad,\r\n // Productivity\r\n FaCalendarAlt, FaClipboard, FaCalculator, FaFolder, FaFile,\r\n FaBookmark, FaTable, FaNewspaper,\r\n // Navigation\r\n FaMapMarkerAlt, FaGlobe, FaHome,\r\n // Google\r\n FaGoogle, SiGoogledrive, SiGooglemeet,\r\n // Development\r\n FaCode, FaTerminal, FaDatabase, FaPuzzlePiece,\r\n // Analytics\r\n FaChartBar, MdDashboard, MdAnalytics,\r\n // Shopping\r\n FaShoppingCart, FaGift, FaTicketSimple,\r\n // Travel\r\n FaPlane, FaCar, FaBicycle, GrDeliver,\r\n // Food\r\n FaUtensils, FaCoffee,\r\n // General\r\n FaRocket, FaUser, FaCog, FaHeart, FaStar, IoApps,\r\n};\r\n\r\n/**\r\n * Get icon component by name\r\n */\r\nexport function getIcon(name: string): IconType {\r\n return iconMap[name] || FaRocket;\r\n}\r\n"],"mappings":"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","names":["IoApps","IoApps"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moraby/app-launcher",
3
- "version": "0.1.3",
3
+ "version": "0.1.6",
4
4
  "description": "A Google-style app launcher component for React/Next.js applications",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",