@moraby/app-launcher 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -0
- package/dist/index.css +147 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +80 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +231 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +243 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# @moraby/app-launcher
|
|
2
|
+
|
|
3
|
+
A Google-style app launcher component for React/Next.js applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @moraby/app-launcher
|
|
9
|
+
# or
|
|
10
|
+
yarn add @moraby/app-launcher
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### With Configuration URL
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { AppLauncher } from '@moraby/app-launcher';
|
|
19
|
+
import '@moraby/app-launcher/styles.css';
|
|
20
|
+
|
|
21
|
+
function Header() {
|
|
22
|
+
return (
|
|
23
|
+
<header>
|
|
24
|
+
<h1>My App</h1>
|
|
25
|
+
<AppLauncher configUrl="https://example.com/apps.json" />
|
|
26
|
+
</header>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### With Direct Apps Array
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { AppLauncher, AppItem } from '@moraby/app-launcher';
|
|
35
|
+
import '@moraby/app-launcher/styles.css';
|
|
36
|
+
|
|
37
|
+
const apps: AppItem[] = [
|
|
38
|
+
{
|
|
39
|
+
id: 'dashboard',
|
|
40
|
+
name: 'Dashboard',
|
|
41
|
+
url: '/dashboard',
|
|
42
|
+
icon: 'MdDashboard',
|
|
43
|
+
color: '#4285F4',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'settings',
|
|
47
|
+
name: 'Settings',
|
|
48
|
+
url: '/settings',
|
|
49
|
+
icon: 'FaCog',
|
|
50
|
+
color: '#5f6368',
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
function Header() {
|
|
55
|
+
return (
|
|
56
|
+
<header>
|
|
57
|
+
<AppLauncher apps={apps} />
|
|
58
|
+
</header>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Configuration JSON Format
|
|
64
|
+
|
|
65
|
+
Export your configuration from the admin app and host it as a JSON file:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"version": "1.0",
|
|
70
|
+
"exportedAt": "2024-01-15T10:00:00.000Z",
|
|
71
|
+
"apps": [
|
|
72
|
+
{
|
|
73
|
+
"id": "my-app",
|
|
74
|
+
"name": "My App",
|
|
75
|
+
"url": "https://myapp.example.com",
|
|
76
|
+
"icon": "FaRocket",
|
|
77
|
+
"color": "#4285F4",
|
|
78
|
+
"description": "My awesome app"
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Props
|
|
85
|
+
|
|
86
|
+
| Prop | Type | Description |
|
|
87
|
+
| ------------ | ------------------------ | --------------------------------------------- |
|
|
88
|
+
| `configUrl` | `string` | URL to fetch configuration from (JSON or API) |
|
|
89
|
+
| `apps` | `AppItem[]` | Direct array of apps to display |
|
|
90
|
+
| `className` | `string` | Custom class name for the container |
|
|
91
|
+
| `onAppClick` | `(app: AppItem) => void` | Custom click handler |
|
|
92
|
+
|
|
93
|
+
## Available Icons
|
|
94
|
+
|
|
95
|
+
The package includes 50+ icons from react-icons. Use any of these names:
|
|
96
|
+
|
|
97
|
+
**Business:** FaBriefcase, IoBusinessSharp, MdWork, FaGraduationCap
|
|
98
|
+
|
|
99
|
+
**Security:** MdOutlineSecurity, FaLock, FaKey, AiOutlineSecurityScan
|
|
100
|
+
|
|
101
|
+
**Communication:** FaEnvelope, MdEmail, FaBell
|
|
102
|
+
|
|
103
|
+
**Media:** FaYoutube, FaMusic, FaCamera, FaImage, FaGamepad
|
|
104
|
+
|
|
105
|
+
**Productivity:** FaCalendarAlt, FaClipboard, FaCalculator, FaFolder, FaFile, FaBookmark, FaTable, FaNewspaper
|
|
106
|
+
|
|
107
|
+
**Navigation:** FaMapMarkerAlt, FaGlobe, FaHome
|
|
108
|
+
|
|
109
|
+
**Development:** FaCode, FaTerminal, FaDatabase, FaPuzzlePiece
|
|
110
|
+
|
|
111
|
+
**Analytics:** FaChartBar, MdDashboard, MdAnalytics
|
|
112
|
+
|
|
113
|
+
**General:** FaRocket, FaUser, FaCog, FaHeart, FaStar, IoApps
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
package/dist/index.css
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/* src/styles.css */
|
|
2
|
+
.app-launcher {
|
|
3
|
+
position: relative;
|
|
4
|
+
display: inline-flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
}
|
|
7
|
+
.app-launcher__trigger {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
width: 40px;
|
|
12
|
+
height: 40px;
|
|
13
|
+
border: none;
|
|
14
|
+
background: transparent;
|
|
15
|
+
border-radius: 50%;
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
transition: background-color 0.2s ease;
|
|
18
|
+
}
|
|
19
|
+
.app-launcher__trigger:hover {
|
|
20
|
+
background-color: rgba(0, 0, 0, 0.08);
|
|
21
|
+
}
|
|
22
|
+
.app-launcher__trigger:focus {
|
|
23
|
+
outline: none;
|
|
24
|
+
background-color: rgba(0, 0, 0, 0.12);
|
|
25
|
+
}
|
|
26
|
+
.app-launcher__trigger-icon {
|
|
27
|
+
font-size: 24px;
|
|
28
|
+
color: #5f6368;
|
|
29
|
+
}
|
|
30
|
+
.app-launcher__dropdown {
|
|
31
|
+
position: absolute;
|
|
32
|
+
top: calc(100% + 8px);
|
|
33
|
+
right: 0;
|
|
34
|
+
width: 336px;
|
|
35
|
+
max-height: 70vh;
|
|
36
|
+
overflow-y: auto;
|
|
37
|
+
background-color: #ffffff;
|
|
38
|
+
border-radius: 8px;
|
|
39
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1), 0 8px 40px rgba(0, 0, 0, 0.2);
|
|
40
|
+
z-index: 9999;
|
|
41
|
+
animation: app-launcher-slide-in 0.15s ease-out;
|
|
42
|
+
}
|
|
43
|
+
@keyframes app-launcher-slide-in {
|
|
44
|
+
from {
|
|
45
|
+
opacity: 0;
|
|
46
|
+
transform: translateY(-8px) scale(0.98);
|
|
47
|
+
}
|
|
48
|
+
to {
|
|
49
|
+
opacity: 1;
|
|
50
|
+
transform: translateY(0) scale(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
.app-launcher__grid {
|
|
54
|
+
display: grid;
|
|
55
|
+
grid-template-columns: repeat(3, 1fr);
|
|
56
|
+
gap: 4px;
|
|
57
|
+
padding: 16px 8px;
|
|
58
|
+
}
|
|
59
|
+
.app-launcher__item {
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: center;
|
|
64
|
+
padding: 12px 8px;
|
|
65
|
+
border: none;
|
|
66
|
+
background: transparent;
|
|
67
|
+
border-radius: 8px;
|
|
68
|
+
cursor: pointer;
|
|
69
|
+
transition: background-color 0.15s ease;
|
|
70
|
+
min-height: 90px;
|
|
71
|
+
}
|
|
72
|
+
.app-launcher__item:hover {
|
|
73
|
+
background-color: rgba(0, 0, 0, 0.04);
|
|
74
|
+
}
|
|
75
|
+
.app-launcher__item:focus {
|
|
76
|
+
outline: none;
|
|
77
|
+
background-color: rgba(0, 0, 0, 0.08);
|
|
78
|
+
}
|
|
79
|
+
.app-launcher__icon-wrapper {
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
justify-content: center;
|
|
83
|
+
width: 48px;
|
|
84
|
+
height: 48px;
|
|
85
|
+
margin-bottom: 8px;
|
|
86
|
+
}
|
|
87
|
+
.app-launcher__icon {
|
|
88
|
+
font-size: 32px;
|
|
89
|
+
transition: transform 0.15s ease;
|
|
90
|
+
}
|
|
91
|
+
.app-launcher__item:hover .app-launcher__icon {
|
|
92
|
+
transform: scale(1.1);
|
|
93
|
+
}
|
|
94
|
+
.app-launcher__name {
|
|
95
|
+
font-family:
|
|
96
|
+
"Google Sans",
|
|
97
|
+
"Roboto",
|
|
98
|
+
-apple-system,
|
|
99
|
+
BlinkMacSystemFont,
|
|
100
|
+
sans-serif;
|
|
101
|
+
font-size: 13px;
|
|
102
|
+
font-weight: 400;
|
|
103
|
+
color: #3c4043;
|
|
104
|
+
text-align: center;
|
|
105
|
+
line-height: 1.3;
|
|
106
|
+
max-width: 100%;
|
|
107
|
+
overflow: hidden;
|
|
108
|
+
text-overflow: ellipsis;
|
|
109
|
+
white-space: nowrap;
|
|
110
|
+
}
|
|
111
|
+
.app-launcher__loading,
|
|
112
|
+
.app-launcher__error {
|
|
113
|
+
padding: 24px;
|
|
114
|
+
text-align: center;
|
|
115
|
+
font-size: 14px;
|
|
116
|
+
color: #5f6368;
|
|
117
|
+
}
|
|
118
|
+
.app-launcher__error {
|
|
119
|
+
color: #d93025;
|
|
120
|
+
}
|
|
121
|
+
.app-launcher__dropdown::-webkit-scrollbar {
|
|
122
|
+
width: 8px;
|
|
123
|
+
}
|
|
124
|
+
.app-launcher__dropdown::-webkit-scrollbar-track {
|
|
125
|
+
background: transparent;
|
|
126
|
+
}
|
|
127
|
+
.app-launcher__dropdown::-webkit-scrollbar-thumb {
|
|
128
|
+
background-color: #dadce0;
|
|
129
|
+
border-radius: 4px;
|
|
130
|
+
}
|
|
131
|
+
@media (max-width: 400px) {
|
|
132
|
+
.app-launcher__dropdown {
|
|
133
|
+
width: 280px;
|
|
134
|
+
right: -8px;
|
|
135
|
+
}
|
|
136
|
+
.app-launcher__icon-wrapper {
|
|
137
|
+
width: 40px;
|
|
138
|
+
height: 40px;
|
|
139
|
+
}
|
|
140
|
+
.app-launcher__icon {
|
|
141
|
+
font-size: 28px;
|
|
142
|
+
}
|
|
143
|
+
.app-launcher__name {
|
|
144
|
+
font-size: 12px;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/*# sourceMappingURL=index.css.map */
|
|
@@ -0,0 +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/* 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,QAAO,WAAY;AACjB,GAvHD;AAwHG,WAAO;AACP,WAAO;AACT;AAEA,GAlED;AAmEG,WAAO;AACP,YAAQ;AACV;AAEA,GA9DD;AA+DG,eAAW;AACb;AAEA,GAxDD;AAyDG,eAAW;AACb;AACF;","names":[]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { IconType } from 'react-icons';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* App configuration for the launcher
|
|
6
|
+
*/
|
|
7
|
+
interface AppItem {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
url: string;
|
|
11
|
+
icon: string;
|
|
12
|
+
color: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Configuration object format (from JSON file or API)
|
|
17
|
+
*/
|
|
18
|
+
interface AppLauncherConfig {
|
|
19
|
+
version?: string;
|
|
20
|
+
exportedAt?: string;
|
|
21
|
+
apps: AppItem[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Props for the AppLauncher component
|
|
25
|
+
*/
|
|
26
|
+
interface AppLauncherProps {
|
|
27
|
+
/**
|
|
28
|
+
* URL to fetch the configuration from (JSON file or API endpoint)
|
|
29
|
+
* Either configUrl or apps must be provided
|
|
30
|
+
*/
|
|
31
|
+
configUrl?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Direct array of apps to display
|
|
34
|
+
* Either configUrl or apps must be provided
|
|
35
|
+
*/
|
|
36
|
+
apps?: AppItem[];
|
|
37
|
+
/**
|
|
38
|
+
* Custom class name for the container
|
|
39
|
+
*/
|
|
40
|
+
className?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Called when an app is clicked
|
|
43
|
+
*/
|
|
44
|
+
onAppClick?: (app: AppItem) => void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Internal app representation with resolved icon
|
|
48
|
+
*/
|
|
49
|
+
interface ResolvedApp {
|
|
50
|
+
id: string;
|
|
51
|
+
name: string;
|
|
52
|
+
url: string;
|
|
53
|
+
icon: IconType;
|
|
54
|
+
color: string;
|
|
55
|
+
description?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A Google-style app launcher component
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // With config URL
|
|
63
|
+
* <AppLauncher configUrl="https://example.com/apps.json" />
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* // With direct apps array
|
|
67
|
+
* <AppLauncher apps={[{ id: '1', name: 'App', url: '/app', icon: 'FaRocket', color: '#4285F4' }]} />
|
|
68
|
+
*/
|
|
69
|
+
declare function AppLauncher({ configUrl, apps: propApps, className, onAppClick, }: AppLauncherProps): react_jsx_runtime.JSX.Element;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Map of icon names to icon components
|
|
73
|
+
*/
|
|
74
|
+
declare const iconMap: Record<string, IconType>;
|
|
75
|
+
/**
|
|
76
|
+
* Get icon component by name
|
|
77
|
+
*/
|
|
78
|
+
declare function getIcon(name: string): IconType;
|
|
79
|
+
|
|
80
|
+
export { type AppItem, AppLauncher, type AppLauncherConfig, type AppLauncherProps, type ResolvedApp, AppLauncher as default, getIcon, iconMap };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { IconType } from 'react-icons';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* App configuration for the launcher
|
|
6
|
+
*/
|
|
7
|
+
interface AppItem {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
url: string;
|
|
11
|
+
icon: string;
|
|
12
|
+
color: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Configuration object format (from JSON file or API)
|
|
17
|
+
*/
|
|
18
|
+
interface AppLauncherConfig {
|
|
19
|
+
version?: string;
|
|
20
|
+
exportedAt?: string;
|
|
21
|
+
apps: AppItem[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Props for the AppLauncher component
|
|
25
|
+
*/
|
|
26
|
+
interface AppLauncherProps {
|
|
27
|
+
/**
|
|
28
|
+
* URL to fetch the configuration from (JSON file or API endpoint)
|
|
29
|
+
* Either configUrl or apps must be provided
|
|
30
|
+
*/
|
|
31
|
+
configUrl?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Direct array of apps to display
|
|
34
|
+
* Either configUrl or apps must be provided
|
|
35
|
+
*/
|
|
36
|
+
apps?: AppItem[];
|
|
37
|
+
/**
|
|
38
|
+
* Custom class name for the container
|
|
39
|
+
*/
|
|
40
|
+
className?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Called when an app is clicked
|
|
43
|
+
*/
|
|
44
|
+
onAppClick?: (app: AppItem) => void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Internal app representation with resolved icon
|
|
48
|
+
*/
|
|
49
|
+
interface ResolvedApp {
|
|
50
|
+
id: string;
|
|
51
|
+
name: string;
|
|
52
|
+
url: string;
|
|
53
|
+
icon: IconType;
|
|
54
|
+
color: string;
|
|
55
|
+
description?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A Google-style app launcher component
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // With config URL
|
|
63
|
+
* <AppLauncher configUrl="https://example.com/apps.json" />
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* // With direct apps array
|
|
67
|
+
* <AppLauncher apps={[{ id: '1', name: 'App', url: '/app', icon: 'FaRocket', color: '#4285F4' }]} />
|
|
68
|
+
*/
|
|
69
|
+
declare function AppLauncher({ configUrl, apps: propApps, className, onAppClick, }: AppLauncherProps): react_jsx_runtime.JSX.Element;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Map of icon names to icon components
|
|
73
|
+
*/
|
|
74
|
+
declare const iconMap: Record<string, IconType>;
|
|
75
|
+
/**
|
|
76
|
+
* Get icon component by name
|
|
77
|
+
*/
|
|
78
|
+
declare function getIcon(name: string): IconType;
|
|
79
|
+
|
|
80
|
+
export { type AppItem, AppLauncher, type AppLauncherConfig, type AppLauncherProps, type ResolvedApp, AppLauncher as default, getIcon, iconMap };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AppLauncher: () => AppLauncher,
|
|
24
|
+
default: () => AppLauncher,
|
|
25
|
+
getIcon: () => getIcon,
|
|
26
|
+
iconMap: () => iconMap
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/AppLauncher.tsx
|
|
31
|
+
var import_react = require("react");
|
|
32
|
+
var import_io52 = require("react-icons/io5");
|
|
33
|
+
|
|
34
|
+
// src/icons.ts
|
|
35
|
+
var import_fa = require("react-icons/fa");
|
|
36
|
+
var import_fa6 = require("react-icons/fa6");
|
|
37
|
+
var import_gr = require("react-icons/gr");
|
|
38
|
+
var import_io5 = require("react-icons/io5");
|
|
39
|
+
var import_md = require("react-icons/md");
|
|
40
|
+
var import_si = require("react-icons/si");
|
|
41
|
+
var import_ai = require("react-icons/ai");
|
|
42
|
+
var iconMap = {
|
|
43
|
+
// Business & Work
|
|
44
|
+
FaBriefcase: import_fa.FaBriefcase,
|
|
45
|
+
IoBusinessSharp: import_io5.IoBusinessSharp,
|
|
46
|
+
MdWork: import_md.MdWork,
|
|
47
|
+
FaGraduationCap: import_fa.FaGraduationCap,
|
|
48
|
+
// Security
|
|
49
|
+
MdOutlineSecurity: import_md.MdOutlineSecurity,
|
|
50
|
+
FaLock: import_fa.FaLock,
|
|
51
|
+
FaKey: import_fa.FaKey,
|
|
52
|
+
AiOutlineSecurityScan: import_ai.AiOutlineSecurityScan,
|
|
53
|
+
// Communication
|
|
54
|
+
FaEnvelope: import_fa.FaEnvelope,
|
|
55
|
+
MdEmail: import_md.MdEmail,
|
|
56
|
+
FaBell: import_fa.FaBell,
|
|
57
|
+
// Media
|
|
58
|
+
FaYoutube: import_fa.FaYoutube,
|
|
59
|
+
FaMusic: import_fa.FaMusic,
|
|
60
|
+
FaCamera: import_fa.FaCamera,
|
|
61
|
+
FaImage: import_fa.FaImage,
|
|
62
|
+
FaGamepad: import_fa.FaGamepad,
|
|
63
|
+
// Productivity
|
|
64
|
+
FaCalendarAlt: import_fa.FaCalendarAlt,
|
|
65
|
+
FaClipboard: import_fa.FaClipboard,
|
|
66
|
+
FaCalculator: import_fa.FaCalculator,
|
|
67
|
+
FaFolder: import_fa.FaFolder,
|
|
68
|
+
FaFile: import_fa.FaFile,
|
|
69
|
+
FaBookmark: import_fa.FaBookmark,
|
|
70
|
+
FaTable: import_fa.FaTable,
|
|
71
|
+
FaNewspaper: import_fa.FaNewspaper,
|
|
72
|
+
// Navigation
|
|
73
|
+
FaMapMarkerAlt: import_fa.FaMapMarkerAlt,
|
|
74
|
+
FaGlobe: import_fa.FaGlobe,
|
|
75
|
+
FaHome: import_fa.FaHome,
|
|
76
|
+
// Google
|
|
77
|
+
FaGoogle: import_fa.FaGoogle,
|
|
78
|
+
SiGoogledrive: import_si.SiGoogledrive,
|
|
79
|
+
SiGooglemeet: import_si.SiGooglemeet,
|
|
80
|
+
// Development
|
|
81
|
+
FaCode: import_fa.FaCode,
|
|
82
|
+
FaTerminal: import_fa.FaTerminal,
|
|
83
|
+
FaDatabase: import_fa.FaDatabase,
|
|
84
|
+
FaPuzzlePiece: import_fa.FaPuzzlePiece,
|
|
85
|
+
// Analytics
|
|
86
|
+
FaChartBar: import_fa.FaChartBar,
|
|
87
|
+
MdDashboard: import_md.MdDashboard,
|
|
88
|
+
MdAnalytics: import_md.MdAnalytics,
|
|
89
|
+
// Shopping
|
|
90
|
+
FaShoppingCart: import_fa.FaShoppingCart,
|
|
91
|
+
FaGift: import_fa.FaGift,
|
|
92
|
+
FaTicketSimple: import_fa6.FaTicketSimple,
|
|
93
|
+
// Travel
|
|
94
|
+
FaPlane: import_fa.FaPlane,
|
|
95
|
+
FaCar: import_fa.FaCar,
|
|
96
|
+
FaBicycle: import_fa.FaBicycle,
|
|
97
|
+
GrDeliver: import_gr.GrDeliver,
|
|
98
|
+
// Food
|
|
99
|
+
FaUtensils: import_fa.FaUtensils,
|
|
100
|
+
FaCoffee: import_fa.FaCoffee,
|
|
101
|
+
// General
|
|
102
|
+
FaRocket: import_fa.FaRocket,
|
|
103
|
+
FaUser: import_fa.FaUser,
|
|
104
|
+
FaCog: import_fa.FaCog,
|
|
105
|
+
FaHeart: import_fa.FaHeart,
|
|
106
|
+
FaStar: import_fa.FaStar,
|
|
107
|
+
IoApps: import_io5.IoApps
|
|
108
|
+
};
|
|
109
|
+
function getIcon(name) {
|
|
110
|
+
return iconMap[name] || import_fa.FaRocket;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/AppLauncher.tsx
|
|
114
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
115
|
+
function AppLauncher({
|
|
116
|
+
configUrl,
|
|
117
|
+
apps: propApps,
|
|
118
|
+
className,
|
|
119
|
+
onAppClick
|
|
120
|
+
}) {
|
|
121
|
+
const [isOpen, setIsOpen] = (0, import_react.useState)(false);
|
|
122
|
+
const [apps, setApps] = (0, import_react.useState)([]);
|
|
123
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
124
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
125
|
+
const containerRef = (0, import_react.useRef)(null);
|
|
126
|
+
(0, import_react.useEffect)(() => {
|
|
127
|
+
if (propApps) {
|
|
128
|
+
setApps(propApps.map(resolveApp));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (configUrl) {
|
|
132
|
+
setLoading(true);
|
|
133
|
+
setError(null);
|
|
134
|
+
fetch(configUrl).then((res) => {
|
|
135
|
+
if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);
|
|
136
|
+
return res.json();
|
|
137
|
+
}).then((config) => {
|
|
138
|
+
setApps(config.apps.map(resolveApp));
|
|
139
|
+
}).catch((err) => {
|
|
140
|
+
setError(err.message);
|
|
141
|
+
console.error("AppLauncher: Failed to load config", err);
|
|
142
|
+
}).finally(() => setLoading(false));
|
|
143
|
+
}
|
|
144
|
+
}, [configUrl, propApps]);
|
|
145
|
+
(0, import_react.useEffect)(() => {
|
|
146
|
+
function handleClickOutside(event) {
|
|
147
|
+
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
|
148
|
+
setIsOpen(false);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (isOpen) {
|
|
152
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
153
|
+
}
|
|
154
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
155
|
+
}, [isOpen]);
|
|
156
|
+
(0, import_react.useEffect)(() => {
|
|
157
|
+
function handleEscape(event) {
|
|
158
|
+
if (event.key === "Escape") setIsOpen(false);
|
|
159
|
+
}
|
|
160
|
+
if (isOpen) {
|
|
161
|
+
document.addEventListener("keydown", handleEscape);
|
|
162
|
+
}
|
|
163
|
+
return () => document.removeEventListener("keydown", handleEscape);
|
|
164
|
+
}, [isOpen]);
|
|
165
|
+
function resolveApp(app) {
|
|
166
|
+
return {
|
|
167
|
+
id: app.id,
|
|
168
|
+
name: app.name,
|
|
169
|
+
url: app.url,
|
|
170
|
+
icon: getIcon(app.icon),
|
|
171
|
+
color: app.color,
|
|
172
|
+
description: app.description
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function handleAppClick(app) {
|
|
176
|
+
if (onAppClick) {
|
|
177
|
+
onAppClick({
|
|
178
|
+
id: app.id,
|
|
179
|
+
name: app.name,
|
|
180
|
+
url: app.url,
|
|
181
|
+
icon: app.icon.name || "FaRocket",
|
|
182
|
+
color: app.color,
|
|
183
|
+
description: app.description
|
|
184
|
+
});
|
|
185
|
+
} else {
|
|
186
|
+
window.location.href = app.url;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `app-launcher ${className || ""}`, ref: containerRef, children: [
|
|
190
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
191
|
+
"button",
|
|
192
|
+
{
|
|
193
|
+
className: "app-launcher__trigger",
|
|
194
|
+
onClick: () => setIsOpen(!isOpen),
|
|
195
|
+
"aria-label": "Open app launcher",
|
|
196
|
+
"aria-expanded": isOpen,
|
|
197
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_io52.IoApps, { className: "app-launcher__trigger-icon" })
|
|
198
|
+
}
|
|
199
|
+
),
|
|
200
|
+
isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "app-launcher__dropdown", children: [
|
|
201
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "app-launcher__loading", children: "Loading..." }),
|
|
202
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "app-launcher__error", children: error }),
|
|
203
|
+
!loading && !error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "app-launcher__grid", children: apps.map((app) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
204
|
+
"button",
|
|
205
|
+
{
|
|
206
|
+
className: "app-launcher__item",
|
|
207
|
+
onClick: () => handleAppClick(app),
|
|
208
|
+
title: app.description || app.name,
|
|
209
|
+
children: [
|
|
210
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "app-launcher__icon-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
211
|
+
app.icon,
|
|
212
|
+
{
|
|
213
|
+
className: "app-launcher__icon",
|
|
214
|
+
style: { color: app.color }
|
|
215
|
+
}
|
|
216
|
+
) }),
|
|
217
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "app-launcher__name", children: app.name })
|
|
218
|
+
]
|
|
219
|
+
},
|
|
220
|
+
app.id
|
|
221
|
+
)) })
|
|
222
|
+
] })
|
|
223
|
+
] });
|
|
224
|
+
}
|
|
225
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
226
|
+
0 && (module.exports = {
|
|
227
|
+
AppLauncher,
|
|
228
|
+
getIcon,
|
|
229
|
+
iconMap
|
|
230
|
+
});
|
|
231
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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 {\r\n AppLauncherProps,\r\n AppItem,\r\n ResolvedApp,\r\n AppLauncherConfig,\r\n} 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}: 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 (\r\n containerRef.current &&\r\n !containerRef.current.contains(event.target as Node)\r\n ) {\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 window.location.href = app.url;\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\r\n className='app-launcher__icon'\r\n style={{ color: app.color }}\r\n />\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 </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
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// src/AppLauncher.tsx
|
|
2
|
+
import { useState, useRef, useEffect } from "react";
|
|
3
|
+
import { IoApps as IoApps2 } from "react-icons/io5";
|
|
4
|
+
|
|
5
|
+
// src/icons.ts
|
|
6
|
+
import {
|
|
7
|
+
FaGoogle,
|
|
8
|
+
FaEnvelope,
|
|
9
|
+
FaYoutube,
|
|
10
|
+
FaCalendarAlt,
|
|
11
|
+
FaMapMarkerAlt,
|
|
12
|
+
FaFile,
|
|
13
|
+
FaBookmark,
|
|
14
|
+
FaTable,
|
|
15
|
+
FaNewspaper,
|
|
16
|
+
FaImage,
|
|
17
|
+
FaRocket,
|
|
18
|
+
FaHome,
|
|
19
|
+
FaUser,
|
|
20
|
+
FaCog,
|
|
21
|
+
FaChartBar,
|
|
22
|
+
FaShoppingCart,
|
|
23
|
+
FaDatabase,
|
|
24
|
+
FaCode,
|
|
25
|
+
FaTerminal,
|
|
26
|
+
FaGlobe,
|
|
27
|
+
FaLock,
|
|
28
|
+
FaKey,
|
|
29
|
+
FaBell,
|
|
30
|
+
FaHeart,
|
|
31
|
+
FaStar,
|
|
32
|
+
FaFolder,
|
|
33
|
+
FaClipboard,
|
|
34
|
+
FaCalculator,
|
|
35
|
+
FaMusic,
|
|
36
|
+
FaCamera,
|
|
37
|
+
FaGamepad,
|
|
38
|
+
FaPuzzlePiece,
|
|
39
|
+
FaBriefcase,
|
|
40
|
+
FaGraduationCap,
|
|
41
|
+
FaPlane,
|
|
42
|
+
FaCar,
|
|
43
|
+
FaBicycle,
|
|
44
|
+
FaUtensils,
|
|
45
|
+
FaCoffee,
|
|
46
|
+
FaGift
|
|
47
|
+
} from "react-icons/fa";
|
|
48
|
+
import { FaTicketSimple } from "react-icons/fa6";
|
|
49
|
+
import { GrDeliver } from "react-icons/gr";
|
|
50
|
+
import { IoBusinessSharp, IoApps } from "react-icons/io5";
|
|
51
|
+
import { MdOutlineSecurity, MdDashboard, MdAnalytics, MdEmail, MdWork } from "react-icons/md";
|
|
52
|
+
import { SiGoogledrive, SiGooglemeet } from "react-icons/si";
|
|
53
|
+
import { AiOutlineSecurityScan } from "react-icons/ai";
|
|
54
|
+
var iconMap = {
|
|
55
|
+
// Business & Work
|
|
56
|
+
FaBriefcase,
|
|
57
|
+
IoBusinessSharp,
|
|
58
|
+
MdWork,
|
|
59
|
+
FaGraduationCap,
|
|
60
|
+
// Security
|
|
61
|
+
MdOutlineSecurity,
|
|
62
|
+
FaLock,
|
|
63
|
+
FaKey,
|
|
64
|
+
AiOutlineSecurityScan,
|
|
65
|
+
// Communication
|
|
66
|
+
FaEnvelope,
|
|
67
|
+
MdEmail,
|
|
68
|
+
FaBell,
|
|
69
|
+
// Media
|
|
70
|
+
FaYoutube,
|
|
71
|
+
FaMusic,
|
|
72
|
+
FaCamera,
|
|
73
|
+
FaImage,
|
|
74
|
+
FaGamepad,
|
|
75
|
+
// Productivity
|
|
76
|
+
FaCalendarAlt,
|
|
77
|
+
FaClipboard,
|
|
78
|
+
FaCalculator,
|
|
79
|
+
FaFolder,
|
|
80
|
+
FaFile,
|
|
81
|
+
FaBookmark,
|
|
82
|
+
FaTable,
|
|
83
|
+
FaNewspaper,
|
|
84
|
+
// Navigation
|
|
85
|
+
FaMapMarkerAlt,
|
|
86
|
+
FaGlobe,
|
|
87
|
+
FaHome,
|
|
88
|
+
// Google
|
|
89
|
+
FaGoogle,
|
|
90
|
+
SiGoogledrive,
|
|
91
|
+
SiGooglemeet,
|
|
92
|
+
// Development
|
|
93
|
+
FaCode,
|
|
94
|
+
FaTerminal,
|
|
95
|
+
FaDatabase,
|
|
96
|
+
FaPuzzlePiece,
|
|
97
|
+
// Analytics
|
|
98
|
+
FaChartBar,
|
|
99
|
+
MdDashboard,
|
|
100
|
+
MdAnalytics,
|
|
101
|
+
// Shopping
|
|
102
|
+
FaShoppingCart,
|
|
103
|
+
FaGift,
|
|
104
|
+
FaTicketSimple,
|
|
105
|
+
// Travel
|
|
106
|
+
FaPlane,
|
|
107
|
+
FaCar,
|
|
108
|
+
FaBicycle,
|
|
109
|
+
GrDeliver,
|
|
110
|
+
// Food
|
|
111
|
+
FaUtensils,
|
|
112
|
+
FaCoffee,
|
|
113
|
+
// General
|
|
114
|
+
FaRocket,
|
|
115
|
+
FaUser,
|
|
116
|
+
FaCog,
|
|
117
|
+
FaHeart,
|
|
118
|
+
FaStar,
|
|
119
|
+
IoApps
|
|
120
|
+
};
|
|
121
|
+
function getIcon(name) {
|
|
122
|
+
return iconMap[name] || FaRocket;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/AppLauncher.tsx
|
|
126
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
127
|
+
function AppLauncher({
|
|
128
|
+
configUrl,
|
|
129
|
+
apps: propApps,
|
|
130
|
+
className,
|
|
131
|
+
onAppClick
|
|
132
|
+
}) {
|
|
133
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
134
|
+
const [apps, setApps] = useState([]);
|
|
135
|
+
const [loading, setLoading] = useState(false);
|
|
136
|
+
const [error, setError] = useState(null);
|
|
137
|
+
const containerRef = useRef(null);
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (propApps) {
|
|
140
|
+
setApps(propApps.map(resolveApp));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (configUrl) {
|
|
144
|
+
setLoading(true);
|
|
145
|
+
setError(null);
|
|
146
|
+
fetch(configUrl).then((res) => {
|
|
147
|
+
if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);
|
|
148
|
+
return res.json();
|
|
149
|
+
}).then((config) => {
|
|
150
|
+
setApps(config.apps.map(resolveApp));
|
|
151
|
+
}).catch((err) => {
|
|
152
|
+
setError(err.message);
|
|
153
|
+
console.error("AppLauncher: Failed to load config", err);
|
|
154
|
+
}).finally(() => setLoading(false));
|
|
155
|
+
}
|
|
156
|
+
}, [configUrl, propApps]);
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
function handleClickOutside(event) {
|
|
159
|
+
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
|
160
|
+
setIsOpen(false);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (isOpen) {
|
|
164
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
165
|
+
}
|
|
166
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
167
|
+
}, [isOpen]);
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
function handleEscape(event) {
|
|
170
|
+
if (event.key === "Escape") setIsOpen(false);
|
|
171
|
+
}
|
|
172
|
+
if (isOpen) {
|
|
173
|
+
document.addEventListener("keydown", handleEscape);
|
|
174
|
+
}
|
|
175
|
+
return () => document.removeEventListener("keydown", handleEscape);
|
|
176
|
+
}, [isOpen]);
|
|
177
|
+
function resolveApp(app) {
|
|
178
|
+
return {
|
|
179
|
+
id: app.id,
|
|
180
|
+
name: app.name,
|
|
181
|
+
url: app.url,
|
|
182
|
+
icon: getIcon(app.icon),
|
|
183
|
+
color: app.color,
|
|
184
|
+
description: app.description
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function handleAppClick(app) {
|
|
188
|
+
if (onAppClick) {
|
|
189
|
+
onAppClick({
|
|
190
|
+
id: app.id,
|
|
191
|
+
name: app.name,
|
|
192
|
+
url: app.url,
|
|
193
|
+
icon: app.icon.name || "FaRocket",
|
|
194
|
+
color: app.color,
|
|
195
|
+
description: app.description
|
|
196
|
+
});
|
|
197
|
+
} else {
|
|
198
|
+
window.location.href = app.url;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return /* @__PURE__ */ jsxs("div", { className: `app-launcher ${className || ""}`, ref: containerRef, children: [
|
|
202
|
+
/* @__PURE__ */ jsx(
|
|
203
|
+
"button",
|
|
204
|
+
{
|
|
205
|
+
className: "app-launcher__trigger",
|
|
206
|
+
onClick: () => setIsOpen(!isOpen),
|
|
207
|
+
"aria-label": "Open app launcher",
|
|
208
|
+
"aria-expanded": isOpen,
|
|
209
|
+
children: /* @__PURE__ */ jsx(IoApps2, { className: "app-launcher__trigger-icon" })
|
|
210
|
+
}
|
|
211
|
+
),
|
|
212
|
+
isOpen && /* @__PURE__ */ jsxs("div", { className: "app-launcher__dropdown", children: [
|
|
213
|
+
loading && /* @__PURE__ */ jsx("div", { className: "app-launcher__loading", children: "Loading..." }),
|
|
214
|
+
error && /* @__PURE__ */ jsx("div", { className: "app-launcher__error", children: error }),
|
|
215
|
+
!loading && !error && /* @__PURE__ */ jsx("div", { className: "app-launcher__grid", children: apps.map((app) => /* @__PURE__ */ jsxs(
|
|
216
|
+
"button",
|
|
217
|
+
{
|
|
218
|
+
className: "app-launcher__item",
|
|
219
|
+
onClick: () => handleAppClick(app),
|
|
220
|
+
title: app.description || app.name,
|
|
221
|
+
children: [
|
|
222
|
+
/* @__PURE__ */ jsx("div", { className: "app-launcher__icon-wrapper", children: /* @__PURE__ */ jsx(
|
|
223
|
+
app.icon,
|
|
224
|
+
{
|
|
225
|
+
className: "app-launcher__icon",
|
|
226
|
+
style: { color: app.color }
|
|
227
|
+
}
|
|
228
|
+
) }),
|
|
229
|
+
/* @__PURE__ */ jsx("span", { className: "app-launcher__name", children: app.name })
|
|
230
|
+
]
|
|
231
|
+
},
|
|
232
|
+
app.id
|
|
233
|
+
)) })
|
|
234
|
+
] })
|
|
235
|
+
] });
|
|
236
|
+
}
|
|
237
|
+
export {
|
|
238
|
+
AppLauncher,
|
|
239
|
+
AppLauncher as default,
|
|
240
|
+
getIcon,
|
|
241
|
+
iconMap
|
|
242
|
+
};
|
|
243
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +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 {\r\n AppLauncherProps,\r\n AppItem,\r\n ResolvedApp,\r\n AppLauncherConfig,\r\n} 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}: 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 (\r\n containerRef.current &&\r\n !containerRef.current.contains(event.target as Node)\r\n ) {\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 window.location.href = app.url;\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\r\n className='app-launcher__icon'\r\n style={{ color: app.color }}\r\n />\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 </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":";AAEA,SAAgB,UAAU,QAAQ,iBAAiB;AACnD,SAAS,UAAAA,eAAc;;;ACDvB;AAAA,EACE;AAAA,EAAU;AAAA,EAAY;AAAA,EAAW;AAAA,EAAe;AAAA,EAChD;AAAA,EAAQ;AAAA,EAAY;AAAA,EAAS;AAAA,EAAa;AAAA,EAAS;AAAA,EACnD;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAY;AAAA,EAAgB;AAAA,EACnD;AAAA,EAAQ;AAAA,EAAY;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAQ;AAAA,EACpD;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAa;AAAA,EAAc;AAAA,EAAS;AAAA,EACtD;AAAA,EAAW;AAAA,EAAe;AAAA,EAAa;AAAA,EAAiB;AAAA,EACxD;AAAA,EAAO;AAAA,EAAW;AAAA,EAAY;AAAA,EAAU;AAAA,OACnC;AACP,SAAS,sBAAsB;AAC/B,SAAS,iBAAiB;AAC1B,SAAS,iBAAiB,cAAc;AACxC,SAAS,mBAAmB,aAAa,aAAa,SAAS,cAAc;AAC7E,SAAS,eAAe,oBAAoB;AAC5C,SAAS,6BAA6B;AAM/B,IAAM,UAAoC;AAAA;AAAA,EAE/C;AAAA,EAAa;AAAA,EAAiB;AAAA,EAAQ;AAAA;AAAA,EAEtC;AAAA,EAAmB;AAAA,EAAQ;AAAA,EAAO;AAAA;AAAA,EAElC;AAAA,EAAY;AAAA,EAAS;AAAA;AAAA,EAErB;AAAA,EAAW;AAAA,EAAS;AAAA,EAAU;AAAA,EAAS;AAAA;AAAA,EAEvC;AAAA,EAAe;AAAA,EAAa;AAAA,EAAc;AAAA,EAAU;AAAA,EACpD;AAAA,EAAY;AAAA,EAAS;AAAA;AAAA,EAErB;AAAA,EAAgB;AAAA,EAAS;AAAA;AAAA,EAEzB;AAAA,EAAU;AAAA,EAAe;AAAA;AAAA,EAEzB;AAAA,EAAQ;AAAA,EAAY;AAAA,EAAY;AAAA;AAAA,EAEhC;AAAA,EAAY;AAAA,EAAa;AAAA;AAAA,EAEzB;AAAA,EAAgB;AAAA,EAAQ;AAAA;AAAA,EAExB;AAAA,EAAS;AAAA,EAAO;AAAA,EAAW;AAAA;AAAA,EAE3B;AAAA,EAAY;AAAA;AAAA,EAEZ;AAAA,EAAU;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAS;AAAA,EAAQ;AAC5C;AAKO,SAAS,QAAQ,MAAwB;AAC9C,SAAO,QAAQ,IAAI,KAAK;AAC1B;;;ADsEQ,cAaQ,YAbR;AAvGD,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,CAAC,MAAM,OAAO,IAAI,SAAwB,CAAC,CAAC;AAClD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AACtD,QAAM,eAAe,OAAuB,IAAI;AAGhD,YAAU,MAAM;AACd,QAAI,UAAU;AACZ,cAAQ,SAAS,IAAI,UAAU,CAAC;AAChC;AAAA,IACF;AAEA,QAAI,WAAW;AACb,iBAAW,IAAI;AACf,eAAS,IAAI;AAEb,YAAM,SAAS,EACZ,KAAK,CAAC,QAAQ;AACb,YAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,oBAAoB,IAAI,MAAM,EAAE;AAC7D,eAAO,IAAI,KAAK;AAAA,MAClB,CAAC,EACA,KAAK,CAAC,WAA8B;AACnC,gBAAQ,OAAO,KAAK,IAAI,UAAU,CAAC;AAAA,MACrC,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,iBAAS,IAAI,OAAO;AACpB,gBAAQ,MAAM,sCAAsC,GAAG;AAAA,MACzD,CAAC,EACA,QAAQ,MAAM,WAAW,KAAK,CAAC;AAAA,IACpC;AAAA,EACF,GAAG,CAAC,WAAW,QAAQ,CAAC;AAGxB,YAAU,MAAM;AACd,aAAS,mBAAmB,OAAmB;AAC7C,UACE,aAAa,WACb,CAAC,aAAa,QAAQ,SAAS,MAAM,MAAc,GACnD;AACA,kBAAU,KAAK;AAAA,MACjB;AAAA,IACF;AAEA,QAAI,QAAQ;AACV,eAAS,iBAAiB,aAAa,kBAAkB;AAAA,IAC3D;AACA,WAAO,MAAM,SAAS,oBAAoB,aAAa,kBAAkB;AAAA,EAC3E,GAAG,CAAC,MAAM,CAAC;AAGX,YAAU,MAAM;AACd,aAAS,aAAa,OAAsB;AAC1C,UAAI,MAAM,QAAQ,SAAU,WAAU,KAAK;AAAA,IAC7C;AAEA,QAAI,QAAQ;AACV,eAAS,iBAAiB,WAAW,YAAY;AAAA,IACnD;AACA,WAAO,MAAM,SAAS,oBAAoB,WAAW,YAAY;AAAA,EACnE,GAAG,CAAC,MAAM,CAAC;AAEX,WAAS,WAAW,KAA2B;AAC7C,WAAO;AAAA,MACL,IAAI,IAAI;AAAA,MACR,MAAM,IAAI;AAAA,MACV,KAAK,IAAI;AAAA,MACT,MAAM,QAAQ,IAAI,IAAI;AAAA,MACtB,OAAO,IAAI;AAAA,MACX,aAAa,IAAI;AAAA,IACnB;AAAA,EACF;AAEA,WAAS,eAAe,KAAkB;AACxC,QAAI,YAAY;AACd,iBAAW;AAAA,QACT,IAAI,IAAI;AAAA,QACR,MAAM,IAAI;AAAA,QACV,KAAK,IAAI;AAAA,QACT,MAAM,IAAI,KAAK,QAAQ;AAAA,QACvB,OAAO,IAAI;AAAA,QACX,aAAa,IAAI;AAAA,MACnB,CAAC;AAAA,IACH,OAAO;AACL,aAAO,SAAS,OAAO,IAAI;AAAA,IAC7B;AAAA,EACF;AAEA,SACE,qBAAC,SAAI,WAAW,gBAAgB,aAAa,EAAE,IAAI,KAAK,cAEtD;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,SAAS,MAAM,UAAU,CAAC,MAAM;AAAA,QAChC,cAAW;AAAA,QACX,iBAAe;AAAA,QAEf,8BAACC,SAAA,EAAO,WAAU,8BAA6B;AAAA;AAAA,IACjD;AAAA,IAGC,UACC,qBAAC,SAAI,WAAU,0BACZ;AAAA,iBAAW,oBAAC,SAAI,WAAU,yBAAwB,wBAAU;AAAA,MAE5D,SAAS,oBAAC,SAAI,WAAU,uBAAuB,iBAAM;AAAA,MAErD,CAAC,WAAW,CAAC,SACZ,oBAAC,SAAI,WAAU,sBACZ,eAAK,IAAI,CAAC,QACT;AAAA,QAAC;AAAA;AAAA,UAEC,WAAU;AAAA,UACV,SAAS,MAAM,eAAe,GAAG;AAAA,UACjC,OAAO,IAAI,eAAe,IAAI;AAAA,UAE9B;AAAA,gCAAC,SAAI,WAAU,8BACb;AAAA,cAAC,IAAI;AAAA,cAAJ;AAAA,gBACC,WAAU;AAAA,gBACV,OAAO,EAAE,OAAO,IAAI,MAAM;AAAA;AAAA,YAC5B,GACF;AAAA,YACA,oBAAC,UAAK,WAAU,sBAAsB,cAAI,MAAK;AAAA;AAAA;AAAA,QAX1C,IAAI;AAAA,MAYX,CACD,GACH;AAAA,OAEJ;AAAA,KAEJ;AAEJ;","names":["IoApps","IoApps"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@moraby/app-launcher",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A Google-style app launcher component for React/Next.js applications",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./styles.css": "./dist/index.css"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"react": ">=17.0.0",
|
|
26
|
+
"react-dom": ">=17.0.0"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"react-icons": "^5.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/react": "^18.0.0",
|
|
33
|
+
"@types/react-dom": "^18.0.0",
|
|
34
|
+
"tsup": "^8.0.0",
|
|
35
|
+
"typescript": "^5.0.0"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"react",
|
|
39
|
+
"nextjs",
|
|
40
|
+
"app-launcher",
|
|
41
|
+
"google-apps",
|
|
42
|
+
"launcher",
|
|
43
|
+
"menu",
|
|
44
|
+
"dropdown"
|
|
45
|
+
],
|
|
46
|
+
"author": "Morteza Araby",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/morteza-araby/app-launcher"
|
|
51
|
+
}
|
|
52
|
+
}
|