react-bootstrap-typeahead icon indicating copy to clipboard operation
react-bootstrap-typeahead copied to clipboard

Support Bootstrap 5

Open ericgio opened this issue 4 years ago • 15 comments

Bootstrap 5 is currently in beta, so I'm creating this issue to track the changes this library will need to make to support the new version out of the box. Please comment if you come across anything not working.

As a general workaround, the typeahead's render props and child render function can be used to customize individual components in a way that better supports BS5.

ericgio avatar Feb 22 '21 06:02 ericgio

  • [x] Rename sr-only classname to visually-hidden. https://getbootstrap.com/docs/5.0/migration/#helpers

rkleine avatar Feb 23 '21 11:02 rkleine

dwaynelavon avatar Mar 03 '21 04:03 dwaynelavon

Bootstrap 5 is now released. Any ETA on when support could be expected?

filipkis avatar May 11 '21 14:05 filipkis

Not exactly sure it's a Bootstrap5 exclusive issue, but I'm using it with the 2.0.0-beta.2 react-bootstrap. Elements with class invalid-feedback are shown only if they're adjacent to an element with class is-invalid. The Typeahead component has the Input element with the correct class, but it's wrapped in two div's, so the standard CSS from Bootstrap doesn't show the error messages. Putting the is-invalid class in the outermost div resolves the issue.

belinde avatar Jul 12 '21 10:07 belinde

Support for visually-hidden classname added in v5.2.0.

@filipkis: I don't have a timeline on when full, out of the box support will happen. I'll try to add things incrementally, but the close button and floating label changes are both non-trivial.

ericgio avatar Jul 13 '21 05:07 ericgio

BS 5.0.2 | RBT 5.2.0 | Remove button styling is missing: https://prnt.sc/1d683mt @ericgio

frankv12 avatar Jul 19 '21 18:07 frankv12

Any ETA for this? On initial testing it seems like the loading indicator shows text+the spinning animation and the close/delete button seem to be missing (as it shows text only too)

fkrauthan avatar Jul 23 '21 23:07 fkrauthan

Multiselect close buttons not rendering correctly. image

geoffbarcalow avatar Sep 20 '21 19:09 geoffbarcalow

There's been quite a change between BS4 and BS5 in regard how the "close" icon/button is implemented.

In BS4, it was using a <span> with the html character "times" inside a <button>:

<button type="button" class="close" aria-label="Close">
  <span aria-hidden="true">&times;</span>
</button>

And in BS5, this changed to just using a <button> (which uses a SVG background for rendering the actual "X" icon):

<button type="button" class="btn-close" aria-label="Close"></button>

And it looks like the current v5.2.0 lib code is still generating the underlying component code to be compatible to BS4, which includes the child <span>: https://github.com/ericgio/react-bootstrap-typeahead/blob/v5.2.0/src/components/ClearButton.js#L71

However, I solved this in our project, where we use BS5, by adding this SCSS snippet. It's overwriting a the .rbt-close class.

// somewhere you import bootstrap and react-bootstrap-typeahead
@import "~bootstrap/scss/bootstrap";
@import "~react-bootstrap-typeahead/css/Typeahead";


// the class override "hack" for BS5

.rbt-aux {
    & .rbt-close {
        margin-top: unset;
        @extend .btn-close; // inherit all the properties of the bootstrap class `.btn-close`
        pointer-events: auto; /* Override pointer-events: none; above */
        span {
            display: none;  // hide the BS4 compatible span element
        }
    }
}

I didn't have enough time to think of an elegant and simple solution from the library code point of view, to keep both BS4 and BS5 compatibility. My conclusion was that the library must be made aware of which Bootstrap version is using.

rcugut avatar Sep 20 '21 19:09 rcugut

My conclusion was that the library must be made aware of which Bootstrap version is using.

That sounds right. When BS4 came out I added a separate CSS file with BS4 overrides that consumers of the library needed to import as well as the main file. This is most likely the approach I'll need to take for BS5 as well.

