Repository: Sage/carbon (ts)

Issue:
Pager is misaligned when the FlatTable scrolls horizontally
### Current behaviour

When the `FlatTable` overflows horizontally (e.g. due to having many columns or wide entries) and has the `Pager` in the footer, it becomes "detached" from the parent table when scrolled:

https://user-images.githubusercontent.com/30114244/208705135-2f7580af-0ce6-4447-9833-b4c46a0615db.mov

### Expected behaviour

The footer should match the table width, not the scrollable parent width (the "viewport").

### CodeSandbox or Storybook URL

https://codesandbox.io/s/friendly-colden-y16igk

### JIRA Ticket (Sage Only)

SBS-34541

### Suggested Solution

As the table and the `Pager` are nested within unrelated containers, it may be needed to restructure the containment to get this fixed. Please notice the mismatch between the table, container and the footer dimensions:

https://user-images.githubusercontent.com/30114244/208706401-1a8a7d90-0b5b-4757-9823-c562ba7e4e5b.mov



### Carbon Version

111.8.0

### Design Tokens Version

2.64.0

### What browsers are you seeing the problem on?

Chrome

### What Operating System are you seeing the problem on?

MacOS

### Anything else we should know?

_No response_

### Confidentiality

- [X] I confirm there is no confidential or commercially sensitive information included.
<Pager /> component is not responsive at smaller screen sizes
### Description

When using the `<Pager />` component and reducing the screen width down to `320px`, the content overspills and isn't behaving responsively.

Please can we add the ability for this component to correctly respond when screen-sizes are reduced to `320px` and for the content to be contained/wrapped within.

### Reproduction

https://carbon.sage.com/iframe.html?args=&globals=&id=pager--using-custom-responsive-settings&viewMode=story

### Steps to reproduce

