Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PositionedOverlay] Add support for horizontal positioning #11443

Closed
wants to merge 7 commits into from

Conversation

chloerice
Copy link
Member

@chloerice chloerice commented Jan 12, 2024

WHY are these changes introduced?

This PR unblocks the new HoverCard component, which needs to be able to be positioned left and right of table cells.

WHAT is this pull request doing?

Adds support for horizontal positioning of positioned overlays.

Demos

Responsive vertical alignment when positioned horizontally right left below above
hovercard-demo-position-right-responsive-alignment.mp4
Screenshot 2023-09-15 at 7 49 43 PM Screenshot 2023-09-15 at 7 49 32 PM Screenshot 2023-09-15 at 7 49 19 PM Screenshot 2023-09-15 at 7 48 59 PM

How to 🎩

🖥 Local development instructions
🗒 General tophatting guidelines
📄 Changelog guidelines

Click to view Playground code used for the above demos
import React, {useState} from 'react';
import {LocationIcon, OrderIcon} from '@shopify/polaris-icons';

import type {PopoverProps} from '../src';
import {
  Page,
  ButtonGroup,
  Box,
  Button,
  Popover,
  Icon,
  Link,
  Text,
  BlockStack,
  InlineStack,
  Card,
  Layout,
} from '../src';

export function Playground() {
  return (
    <Page title="Playground">
      <Layout>
        <Layout.Section>
          <PopoverPositioneExample />
        </Layout.Section>
      </Layout>
    </Page>
  );
}

export function PopoverPositioneExample() {
  const [active, setActive] = useState(false);
  const [position, setPosition] =
    useState<PopoverProps['preferredPosition']>('right');

  const handleChangePosition =
    (position: PopoverProps['preferredPosition']) => () => {
      setPosition(position);
    };

  const toggleActive = (active: boolean) => () => {
    setActive(active);
  };

  const activator = (
    <Link removeUnderline url="#" onClick={toggleActive(true)}>
      Saul Goodman
    </Link>
  );

  const customerDetailPreviewContent = (
    <Box padding="400">
      <BlockStack gap="400">
        <BlockStack gap="0">
          <Text as="span" variant="headingSm">
            <Link removeUnderline>Saul Goodman</Link>
          </Text>
          <Text as="span" variant="bodyMd">
            <Link url="mailto:help@bettercallsaul.com">
              help@bettercallsaul.com
            </Link>
          </Text>
          <Text as="p" variant="bodyMd">
            <Link url="tel:+1505-842-5662">+1 505-842-5662</Link>
          </Text>
        </BlockStack>
        <Box width="100%">
          <BlockStack gap="100">
            <InlineStack wrap={false} gap="100" align="start">
              <Icon tone="subdued" source={LocationIcon} />
              <Text tone="subdued" as="p">
                Albequerque, NM, USA
              </Text>
            </InlineStack>
            <InlineStack wrap={false} gap="100" align="start">
              <Box>
                <Icon tone="subdued" source={OrderIcon} />
              </Box>
              <Text tone="subdued" as="p">
                8 Orders
              </Text>
            </InlineStack>
          </BlockStack>
        </Box>
      </BlockStack>
    </Box>
  );

  const positionControlBar = (
    <BlockStack gap="100" inlineAlign="center">
      <Box id="Filler" minHeight="200px" />
      <Text as="p" tone="subdued">
        Use the buttons below to change the preferred popover position
      </Text>
      <ButtonGroup variant="segmented">
        <Button
          pressed={position === 'left'}
          onClick={handleChangePosition('left')}
        >
          Left
        </Button>
        <Button
          pressed={position === 'right'}
          onClick={handleChangePosition('right')}
        >
          Right
        </Button>
        <Button
          pressed={position === 'above'}
          onClick={handleChangePosition('above')}
        >
          Above
        </Button>
        <Button
          pressed={position === 'below'}
          onClick={handleChangePosition('below')}
        >
          Below
        </Button>
      </ButtonGroup>
    </BlockStack>
  );

  return (
    <InlineStack align="center" blockAlign="center">
      <BlockStack gap="500">
        {positionControlBar}

        <Card>
          <BlockStack gap="300">
            <Text as="h2" variant="headingSm">
              Customer
            </Text>
            <Popover
              active={active}
              preferredPosition={position}
              preferredAlignment="left"
              activator={activator}
              onClose={toggleActive(false)}
            >
              {customerDetailPreviewContent}
            </Popover>
          </BlockStack>
        </Card>
      </BlockStack>
    </InlineStack>
  );
}