The switch to using a background image for the close button is pretty unfortunate from a flexibility point of view, since it makes it much more difficult to customize, eg: applying a different color.

Still no set timeline for complete out of the box support, but I'd like to include it with v6.0, currently in alpha.

ericgio avatar Oct 18 '21 04:10 ericgio

Multiselect close buttons not rendering correctly. image

I'm having the same issue

waltervi avatar Nov 02 '21 15:11 waltervi

In case someone wants an alternative solution to the SCSS fix, I believe this fix works in plain CSS:

.rbt-token .rbt-token-remove-button {
  margin-right: -10px;
}

.rbt-input-wrapper div:nth-child(n+2) {
  padding-left: 10px;
}

It's far from good, but it does the job for my use case. We basically pad the button 10px to the right and adjust everything (cursor position, etc) accordingly -- but only if there is more than one item actually present in the div.

Jorl17 avatar Dec 08 '21 19:12 Jorl17

Something quick and dirty:

const StyledTypeahead = styled(Typeahead)`
  .rbt-close {
    border: none;
    background: transparent;
  }
`

cesarvarela avatar Jan 21 '22 04:01 cesarvarela

The BS5 clear button is now supported as of v6.0.0-alpha.7. Along with the existing Typeahead.css file, you'll also need to add the Typeahead.bs5.css file that is now included with the package.

ericgio avatar Feb 04 '22 20:02 ericgio

As of v6.0.0-alpha.8 you can use floating labels with the typeahead as follows:

<Typeahead
  ...
  renderInput={({ inputRef, referenceElementRef, ...inputProps }) => (
    <Hint>
      <FloatingLabel controlId="name-input" label="Name">
          <Form.Control
            {...inputProps}
            ref={(node) => {
              inputRef(node);
              referenceElementRef(node);
            }}
          />
      </FloatingLabel>
    </Hint>
  )}
/>

ericgio avatar Feb 11 '22 08:02 ericgio

Bootstrap v5 officially supported as of v6.0.0

ericgio avatar Sep 05 '22 00:09 ericgio

@ericgio

As of v6.0.0-alpha.8 you can use floating labels with the typeahead as follows:

<Typeahead
  ...
  renderInput={({ inputRef, referenceElementRef, ...inputProps }) => (
    <Hint>
      <FloatingLabel controlId="name-input" label="Name">
          <Form.Control
            {...inputProps}
            ref={(node) => {
              inputRef(node);
              referenceElementRef(node);
            }}
          />
      </FloatingLabel>
    </Hint>
  )}
/>

I tried this implementation provided and this does work. Thank you!

However I feel like this is a rather ugly implementation. Couldn't some behavior like this be provided like a property or something, so maybe we could open a feature request?

Also I don't really understand what the <Hint> in this example is needed for as it has some hardcoded style tag which destroys the width behavior of the element. Seems to work also without the <Hint> though I am not sure if this will rather be a problem for some features I haven't yet tested.

nikischin avatar Aug 23 '23 22:08 nikischin

@nikischin

I feel like this is a rather ugly implementation. Couldn't some behavior like this be provided like a property or something

I agree that customizing the input this way is a bit more difficult for the developer than simply setting some prop to true. On the other hand, introducing that prop adds complexity to the code and makes the package a bit more heavyweight and ugly for everyone (especially me 😄). I'd like to support floating labels in the component, but I'm not sure they're common enough to warrant the additional complexity of being baked in. The approach above feels like the right compromise to me and provides enough flexibility for developers to tailor the implementation to their needs.

ericgio avatar Aug 23 '23 22:08 ericgio

@ericgio thank you so much for the fast reply! Generally speaking I can totally understand! Though from my perspective as one of those using the floating labels it's rather not so satisfying hah. Though what to support and what not is in the end up to you and I totally do understand your argumentation.

Talking about the second part of my comment (which partly got lost in Github formatting) could you give some information why the <Hint> is needed in the example and why it needs to have a hardcoded style tag like position: relative; flex: 1 1 0%;... as for my implementation this does effect the size of the input and makes it not behave like the standard Bootstrap <Form.Controls>

