Repository: Telefonica/mistica-web
Pull request #1359: feat(PinField): wrap to next line when digits don't fit in viewport
Author: login: atabel
Labels: name: released
Created: 2025-05-05T15:30:22Z | Merged: 2025-05-06T07:10:49Z

https://jira.tid.es/browse/WEB-2146


Diff:
diff --git a/src/__screenshot_tests__/__image_snapshots__/inline-screenshot-test-tsx-inline-wrap-vertical-space-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/inline-screenshot-test-tsx-inline-wrap-vertical-space-1-snap.png
new file mode 100644
index 0000000000..4f00db63e6
Binary files /dev/null and b/src/__screenshot_tests__/__image_snapshots__/inline-screenshot-test-tsx-inline-wrap-vertical-space-1-snap.png differ
diff --git a/src/__screenshot_tests__/__image_snapshots__/input-fields-screenshot-test-tsx-pin-field-increased-font-size-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/input-fields-screenshot-test-tsx-pin-field-increased-font-size-1-snap.png
index 6a4538bc4e..8eb215727e 100644
Binary files a/src/__screenshot_tests__/__image_snapshots__/input-fields-screenshot-test-tsx-pin-field-increased-font-size-1-snap.png and b/src/__screenshot_tests__/__image_snapshots__/input-fields-screenshot-test-tsx-pin-field-increased-font-size-1-snap.png differ
diff --git a/src/__screenshot_tests__/inline-screenshot-test.tsx b/src/__screenshot_tests__/inline-screenshot-test.tsx
index 019c739881..d4193bca96 100644
--- a/src/__screenshot_tests__/inline-screenshot-test.tsx
+++ b/src/__screenshot_tests__/inline-screenshot-test.tsx
@@ -32,6 +32,13 @@ test('Inline wrap', async () => {
     expect(image).toMatchImageSnapshot();
 });
 