🎩 checklist

@chloerice chloerice added the Priority: Urgent Bug blocking merchants, or critical directive label Jan 12, 2024
@chloerice chloerice force-pushed the positioned-overlay-horizontal-position branch 2 times, most recently from 70ad850 to e223144 Compare January 22, 2024 20:54
@chloerice chloerice force-pushed the positioned-overlay-horizontal-position branch from 211d227 to 507a80f Compare February 5, 2024 17:15
left: left == null || isNaN(left) ? undefined : left,
right: right == null || isNaN(right) ? undefined : right,
width: width == null || isNaN(width) ? undefined : width,
'--pc-positioned-overlay-top': nextTop,
Copy link
Member Author

@chloerice chloerice Feb 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though these could all technically move to CSS variables set and updated in the CSS, these properties still need to be set on the style property otherwise there is cascade collision with children.

this.setState(
{
measuring: false,
activatorRect: getRectForNode(activator),
activatorRect,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was already defined as a variable on line 265 (line 274 now), so removed the redundancy.

@chloerice chloerice force-pushed the positioned-overlay-horizontal-position branch from 507a80f to 09cc676 Compare February 5, 2024 18:26
const verticalMargins = overlayMargins.activator + overlayMargins.container;
const minimumSpaceToScroll = overlayMargins.container;
const distanceToTopScroll =
Copy link
Member Author

@chloerice chloerice Feb 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This math was causing the available vertical space to be undercalculated, causing the overlay to switch positions sooner than necessary. I fixed this and made the variable names clearer so it's easier to understand what's going on here.

@chloerice chloerice force-pushed the positioned-overlay-horizontal-position branch from 5e194a0 to d7a7c3d Compare February 5, 2024 20:01
Comment on lines +1 to +7
:root {
// stylelint-disable polaris/conventions/polaris/custom-property-allowed-list -- PositionedOverlay CSS Properties; Supports consuming component styles tying into changes in order to animate without hacky JavaScript timers and forcing reflow
--pc-positioned-overlay-top: initial;
--pc-positioned-overlay-bottom: initial;
// stylelint-enable
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CSS Custom Properties have a default value of initial when not set, so defining these on :root is the same as not defining them at all.

Suggested change
:root {
// stylelint-disable polaris/conventions/polaris/custom-property-allowed-list -- PositionedOverlay CSS Properties; Supports consuming component styles tying into changes in order to animate without hacky JavaScript timers and forcing reflow
--pc-positioned-overlay-top: initial;
--pc-positioned-overlay-bottom: initial;
// stylelint-enable
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Jess! They're initialized so they can be updated in the PositionedOverlay.tsx. So are you saying they don't need to exist before they're used? (they're consumed in HoverCardOverlay so that the animation of top/bottom can be pure CSS instead the hacky forced reflow with JS like Popover does.

@aaronccasanova aaronccasanova force-pushed the positioned-overlay-horizontal-position branch from 08c0259 to d38a750 Compare April 19, 2024 21:52
@github-actions github-actions bot added the cla-needed Added by a bot. Contributor needs to sign the CLA Agreement. label Apr 19, 2024
@translation-platform
Copy link
Contributor

Localization quality issues found

The following issues may affect the quality of localized translations if they are not addressed:

  • The value Clear for key Polaris.ActionList.SearchField.clearButtonLabel is very short. Short strings are more likely to be misunderstood by translators without context. Please provide additional context for the translators if possible.
  • The value Search for key Polaris.ActionList.SearchField.search is very short. Short strings are more likely to be misunderstood by translators without context. Please provide additional context for the translators if possible.
  • The value Search actions for key Polaris.ActionList.SearchField.placeholder is very short. Short strings are more likely to be misunderstood by translators without context. Please provide additional context for the translators if possible.

Please look out for other instances of this issue in your PR and fix them as well if possible.

Questions about these messages? Hop in the #help-localization Slack channel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla-needed Added by a bot. Contributor needs to sign the CLA Agreement. Priority: Urgent Bug blocking merchants, or critical directive
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants