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

Dropdown: Support arbitrary element for text #477

Closed
jeffcarbs opened this issue Sep 8, 2016 · 7 comments
Closed

Dropdown: Support arbitrary element for text #477

jeffcarbs opened this issue Sep 8, 2016 · 7 comments

Comments

@jeffcarbs
Copy link
Member

jeffcarbs commented Sep 8, 2016

My use-case

I have a menu that I want to be triggered by a custom element. It's not a select-type dropdown, just a menu of items that becomes visible after clicking this element.

EDIT: To give a more concrete example, think of something like the github user menu with a custom <Avatar /> component:
screen shot 2016-09-08 at 5 55 15 pm

Currently, you can specify text for the dropdown:

<Dropdown text='something'>
  <Dropdown.Menu>
    {/* items */}
  </Dropdown.Menu>
</Dropdown>
<div tabindex="0" class="ui dropdown">
  <div class="text">
    something
  </div>
  <i class="dropdown icon">
  </i>
  <div class="menu transition">
  </div>
</div>

Ideally I'd want to be able to pass in a custom component that gets rendered where the text currently does. For example:

<Dropdown text={MyCustomComponent}>
  <Dropdown.Menu>
    {/* items */}
  </Dropdown.Menu>
</Dropdown>
<div tabindex="0" class="ui dropdown">
  <MyCustomComponent>
    <!--  whatever else -->
  </MyCustomComponent>
  <i class="dropdown icon">
  </i>
  <div class="menu transition">
  </div>
</div>

Note: This is different than using as={MyCustomComponent}, which would render the outer container as MyCustomComponent:

<Dropdown as={MyCustomComponent}>
  <Dropdown.Menu>
    {/* items */}
  </Dropdown.Menu>
</Dropdown>
<MyCustomComponent tabindex="0" class="ui dropdown">
  <div class="text">
  </div>
  <i class="dropdown icon">
  </i>
  <div class="menu transition">
  </div>
</MyCustomComponent>

I only want to render the text portion to be custom.

I could also see allowing composing the dropdown text manually via Dropdown.Text like:

<Dropdown>
  <Dropdown.Text as={MyCustomComponent} />
  <Dropdown.Menu>
    {/* items */}
  </Dropdown.Menu>
</Dropdown>
<div tabindex="0" class="ui dropdown">
  <MyCustomComponent class='text'>
    <!--  whatever else -->
  </MyCustomComponent>
  <i class="dropdown icon">
  </i>
  <div class="menu transition">
  </div>
</div>

Thoughts?

@levithomason
Copy link
Member

I believe the goal is the ability to use any Dropdown trigger element. If so, I'm in full support as it's something that has been on my thought list as well. I think it would be great to be able to have any "trigger" component open any "menu" component.

I'm not sure where the optimal place to do this is. It may be the text, however here are my concerns with that:

  1. The text is not actually the trigger, the outer Dropdown element is. Seems the goal would be to supply a different "trigger" element opposed to customizing the text.
  2. The text is extremely coupled to use as text for the selection use case. It shows the value of the currently active item. It serves as placeholder text, receiving the default class. When there is a query it is hidden with the filtered class (whether it is a placeholder or the current value).
  3. Intuitively, my hunch is that text is not the first place one would think to customize the trigger element. I hope we can find a more intuitive place/name for the trigger.

All said, I don't have any great proposal ATM either. ...20m later, I experimented with adding a trigger prop which takes an element. I then added a single conditional to the renderText() method to return the trigger if present. The results seem to be pretty awesome:

Dropdown.js

  renderText = () => {
+   if (this.props.trigger) return this.props.trigger
    const { multiple, placeholder, search, text } = this.props
    const { searchQuery, value, open } = this.state

Example

import React from 'react'
import { Image, Dropdown } from 'stardust'

const trigger = <Image avatar src='https://avatars2.githubusercontent.com/u/5067638?v=3&s=40' />

const DropdownUserMenuExample = () => (
  <Dropdown pointing='top right' trigger={trigger} icon='caret down'>
    <Dropdown.Menu>
      <Dropdown.Item disabled>
        <div>Signed In as levithomason</div>
        <strong>levithomason</strong>
      </Dropdown.Item>
      <Dropdown.Divider />
      <Dropdown.Item>Your Profile</Dropdown.Item>
      <Dropdown.Item>Your Stars</Dropdown.Item>
      <Dropdown.Item>Explore</Dropdown.Item>
      <Dropdown.Item>Integrations</Dropdown.Item>
      <Dropdown.Item>Help</Dropdown.Item>
      <Dropdown.Divider />
      <Dropdown.Item>Settings</Dropdown.Item>
      <Dropdown.Item>Sign Out</Dropdown.Item>
    </Dropdown.Menu>
  </Dropdown>
)

export default DropdownUserMenuExample

Result

http://g.recordit.co/oxojExm79z.gif

I'm not a fan of creating complicated elements and passing them through props, but it is cleaner than trying to parse out a specific child IMO.

Also, this would conflict with any selection type Dropdown, so we'd just have to exclude use of trigger with selection.

Feedback?

@levithomason
Copy link
Member

levithomason commented Sep 9, 2016

Testing more examples, I think you're absolutely right about using the text section of the Dropdown markup. I've still mapped it to trigger for these, but it seems to work extremely well for just about any component:

http://g.recordit.co/PBbweMgA1R.gif

For the Card example, I've passed a Dropdown as a <Card.Content extra /> child, with the trigger being an anchor with an icon and some text:

<Card.Content extra>
  <Dropdown trigger={<a><Icon name='user' /> 16 Friends</a>}>
  ...

For kicks, I also passed an entire Card component as the trigger, which as you'd expect, works just fine.

@levithomason
Copy link
Member

I'd accept a PR for this.

@jeffcarbs
Copy link
Member Author

Awesome!

It seems like there are two main (but pretty different) uses for the Dropdown element:

  • Take the place of a select element.
  • A hidden menu that's shown after clicking a "trigger" (currently just the element itself).

There's a ton of support for the first case, not much for the second. I think having something like a customizable trigger would go along way in making the second case more developer-friendly. I think those examples you whipped up help make the case.

@levithomason
Copy link
Member

Agreed, this is why we created the <Select />. I think we should move toward better separating these use cases.

@akhilaudable
Copy link

akhilaudable commented Apr 5, 2018

I am facing this issue:

Warning: Failed prop type: Invalid prop `as` supplied to `DropdownItem`.

Here is the code:

import {Form, Dimmer, Loader, Message, Header, Dropdown} from 'semantic-ui-react';
import UserPlateFunction from './components/UserPlate';

class Setting extends Component {
render(){
  const {members} = this.state;
  return(
      <Dropdown>
            <Dropdown.Menu>
            {Object.keys(members).map((key, i) => {
                return (
                  <Dropdown.Item 
                  as={UserPlateFunction(members[key].user.gravatar)}
                  />
                  ) 
              })
          } </Dropdown.Menu>
      </Dropdown>
         )
    }
}

The state variables are set in the custom methods(It's too lengthy to add here).

Any idea what's wrong? Thanks in advance.

@brianespinosa
Copy link
Member

@akhilaudable For usage questions use Stack Overflow. Please refrain from commenting very old resolved bugs looking for help.

@Semantic-Org Semantic-Org locked as resolved and limited conversation to collaborators Apr 5, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants