kitschpatrol / svelte-tweakpane-ui

A Svelte component library wrapping UI elements from Tweakpane, plus some additional functionality for convenience and flexibility.
https:///kitschpatrol.com/svelte-tweakpane-ui
MIT License
97 stars 2 forks source link

Fitting inline checkboxes #13

Closed cristianvogel closed 1 month ago

cristianvogel commented 1 month ago

Hey, me again!

So now , I can't seem to get the layout I need from inline positioning. I've tried using width on an explicit <Pane> that wraps them but I can't seem to get it right.

Screenshot 2024-06-10 at 14 36 16

{#if paramId === "darkMode"}
<div class="flex items-center">
  <Pane position="inline"  theme={customizedTweakPaneTheme} expanded >
    <Checkbox
      bind:value={darkMode}
      label={"DarkMode:"}
      on:change={(e) =>
        handleCheckBoxEvent({
          paramId: "darkMode",
          value: e.detail.value,
          origin: e.detail.origin,
        })}
    />
  </Pane>
  <Pane position="inline" theme={customizedTweakPaneTheme} expanded >
    <Checkbox
    bind:value={numbers}
    label={"Numbers:"}
    on:change={(e) =>
      handleCheckBoxEvent({
        paramId: "numbers",
        value: e.detail.value,
        origin: e.detail.origin,
      })}
  />
  </Pane>
</div>
{/if}```
kitschpatrol commented 1 month ago

Hey Cristian, Tweakpane really likes the vertical stack layout, so going horizontal is generally a bit uphill. (With the exception of the <ButtonGrid> and <RadioGrid> components.)

There's also a third-party table plugin for vanilla Tweakpane, but this is not integrated with Svelte Tweakpane UI because the approach it takes (managing its own nested Pane instances) isn't really compatible.

For the example you've shared, one thing to remember is the implicit pane feature in Svelte Tweakpane UI, which automatically wraps any component that is not somewhere inside an explicit <Pane> component in an "implicit" <Pane> with position inline, so the extra panes wrapping your checkboxes are probably not necessary. (This is a tricky feature, but it's probably worth it... internally, there's no way for a Tweakpane control to exist without a parent Pane, but I wanted people to be able to get started without worrying about that.)

Also, you can set a theme prop value directly on any stand-alone component.

One thing that will also help is the bladeValueWidth theme value, which changes the width of the right "column" vs. the left inside the Tweakpane blade.

Here's an example combining these points, and using CSS grid for the layout:

<script lang="ts">
  import { Checkbox, Slider } from 'svelte-tweakpane-ui';
  let colors = 0.95;
  let darkMode = true;
  let numbers = true;
</script>

<div class="grid-wrapper">
  <Slider
    bind:value={colors}
    min={0}
    max={1}
    label="Colors"
    theme={{ baseBorderRadius: '0', bladeValueWidth: '244px' }}
  />
  <Checkbox
    bind:value={darkMode}
    label="Dark Mode"
    theme={{ baseBorderRadius: '0', bladeValueWidth: '75.5px' }}
  />
  <Checkbox
    bind:value={numbers}
    label="Numbers"
    theme={{ baseBorderRadius: '0', bladeValueWidth: '80px' }}
  />
</div>

<style>
  div.grid-wrapper {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    max-width: 337px;
  }

  div.grid-wrapper > :global(div:first-child) {
    grid-column: span 2;
  }
</style>

Which gives a component like this:

image

As you can see the bladeValueWidths are a little fussy...

Tweakpane positions the checkboxes for nice alignment with the vertical column established by the other controls, so I think they feel a bit orphaned when laid out horizontally, and again without the vertical grid the strangeness of having labels to the right of the checkbox control sticks out more. (Though they could probably be flipped around with more aggressive CSS...)

Also worth noting, bladeValueWidth can take a % value just fine if you want dynamic widths.

So, it's not easy, but it's possible!

cristianvogel commented 1 month ago

Thanks Eric, that's going to work for me. Although I would like to see a bit more horizontal layout friendliness, as often a lowly checkbox can garner more attention than it ought to. In such control panes, they are usually controlling a relatively minor feature in the state

cristianvogel commented 1 month ago

One more thing;

I was wrapping them explicitly in extra <Pane> tags, because I want to override the expanded, user-expandable attributes, as I do not need the default behaviour that they auto show and hide when they are inline.

Can that also be done on a component basis?

kitschpatrol commented 1 month ago

Ok, sounds good. I'll respond in sections since we're talking about a few different things here. 😅

Auto show / hide

Can you clarify what you mean by this? Is this something happening programmatically elsewhere to show / hide the checkboxes when needed? I don't think the user has any means to expand / collapse an inline Pane unless it has its title prop is set, making the title bar visible... or on a per-component basis if there's a collapsible component like a <Folder> (which does expose userExpandable).

Panes

There's no real harm in specifying extra panes since they would be created (implicitly) anyway, though there are probably minor performance improvements (and some conceptual sanity) to be found in creating a single <Pane> to wrap everything when you can.

Single pane grid approach

There is another strategy to accomplish the horizontal layout with a single explicit <Pane>, based on your original flexbox approach, which makes more sense than grid here.

The CSS is just a bit more brittle than the previous example in my opinion, since it messes with Tweakpane classes directly instead of using a wrapper div.

Also flex-direction: row-reverse; solves the "checkbox on the wrong side" problem nicely, but leaves the issue of making the labels clickable.

<script lang="ts">
    import { Checkbox, Pane, Slider } from 'svelte-tweakpane-ui';
    let colors = 0.95;
    let darkMode = true;
    let numbers = false;
    let oneMore = true;
</script>

<Pane position="inline" theme={{ bladeValueWidth: '244px' }} width={337}>
    <Slider bind:value={colors} min={0} max={1} label="Colors" />
    <Checkbox bind:value={darkMode} label="Dark Mode" />
    <Checkbox bind:value={numbers} label="Numbers" />
    <Checkbox bind:value={oneMore} label="One More" />
</Pane>

<style>
    :global(div.tp-rotv_c) {
        display: flex;
        flex-wrap: wrap;
    }

    :global(div.tp-lblv) {
        flex-grow: 1;
    }

    :global(.tp-lblv:has(.tp-ckbv)) {
        flex-direction: row-reverse;
        flex-grow: 0;
    }

    :global(.tp-lblv_v:has(.tp-ckbv)) {
        width: fit-content;
    }
</style>

Ends up looking like:

Screenshot 2024-06-12 at 02 02 20 of Chromium - httplocalhost5173TestGridLayoutPane svelte@2x
kitschpatrol commented 1 month ago

Closing this since it looks like you have a fix, but feel free to re-open if needed.