@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 +4 -62
- package/package.json +2 -2
- package/src/components/global-header/CategoriesButton.tsx +0 -1
- package/src/components/layout/AppSidebar.tsx +1 -1
- package/src/components/patterns/p-navigation-menu-2.tsx +69 -0
- package/src/components/patterns/p-navigation-menu-4.tsx +130 -0
- package/src/components/reui/filters.tsx +1 -8
- package/src/components/reui/index.ts +0 -1
- package/src/components/ui/field.tsx +1 -1
- package/src/components/ui/index.ts +2 -0
- package/src/components/ui/label.tsx +1 -1
- package/src/components/ui/select.tsx +1 -3
- package/src/components/ui/separator.tsx +3 -1
- package/src/components/ui/simple-input.tsx +114 -0
- package/src/components/ui/simple-select.tsx +310 -0
- package/.claude/ralph-loop.local.md +0 -9
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": "
|
|
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
|
-
"@
|
|
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
|
|
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@
|
|
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
|
+
"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.
|
|
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",
|
|
@@ -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
|
|
@@ -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 "
|
|
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,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 "
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
8
6
|
|
|
9
7
|
function Select({
|
|
10
8
|
...props
|
|
@@ -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%
|