create-appraisejs 0.1.10-alpha.8 → 0.2.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (414) hide show
  1. package/README.md +52 -52
  2. package/dist/cli.e2e.test.d.ts +2 -0
  3. package/dist/cli.e2e.test.d.ts.map +1 -0
  4. package/dist/cli.e2e.test.js +73 -0
  5. package/dist/cli.e2e.test.js.map +1 -0
  6. package/dist/config.test.d.ts +2 -0
  7. package/dist/config.test.d.ts.map +1 -0
  8. package/dist/config.test.js +65 -0
  9. package/dist/config.test.js.map +1 -0
  10. package/dist/copy-template.test.d.ts +2 -0
  11. package/dist/copy-template.test.d.ts.map +1 -0
  12. package/dist/copy-template.test.js +71 -0
  13. package/dist/copy-template.test.js.map +1 -0
  14. package/dist/download-repo.test.d.ts +2 -0
  15. package/dist/download-repo.test.d.ts.map +1 -0
  16. package/dist/download-repo.test.js +14 -0
  17. package/dist/download-repo.test.js.map +1 -0
  18. package/dist/install.test.d.ts +2 -0
  19. package/dist/install.test.d.ts.map +1 -0
  20. package/dist/install.test.js +119 -0
  21. package/dist/install.test.js.map +1 -0
  22. package/dist/prepare-template-utils.d.ts.map +1 -1
  23. package/dist/prepare-template-utils.js +5 -0
  24. package/dist/prepare-template-utils.js.map +1 -1
  25. package/dist/prompts.test.d.ts +2 -0
  26. package/dist/prompts.test.d.ts.map +1 -0
  27. package/dist/prompts.test.js +58 -0
  28. package/dist/prompts.test.js.map +1 -0
  29. package/dist/scaffold-gitignore.d.ts +1 -0
  30. package/dist/scaffold-gitignore.d.ts.map +1 -1
  31. package/dist/scaffold-gitignore.js +3 -0
  32. package/dist/scaffold-gitignore.js.map +1 -1
  33. package/package.json +3 -2
  34. package/templates/default/.appraise-template-meta.json +2 -2
  35. package/templates/default/.env.example +2 -2
  36. package/templates/default/.vscode/settings.json +2 -1
  37. package/templates/default/README.md +53 -53
  38. package/templates/default/automation/mapping/locator-map.json +1 -14
  39. package/templates/default/automation/steps/actions/click.step.ts +58 -58
  40. package/templates/default/automation/steps/actions/hover.step.ts +27 -27
  41. package/templates/default/automation/steps/actions/input.step.ts +84 -84
  42. package/templates/default/automation/steps/actions/navigation.step.ts +70 -70
  43. package/templates/default/automation/steps/actions/random_data.step.ts +142 -142
  44. package/templates/default/automation/steps/actions/store.step.ts +86 -86
  45. package/templates/default/automation/steps/actions/wait.step.ts +90 -90
  46. package/templates/default/automation/steps/validations/active_state_assertion.step.ts +30 -30
  47. package/templates/default/automation/steps/validations/navigation_assertion.step.ts +22 -22
  48. package/templates/default/automation/steps/validations/text_assertion.step.ts +107 -107
  49. package/templates/default/automation/steps/validations/visibility_assertion.step.ts +26 -26
  50. package/templates/default/components.json +24 -24
  51. package/templates/default/cucumber.mjs +16 -16
  52. package/templates/default/eslint.config.mjs +16 -16
  53. package/templates/default/gitignore +3 -0
  54. package/templates/default/next-env.d.ts +6 -6
  55. package/templates/default/next.config.ts +11 -11
  56. package/templates/default/package-lock.json +146 -75
  57. package/templates/default/package.json +2 -1
  58. package/templates/default/packages/cucumber-runtime/package.json +13 -13
  59. package/templates/default/packages/cucumber-runtime/src/cache.util.ts +93 -93
  60. package/templates/default/packages/cucumber-runtime/src/cli.ts +68 -68
  61. package/templates/default/packages/cucumber-runtime/src/environment.util.ts +21 -21
  62. package/templates/default/packages/cucumber-runtime/src/executor.ts +32 -32
  63. package/templates/default/packages/cucumber-runtime/src/hooks.ts +25 -3
  64. package/templates/default/packages/cucumber-runtime/src/index.ts +17 -17
  65. package/templates/default/packages/cucumber-runtime/src/locator.util.ts +234 -234
  66. package/templates/default/packages/cucumber-runtime/src/parameter-types.ts +7 -7
  67. package/templates/default/packages/cucumber-runtime/src/paths.ts +66 -1
  68. package/templates/default/packages/cucumber-runtime/src/random-data.util.ts +35 -35
  69. package/templates/default/packages/cucumber-runtime/src/types.ts +13 -13
  70. package/templates/default/packages/cucumber-runtime/src/world.ts +44 -44
  71. package/templates/default/packages/cucumber-runtime/tsconfig.json +11 -11
  72. package/templates/default/packages/locator-picker-companion/dist/cli.d.ts +1 -0
  73. package/templates/default/packages/locator-picker-companion/dist/cli.js +302 -0
  74. package/templates/default/packages/locator-picker-companion/dist/index.d.ts +3 -0
  75. package/templates/default/packages/locator-picker-companion/dist/index.js +3 -0
  76. package/templates/default/packages/locator-picker-companion/dist/injected-picker-script.d.ts +1 -0
  77. package/templates/default/packages/locator-picker-companion/dist/injected-picker-script.js +615 -0
  78. package/templates/default/packages/locator-picker-companion/dist/launcher.d.ts +11 -0
  79. package/templates/default/packages/locator-picker-companion/dist/launcher.js +59 -0
  80. package/templates/default/packages/locator-picker-companion/dist/selector-generator.d.ts +3 -0
  81. package/templates/default/packages/locator-picker-companion/dist/selector-generator.js +257 -0
  82. package/templates/default/packages/locator-picker-companion/dist/session-file.d.ts +22 -0
  83. package/templates/default/packages/locator-picker-companion/dist/session-file.js +150 -0
  84. package/templates/default/packages/locator-picker-companion/dist/types.d.ts +31 -0
  85. package/templates/default/packages/locator-picker-companion/dist/types.js +1 -0
  86. package/templates/default/packages/locator-picker-companion/package.json +17 -0
  87. package/templates/default/packages/locator-picker-companion/src/cli.ts +428 -0
  88. package/templates/default/packages/locator-picker-companion/src/index.ts +3 -0
  89. package/templates/default/packages/locator-picker-companion/src/injected-picker-script.ts +771 -0
  90. package/templates/default/packages/locator-picker-companion/src/launcher.ts +77 -0
  91. package/templates/default/packages/locator-picker-companion/src/selector-generator.ts +339 -0
  92. package/templates/default/packages/locator-picker-companion/src/session-file.ts +226 -0
  93. package/templates/default/packages/locator-picker-companion/src/types.ts +42 -0
  94. package/templates/default/packages/locator-picker-companion/tsconfig.json +14 -0
  95. package/templates/default/postcss.config.mjs +8 -8
  96. package/templates/default/prisma/dev.db +0 -0
  97. package/templates/default/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -16
  98. package/templates/default/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -27
  99. package/templates/default/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -17
  100. package/templates/default/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -12
  101. package/templates/default/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -28
  102. package/templates/default/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -12
  103. package/templates/default/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -10
  104. package/templates/default/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -10
  105. package/templates/default/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -108
  106. package/templates/default/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -55
  107. package/templates/default/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -27
  108. package/templates/default/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -59
  109. package/templates/default/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -34
  110. package/templates/default/prisma/migrations/20260313093000_add_report_step_screenshot_path/migration.sql +1 -0
  111. package/templates/default/prisma/migrations/20260318120000_add_test_suite_context_to_test_run_test_case/migration.sql +20 -0
  112. package/templates/default/prisma/migrations/20260318173512_add_support_of_test_suite_level_runs/migration.sql +2 -0
  113. package/templates/default/prisma/schema.prisma +4 -0
  114. package/templates/default/scripts/install-playwright.ts +20 -0
  115. package/templates/default/scripts/protect-seeded-files.ts +38 -38
  116. package/templates/default/scripts/regenerate-features.ts +94 -94
  117. package/templates/default/scripts/setup-env.ts +19 -19
  118. package/templates/default/scripts/sync-all.ts +22 -66
  119. package/templates/default/scripts/sync-appraise-base-template.ts +57 -19
  120. package/templates/default/scripts/sync-environments.ts +40 -21
  121. package/templates/default/scripts/sync-locator-groups.ts +410 -410
  122. package/templates/default/scripts/sync-locators.ts +398 -398
  123. package/templates/default/scripts/sync-modules.ts +341 -341
  124. package/templates/default/scripts/sync-tags.ts +2 -3
  125. package/templates/default/scripts/sync-template-step-groups.ts +0 -3
  126. package/templates/default/scripts/sync-template-steps.ts +37 -11
  127. package/templates/default/scripts/sync-test-cases.ts +68 -16
  128. package/templates/default/scripts/sync-test-suites.ts +7 -5
  129. package/templates/default/src/actions/conflict/conflict.action.ts +33 -33
  130. package/templates/default/src/actions/dashboard/dashboard-actions.ts +240 -240
  131. package/templates/default/src/actions/environments/environment-actions.ts +188 -188
  132. package/templates/default/src/actions/locator/locator-actions.ts +490 -490
  133. package/templates/default/src/actions/locator-groups/locator-group-actions.ts +6 -3
  134. package/templates/default/src/actions/locator-picker/locator-picker-actions.ts +243 -0
  135. package/templates/default/src/actions/modules/module-actions.ts +135 -135
  136. package/templates/default/src/actions/reports/report-actions.ts +51 -49
  137. package/templates/default/src/actions/review/review-actions.ts +147 -147
  138. package/templates/default/src/actions/settings/sync-actions.ts +46 -0
  139. package/templates/default/src/actions/tags/tag-actions.ts +107 -107
  140. package/templates/default/src/actions/template-step/template-step-actions.ts +38 -4
  141. package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +10 -3
  142. package/templates/default/src/actions/template-test-case/template-test-case-actions.ts +238 -238
  143. package/templates/default/src/actions/test-case/test-case-actions.ts +356 -356
  144. package/templates/default/src/actions/test-run/test-run-actions.ts +222 -129
  145. package/templates/default/src/actions/test-suite/test-suite-actions.ts +95 -17
  146. package/templates/default/src/actions/user/user-actions.ts +13 -13
  147. package/templates/default/src/app/(base)/environments/create/page.tsx +28 -28
  148. package/templates/default/src/app/(base)/environments/environment-form.tsx +219 -219
  149. package/templates/default/src/app/(base)/environments/environment-table-columns.tsx +96 -96
  150. package/templates/default/src/app/(base)/environments/environment-table.tsx +24 -24
  151. package/templates/default/src/app/(base)/environments/modify/[id]/page.tsx +46 -46
  152. package/templates/default/src/app/(base)/environments/page.tsx +59 -59
  153. package/templates/default/src/app/(base)/layout.tsx +10 -10
  154. package/templates/default/src/app/(base)/locator-groups/create/page.tsx +44 -44
  155. package/templates/default/src/app/(base)/locator-groups/locator-group-form.tsx +215 -215
  156. package/templates/default/src/app/(base)/locator-groups/locator-group-table-columns.tsx +77 -77
  157. package/templates/default/src/app/(base)/locator-groups/locator-group-table.tsx +28 -28
  158. package/templates/default/src/app/(base)/locator-groups/modify/[id]/page.tsx +46 -46
  159. package/templates/default/src/app/(base)/locator-groups/page.tsx +20 -8
  160. package/templates/default/src/app/(base)/locators/create/create-locator-workspace.tsx +542 -0
  161. package/templates/default/src/app/(base)/locators/create/page.tsx +32 -19
  162. package/templates/default/src/app/(base)/locators/locator-form.tsx +163 -163
  163. package/templates/default/src/app/(base)/locators/locator-table-columns.tsx +73 -73
  164. package/templates/default/src/app/(base)/locators/locator-table.tsx +28 -28
  165. package/templates/default/src/app/(base)/locators/modify/[id]/page.tsx +56 -27
  166. package/templates/default/src/app/(base)/locators/page.tsx +13 -13
  167. package/templates/default/src/app/(base)/locators/picker/page.tsx +7 -0
  168. package/templates/default/src/app/(base)/locators/sync-locators-button.tsx +66 -66
  169. package/templates/default/src/app/(base)/modules/create/page.tsx +34 -34
  170. package/templates/default/src/app/(base)/modules/modify/[id]/page.tsx +46 -46
  171. package/templates/default/src/app/(base)/modules/module-form.tsx +126 -126
  172. package/templates/default/src/app/(base)/modules/module-table-columns.tsx +85 -85
  173. package/templates/default/src/app/(base)/modules/module-table.tsx +24 -24
  174. package/templates/default/src/app/(base)/modules/page.tsx +59 -59
  175. package/templates/default/src/app/(base)/reports/[id]/page.tsx +40 -33
  176. package/templates/default/src/app/(base)/reports/duration-chart.tsx +33 -33
  177. package/templates/default/src/app/(base)/reports/feature-chart.tsx +78 -78
  178. package/templates/default/src/app/(base)/reports/overview-chart.tsx +49 -46
  179. package/templates/default/src/app/(base)/reports/page.tsx +98 -98
  180. package/templates/default/src/app/(base)/reports/report-metric-card.tsx +78 -16
  181. package/templates/default/src/app/(base)/reports/report-table-columns.tsx +189 -189
  182. package/templates/default/src/app/(base)/reports/report-table.tsx +72 -72
  183. package/templates/default/src/app/(base)/reports/report-view-table-columns.tsx +12 -1
  184. package/templates/default/src/app/(base)/reports/report-view-table.tsx +1 -0
  185. package/templates/default/src/app/(base)/reports/test-cases/page.tsx +40 -42
  186. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +115 -115
  187. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table.tsx +27 -27
  188. package/templates/default/src/app/(base)/reports/test-suites/page.tsx +42 -42
  189. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table-columns.tsx +79 -79
  190. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table.tsx +27 -27
  191. package/templates/default/src/app/(base)/reports/view-logs-button.tsx +58 -60
  192. package/templates/default/src/app/(base)/reviews/create/page.tsx +26 -26
  193. package/templates/default/src/app/(base)/reviews/created-reviews-table.tsx +15 -15
  194. package/templates/default/src/app/(base)/reviews/modify/[id]/page.tsx +26 -26
  195. package/templates/default/src/app/(base)/reviews/page.tsx +26 -26
  196. package/templates/default/src/app/(base)/reviews/review/[id]/page.tsx +26 -26
  197. package/templates/default/src/app/(base)/reviews/review-form.tsx +11 -11
  198. package/templates/default/src/app/(base)/reviews/review-table-by-creator-columns.tsx +9 -9
  199. package/templates/default/src/app/(base)/reviews/review-table-by-reviewer-columns.tsx +9 -9
  200. package/templates/default/src/app/(base)/reviews/reviewer-reviews-table.tsx +15 -15
  201. package/templates/default/src/app/(base)/settings/page.tsx +30 -0
  202. package/templates/default/src/app/(base)/settings/settings-sync-panel.tsx +261 -0
  203. package/templates/default/src/app/(base)/tags/create/page.tsx +39 -39
  204. package/templates/default/src/app/(base)/tags/modify/[id]/page.tsx +50 -50
  205. package/templates/default/src/app/(base)/tags/page.tsx +58 -58
  206. package/templates/default/src/app/(base)/tags/tag-form.tsx +147 -147
  207. package/templates/default/src/app/(base)/tags/tag-table-columns.tsx +63 -63
  208. package/templates/default/src/app/(base)/tags/tag-table.tsx +29 -29
  209. package/templates/default/src/app/(base)/template-step-groups/create/page.tsx +28 -28
  210. package/templates/default/src/app/(base)/template-step-groups/modify/[id]/page.tsx +45 -45
  211. package/templates/default/src/app/(base)/template-step-groups/page.tsx +60 -60
  212. package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.tsx +167 -167
  213. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table-columns.tsx +89 -89
  214. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table.tsx +32 -32
  215. package/templates/default/src/app/(base)/template-steps/create/page.tsx +37 -37
  216. package/templates/default/src/app/(base)/template-steps/modify/[id]/page.tsx +49 -49
  217. package/templates/default/src/app/(base)/template-steps/page.tsx +58 -58
  218. package/templates/default/src/app/(base)/template-steps/paramChip.tsx +213 -213
  219. package/templates/default/src/app/(base)/template-steps/template-step-form.tsx +384 -384
  220. package/templates/default/src/app/(base)/template-steps/template-step-table-columns.tsx +158 -158
  221. package/templates/default/src/app/(base)/template-steps/template-step-table.tsx +24 -24
  222. package/templates/default/src/app/(base)/template-test-cases/create/page.tsx +56 -56
  223. package/templates/default/src/app/(base)/template-test-cases/modify/[id]/page.tsx +89 -89
  224. package/templates/default/src/app/(base)/template-test-cases/page.tsx +58 -58
  225. package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.tsx +84 -84
  226. package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.tsx +262 -262
  227. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table-columns.tsx +76 -76
  228. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table.tsx +32 -32
  229. package/templates/default/src/app/(base)/test-cases/create/page.tsx +76 -76
  230. package/templates/default/src/app/(base)/test-cases/create-from-template/generate/[id]/page.tsx +96 -96
  231. package/templates/default/src/app/(base)/test-cases/create-from-template/page.tsx +38 -38
  232. package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.tsx +73 -73
  233. package/templates/default/src/app/(base)/test-cases/modify/[id]/page.tsx +106 -106
  234. package/templates/default/src/app/(base)/test-cases/page.tsx +2 -2
  235. package/templates/default/src/app/(base)/test-cases/test-case-flow.tsx +82 -82
  236. package/templates/default/src/app/(base)/test-cases/test-case-form.tsx +395 -395
  237. package/templates/default/src/app/(base)/test-cases/test-case-table-columns.tsx +6 -71
  238. package/templates/default/src/app/(base)/test-cases/test-case-table.tsx +2 -2
  239. package/templates/default/src/app/(base)/test-runs/[id]/page.tsx +4 -1
  240. package/templates/default/src/app/(base)/test-runs/create/page.tsx +11 -9
  241. package/templates/default/src/app/(base)/test-runs/page.tsx +60 -60
  242. package/templates/default/src/app/(base)/test-runs/test-run-form.tsx +37 -44
  243. package/templates/default/src/app/(base)/test-runs/test-run-table-columns.tsx +229 -229
  244. package/templates/default/src/app/(base)/test-runs/test-run-table.tsx +127 -127
  245. package/templates/default/src/app/(base)/test-suites/create/page.tsx +3 -2
  246. package/templates/default/src/app/(base)/test-suites/modify/[id]/page.tsx +2 -1
  247. package/templates/default/src/app/(base)/test-suites/page.tsx +57 -21
  248. package/templates/default/src/app/(base)/test-suites/test-suite-form.tsx +16 -17
  249. package/templates/default/src/app/(base)/test-suites/test-suite-table-columns.tsx +2 -1
  250. package/templates/default/src/app/(base)/test-suites/test-suite-table.tsx +29 -29
  251. package/templates/default/src/app/(dashboard-components)/app-drawer.tsx +187 -187
  252. package/templates/default/src/app/(dashboard-components)/data-card-grid.tsx +12 -12
  253. package/templates/default/src/app/(dashboard-components)/data-card.tsx +26 -26
  254. package/templates/default/src/app/(dashboard-components)/execution-health-panel.tsx +56 -56
  255. package/templates/default/src/app/(dashboard-components)/ongoing-test-runs-card.tsx +87 -87
  256. package/templates/default/src/app/(dashboard-components)/quick-actions-drawer.tsx +44 -44
  257. package/templates/default/src/app/api/reports/steps/[stepId]/screenshot/route.ts +52 -0
  258. package/templates/default/src/app/api/test-runs/[runId]/download/route.ts +97 -34
  259. package/templates/default/src/app/api/test-runs/[runId]/logs/route.ts +4 -1
  260. package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +146 -147
  261. package/templates/default/src/app/globals.css +147 -147
  262. package/templates/default/src/app/layout.tsx +4 -0
  263. package/templates/default/src/app/page.tsx +2 -0
  264. package/templates/default/src/assets/icons/empty-tube.tsx +23 -23
  265. package/templates/default/src/assets/icons/tube-plus.tsx +29 -29
  266. package/templates/default/src/components/base-node.tsx +21 -21
  267. package/templates/default/src/components/chart/pie-chart.tsx +73 -73
  268. package/templates/default/src/components/data-extraction/locator-inspector.tsx +460 -460
  269. package/templates/default/src/components/data-state/empty-state.tsx +40 -40
  270. package/templates/default/src/components/data-visualization/info-card.tsx +70 -70
  271. package/templates/default/src/components/data-visualization/info-grid.tsx +22 -22
  272. package/templates/default/src/components/diagram/button-edge.tsx +54 -54
  273. package/templates/default/src/components/diagram/dynamic-parameters.tsx +474 -474
  274. package/templates/default/src/components/diagram/edit-header-option.tsx +36 -36
  275. package/templates/default/src/components/diagram/flow-diagram.tsx +470 -470
  276. package/templates/default/src/components/diagram/node-form.tsx +262 -262
  277. package/templates/default/src/components/diagram/options-header-node.tsx +57 -57
  278. package/templates/default/src/components/diagram/template-step-combobox.tsx +155 -155
  279. package/templates/default/src/components/form/error-message.tsx +7 -7
  280. package/templates/default/src/components/kokonutui/smooth-tab.tsx +453 -453
  281. package/templates/default/src/components/loading-skeleton/data-table/data-table-skeleton.tsx +30 -30
  282. package/templates/default/src/components/loading-skeleton/form/button-skeleton.tsx +8 -8
  283. package/templates/default/src/components/loading-skeleton/form/icon-button-skeleton.tsx +8 -8
  284. package/templates/default/src/components/loading-skeleton/form/text-input-skeleton.tsx +8 -8
  285. package/templates/default/src/components/loading-skeleton/visualization/table-skeleton.tsx +14 -14
  286. package/templates/default/src/components/logo.tsx +15 -15
  287. package/templates/default/src/components/navigation/command-badge.tsx +34 -34
  288. package/templates/default/src/components/navigation/command-chain-input.tsx +51 -51
  289. package/templates/default/src/components/navigation/entity-search-command.tsx +116 -116
  290. package/templates/default/src/components/navigation/nav-card.tsx +31 -31
  291. package/templates/default/src/components/navigation/nav-command.tsx +13 -0
  292. package/templates/default/src/components/navigation/nav-link.tsx +60 -60
  293. package/templates/default/src/components/navigation/nav-menu-card-deck.tsx +112 -112
  294. package/templates/default/src/components/node-header.tsx +159 -159
  295. package/templates/default/src/components/reports/test-case-logs-modal.tsx +310 -253
  296. package/templates/default/src/components/table/table-actions.tsx +172 -172
  297. package/templates/default/src/components/test-case/test-case-columns.tsx +76 -0
  298. package/templates/default/src/components/test-case/test-case-picker.tsx +244 -0
  299. package/templates/default/src/components/test-run/download-logs-button.tsx +92 -99
  300. package/templates/default/src/components/test-run/log-viewer.tsx +1 -14
  301. package/templates/default/src/components/test-run/test-run-details.tsx +15 -2
  302. package/templates/default/src/components/test-run/test-run-header.tsx +149 -149
  303. package/templates/default/src/components/test-run/view-report-button.tsx +102 -102
  304. package/templates/default/src/components/test-suite/test-suite-picker.tsx +444 -0
  305. package/templates/default/src/components/theme/mode-toggle.tsx +54 -54
  306. package/templates/default/src/components/theme/theme-provider.tsx +8 -8
  307. package/templates/default/src/components/typography/page-header-subtitle.tsx +7 -7
  308. package/templates/default/src/components/typography/page-header.tsx +7 -7
  309. package/templates/default/src/components/ui/alert-dialog.tsx +106 -106
  310. package/templates/default/src/components/ui/alert.tsx +43 -43
  311. package/templates/default/src/components/ui/avatar.tsx +40 -40
  312. package/templates/default/src/components/ui/badge.tsx +29 -29
  313. package/templates/default/src/components/ui/button.tsx +47 -47
  314. package/templates/default/src/components/ui/calendar.tsx +158 -158
  315. package/templates/default/src/components/ui/card.tsx +43 -43
  316. package/templates/default/src/components/ui/checkbox.tsx +28 -28
  317. package/templates/default/src/components/ui/command.tsx +135 -135
  318. package/templates/default/src/components/ui/data-table-column-header.tsx +61 -61
  319. package/templates/default/src/components/ui/data-table-pagination.tsx +87 -87
  320. package/templates/default/src/components/ui/data-table-view-options.tsx +50 -50
  321. package/templates/default/src/components/ui/data-table.tsx +261 -261
  322. package/templates/default/src/components/ui/dialog.tsx +97 -97
  323. package/templates/default/src/components/ui/dropdown-menu.tsx +182 -182
  324. package/templates/default/src/components/ui/input.tsx +22 -22
  325. package/templates/default/src/components/ui/kbd.tsx +28 -28
  326. package/templates/default/src/components/ui/label.tsx +19 -19
  327. package/templates/default/src/components/ui/loading.tsx +12 -12
  328. package/templates/default/src/components/ui/multi-select-with-preview.tsx +116 -116
  329. package/templates/default/src/components/ui/multi-select.tsx +142 -142
  330. package/templates/default/src/components/ui/navigation-menu.tsx +120 -120
  331. package/templates/default/src/components/ui/popover.tsx +33 -33
  332. package/templates/default/src/components/ui/progress.tsx +25 -25
  333. package/templates/default/src/components/ui/radio-group.tsx +44 -44
  334. package/templates/default/src/components/ui/scroll-area.tsx +40 -40
  335. package/templates/default/src/components/ui/select.tsx +151 -151
  336. package/templates/default/src/components/ui/separator.tsx +22 -22
  337. package/templates/default/src/components/ui/skeleton.tsx +7 -7
  338. package/templates/default/src/components/ui/table.tsx +76 -76
  339. package/templates/default/src/components/ui/tabs.tsx +55 -55
  340. package/templates/default/src/components/ui/textarea.tsx +21 -21
  341. package/templates/default/src/components/ui/toast.tsx +113 -113
  342. package/templates/default/src/components/ui/toaster.tsx +26 -26
  343. package/templates/default/src/components/user-prompt/delete-prompt.tsx +87 -87
  344. package/templates/default/src/config/db-config.ts +10 -10
  345. package/templates/default/src/constants/form-opts/diagram/node-form.ts +30 -30
  346. package/templates/default/src/constants/form-opts/environment-form-opts.ts +24 -24
  347. package/templates/default/src/constants/form-opts/locator-form-opts.ts +20 -20
  348. package/templates/default/src/constants/form-opts/locator-group-form-opts.ts +28 -28
  349. package/templates/default/src/constants/form-opts/module-form-opts.ts +21 -21
  350. package/templates/default/src/constants/form-opts/review-form-opts.ts +23 -23
  351. package/templates/default/src/constants/form-opts/tag-form-opts.ts +42 -42
  352. package/templates/default/src/constants/form-opts/template-selection-form-opts.ts +16 -16
  353. package/templates/default/src/constants/form-opts/template-step-group-form-opts.ts +24 -24
  354. package/templates/default/src/constants/form-opts/template-test-case-form-opts.ts +39 -39
  355. package/templates/default/src/constants/form-opts/template-test-step-form-opts.ts +36 -36
  356. package/templates/default/src/constants/form-opts/test-case-form-opts.ts +43 -43
  357. package/templates/default/src/constants/form-opts/test-run-form-opts.ts +5 -3
  358. package/templates/default/src/constants/form-opts/test-suite-form-opts.ts +24 -24
  359. package/templates/default/src/hooks/use-toast.ts +187 -187
  360. package/templates/default/src/lib/automation/paths.ts +42 -12
  361. package/templates/default/src/lib/automation/projection-service.ts +2 -1
  362. package/templates/default/src/lib/bidirectional-sync.ts +432 -432
  363. package/templates/default/src/lib/database-sync.ts +2 -7
  364. package/templates/default/src/lib/environment-file-utils.ts +184 -184
  365. package/templates/default/src/lib/executor/local-executor-adapter.ts +24 -25
  366. package/templates/default/src/lib/executor/types.ts +2 -2
  367. package/templates/default/src/lib/feature-file-generator.ts +5 -66
  368. package/templates/default/src/lib/gherkin-parser.ts +259 -259
  369. package/templates/default/src/lib/locator-group-file-utils.ts +307 -307
  370. package/templates/default/src/lib/locator-picker/session-manager.ts +452 -0
  371. package/templates/default/src/lib/locator-picker/suggestions.ts +124 -0
  372. package/templates/default/src/lib/metrics/metric-calculator.ts +5 -0
  373. package/templates/default/src/lib/module-hierarchy-builder.ts +205 -205
  374. package/templates/default/src/lib/path-helpers/module-path.ts +71 -71
  375. package/templates/default/src/lib/process/task-spawner.ts +39 -2
  376. package/templates/default/src/lib/sync/projected-feature-utils.ts +113 -0
  377. package/templates/default/src/lib/sync/sync-executor.ts +130 -0
  378. package/templates/default/src/lib/sync/sync-pending-counts.test.ts +239 -0
  379. package/templates/default/src/lib/sync/sync-pending-counts.ts +1358 -0
  380. package/templates/default/src/lib/sync/sync-registry.ts +149 -0
  381. package/templates/default/src/lib/tag-utils.ts +28 -0
  382. package/templates/default/src/lib/template-sync-utils.d.ts +6 -6
  383. package/templates/default/src/lib/template-sync-utils.js +46 -46
  384. package/templates/default/src/lib/template-sync-utils.ts +63 -63
  385. package/templates/default/src/lib/test-case-utils.ts +6 -6
  386. package/templates/default/src/lib/test-run/log-formatter.ts +83 -83
  387. package/templates/default/src/lib/test-run/matching.ts +117 -0
  388. package/templates/default/src/lib/test-run/process-manager.ts +2 -0
  389. package/templates/default/src/lib/test-run/report-parser.ts +352 -316
  390. package/templates/default/src/lib/test-run/test-run-executor.ts +13 -15
  391. package/templates/default/src/lib/test-run/winston-logger.ts +64 -66
  392. package/templates/default/src/lib/test-suite-identifier-service.ts +76 -0
  393. package/templates/default/src/lib/test-suite-utils.ts +6 -0
  394. package/templates/default/src/lib/transformers/gherkin-converter.ts +42 -42
  395. package/templates/default/src/lib/transformers/key-to-icon-transformer.tsx +95 -95
  396. package/templates/default/src/lib/transformers/template-test-case-converter.ts +160 -160
  397. package/templates/default/src/lib/utils/node-param-validation.ts +81 -81
  398. package/templates/default/src/lib/utils/template-step-file-generator.ts +104 -104
  399. package/templates/default/src/lib/utils/template-step-file-manager-intelligent.ts +0 -8
  400. package/templates/default/src/lib/utils/template-step-file-manager.ts +166 -166
  401. package/templates/default/src/lib/utils.ts +31 -31
  402. package/templates/default/src/types/diagram/diagram.ts +34 -34
  403. package/templates/default/src/types/diagram/template-step.ts +11 -11
  404. package/templates/default/src/types/executor/browser.type.ts +1 -1
  405. package/templates/default/src/types/form/actionHandler.ts +6 -6
  406. package/templates/default/src/types/locator/locator.type.ts +11 -11
  407. package/templates/default/src/types/locator-picker.ts +65 -0
  408. package/templates/default/src/types/step/step.type.ts +1 -1
  409. package/templates/default/src/types/table/data-table.ts +6 -6
  410. package/templates/default/src/types/test-case-picker.ts +6 -0
  411. package/templates/default/src/types/test-suite-picker.ts +18 -0
  412. package/templates/default/tailwind.config.ts +62 -62
  413. package/templates/default/tsconfig.json +4 -1
  414. package/templates/default/appraisejs.config.json +0 -1
