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

feat(playground): allow resizing of playground sections #537

Open
wants to merge 4 commits into
base: canary
Choose a base branch
from

Conversation

chankruze
Copy link

@chankruze chankruze commented Oct 9, 2024

Fixes #524

  • use @spectrum-web-components/split-view in play-ground web-component
  • add sp-split-view type declaration in global.d.ts file for now

@enzonotario I have made some changes but I want you to have a look and check few things listed below. Meanwhile I am working on it to make it responsive:

  • playground css is not broken ?
  • implement mobile layout
  • a better way to infer the types for sp-split-view in JSX namespace (I looked at import { SplitView } from '@spectrum-web-components/split-view'; but couldn't figure it out)

* use `@spectrum-web-components/split-view` in `play-ground` web-component
* add `sp-split-view` type declaration in `global.d.ts` file for now

Signed-off-by: chankruze <[email protected]>
@chankruze
Copy link
Author

chankruze commented Oct 9, 2024

@enzonotario @aralroca I saw that the current playground has only code editor visible in mobile device.

  • Is it intentional?
  • Do we have any plan to show the output in future ?
  • If above changes looks good then I may work on showing preview in mobile devices also. If that's in plan.

image

@chankruze chankruze changed the title feat: allow resizing of playground sections feat(playground): allow resizing of playground sections Oct 9, 2024
@enzonotario
Copy link
Collaborator

mm I see current Playground does display Output on mobile:

brisa build_playground(iPhone 12 Pro)

I think it's because it should detect that is on mobile and then set vertical prop for sp-split-view

A Playground that I like and take as reference is Solid Playground. I like the Desktop view, but not the Mobile view.

Aral also mentioned Qwik Playground: From this, I like this Mobile View. I mean having tabs for select Editor/Output, rather than a Vertical Split View.

@@ -1,17 +1,18 @@
import type { WebContext } from 'brisa';
import "@spectrum-web-components/split-view/sp-split-view.js";
Copy link
Collaborator

@aralroca aralroca Oct 9, 2024

Choose a reason for hiding this comment

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

This works and is fine. However, so that the web component can be consumed anywhere (every server component, etc) and you don't need to add the packages/www/src/global.d.ts type declaration, you can do it this way:

https://brisa.build/building-your-application/components-details/external-libraries#third-party-brisa-web-components-vs-native-web-components

The beauty of this implementation is that it only adds the import when it is consumed and its integration is done throughout the app. It also allows to add better types (attributes, etc), and for the WC of Brisa its part of SSR.

It is only a comment, it is not necessary that you change it for this specific case it is well, I comment it simply so that you know that exist this way.

Copy link
Author

@chankruze chankruze Oct 9, 2024

Choose a reason for hiding this comment

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

Exactly, I anticipated that, therefore asked about this. I am excited for this framework ❤️ Thank you very much for the explanation.

Copy link
Collaborator

@aralroca aralroca left a comment

Choose a reason for hiding this comment

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

Amazing work @chankruze !!! This is very well welcomed! ❤️ Welcome as a contributor

The parts you mention that are missing, we add them in the issue and iterate with different PRs, this already gives an improvement to the current playground.

@aralroca
Copy link
Collaborator

aralroca commented Oct 9, 2024

Ok, here @enzonotario has contributed a lot in the Playground, any advice like the one he has commented is good to keep in mind before merging the PR. I hadn't noticed what @enzonotario said that currently breaks the mobile version, it would be nice to adapt it ☺️

Signed-off-by: chankruze <[email protected]>
@chankruze chankruze force-pushed the canary branch 2 times, most recently from efcb40d to b99c390 Compare October 10, 2024 07:58
@chankruze
Copy link
Author

chankruze commented Oct 10, 2024

@aralroca @enzonotario I have update the mobile layout as per discussion. Prior to that, I tried setting the vertial prop with these approach:

Approach 1
import "@spectrum-web-components/split-view/sp-split-view.js";
import type { WebContext } from "brisa";

export default async function PlayGround(
  { defaultValue }: { defaultValue: string },
  { state, css, cleanup, onMount, self }: WebContext,
) {
  const code = state<string>("");
  const activeTab = state<string>("tab-wc");

  function onReceiveCompiledCode(e: MessageEvent) {
    if (e.data.source !== "brisa-playground-preview") return;
    if (e.data.ready) sendDefaultCode();
    if (typeof e.data.code === "string") {
      code.value = e.data.code;
    }
  }

  function sendDefaultCode() {
    const preview = self.querySelector("#preview-iframe") as HTMLIFrameElement;
    preview?.contentWindow?.postMessage({ code: defaultValue });
  }

  function updateSplitViewOrientation(splitView: HTMLElement) {
    if (window.innerWidth <= 968) {
      splitView.setAttribute("vertical", "");
    } else {
      splitView.removeAttribute("vertical");
    }
  }

  onMount(() => {
    // Ensure DOM elements are accessible after mounting
    const splitView = self.querySelector("sp-split-view") as HTMLElement;

    if (splitView) {
      // Set initial orientation
      updateSplitViewOrientation(splitView);

      // Listen for window resize events
      window.addEventListener("resize", () => updateSplitViewOrientation(splitView));
    }

    window.addEventListener("message", onReceiveCompiledCode);
  });

  cleanup(() => {
    window.removeEventListener("message", onReceiveCompiledCode);
    window.removeEventListener("resize", updateSplitViewOrientation);
  });

  css`
    .playground {
      width: 100%;
      display: flex;
      flex-direction: row;
      gap: 0.5rem;
      margin: 0;
      height: calc(100vh - 100px);
      background-color: var(--playground-bg-color);
    }

    .original-code {
      height: 100%;
    }

    .output {
      height: 100%;
      display: flex;
      flex-direction: column;
      background-color: var(--color-white);

      .tab-list {
        display: flex;
        flex-shrink: 0;
        margin: 0;
      }

      button[role="tab"] {
        flex: 1;
        border: none;
        background: none;
        padding: 0.25rem 0.5rem;
        cursor: pointer;
        border-bottom: 2px solid transparent;
      }
      button[role="tab"]:hover {
        background-color: var(--color-light-gray);
      }
      button[role="tab"][aria-selected="true"] {
        border-bottom: 2px solid var(--color-dark);
      }

      .tab-content {
        display: none;
      }
      .tab-content.active {
        display: flex;
        flex-grow: 1;
      }
    }

    #tab-compiled {
      padding: 0.5rem;
    }
    #tab-compiled textarea {
      display: flex;
      flex-grow: 1;
      resize: none;
      field-sizing: content;
      font-size: 1rem;
      border-radius: 0.5rem;
    }
  `;

  return (
    <sp-split-view
      class="playground"
      resizable
      label="Resize the panels"
    >
      <div class="original-code">
        <slot name="code-editor" />
      </div>
      <div class="output">
        <div role="tablist" class="tab-list">
          <button
            id="tab-wc"
            type="button"
            role="tab"
            title="Web Component"
            aria-label="Web Component"
            aria-selected={activeTab.value === "tab-wc"}
            onClick={() => (activeTab.value = "tab-wc")}
          >
            Web Component
          </button>
          <button
            id="tab-compiled"
            type="button"
            role="tab"
            title="Compiled Code"
            aria-label="Compiled Code"
            aria-selected={activeTab.value === "tab-compiled"}
            onClick={() => (activeTab.value = "tab-compiled")}
          >
            Compiled Code
          </button>
        </div>

        <div
          id="tab-wc"
          class={`tab-content ${activeTab.value === "tab-wc" ? "active" : ""}`}
        >
          <slot name="preview-iframe" />
        </div>

        <div
          id="tab-compiled"
          class={`tab-content ${activeTab.value === "tab-compiled" ? "active" : ""}`}
        >
          <textarea disabled>{code.value}</textarea>
        </div>
      </div>
    </sp-split-view>
  );
}
Approach 2
import "@spectrum-web-components/split-view/sp-split-view.js";
import type { WebContext } from "brisa";

export default async function PlayGround(
  { defaultValue }: { defaultValue: string },
  { state, css, cleanup, onMount, self }: WebContext,
) {
  const code = state<string>("");
  const activeTab = state<string>("tab-wc");

  function updateSplitViewOrientation() {
    const splitView = self.shadowRoot!.querySelector("sp-split-view") as HTMLElement;

    if (window.innerWidth <= 968) {
      splitView.setAttribute("vertical", "");
    } else {
      splitView.removeAttribute("vertical");
    }
  }

  function onReceiveCompiledCode(e: MessageEvent) {
    if (e.data.source !== "brisa-playground-preview") return;
    if (e.data.ready) sendDefaultCode();
    if (typeof e.data.code === "string") {
      code.value = e.data.code;
    }
  }

  function sendDefaultCode() {
    const preview = self.querySelector("#preview-iframe") as HTMLIFrameElement;
    preview.contentWindow?.postMessage({ code: defaultValue });
  }

  onMount(() => {
    // Set initial layout based on screen size
    updateSplitViewOrientation();

    // Listen for window resize events
    window.addEventListener("resize", updateSplitViewOrientation);
    window.addEventListener("message", onReceiveCompiledCode);
  });

  cleanup(() => {
    window.removeEventListener("resize", updateSplitViewOrientation);
    window.removeEventListener("message", onReceiveCompiledCode);
  });

  css`
    .playground {
      width: 100%;
      display: flex;
      flex-direction: row;
      gap: 0.5rem;
      margin: 0;
      height: calc(100vh - 100px);
      background-color: var(--playground-bg-color);
    }

    .original-code {
      height: 100%;
    }

    .output {
      height: 100%;
      display: flex;
      flex-direction: column;
      background-color: var(--color-white);

      .tab-list {
        display: flex;
        flex-shrink: 0;
        margin: 0;
      }

      button[role="tab"] {
        flex: 1;
        border: none;
        background: none;
        padding: 0.25rem 0.5rem;
        cursor: pointer;
        border-bottom: 2px solid transparent;
      }
      button[role="tab"]:hover {
        background-color: var(--color-light-gray);
      }
      button[role="tab"][aria-selected="true"] {
        border-bottom: 2px solid var(--color-dark);
      }

      .tab-content {
        display: none;
      }
      .tab-content.active {
        display: flex;
        flex-grow: 1;
      }
    }

    #tab-compiled {
      padding: 0.5rem;
    }
    #tab-compiled textarea {
      display: flex;
      flex-grow: 1;
      resize: none;
      field-sizing: content;
      font-size: 1rem;
      border-radius: 0.5rem;
    }

    @media (max-width: 968px) {
      .playground {
        flex-direction: column;
      }

      .original-code {
        width: 100%;
      }

      .output {
        width: 100%;
        height: 100%;
      }
    }
  `;

  return (
    <sp-split-view
      class="playground"
      resizable
      label="Resize the panels"
    >
      <div class="original-code">
        <slot name="code-editor" />
      </div>
      <div class="output">
        <div role="tablist" class="tab-list">
          <button
            id="tab-wc"
            type="button"
            role="tab"
            title="Web Component"
            aria-label="Web Component"
            aria-selected={activeTab.value === "tab-wc"}
            onClick={() => (activeTab.value = "tab-wc")}
          >
            Web Component
          </button>
          <button
            id="tab-compiled"
            type="button"
            role="tab"
            title="Compiled Code"
            aria-label="Compiled Code"
            aria-selected={activeTab.value === "tab-compiled"}
            onClick={() => (activeTab.value = "tab-compiled")}
          >
            Compiled Code
          </button>
        </div>

        <div
          id="tab-wc"
          class={`tab-content ${activeTab.value === "tab-wc" ? "active" : ""}`}
        >
          <slot name="preview-iframe" />
        </div>

        <div
          id="tab-compiled"
          class={`tab-content ${activeTab.value === "tab-compiled" ? "active" : ""}`}
        >
          <textarea disabled>{code.value}</textarea>
        </div>
      </div>
    </sp-split-view>
  );
}
Approach 3
import "@spectrum-web-components/split-view/sp-split-view.js";
import type { WebContext } from "brisa";

export default async function PlayGround(
  { defaultValue }: { defaultValue: string },
  { state, css, cleanup, onMount, self }: WebContext,
) {
  const code = state<string>("");
  const activeTab = state<string>("tab-wc");
  const isMobileLayout = state<boolean>(false);

  function updateSplitViewOrientation() {
    isMobileLayout.value = window.innerWidth <= 968;
  }

  function onReceiveCompiledCode(e: MessageEvent) {
    if (e.data.source !== "brisa-playground-preview") return;
    if (e.data.ready) sendDefaultCode();
    if (typeof e.data.code === "string") {
      code.value = e.data.code;
    }
  }

  function sendDefaultCode() {
    const preview = self.querySelector("#preview-iframe") as HTMLIFrameElement;
    preview.contentWindow?.postMessage({ code: defaultValue });
  }

  onMount(() => {
    // Set initial layout based on screen size
    updateSplitViewOrientation();

    // Listen for window resize events
    window.addEventListener("resize", updateSplitViewOrientation);
    window.addEventListener("message", onReceiveCompiledCode);
  });

  cleanup(() => {
    window.removeEventListener("resize", updateSplitViewOrientation);
    window.removeEventListener("message", onReceiveCompiledCode);
  });

  css`
    .playground {
      width: 100%;
      display: flex;
      flex-direction: row;
      gap: 0.5rem;
      margin: 0;
      height: calc(100vh - 100px);
      background-color: var(--playground-bg-color);
    }

    .original-code {
      height: 100%;
    }

    .output {
      height: 100%;
      display: flex;
      flex-direction: column;
      background-color: var(--color-white);

      .tab-list {
        display: flex;
        flex-shrink: 0;
        margin: 0;
      }

      button[role="tab"] {
        flex: 1;
        border: none;
        background: none;
        padding: 0.25rem 0.5rem;
        cursor: pointer;
        border-bottom: 2px solid transparent;
      }
      button[role="tab"]:hover {
        background-color: var(--color-light-gray);
      }
      button[role="tab"][aria-selected="true"] {
        border-bottom: 2px solid var(--color-dark);
      }

      .tab-content {
        display: none;
      }
      .tab-content.active {
        display: flex;
        flex-grow: 1;
      }
    }

    #tab-compiled {
      padding: 0.5rem;
    }
    #tab-compiled textarea {
      display: flex;
      flex-grow: 1;
      resize: none;
      field-sizing: content;
      font-size: 1rem;
      border-radius: 0.5rem;
    }

    @media (max-width: 968px) {
      .playground {
        flex-direction: column;
      }

      .original-code {
        width: 100%;
      }

      .output {
        width: 100%;
        height: 100%;
      }
    }
  `;

  return (
    <sp-split-view
      class="playground"
      resizable
      label="Resize the panels"
      {...(isMobileLayout.value ? { vertical: true } : {})}  {/* Conditionally set the vertical attribute */}
    >
      <div class="original-code">
        <slot name="code-editor" />
      </div>
      <div class="output">
        <div role="tablist" class="tab-list">
          <button
            id="tab-wc"
            type="button"
            role="tab"
            title="Web Component"
            aria-label="Web Component"
            aria-selected={activeTab.value === "tab-wc"}
            onClick={() => (activeTab.value = "tab-wc")}
          >
            Web Component
          </button>
          <button
            id="tab-compiled"
            type="button"
            role="tab"
            title="Compiled Code"
            aria-label="Compiled Code"
            aria-selected={activeTab.value === "tab-compiled"}
            onClick={() => (activeTab.value = "tab-compiled")}
          >
            Compiled Code
          </button>
        </div>

        <div
          id="tab-wc"
          class={`tab-content ${activeTab.value === "tab-wc" ? "active" : ""}`}
        >
          <slot name="preview-iframe" />
        </div>

        <div
          id="tab-compiled"
          class={`tab-content ${activeTab.value === "tab-compiled" ? "active" : ""}`}
        >
          <textarea disabled>{code.value}</textarea>
        </div>
      </div>
    </sp-split-view>
  );
}

But none of them worked as expected (not even vertical resizer appeared). Then I decided to return different jsx (i.e. for mobile) like this 55386ae#diff-00554d16c9f10fab4e6c909be20bb010dfa35634d3ba195371878487f016d6c4 and this worked as expected. But now the issue was it was not resizing due to our css.

Therefore I decided to implement the tabbed panel approach as we discussed earlier yesterday.

Demo

@aralroca
Copy link
Collaborator

aralroca commented Oct 10, 2024

@chankruze I see that this sp-split-view WC adds splitter-pos attribute, maybe we can use this one connected to a signal? We can use signals inside the css template literal to define inside the @media the correct size (if is needed)

Screenshot 2024-10-10 at 11 48 44
  <sp-split-view
      class="playground"
+    onChange={(e) => console.log(e.target.getAttribute('splitter-pos'))}
      resizable
      label="Resize the panels"
    >
     {/* ... */}
    </sp-split-view>

@aralroca
Copy link
Collaborator

@aralroca @enzonotario I have update the mobile layout as per discussion. Prior to that, I tried setting the vertial prop with these approach:

I would try to keep the @media and have it look like it does now in production, but have the middle bar be the one that can be moved. What is the problem you are running into right now with this? If it is something about the height, maybe it can be controlled by the attribute I mentioned above. Thanks!

@media (max-width: 968px) {
.playground {
flex-direction: column;
}
.original-code {
width: 100%;
height: 100%;
}
.output {
width: 100%;
}
}

Screenshot 2024-10-10 at 12 09 24

@chankruze
Copy link
Author

@aralroca Yes, it's just the height, I will try to fix it with the signals in css because we have the value in the attribute of split view

@aralroca
Copy link
Collaborator

@chankruze any news on this? It would be great to finish your fantastic work! 🤗 If there are any problems, I can give you a hand.

@chankruze
Copy link
Author

@aralroca Recently I wasn't able to work on this, tomorrow I'll check few things and let you know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[playground] Allow resizing of playground sections
3 participants