<s-popover> improvements

Hi,

I’ve started playing with the new s-popover component, great work on this!

Unfortunately, there is no way to programatically close the popover (for instance after clicking on a button inside the popover). Could you please add show, hide and toggle methods to the component?

thanks.

4 Likes

You should be able to do so by adding commandFor=”<popover id>” and command=”–hide” to the buttons like that:

<s-button commandFor="product-options-popover">Product options</s-button>
<s-popover id="product-options-popover" accessibilityLabel="Product options">
  <s-button variant="tertiary" commandFor="product-options-popover" command="--hide">Import</s-button>
  <s-button variant="tertiary">Export</s-button>
</s-popover>
1 Like

This code does not work, unfortunately (you can try it here: Dezi)

Popover won’t open if it contains another button with a command.

If you can replicate this in a codepen or codesandbox and share it with us, that would be great. It should work this way.

I created a sandbox here:

https://codesandbox.io/p/sandbox/fancy-waterfall-tyzlr8

It looks like there are more elements which are preventing the popover to open. For example s-query-container.

This sandbox isn’t available - potentially set to private?

Sorry, I forgot to check the permissions. I just made the sandbox public.

Thanks! Taking a look at it. We’ll let you know when we have updates to share.

@Anthony_Frehner another reason we need a way to programmatically open/close a popover: I’m trying to replicate the autocomplete feature:

I’d like to open the popover after the user has type some information. Of course it would be better if Shopify would have a native autocomplete feautre, but opening the popover would already unlock such use case.

5 Likes

Thanks for the solution. I was really struggling here.

  1. Btw is this the only way to do it?
  2. Why’s doesn’t it say so in the Popover Doc. I spent so much time on this.

How would you programmatically open the popover though? From the docs it’s not quite clear how event handling works. The AI Assistant suggests:

document.getElementById(“product-options-popover”).setAttribute(“open”, ““);

But this doens’t work.

I’m programmatically closing the popover by sending it an event:

const handleOptionClick = useCallback(
    (event: CallbackEvent<"s-choice-list">) => {
     // Some callback code
     // ...

      const popover = popoverRef.current;
      if (!popover) return;

      // Hide the popover using the command event
      // Because Safari doesn't support CommandEvent yet, we assemble this, which works.
      const commandEvent = new CustomEvent("command", { bubbles: true });
      Object.defineProperty(commandEvent, "command", {
        value: "--hide",
        enumerable: true,
      });
      popover.dispatchEvent(commandEvent);
    },
    [onChange, selectedPeriod],
  );

Interestingly @Anthony_Frehner , the popover has a hidePopover() method – it seems to advertise that it supports the Popover API. However, at runtime there is an error that it is not implemented. Is the Popover API going to be implemented for popover? The typings and the runtime expose this method – seems a bit odd that wouldn’t work given that.

That’s an interesting approach. I gave it a few tries and wasn’t able to get it to work.

I have a use case where I have some buttons inside an <s-clickable and I didn’t want to necessarily toggle the popover when they were clicked

What I was able to do though was something along the lines of below. It’s a combination of keeping the command and commandFor but also programatically closing the popover as well. I wasn’t able to get a CustomEvent working at all like your example so it’s not fully browser compatible

export default function FormColorPicker() {
  const activatorId = useId();
  const popoverId = useId();

  const activatorRef = useRef<HTMLDivElement>(null);
  const popoverRef = useRef<HTMLElementTagNameMap['s-popover']>(null);

  const hidePopover = useCallback(() => {
    const popover = popoverRef.current;
    const activator = activatorRef.current;

    if (!popover || !activator) return

    // @ts-ignore
    popover.dispatchEvent(new CommandEvent("command", {
      source: activator,
      command: "--hide",
    }))
  }, [])

  return (
    <>
      <div ref={activatorRef} id={activatorId}>
        <s-clickable
          padding="small-200"
          borderRadius="base"
          commandFor={popoverId}
          command="--toggle"
        >
          <s-stack direction="inline" gap="base" justifyContent="space-between">
            <s-text>Some label</s-text>

            <s-button-group gap="none">
              <s-button
                slot="secondary-actions"
                onClick={e => {
                  e.preventDefault()
                  e.stopPropagation()

                  field.form.resetField(field.name)

                  hidePopover()
                }}
                tone="critical"
              >
                Reset
              </s-button>

              <s-button
                slot="secondary-actions"
                onClick={e => {
                  e.preventDefault()
                  e.stopPropagation()

                  field.handleChange(null);

                  hidePopover()
                }}
                tone="critical"
              >
                Clear
              </s-button>
            </s-button-group>
          </s-stack>
        </s-clickable>
      </div>

      <s-popover id={popoverId} ref={popoverRef}>
        My popover content
      </s-popover>
    </>
  );
}

@DanGamble can you tell me what browser my approach isn’t working in? I abridged it a bit and I wonder if I left an important piece out. I’d love to know where it doesn’t work because I’m going to rely on this in production.

If it works for you then awesome, it’s just that snippet alone wouldn’t work for me. I had to use CommandEvent . I wasn’t able to get CustomEvent to work for me at all

On which browser does it not work? I’d love to reproduce and maybe explore a fix. Issue with CommandEvent is it’s not supported by Safari.

I was just using Arc (Chrome). I’m sure I was doing something wrong if yours is working, I just wasn’t able to with a combination of CustomEvent or extending Event like Shopify do as well for their polyfill

I have it working as an auto complete, my issue is that if the results don’t fit on screen the component renders it all above or below the view window and I haven’t been able to figure out how to make it scrollable properly. It does work correct if the amount of content is small enough to fit on the screen but I want it to work like how the auto complete in Polaris React does, as I already have clickable chips filling in the filter role so I am so close to having it be the same.

Hey, just wanted to provide an update here; we’re still thinking/looking into this, as there are accessibility concerns; if we were to introduce these methods, we would need some way to wire up the activator element to the popover and things like that.

Thanks everyone.