Repository: Sage/carbon (ts)

Issue:
Pager - disable free-text page number navigation
### Desired behaviour

The ability to type in the desired page number should be optional (i.e. input containing current page can be disabled)

### Current behaviour

The Pager allows you to navigate to pages which may not necessarily exist for the use case implemented. Further explanation from Slack discussion on the matter:

`We make our first call without tokens, which is interpreted by the database as "get page 1". If there are more records than the page size you've asked for, you'll get a token back for the next X records. Page two would then return a token to get you the previous set of results (and another, separate token if there are records beyond the ones shown - we work with a previousToken and nextToken approach). These tokens control Carbon's navigation buttons, but asking the database for "page 2" couldn't work as we have know way of knowing what the token for page 2's records would be - it's all calculated server-side.`

### Suggested Solution

Allow me to pass a prop to the pager that disables the input and replaces it with a simple text label (`Page X of Y`)

### CodeSandbox or Storybook URL

_No response_

### Anything else we should know?

_No response_

### Confidentiality

- [X] I confirm there is no confidential or commercially sensitive information included.

Pull request:
feat(pager): surface interactivePageNumber prop to ensure selecting page number is optional
fix #5674

### Proposed behaviour

<!--
A clear and concise description of what changes this PR makes.

If applicable, add screenshots of a codesandbox to help explain your request. You can paste these directly into GitHub.

Please DO NOT share screenshots or the source code of your project.

You can create a codesandbox to show the behaviour before/after this pull request by forking this template https://codesandbox.io/s/carbon-quickstart-j5pb2

If you include a CodeSandbox link, the bot will fork it with the new built version of carbon.
If you have a commit that includes fixes #XXXX and issue #XXXX has a CodeSandbox link in the body, the bot will fork
it with the new built version of carbon.
-->

This PR will surface the `interactivePageNumber` prop on the `pager` component, and internal component `pager-navigation`, when `false` one full label will be rendered which includes the current page and total page count all in one string. This will give consumers flexibility of the interactivity of the page number, and limit the means end-users use to navigate the pager.

<img width="1050" alt="Screenshot 2023-03-24 at 14 43 21" src="https://user-images.githubusercontent.com/114918852/227557103-4975267d-5483-4065-abd6-abd896ae5a26.png">


### Current behaviour

<!--
A clear and concise description of the behaviour before this change.

If applicable, add screenshots. You can paste these directly into GitHub.
-->

Currently, the page number is returned as an interactive number input, with two labels either side. 

