TheComputerM / svelte-materialify

A Material UI Design Component library for Svelte heavily inspired by vuetify.
https://svelte-materialify.vercel.app
MIT License
622 stars 84 forks source link

Select component question/proposal #19

Closed Florian-Schoenherr closed 4 years ago

Florian-Schoenherr commented 4 years ago

First of all, I want to thank you for making this lib, it's really much easier to work with than the other material component libs. Now I have a question, or maybe several, on how the Selects are supposed to be used (in my usecase)

const items = [
    { name: 'Foo', value: 'foo' },
    { name: 'Bar', value: 'bar' },
    { name: 'Fizz', value: 'fizz' },
    { name: 'Buzz', value: 'buzz' },
 ];

If you use these items, the Select shows the different names inside a ListGroup and after choosing then shows you the chosen value inside a TextField and via binding gives back the value.

My usecase is a little different. I would want my items to have ids, but never show them (or rather, only show them when in details-mode, because otherwise this wouldn't be an issue). Essentially it should work like the normal html-select API, so option = { text: 'Something', id: 'Invisible' } <option value={option}>{option.text}</option>

So, these values are shown: Select: Foo | Bar | Fizz | Buzz Selected: Foo

And the value-binding should return this to me: { name: 'Foo', id: 'someUuid' }

The only workaround I found was to put the id inside the value:-part of items, which then shows the uuid as selected. Or I could overwrite some sort of slot, but I really wouldn't know where to start.

Maybe it would work to export a callback-function for this right here (one of the TextField-props in Select): callback here pls This would also be a nice idea because people could use their own delimiter or do whatever else with it. value={someCallback(value)}

then I could do something like

someCallback(id) {
$someStore = items.find(i => i.id === id);
}
TheComputerM commented 4 years ago

Hey, try this:

<script>
    import * as S from "svelte-materialify";

    const items = [{text: 'Foo', id: 1}, {text: 'Bar', id: 2}];
</script>

<S.MaterialApp>
    <S.Select {items}>
        <span let:item slot="item">
            <S.ListItem value={item.text}>
                {item.text}
            </S.ListItem>
        </span>
        Select
    </S.Select>
</S.MaterialApp>
Florian-Schoenherr commented 4 years ago

In the Svelte-REPL it doesn't work. "Error: Uncaught (in promise): Cannot read property 'c' of undefined" Maybe you fixed something since the last release? But if it works on your end, I'll try tomorrow in my code. On my end it's pretty late. And again, thank you!

TheComputerM commented 4 years ago

I think that is a problem with hydrating components, try advanced installation or this template

Florian-Schoenherr commented 4 years ago

Ok, it runs fine inside my project. Now when I choose something it shows its index inside the TextField, not the text. (also, maybe that matters: my id is a string)

TheComputerM commented 4 years ago

Updated the example, try again.

Florian-Schoenherr commented 4 years ago

Okay, that's how it should look! But it doesn't give back the id when I bind:value on the Select, it gives back the text. I hope I'm not bothering you. Thank you for being so blazingly fast!

TheComputerM commented 4 years ago

The component only outputs the value of the ListItems to be efficient so you have to do something like:

const id = items.find(x => x.text === value[0]).id
Florian-Schoenherr commented 4 years ago

The point of the id is, that there could be texts which are the same. Users could choose between "Foo" and "Foo", which is silly, but then they can choose to show all the details (which changes the text in the Select fields). Maybe my usecase is a little dumb, but I found something that works and shouldn't be less efficient unless the user wants it to.

<script>
  import * as S from "svelte-materialify/src";
  const items = [{text: 'Foo', id: "1"}, {text: 'Bar', id: "2"}];
  let value;
  let label = "Items";
  $: console.log(value);
  const getTextFromId = (val) => typeof val[0] === 'string' ? items.find(i => i.id === val[0]).text : "";
</script>

<S.MaterialApp>
  <S.Select {items} bind:value={value} mandatory callback={getTextFromId}>
    <span let:item slot="item">
      <S.ListItem value={item.id}>
        {item.text}
      </S.ListItem>
    </span>
    {label}
  </S.Select>
</S.MaterialApp>

inside Select:

const join = (val) => val.join(', ');
export let callback = join;

and

<TextField
        {filled}
        {outlined}
        {solo}
        {dense}
        {disabled}
        value={callback(value)}
        {placeholder}
        {hint}
        readonly>

So this is a 3 lines change and also allows devs to specify what string they want to show after choosing (like I said in the first comment, e.g. don't join value via comma, but maybe hyphens or whatever else)

TheComputerM commented 4 years ago

Nice idea, instead of calling the function callback, it is better to call it format, PRs welcome.

Florian-Schoenherr commented 4 years ago

have just thrown that together, didn't have an idea for a name :)