Visible in Carbon [docs](https://carbon.sage.com/?path=/docs/pager--docs#custom-responsive-example), when viewing _any_ of the `<Pager />` stories in Canvas mode and reducing to `320px`.

example: `using-custom-responsive-settings`
![Image](https://github.com/user-attachments/assets/e8e5fdc5-d462-44ad-8efa-4ddc543f812f)

### JIRA ticket numbers (Sage only)

_No response_

### Suggested solution

_No response_

### Carbon version

144.9.11

### Design tokens version

4.34.0

### Relevant browsers

Chrome

### Relevant OSs

MacOS

### Additional context

_No response_

### Confidentiality

- [x] I confirm there is no confidential or commercially sensitive information included.

Pull request description:
feat(pager): add small screen styling 
fix #7184
fix #5704

### Proposed behaviour

<!--
A clear and concise description of what changes this PR makes. If applicable, include any UI screenshots to help explain your request. If you are a Sage contributor, please DO NOT share any commercially sensitive information, such as UI screenshots or code from Sage products.
-->

Adds `smallScreenBreakpoint` prop that allows consumers to wrap (if dropdown and/or total elements are rendered) and reduce spacing around the elements within `Pager`:
![image](https://github.com/user-attachments/assets/ea87ff4d-62f5-4a40-88f7-832ed54be949)
![image](https://github.com/user-attachments/assets/3a78b92f-af20-4dab-9b74-7ba9972d937d)
![image](https://github.com/user-attachments/assets/6338bd77-a980-41c2-8864-d5c7b4258a43)

### Current behaviour

<!--
A clear and concise description of the behaviour before this change. If applicable, include any UI screenshots to help explain your request. If you are a Sage contributor, please DO NOT share any commercially sensitive information, such as UI screenshots or code from Sage products.
-->

- Content in `Pager` overflows in small viewports: 
![image](https://github.com/user-attachments/assets/e5bdd3f4-14fb-46ad-ba8f-0a50bc4f527c)
![image](https://github.com/user-attachments/assets/3e43d6e7-7de6-4f17-b972-274307c99758)
![image](https://github.com/user-attachments/assets/f7e47819-884d-43cc-bdc5-005d282209da)


### 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
- [ ] Unit tests added or updated if required
- [x] Playwright automation tests added or updated if required
- [ ] Storybook added or updated if required
- [ ] 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 provided StackBlitz sandbox/Storybook
- [ ] Add new Playwright 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?

If this PR addresses a pre-existing bug, please include a link to a sandbox that reproduces the original bug. A starter template has been provided to help you do this:
<https://stackblitz.com/fork/github/Parsium/carbon-starter>
-->


Patch:
diff --git a/playwright/components/pager/locators.ts b/playwright/components/pager/locators.ts
index a123e53d7..c4c8e0513 100644
--- a/playwright/components/pager/locators.ts
+++ b/playwright/components/pager/locators.ts
@@ -1,8 +1,7 @@
 // component preview locators
 export const PAGER_SUMMARY = '[data-component="pager"]';
 export const PAGE_SELECT = '[data-component="simple-select"]';
-export const MAX_PAGES =
-  'div[data-component="pager"] > div:nth-child(2) > div > div:nth-child(3)';
+export const MAX_PAGES = '[data-element="max-pages"]';
 export const PAGER_PREVIOUS_ARROW = "previous";
 export const PAGER_NEXT_ARROW = "next";
 export const PAGER_FIRST_ARROW = "first";
diff --git a/src/components/flat-table/flat-table.stories.tsx b/src/components/flat-table/flat-table.stories.tsx
index 252eb02ee..342f3bbed 100644
--- a/src/components/flat-table/flat-table.stories.tsx
+++ b/src/components/flat-table/flat-table.stories.tsx
@@ -2102,8 +2102,11 @@ export const Paginated: Story = () => {
   return (
     <FlatTable
       title="Table for pagination"
+      overflowX="auto"
+      width="100%"
       footer={
         <Pager
+          smallScreenBreakpoint="550px"
           totalRecords={rows.length}
           showPageSizeSelection
           pageSize={10}
@@ -2129,6 +2132,9 @@ export const Paginated: Story = () => {
   );
 };
 Paginated.storyName = "Paginated";
+Paginated.parameters = {
+  chromatic: { viewports: [1200, 320] },
+};
 
 export const PaginatedWithStickyHeader: Story = () => {
   const [placementUp, setPlacementUp] = useState(true);
@@ -2494,8 +2500,11 @@ export const PaginatedWithStickyHeader: Story = () => {
         title="Table for pagination with sticky header and footer"
         hasStickyHead
         hasStickyFooter
+        overflowX="auto"
+        width="100%"
         footer={
           <Pager
+            smallScreenBreakpoint="550px"
             totalRecords={rows.length}
             showPageSizeSelection
             pageSize={10}
diff --git a/src/components/pager/__internal__/pager-navigation.component.tsx b/src/components/pager/__internal__/pager-navigation.component.tsx
index b08104b0b..ec650d875 100644
--- a/src/components/pager/__internal__/pager-navigation.component.tsx
+++ b/src/components/pager/__internal__/pager-navigation.component.tsx
@@ -57,6 +57,8 @@ export interface PagerNavigationProps {
   interactivePageNumber?: boolean;
   /** If true, sets css property visibility: hidden on all disabled elements  */
   hideDisabledElements?: boolean;
+  /** Breakpoint for small screen styling to be applied. */
+  smallScreenBreakpoint?: string;
 }
 
 const PagerNavigation = ({
@@ -74,6 +76,7 @@ const PagerNavigation = ({
   showPageCount = true,
   interactivePageNumber = true,
   hideDisabledElements = false,
+  smallScreenBreakpoint,
 }: PagerNavigationProps) => {
   const l = useLocale();
   const guid = useRef(createGuid());
@@ -166,7 +169,7 @@ const PagerNavigation = ({
   );
 
   return (
-    <StyledPagerNavigation>
+    <StyledPagerNavigation smallScreenBreakpoint={smallScreenBreakpoint}>
       {!hasOnePage && renderButtonsBeforeCount()}
       {showPageCount &&
         (interactivePageNumber ? (
@@ -184,7 +187,9 @@ const PagerNavigation = ({
                 Events.isEnterKey(ev) ? handlePageInputChange(ev) : false
               }
             />
-            <StyledPagerNoSelect>{l.pager.ofY(pageCount)}</StyledPagerNoSelect>
+            <StyledPagerNoSelect data-element="max-pages">
+              {l.pager.ofY(pageCount)}
+            </StyledPagerNoSelect>
           </StyledPagerNavInner>
         ) : (
           <StyledPagerNavLabel
diff --git a/src/components/pager/pager.component.tsx b/src/components/pager/pager.component.tsx
index 6ff7d2871..2bb6da65c 100644
--- a/src/components/pager/pager.component.tsx
+++ b/src/components/pager/pager.component.tsx
@@ -74,6 +74,8 @@ export interface PagerProps extends TagProps {
   showPageCount?: boolean;
   /** What variant the Pager background should be */
   variant?: "default" | "alternate";
+  /** Breakpoint for small screen styling to be applied. */
+  smallScreenBreakpoint?: string;
 }
 
 export const Pager = ({
@@ -101,6 +103,7 @@ export const Pager = ({
   showPreviousAndNextButtons = true,
   showPageCount = true,
   variant = "default",
+  smallScreenBreakpoint,
   ...rest
 }: PagerProps) => {
   const l = useLocale();
@@ -251,31 +254,33 @@ export const Pager = ({
         <div>{child}</div>
       );
     return (
-      showPageSizeSelection && (
-        <StyledPagerSizeOptionsInner>
-          {showPageSizeLabelBefore &&
-            wrapper(showPageSizeLabelBefore, l.pager.show())}
-          {sizeSelector()}
-          {showPageSizeLabelAfter &&
-            wrapper(
-              !showPageSizeLabelBefore,
-              l.pager.records(currentPageSize, false),
-            )}
-        </StyledPagerSizeOptionsInner>
-      )
+      <StyledPagerSizeOptionsInner>
+        {showPageSizeLabelBefore &&
+          wrapper(showPageSizeLabelBefore, l.pager.show())}
+        {sizeSelector()}
+        {showPageSizeLabelAfter &&
+          wrapper(
+            !showPageSizeLabelBefore,
+            l.pager.records(currentPageSize, false),
+          )}
+      </StyledPagerSizeOptionsInner>
     );
   };
 
-  const renderTotalRecords = () =>
-    showTotalRecords && l.pager.records(totalRecords);
-
   return (
     <StyledPagerContainer
       variant={variant}
+      smallScreenBreakpoint={smallScreenBreakpoint}
+      showPageSizeSelection={showPageSizeSelection}
+      showTotalRecords={showTotalRecords}
       {...rest}
       {...tagComponent("pager", rest)}
     >
-      <StyledPagerSizeOptions>{renderPageSizeOptions()}</StyledPagerSizeOptions>
+      {showPageSizeSelection && (
+        <StyledPagerSizeOptions>
+          {renderPageSizeOptions()}
+        </StyledPagerSizeOptions>
+      )}
       <PagerNavigation
         pageSize={currentPageSize}
         currentPage={page}
@@ -291,8 +296,13 @@ export const Pager = ({
         showFirstAndLastButtons={showFirstAndLastButtons}
         showPreviousAndNextButtons={showPreviousAndNextButtons}
         showPageCount={showPageCount}
+        smallScreenBreakpoint={smallScreenBreakpoint}
       />
-      <StyledPagerSummary>{renderTotalRecords()}</StyledPagerSummary>
+      {showTotalRecords && (
+        <StyledPagerSummary smallScreenBreakpoint={smallScreenBreakpoint}>
+          {l.pager.records(totalRecords)}
+        </StyledPagerSummary>
+      )}
     </StyledPagerContainer>
   );
 };
diff --git a/src/components/pager/pager.mdx b/src/components/pager/pager.mdx
index 266d8e43a..57e480972 100644
--- a/src/components/pager/pager.mdx
+++ b/src/components/pager/pager.mdx
@@ -58,7 +58,7 @@ In the example below the `First` and `Last` buttons and `totalRecords` label hav
 ### Smart elements
 
 The examples below demonstrate the `Pager`'s smart functionality. In the first there are only enough records for one page,
-and as such the navigation buttons are not neccessary and therefore not rendered. In the second example there are only enough
+and as such the navigation buttons are not necessary and therefore not rendered. In the second example there are only enough
 records for two pages, the `First` and `Last` buttons are not needed and as a result will not render.
 
 <Canvas of={PagerStories.SmartFunctionality} />
@@ -87,12 +87,20 @@ Due to the fact that it is last page, next and last links are disabled.
 
 ### Custom responsive example
 
-The`show...` props can also be used to implement responsive behaviour in the `Pager`. Below is an example that will
+The`show...` props can be used to implement responsive behaviour in the `Pager`. Below is an example that will
 conditionally render the internal elements as the screen size is adjusted. This example is best viewed in the Canvas tab
 using full-screen mode with device or viewport emulation.
 
 <Canvas of={PagerStories.UsingCustomResponsiveSettings} />
 
+### Small screen breakpoint
+The `smallScreenBreakpoint` prop allows you to set the screen size at which the `Pager` will switch to small screen layout. 
+This will cause the navigation links to wrap onto a new line if `showPageSizeSelection` or `showTotalRecords` is set to `true`, as well as reduce the spacing around the elements.
+
+**Note:** This prop does not guarantee that the elements within `Pager` do not overflow, it is up to consumers to ensure they set the appropriate props and breakpoints.
+
+<Canvas of={PagerStories.SmallScreenBreakpoint} />
+
 ## Props
 
 ### Pager
diff --git a/src/components/pager/pager.pw.tsx b/src/components/pager/pager.pw.tsx
index a3fdcebdc..f8d6e9622 100644
--- a/src/components/pager/pager.pw.tsx
+++ b/src/components/pager/pager.pw.tsx
@@ -385,19 +385,6 @@ test.describe("Prop tests", () => {
       }
     });
   });
-
-  test(`should render pager nav label with correct styling when interactivePageNumber is false`, async ({
-    mount,
-    page,
-  }) => {
-    await mount(
-      <PagerComponent currentPage={1} interactivePageNumber={false} />,
-    );
-
-    const labelWrapper = currentPageLabelWrapper(page);
-    await expect(labelWrapper).toHaveCSS("padding", "9px 12px");
-    await expect(labelWrapper).toHaveCSS("margin", "4px 0px");
-  });
 });
 
 test.describe("Functional tests", () => {
diff --git a/src/components/pager/pager.stories.tsx b/src/components/pager/pager.stories.tsx
index 31ab4d62e..d9e20f77b 100644
--- a/src/components/pager/pager.stories.tsx
+++ b/src/components/pager/pager.stories.tsx
@@ -225,6 +225,7 @@ export const UsingCustomResponsiveSettings: Story = () => {
       currentPage={1}
       onPagination={() => {}}
       {...responsiveProps()}
+      smallScreenBreakpoint="375px"
       pageSizeSelectionOptions={[
         { id: "10", name: 10 },
         { id: "25", name: 25 },
@@ -236,5 +237,31 @@ export const UsingCustomResponsiveSettings: Story = () => {
 };
 UsingCustomResponsiveSettings.storyName = "Using Custom Responsive Settings";
 UsingCustomResponsiveSettings.parameters = {
-  chromatic: { disableSnapshot: true },
+  chromatic: { viewports: [1200, 920, 320] },
+};
+
+export const SmallScreenBreakpoint: Story = () => {
+  const shouldShowExtraLinks = useMediaQuery("(min-width: 375px)");
+
+  return (
+    <Pager
+      smallScreenBreakpoint="705px"
+      totalRecords={1000}
+      showPageSizeSelection
+      showFirstAndLastButtons={shouldShowExtraLinks}
+      pageSize={10}
+      currentPage={1}
+      onPagination={() => {}}
+      pageSizeSelectionOptions={[
+        { id: "10", name: 10 },
+        { id: "25", name: 25 },
+        { id: "50", name: 50 },
+        { id: "100", name: 100 },
+      ]}
+    />
+  );
+};
+SmallScreenBreakpoint.storyName = "Small Screen Breakpoint";
+SmallScreenBreakpoint.parameters = {
+  chromatic: { viewports: [1200, 675, 375, 320] },
 };
diff --git a/src/components/pager/pager.style.ts b/src/components/pager/pager.style.ts
index 05c3e461b..db93cc218 100644
--- a/src/components/pager/pager.style.ts
+++ b/src/components/pager/pager.style.ts
@@ -1,6 +1,5 @@
 import styled, { css } from "styled-components";
 
-import { PagerProps } from ".";
 import StyledInput from "../../__internal__/input/input.style";
 import StyledInputPresentation from "../../__internal__/input/input-presentation.style";
 import StyledFormField from "../../__internal__/form-field/form-field.style";
@@ -8,10 +7,6 @@ import InputIconToggleStyle from "../../__internal__/input-icon-toggle/input-ico
 import { StyledSelectText } from "../select/__internal__/select-textbox/select-textbox.style";
 import Link from "../link";
 
-interface StyledPagerProps {
-  hideDisabledButtons?: boolean;
-}
-
 const StyledSelectContainer = styled.div`
   height: 26px;
   width: 55px;
@@ -22,15 +17,42 @@ const StyledSelectContainer = styled.div`
   }
 `;
 
-const StyledPagerContainer = styled.div<Pick<PagerProps, "variant">>`
-  display: flex;
-  justify-content: space-between;
-  padding: 0px 24px;
+interface StyledPagerContainerProps {
+  variant?: "alternate" | "default";
+  smallScreenBreakpoint?: string;
+  showPageSizeSelection?: boolean;
+  showTotalRecords?: boolean;
+}
+
+const StyledPagerContainer = styled.div<StyledPagerContainerProps>`
+  box-sizing: border-box;
+  display: grid;
   align-items: center;
-  font-size: 13px;
+  justify-content: space-between;
+  padding: var(--sizing050) var(--sizing300);
+  width: 100%;
+  min-height: var(--sizing550);
+  font-size: var(--fontSizes100);
   color: var(--colorsUtilityYin090);
   border: 1px solid var(--colorsUtilityMajor100);
   border-radius: var(--borderRadius100);
+  grid-template-columns: repeat(3, 1fr);
+  flex-wrap: wrap;
+
+  ${({ smallScreenBreakpoint, showPageSizeSelection, showTotalRecords }) =>
+    smallScreenBreakpoint &&
+    css`
+      @media (max-width: ${smallScreenBreakpoint}) {
+        grid-template-columns: 1fr;
+        padding: var(--sizing050) var(--sizing100);
+
+        ${(showPageSizeSelection || showTotalRecords) &&
+        css`
+          grid-template-columns: 1fr 1fr;
+          grid-template-rows: 1fr 1fr;
+        `}
+      }
+    `}
 
   ${({ variant }) => css`
     background-color: ${variant === "alternate"
@@ -40,9 +62,7 @@ const StyledPagerContainer = styled.div<Pick<PagerProps, "variant">>`
 `;
 
 const StyledPagerSizeOptions = styled.div`
-  display: flex;
-  flex: 1 1 30%;
-  justify-content: flex-start;
+  grid-area: 1 / 1 / 1 / 1;
 
   ${StyledInputPresentation} {
     width: 55px;
@@ -72,11 +92,27 @@ const StyledPagerSizeOptionsInner = styled.div`
   align-items: center;
 `;
 
-const StyledPagerNavigation = styled.div`
+interface StyledPagerProps {
+  smallScreenBreakpoint?: string;
+}
+
+const StyledPagerNavigation = styled.div<StyledPagerProps>`
   display: flex;
-  flex: 1 1 auto;
   justify-content: center;
   align-items: center;
+  padding: 0 var(--spacing200);
+  gap: var(--spacing400);
+  grid-area: 1 / 2 / 1 / 2;
+
+  ${({ smallScreenBreakpoint }) =>
+    smallScreenBreakpoint &&
+    css`
+      @media (max-width: ${smallScreenBreakpoint}) {
+        padding: 0;
+        gap: var(--spacing200);
+        grid-area: 2 / 1 / 2 / 3;
+      }
+    `}
 
   && ${StyledInputPresentation} {
     padding: 0;
@@ -96,8 +132,6 @@ const StyledPagerNavigation = styled.div`
 const StyledPagerNavInner = styled.div`
   display: flex;
   align-items: center;
-  padding: 0 12px;
-  margin: 4px 0;
 
   && ${StyledFormField} {
     margin-bottom: 0;
@@ -106,16 +140,13 @@ const StyledPagerNavInner = styled.div`
 
 const StyledPagerNavLabel = styled.label`
   white-space: nowrap;
-  padding: 9px 12px;
-  margin: 4px 0;
 `;
 
-const StyledPagerLink = styled(Link)<
-  Pick<StyledPagerProps, "hideDisabledButtons">
->`
-  margin-left: 17px;
-  margin-right: 17px;
+interface StyledPagerLinkProps {
+  hideDisabledButtons?: boolean;
+}
 
+const StyledPagerLink = styled(Link)<StyledPagerLinkProps>`
   ${({ hideDisabledButtons }) =>
     hideDisabledButtons &&
     css`
@@ -131,10 +162,18 @@ const StyledPagerNoSelect = styled.div`
   font-weight: normal;
 `;
 
-const StyledPagerSummary = styled.div`
-  display: flex;
-  flex: 1 1 30%;
-  justify-content: flex-end;
+const StyledPagerSummary = styled.div<StyledPagerProps>`
+  justify-self: end;
+  white-space: nowrap;
+  grid-area: 1 / 3 / 1 / 3;
+
+  ${({ smallScreenBreakpoint }) =>
+    smallScreenBreakpoint &&
+    css`
+      @media (max-width: ${smallScreenBreakpoint}) {
+        grid-area: 1 / 2 / 1 / 2;
+      }
+    `}
 `;
 
 export {


Test patch:
diff --git a/src/components/pager/pager-test.stories.tsx b/src/components/pager/pager-test.stories.tsx
index 6663292ea..c636b9d5b 100644
--- a/src/components/pager/pager-test.stories.tsx
+++ b/src/components/pager/pager-test.stories.tsx
@@ -96,4 +96,5 @@ Default.args = {
   showFirstAndLastButtons: true,
   showPreviousAndNextButtons: true,
   showPageCount: true,
+  smallScreenBreakpoint: "500px",
 };
diff --git a/src/components/pager/pager.test.tsx b/src/components/pager/pager.test.tsx
index 631724378..166f20416 100644
--- a/src/components/pager/pager.test.tsx
+++ b/src/components/pager/pager.test.tsx
@@ -493,3 +493,40 @@ test("renders with correct styles when `variant` is `alternate`", () => {
     "var(--colorsUtilityMajor040)",
   );
 });
+
+// coverage for `smallScreenBreakpoint` prop - tested in chromatic
+describe("when smallScreenBreakpoint is set", () => {
+  it("renders with correct styles when viewport size is small and `showTotalRecords` is false", () => {
+    render(
+      <Pager
+        data-role="pager"
+        smallScreenBreakpoint="500px"
+        showTotalRecords={false}
+        onPagination={() => {}}
+      />,
+    );
+
+    expect(screen.getByTestId("pager")).toHaveStyleRule(
+      "grid-template-columns",
+      "1fr",
+      { media: "(max-width: 500px)" },
+    );
+  });
+
+  it("renders with correct styles when viewport size is small and `showPageSizeSelection` is true", () => {
+    render(
+      <Pager
+        data-role="pager"
+        smallScreenBreakpoint="500px"
+        showPageSizeSelection
+        onPagination={() => {}}
+      />,
+    );
+
+    expect(screen.getByTestId("pager")).toHaveStyleRule(
+      "grid-template-columns",
+      "1fr 1fr",
+      { media: "(max-width: 500px)" },
+    );
+  });
+});