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(ui-select,ui-simple-select): add support for rendering selected … #1838

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

ToMESSKa
Copy link
Contributor

@ToMESSKa ToMESSKa commented Dec 20, 2024

…option's before and after content in Select and SimpleSelect input

Closes: INSTUI-4225

TEST PLAN:

  • test the case with icons before the options:
    1. open the last example in Select (Icons)
    2. set isOptionContentAppliedToInput to true on Select
    3. the selected option's icon's should appear now in the beginning the input field and also when user selects a new option from the dropdown

  • test the case with icons after the options:
    1. set isOptionContentAppliedToInput to true on Select in the last example
    2. in SingleSelectExample's options props, rewrite renderBeforeLabel to renderBeforeLabel
    3. in Select.Option replace renderBeforeLabel={option.renderBeforeLabel} with renderAfterLabel=. {option.renderAfterLabel}
    4. now the arrow icon should be replaced with the selected option's icon

  • test the case when options have both before and after icons, both should be rendered

  • test the case when options are grouped: see the group example in Select (Composing option groups), it should render the selected option's before or after icon or both

  • test the case when none of the Select.Options' isSelected prop is true, the icon should not be rendered and a there should be a warning

  • test the case when Select's inputValue is empty, the icon should not be rendered

  • test the case when Select's own renderBeforeInput or renderAfterInput prop is set, it should display the selected option's content (icon) instead of Select's renderBeforeInput or renderAfterInput value

  • test the case when one of option has an empty renderAfterInput value, the default arrow icon should be rendered, but if another option with a non-empty renderAfterInput is selected, that option's icon should be rendered

  • test all the cases above in SimpleSelect

  • review the updated documentation and the new tests

@ToMESSKa ToMESSKa self-assigned this Dec 20, 2024
Copy link

github-actions bot commented Dec 20, 2024

PR Preview Action v1.6.0

🚀 View preview at
https://instructure.github.io/instructure-ui/pr-preview/pr-1838/

Built to branch gh-pages at 2025-01-28 12:59 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@ToMESSKa ToMESSKa force-pushed the INSTUI-4225_support_for_icon_rendering_in_select_simple_select_input_and_automatic_label_content_propagation branch 6 times, most recently from 5be2cc4 to a2725ad Compare January 16, 2025 15:25
@ToMESSKa ToMESSKa marked this pull request as ready for review January 17, 2025 14:48
@ToMESSKa ToMESSKa requested a review from matyasf January 17, 2025 14:49
Copy link
Collaborator

@matyasf matyasf left a comment

Choose a reason for hiding this comment

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

Looks good, see my remarks. Also please check out Chromatic, maybe this prop should be ignored there (see excludeProps in the examples.ts files)?

