@oneuptime/common 10.7.2 → 10.8.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.
- package/Models/DatabaseModels/CloudResource.ts +846 -0
- package/Models/DatabaseModels/CloudResourceInstance.ts +276 -0
- package/Models/DatabaseModels/CloudResourceLabelRule.ts +510 -0
- package/Models/DatabaseModels/CloudResourceOwnerRule.ts +592 -0
- package/Models/DatabaseModels/CloudResourceOwnerTeam.ts +487 -0
- package/Models/DatabaseModels/CloudResourceOwnerUser.ts +486 -0
- package/Models/DatabaseModels/Host.ts +209 -0
- package/Models/DatabaseModels/Index.ts +36 -0
- package/Models/DatabaseModels/RumApplication.ts +731 -0
- package/Models/DatabaseModels/RumApplicationClient.ts +229 -0
- package/Models/DatabaseModels/RumApplicationLabelRule.ts +510 -0
- package/Models/DatabaseModels/RumApplicationOwnerRule.ts +592 -0
- package/Models/DatabaseModels/RumApplicationOwnerTeam.ts +486 -0
- package/Models/DatabaseModels/RumApplicationOwnerUser.ts +485 -0
- package/Models/DatabaseModels/ServerlessFunction.ts +881 -0
- package/Models/DatabaseModels/ServerlessFunctionInstance.ts +212 -0
- package/Models/DatabaseModels/ServerlessFunctionLabelRule.ts +510 -0
- package/Models/DatabaseModels/ServerlessFunctionOwnerRule.ts +592 -0
- package/Models/DatabaseModels/ServerlessFunctionOwnerTeam.ts +487 -0
- package/Models/DatabaseModels/ServerlessFunctionOwnerUser.ts +486 -0
- package/Models/DatabaseModels/Service.ts +268 -0
- package/Models/DatabaseModels/TelemetryException.ts +15 -1
- package/Models/DatabaseModels/WorkflowLog.ts +52 -0
- package/Server/API/TelemetryAPI.ts +78 -25
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780931746908-AddResumeStateToWorkflowLog.ts +21 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780931863719-AddTelemetryResourceMetadataColumns.ts +108 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780933132562-AddServerlessFunctionTables.ts +205 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780935387827-AddCloudResourceTables.ts +195 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780936579718-AddRumApplicationTables.ts +202 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780938407319-AddServerlessFunctionRuleTables.ts +156 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780940721814-AddCloudResourceRuleTables.ts +149 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780940998002-AddRumApplicationRuleTables.ts +149 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780941762204-AddTelemetryResourceInventoryTables.ts +95 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780985763463-AddRumApplicationSdkLanguage.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780987192743-RecastCloudResourcesByEnvironment.ts +30 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +22 -0
- package/Server/Infrastructure/Queue.ts +11 -0
- package/Server/Services/CloudResourceInstanceService.ts +76 -0
- package/Server/Services/CloudResourceLabelRuleEngineService.ts +175 -0
- package/Server/Services/CloudResourceLabelRuleService.ts +14 -0
- package/Server/Services/CloudResourceOwnerRuleEngineService.ts +192 -0
- package/Server/Services/CloudResourceOwnerRuleService.ts +14 -0
- package/Server/Services/CloudResourceOwnerTeamService.ts +10 -0
- package/Server/Services/CloudResourceOwnerUserService.ts +10 -0
- package/Server/Services/CloudResourceService.ts +342 -0
- package/Server/Services/ExceptionAggregationService.ts +3 -0
- package/Server/Services/HostService.ts +42 -0
- package/Server/Services/LogAggregationService.ts +44 -16
- package/Server/Services/MetricAggregationService.ts +3 -0
- package/Server/Services/OpenTelemetryIngestService.ts +148 -1
- package/Server/Services/RumApplicationClientService.ts +69 -0
- package/Server/Services/RumApplicationLabelRuleEngineService.ts +175 -0
- package/Server/Services/RumApplicationLabelRuleService.ts +14 -0
- package/Server/Services/RumApplicationOwnerRuleEngineService.ts +192 -0
- package/Server/Services/RumApplicationOwnerRuleService.ts +14 -0
- package/Server/Services/RumApplicationOwnerTeamService.ts +10 -0
- package/Server/Services/RumApplicationOwnerUserService.ts +10 -0
- package/Server/Services/RumApplicationService.ts +301 -0
- package/Server/Services/ServerlessFunctionInstanceService.ts +61 -0
- package/Server/Services/ServerlessFunctionLabelRuleEngineService.ts +182 -0
- package/Server/Services/ServerlessFunctionLabelRuleService.ts +14 -0
- package/Server/Services/ServerlessFunctionOwnerRuleEngineService.ts +199 -0
- package/Server/Services/ServerlessFunctionOwnerRuleService.ts +14 -0
- package/Server/Services/ServerlessFunctionOwnerTeamService.ts +10 -0
- package/Server/Services/ServerlessFunctionOwnerUserService.ts +10 -0
- package/Server/Services/ServerlessFunctionService.ts +351 -0
- package/Server/Services/ServiceService.ts +95 -8
- package/Server/Services/TraceAggregationService.ts +131 -4
- package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +39 -0
- package/Server/Types/Workflow/ComponentCode.ts +9 -0
- package/Server/Types/Workflow/Components/Index.ts +2 -0
- package/Server/Types/Workflow/Components/Sleep.ts +105 -0
- package/Server/Types/Workflow/Workflow.ts +6 -0
- package/Server/Utils/Telemetry/ResourceFacetResolver.ts +150 -0
- package/Types/Permission.ts +692 -1
- package/Types/Telemetry/ServiceType.ts +3 -0
- package/Types/Workflow/ComponentID.ts +1 -0
- package/Types/Workflow/Components/Sleep.ts +71 -0
- package/Types/Workflow/Components.ts +2 -0
- package/Types/Workflow/WorkflowStatus.ts +1 -0
- package/UI/Components/BulkUpdate/BulkLabelActions.tsx +159 -32
- package/UI/Components/Navbar/NavBar.tsx +72 -123
- package/UI/Components/Navbar/NavBarMenuModal.tsx +642 -0
- package/UI/Components/Workflow/WorkflowStatus.tsx +3 -0
- package/build/dist/Models/DatabaseModels/CloudResource.js +871 -0
- package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -0
- package/build/dist/Models/DatabaseModels/CloudResourceInstance.js +300 -0
- package/build/dist/Models/DatabaseModels/CloudResourceInstance.js.map +1 -0
- package/build/dist/Models/DatabaseModels/CloudResourceLabelRule.js +520 -0
- package/build/dist/Models/DatabaseModels/CloudResourceLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/CloudResourceOwnerRule.js +601 -0
- package/build/dist/Models/DatabaseModels/CloudResourceOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/CloudResourceOwnerTeam.js +503 -0
- package/build/dist/Models/DatabaseModels/CloudResourceOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/CloudResourceOwnerUser.js +502 -0
- package/build/dist/Models/DatabaseModels/CloudResourceOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Host.js +215 -0
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +36 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +751 -0
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RumApplicationClient.js +252 -0
- package/build/dist/Models/DatabaseModels/RumApplicationClient.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RumApplicationLabelRule.js +520 -0
- package/build/dist/Models/DatabaseModels/RumApplicationLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RumApplicationOwnerRule.js +601 -0
- package/build/dist/Models/DatabaseModels/RumApplicationOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RumApplicationOwnerTeam.js +503 -0
- package/build/dist/Models/DatabaseModels/RumApplicationOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RumApplicationOwnerUser.js +502 -0
- package/build/dist/Models/DatabaseModels/RumApplicationOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunction.js +908 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunction.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunctionInstance.js +234 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunctionInstance.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunctionLabelRule.js +520 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunctionLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunctionOwnerRule.js +601 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunctionOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunctionOwnerTeam.js +503 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunctionOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunctionOwnerUser.js +502 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunctionOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Service.js +276 -0
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js +12 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowLog.js +53 -0
- package/build/dist/Models/DatabaseModels/WorkflowLog.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +61 -23
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780931746908-AddResumeStateToWorkflowLog.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780931746908-AddResumeStateToWorkflowLog.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780931863719-AddTelemetryResourceMetadataColumns.js +53 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780931863719-AddTelemetryResourceMetadataColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780933132562-AddServerlessFunctionTables.js +82 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780933132562-AddServerlessFunctionTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780935387827-AddCloudResourceTables.js +82 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780935387827-AddCloudResourceTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780936579718-AddRumApplicationTables.js +83 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780936579718-AddRumApplicationTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780938407319-AddServerlessFunctionRuleTables.js +67 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780938407319-AddServerlessFunctionRuleTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780940721814-AddCloudResourceRuleTables.js +60 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780940721814-AddCloudResourceRuleTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780940998002-AddRumApplicationRuleTables.js +60 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780940998002-AddRumApplicationRuleTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780941762204-AddTelemetryResourceInventoryTables.js +45 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780941762204-AddTelemetryResourceInventoryTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780985763463-AddRumApplicationSdkLanguage.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780985763463-AddRumApplicationSdkLanguage.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780987192743-RecastCloudResourcesByEnvironment.js +27 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780987192743-RecastCloudResourcesByEnvironment.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +3 -0
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceInstanceService.js +76 -0
- package/build/dist/Server/Services/CloudResourceInstanceService.js.map +1 -0
- package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js +160 -0
- package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/CloudResourceLabelRuleService.js +13 -0
- package/build/dist/Server/Services/CloudResourceLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js +179 -0
- package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/CloudResourceOwnerRuleService.js +13 -0
- package/build/dist/Server/Services/CloudResourceOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/CloudResourceOwnerTeamService.js +9 -0
- package/build/dist/Server/Services/CloudResourceOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/CloudResourceOwnerUserService.js +9 -0
- package/build/dist/Server/Services/CloudResourceOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/CloudResourceService.js +287 -0
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -0
- package/build/dist/Server/Services/ExceptionAggregationService.js +3 -0
- package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +29 -1
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +36 -10
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricAggregationService.js +3 -0
- package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +98 -2
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationClientService.js +70 -0
- package/build/dist/Server/Services/RumApplicationClientService.js.map +1 -0
- package/build/dist/Server/Services/RumApplicationLabelRuleEngineService.js +160 -0
- package/build/dist/Server/Services/RumApplicationLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/RumApplicationLabelRuleService.js +13 -0
- package/build/dist/Server/Services/RumApplicationLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/RumApplicationOwnerRuleEngineService.js +179 -0
- package/build/dist/Server/Services/RumApplicationOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/RumApplicationOwnerRuleService.js +13 -0
- package/build/dist/Server/Services/RumApplicationOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/RumApplicationOwnerTeamService.js +9 -0
- package/build/dist/Server/Services/RumApplicationOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/RumApplicationOwnerUserService.js +9 -0
- package/build/dist/Server/Services/RumApplicationOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/RumApplicationService.js +259 -0
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -0
- package/build/dist/Server/Services/ServerlessFunctionInstanceService.js +64 -0
- package/build/dist/Server/Services/ServerlessFunctionInstanceService.js.map +1 -0
- package/build/dist/Server/Services/ServerlessFunctionLabelRuleEngineService.js +160 -0
- package/build/dist/Server/Services/ServerlessFunctionLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServerlessFunctionLabelRuleService.js +13 -0
- package/build/dist/Server/Services/ServerlessFunctionLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js +179 -0
- package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServerlessFunctionOwnerRuleService.js +13 -0
- package/build/dist/Server/Services/ServerlessFunctionOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServerlessFunctionOwnerTeamService.js +9 -0
- package/build/dist/Server/Services/ServerlessFunctionOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/ServerlessFunctionOwnerUserService.js +9 -0
- package/build/dist/Server/Services/ServerlessFunctionOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/ServerlessFunctionService.js +299 -0
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -0
- package/build/dist/Server/Services/ServiceService.js +63 -7
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +108 -4
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +39 -0
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
- package/build/dist/Server/Types/Workflow/ComponentCode.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Index.js +2 -0
- package/build/dist/Server/Types/Workflow/Components/Index.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Sleep.js +85 -0
- package/build/dist/Server/Types/Workflow/Components/Sleep.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +90 -0
- package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -1
- package/build/dist/Types/Permission.js +609 -1
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Telemetry/ServiceType.js +3 -0
- package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
- package/build/dist/Types/Workflow/ComponentID.js +1 -0
- package/build/dist/Types/Workflow/ComponentID.js.map +1 -1
- package/build/dist/Types/Workflow/Components/Sleep.js +64 -0
- package/build/dist/Types/Workflow/Components/Sleep.js.map +1 -0
- package/build/dist/Types/Workflow/Components.js +2 -0
- package/build/dist/Types/Workflow/Components.js.map +1 -1
- package/build/dist/Types/Workflow/WorkflowStatus.js +1 -0
- package/build/dist/Types/Workflow/WorkflowStatus.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js +113 -19
- package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +34 -66
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +412 -0
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -0
- package/build/dist/UI/Components/Workflow/WorkflowStatus.js +3 -0
- package/build/dist/UI/Components/Workflow/WorkflowStatus.js.map +1 -1
- package/package.json +1 -1
- package/UI/Components/Navbar/NavBarMenu.tsx +0 -183
- package/UI/Components/Navbar/NavBarMenuItem.tsx +0 -146
- package/build/dist/UI/Components/Navbar/NavBarMenu.js +0 -82
- package/build/dist/UI/Components/Navbar/NavBarMenu.js.map +0 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuItem.js +0 -109
- package/build/dist/UI/Components/Navbar/NavBarMenuItem.js.map +0 -1
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
import Icon from "../Icon/Icon";
|
|
2
|
+
import Link from "../Link/Link";
|
|
3
|
+
import Navigation from "../../Utils/Navigation";
|
|
4
|
+
import IconProp from "../../../Types/Icon/IconProp";
|
|
5
|
+
import URL from "../../../Types/API/URL";
|
|
6
|
+
import type { MoreMenuItem } from "./NavBar";
|
|
7
|
+
import React, {
|
|
8
|
+
FunctionComponent,
|
|
9
|
+
ReactElement,
|
|
10
|
+
ReactNode,
|
|
11
|
+
useMemo,
|
|
12
|
+
useRef,
|
|
13
|
+
useState,
|
|
14
|
+
useEffect,
|
|
15
|
+
} from "react";
|
|
16
|
+
|
|
17
|
+
interface IconColorClasses {
|
|
18
|
+
bg: string;
|
|
19
|
+
ring: string;
|
|
20
|
+
text: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* Icon color map carried over from the former NavBarMenuItem, extended with the
|
|
25
|
+
* few colors the dashboard items use (yellow/red/teal) plus a glyph text color
|
|
26
|
+
* so every product renders in its intended color. Unknown colors fall back to
|
|
27
|
+
* indigo.
|
|
28
|
+
*/
|
|
29
|
+
const ICON_COLOR_CLASSES: Record<string, IconColorClasses> = {
|
|
30
|
+
purple: {
|
|
31
|
+
bg: "bg-purple-50",
|
|
32
|
+
ring: "ring-purple-200",
|
|
33
|
+
text: "text-purple-600",
|
|
34
|
+
},
|
|
35
|
+
blue: { bg: "bg-blue-50", ring: "ring-blue-200", text: "text-blue-600" },
|
|
36
|
+
gray: { bg: "bg-gray-100", ring: "ring-gray-300", text: "text-gray-600" },
|
|
37
|
+
amber: { bg: "bg-amber-50", ring: "ring-amber-200", text: "text-amber-600" },
|
|
38
|
+
green: { bg: "bg-green-50", ring: "ring-green-200", text: "text-green-600" },
|
|
39
|
+
cyan: { bg: "bg-cyan-50", ring: "ring-cyan-200", text: "text-cyan-600" },
|
|
40
|
+
slate: { bg: "bg-slate-100", ring: "ring-slate-300", text: "text-slate-600" },
|
|
41
|
+
indigo: {
|
|
42
|
+
bg: "bg-indigo-50",
|
|
43
|
+
ring: "ring-indigo-200",
|
|
44
|
+
text: "text-indigo-600",
|
|
45
|
+
},
|
|
46
|
+
rose: { bg: "bg-rose-50", ring: "ring-rose-200", text: "text-rose-600" },
|
|
47
|
+
violet: {
|
|
48
|
+
bg: "bg-violet-50",
|
|
49
|
+
ring: "ring-violet-200",
|
|
50
|
+
text: "text-violet-600",
|
|
51
|
+
},
|
|
52
|
+
orange: {
|
|
53
|
+
bg: "bg-orange-50",
|
|
54
|
+
ring: "ring-orange-200",
|
|
55
|
+
text: "text-orange-600",
|
|
56
|
+
},
|
|
57
|
+
stone: { bg: "bg-stone-100", ring: "ring-stone-300", text: "text-stone-600" },
|
|
58
|
+
sky: { bg: "bg-sky-50", ring: "ring-sky-200", text: "text-sky-600" },
|
|
59
|
+
emerald: {
|
|
60
|
+
bg: "bg-emerald-50",
|
|
61
|
+
ring: "ring-emerald-200",
|
|
62
|
+
text: "text-emerald-600",
|
|
63
|
+
},
|
|
64
|
+
yellow: {
|
|
65
|
+
bg: "bg-yellow-50",
|
|
66
|
+
ring: "ring-yellow-200",
|
|
67
|
+
text: "text-yellow-600",
|
|
68
|
+
},
|
|
69
|
+
red: { bg: "bg-red-50", ring: "ring-red-200", text: "text-red-600" },
|
|
70
|
+
teal: { bg: "bg-teal-50", ring: "ring-teal-200", text: "text-teal-600" },
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Persist the handful of most-recently opened products across sessions.
|
|
74
|
+
const RECENT_STORAGE_KEY: string = "oneuptime-navbar-recent-products";
|
|
75
|
+
const RECENT_LIMIT: number = 5;
|
|
76
|
+
|
|
77
|
+
const readRecentRoutes: () => string[] = (): string[] => {
|
|
78
|
+
try {
|
|
79
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
const raw: string | null = window.localStorage.getItem(RECENT_STORAGE_KEY);
|
|
83
|
+
if (!raw) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
const parsed: unknown = JSON.parse(raw);
|
|
87
|
+
if (!Array.isArray(parsed)) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
return parsed.filter((value: unknown): value is string => {
|
|
91
|
+
return typeof value === "string";
|
|
92
|
+
});
|
|
93
|
+
} catch {
|
|
94
|
+
// Ignore storage access / parse errors (e.g. private mode, bad JSON).
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const writeRecentRoute: (routeString: string) => void = (
|
|
100
|
+
routeString: string,
|
|
101
|
+
): void => {
|
|
102
|
+
try {
|
|
103
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const existing: string[] = readRecentRoutes().filter((route: string) => {
|
|
107
|
+
return route !== routeString;
|
|
108
|
+
});
|
|
109
|
+
const next: string[] = [routeString, ...existing].slice(0, RECENT_LIMIT);
|
|
110
|
+
window.localStorage.setItem(RECENT_STORAGE_KEY, JSON.stringify(next));
|
|
111
|
+
} catch {
|
|
112
|
+
// Ignore storage write errors (e.g. private mode, quota exceeded).
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export interface ComponentProps {
|
|
117
|
+
items: MoreMenuItem[];
|
|
118
|
+
footer?:
|
|
119
|
+
| {
|
|
120
|
+
title: string;
|
|
121
|
+
description: string;
|
|
122
|
+
link: URL;
|
|
123
|
+
}
|
|
124
|
+
| undefined;
|
|
125
|
+
searchPlaceholder?: string | undefined;
|
|
126
|
+
noResultsText?: string | undefined;
|
|
127
|
+
keyboardHint?: string | undefined;
|
|
128
|
+
recentLabel?: string | undefined;
|
|
129
|
+
onClose: () => void;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
interface IndexedItem {
|
|
133
|
+
item: MoreMenuItem;
|
|
134
|
+
flatIndex: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
interface MenuGroup {
|
|
138
|
+
category: string;
|
|
139
|
+
items: IndexedItem[];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface RawGroup {
|
|
143
|
+
category: string;
|
|
144
|
+
items: MoreMenuItem[];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const NavBarMenuModal: FunctionComponent<ComponentProps> = (
|
|
148
|
+
props: ComponentProps,
|
|
149
|
+
): ReactElement => {
|
|
150
|
+
const [query, setQuery] = useState<string>("");
|
|
151
|
+
const [activeIndex, setActiveIndex] = useState<number>(0);
|
|
152
|
+
// Drives the open transition (fade + scale-in) on the first paint.
|
|
153
|
+
const [isShown, setIsShown] = useState<boolean>(false);
|
|
154
|
+
// Recently opened product routes, read once when the modal opens.
|
|
155
|
+
const [recentRoutes] = useState<string[]>(() => {
|
|
156
|
+
return readRecentRoutes();
|
|
157
|
+
});
|
|
158
|
+
const inputRef: React.RefObject<HTMLInputElement> =
|
|
159
|
+
useRef<HTMLInputElement>(null);
|
|
160
|
+
const cellRefs: React.MutableRefObject<Array<HTMLDivElement | null>> = useRef<
|
|
161
|
+
Array<HTMLDivElement | null>
|
|
162
|
+
>([]);
|
|
163
|
+
|
|
164
|
+
const recentLabel: string = props.recentLabel || "Recent";
|
|
165
|
+
|
|
166
|
+
// Show the OS-appropriate shortcut hint (⌘K on macOS, Ctrl K elsewhere).
|
|
167
|
+
const shortcutLabel: string = useMemo(() => {
|
|
168
|
+
if (typeof navigator === "undefined") {
|
|
169
|
+
return "⌘K";
|
|
170
|
+
}
|
|
171
|
+
const platform: string = (
|
|
172
|
+
navigator.platform ||
|
|
173
|
+
navigator.userAgent ||
|
|
174
|
+
""
|
|
175
|
+
).toLowerCase();
|
|
176
|
+
const isApplePlatform: boolean =
|
|
177
|
+
platform.includes("mac") ||
|
|
178
|
+
platform.includes("iphone") ||
|
|
179
|
+
platform.includes("ipad") ||
|
|
180
|
+
platform.includes("ipod");
|
|
181
|
+
return isApplePlatform ? "⌘K" : "Ctrl K";
|
|
182
|
+
}, []);
|
|
183
|
+
|
|
184
|
+
// Filter items by the search query (title + description + category).
|
|
185
|
+
const filteredItems: MoreMenuItem[] = useMemo(() => {
|
|
186
|
+
const normalizedQuery: string = query.trim().toLowerCase();
|
|
187
|
+
if (!normalizedQuery) {
|
|
188
|
+
return props.items;
|
|
189
|
+
}
|
|
190
|
+
return props.items.filter((item: MoreMenuItem) => {
|
|
191
|
+
const haystack: string = `${item.title} ${item.description} ${
|
|
192
|
+
item.category || ""
|
|
193
|
+
}`.toLowerCase();
|
|
194
|
+
return haystack.includes(normalizedQuery);
|
|
195
|
+
});
|
|
196
|
+
}, [props.items, query]);
|
|
197
|
+
|
|
198
|
+
/*
|
|
199
|
+
* The "Recent" row: resolve stored routes to current items, skip the page
|
|
200
|
+
* we're already on, and cap the count. Only shown while idle (no query).
|
|
201
|
+
*/
|
|
202
|
+
const recentItems: MoreMenuItem[] = useMemo(() => {
|
|
203
|
+
if (recentRoutes.length === 0) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
const itemByRoute: Map<string, MoreMenuItem> = new Map();
|
|
207
|
+
props.items.forEach((item: MoreMenuItem) => {
|
|
208
|
+
itemByRoute.set(item.route.toString(), item);
|
|
209
|
+
});
|
|
210
|
+
const resolved: MoreMenuItem[] = [];
|
|
211
|
+
recentRoutes.forEach((route: string) => {
|
|
212
|
+
const item: MoreMenuItem | undefined = itemByRoute.get(route);
|
|
213
|
+
if (item && !Navigation.isStartWith(item.activeRoute || item.route)) {
|
|
214
|
+
resolved.push(item);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
return resolved.slice(0, RECENT_LIMIT);
|
|
218
|
+
}, [recentRoutes, props.items]);
|
|
219
|
+
|
|
220
|
+
// Group filtered items by category, preserving first-seen order.
|
|
221
|
+
const categoryGroups: RawGroup[] = useMemo(() => {
|
|
222
|
+
const order: string[] = [];
|
|
223
|
+
const byCategory: Map<string, MoreMenuItem[]> = new Map();
|
|
224
|
+
filteredItems.forEach((item: MoreMenuItem) => {
|
|
225
|
+
const category: string = item.category || "Other";
|
|
226
|
+
if (!byCategory.has(category)) {
|
|
227
|
+
byCategory.set(category, []);
|
|
228
|
+
order.push(category);
|
|
229
|
+
}
|
|
230
|
+
byCategory.get(category)!.push(item);
|
|
231
|
+
});
|
|
232
|
+
return order.map((category: string) => {
|
|
233
|
+
return { category, items: byCategory.get(category)! };
|
|
234
|
+
});
|
|
235
|
+
}, [filteredItems]);
|
|
236
|
+
|
|
237
|
+
/*
|
|
238
|
+
* Final render order: a "Recent" group (idle only) followed by the category
|
|
239
|
+
* groups, with every item stamped with its index in the flattened order so
|
|
240
|
+
* keyboard navigation and cell refs stay in sync.
|
|
241
|
+
*/
|
|
242
|
+
const displayGroups: MenuGroup[] = useMemo(() => {
|
|
243
|
+
const raw: RawGroup[] = [];
|
|
244
|
+
if (!query.trim() && recentItems.length > 0) {
|
|
245
|
+
raw.push({ category: recentLabel, items: recentItems });
|
|
246
|
+
}
|
|
247
|
+
raw.push(...categoryGroups);
|
|
248
|
+
|
|
249
|
+
let flatIndex: number = 0;
|
|
250
|
+
return raw.map((group: RawGroup) => {
|
|
251
|
+
const items: IndexedItem[] = group.items.map((item: MoreMenuItem) => {
|
|
252
|
+
const entry: IndexedItem = { item, flatIndex };
|
|
253
|
+
flatIndex++;
|
|
254
|
+
return entry;
|
|
255
|
+
});
|
|
256
|
+
return { category: group.category, items };
|
|
257
|
+
});
|
|
258
|
+
}, [query, recentItems, categoryGroups, recentLabel]);
|
|
259
|
+
|
|
260
|
+
// Flattened list in render order — used for keyboard navigation.
|
|
261
|
+
const flatItems: MoreMenuItem[] = useMemo(() => {
|
|
262
|
+
return displayGroups.reduce((acc: MoreMenuItem[], group: MenuGroup) => {
|
|
263
|
+
group.items.forEach((entry: IndexedItem) => {
|
|
264
|
+
acc.push(entry.item);
|
|
265
|
+
});
|
|
266
|
+
return acc;
|
|
267
|
+
}, []);
|
|
268
|
+
}, [displayGroups]);
|
|
269
|
+
|
|
270
|
+
// Index of the product matching the current page (the "you are here" item).
|
|
271
|
+
const currentFlatIndex: number = useMemo(() => {
|
|
272
|
+
return flatItems.findIndex((item: MoreMenuItem) => {
|
|
273
|
+
return Navigation.isStartWith(item.activeRoute || item.route);
|
|
274
|
+
});
|
|
275
|
+
}, [flatItems]);
|
|
276
|
+
|
|
277
|
+
// Play the open animation and focus the search box on mount.
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
setIsShown(true);
|
|
280
|
+
inputRef.current?.focus();
|
|
281
|
+
}, []);
|
|
282
|
+
|
|
283
|
+
/*
|
|
284
|
+
* When idle, pre-select the current page so the modal opens "where you are";
|
|
285
|
+
* while searching, snap the selection to the first result.
|
|
286
|
+
*/
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
if (query) {
|
|
289
|
+
setActiveIndex(0);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
setActiveIndex(currentFlatIndex >= 0 ? currentFlatIndex : 0);
|
|
293
|
+
}, [query, currentFlatIndex]);
|
|
294
|
+
|
|
295
|
+
// Keep the active cell scrolled into view as the selection moves.
|
|
296
|
+
useEffect(() => {
|
|
297
|
+
cellRefs.current[activeIndex]?.scrollIntoView({ block: "nearest" });
|
|
298
|
+
}, [activeIndex]);
|
|
299
|
+
|
|
300
|
+
// Record a selection and close the modal.
|
|
301
|
+
const selectItem: (item: MoreMenuItem) => void = (
|
|
302
|
+
item: MoreMenuItem,
|
|
303
|
+
): void => {
|
|
304
|
+
writeRecentRoute(item.route.toString());
|
|
305
|
+
props.onClose();
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Wrap the portions of text that match the query in a highlight.
|
|
309
|
+
const highlightMatch: (text: string) => ReactNode = (
|
|
310
|
+
text: string,
|
|
311
|
+
): ReactNode => {
|
|
312
|
+
const needle: string = query.trim().toLowerCase();
|
|
313
|
+
if (!needle) {
|
|
314
|
+
return text;
|
|
315
|
+
}
|
|
316
|
+
const haystack: string = text.toLowerCase();
|
|
317
|
+
const parts: ReactNode[] = [];
|
|
318
|
+
let cursor: number = 0;
|
|
319
|
+
let matchAt: number = haystack.indexOf(needle, cursor);
|
|
320
|
+
let key: number = 0;
|
|
321
|
+
while (matchAt !== -1) {
|
|
322
|
+
if (matchAt > cursor) {
|
|
323
|
+
parts.push(text.slice(cursor, matchAt));
|
|
324
|
+
}
|
|
325
|
+
parts.push(
|
|
326
|
+
<mark
|
|
327
|
+
key={`m-${key}`}
|
|
328
|
+
className="rounded-sm bg-yellow-100 px-0.5 text-inherit"
|
|
329
|
+
>
|
|
330
|
+
{text.slice(matchAt, matchAt + needle.length)}
|
|
331
|
+
</mark>,
|
|
332
|
+
);
|
|
333
|
+
key++;
|
|
334
|
+
cursor = matchAt + needle.length;
|
|
335
|
+
matchAt = haystack.indexOf(needle, cursor);
|
|
336
|
+
}
|
|
337
|
+
if (cursor < text.length) {
|
|
338
|
+
parts.push(text.slice(cursor));
|
|
339
|
+
}
|
|
340
|
+
return parts;
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const moveVertical: (direction: "up" | "down") => void = (
|
|
344
|
+
direction: "up" | "down",
|
|
345
|
+
): void => {
|
|
346
|
+
const cells: Array<HTMLDivElement | null> = cellRefs.current;
|
|
347
|
+
const active: HTMLDivElement | null = cells[activeIndex] || null;
|
|
348
|
+
if (!active) {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const activeRect: DOMRect = active.getBoundingClientRect();
|
|
352
|
+
const activeCenterX: number = activeRect.left + activeRect.width / 2;
|
|
353
|
+
|
|
354
|
+
// Find the nearest row in the requested direction.
|
|
355
|
+
let targetTop: number | null = null;
|
|
356
|
+
for (let i: number = 0; i < flatItems.length; i++) {
|
|
357
|
+
const cell: HTMLDivElement | null = cells[i] || null;
|
|
358
|
+
if (!cell) {
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
const top: number = cell.getBoundingClientRect().top;
|
|
362
|
+
if (direction === "down" && top > activeRect.top + 1) {
|
|
363
|
+
if (targetTop === null || top < targetTop) {
|
|
364
|
+
targetTop = top;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (direction === "up" && top < activeRect.top - 1) {
|
|
368
|
+
if (targetTop === null || top > targetTop) {
|
|
369
|
+
targetTop = top;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (targetTop === null) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Within that row, pick the cell whose horizontal center is closest.
|
|
378
|
+
let bestIndex: number = activeIndex;
|
|
379
|
+
let bestDistance: number = Number.POSITIVE_INFINITY;
|
|
380
|
+
for (let i: number = 0; i < flatItems.length; i++) {
|
|
381
|
+
const cell: HTMLDivElement | null = cells[i] || null;
|
|
382
|
+
if (!cell) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
const rect: DOMRect = cell.getBoundingClientRect();
|
|
386
|
+
if (Math.abs(rect.top - targetTop) <= 1) {
|
|
387
|
+
const centerX: number = rect.left + rect.width / 2;
|
|
388
|
+
const distance: number = Math.abs(centerX - activeCenterX);
|
|
389
|
+
if (distance < bestDistance) {
|
|
390
|
+
bestDistance = distance;
|
|
391
|
+
bestIndex = i;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
setActiveIndex(bestIndex);
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const handleKeyDown: (
|
|
399
|
+
event: React.KeyboardEvent<HTMLInputElement>,
|
|
400
|
+
) => void = (event: React.KeyboardEvent<HTMLInputElement>): void => {
|
|
401
|
+
if (event.key === "Escape") {
|
|
402
|
+
event.preventDefault();
|
|
403
|
+
props.onClose();
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (flatItems.length === 0) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (event.key === "ArrowRight") {
|
|
410
|
+
event.preventDefault();
|
|
411
|
+
setActiveIndex((index: number) => {
|
|
412
|
+
return Math.min(index + 1, flatItems.length - 1);
|
|
413
|
+
});
|
|
414
|
+
} else if (event.key === "ArrowLeft") {
|
|
415
|
+
event.preventDefault();
|
|
416
|
+
setActiveIndex((index: number) => {
|
|
417
|
+
return Math.max(index - 1, 0);
|
|
418
|
+
});
|
|
419
|
+
} else if (event.key === "ArrowDown") {
|
|
420
|
+
event.preventDefault();
|
|
421
|
+
moveVertical("down");
|
|
422
|
+
} else if (event.key === "ArrowUp") {
|
|
423
|
+
event.preventDefault();
|
|
424
|
+
moveVertical("up");
|
|
425
|
+
} else if (event.key === "Enter") {
|
|
426
|
+
event.preventDefault();
|
|
427
|
+
const item: MoreMenuItem | undefined = flatItems[activeIndex];
|
|
428
|
+
if (item) {
|
|
429
|
+
selectItem(item);
|
|
430
|
+
Navigation.navigate(item.route);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
const keycapClass: string =
|
|
436
|
+
"inline-flex h-5 min-w-[1.25rem] items-center justify-center rounded border border-gray-200 bg-white px-1 text-[11px] font-medium text-gray-500 shadow-sm";
|
|
437
|
+
|
|
438
|
+
return (
|
|
439
|
+
<div
|
|
440
|
+
className="relative z-50"
|
|
441
|
+
role="dialog"
|
|
442
|
+
aria-modal="true"
|
|
443
|
+
aria-label="Products menu"
|
|
444
|
+
>
|
|
445
|
+
{/* Backdrop */}
|
|
446
|
+
<div
|
|
447
|
+
className={`fixed inset-0 bg-gray-900 bg-opacity-50 backdrop-blur-sm transition-opacity duration-200 ${
|
|
448
|
+
isShown ? "opacity-100" : "opacity-0"
|
|
449
|
+
}`}
|
|
450
|
+
/>
|
|
451
|
+
|
|
452
|
+
{/* Scroll + click-away container */}
|
|
453
|
+
<div
|
|
454
|
+
className="fixed inset-0 z-50 overflow-y-auto"
|
|
455
|
+
onClick={props.onClose}
|
|
456
|
+
>
|
|
457
|
+
<div className="flex min-h-full items-start justify-center p-4 sm:p-6">
|
|
458
|
+
<div
|
|
459
|
+
className={`relative mt-[6vh] flex w-full max-w-4xl flex-col overflow-hidden rounded-2xl bg-white shadow-2xl ring-1 ring-black ring-opacity-5 transition-all duration-200 ease-out ${
|
|
460
|
+
isShown ? "scale-100 opacity-100" : "scale-95 opacity-0"
|
|
461
|
+
}`}
|
|
462
|
+
style={{ maxHeight: "80vh" }}
|
|
463
|
+
onClick={(event: React.MouseEvent<HTMLDivElement>) => {
|
|
464
|
+
event.stopPropagation();
|
|
465
|
+
}}
|
|
466
|
+
>
|
|
467
|
+
{/* Search header */}
|
|
468
|
+
<div className="flex items-center gap-3 border-b border-gray-100 px-4 py-3">
|
|
469
|
+
<Icon
|
|
470
|
+
icon={IconProp.Search}
|
|
471
|
+
className="h-5 w-5 flex-shrink-0 text-gray-400"
|
|
472
|
+
/>
|
|
473
|
+
<input
|
|
474
|
+
ref={inputRef}
|
|
475
|
+
type="text"
|
|
476
|
+
role="combobox"
|
|
477
|
+
aria-expanded={true}
|
|
478
|
+
aria-controls="navbar-menu-listbox"
|
|
479
|
+
aria-activedescendant={
|
|
480
|
+
flatItems.length > 0
|
|
481
|
+
? `navbar-menu-option-${activeIndex}`
|
|
482
|
+
: undefined
|
|
483
|
+
}
|
|
484
|
+
aria-label={props.searchPlaceholder || "Search products"}
|
|
485
|
+
value={query}
|
|
486
|
+
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
487
|
+
setQuery(event.target.value);
|
|
488
|
+
}}
|
|
489
|
+
onKeyDown={handleKeyDown}
|
|
490
|
+
placeholder={props.searchPlaceholder || "Search…"}
|
|
491
|
+
className="flex-1 border-0 bg-transparent p-0 text-base text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-0"
|
|
492
|
+
/>
|
|
493
|
+
<kbd className="hidden items-center rounded border border-gray-200 bg-gray-50 px-1.5 py-0.5 text-xs font-medium text-gray-400 sm:inline-flex">
|
|
494
|
+
{shortcutLabel}
|
|
495
|
+
</kbd>
|
|
496
|
+
</div>
|
|
497
|
+
|
|
498
|
+
{/* Body */}
|
|
499
|
+
<div
|
|
500
|
+
id="navbar-menu-listbox"
|
|
501
|
+
role="listbox"
|
|
502
|
+
aria-label="Products"
|
|
503
|
+
className="flex-1 overflow-y-auto px-4 py-4"
|
|
504
|
+
>
|
|
505
|
+
{flatItems.length === 0 ? (
|
|
506
|
+
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
507
|
+
<Icon
|
|
508
|
+
icon={IconProp.Search}
|
|
509
|
+
className="mb-3 h-8 w-8 text-gray-300"
|
|
510
|
+
/>
|
|
511
|
+
<p className="text-sm text-gray-500">
|
|
512
|
+
{props.noResultsText || "No results found."}
|
|
513
|
+
</p>
|
|
514
|
+
{query && (
|
|
515
|
+
<p className="mt-1 text-xs text-gray-400">
|
|
516
|
+
“{query}”
|
|
517
|
+
</p>
|
|
518
|
+
)}
|
|
519
|
+
</div>
|
|
520
|
+
) : (
|
|
521
|
+
displayGroups.map((group: MenuGroup) => {
|
|
522
|
+
return (
|
|
523
|
+
<div key={group.category} className="mb-6 last:mb-1">
|
|
524
|
+
<h3 className="mb-2 px-1 text-xs font-semibold uppercase tracking-wider text-gray-500">
|
|
525
|
+
{group.category}
|
|
526
|
+
</h3>
|
|
527
|
+
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
|
|
528
|
+
{group.items.map((entry: IndexedItem) => {
|
|
529
|
+
const item: MoreMenuItem = entry.item;
|
|
530
|
+
const flatIndex: number = entry.flatIndex;
|
|
531
|
+
const isActive: boolean = flatIndex === activeIndex;
|
|
532
|
+
const isCurrent: boolean =
|
|
533
|
+
flatIndex === currentFlatIndex;
|
|
534
|
+
const colors: IconColorClasses =
|
|
535
|
+
ICON_COLOR_CLASSES[item.iconColor || "indigo"] ||
|
|
536
|
+
ICON_COLOR_CLASSES["indigo"]!;
|
|
537
|
+
return (
|
|
538
|
+
<div
|
|
539
|
+
key={entry.flatIndex}
|
|
540
|
+
id={`navbar-menu-option-${flatIndex}`}
|
|
541
|
+
role="option"
|
|
542
|
+
aria-selected={isActive}
|
|
543
|
+
ref={(element: HTMLDivElement | null) => {
|
|
544
|
+
cellRefs.current[flatIndex] = element;
|
|
545
|
+
}}
|
|
546
|
+
onMouseEnter={() => {
|
|
547
|
+
setActiveIndex(flatIndex);
|
|
548
|
+
}}
|
|
549
|
+
className="relative"
|
|
550
|
+
>
|
|
551
|
+
{isCurrent && (
|
|
552
|
+
<span
|
|
553
|
+
aria-hidden="true"
|
|
554
|
+
className="absolute right-2 top-2 z-10 h-2 w-2 rounded-full bg-indigo-500 ring-2 ring-white"
|
|
555
|
+
/>
|
|
556
|
+
)}
|
|
557
|
+
<Link
|
|
558
|
+
to={item.route}
|
|
559
|
+
onClick={() => {
|
|
560
|
+
selectItem(item);
|
|
561
|
+
}}
|
|
562
|
+
className={`group flex h-full flex-col items-center gap-2 rounded-xl p-3 text-center transition-all duration-150 ${
|
|
563
|
+
isActive
|
|
564
|
+
? "scale-[1.03] bg-indigo-50 shadow-sm ring-2 ring-indigo-400"
|
|
565
|
+
: isCurrent
|
|
566
|
+
? "bg-indigo-50/60 ring-1 ring-indigo-200 hover:bg-indigo-50"
|
|
567
|
+
: "hover:bg-gray-50"
|
|
568
|
+
}`}
|
|
569
|
+
>
|
|
570
|
+
<div
|
|
571
|
+
className={`flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-xl ${colors.bg} ring-1 ${colors.ring}`}
|
|
572
|
+
>
|
|
573
|
+
<Icon
|
|
574
|
+
icon={item.icon}
|
|
575
|
+
className={`h-6 w-6 transition-transform duration-150 group-hover:scale-110 ${colors.text}`}
|
|
576
|
+
/>
|
|
577
|
+
</div>
|
|
578
|
+
<div className="w-full">
|
|
579
|
+
<p className="text-sm font-medium text-gray-900">
|
|
580
|
+
{highlightMatch(item.title)}
|
|
581
|
+
</p>
|
|
582
|
+
<p className="mt-0.5 text-xs leading-snug text-gray-500 line-clamp-2">
|
|
583
|
+
{highlightMatch(item.description)}
|
|
584
|
+
</p>
|
|
585
|
+
</div>
|
|
586
|
+
</Link>
|
|
587
|
+
</div>
|
|
588
|
+
);
|
|
589
|
+
})}
|
|
590
|
+
</div>
|
|
591
|
+
</div>
|
|
592
|
+
);
|
|
593
|
+
})
|
|
594
|
+
)}
|
|
595
|
+
</div>
|
|
596
|
+
|
|
597
|
+
{/* Footer */}
|
|
598
|
+
{props.footer && (
|
|
599
|
+
<div className="flex items-center justify-between gap-4 border-t border-gray-100 bg-gray-50 px-4 py-3">
|
|
600
|
+
<Link
|
|
601
|
+
to={props.footer.link}
|
|
602
|
+
openInNewTab={true}
|
|
603
|
+
className="group flex min-w-0 items-center gap-3"
|
|
604
|
+
>
|
|
605
|
+
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-gray-100 ring-1 ring-gray-200 transition-all group-hover:bg-gray-200">
|
|
606
|
+
<Icon
|
|
607
|
+
icon={IconProp.GitHub}
|
|
608
|
+
className="h-4 w-4 text-gray-700"
|
|
609
|
+
/>
|
|
610
|
+
</div>
|
|
611
|
+
<div className="min-w-0 text-left">
|
|
612
|
+
<p className="truncate text-sm font-medium text-gray-900">
|
|
613
|
+
{props.footer.title}
|
|
614
|
+
</p>
|
|
615
|
+
<p className="truncate text-xs text-gray-500">
|
|
616
|
+
{props.footer.description}
|
|
617
|
+
</p>
|
|
618
|
+
</div>
|
|
619
|
+
</Link>
|
|
620
|
+
{props.keyboardHint && (
|
|
621
|
+
<div
|
|
622
|
+
aria-label={props.keyboardHint}
|
|
623
|
+
className="hidden flex-shrink-0 items-center gap-2 text-xs text-gray-400 md:flex"
|
|
624
|
+
>
|
|
625
|
+
<span className="flex items-center gap-1">
|
|
626
|
+
<kbd className={keycapClass}>↑</kbd>
|
|
627
|
+
<kbd className={keycapClass}>↓</kbd>
|
|
628
|
+
</span>
|
|
629
|
+
<kbd className={keycapClass}>↵</kbd>
|
|
630
|
+
<kbd className={keycapClass}>esc</kbd>
|
|
631
|
+
</div>
|
|
632
|
+
)}
|
|
633
|
+
</div>
|
|
634
|
+
)}
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
637
|
+
</div>
|
|
638
|
+
</div>
|
|
639
|
+
);
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
export default NavBarMenuModal;
|
|
@@ -19,6 +19,9 @@ const WorkflowStatusElement: FunctionComponent<ComponentProps> = (
|
|
|
19
19
|
if (props.status === WorkflowStatus.Scheduled) {
|
|
20
20
|
return <Pill color={Yellow} text="Scheduled" />;
|
|
21
21
|
}
|
|
22
|
+
if (props.status === WorkflowStatus.Waiting) {
|
|
23
|
+
return <Pill color={Yellow} text="Waiting" />;
|
|
24
|
+
}
|
|
22
25
|
if (props.status === WorkflowStatus.Error) {
|
|
23
26
|
return <Pill color={Red} text="Error" />;
|
|
24
27
|
}
|