![Screenshot 2023-03-22 at 15 47 57](https://user-images.githubusercontent.com/114918852/226961088-5c699aeb-9816-407c-a73e-4a5305b9021f.png)


### Checklist

<!-- Each PR should include the following -->

- [x] Commits follow our style guide
- [x] Related issues linked in commit messages if required
- [x] Screenshots are included in the PR if useful
- [ ] All themes are supported if required
- [x] Unit tests added or updated if required
- [x] Cypress automation tests added or updated if required
- [x] Storybook added or updated if required
- [x] Translations added or updated (including creating or amending translation keys table in storybook) if required
- [ ] Typescript `d.ts` file added or updated if required
- [ ] Related docs have been updated if required

#### QA

- [ ] Tested in CodeSandbox/storybook
- [ ] Add new Cypress test coverage if required
- [ ] Carbon implementation matches Design System/designs
- [ ] UI Tests GitHub check reviewed if required

### Additional context

<!-- Add any other context or links about the pull request here. -->

### Testing instructions

<!-- How can a reviewer test this PR? -->

The following CodeSandbox is an example of the broken behaviour.
You can see the new behaviour by looking at the version in the comment by `codesandbox[bot]`.

<!-- Add CodeSandbox here -->


Patch:
diff --git a/cypress/locators/pager/index.js b/cypress/locators/pager/index.js
index 6d2f20812..0faa31d7d 100644
--- a/cypress/locators/pager/index.js
+++ b/cypress/locators/pager/index.js
@@ -10,6 +10,7 @@ import {
   SHOW_LABEL_BEFORE,
   PAGE_SIZE_LABEL_AFTER,
   CURRENT_PAGE,
+  CURRENT_PAGE_LABEL,
 } from "./locators";
 import { COMMMON_DATA_ELEMENT_INPUT } from "../locators";
 import { getDataElementByValue } from "..";
@@ -21,6 +22,7 @@ export const pageSelect = () =>
 export const pageSelectElement = () => cy.get(PAGE_SELECT);
 export const maxPages = () => cy.get(MAX_PAGES);
 export const currentPageWrapper = () => cy.get(CURRENT_PAGE);
+export const currentPageLabelWrapper = () => cy.get(CURRENT_PAGE_LABEL);
 export const currentPageInput = () => cy.get(PAGER_SUMMARY).find("input");
 export const previousArrow = () =>
   getDataElementByValue(`${COMMON_PART_OF_PAGER_LINK}${PAGER_PREVIOUS_ARROW}`);
diff --git a/cypress/locators/pager/locators.js b/cypress/locators/pager/locators.js
index 20568ddf1..ef64a7fb5 100644
--- a/cypress/locators/pager/locators.js
+++ b/cypress/locators/pager/locators.js
@@ -11,3 +11,4 @@ export const COMMON_PART_OF_PAGER_LINK = "pager-link-";
 export const SHOW_LABEL_BEFORE = "Show";
 export const PAGE_SIZE_LABEL_AFTER = "items";
 export const CURRENT_PAGE = '[data-element="current-page"]';
+export const CURRENT_PAGE_LABEL = '[data-element="current-page-label"]';
diff --git a/src/components/pager/__internal__/pager-navigation.component.tsx b/src/components/pager/__internal__/pager-navigation.component.tsx
index e54d6f64e..d5d37745a 100644
--- a/src/components/pager/__internal__/pager-navigation.component.tsx
+++ b/src/components/pager/__internal__/pager-navigation.component.tsx
@@ -4,6 +4,7 @@ import {
   StyledPagerNavigation,
   StyledPagerNavInner,
   StyledPagerNoSelect,
+  StyledPagerNavLabel,
 } from "../pager.style";
 import NumberInput from "../../number";
 import Events from "../../../__internal__/utils/helpers/events";
@@ -52,6 +53,8 @@ export interface PagerNavigationProps {
   showPreviousAndNextButtons?: boolean;
   /** Should the page count input be shown */
   showPageCount?: boolean;
+  /** If true, page number navigation will be changed to a non-interactive label */
+  interactivePageNumber?: boolean;
   /** If true, sets css property visibility: hidden on all disabled elements  */
   hideDisabledElements?: boolean;
 }
@@ -69,11 +72,15 @@ const PagerNavigation = ({
   showFirstAndLastButtons = true,
   showPreviousAndNextButtons = true,
   showPageCount = true,
+  interactivePageNumber = true,
   hideDisabledElements = false,
 }: PagerNavigationProps) => {
   const l = useLocale();
   const guid = useRef(createGuid());
   const currentPageId = `Pager_${guid.current}`;
+  const navLabelString = `${l.pager.pageX()} ${currentPage.toString()} ${l.pager.ofY(
+    pageCount
+  )}`;
   const hasOnePage = pageCount <= 1;
   const hasTwoPages = pageCount === 2;
   const pagerNavigationProps = {
@@ -161,24 +168,33 @@ const PagerNavigation = ({
   return (
     <StyledPagerNavigation>
       {!hasOnePage && renderButtonsBeforeCount()}
-      {showPageCount && (
-        <StyledPagerNavInner>
-          <label htmlFor={currentPageId}>
-            <StyledPagerNoSelect>{l.pager.pageX()}</StyledPagerNoSelect>
-          </label>
-          <NumberInput
-            value={currentPage.toString()}
-            data-element="current-page"
-            onChange={handleCurrentPageChange}
-            onBlur={handlePageInputChange}
+      {showPageCount &&
+        (interactivePageNumber ? (
+          <StyledPagerNavInner>
+            <label htmlFor={currentPageId}>
+              <StyledPagerNoSelect>{l.pager.pageX()}</StyledPagerNoSelect>
+            </label>
+            <NumberInput
+              value={currentPage.toString()}
+              data-element="current-page"
+              onChange={handleCurrentPageChange}
+              onBlur={handlePageInputChange}
+              id={currentPageId}
+              onKeyUp={(ev) =>
+                Events.isEnterKey(ev) ? handlePageInputChange(ev) : false
+              }
+            />
+            <StyledPagerNoSelect>{l.pager.ofY(pageCount)}</StyledPagerNoSelect>
+          </StyledPagerNavInner>
+        ) : (
+          <StyledPagerNavLabel
+            data-element="current-page-label"
             id={currentPageId}
-            onKeyUp={(ev) =>
-              Events.isEnterKey(ev) ? handlePageInputChange(ev) : false
-            }
-          />
-          <StyledPagerNoSelect>{l.pager.ofY(pageCount)}</StyledPagerNoSelect>
-        </StyledPagerNavInner>
-      )}
+            aria-live="polite"
+          >
+            {navLabelString}
+          </StyledPagerNavLabel>
+        ))}
       {!hasOnePage && renderButtonsAfterCount()}
     </StyledPagerNavigation>
   );
diff --git a/src/components/pager/pager.component.tsx b/src/components/pager/pager.component.tsx
index 936204a96..2e6a1ee4c 100644
--- a/src/components/pager/pager.component.tsx
+++ b/src/components/pager/pager.component.tsx
@@ -47,6 +47,8 @@ export interface PagerProps {
   ) => void;
   /** Current visible page */
   currentPage?: number | string;
+  /** If true, page number navigation will be changed to a non-interactive label */
+  interactivePageNumber?: boolean;
   /** If true, sets css property visibility: hidden on all disabled elements  */
   hideDisabledElements?: boolean;
   /** Total number of records */
@@ -76,6 +78,7 @@ export interface PagerProps {
 export const Pager = ({
   currentPage = 1,
   hideDisabledElements = false,
+  interactivePageNumber = true,
   pageSizeSelectionOptions = [
     { id: "10", name: 10 },
     { id: "25", name: 25 },
@@ -258,6 +261,7 @@ export const Pager = ({
       <PagerNavigation
         pageSize={currentPageSize}
         currentPage={page}
+        interactivePageNumber={interactivePageNumber}
         hideDisabledElements={hideDisabledElements}
         setCurrentPage={setPage}
         onNext={handleOnNext}
diff --git a/src/components/pager/pager.spec.tsx b/src/components/pager/pager.spec.tsx
index d7f054d31..c62b92839 100644
--- a/src/components/pager/pager.spec.tsx
+++ b/src/components/pager/pager.spec.tsx
@@ -10,6 +10,7 @@ import SelectList from "../select/select-list/select-list.component";
 import {
   StyledPagerLink,
   StyledPagerNavInner,
+  StyledPagerNavLabel,
   StyledPagerSizeOptionsInner,
   StyledPagerSummary,
   StyledSelectContainer,
@@ -199,6 +200,36 @@ describe("Pager", () => {
       );
     });
 
+    it("when interactivePageNumber is false, pager nav number input is not rendered", () => {
+      wrapper = getWrapper({ interactivePageNumber: false });
+      expect(wrapper.find(StyledPagerNavInner).exists()).toEqual(false);
+    });
+
+    it.each([1, 5, 10])(
+      "when interactivePageNumber is false, pager nav label is rendered with correct inner text",
+      (pageIndex) => {
+        wrapper = getWrapper({
+          interactivePageNumber: false,
+          currentPage: pageIndex,
+        });
+        expect(wrapper.find(StyledPagerNavLabel).text()).toBe(
+          `Page ${pageIndex} of 10`
+        );
+      }
+    );
+
+    it("when interactivePageNumber is false, pager nav label is rendered with correct styling", () => {
+      wrapper = getWrapper({ interactivePageNumber: false, currentPage: 1 });
+
+      assertStyleMatch(
+        {
+          padding: "9px 12px",
+          margin: "4px 0",
+        },
+        wrapper.find(StyledPagerNavLabel)
+      );
+    });
+
     it("changes page correctly on clicking first link", () => {
       wrapper = getWrapper({ currentPage: 10 });
       const navLinks = wrapper.find(StyledPagerLink);
diff --git a/src/components/pager/pager.stories.mdx b/src/components/pager/pager.stories.mdx
index 69d4dcb4c..6217cfdc3 100644
--- a/src/components/pager/pager.stories.mdx
+++ b/src/components/pager/pager.stories.mdx
@@ -35,6 +35,15 @@ import Pager from "carbon-react/lib/components/pager";
   <Story story={stories.Default} name="default" />
 </Canvas>
 
+### with interactivePageNumber
+
+When the `interactivePageNumber` prop is `false` the number input which contains the current
+page number is replaced with a non-interactive label which will include the current page, and total page count all in one string.
+
+<Canvas>
+  <Story story={stories.InteractivePageNumber} name="interactivePageNumber" />
+</Canvas>
+
 ### with hideDisabledElements
 
 The example below demonstrates the functionality of the `hideDisabledElements` prop, when `true` all elements that typically would be disabled
diff --git a/src/components/pager/pager.stories.tsx b/src/components/pager/pager.stories.tsx
index 105e1f66f..cdc89905a 100644
--- a/src/components/pager/pager.stories.tsx
+++ b/src/components/pager/pager.stories.tsx
@@ -29,6 +29,33 @@ Default.argTypes = {
   },
 };
 
+export const InteractivePageNumber: ComponentStory<typeof Pager> = (
+  args: PagerProps
+) => <Pager {...args} />;
+
+InteractivePageNumber.args = {
+  totalRecords: "100",
+  interactivePageNumber: false,
+  showPageSizeSelection: false,
+  currentPage: "1",
+  pageSizeSelectionOptions: [
+    { id: "1", name: 1 },
+    { id: "10", name: 10 },
+    { id: "25", name: 25 },
+    { id: "50", name: 50 },
+    { id: "100", name: 100 },
+  ],
+};
+
+InteractivePageNumber.argTypes = {
+  pageSize: {
+    options: [1, 10, 25, 50, 100],
+    control: {
+      type: "select",
+    },
+  },
+};
+
 export const HideDisabledElements: ComponentStory<typeof Pager> = (
   args: PagerProps
 ) => <Pager {...args} />;
diff --git a/src/components/pager/pager.style.tsx b/src/components/pager/pager.style.tsx
index 71283f0aa..3dec2dcd3 100644
--- a/src/components/pager/pager.style.tsx
+++ b/src/components/pager/pager.style.tsx
@@ -109,6 +109,12 @@ const StyledPagerNavInner = styled.div`
   }
 `;
 
+const StyledPagerNavLabel = styled.label`
+  white-space: nowrap;
+  padding: 9px 12px;
+  margin: 4px 0;
+`;
+
 const StyledPagerLink = styled(Link)<
   Pick<StyledPagerProps, "hideDisabledButtons">
 >`
@@ -143,6 +149,7 @@ export {
   StyledPagerSizeOptionsInner,
   StyledPagerNavigation,
   StyledPagerNavInner,
+  StyledPagerNavLabel,
   StyledPagerLink,
   StyledPagerNoSelect,
   StyledPagerSummary,


Test patch:
diff --git a/src/components/pager/pager.test.js b/src/components/pager/pager.test.js
index da56b707e..ed9679a5d 100644
--- a/src/components/pager/pager.test.js
+++ b/src/components/pager/pager.test.js
@@ -19,6 +19,7 @@ import {
   firstArrow,
   lastArrow,
   currentPageWrapper,
+  currentPageLabelWrapper,
   currentPageInput,
   pagerSummary,
   pageSelectElement,
@@ -255,6 +256,44 @@ context("Test for Pager component", () => {
       firstArrow().should("be.visible");
       previousArrow().should("be.visible");
     });
+
+    it.each([
+      [false, "not.exist"],
+      [true, "be.visible"],
+    ])(
+      "when interactivePageNumber is %s, standard pager nav number input rendered correctly",
+      (boolVal, assertion) => {
+        CypressMountWithProviders(
+          <PagerComponent currentPage={1} interactivePageNumber={boolVal} />
+        );
+
+        currentPageWrapper().should(assertion);
+      }
+    );
+
+    it.each([
+      [true, "not.exist"],
+      [false, "be.visible"],
+    ])(
+      "when interactivePageNumber is %s, pager nav label is rendered correctly",
+      (boolVal, assertion) => {
+        CypressMountWithProviders(
+          <PagerComponent currentPage={1} interactivePageNumber={boolVal} />
+        );
+
+        currentPageLabelWrapper().should(assertion);
+      }
+    );
+
+    it("when interactivePageNumber is false, pager nav label is rendered with correct styling", () => {
+      CypressMountWithProviders(
+        <PagerComponent currentPage={1} interactivePageNumber={false} />
+      );
+
+      currentPageLabelWrapper()
+        .should("have.css", "padding", "9px 12px")
+        .and("have.css", "margin", "4px 0px");
+    });
   });
 
   describe("check funtionality for Pager component", () => {
@@ -662,20 +701,28 @@ context("Test for Pager component", () => {
       }
     );
 
-    // FE-4659
-    describe.skip("should render Pager component for accessibility tests", () => {
-      it.each([true, false])(
-        "should render Pager with showPageSizeLabelBefore prop set to %s for accessibility tests",
-        (boolVal) => {
-          CypressMountWithProviders(
-            <PagerComponent
-              showPageSizeLabelBefore={boolVal}
-              showPageSizeSelection
-            />
-          );
-          cy.checkAccessibility();
-        }
-      );
-    });
+    it.each([true, false])(
+      "should render Pager with showPageSizeLabelBefore prop set to %s for accessibility tests",
+      (boolVal) => {
+        CypressMountWithProviders(
+          <PagerComponent
+            showPageSizeLabelBefore={boolVal}
+            showPageSizeSelection
+          />
+        );
+        cy.checkAccessibility();
+      }
+    );
+
+    it.each([false, true])(
+      "should render Pager with interactivePageNumber prop set to %s for accessibility tests",
+      (boolVal) => {
+        CypressMountWithProviders(
+          <PagerComponent currentPage={1} interactivePageNumber={boolVal} />
+        );
+
+        cy.checkAccessibility();
+      }
+    );
   });
 });