position: 'before' | 'after',
defaultReturn: Renderable
): Renderable {
const isInputValueEmpty = inputValue === ''
Copy link
Collaborator

Choose a reason for hiding this comment

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

What if this is null or undefined? I think we should handle these cases

Copy link
Contributor Author

@ToMESSKa ToMESSKa Jan 28, 2025

Choose a reason for hiding this comment

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

@matyasf I added the excludeProps to isOptionContentAppliedToInput. Also now my code handles null or undefined inputValue.

packages/ui-select/src/Select/index.tsx Show resolved Hide resolved
Comment on lines +608 to +613
return console.warn(
"isOptionContentAppliedToInput is set but no option has an isSelected='true' prop so desired content cannot be displayed in input filed"
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd remove this, this can be a valid case if there is nothing selected initially.

Copy link
Contributor Author

@ToMESSKa ToMESSKa Jan 28, 2025

Choose a reason for hiding this comment

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

@matyasf I think this is still a valid case if inputValue is set but isSelected is not. In this case, you end up with an option in the input field but without an icon despite you wanting to display an icon. (Try to set the selectedOptionId to null in the state and replace line 22 with this below in the last Select class example (Icons) :
const option = selectedOptionId ? this.getOptionById(selectedOptionId).label : '')

Also If nothing selected initially, this warning will not appear because the user must have set inputValue to null/'' as well (hopefully) and inputValue is already checked for being null in the code before. Try this example when nothing is selected initially, no warning.

class SingleSelectExample extends React.Component {
  state = {
    inputValue: '',
    isShowingOptions: false,
    highlightedOptionId: null,
    selectedOptionId: null,
    announcement: null
  }

  getOptionById(queryId) {
    return this.props.options.find(({ id }) => id === queryId)
  }

  handleShowOptions = () => {
    this.setState({ isShowingOptions: true })
  }

  handleHideOptions = () => {
    const { selectedOptionId } = this.state
    const option = selectedOptionId ? this.getOptionById(selectedOptionId).label : ''
    this.setState({
      isShowingOptions: false,
      highlightedOptionId: null,
      inputValue: option,
      announcement: 'List collapsed.'
    })
  }

  handleBlur = () => {
    this.setState({ highlightedOptionId: null })
  }

  handleHighlightOption = (event, { id }) => {
    event.persist()
    const optionsAvailable = `${this.props.options.length} options available.`
    const nowOpen = !this.state.isShowingOptions ? `List expanded. ${optionsAvailable}` : ''
    const option = this.getOptionById(id).label
    this.setState((state) => ({
      highlightedOptionId: id,
      inputValue: event.type === 'keydown' ? option : state.inputValue,
      announcement: `${option} ${nowOpen}`
    }))
  }

  handleSelectOption = (event, { id }) => {
    const option = this.getOptionById(id).label
    this.setState({
      selectedOptionId: id,
      inputValue: option,
      isShowingOptions: false,
      announcement: `"${option}" selected. List collapsed.`
    })
  }

  render() {
    const {
      inputValue,
      isShowingOptions,
      highlightedOptionId,
      selectedOptionId,
      announcement
    } = this.state

    return (
      <div>
        <Select
          renderLabel="Option Icons"
          assistiveText="Use arrow keys to navigate options."
          inputValue={inputValue}
          isShowingOptions={isShowingOptions}
          onBlur={this.handleBlur}
          onRequestShowOptions={this.handleShowOptions}
          onRequestHideOptions={this.handleHideOptions}
          onRequestHighlightOption={this.handleHighlightOption}
          onRequestSelectOption={this.handleSelectOption}
          isOptionContentAppliedToInput
        >
          {this.props.options.map((option) => (
            <Select.Option
              id={option.id}
              key={option.id}
              isHighlighted={option.id === highlightedOptionId}
              isSelected={option.id === selectedOptionId}
              renderBeforeLabel={option.renderBeforeLabel}
            >
              {option.label}
            </Select.Option>
          ))}
        </Select>
        <Alert
          liveRegion={() => document.getElementById('flash-messages')}
          liveRegionPoliteness="assertive"
          screenReaderOnly
        >
          {announcement}
        </Alert>
      </div>
    )
  }
}

render(
  <View>
    <SingleSelectExample
      options={[
        { id: 'opt1', label: 'Text', renderBeforeLabel: 'XY' },
        { id: 'opt2', label: 'Icon', renderBeforeLabel: <IconCheckSolid /> },
        {
          id: 'opt3',
          label: 'Colored Icon',
          renderBeforeLabel: (props) => {
            let color = 'brand'
            if (props.isHighlighted) color = 'primary-inverse'
            if (props.isSelected) color = 'primary'
            if (props.isDisabled) color = 'warning'
            return <IconInstructureSolid color={color} />
          }
        }
      ]}
    />
  </View>
)

Comment on lines 82 to 95
* Whether or not the before and after content of the selected option appear in the input field.
*
* If the selected option has both before and after content, both will be displayed in the input field.
*
* One of the Select.Options isSelected prop should be true in order to display the content in the input field.
*
* Before and after content will not be displayed, if Select's inputValue is an empty value.
*
* If true and the selected option has a renderAfterInput value, it will replace the default arrow icon.
*
* If true and Select's own renderBeforeInput or renderAfterInput prop is set, it will display the selected option's content instead of Select's renderBeforeInput or renderAfterInput value.
*
* If the selected option's renderAfterInput value is empty, default arrow icon will be rendered.
*/
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please use Markdown here, e.g. code blocks (backticks)

Copy link
Contributor Author

@ToMESSKa ToMESSKa Jan 28, 2025

Choose a reason for hiding this comment

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

@matyasf I placed the backticks where needed.

…option's before and after content in Select and SimpleSelect input

Closes: INSTUI-4225
@ToMESSKa ToMESSKa force-pushed the INSTUI-4225_support_for_icon_rendering_in_select_simple_select_input_and_automatic_label_content_propagation branch from a2725ad to 73c40b2 Compare January 28, 2025 12:53
@ToMESSKa ToMESSKa requested review from balzss and matyasf January 28, 2025 13:22
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.

2 participants