+test('Inline wrap vertical space', async () => {
+    const page = await openStoryPage({id: 'layout-inline--wrap', args: {verticalSpace: 16}});
+
+    const image = await page.screenshot();
+    expect(image).toMatchImageSnapshot();
+});
+
 // This test is unstable (https://jira.tid.es/browse/WEB-1648)
 // eslint-disable-next-line jest/no-disabled-tests
 test.skip('Inline negative space', async () => {
diff --git a/src/__stories__/inline-story.tsx b/src/__stories__/inline-story.tsx
index 9be2400d05..86b1b8f72d 100644
--- a/src/__stories__/inline-story.tsx
+++ b/src/__stories__/inline-story.tsx
@@ -107,13 +107,14 @@ Default.storyName = 'Inline';
 
 type Args = {
     space: React.ComponentProps<typeof Inline>['space'];
+    verticalSpace: React.ComponentProps<typeof Inline>['verticalSpace'];
     numItems: number;
 };
 
-export const Wrap: StoryComponent<Args> = ({space, numItems}) => {
+export const Wrap: StoryComponent<Args> = ({space, verticalSpace, numItems}) => {
     const tagTypes = ['active', 'inactive', 'success', 'warning', 'error', 'promo', 'info'] as const;
     return (
-        <Inline space={space} wrap>
+        <Inline space={space} verticalSpace={verticalSpace} wrap>
             {Array.from({length: numItems}, (_, i) => {
                 const type = tagTypes[i % tagTypes.length];
                 return (
@@ -128,6 +129,7 @@ export const Wrap: StoryComponent<Args> = ({space, numItems}) => {
 
 Wrap.args = {
     space: 8,
+    verticalSpace: 8,
     numItems: 50,
 };
 
@@ -136,6 +138,10 @@ Wrap.argTypes = {
         options: [0, 2, 4, 8, 12, 16, 24, 32, 40, 48, 56, 64],
         control: {type: 'select'},
     },
+    verticalSpace: {
+        options: [0, 2, 4, 8, 12, 16, 24, 32, 40, 48, 56, 64],
+        control: {type: 'select'},
+    },
 };
 
 Wrap.storyName = 'Inline wrap';
diff --git a/src/inline.css.ts b/src/inline.css.ts
index 34876b996c..ebf2faf435 100644
--- a/src/inline.css.ts
+++ b/src/inline.css.ts
@@ -6,25 +6,42 @@ const spaceMobile = createVar();
 const spaceTablet = createVar();
 const spaceDesktop = createVar();
 
-export const vars = {space, spaceMobile, spaceTablet, spaceDesktop};
+const verticalSpace = createVar();
+const verticalSpaceMobile = createVar();
+const verticalSpaceTablet = createVar();
+const verticalSpaceDesktop = createVar();
+
+export const vars = {
+    space,
+    spaceMobile,
+    spaceTablet,
+    spaceDesktop,
+    verticalSpace,
+    verticalSpaceMobile,
+    verticalSpaceTablet,
+    verticalSpaceDesktop,
+};
 
 export const marginInline = style({
-    marginTop: `calc(${space} * -1)`,
+    marginTop: `calc(${verticalSpace} * -1)`,
     marginLeft: `calc(${space} * -1)`,
     '@media': {
         [mq.mobile]: {
             vars: {
                 [space]: spaceMobile,
+                [verticalSpace]: verticalSpaceMobile,
             },
         },
         [mq.tablet]: {
             vars: {
                 [space]: fallbackVar(spaceTablet, spaceMobile),
+                [verticalSpace]: fallbackVar(verticalSpaceTablet, verticalSpaceMobile),
             },
         },
         [mq.desktopOrBigger]: {
             vars: {
                 [space]: spaceDesktop,
+                [verticalSpace]: verticalSpaceDesktop,
             },
         },
     },
@@ -39,7 +56,7 @@ const supportsFlexGap = '(display: flex) and (gap: 0px)';
 export const inline = style({
     pointerEvents: 'none', // to prevent negative margins from affecting clickable areas
     gridAutoFlow: 'column',
-    marginTop: `calc(${space} * -1)`,
+    marginTop: `calc(${fallbackVar(verticalSpace, space)} * -1)`,
     marginLeft: `calc(${space} * -1)`,
     '@supports': {
         [supportsFlexGap]: {
@@ -47,6 +64,7 @@ export const inline = style({
             pointerEvents: 'auto', // restore
             flexDirection: 'row',
             gap: space,
+            rowGap: fallbackVar(verticalSpace, space),
         },
     },
 });
@@ -82,7 +100,7 @@ export const stringSpaceWithWrap = style({
 
 globalStyle(`${marginInline} > div`, {
     marginLeft: space,
-    marginTop: space,
+    marginTop: fallbackVar(verticalSpace, space),
 });
 
 globalStyle(`${inline} > div`, {
diff --git a/src/inline.tsx b/src/inline.tsx
index 6eeb64dc08..0475a955c9 100644
--- a/src/inline.tsx
+++ b/src/inline.tsx
@@ -20,24 +20,56 @@ const calcSpaceValue = (space: NumericSpace | FlexSpace) => {
     }
 };
 
-const calcInlineVars = (space: FlexSpace | ByBreakpoint<NumericSpace>) => {
-    if (typeof space === 'number' || typeof space === 'string') {
-        return {
-            [styles.vars.space]: calcSpaceValue(space),
+const calcInlineVars = (
+    space: FlexSpace | ByBreakpoint<NumericSpace>,
+    verticalSpace?: ByBreakpoint<NumericSpace>
+) => {
+    const calcSpaceVars = (
+        space: FlexSpace | ByBreakpoint<NumericSpace>,
+        varNames: {default: string; mobile: string; tablet: string; desktop: string}
+    ) => {
+        if (typeof space === 'number' || typeof space === 'string') {
+            return {
+                [varNames.default]: calcSpaceValue(space),
+            };
+        }
+        const vars = {
+            [varNames.mobile]: calcSpaceValue(space.mobile),
+            [varNames.desktop]: calcSpaceValue(space.desktop),
         };
-    }
-    const vars = {
-        [styles.vars.spaceMobile]: calcSpaceValue(space.mobile),
-        [styles.vars.spaceDesktop]: calcSpaceValue(space.desktop),
+        if (space.tablet) {
+            vars[varNames.tablet] = calcSpaceValue(space.tablet);
+        }
+        return vars;
     };
-    if (space.tablet) {
-        vars[styles.vars.spaceTablet] = calcSpaceValue(space.tablet);
+
+    const spaceVars = calcSpaceVars(space, {
+        default: styles.vars.space,
+        mobile: styles.vars.spaceMobile,
+        tablet: styles.vars.spaceTablet,
+        desktop: styles.vars.spaceDesktop,
+    });
+
+    if (verticalSpace) {
+        const verticalSpaceVars = calcSpaceVars(verticalSpace, {
+            default: styles.vars.verticalSpace,
+            mobile: styles.vars.verticalSpaceMobile,
+            tablet: styles.vars.verticalSpaceTablet,
+            desktop: styles.vars.verticalSpaceDesktop,
+        });
+
+        return {
+            ...spaceVars,
+            ...verticalSpaceVars,
+        };
     }
-    return vars;
+
+    return spaceVars;
 };
 
 type Props = {
     space: FlexSpace | ByBreakpoint<NumericSpace>;
+    verticalSpace?: ByBreakpoint<NumericSpace>;
     alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline';
     children: React.ReactNode;
     className?: string;
@@ -51,6 +83,7 @@ type Props = {
 
 const Inline = ({
     space,
+    verticalSpace,
     className,
     children,
     role,
@@ -72,7 +105,7 @@ const Inline = ({
                 wrap ? styles.wrap : fullWidth ? styles.fullWidth : styles.noFullWidth,
                 isStringSpace ? (wrap ? styles.stringSpaceWithWrap : styles.stringSpace) : styles.marginInline
             )}
-            style={{...applyCssVars(calcInlineVars(space)), alignItems}}
+            style={{...applyCssVars(calcInlineVars(space, verticalSpace)), alignItems}}
             role={role}
             aria-label={ariaLabel}
             aria-labelledby={ariaLabel ? undefined : ariaLabelledBy}
diff --git a/src/pin-field.tsx b/src/pin-field.tsx
index a06cf9ddb6..1cd18aa4ec 100644
--- a/src/pin-field.tsx
+++ b/src/pin-field.tsx
@@ -152,7 +152,7 @@ const PinInput = ({
     };
 
     return (
-        <Inline space={8}>
+        <Inline space={8} wrap verticalSpace={16}>
             {Array.from({length}).map((_, index) => (
                 <div
                     key={index}


Reviews:
user: login: atabel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T15:31:22Z
user: login: atabel
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T15:32:09Z
user: login: yceballost
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:48:52Z

Review comments:
path: src/inline.tsx
user: login: atabel
body: added support for `verticalSpace` prop in `Inline` component when used with `wrap`
created_at: 2025-05-05T15:31:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -152,7 +152,7 @@ const PinInput = ({
     };
 
     return (
-        <Inline space={8}>
+        <Inline space={8} wrap verticalSpace={16}>
path: src/pin-field.tsx
user: login: atabel
body: This is the only change needed in `PinField`, but needed changes in `Inline` to support this
created_at: 2025-05-05T15:32:09Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-05T15:32:26Z
body: **Size stats**

<table>
        <th>
            <td><b>master</b>
            <td><b>this branch</b>
            <td><b>diff</b>
        <tr>
            <td><b>Total JS</b>
            <td>12.4 MB
            <td>12.4 MB
            <td>+732 B
        <tr>
            <td><b>JS without icons</b>
            <td>2.12 MB
            <td>2.12 MB
            <td>+732 B
        <tr>
            <td><b>Lib overhead</b>
            <td>77.3 kB
            <td>77.3 kB
            <td> 0 B
        <tr>
            <td><b>Lib overhead (gzip)</b>
            <td>17.6 kB
            <td>17.6 kB
            <td> 0 B
    </table>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-05T15:33:28Z
body: **Accessibility report**
✔️ No issues found

ℹ️ You can run this locally by executing `yarn audit-accessibility`.
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-05T15:37:49Z
body: Deploy preview for _mistica-web_ ready!

✅ Preview
https://mistica-qeir5bjzb-flows-projects-65bb050e.vercel.app

Built with commit 500fdaff60098e62411e9407cae0a6a8f46fadac.
This pull request is being automatically deployed with [vercel-action](https://github.com/marketplace/actions/vercel-action)
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-05T15:42:39Z
body: **Screenshot tests report**

✔️ All passing
author_association: NONE
user: login: tuentisre
created_at: 2025-05-06T11:21:16Z
body: :tada: This PR is included in version 16.23.0 :tada:

The release is available on:
- [npm package (@latest dist-tag)](https://www.npmjs.com/package/@telefonica/mistica/v/16.23.0)
- [GitHub release](https://github.com/Telefonica/mistica-web/releases/tag/v16.23.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: COLLABORATOR