Also I guess there is no chance of implementing the floating label for a multi input? :)

nikischin avatar Aug 23 '23 23:08 nikischin

I was trying to create a custom implementation for the floatingLabel on a multi typeahead, though, it does not work as expected, not sure what I am doing wrong, though I cannot see any of the selected options as the selected is always undefined.

    <Typeahead
            id={'th' + formId}
            inputProps={{ className: 'left-icon icon-tag' }}
            type='text'
            placeholder='Tags'
            options={[ '2023', 'Indoor', 'Outdoor' ]}
            defaultSelected={[ '2023' ]}
            multiple={true}
            renderInput={({ inputRef, referenceElementRef, onRemove, selected, ...inputProps })=> (
                <Hint className='rbt-hint'>
                    {selected?.map((option, idx)=> (
                        <Token
                            key={idx}
                            onRemove={onRemove}
                            option={option}
                            className="medal-token"
                        >
                            {option.medal}
                        </Token>
                    ))}
                    <FloatingLabel controlId={formId} label={inputProps.placeholder}>
                        <FormControl
                            {...inputProps}
                            ref={(node)=> {
                                inputRef(node);
                                referenceElementRef(node);
                            }}
                        />
                    </FloatingLabel>
                </Hint>
            )}
        />

Not yet sure where exactly to place the Token for the optimal appearance, though I cannot see any Token in neither the dom nor the props or anything.

Also I noticed the className provided by inputProps={{ className: 'left-icon icon-tag' }} is translated into inputclassname on multiselect, while it is translated into class/className in normal Typeahead without multi set to true. Seems like a bug to me if I am not mistaken?

nikischin avatar Aug 24 '23 21:08 nikischin

I guess there is no chance of implementing the floating label for a multi input? :)

The multi-input unfortunately does not play well with Bootstrap's floating label due to the way it uses CSS selectors.

I cannot see any of the selected options as the selected is always undefined.

It looks like you're trying to de-structure selected from the first argument in renderInput but it doesn't exist there. There's a second argument that provides the component's internal state, including selected.

the className provided by inputProps is translated into inputclassname on multiselect, while it is translated into class/className in normal Typeahead without multi set to true

That's right. The multi-select input component accepts both a className and inputClassName because the input element is nested within containing elements. So the latter is for the input element, while the former is for the overall containing element. Note that inputProps is exposed mainly to help customize the default implementation. Because you're using a custom implementation with renderInput, you can just apply those classnames directly (eg: to the FormControl)

ericgio avatar Aug 29 '23 05:08 ericgio

Hi, If that can help anyone: here is the css styles that I use to have RBT working with Floating Label:

/**
 * Extend Typeahead to support floating labels.
 */
.form-floating > .rbt .form-control {
  height: calc(3.5rem + calc(var(--bs-border-width) * 2));
  min-height: calc(3.5rem + calc(var(--bs-border-width) * 2));
  line-height: 1.25;
}

.form-floating > .rbt .form-control:focus,
.form-floating > .rbt .form-control:not(:placeholder-shown) {
  padding-top: 1.625rem;
  padding-bottom: 0.625rem;
}

.form-floating > .rbt:has(.form-control:focus) ~ label,
.form-floating > .rbt:has(.form-control:not(:placeholder-shown)) ~ label {
  color: rgba(var(--bs-body-color-rgb), 0.65);
  transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);
}

.form-floating > .rbt:has(.form-control:not(:focus):placeholder-shown) ~ label {
  display: none;
}

Then my JSX looks as simple as expected:

  <Form.FloatingLabel controlId="floatingSelect1" label="Floating label">
    <Typeahead options={options} clearButton={true} allowNew={true} placeholder="Choose an option..." />
  </Form.FloatingLabel>

Testing with react-bootstrap and confirmed with the following options:

  • with or without placeholder
  • with or without clear button
  • with or without allow new

superfaz avatar Feb 02 '24 10:02 superfaz