@@ -1,453 +1,453 @@
1
- "use client";
2
-
3
- /**
4
- * @author: @dorian_baffier
5
- * @description: Smooth Tab
6
- * @version: 1.0.0
7
- * @date: 2025-06-26
8
- * @license: MIT
9
- * @website: https://kokonutui.com
10
- * @github: https://github.com/kokonut-labs/kokonutui
11
- */
12
-
13
- import * as React from "react";
14
- import { AnimatePresence, motion } from "motion/react";
15
- import { cn } from "@/lib/utils";
16
- import type { LucideIcon } from "lucide-react";
17
-
18
- interface TabItem {
19
- id: string;
20
- title: string;
21
- icon?: LucideIcon;
22
- content?: React.ReactNode;
23
- cardContent?: React.ReactNode;
24
- color: string;
25
- }
26
-
27
- const WaveformPath = () => (
28
- <motion.path
29
- d="M0 50
30
- C 20 40, 40 30, 60 50
31
- C 80 70, 100 60, 120 50
32
- C 140 40, 160 30, 180 50
33
- C 200 70, 220 60, 240 50
34
- C 260 40, 280 30, 300 50
35
- C 320 70, 340 60, 360 50
36
- C 380 40, 400 30, 420 50
37
- L 420 100 L 0 100 Z"
38
- initial={false}
39
- animate={{
40
- x: [0, 10, 0],
41
- transition: {
42
- duration: 5,
43
- ease: "linear",
44
- repeat: Number.POSITIVE_INFINITY,
45
- },
46
- }}
47
- />
48
- );
49
-
50
- const DEFAULT_TABS: TabItem[] = [
51
- {
52
- id: "Models",
53
- title: "Models",
54
- color: "bg-blue-500 hover:bg-blue-600",
55
- cardContent: (
56
- <div className="relative h-full">
57
- <div className="absolute inset-0 overflow-hidden">
58
- <svg
59
- className="absolute bottom-0 w-full h-32"
60
- viewBox="0 0 420 100"
61
- preserveAspectRatio="none"
62
- aria-hidden="true"
63
- role="presentation"
64
- >
65
- <motion.g
66
- initial={{ opacity: 0 }}
67
- animate={{ opacity: 0.15 }}
68
- transition={{ duration: 0.5 }}
69
- className="fill-blue-500 stroke-blue-500"
70
- style={{ strokeWidth: 1 }}
71
- >
72
- <WaveformPath />
73
- </motion.g>
74
- <motion.g
75
- initial={{ opacity: 0 }}
76
- animate={{ opacity: 0.1 }}
77
- transition={{ duration: 0.5 }}
78
- className="fill-blue-500 stroke-blue-500"
79
- style={{
80
- strokeWidth: 1,
81
- transform: "translateY(10px)",
82
- }}
83
- >
84
- <WaveformPath />
85
- </motion.g>
86
- </svg>
87
- </div>
88
- <div className="p-6 h-full relative flex flex-col">
89
- <div className="space-y-2">
90
- <h3 className="text-2xl font-semibold tracking-tight bg-gradient-to-r from-foreground via-foreground/90 to-foreground/70 [text-shadow:_0_1px_1px_rgb(0_0_0_/_10%)]">
91
- Models
92
- </h3>
93
- <p className="text-sm text-black/50 dark:text-white/50 leading-relaxed max-w-[90%]">
94
- Choose the model you want to use
95
- </p>
96
- </div>
97
- </div>
98
- </div>
99
- ),
100
- },
101
- {
102
- id: "MCPs",
103
- title: "MCPs",
104
- color: "bg-purple-500 hover:bg-purple-600",
105
- cardContent: (
106
- <div className="relative h-full">
107
- <div className="absolute inset-0 overflow-hidden">
108
- <svg
109
- className="absolute bottom-0 w-full h-32"
110
- viewBox="0 0 420 100"
111
- preserveAspectRatio="none"
112
- aria-hidden="true"
113
- role="presentation"
114
- >
115
- <motion.g
116
- initial={{ opacity: 0 }}
117
- animate={{ opacity: 0.15 }}
118
- transition={{ duration: 0.5 }}
119
- className="fill-purple-500 stroke-purple-500"
120
- style={{ strokeWidth: 1 }}
121
- >
122
- <WaveformPath />
123
- </motion.g>
124
- <motion.g
125
- initial={{ opacity: 0 }}
126
- animate={{ opacity: 0.1 }}
127
- transition={{ duration: 0.5 }}
128
- className="fill-purple-500 stroke-purple-500"
129
- style={{
130
- strokeWidth: 1,
131
- transform: "translateY(10px)",
132
- }}
133
- >
134
- <WaveformPath />
135
- </motion.g>
136
- </svg>
137
- </div>
138
- <div className="p-6 h-full relative flex flex-col">
139
- <div className="space-y-2">
140
- <h3 className="text-xl font-semibold tracking-tight bg-gradient-to-r from-foreground via-foreground/90 to-foreground/70 [text-shadow:_0_1px_1px_rgb(0_0_0_/_10%)]">
141
- MCPs
142
- </h3>
143
- <p className="text-sm text-black/50 dark:text-white/50 leading-relaxed max-w-[90%]">
144
- Choose the MCP you want to use
145
- </p>
146
- </div>
147
- </div>
148
- </div>
149
- ),
150
- },
151
- {
152
- id: "Agents",
153
- title: "Agents",
154
- color: "bg-emerald-500 hover:bg-emerald-600",
155
- cardContent: (
156
- <div className="relative h-full">
157
- <div className="absolute inset-0 overflow-hidden">
158
- <svg
159
- className="absolute bottom-0 w-full h-32"
160
- viewBox="0 0 420 100"
161
- preserveAspectRatio="none"
162
- aria-hidden="true"
163
- role="presentation"
164
- >
165
- <motion.g
166
- initial={{ opacity: 0 }}
167
- animate={{ opacity: 0.15 }}
168
- transition={{ duration: 0.5 }}
169
- className="fill-emerald-500 stroke-emerald-500"
170
- style={{ strokeWidth: 1 }}
171
- >
172
- <WaveformPath />
173
- </motion.g>
174
- <motion.g
175
- initial={{ opacity: 0 }}
176
- animate={{ opacity: 0.1 }}
177
- transition={{ duration: 0.5 }}
178
- className="fill-emerald-500 stroke-emerald-500"
179
- style={{
180
- strokeWidth: 1,
181
- transform: "translateY(10px)",
182
- }}
183
- >
184
- <WaveformPath />
185
- </motion.g>
186
- </svg>
187
- </div>
188
- <div className="p-6 h-full relative flex flex-col">
189
- <div className="space-y-2">
190
- <h3 className="text-2xl font-semibold tracking-tight bg-gradient-to-r from-foreground via-foreground/90 to-foreground/70 [text-shadow:_0_1px_1px_rgb(0_0_0_/_10%)]">
191
- Agents
192
- </h3>
193
- <p className="text-sm text-black/50 dark:text-white/50 leading-relaxed max-w-[90%]">
194
- Choose the agent you want to use
195
- </p>
196
- </div>
197
- </div>
198
- </div>
199
- ),
200
- },
201
- {
202
- id: "Users",
203
- title: "Users",
204
- color: "bg-amber-500 hover:bg-amber-600",
205
- cardContent: (
206
- <div className="relative h-full">
207
- <div className="absolute inset-0 overflow-hidden">
208
- <svg
209
- className="absolute bottom-0 w-full h-32"
210
- viewBox="0 0 420 100"
211
- preserveAspectRatio="none"
212
- aria-hidden="true"
213
- role="presentation"
214
- >
215
- <motion.g
216
- initial={{ opacity: 0 }}
217
- animate={{ opacity: 0.15 }}
218
- transition={{ duration: 0.5 }}
219
- className="fill-amber-500 stroke-amber-500"
220
- style={{ strokeWidth: 1 }}
221
- >
222
- <WaveformPath />
223
- </motion.g>
224
- <motion.g
225
- initial={{ opacity: 0 }}
226
- animate={{ opacity: 0.1 }}
227
- transition={{ duration: 0.5 }}
228
- className="fill-amber-500 stroke-amber-500"
229
- style={{
230
- strokeWidth: 1,
231
- transform: "translateY(10px)",
232
- }}
233
- >
234
- <WaveformPath />
235
- </motion.g>
236
- </svg>
237
- </div>
238
- <div className="p-6 h-full relative flex flex-col">
239
- <div className="space-y-2">
240
- <h3 className="text-2xl font-semibold tracking-tight bg-gradient-to-r from-foreground via-foreground/90 to-foreground/70 [text-shadow:_0_1px_1px_rgb(0_0_0_/_10%)]">
241
- Users
242
- </h3>
243
- <p className="text-sm text-black/50 dark:text-white/50 leading-relaxed max-w-[90%]">
244
- Choose the user you want to use
245
- </p>
246
- </div>
247
- </div>
248
- </div>
249
- ),
250
- },
251
- ];
252
-
253
- interface SmoothTabProps {
254
- items?: TabItem[];
255
- defaultTabId?: string;
256
- className?: string;
257
- activeColor?: string;
258
- onChange?: (tabId: string) => void;
259
- }
260
-
261
- const slideVariants = {
262
- enter: (direction: number) => ({
263
- x: direction > 0 ? "100%" : "-100%",
264
- opacity: 0,
265
- filter: "blur(8px)",
266
- scale: 0.95,
267
- position: "absolute" as const,
268
- }),
269
- center: {
270
- x: 0,
271
- opacity: 1,
272
- filter: "blur(0px)",
273
- scale: 1,
274
- position: "absolute" as const,
275
- },
276
- exit: (direction: number) => ({
277
- x: direction < 0 ? "100%" : "-100%",
278
- opacity: 0,
279
- filter: "blur(8px)",
280
- scale: 0.95,
281
- position: "absolute" as const,
282
- }),
283
- };
284
-
285
- const transition = {
286
- duration: 0.4,
287
- ease: [0.32, 0.72, 0, 1],
288
- };
289
-
290
- export default function SmoothTab({
291
- items = DEFAULT_TABS,
292
- defaultTabId = DEFAULT_TABS[0].id,
293
- className,
294
- activeColor = "bg-[#1F9CFE]",
295
- onChange,
296
- }: SmoothTabProps) {
297
- const [selected, setSelected] = React.useState<string>(defaultTabId);
298
- const [direction, setDirection] = React.useState(0);
299
- const [dimensions, setDimensions] = React.useState({ width: 0, left: 0 });
300
-
301
- // Reference for the selected button
302
- const buttonRefs = React.useRef<Map<string, HTMLButtonElement>>(new Map());
303
- const containerRef = React.useRef<HTMLDivElement>(null);
304
-
305
- // Update dimensions whenever selected tab changes or on mount
306
- React.useLayoutEffect(() => {
307
- const updateDimensions = () => {
308
- const selectedButton = buttonRefs.current.get(selected);
309
- const container = containerRef.current;
310
-
311
- if (selectedButton && container) {
312
- const rect = selectedButton.getBoundingClientRect();
313
- const containerRect = container.getBoundingClientRect();
314
-
315
- setDimensions({
316
- width: rect.width,
317
- left: rect.left - containerRect.left,
318
- });
319
- }
320
- };
321
-
322
- // Initial update
323
- requestAnimationFrame(() => {
324
- updateDimensions();
325
- });
326
-
327
- // Update on resize
328
- window.addEventListener("resize", updateDimensions);
329
- return () => window.removeEventListener("resize", updateDimensions);
330
- }, [selected]);
331
-
332
- const handleTabClick = (tabId: string) => {
333
- const currentIndex = items.findIndex((item) => item.id === selected);
334
- const newIndex = items.findIndex((item) => item.id === tabId);
335
- setDirection(newIndex > currentIndex ? 1 : -1);
336
- setSelected(tabId);
337
- onChange?.(tabId);
338
- };
339
-
340
- const handleKeyDown = (
341
- e: React.KeyboardEvent<HTMLButtonElement>,
342
- tabId: string
343
- ) => {
344
- if (e.key === "Enter" || e.key === " ") {
345
- e.preventDefault();
346
- handleTabClick(tabId);
347
- }
348
- };
349
-
350
- const selectedItem = items.find((item) => item.id === selected);
351
-
352
- return (
353
- <div className="flex flex-col h-full">
354
- {/* Card Content Area */}
355
- <div className="flex-1 mb-4 relative">
356
- <div className="bg-card border rounded-lg h-[200px] w-full relative">
357
- <div className="absolute inset-0 overflow-hidden rounded-lg">
358
- <AnimatePresence
359
- initial={false}
360
- mode="popLayout"
361
- custom={direction}
362
- >
363
- <motion.div
364
- key={`card-${selected}`}
365
- custom={direction}
366
- variants={slideVariants as React.ComponentProps<typeof motion.div>['variants']}
367
- initial="enter"
368
- animate="center"
369
- exit="exit"
370
- transition={transition as unknown as React.ComponentProps<typeof motion.div>['transition']}
371
- className="absolute inset-0 w-full h-full will-change-transform bg-card"
372
- style={{
373
- backfaceVisibility: "hidden",
374
- WebkitBackfaceVisibility: "hidden",
375
- }}
376
- >
377
- {selectedItem?.cardContent}
378
- </motion.div>
379
- </AnimatePresence>
380
- </div>
381
- </div>
382
- </div>
383
-
384
- {/* Bottom Toolbar */}
385
- <div
386
- ref={containerRef}
387
- role="tablist"
388
- aria-label="Smooth tabs"
389
- className={cn(
390
- "flex items-center justify-between gap-1 py-1 mt-auto relative",
391
- "bg-background w-[400px] mx-auto",
392
- "border rounded-xl",
393
- "transition-all duration-200",
394
- className
395
- )}
396
- >
397
- {/* Sliding Background */}
398
- <motion.div
399
- className={cn(
400
- "absolute rounded-lg z-[1]",
401
- selectedItem?.color || activeColor
402
- )}
403
- initial={false}
404
- animate={{
405
- width: dimensions.width - 8,
406
- x: dimensions.left + 4,
407
- opacity: 1,
408
- }}
409
- transition={{
410
- type: "spring",
411
- stiffness: 400,
412
- damping: 30,
413
- }}
414
- style={{ height: "calc(100% - 8px)", top: "4px" }}
415
- />
416
-
417
- <div className="grid grid-cols-4 w-full gap-1 relative z-[2]">
418
- {items.map((item) => {
419
- const isSelected = selected === item.id;
420
- return (
421
- <motion.button
422
- key={item.id}
423
- ref={(el) => {
424
- if (el) buttonRefs.current.set(item.id, el);
425
- else buttonRefs.current.delete(item.id);
426
- }}
427
- type="button"
428
- role="tab"
429
- aria-selected={isSelected}
430
- aria-controls={`panel-${item.id}`}
431
- id={`tab-${item.id}`}
432
- tabIndex={isSelected ? 0 : -1}
433
- onClick={() => handleTabClick(item.id)}
434
- onKeyDown={(e) => handleKeyDown(e, item.id)}
435
- className={cn(
436
- "relative flex items-center justify-center gap-0.5 rounded-lg px-2 py-1.5",
437
- "text-sm font-medium transition-all duration-300",
438
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
439
- "truncate",
440
- isSelected
441
- ? "text-white"
442
- : "text-muted-foreground hover:bg-muted/50 hover:text-foreground"
443
- )}
444
- >
445
- <span className="truncate">{item.title}</span>
446
- </motion.button>
447
- );
448
- })}
449
- </div>
450
- </div>
451
- </div>
452
- );
453
- }
1
+ "use client";
2
+
3
+ /**
4
+ * @author: @dorian_baffier
5
+ * @description: Smooth Tab
6
+ * @version: 1.0.0
7
+ * @date: 2025-06-26
8
+ * @license: MIT
9
+ * @website: https://kokonutui.com
10
+ * @github: https://github.com/kokonut-labs/kokonutui
11
+ */
12
+
13
+ import * as React from "react";
14
+ import { AnimatePresence, motion } from "motion/react";
15
+ import { cn } from "@/lib/utils";
16
+ import type { LucideIcon } from "lucide-react";
17
+
18
+ interface TabItem {
19
+ id: string;
20
+ title: string;
21
+ icon?: LucideIcon;
22
+ content?: React.ReactNode;
23
+ cardContent?: React.ReactNode;
24
+ color: string;
25
+ }
26
+
27
+ const WaveformPath = () => (
28
+ <motion.path
29
+ d="M0 50
30
+ C 20 40, 40 30, 60 50
31
+ C 80 70, 100 60, 120 50
32
+ C 140 40, 160 30, 180 50
33
+ C 200 70, 220 60, 240 50
34
+ C 260 40, 280 30, 300 50
35
+ C 320 70, 340 60, 360 50
36
+ C 380 40, 400 30, 420 50
37
+ L 420 100 L 0 100 Z"
38
+ initial={false}
39
+ animate={{
40
+ x: [0, 10, 0],
41
+ transition: {
42
+ duration: 5,
43
+ ease: "linear",
44
+ repeat: Number.POSITIVE_INFINITY,
45
+ },
46
+ }}
47
+ />
48
+ );
49
+
50
+ const DEFAULT_TABS: TabItem[] = [
51
+ {
52
+ id: "Models",
53
+ title: "Models",
54
+ color: "bg-blue-500 hover:bg-blue-600",
55
+ cardContent: (
56
+ <div className="relative h-full">
57
+ <div className="absolute inset-0 overflow-hidden">
58
+ <svg
59
+ className="absolute bottom-0 w-full h-32"
60
+ viewBox="0 0 420 100"
61
+ preserveAspectRatio="none"
62
+ aria-hidden="true"
63
+ role="presentation"
64
+ >
65
+ <motion.g
66
+ initial={{ opacity: 0 }}
67
+ animate={{ opacity: 0.15 }}
68
+ transition={{ duration: 0.5 }}
69
+ className="fill-blue-500 stroke-blue-500"
70
+ style={{ strokeWidth: 1 }}
71
+ >
72
+ <WaveformPath />
73
+ </motion.g>
74
+ <motion.g
75
+ initial={{ opacity: 0 }}
76
+ animate={{ opacity: 0.1 }}
77
+ transition={{ duration: 0.5 }}
78
+ className="fill-blue-500 stroke-blue-500"
79
+ style={{
80
+ strokeWidth: 1,
81
+ transform: "translateY(10px)",
82
+ }}
83
+ >
84
+ <WaveformPath />
85
+ </motion.g>
86
+ </svg>
87
+ </div>
88
+ <div className="p-6 h-full relative flex flex-col">
89
+ <div className="space-y-2">
90
+ <h3 className="text-2xl font-semibold tracking-tight bg-gradient-to-r from-foreground via-foreground/90 to-foreground/70 [text-shadow:_0_1px_1px_rgb(0_0_0_/_10%)]">
91
+ Models
92
+ </h3>
93
+ <p className="text-sm text-black/50 dark:text-white/50 leading-relaxed max-w-[90%]">
94
+ Choose the model you want to use
95
+ </p>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ ),
100
+ },
101
+ {
102
+ id: "MCPs",
103
+ title: "MCPs",
104
+ color: "bg-purple-500 hover:bg-purple-600",
105
+ cardContent: (
106
+ <div className="relative h-full">
107
+ <div className="absolute inset-0 overflow-hidden">
108
+ <svg
109
+ className="absolute bottom-0 w-full h-32"
110
+ viewBox="0 0 420 100"
111
+ preserveAspectRatio="none"
112
+ aria-hidden="true"
113
+ role="presentation"
114
+ >
115
+ <motion.g
116
+ initial={{ opacity: 0 }}
117
+ animate={{ opacity: 0.15 }}
118
+ transition={{ duration: 0.5 }}
119
+ className="fill-purple-500 stroke-purple-500"
120
+ style={{ strokeWidth: 1 }}
121
+ >
122
+ <WaveformPath />
123
+ </motion.g>
124
+ <motion.g
125
+ initial={{ opacity: 0 }}
126
+ animate={{ opacity: 0.1 }}
127
+ transition={{ duration: 0.5 }}
128
+ className="fill-purple-500 stroke-purple-500"
129
+ style={{
130
+ strokeWidth: 1,
131
+ transform: "translateY(10px)",
132
+ }}
133
+ >
134
+ <WaveformPath />
135
+ </motion.g>
136
+ </svg>
137
+ </div>
138
+ <div className="p-6 h-full relative flex flex-col">
139
+ <div className="space-y-2">
140
+ <h3 className="text-xl font-semibold tracking-tight bg-gradient-to-r from-foreground via-foreground/90 to-foreground/70 [text-shadow:_0_1px_1px_rgb(0_0_0_/_10%)]">
141
+ MCPs
142
+ </h3>
143
+ <p className="text-sm text-black/50 dark:text-white/50 leading-relaxed max-w-[90%]">
144
+ Choose the MCP you want to use
145
+ </p>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ ),
150
+ },
151
+ {
152
+ id: "Agents",
153
+ title: "Agents",
154
+ color: "bg-emerald-500 hover:bg-emerald-600",
155
+ cardContent: (
156
+ <div className="relative h-full">
157
+ <div className="absolute inset-0 overflow-hidden">
158
+ <svg
159
+ className="absolute bottom-0 w-full h-32"
160
+ viewBox="0 0 420 100"
161
+ preserveAspectRatio="none"
162
+ aria-hidden="true"
163
+ role="presentation"
164
+ >
165
+ <motion.g
166
+ initial={{ opacity: 0 }}
167
+ animate={{ opacity: 0.15 }}
168
+ transition={{ duration: 0.5 }}
169
+ className="fill-emerald-500 stroke-emerald-500"
170
+ style={{ strokeWidth: 1 }}
171
+ >
172
+ <WaveformPath />
173
+ </motion.g>
174
+ <motion.g
175
+ initial={{ opacity: 0 }}
176
+ animate={{ opacity: 0.1 }}
177
+ transition={{ duration: 0.5 }}
178
+ className="fill-emerald-500 stroke-emerald-500"
179
+ style={{
180
+ strokeWidth: 1,
181
+ transform: "translateY(10px)",
182
+ }}
183
+ >
184
+ <WaveformPath />
185
+ </motion.g>
186
+ </svg>
187
+ </div>
188
+ <div className="p-6 h-full relative flex flex-col">
189
+ <div className="space-y-2">
190
+ <h3 className="text-2xl font-semibold tracking-tight bg-gradient-to-r from-foreground via-foreground/90 to-foreground/70 [text-shadow:_0_1px_1px_rgb(0_0_0_/_10%)]">
191
+ Agents
192
+ </h3>
193
+ <p className="text-sm text-black/50 dark:text-white/50 leading-relaxed max-w-[90%]">
194
+ Choose the agent you want to use
195
+ </p>
196
+ </div>
197
+ </div>
198
+ </div>
199
+ ),
200
+ },
201
+ {
202
+ id: "Users",
203
+ title: "Users",
204
+ color: "bg-amber-500 hover:bg-amber-600",
205
+ cardContent: (
206
+ <div className="relative h-full">
207
+ <div className="absolute inset-0 overflow-hidden">
208
+ <svg
209
+ className="absolute bottom-0 w-full h-32"
210
+ viewBox="0 0 420 100"
211
+ preserveAspectRatio="none"
212
+ aria-hidden="true"
213
+ role="presentation"
214
+ >
215
+ <motion.g
216
+ initial={{ opacity: 0 }}
217
+ animate={{ opacity: 0.15 }}
218
+ transition={{ duration: 0.5 }}
219
+ className="fill-amber-500 stroke-amber-500"
220
+ style={{ strokeWidth: 1 }}
221
+ >
222
+ <WaveformPath />
223
+ </motion.g>
224
+ <motion.g
225
+ initial={{ opacity: 0 }}
226
+ animate={{ opacity: 0.1 }}
227
+ transition={{ duration: 0.5 }}
228
+ className="fill-amber-500 stroke-amber-500"
229
+ style={{
230
+ strokeWidth: 1,
231
+ transform: "translateY(10px)",
232
+ }}
233
+ >
234
+ <WaveformPath />
235
+ </motion.g>
236
+ </svg>
237
+ </div>
238
+ <div className="p-6 h-full relative flex flex-col">
239
+ <div className="space-y-2">
240
+ <h3 className="text-2xl font-semibold tracking-tight bg-gradient-to-r from-foreground via-foreground/90 to-foreground/70 [text-shadow:_0_1px_1px_rgb(0_0_0_/_10%)]">
241
+ Users
242
+ </h3>
243
+ <p className="text-sm text-black/50 dark:text-white/50 leading-relaxed max-w-[90%]">
244
+ Choose the user you want to use
245
+ </p>
246
+ </div>
247
+ </div>
248
+ </div>
249
+ ),
250
+ },
251
+ ];
252
+
253
+ interface SmoothTabProps {
254
+ items?: TabItem[];
255
+ defaultTabId?: string;
256
+ className?: string;
257
+ activeColor?: string;
258
+ onChange?: (tabId: string) => void;
259
+ }
260
+
261
+ const slideVariants = {
262
+ enter: (direction: number) => ({
263
+ x: direction > 0 ? "100%" : "-100%",
264
+ opacity: 0,
265
+ filter: "blur(8px)",
266
+ scale: 0.95,
267
+ position: "absolute" as const,
268
+ }),
269
+ center: {
270
+ x: 0,
271
+ opacity: 1,
272
+ filter: "blur(0px)",
273
+ scale: 1,
274
+ position: "absolute" as const,
275
+ },
276
+ exit: (direction: number) => ({
277
+ x: direction < 0 ? "100%" : "-100%",
278
+ opacity: 0,
279
+ filter: "blur(8px)",
280
+ scale: 0.95,
281
+ position: "absolute" as const,
282
+ }),
283
+ };
284
+
285
+ const transition = {
286
+ duration: 0.4,
287
+ ease: [0.32, 0.72, 0, 1],
288
+ };
289
+
290
+ export default function SmoothTab({
291
+ items = DEFAULT_TABS,
292
+ defaultTabId = DEFAULT_TABS[0].id,
293
+ className,
294
+ activeColor = "bg-[#1F9CFE]",
295
+ onChange,
296
+ }: SmoothTabProps) {
297
+ const [selected, setSelected] = React.useState<string>(defaultTabId);
298
+ const [direction, setDirection] = React.useState(0);
299
+ const [dimensions, setDimensions] = React.useState({ width: 0, left: 0 });
300
+
301
+ // Reference for the selected button
302
+ const buttonRefs = React.useRef<Map<string, HTMLButtonElement>>(new Map());
303
+ const containerRef = React.useRef<HTMLDivElement>(null);
304
+
305
+ // Update dimensions whenever selected tab changes or on mount
306
+ React.useLayoutEffect(() => {
307
+ const updateDimensions = () => {
308
+ const selectedButton = buttonRefs.current.get(selected);
309
+ const container = containerRef.current;
310
+
311
+ if (selectedButton && container) {
312
+ const rect = selectedButton.getBoundingClientRect();
313
+ const containerRect = container.getBoundingClientRect();
314
+
315
+ setDimensions({
316
+ width: rect.width,
317
+ left: rect.left - containerRect.left,
318
+ });
319
+ }
320
+ };
321
+
322
+ // Initial update
323
+ requestAnimationFrame(() => {
324
+ updateDimensions();
325
+ });
326
+
327
+ // Update on resize
328
+ window.addEventListener("resize", updateDimensions);
329
+ return () => window.removeEventListener("resize", updateDimensions);
330
+ }, [selected]);
331
+
332
+ const handleTabClick = (tabId: string) => {
333
+ const currentIndex = items.findIndex((item) => item.id === selected);
334
+ const newIndex = items.findIndex((item) => item.id === tabId);
335
+ setDirection(newIndex > currentIndex ? 1 : -1);
336
+ setSelected(tabId);
337
+ onChange?.(tabId);
338
+ };
339
+
340
+ const handleKeyDown = (
341
+ e: React.KeyboardEvent<HTMLButtonElement>,
342
+ tabId: string
343
+ ) => {
344
+ if (e.key === "Enter" || e.key === " ") {
345
+ e.preventDefault();
346
+ handleTabClick(tabId);
347
+ }
348
+ };
349
+
350
+ const selectedItem = items.find((item) => item.id === selected);
351
+
352
+ return (
353
+ <div className="flex flex-col h-full">
354
+ {/* Card Content Area */}
355
+ <div className="flex-1 mb-4 relative">
356
+ <div className="bg-card border rounded-lg h-[200px] w-full relative">
357
+ <div className="absolute inset-0 overflow-hidden rounded-lg">
358
+ <AnimatePresence
359
+ initial={false}
360
+ mode="popLayout"
361
+ custom={direction}
362
+ >
363
+ <motion.div
364
+ key={`card-${selected}`}
365
+ custom={direction}
366
+ variants={slideVariants as React.ComponentProps<typeof motion.div>['variants']}
367
+ initial="enter"
368
+ animate="center"
369
+ exit="exit"
370
+ transition={transition as unknown as React.ComponentProps<typeof motion.div>['transition']}
371
+ className="absolute inset-0 w-full h-full will-change-transform bg-card"
372
+ style={{
373
+ backfaceVisibility: "hidden",
374
+ WebkitBackfaceVisibility: "hidden",
375
+ }}
376
+ >
377
+ {selectedItem?.cardContent}
378
+ </motion.div>
379
+ </AnimatePresence>
380
+ </div>
381
+ </div>
382
+ </div>
383
+
384
+ {/* Bottom Toolbar */}
385
+ <div
386
+ ref={containerRef}
387
+ role="tablist"
388
+ aria-label="Smooth tabs"
389
+ className={cn(
390
+ "flex items-center justify-between gap-1 py-1 mt-auto relative",
391
+ "bg-background w-[400px] mx-auto",
392
+ "border rounded-xl",
393
+ "transition-all duration-200",
394
+ className
395
+ )}
396
+ >
397
+ {/* Sliding Background */}
398
+ <motion.div
399
+ className={cn(
400
+ "absolute rounded-lg z-[1]",
401
+ selectedItem?.color || activeColor
402
+ )}
403
+ initial={false}
404
+ animate={{
405
+ width: dimensions.width - 8,
406
+ x: dimensions.left + 4,
407
+ opacity: 1,
408
+ }}
409
+ transition={{
410
+ type: "spring",
411
+ stiffness: 400,
412
+ damping: 30,
413
+ }}
414
+ style={{ height: "calc(100% - 8px)", top: "4px" }}
415
+ />
416
+
417
+ <div className="grid grid-cols-4 w-full gap-1 relative z-[2]">
418
+ {items.map((item) => {
419
+ const isSelected = selected === item.id;
420
+ return (
421
+ <motion.button
422
+ key={item.id}
423
+ ref={(el) => {
424
+ if (el) buttonRefs.current.set(item.id, el);
425
+ else buttonRefs.current.delete(item.id);
426
+ }}
427
+ type="button"
428
+ role="tab"
429
+ aria-selected={isSelected}
430
+ aria-controls={`panel-${item.id}`}
431
+ id={`tab-${item.id}`}
432
+ tabIndex={isSelected ? 0 : -1}
433
+ onClick={() => handleTabClick(item.id)}
434
+ onKeyDown={(e) => handleKeyDown(e, item.id)}
435
+ className={cn(
436
+ "relative flex items-center justify-center gap-0.5 rounded-lg px-2 py-1.5",
437
+ "text-sm font-medium transition-all duration-300",
438
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
439
+ "truncate",
440
+ isSelected
441
+ ? "text-white"
442
+ : "text-muted-foreground hover:bg-muted/50 hover:text-foreground"
443
+ )}
444
+ >
445
+ <span className="truncate">{item.title}</span>
446
+ </motion.button>
447
+ );
448
+ })}
449
+ </div>
450
+ </div>
451
+ </div>
452
+ );
453
+ }