@oneuptime/common 12.0.9 → 12.0.11
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/NetworkDevice.ts +44 -0
- package/Models/DatabaseModels/NetworkDeviceLink.ts +116 -0
- package/Models/DatabaseModels/RunnerJob.ts +17 -2
- package/Server/API/LlmProviderAPI.ts +8 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.ts +52 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +240 -0
- package/Server/Services/NetworkDeviceLinkService.ts +338 -2
- package/Server/Services/NetworkDeviceService.ts +265 -12
- package/Server/Services/RunnerJobService.ts +24 -1
- package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +19 -5
- package/Server/Types/Database/JSONColumnQuery.ts +42 -4
- package/Server/Utils/LLM/LLMService.ts +108 -19
- package/Server/Utils/Monitor/MonitorAlert.ts +22 -115
- package/Server/Utils/Monitor/MonitorIncident.ts +7 -337
- package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +27 -2
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +32 -0
- package/Server/Utils/Monitor/SeriesResourceLinker.ts +469 -0
- package/Server/Utils/SSRFProtection.ts +5 -4
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +186 -38
- package/Tests/App/AdminDashboard/AdminUsersBulkAddToProject.test.tsx +955 -0
- package/Tests/App/AdminDashboard/BulkAddUsersToProject.test.ts +778 -0
- package/Tests/App/AdminDashboard/BulkAddUsersToProjectModal.test.tsx +1051 -0
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +420 -112
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +81 -6
- package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +543 -0
- package/Tests/App/Dashboard/PayAsYouGoFreePlan.test.ts +125 -0
- package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +612 -0
- package/Tests/App/Dashboard/SloWidgetChartRendering.test.tsx +536 -0
- package/Tests/App/Dashboard/UserElementEmail.test.tsx +447 -0
- package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +284 -0
- package/Tests/Models/AnalyticsModels/CommonModel.test.ts +312 -0
- package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +250 -0
- package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +46 -18
- package/Tests/Server/Services/NetworkDeviceLabelRuleRun.test.ts +618 -0
- package/Tests/Server/Services/NetworkDeviceLinkParentValidation.test.ts +1043 -0
- package/Tests/Server/Services/NetworkSiteAssignmentRuleRun.test.ts +706 -0
- package/Tests/Server/Services/RunnerJobEnqueue.test.ts +412 -0
- package/Tests/Server/Types/Billing/PayAsYouGoMeteredPricing.test.ts +125 -0
- package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +508 -0
- package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +2 -2
- package/Tests/Server/Utils/AI/LLMServiceRetryPolicy.test.ts +450 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +556 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +547 -0
- package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +699 -0
- package/Tests/Server/Utils/SeriesResourceLabels.test.ts +55 -0
- package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +668 -0
- package/Tests/Types/API/URLMalformedTolerance.test.ts +301 -0
- package/Tests/Types/API/URLTelScheme.test.ts +261 -0
- package/Tests/Types/API/URLUnsupportedScheme.test.ts +148 -0
- package/Tests/Types/Billing/PayAsYouGoPricing.test.ts +64 -0
- package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +107 -0
- package/Tests/Types/Monitor/MonitorType.test.ts +62 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +184 -0
- package/Tests/Types/Runbook/RunbookStepType.test.ts +124 -0
- package/Tests/Types/Workflow/BaseModelComponents.test.ts +17 -0
- package/Tests/UI/Components/Button.test.tsx +279 -0
- package/Tests/UI/Components/Charts/ChartXAxisDataKey.test.tsx +256 -0
- package/Tests/UI/Components/Charts/DataPointUtil.test.ts +323 -0
- package/Tests/UI/Components/DictionaryValuePlaceholderAndSuggestions.test.tsx +595 -0
- package/Tests/UI/Components/Icon.test.tsx +57 -0
- package/Tests/UI/Components/List.test.tsx +20 -3
- package/Tests/UI/Components/LogsSavedViewsDropdown.test.tsx +304 -0
- package/Tests/UI/Components/LogsViewerClearSavedView.test.tsx +179 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +349 -1
- package/Tests/UI/Components/Pagination.test.tsx +721 -146
- package/Tests/UI/Components/PaginationIntegration.test.tsx +386 -0
- package/Tests/UI/Components/PaginationUtil.test.ts +417 -0
- package/Tests/UI/Components/TelemetrySavedViewsDropdown.test.tsx +343 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnControl.test.ts +289 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnOperators.test.ts +271 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRow.test.ts +119 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.test.ts +968 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +766 -0
- package/Tests/UI/Utils/RunNetworkRule.test.ts +320 -0
- package/Tests/Utils/APIRetryPolicy.test.ts +823 -0
- package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +502 -0
- package/Tests/Utils/Monitor/NetworkTopologyDeclaredHierarchy.test.ts +759 -0
- package/Tests/Utils/NetworkAutomation/RuleRunSummary.test.ts +286 -0
- package/Tests/Utils/Schema/BaseSchema.test.ts +651 -0
- package/Types/API/Protocol.ts +9 -0
- package/Types/API/URL.ts +286 -20
- package/Types/Billing/PayAsYouGoPricing.ts +47 -0
- package/Types/CustomField/CustomFieldType.ts +28 -0
- package/Types/Monitor/MonitorType.ts +11 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +121 -0
- package/Types/Runbook/RunbookStepType.ts +23 -0
- package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +7 -0
- package/UI/Components/Button/Button.tsx +14 -22
- package/UI/Components/Charts/Area/AreaChart.tsx +4 -2
- package/UI/Components/Charts/Bar/BarChart.tsx +4 -2
- package/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.ts +13 -0
- package/UI/Components/Charts/Line/LineChart.tsx +4 -2
- package/UI/Components/Charts/Types/XAxis/XAxis.ts +6 -0
- package/UI/Components/Charts/Utils/DataPoint.ts +15 -12
- package/UI/Components/Dictionary/Dictionary.tsx +4 -39
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/UI/Components/LogsViewer/LogsViewer.tsx +5 -1
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +8 -0
- package/UI/Components/LogsViewer/components/LogsPagination.tsx +29 -91
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +2 -0
- package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +66 -1
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +55 -0
- package/UI/Components/Pagination/Pagination.tsx +373 -328
- package/UI/Components/Pagination/PaginationUtil.ts +263 -0
- package/UI/Components/Pagination/Screenshots/README.md +15 -0
- package/UI/Components/Pagination/Screenshots/pagination-before-after.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-go-to-page-modal.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-long-list.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-mobile.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-telemetry.png +0 -0
- package/UI/Components/TelemetryViewer/components/SavedViewsDropdown.tsx +66 -1
- package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +38 -92
- package/UI/Components/TextArea/TextArea.tsx +3 -0
- package/UI/Components/Workflow/ArgumentsForm.tsx +18 -1
- package/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.tsx +177 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.tsx +243 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnControl.ts +255 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.tsx +178 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnOperators.ts +206 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnRow.ts +157 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.ts +517 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnValueInput.tsx +482 -0
- package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +131 -0
- package/UI/Components/Workflow/ColumnEditor/ModelRecordForm.tsx +163 -0
- package/UI/Components/Workflow/ModelColumnEditor.tsx +284 -100
- package/UI/Utils/NetworkAutomation/RunNetworkRule.ts +122 -0
- package/Utils/API.ts +243 -36
- package/Utils/Monitor/NetworkDeviceRoleUtil.ts +45 -0
- package/Utils/Monitor/NetworkTopologyUtil.ts +42 -2
- package/Utils/NetworkAutomation/RuleRunSummary.ts +147 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +45 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +115 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunnerJob.js +17 -2
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
- package/build/dist/Server/API/LlmProviderAPI.js +9 -1
- package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js +33 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +202 -0
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLinkService.js +253 -1
- package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +223 -12
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/RunnerJobService.js +20 -2
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +12 -5
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
- package/build/dist/Server/Types/Database/JSONColumnQuery.js +40 -4
- package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +78 -22
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -98
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +7 -254
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +14 -2
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +27 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +285 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -0
- package/build/dist/Server/Utils/SSRFProtection.js +5 -4
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +128 -35
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
- package/build/dist/Types/API/Protocol.js +9 -0
- package/build/dist/Types/API/Protocol.js.map +1 -1
- package/build/dist/Types/API/URL.js +241 -18
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/Billing/PayAsYouGoPricing.js +41 -0
- package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldType.js +26 -0
- package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +10 -0
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +39 -0
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookStepType.js +21 -0
- package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
- package/build/dist/UI/Components/Button/Button.js +10 -16
- package/build/dist/UI/Components/Button/Button.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +2 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +2 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js +12 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +2 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js +17 -10
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/Dictionary.js +4 -18
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +3 -3
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +5 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js +14 -44
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +20 -2
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +42 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +174 -199
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/Pagination/PaginationUtil.js +160 -0
- package/build/dist/UI/Components/Pagination/PaginationUtil.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js +20 -2
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +23 -47
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js +14 -2
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js +86 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js +119 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js +206 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js +57 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js +150 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js +67 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js +309 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js +204 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +66 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js +83 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +187 -68
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
- package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js +75 -0
- package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js.map +1 -0
- package/build/dist/Utils/API.js +175 -34
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +37 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +25 -3
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js +78 -0
- package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import
|
|
2
|
-
import BasicFormModal from "../FormModal/BasicFormModal";
|
|
3
|
-
import FormFieldSchemaType from "../Forms/Types/FormFieldSchemaType";
|
|
1
|
+
import Icon from "../Icon/Icon";
|
|
4
2
|
import IconProp from "../../../Types/Icon/IconProp";
|
|
3
|
+
import Modal from "../Modal/Modal";
|
|
4
|
+
import PaginationUtil, {
|
|
5
|
+
DefaultItemsOnPageOptions,
|
|
6
|
+
ItemRange,
|
|
7
|
+
PageWindowItem,
|
|
8
|
+
} from "./PaginationUtil";
|
|
5
9
|
import React, {
|
|
6
10
|
FunctionComponent,
|
|
11
|
+
KeyboardEvent,
|
|
7
12
|
ReactElement,
|
|
8
13
|
useEffect,
|
|
14
|
+
useId,
|
|
9
15
|
useState,
|
|
10
16
|
} from "react";
|
|
11
17
|
|
|
12
|
-
export interface PaginationNavigationItem {
|
|
13
|
-
pageNumber: number;
|
|
14
|
-
itemsOnPage: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
18
|
export interface ComponentProps {
|
|
18
19
|
currentPageNumber: number;
|
|
19
20
|
totalItemsCount: number;
|
|
@@ -28,7 +29,7 @@ export interface ComponentProps {
|
|
|
28
29
|
* Optional. Set by analytics list endpoints that skip COUNT(*) for
|
|
29
30
|
* performance — `totalItemsCount` is then only a lower bound, so
|
|
30
31
|
* the page-count math and "X of Y" label don't apply. When set,
|
|
31
|
-
* we render prev/next-only with no
|
|
32
|
+
* we render prev/next-only with no numbered pages.
|
|
32
33
|
*/
|
|
33
34
|
hasMore?: boolean | undefined;
|
|
34
35
|
/*
|
|
@@ -38,386 +39,430 @@ export interface ComponentProps {
|
|
|
38
39
|
* so the printed range is clamped to rows the page can really show.
|
|
39
40
|
*/
|
|
40
41
|
itemsOnCurrentPage?: number | undefined;
|
|
42
|
+
// Optional. Page sizes offered in the "rows per page" dropdown.
|
|
43
|
+
itemsOnPageOptions?: Array<number> | undefined;
|
|
44
|
+
/*
|
|
45
|
+
* Optional. Denser type and padding, for footers that sit under a
|
|
46
|
+
* compact data view (logs, traces) rather than under a full table.
|
|
47
|
+
*/
|
|
48
|
+
isCompact?: boolean | undefined;
|
|
49
|
+
// Optional. Appended to the container, for the caller's background/border.
|
|
50
|
+
className?: string | undefined;
|
|
51
|
+
/*
|
|
52
|
+
* Optional. Freezes every control without claiming the view is loading or
|
|
53
|
+
* broken — for callers that gate paging on something of their own.
|
|
54
|
+
*/
|
|
55
|
+
isDisabled?: boolean | undefined;
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
const Pagination: FunctionComponent<ComponentProps> = (
|
|
44
59
|
props: ComponentProps,
|
|
45
60
|
): ReactElement => {
|
|
61
|
+
/*
|
|
62
|
+
* Has-more mode: the count is a lower bound, so there is no last page to
|
|
63
|
+
* link to and no "of N" to print. Prev/next and the page size are all the
|
|
64
|
+
* control can honestly offer.
|
|
65
|
+
*/
|
|
46
66
|
const isHasMoreMode: boolean = props.hasMore !== undefined;
|
|
47
67
|
|
|
48
|
-
const
|
|
49
|
-
const
|
|
68
|
+
const uniqueId: string = useId();
|
|
69
|
+
const itemsOnPageSelectId: string = `pagination-items-on-page-${uniqueId}`;
|
|
70
|
+
const goToPageInputId: string = `pagination-go-to-page-${uniqueId}`;
|
|
71
|
+
|
|
72
|
+
const totalPageCount: number = PaginationUtil.getTotalPageCount(
|
|
73
|
+
props.totalItemsCount,
|
|
74
|
+
props.itemsOnPage,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const currentPageNumber: number = Math.max(
|
|
78
|
+
Math.floor(props.currentPageNumber) || 1,
|
|
79
|
+
1,
|
|
80
|
+
);
|
|
50
81
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
let maxPageNo: number =
|
|
54
|
-
props.totalItemsCount % props.itemsOnPage === 0
|
|
55
|
-
? props.totalItemsCount / props.itemsOnPage + 1
|
|
56
|
-
: props.totalItemsCount / props.itemsOnPage;
|
|
82
|
+
const isDisabled: boolean =
|
|
83
|
+
props.isLoading || props.isError || Boolean(props.isDisabled);
|
|
57
84
|
|
|
58
|
-
|
|
59
|
-
|
|
85
|
+
const isPreviousDisabled: boolean = currentPageNumber <= 1 || isDisabled;
|
|
86
|
+
/*
|
|
87
|
+
* An empty list is one page long, so the last-page check already covers
|
|
88
|
+
* it - there is nowhere forward to go from page 1 of 1.
|
|
89
|
+
*/
|
|
90
|
+
const isNextDisabled: boolean = isHasMoreMode
|
|
91
|
+
? !props.hasMore || isDisabled
|
|
92
|
+
: currentPageNumber >= totalPageCount || isDisabled;
|
|
93
|
+
|
|
94
|
+
const pageWindow: Array<PageWindowItem> = isHasMoreMode
|
|
95
|
+
? []
|
|
96
|
+
: PaginationUtil.getPageWindow(currentPageNumber, totalPageCount);
|
|
97
|
+
|
|
98
|
+
/*
|
|
99
|
+
* The collapsed gaps are the jump affordance: a gap is drawn exactly where
|
|
100
|
+
* pages the reader might want are not on the bar, so that is where asking
|
|
101
|
+
* for one belongs. Nothing is spent on the toolbar until they ask.
|
|
102
|
+
*/
|
|
103
|
+
const [isGoToPageModalVisible, setIsGoToPageModalVisible] =
|
|
104
|
+
useState<boolean>(false);
|
|
105
|
+
const [goToPageValue, setGoToPageValue] = useState<string>("");
|
|
106
|
+
|
|
107
|
+
// A page change from anywhere (prev/next, a number, the URL) closes the box.
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
setIsGoToPageModalVisible(false);
|
|
110
|
+
setGoToPageValue("");
|
|
111
|
+
}, [currentPageNumber]);
|
|
112
|
+
|
|
113
|
+
const itemRange: ItemRange = PaginationUtil.getItemRange({
|
|
114
|
+
currentPageNumber: currentPageNumber,
|
|
115
|
+
itemsOnPage: props.itemsOnPage,
|
|
116
|
+
totalItemsCount: props.totalItemsCount,
|
|
117
|
+
itemsOnCurrentPage: props.itemsOnCurrentPage,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const itemsOnPageOptions: Array<number> =
|
|
121
|
+
PaginationUtil.getItemsOnPageOptions(
|
|
122
|
+
props.itemsOnPage,
|
|
123
|
+
props.itemsOnPageOptions || DefaultItemsOnPageOptions,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const textSizeClassName: string = props.isCompact ? "text-xs" : "text-sm";
|
|
127
|
+
|
|
128
|
+
type NavigateToPageFunction = (pageNumber: number) => void;
|
|
129
|
+
|
|
130
|
+
const navigateToPage: NavigateToPageFunction = (pageNumber: number): void => {
|
|
131
|
+
if (isDisabled) {
|
|
132
|
+
return;
|
|
60
133
|
}
|
|
61
134
|
|
|
62
|
-
|
|
135
|
+
props.onNavigateToPage(pageNumber, props.itemsOnPage);
|
|
63
136
|
};
|
|
64
137
|
|
|
65
|
-
|
|
66
|
-
setMinAndMaxPageNumber();
|
|
67
|
-
}, []);
|
|
138
|
+
type SubmitGoToPageFunction = () => void;
|
|
68
139
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}, [props.totalItemsCount, props.itemsOnPage]);
|
|
140
|
+
const submitGoToPage: SubmitGoToPageFunction = (): void => {
|
|
141
|
+
const requestedPageNumber: number = Number(goToPageValue);
|
|
72
142
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
143
|
+
if (!requestedPageNumber) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
setIsGoToPageModalVisible(false);
|
|
148
|
+
navigateToPage(
|
|
149
|
+
PaginationUtil.clampPageNumber(requestedPageNumber, totalPageCount),
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
type GetSummaryFunction = () => string;
|
|
154
|
+
|
|
155
|
+
const getSummary: GetSummaryFunction = (): string => {
|
|
156
|
+
const pluralLabel: string = props.pluralLabel.toLowerCase();
|
|
157
|
+
const singularLabel: string = props.singularLabel.toLowerCase();
|
|
158
|
+
|
|
159
|
+
if (itemRange.isEmpty) {
|
|
160
|
+
return `No ${pluralLabel}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const rangeText: string =
|
|
164
|
+
itemRange.firstItemNumber === itemRange.lastItemNumber
|
|
165
|
+
? itemRange.firstItemNumber.toLocaleString()
|
|
166
|
+
: `${itemRange.firstItemNumber.toLocaleString()}-${itemRange.lastItemNumber.toLocaleString()}`;
|
|
167
|
+
|
|
168
|
+
if (isHasMoreMode) {
|
|
169
|
+
/*
|
|
170
|
+
* The count cannot be printed here — it is a lower bound that also
|
|
171
|
+
* includes the probe row the payload dropped. The trailing "+" is all
|
|
172
|
+
* that can be said about what comes after this page.
|
|
173
|
+
*/
|
|
174
|
+
return `Showing ${rangeText}${props.hasMore ? "+" : ""} ${pluralLabel}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return `Showing ${rangeText} of ${props.totalItemsCount.toLocaleString()} ${
|
|
178
|
+
props.totalItemsCount === 1 ? singularLabel : pluralLabel
|
|
179
|
+
}`;
|
|
180
|
+
};
|
|
86
181
|
|
|
87
182
|
/*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
183
|
+
* A minimum width rather than padding alone, so a row of single-digit
|
|
184
|
+
* pages does not read as a row of narrower buttons than the two- and
|
|
185
|
+
* three-digit ones beside it.
|
|
90
186
|
*/
|
|
91
|
-
const
|
|
92
|
-
props.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
187
|
+
const pageButtonBaseClassName: string = `relative inline-flex items-center justify-center border border-gray-300 font-medium transition-colors focus:z-20 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-500 ${
|
|
188
|
+
props.isCompact
|
|
189
|
+
? "min-w-8 px-2 py-1.5 text-xs"
|
|
190
|
+
: "min-w-9 px-2 py-2 text-sm"
|
|
191
|
+
}`;
|
|
192
|
+
|
|
193
|
+
type GetArrowButtonClassNameFunction = (isButtonDisabled: boolean) => string;
|
|
194
|
+
|
|
195
|
+
const getArrowButtonClassName: GetArrowButtonClassNameFunction = (
|
|
196
|
+
isButtonDisabled: boolean,
|
|
197
|
+
): string => {
|
|
198
|
+
return `${pageButtonBaseClassName} ${
|
|
199
|
+
isButtonDisabled
|
|
200
|
+
? "cursor-not-allowed bg-gray-50 text-gray-300"
|
|
201
|
+
: "cursor-pointer bg-white text-gray-500 hover:bg-gray-50 hover:text-gray-700"
|
|
202
|
+
}`;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
type GetPageNumberButtonFunction = (pageNumber: number) => ReactElement;
|
|
206
|
+
|
|
207
|
+
const getPageNumberButton: GetPageNumberButtonFunction = (
|
|
208
|
+
pageNumber: number,
|
|
209
|
+
): ReactElement => {
|
|
210
|
+
const isCurrentPage: boolean = pageNumber === currentPageNumber;
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<li className="hidden sm:flex" key={`page-${pageNumber}`}>
|
|
214
|
+
<button
|
|
215
|
+
type="button"
|
|
216
|
+
data-testid={`pagination-page-${pageNumber}`}
|
|
217
|
+
aria-label={
|
|
218
|
+
isCurrentPage ? `Page ${pageNumber}` : `Go to page ${pageNumber}`
|
|
219
|
+
}
|
|
220
|
+
aria-current={isCurrentPage ? "page" : undefined}
|
|
221
|
+
disabled={isDisabled}
|
|
222
|
+
onClick={() => {
|
|
223
|
+
if (!isCurrentPage) {
|
|
224
|
+
navigateToPage(pageNumber);
|
|
225
|
+
}
|
|
226
|
+
}}
|
|
227
|
+
className={`${pageButtonBaseClassName} ${
|
|
228
|
+
isCurrentPage
|
|
229
|
+
? "z-10 border-indigo-500 bg-indigo-50 text-indigo-600"
|
|
230
|
+
: "bg-white text-gray-600 hover:bg-gray-50"
|
|
231
|
+
} ${
|
|
232
|
+
isDisabled && !isCurrentPage
|
|
233
|
+
? "cursor-not-allowed text-gray-300"
|
|
234
|
+
: "cursor-pointer"
|
|
235
|
+
}`}
|
|
236
|
+
>
|
|
237
|
+
{pageNumber.toLocaleString()}
|
|
238
|
+
</button>
|
|
239
|
+
</li>
|
|
240
|
+
);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
type GetEllipsisFunction = (key: string) => ReactElement;
|
|
244
|
+
|
|
245
|
+
const getEllipsis: GetEllipsisFunction = (key: string): ReactElement => {
|
|
246
|
+
return (
|
|
247
|
+
<li className="hidden sm:flex" key={key}>
|
|
248
|
+
<button
|
|
249
|
+
type="button"
|
|
250
|
+
data-testid={`pagination-${key}`}
|
|
251
|
+
aria-label="Go to a page in between"
|
|
252
|
+
title="Go to page"
|
|
253
|
+
disabled={isDisabled}
|
|
254
|
+
onClick={() => {
|
|
255
|
+
if (!isDisabled) {
|
|
256
|
+
setIsGoToPageModalVisible(true);
|
|
257
|
+
}
|
|
258
|
+
}}
|
|
259
|
+
className={`${pageButtonBaseClassName} bg-white text-gray-400 ${
|
|
260
|
+
isDisabled
|
|
261
|
+
? "cursor-not-allowed"
|
|
262
|
+
: "cursor-pointer hover:bg-gray-50 hover:text-gray-600"
|
|
263
|
+
}`}
|
|
264
|
+
>
|
|
265
|
+
…
|
|
266
|
+
</button>
|
|
267
|
+
</li>
|
|
268
|
+
);
|
|
269
|
+
};
|
|
103
270
|
|
|
104
271
|
return (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
{lastOnPage < firstOnPage
|
|
120
|
-
? `No ${props.pluralLabel.toLowerCase()}.`
|
|
121
|
-
: `Showing ${firstOnPage} to ${lastOnPage}${
|
|
122
|
-
props.hasMore ? "+" : ""
|
|
123
|
-
} ${props.pluralLabel.toLowerCase()}.`}
|
|
124
|
-
</span>
|
|
125
|
-
)}
|
|
126
|
-
{!props.isLoading && !isHasMoreMode && (
|
|
127
|
-
<span>
|
|
128
|
-
{props.totalItemsCount.toLocaleString()}{" "}
|
|
129
|
-
{props.totalItemsCount > 1
|
|
130
|
-
? props.pluralLabel
|
|
131
|
-
: props.singularLabel}{" "}
|
|
132
|
-
{`in total. Showing ${
|
|
133
|
-
props.itemsOnPage * (props.currentPageNumber - 1) + 1
|
|
134
|
-
} to ${
|
|
135
|
-
props.itemsOnPage * props.currentPageNumber
|
|
136
|
-
} on this page.`}
|
|
137
|
-
</span>
|
|
138
|
-
)}
|
|
272
|
+
<>
|
|
273
|
+
<nav
|
|
274
|
+
className={`flex flex-col gap-3 border-t border-gray-200 bg-white px-4 py-3 text-left sm:flex-row sm:items-center sm:justify-between ${
|
|
275
|
+
props.className || ""
|
|
276
|
+
}`}
|
|
277
|
+
data-testid={props.dataTestId}
|
|
278
|
+
aria-label={`Pagination for ${props.pluralLabel}`}
|
|
279
|
+
>
|
|
280
|
+
<p
|
|
281
|
+
className={`${textSizeClassName} shrink-0 whitespace-nowrap text-gray-500`}
|
|
282
|
+
data-testid="pagination-summary"
|
|
283
|
+
aria-live="polite"
|
|
284
|
+
>
|
|
285
|
+
{props.isLoading ? "Loading…" : getSummary()}
|
|
139
286
|
</p>
|
|
140
|
-
</div>
|
|
141
|
-
|
|
142
|
-
{/* Desktop layout: All controls together on right */}
|
|
143
|
-
<div className="hidden md:flex">
|
|
144
|
-
<div className="inline-flex -space-x-px rounded-md shadow-sm">
|
|
145
|
-
<div className="my-2">
|
|
146
|
-
<Button
|
|
147
|
-
dataTestId="show-pagination-modal-button"
|
|
148
|
-
className="mx-2 my-2"
|
|
149
|
-
buttonSize={ButtonSize.ExtraSmall}
|
|
150
|
-
icon={IconProp.AdjustmentHorizontal}
|
|
151
|
-
buttonStyle={ButtonStyleType.ICON_LIGHT}
|
|
152
|
-
ariaLabel="Open pagination settings"
|
|
153
|
-
onClick={() => {
|
|
154
|
-
setShowPaginationModel(true);
|
|
155
|
-
}}
|
|
156
|
-
/>
|
|
157
|
-
</div>
|
|
158
287
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
288
|
+
<div className="flex flex-wrap items-center justify-start gap-x-3 gap-y-3 sm:justify-end">
|
|
289
|
+
<div className="flex items-center gap-2">
|
|
290
|
+
<label
|
|
291
|
+
htmlFor={itemsOnPageSelectId}
|
|
292
|
+
className={`${textSizeClassName} whitespace-nowrap text-gray-500`}
|
|
293
|
+
>
|
|
294
|
+
Rows per page
|
|
295
|
+
</label>
|
|
296
|
+
<div className="relative">
|
|
297
|
+
<select
|
|
298
|
+
id={itemsOnPageSelectId}
|
|
299
|
+
data-testid="pagination-items-on-page-select"
|
|
300
|
+
value={props.itemsOnPage}
|
|
301
|
+
disabled={isDisabled}
|
|
302
|
+
onChange={(event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
303
|
+
const newItemsOnPage: number = Number(event.target.value);
|
|
167
304
|
|
|
168
|
-
if (
|
|
169
|
-
|
|
305
|
+
if (!newItemsOnPage || newItemsOnPage === props.itemsOnPage) {
|
|
306
|
+
return;
|
|
170
307
|
}
|
|
171
308
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
309
|
+
/*
|
|
310
|
+
* Row 400 of the old page size is not row 400 of the new one,
|
|
311
|
+
* so resizing the page returns to the top of the list rather
|
|
312
|
+
* than to an offset that may no longer exist.
|
|
313
|
+
*/
|
|
314
|
+
props.onNavigateToPage(1, newItemsOnPage);
|
|
178
315
|
}}
|
|
179
|
-
className={`
|
|
180
|
-
isPreviousDisabled
|
|
181
|
-
? "bg-gray-100 cursor-not-allowed"
|
|
182
|
-
: "hover:bg-gray-50 cursor-pointer"
|
|
183
|
-
}`}
|
|
316
|
+
className={`cursor-pointer appearance-none rounded-md border border-gray-300 bg-white py-1.5 pl-2.5 pr-8 font-medium text-gray-700 shadow-sm transition-colors hover:bg-gray-50 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-400 ${textSizeClassName}`}
|
|
184
317
|
>
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
318
|
+
{itemsOnPageOptions.map((option: number) => {
|
|
319
|
+
return (
|
|
320
|
+
<option key={option} value={option}>
|
|
321
|
+
{option}
|
|
322
|
+
</option>
|
|
323
|
+
);
|
|
324
|
+
})}
|
|
325
|
+
</select>
|
|
326
|
+
<span className="pointer-events-none absolute inset-y-0 right-2 flex items-center">
|
|
327
|
+
<Icon
|
|
328
|
+
icon={IconProp.ChevronDown}
|
|
329
|
+
className="h-4 w-4 text-gray-400"
|
|
330
|
+
/>
|
|
331
|
+
</span>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
|
|
335
|
+
<ul
|
|
336
|
+
className="isolate inline-flex -space-x-px rounded-md shadow-sm"
|
|
337
|
+
role="list"
|
|
338
|
+
>
|
|
188
339
|
<li className="flex">
|
|
189
340
|
<button
|
|
190
341
|
type="button"
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
aria-label=
|
|
194
|
-
data-testid="current-page-link"
|
|
195
|
-
className={` z-10 inline-flex items-center border border-x-0 border-gray-300 hover:bg-gray-50 px-4 py-2 text-sm font-medium text-text-600 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-500 ${
|
|
196
|
-
isCurrentPageButtonDisabled ? "bg-gray-100" : ""
|
|
197
|
-
}`}
|
|
342
|
+
data-testid="pagination-previous-button"
|
|
343
|
+
disabled={isPreviousDisabled}
|
|
344
|
+
aria-label="Go to previous page"
|
|
198
345
|
onClick={() => {
|
|
199
|
-
if (!
|
|
200
|
-
|
|
346
|
+
if (!isPreviousDisabled) {
|
|
347
|
+
navigateToPage(currentPageNumber - 1);
|
|
201
348
|
}
|
|
202
349
|
}}
|
|
350
|
+
className={`${getArrowButtonClassName(
|
|
351
|
+
isPreviousDisabled,
|
|
352
|
+
)} rounded-l-md`}
|
|
203
353
|
>
|
|
204
|
-
<
|
|
354
|
+
<Icon icon={IconProp.ChevronLeft} className="h-4 w-4" />
|
|
205
355
|
</button>
|
|
206
356
|
</li>
|
|
207
|
-
<li className="flex">
|
|
208
|
-
<button
|
|
209
|
-
type="button"
|
|
210
|
-
disabled={isNextDisabled}
|
|
211
|
-
aria-label="Go to next page"
|
|
212
|
-
onClick={() => {
|
|
213
|
-
let currentPageNumber: number = props.currentPageNumber;
|
|
214
|
-
|
|
215
|
-
if (typeof currentPageNumber === "string") {
|
|
216
|
-
currentPageNumber = parseInt(currentPageNumber);
|
|
217
|
-
}
|
|
218
357
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
? "bg-gray-100 cursor-not-allowed"
|
|
229
|
-
: " hover:bg-gray-50 cursor-pointer"
|
|
230
|
-
}`}
|
|
358
|
+
{/*
|
|
359
|
+
* The numbered list is desktop-only; narrow screens get this
|
|
360
|
+
* single indicator instead so the control never wraps into a
|
|
361
|
+
* second row of buttons.
|
|
362
|
+
*/}
|
|
363
|
+
<li className="flex sm:hidden">
|
|
364
|
+
<span
|
|
365
|
+
data-testid="pagination-current-page-indicator"
|
|
366
|
+
className={`${pageButtonBaseClassName} bg-white text-gray-600`}
|
|
231
367
|
>
|
|
232
|
-
|
|
233
|
-
|
|
368
|
+
{isHasMoreMode
|
|
369
|
+
? `Page ${currentPageNumber.toLocaleString()}`
|
|
370
|
+
: `Page ${currentPageNumber.toLocaleString()} of ${totalPageCount.toLocaleString()}`}
|
|
371
|
+
</span>
|
|
234
372
|
</li>
|
|
235
|
-
</ul>
|
|
236
|
-
</div>
|
|
237
|
-
</div>
|
|
238
|
-
|
|
239
|
-
{/* Mobile layout: Navigate button on left, pagination controls on right */}
|
|
240
|
-
<div className="md:hidden my-2">
|
|
241
|
-
<Button
|
|
242
|
-
dataTestId="show-pagination-modal-button-mobile"
|
|
243
|
-
className="my-2"
|
|
244
|
-
buttonSize={ButtonSize.ExtraSmall}
|
|
245
|
-
icon={IconProp.AdjustmentHorizontal}
|
|
246
|
-
buttonStyle={ButtonStyleType.ICON_LIGHT}
|
|
247
|
-
ariaLabel="Open pagination settings"
|
|
248
|
-
onClick={() => {
|
|
249
|
-
setShowPaginationModel(true);
|
|
250
|
-
}}
|
|
251
|
-
/>
|
|
252
|
-
</div>
|
|
253
373
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
374
|
+
{isHasMoreMode && (
|
|
375
|
+
<li className="hidden sm:flex">
|
|
376
|
+
<span
|
|
377
|
+
data-testid="pagination-current-page-indicator-desktop"
|
|
378
|
+
aria-current="page"
|
|
379
|
+
className={`${pageButtonBaseClassName} z-10 border-indigo-500 bg-indigo-50 text-indigo-600`}
|
|
380
|
+
>
|
|
381
|
+
{`Page ${currentPageNumber.toLocaleString()}`}
|
|
382
|
+
</span>
|
|
383
|
+
</li>
|
|
384
|
+
)}
|
|
264
385
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
386
|
+
{pageWindow.map((item: PageWindowItem) => {
|
|
387
|
+
if (typeof item === "number") {
|
|
388
|
+
return getPageNumberButton(item);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return getEllipsis(item);
|
|
392
|
+
})}
|
|
268
393
|
|
|
269
|
-
if (props.onNavigateToPage && !isPreviousDisabled) {
|
|
270
|
-
props.onNavigateToPage(
|
|
271
|
-
currentPageNumber - 1,
|
|
272
|
-
props.itemsOnPage,
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
}}
|
|
276
|
-
className={` inline-flex items-center rounded-l-md border border-gray-300 bg-white px-2 py-2 text-sm font-medium text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-500 ${
|
|
277
|
-
isPreviousDisabled
|
|
278
|
-
? "bg-gray-100 cursor-not-allowed"
|
|
279
|
-
: "hover:bg-gray-50 cursor-pointer"
|
|
280
|
-
}`}
|
|
281
|
-
>
|
|
282
|
-
<span className="page-link">Previous</span>
|
|
283
|
-
</button>
|
|
284
|
-
</li>
|
|
285
|
-
<li className="flex">
|
|
286
|
-
<button
|
|
287
|
-
type="button"
|
|
288
|
-
disabled={isCurrentPageButtonDisabled}
|
|
289
|
-
aria-current="page"
|
|
290
|
-
aria-label={`Page ${props.currentPageNumber}, current page. Select to jump to another page.`}
|
|
291
|
-
data-testid="current-page-link-mobile"
|
|
292
|
-
className={` z-10 inline-flex items-center border border-x-0 border-gray-300 hover:bg-gray-50 px-4 py-2 text-sm font-medium text-text-600 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-indigo-500 ${
|
|
293
|
-
isCurrentPageButtonDisabled ? "bg-gray-100" : ""
|
|
294
|
-
}`}
|
|
295
|
-
onClick={() => {
|
|
296
|
-
if (!isHasMoreMode) {
|
|
297
|
-
setShowPaginationModel(true);
|
|
298
|
-
}
|
|
299
|
-
}}
|
|
300
|
-
>
|
|
301
|
-
<span>{props.currentPageNumber}</span>
|
|
302
|
-
</button>
|
|
303
|
-
</li>
|
|
304
394
|
<li className="flex">
|
|
305
395
|
<button
|
|
306
396
|
type="button"
|
|
397
|
+
data-testid="pagination-next-button"
|
|
307
398
|
disabled={isNextDisabled}
|
|
308
399
|
aria-label="Go to next page"
|
|
309
400
|
onClick={() => {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (typeof currentPageNumber === "string") {
|
|
313
|
-
currentPageNumber = parseInt(currentPageNumber);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (props.onNavigateToPage && !isNextDisabled) {
|
|
317
|
-
props.onNavigateToPage(
|
|
318
|
-
currentPageNumber + 1,
|
|
319
|
-
props.itemsOnPage,
|
|
320
|
-
);
|
|
401
|
+
if (!isNextDisabled) {
|
|
402
|
+
navigateToPage(currentPageNumber + 1);
|
|
321
403
|
}
|
|
322
404
|
}}
|
|
323
|
-
className={
|
|
324
|
-
isNextDisabled
|
|
325
|
-
|
|
326
|
-
: " hover:bg-gray-50 cursor-pointer"
|
|
327
|
-
}`}
|
|
405
|
+
className={`${getArrowButtonClassName(
|
|
406
|
+
isNextDisabled,
|
|
407
|
+
)} rounded-r-md`}
|
|
328
408
|
>
|
|
329
|
-
<
|
|
409
|
+
<Icon icon={IconProp.ChevronRight} className="h-4 w-4" />
|
|
330
410
|
</button>
|
|
331
411
|
</li>
|
|
332
412
|
</ul>
|
|
333
413
|
</div>
|
|
334
|
-
</
|
|
414
|
+
</nav>
|
|
335
415
|
|
|
336
|
-
{
|
|
337
|
-
<
|
|
338
|
-
|
|
339
|
-
|
|
416
|
+
{isGoToPageModalVisible && (
|
|
417
|
+
<Modal
|
|
418
|
+
title="Go to page"
|
|
419
|
+
description={`This list has ${totalPageCount.toLocaleString()} pages. Enter the one you want to see.`}
|
|
420
|
+
submitButtonText="Go"
|
|
421
|
+
closeButtonText="Cancel"
|
|
422
|
+
disableSubmitButton={goToPageValue === ""}
|
|
423
|
+
onSubmit={submitGoToPage}
|
|
340
424
|
onClose={() => {
|
|
341
|
-
|
|
425
|
+
setIsGoToPageModalVisible(false);
|
|
426
|
+
setGoToPageValue("");
|
|
342
427
|
}}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
428
|
+
>
|
|
429
|
+
<div>
|
|
430
|
+
<label
|
|
431
|
+
htmlFor={goToPageInputId}
|
|
432
|
+
className="block text-sm font-medium text-gray-700"
|
|
433
|
+
>
|
|
434
|
+
Page number
|
|
435
|
+
</label>
|
|
436
|
+
<input
|
|
437
|
+
id={goToPageInputId}
|
|
438
|
+
data-testid="pagination-go-to-page-input"
|
|
439
|
+
type="number"
|
|
440
|
+
inputMode="numeric"
|
|
441
|
+
autoFocus={true}
|
|
442
|
+
min={1}
|
|
443
|
+
max={totalPageCount}
|
|
444
|
+
value={goToPageValue}
|
|
445
|
+
placeholder={`1-${totalPageCount}`}
|
|
446
|
+
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
447
|
+
setGoToPageValue(event.target.value);
|
|
448
|
+
}}
|
|
346
449
|
/*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
* size.
|
|
450
|
+
* The modal's footer button is not a form submit, so Enter is
|
|
451
|
+
* wired up by hand - typing a number and pressing Enter is how
|
|
452
|
+
* anyone who reached for this box expects it to end.
|
|
351
453
|
*/
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
itemsOnPage: props.itemsOnPage,
|
|
363
|
-
},
|
|
364
|
-
fields: [
|
|
365
|
-
...(isHasMoreMode
|
|
366
|
-
? []
|
|
367
|
-
: [
|
|
368
|
-
{
|
|
369
|
-
title: "Page Number",
|
|
370
|
-
description: `You can enter page numbers from ${
|
|
371
|
-
minPageNumber !== maxPageNumber
|
|
372
|
-
? minPageNumber + " to " + maxPageNumber
|
|
373
|
-
: minPageNumber
|
|
374
|
-
}. Please enter it here:`,
|
|
375
|
-
field: {
|
|
376
|
-
pageNumber: true,
|
|
377
|
-
},
|
|
378
|
-
disabled: minPageNumber === maxPageNumber,
|
|
379
|
-
placeholder: "1",
|
|
380
|
-
required: true,
|
|
381
|
-
validation: {
|
|
382
|
-
minValue: minPageNumber,
|
|
383
|
-
maxValue: maxPageNumber,
|
|
384
|
-
},
|
|
385
|
-
fieldType: FormFieldSchemaType.PositiveNumber,
|
|
386
|
-
},
|
|
387
|
-
]),
|
|
388
|
-
{
|
|
389
|
-
title: `${props.pluralLabel} on Page `,
|
|
390
|
-
description: `Enter the number of ${props.pluralLabel.toLowerCase()} you would like to see on the page:`,
|
|
391
|
-
field: {
|
|
392
|
-
itemsOnPage: true,
|
|
393
|
-
},
|
|
394
|
-
placeholder: "10",
|
|
395
|
-
required: true,
|
|
396
|
-
fieldType: FormFieldSchemaType.Dropdown,
|
|
397
|
-
dropdownOptions: [
|
|
398
|
-
{
|
|
399
|
-
value: 10,
|
|
400
|
-
label: "10",
|
|
401
|
-
},
|
|
402
|
-
{
|
|
403
|
-
value: 20,
|
|
404
|
-
label: "20",
|
|
405
|
-
},
|
|
406
|
-
{
|
|
407
|
-
value: 25,
|
|
408
|
-
label: "25",
|
|
409
|
-
},
|
|
410
|
-
{
|
|
411
|
-
value: 50,
|
|
412
|
-
label: "50",
|
|
413
|
-
},
|
|
414
|
-
],
|
|
415
|
-
},
|
|
416
|
-
],
|
|
417
|
-
}}
|
|
418
|
-
/>
|
|
454
|
+
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => {
|
|
455
|
+
if (event.key === "Enter") {
|
|
456
|
+
event.preventDefault();
|
|
457
|
+
submitGoToPage();
|
|
458
|
+
}
|
|
459
|
+
}}
|
|
460
|
+
className="mt-2 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 shadow-sm placeholder:text-gray-400 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
|
461
|
+
/>
|
|
462
|
+
</div>
|
|
463
|
+
</Modal>
|
|
419
464
|
)}
|
|
420
|
-
|
|
465
|
+
</>
|
|
421
466
|
);
|
|
422
467
|
};
|
|
423
468
|
|