@mostrom/app-shell 0.1.3 → 0.1.4

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/bun.lock CHANGED
@@ -6,15 +6,13 @@
6
6
  "name": "@platform/app-shell",
7
7
  "dependencies": {
8
8
  "@base-ui/react": "^1.1.0",
9
- "@cloudscape-design/components": "*",
10
- "@cloudscape-design/global-styles": "*",
11
9
  "@dnd-kit/core": "^6.3.1",
12
10
  "@dnd-kit/modifiers": "^9.0.0",
13
11
  "@dnd-kit/sortable": "^10.0.0",
14
12
  "@dnd-kit/utilities": "^3.2.2",
15
13
  "@floating-ui/dom": "^1.7.5",
16
14
  "@hookform/resolvers": "^5.2.2",
17
- "@platform/service-catalog": "file:../service-catalog",
15
+ "@platform/service-catalog": "npm:@mostrom/service-catalog@latest",
18
16
  "@radix-ui/react-dropdown-menu": "^2.1.4",
19
17
  "@tanstack/react-table": "^8.21.3",
20
18
  "class-variance-authority": "^0.7.1",
@@ -52,18 +50,6 @@
52
50
 
53
51
  "@base-ui/utils": ["@base-ui/utils@0.2.4", "", { "dependencies": { "@babel/runtime": "^7.28.4", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-smZwpMhjO29v+jrZusBSc5T+IJ3vBb9cjIiBjtKcvWmRj9Z4DWGVR3efr1eHR56/bqY5a4qyY9ElkOY5ljo3ng=="],
54
52
 
55
- "@cloudscape-design/collection-hooks": ["@cloudscape-design/collection-hooks@1.0.82", "", { "dependencies": { "@cloudscape-design/component-toolkit": "^1.0.0-beta" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-e9l8aRGlwXuZ46lK3pSc7OsGj3eKiAzkNTGhTkjxSBa749K1dY/MZ001elJPWpq+ZI5rui5j5950cNPM5sG+sQ=="],
56
-
57
- "@cloudscape-design/component-toolkit": ["@cloudscape-design/component-toolkit@1.0.0-beta.138", "", { "dependencies": { "tslib": "^2.3.1", "weekstart": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-soKxjnmFvY5XroSVqsD0FkYI2ZclF0FipdM8cfrF+eGhuOe5Vs2kcVH+OJz4ZYJddgwaGHCbTO6s0iOA16yVeA=="],
58
-
59
- "@cloudscape-design/components": ["@cloudscape-design/components@3.0.1200", "", { "dependencies": { "@cloudscape-design/collection-hooks": "^1.0.0", "@cloudscape-design/component-toolkit": "^1.0.0-beta", "@cloudscape-design/test-utils-core": "^1.0.0", "@cloudscape-design/theming-runtime": "^1.0.0", "@dnd-kit/core": "^6.0.8", "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.1", "ace-builds": "^1.34.0", "clsx": "^1.1.0", "d3-shape": "^1.3.7", "date-fns": "^2.25.0", "intl-messageformat": "^10.3.1", "mnth": "^2.0.0", "react-keyed-flatten-children": "^2.2.1", "react-transition-group": "^4.4.2", "tslib": "^2.4.0", "weekstart": "^1.1.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-V/2Ah+f2D4IbNTGu5gI+9dZTWf0V+CVpyKCVxSGlfiti/OLH5kjzeZTzFxSVVDH2ZzMm0CdZH1W8iw94wGnVvQ=="],
60
-
61
- "@cloudscape-design/global-styles": ["@cloudscape-design/global-styles@1.0.50", "", {}, "sha512-IF/z6yhFvN+jWJeXgpHqvs5Eib52R3xOIzPkX/HcoXcDIO818IIwx9cFFgnt9ZHKyduIlmQ1wtBbCdIixMISHw=="],
62
-
63
- "@cloudscape-design/test-utils-core": ["@cloudscape-design/test-utils-core@1.0.71", "", { "dependencies": { "css-selector-tokenizer": "^0.8.0", "css.escape": "^1.5.1" } }, "sha512-4rXQlsd6Rdg+KCNoltLa1+0wjgNugbduuK7L4nTa/Aw/gJIYqrTU78ip5A2VmbDhiE6fiGqtCFmU6LcKCMP//A=="],
64
-
65
- "@cloudscape-design/theming-runtime": ["@cloudscape-design/theming-runtime@1.0.91", "", { "dependencies": { "@material/material-color-utilities": "^0.3.0", "tslib": "^2.4.0" } }, "sha512-umIE/M+tzrFo9c4Skrh4AFFtaDC4eANkjp+xYikUj7ng6wTXj6kkSzxbVbg2fBEmIUgLYAzMHYbyKUzQWuz0ag=="],
66
-
67
53
  "@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
68
54
 
69
55
  "@dnd-kit/accessibility": ["@dnd-kit/accessibility@3.1.1", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, ""],
@@ -84,21 +70,9 @@
84
70
 
85
71
  "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
86
72
 
87
- "@formatjs/ecma402-abstract": ["@formatjs/ecma402-abstract@2.3.6", "", { "dependencies": { "@formatjs/fast-memoize": "2.2.7", "@formatjs/intl-localematcher": "0.6.2", "decimal.js": "^10.4.3", "tslib": "^2.8.0" } }, "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw=="],
88
-
89
- "@formatjs/fast-memoize": ["@formatjs/fast-memoize@2.2.7", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ=="],
90
-
91
- "@formatjs/icu-messageformat-parser": ["@formatjs/icu-messageformat-parser@2.11.4", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.6", "@formatjs/icu-skeleton-parser": "1.8.16", "tslib": "^2.8.0" } }, "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw=="],
92
-
93
- "@formatjs/icu-skeleton-parser": ["@formatjs/icu-skeleton-parser@1.8.16", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.6", "tslib": "^2.8.0" } }, "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ=="],
94
-
95
- "@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.6.2", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA=="],
96
-
97
73
  "@hookform/resolvers": ["@hookform/resolvers@5.2.2", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA=="],
98
74
 
99
- "@material/material-color-utilities": ["@material/material-color-utilities@0.3.0", "", {}, "sha512-ztmtTd6xwnuh2/xu+Vb01btgV8SQWYCaK56CkRK8gEkWe5TuDyBcYJ0wgkMRn+2VcE9KUmhvkz+N9GHrqw/C0g=="],
100
-
101
- "@platform/service-catalog": ["@platform/service-catalog@file:../service-catalog", {}],
75
+ "@platform/service-catalog": ["@mostrom/service-catalog@0.1.3", "", {}, "sha512-q3GMS6sz4+KTRSFrI5zOPYInCYqnNp6HdZ4yjNq8J6X8Pn2HyEih4QVfB/C4LtJ5g6Qa6P6GS9/GEzctkAnJzA=="],
102
76
 
103
77
  "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="],
104
78
 
@@ -248,8 +222,6 @@
248
222
 
249
223
  "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
250
224
 
251
- "ace-builds": ["ace-builds@1.43.6", "", {}, "sha512-L1ddibQ7F3vyXR2k2fg+I8TQTPWVA6CKeDQr/h2+8CeyTp3W6EQL8xNFZRTztuP8xNOAqL3IYPqdzs31GCjDvg=="],
252
-
253
225
  "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
254
226
 
255
227
  "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
@@ -258,12 +230,6 @@
258
230
 
259
231
  "cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="],
260
232
 
261
- "css-selector-tokenizer": ["css-selector-tokenizer@0.8.0", "", { "dependencies": { "cssesc": "^3.0.0", "fastparse": "^1.1.2" } }, "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg=="],
262
-
263
- "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="],
264
-
265
- "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
266
-
267
233
  "csstype": ["csstype@3.2.3", "", {}, ""],
268
234
 
269
235
  "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
@@ -276,11 +242,11 @@
276
242
 
277
243
  "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
278
244
 
279
- "d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="],
245
+ "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
280
246
 
281
247
  "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
282
248
 
283
- "d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="],
249
+ "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
284
250
 
285
251
  "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
286
252
 
@@ -292,8 +258,6 @@
292
258
 
293
259
  "date-fns-jalali": ["date-fns-jalali@4.1.0-0", "", {}, "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg=="],
294
260
 
295
- "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
296
-
297
261
  "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="],
298
262
 
299
263
  "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
@@ -304,14 +268,10 @@
304
268
 
305
269
  "fast-equals": ["fast-equals@5.4.0", "", {}, "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw=="],
306
270
 
307
- "fastparse": ["fastparse@1.1.2", "", {}, "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ=="],
308
-
309
271
  "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
310
272
 
311
273
  "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
312
274
 
313
- "intl-messageformat": ["intl-messageformat@10.7.18", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.6", "@formatjs/fast-memoize": "2.2.7", "@formatjs/icu-messageformat-parser": "2.11.4", "tslib": "^2.8.0" } }, "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g=="],
314
-
315
275
  "js-tokens": ["js-tokens@4.0.0", "", {}, ""],
316
276
 
317
277
  "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="],
@@ -320,8 +280,6 @@
320
280
 
321
281
  "lucide-react": ["lucide-react@0.563.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA=="],
322
282
 
323
- "mnth": ["mnth@2.0.0", "", { "dependencies": { "@babel/runtime": "^7.8.0" } }, "sha512-3ZH4UWBGpAwCKdfjynLQpUDVZWMe6vRHwarIpMdGLUp89CVR9hjzgyWERtMyqx+fPEqQ/PsAxFwvwPxLFxW40A=="],
324
-
325
283
  "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
326
284
 
327
285
  "object-assign": ["object-assign@4.1.1", "", {}, ""],
@@ -340,8 +298,6 @@
340
298
 
341
299
  "react-is": ["react-is@18.3.1", "", {}, ""],
342
300
 
343
- "react-keyed-flatten-children": ["react-keyed-flatten-children@2.2.1", "", { "dependencies": { "react-is": "^18.2.0" }, "peerDependencies": { "react": ">=15.0.0" } }, "sha512-6yBLVO6suN8c/OcJk1mzIrUHdeEzf5rtRVBhxEXAHO49D7SlJ70cG4xrSJrBIAG7MMeQ+H/T151mM2dRDNnFaA=="],
344
-
345
301
  "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="],
346
302
 
347
303
  "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
@@ -382,22 +338,8 @@
382
338
 
383
339
  "victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="],
384
340
 
385
- "weekstart": ["weekstart@1.1.0", "", {}, "sha512-ZO3I7c7J9nwGN1PZKZeBYAsuwWEsCOZi5T68cQoVNYrzrpp5Br0Bgi0OF4l8kH/Ez7nKfxa5mSsXjsgris3+qg=="],
386
-
387
341
  "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
388
342
 
389
- "@cloudscape-design/component-toolkit/weekstart": ["weekstart@2.0.0", "", {}, "sha512-HjYc14IQUwDcnGICuc8tVtqAd6EFpoAQMqgrqcNtWWZB+F1b7iTq44GzwM1qvnH4upFgbhJsaNHuK93NOFheSg=="],
390
-
391
- "@cloudscape-design/components/@dnd-kit/sortable": ["@dnd-kit/sortable@7.0.2", "", { "dependencies": { "@dnd-kit/utilities": "^3.2.0", "tslib": "^2.0.0" }, "peerDependencies": { "@dnd-kit/core": "^6.0.7", "react": ">=16.8.0" } }, "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA=="],
392
-
393
- "@cloudscape-design/components/clsx": ["clsx@1.2.1", "", {}, "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="],
394
-
395
- "@cloudscape-design/components/date-fns": ["date-fns@2.30.0", "", { "dependencies": { "@babel/runtime": "^7.21.0" } }, "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw=="],
396
-
397
343
  "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
398
-
399
- "victory-vendor/d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
400
-
401
- "victory-vendor/d3-shape/d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
402
344
  }
403
345
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostrom/app-shell",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -29,7 +29,7 @@
29
29
  "@dnd-kit/utilities": "^3.2.2",
30
30
  "@floating-ui/dom": "^1.7.5",
31
31
  "@hookform/resolvers": "^5.2.2",
32
- "@platform/service-catalog": "npm:@mostrom/service-catalog@^0.1.3",
32
+ "@platform/service-catalog": "npm:@mostrom/service-catalog@^0.1.4",
33
33
  "@radix-ui/react-dropdown-menu": "^2.1.4",
34
34
  "@tanstack/react-table": "^8.21.3",
35
35
  "class-variance-authority": "^0.7.1",
@@ -1,6 +1,5 @@
1
1
  "use client";
2
2
 
3
- import React from "react";
4
3
  import { Button } from "../ui/button";
5
4
  import {
6
5
  DropdownMenu,
@@ -225,4 +225,4 @@ export function AppSidebar({
225
225
  );
226
226
  }
227
227
 
228
- export { useSidebar, SidebarProvider, SidebarTrigger, SidebarInset } from "./ui/sidebar";
228
+ export { useSidebar, SidebarProvider, SidebarTrigger, SidebarInset } from "../ui/sidebar";
@@ -0,0 +1,69 @@
1
+ import {
2
+ NavigationMenu,
3
+ NavigationMenuContent,
4
+ NavigationMenuItem,
5
+ NavigationMenuLink,
6
+ NavigationMenuList,
7
+ NavigationMenuTrigger,
8
+ } from "@/components/ui/navigation-menu"
9
+
10
+ const components = [
11
+ {
12
+ title: "Alert Dialog",
13
+ href: "#",
14
+ description:
15
+ "A modal dialog that interrupts the user with important content.",
16
+ },
17
+ {
18
+ title: "Hover Card",
19
+ href: "#",
20
+ description:
21
+ "For sighted users to preview content available behind a link.",
22
+ },
23
+ {
24
+ title: "Progress",
25
+ href: "#",
26
+ description:
27
+ "Displays an indicator showing the completion progress of a task.",
28
+ },
29
+ {
30
+ title: "Scroll-area",
31
+ href: "#",
32
+ description: "Visually or semantically separates content.",
33
+ },
34
+ ]
35
+
36
+ export function Pattern() {
37
+ return (
38
+ <div className="flex items-center justify-center">
39
+ <NavigationMenu>
40
+ <NavigationMenuList>
41
+ <NavigationMenuItem>
42
+ <NavigationMenuTrigger>Components</NavigationMenuTrigger>
43
+ <NavigationMenuContent>
44
+ <ul className="grid w-[400px] gap-2 md:w-[500px] md:grid-cols-2">
45
+ {components.map((component) => (
46
+ <li key={component.title}>
47
+ <NavigationMenuLink
48
+ asChild
49
+ className="flex flex-col items-start gap-2 p-3"
50
+ >
51
+ <a href={component.href}>
52
+ <div className="text-sm leading-none font-medium">
53
+ {component.title}
54
+ </div>
55
+ <p className="text-muted-foreground line-clamp-2 text-sm leading-snug">
56
+ {component.description}
57
+ </p>
58
+ </a>
59
+ </NavigationMenuLink>
60
+ </li>
61
+ ))}
62
+ </ul>
63
+ </NavigationMenuContent>
64
+ </NavigationMenuItem>
65
+ </NavigationMenuList>
66
+ </NavigationMenu>
67
+ </div>
68
+ )
69
+ }
@@ -0,0 +1,130 @@
1
+ import { Button } from "@/components/ui/button"
2
+ import {
3
+ NavigationMenu,
4
+ NavigationMenuContent,
5
+ NavigationMenuItem,
6
+ NavigationMenuLink,
7
+ NavigationMenuList,
8
+ NavigationMenuTrigger,
9
+ } from "@/components/ui/navigation-menu"
10
+ import { UserIcon, BuildingIcon, CircleDollarSignIcon, LayoutGridIcon, SparklesIcon, RadioIcon } from "lucide-react"
11
+
12
+ const industries = [
13
+ {
14
+ title: "Individuals",
15
+ description: "Keep your finances organized.",
16
+ href: "#",
17
+ icon: (
18
+ <UserIcon
19
+ />
20
+ ),
21
+ },
22
+ {
23
+ title: "LLCs",
24
+ description: "Benefit from tax write-offs.",
25
+ href: "#",
26
+ icon: (
27
+ <BuildingIcon
28
+ />
29
+ ),
30
+ },
31
+ {
32
+ title: "Freelancers",
33
+ description: "For independent workers.",
34
+ href: "#",
35
+ icon: (
36
+ <CircleDollarSignIcon
37
+ />
38
+ ),
39
+ },
40
+ {
41
+ title: "Investors",
42
+ description: "Make and grow your money.",
43
+ href: "#",
44
+ icon: (
45
+ <LayoutGridIcon
46
+ />
47
+ ),
48
+ },
49
+ {
50
+ title: "Small businesses",
51
+ description: "We take care of your taxes.",
52
+ href: "#",
53
+ icon: (
54
+ <SparklesIcon
55
+ />
56
+ ),
57
+ },
58
+ {
59
+ title: "Crypto",
60
+ description: "For tech enthusiasts.",
61
+ href: "#",
62
+ icon: (
63
+ <CircleDollarSignIcon
64
+ />
65
+ ),
66
+ },
67
+ {
68
+ title: "Big companies",
69
+ description: "Run your finances easily.",
70
+ href: "#",
71
+ icon: (
72
+ <BuildingIcon
73
+ />
74
+ ),
75
+ },
76
+ {
77
+ title: "Investments",
78
+ description: "Launch your ideas worldwide.",
79
+ href: "#",
80
+ icon: (
81
+ <RadioIcon
82
+ />
83
+ ),
84
+ },
85
+ ]
86
+
87
+ export function Pattern() {
88
+ return (
89
+ <div className="flex items-center justify-center">
90
+ <NavigationMenu>
91
+ <NavigationMenuList>
92
+ <NavigationMenuItem>
93
+ <NavigationMenuTrigger>Industries</NavigationMenuTrigger>
94
+ <NavigationMenuContent>
95
+ <div className="w-[500px]">
96
+ <ul className="grid grid-cols-2 gap-1">
97
+ {industries.map((item) => (
98
+ <li key={item.title}>
99
+ <NavigationMenuLink
100
+ asChild
101
+ className="flex items-start gap-2 p-3"
102
+ >
103
+ <a href="#">
104
+ {item.icon}
105
+ <div className="flex flex-col gap-0.5">
106
+ <div className="text-sm leading-none font-medium">
107
+ {item.title}
108
+ </div>
109
+ <p className="text-muted-foreground text-xs leading-snug">
110
+ {item.description}
111
+ </p>
112
+ </div>
113
+ </a>
114
+ </NavigationMenuLink>
115
+ </li>
116
+ ))}
117
+ </ul>
118
+ <div className="mt-2 px-1 pb-1">
119
+ <Button className="w-full" asChild>
120
+ <a href="#">Learn more</a>
121
+ </Button>
122
+ </div>
123
+ </div>
124
+ </NavigationMenuContent>
125
+ </NavigationMenuItem>
126
+ </NavigationMenuList>
127
+ </NavigationMenu>
128
+ </div>
129
+ )
130
+ }
@@ -251,10 +251,10 @@ function FilterInput<T = unknown>({
251
251
  className?: string
252
252
  field?: FilterFieldConfig<T>
253
253
  }) {
254
- const context = useFilterContext()
255
254
  const [isValid, setIsValid] = useState(true)
256
255
  const [validationMessage, setValidationMessage] = useState("")
257
256
  const inputRef = useRef<HTMLInputElement>(null)
257
+ const context = useFilterContext()
258
258
 
259
259
  useEffect(() => {
260
260
  if (props.autoFocus) {
@@ -396,12 +396,6 @@ function FilterRemoveButton({
396
396
  }: FilterRemoveButtonProps) {
397
397
  const context = useFilterContext()
398
398
 
399
- const sizeMap = {
400
- sm: "sm" as const,
401
- default: "sm" as const,
402
- lg: "default" as const,
403
- }
404
-
405
399
  return (
406
400
  <Button
407
401
  variant="outline"
@@ -984,7 +978,6 @@ function FilterValueSelector<T = unknown>({
984
978
  operator,
985
979
  autoFocus,
986
980
  }: FilterValueSelectorProps<T>) {
987
- const context = useFilterContext()
988
981
 
989
982
  if (operator === "empty" || operator === "not_empty") {
990
983
  return null
@@ -1,7 +1,6 @@
1
1
  // ReUI Components
2
2
  export * from "./alert";
3
3
  export * from "./autocomplete";
4
- export * from "./badge";
5
4
  export * from "./date-selector";
6
5
  export * from "./filters";
7
6
  export * from "./frame";
@@ -3,7 +3,7 @@
3
3
  import { useMemo } from "react"
4
4
  import { cva, type VariantProps } from "class-variance-authority"
5
5
 
6
- import { cn } from "../../lib/utils"
6
+ import { cn } from "@/lib/utils"
7
7
  import { Label } from "@/components/ui/label"
8
8
  import { Separator } from "@/components/ui/separator"
9
9
 
@@ -23,6 +23,8 @@ export * from "./navigation-menu";
23
23
  export * from "./popover";
24
24
  export * from "./scroll-area";
25
25
  export * from "./select";
26
+ export * from "./simple-select";
27
+ export * from "./simple-input";
26
28
  export * from "./separator";
27
29
  export * from "./sheet";
28
30
  export * from "./space-between";
@@ -1,7 +1,7 @@
1
1
  import * as React from "react"
2
2
  import { Label as LabelPrimitive } from "radix-ui"
3
3
 
4
- import { cn } from "../../lib/utils"
4
+ import { cn } from "@/lib/utils"
5
5
 
6
6
  function Label({
7
7
  className,
@@ -1,10 +1,8 @@
1
- "use client"
2
-
3
1
  import * as React from "react"
4
2
  import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
5
3
  import { Select as SelectPrimitive } from "radix-ui"
6
4
 
7
- import { cn } from "../../lib/utils"
5
+ import { cn } from "@/lib/utils"
8
6
 
9
7
  function Select({
10
8
  ...props
@@ -1,7 +1,9 @@
1
+ "use client"
2
+
1
3
  import * as React from "react"
2
4
  import { Separator as SeparatorPrimitive } from "radix-ui"
3
5
 
4
- import { cn } from "../../lib/utils"
6
+ import { cn } from "@/lib/utils"
5
7
 
6
8
  function Separator({
7
9
  className,
@@ -0,0 +1,114 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../lib/utils"
5
+
6
+ /** Props for SimpleInput component - Cloudscape-compatible API */
7
+ export interface SimpleInputProps {
8
+ /** Current value of the input */
9
+ value: string
10
+ /** Callback when value changes - matches Cloudscape pattern */
11
+ onChange: (event: { detail: { value: string } }) => void
12
+ /** Input type */
13
+ type?: "text" | "number" | "search" | "email" | "url" | "password" | "tel"
14
+ /** Input mode for mobile keyboards */
15
+ inputMode?: "text" | "numeric" | "decimal" | "email" | "tel" | "url" | "search" | "none"
16
+ /** Placeholder text */
17
+ placeholder?: string
18
+ /** Whether the input is disabled */
19
+ disabled?: boolean
20
+ /** Whether the input is read-only */
21
+ readOnly?: boolean
22
+ /** Whether the input should auto-focus */
23
+ autoFocus?: boolean
24
+ /** Aria label for accessibility */
25
+ ariaLabel?: string
26
+ /** Additional className */
27
+ className?: string
28
+ /** Name attribute */
29
+ name?: string
30
+ /** Auto-complete attribute */
31
+ autoComplete?: string
32
+ /** Whether the input is invalid */
33
+ invalid?: boolean
34
+ /** Step for number inputs */
35
+ step?: number | string
36
+ /** Min value for number inputs */
37
+ min?: number | string
38
+ /** Max value for number inputs */
39
+ max?: number | string
40
+ }
41
+
42
+ /**
43
+ * SimpleInput - An input component with Cloudscape-compatible API.
44
+ *
45
+ * Provides a declarative API similar to Cloudscape Input while using
46
+ * the app-shell styling.
47
+ *
48
+ * @example
49
+ * ```tsx
50
+ * <SimpleInput
51
+ * value={name}
52
+ * onChange={({ detail }) => setName(detail.value)}
53
+ * placeholder="Enter your name"
54
+ * />
55
+ * ```
56
+ *
57
+ * @example Number input
58
+ * ```tsx
59
+ * <SimpleInput
60
+ * type="number"
61
+ * value={String(amount)}
62
+ * onChange={({ detail }) => setAmount(parseInt(detail.value, 10) || 0)}
63
+ * placeholder="0"
64
+ * />
65
+ * ```
66
+ */
67
+ export function SimpleInput({
68
+ value,
69
+ onChange,
70
+ type = "text",
71
+ inputMode,
72
+ placeholder,
73
+ disabled = false,
74
+ readOnly = false,
75
+ autoFocus = false,
76
+ ariaLabel,
77
+ className,
78
+ name,
79
+ autoComplete,
80
+ invalid = false,
81
+ step,
82
+ min,
83
+ max,
84
+ }: SimpleInputProps) {
85
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
86
+ onChange({ detail: { value: event.target.value } })
87
+ }
88
+
89
+ return (
90
+ <input
91
+ type={type}
92
+ value={value}
93
+ onChange={handleChange}
94
+ placeholder={placeholder}
95
+ disabled={disabled}
96
+ readOnly={readOnly}
97
+ autoFocus={autoFocus}
98
+ aria-label={ariaLabel}
99
+ aria-invalid={invalid || undefined}
100
+ name={name}
101
+ autoComplete={autoComplete}
102
+ inputMode={inputMode}
103
+ step={step}
104
+ min={min}
105
+ max={max}
106
+ className={cn(
107
+ "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
108
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
109
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
110
+ className
111
+ )}
112
+ />
113
+ )
114
+ }
@@ -0,0 +1,310 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { CheckIcon, ChevronDownIcon, SearchIcon } from "lucide-react"
5
+ import { cn } from "../../lib/utils"
6
+
7
+ /** Option type for SimpleSelect */
8
+ export interface SelectOption {
9
+ /** Display label for the option */
10
+ label: string
11
+ /** Value of the option */
12
+ value: string
13
+ /** Optional description shown below the label */
14
+ description?: string
15
+ /** Whether the option is disabled */
16
+ disabled?: boolean
17
+ }
18
+
19
+ /** Grouped options type for SimpleSelect */
20
+ export interface SelectOptionGroup {
21
+ /** Group label */
22
+ label: string
23
+ /** Options in this group */
24
+ options: SelectOption[]
25
+ }
26
+
27
+ /** Props for SimpleSelect component */
28
+ export interface SimpleSelectProps {
29
+ /** Currently selected option */
30
+ selectedOption: SelectOption | null
31
+ /** Callback when selection changes - matches Cloudscape pattern */
32
+ onChange: (event: { detail: { selectedOption: SelectOption } }) => void
33
+ /** Options to display - can be flat array or grouped */
34
+ options: (SelectOption | SelectOptionGroup)[]
35
+ /** Placeholder text when no option is selected */
36
+ placeholder?: string
37
+ /** Whether the select is disabled */
38
+ disabled?: boolean
39
+ /** Enable filtering/search - "auto" enables search */
40
+ filteringType?: "auto" | "none"
41
+ /** Additional className for the trigger */
42
+ className?: string
43
+ /** Trigger size */
44
+ size?: "sm" | "default"
45
+ }
46
+
47
+ /** Type guard to check if an option is a group */
48
+ function isOptionGroup(option: SelectOption | SelectOptionGroup): option is SelectOptionGroup {
49
+ return "options" in option && Array.isArray(option.options)
50
+ }
51
+
52
+ /** Flatten grouped options into a flat list */
53
+ function flattenOptions(options: (SelectOption | SelectOptionGroup)[]): SelectOption[] {
54
+ return options.flatMap((opt) => (isOptionGroup(opt) ? opt.options : [opt]))
55
+ }
56
+
57
+ /**
58
+ * SimpleSelect - A select component with Cloudscape-compatible API.
59
+ *
60
+ * Provides a declarative API similar to Cloudscape Select while using
61
+ * custom dropdown implementation for flexibility.
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * <SimpleSelect
66
+ * selectedOption={selectedOption}
67
+ * onChange={({ detail }) => setSelectedOption(detail.selectedOption)}
68
+ * options={[
69
+ * { label: "Option 1", value: "1" },
70
+ * { label: "Option 2", value: "2" },
71
+ * ]}
72
+ * placeholder="Select an option"
73
+ * />
74
+ * ```
75
+ *
76
+ * @example With grouped options
77
+ * ```tsx
78
+ * <SimpleSelect
79
+ * selectedOption={selectedTimezone}
80
+ * onChange={({ detail }) => setSelectedTimezone(detail.selectedOption)}
81
+ * options={[
82
+ * { label: "Americas", options: [
83
+ * { label: "New York", value: "America/New_York" },
84
+ * { label: "Los Angeles", value: "America/Los_Angeles" },
85
+ * ]},
86
+ * { label: "Europe", options: [
87
+ * { label: "London", value: "Europe/London" },
88
+ * { label: "Paris", value: "Europe/Paris" },
89
+ * ]},
90
+ * ]}
91
+ * filteringType="auto"
92
+ * />
93
+ * ```
94
+ */
95
+ export function SimpleSelect({
96
+ selectedOption,
97
+ onChange,
98
+ options,
99
+ placeholder = "Select...",
100
+ disabled = false,
101
+ filteringType = "none",
102
+ className,
103
+ size = "default",
104
+ }: SimpleSelectProps) {
105
+ const [isOpen, setIsOpen] = React.useState(false)
106
+ const [filterText, setFilterText] = React.useState("")
107
+ const containerRef = React.useRef<HTMLDivElement>(null)
108
+ const inputRef = React.useRef<HTMLInputElement>(null)
109
+
110
+ const isSearchable = filteringType === "auto"
111
+
112
+ // Close dropdown when clicking outside
113
+ React.useEffect(() => {
114
+ function handleClickOutside(event: MouseEvent) {
115
+ if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
116
+ setIsOpen(false)
117
+ setFilterText("")
118
+ }
119
+ }
120
+
121
+ if (isOpen) {
122
+ document.addEventListener("mousedown", handleClickOutside)
123
+ return () => document.removeEventListener("mousedown", handleClickOutside)
124
+ }
125
+ }, [isOpen])
126
+
127
+ // Focus input when dropdown opens (for searchable selects)
128
+ React.useEffect(() => {
129
+ if (isOpen && isSearchable && inputRef.current) {
130
+ inputRef.current.focus()
131
+ }
132
+ }, [isOpen, isSearchable])
133
+
134
+ // Handle keyboard navigation
135
+ const handleKeyDown = (event: React.KeyboardEvent) => {
136
+ if (disabled) return
137
+
138
+ switch (event.key) {
139
+ case "Enter":
140
+ case " ":
141
+ if (!isOpen) {
142
+ event.preventDefault()
143
+ setIsOpen(true)
144
+ }
145
+ break
146
+ case "Escape":
147
+ setIsOpen(false)
148
+ setFilterText("")
149
+ break
150
+ case "ArrowDown":
151
+ if (!isOpen) {
152
+ event.preventDefault()
153
+ setIsOpen(true)
154
+ }
155
+ break
156
+ }
157
+ }
158
+
159
+ const handleSelect = (option: SelectOption) => {
160
+ onChange({ detail: { selectedOption: option } })
161
+ setIsOpen(false)
162
+ setFilterText("")
163
+ }
164
+
165
+ // Filter options based on search text
166
+ const getFilteredOptions = (): (SelectOption | SelectOptionGroup)[] => {
167
+ if (!filterText.trim()) return options
168
+
169
+ const searchLower = filterText.toLowerCase()
170
+
171
+ return options
172
+ .map((opt) => {
173
+ if (isOptionGroup(opt)) {
174
+ const filteredGroupOptions = opt.options.filter(
175
+ (o) =>
176
+ o.label.toLowerCase().includes(searchLower) ||
177
+ o.value.toLowerCase().includes(searchLower) ||
178
+ o.description?.toLowerCase().includes(searchLower)
179
+ )
180
+ if (filteredGroupOptions.length === 0) return null
181
+ return { ...opt, options: filteredGroupOptions }
182
+ }
183
+ const matches =
184
+ opt.label.toLowerCase().includes(searchLower) ||
185
+ opt.value.toLowerCase().includes(searchLower) ||
186
+ opt.description?.toLowerCase().includes(searchLower)
187
+ return matches ? opt : null
188
+ })
189
+ .filter(Boolean) as (SelectOption | SelectOptionGroup)[]
190
+ }
191
+
192
+ const filteredOptions = getFilteredOptions()
193
+ const allOptions = flattenOptions(options)
194
+
195
+ const renderOption = (option: SelectOption, index: number) => {
196
+ const isSelected = selectedOption?.value === option.value
197
+ return (
198
+ <div
199
+ key={`${option.value}-${index}`}
200
+ role="option"
201
+ aria-selected={isSelected}
202
+ className={cn(
203
+ "relative flex w-full cursor-pointer items-center rounded-sm py-1.5 pr-8 pl-2 text-sm outline-none select-none",
204
+ "hover:bg-accent hover:text-accent-foreground",
205
+ isSelected && "bg-accent/50",
206
+ option.disabled && "pointer-events-none opacity-50"
207
+ )}
208
+ onClick={() => !option.disabled && handleSelect(option)}
209
+ >
210
+ <span className="absolute right-2 flex size-3.5 items-center justify-center">
211
+ {isSelected && <CheckIcon className="size-4" />}
212
+ </span>
213
+ <div className="flex flex-col gap-0.5">
214
+ <span>{option.label}</span>
215
+ {option.description && (
216
+ <span className="text-muted-foreground text-xs">{option.description}</span>
217
+ )}
218
+ </div>
219
+ </div>
220
+ )
221
+ }
222
+
223
+ const renderOptions = () => {
224
+ if (filteredOptions.length === 0) {
225
+ return (
226
+ <div className="text-muted-foreground py-2 text-center text-sm">
227
+ {allOptions.length === 0 ? "No options available" : "No options found"}
228
+ </div>
229
+ )
230
+ }
231
+
232
+ return filteredOptions.map((opt, index) => {
233
+ if (isOptionGroup(opt)) {
234
+ return (
235
+ <div key={`group-${opt.label}-${index}`}>
236
+ <div className="text-muted-foreground px-2 py-1.5 text-xs font-medium">
237
+ {opt.label}
238
+ </div>
239
+ {opt.options.map((groupOpt, gIndex) => renderOption(groupOpt, gIndex))}
240
+ </div>
241
+ )
242
+ }
243
+ return renderOption(opt, index)
244
+ })
245
+ }
246
+
247
+ return (
248
+ <div ref={containerRef} className="relative w-full">
249
+ {/* Trigger Button */}
250
+ <button
251
+ type="button"
252
+ role="combobox"
253
+ aria-expanded={isOpen}
254
+ aria-haspopup="listbox"
255
+ disabled={disabled}
256
+ onClick={() => !disabled && setIsOpen(!isOpen)}
257
+ onKeyDown={handleKeyDown}
258
+ className={cn(
259
+ "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground",
260
+ "focus-visible:border-ring focus-visible:ring-ring/50",
261
+ "flex w-full items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm",
262
+ "whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px]",
263
+ "disabled:cursor-not-allowed disabled:opacity-50",
264
+ size === "default" ? "h-9" : "h-8",
265
+ className
266
+ )}
267
+ >
268
+ <span className={cn("truncate", !selectedOption && "text-muted-foreground")}>
269
+ {selectedOption?.label ?? placeholder}
270
+ </span>
271
+ <ChevronDownIcon className="size-4 shrink-0 opacity-50" />
272
+ </button>
273
+
274
+ {/* Dropdown */}
275
+ {isOpen && (
276
+ <div
277
+ role="listbox"
278
+ className={cn(
279
+ "bg-popover text-popover-foreground absolute z-50 mt-1 w-full min-w-[8rem] overflow-hidden rounded-md border shadow-md",
280
+ "animate-in fade-in-0 zoom-in-95"
281
+ )}
282
+ >
283
+ {/* Search Input */}
284
+ {isSearchable && (
285
+ <div className="border-b p-2">
286
+ <div className="relative">
287
+ <SearchIcon className="text-muted-foreground absolute left-2 top-1/2 size-4 -translate-y-1/2" />
288
+ <input
289
+ ref={inputRef}
290
+ type="text"
291
+ value={filterText}
292
+ onChange={(e) => setFilterText(e.target.value)}
293
+ placeholder="Search..."
294
+ className={cn(
295
+ "border-input placeholder:text-muted-foreground focus-visible:ring-ring/50",
296
+ "h-8 w-full rounded-md border bg-transparent py-1 pr-3 pl-8 text-sm outline-none",
297
+ "focus-visible:border-ring focus-visible:ring-[3px]"
298
+ )}
299
+ />
300
+ </div>
301
+ </div>
302
+ )}
303
+
304
+ {/* Options List */}
305
+ <div className="max-h-[300px] overflow-y-auto p-1">{renderOptions()}</div>
306
+ </div>
307
+ )}
308
+ </div>
309
+ )
310
+ }
@@ -1,9 +0,0 @@
1
- ---
2
- active: true
3
- iteration: 1
4
- max_iterations: 0
5
- completion_promise: null
6
- started_at: "2026-02-13T12:51:42Z"
7
- ---
8
-
9
- Without diverging from the drawer requirements in @docs/prompts/todo/drawer-fields.md ensure the drawer layout wise and style wise mimics this exact image docs/prompts/todo/Screenshot 2026-02-12 at 18.19.47.png and use playwright to take screenshots and do a comparison until the layouts match 100%