|
1 | 1 | import classNames from 'classnames'; |
2 | | -import React from 'react'; |
| 2 | +import React, { useMemo, useCallback } from 'react'; |
3 | 3 | import PropTypes from 'prop-types'; |
4 | 4 |
|
5 | | -import { uncontrollable } from 'uncontrollable'; |
| 5 | +import { useUncontrolled } from 'uncontrollable'; |
6 | 6 |
|
7 | 7 | import createWithBsPrefix from './utils/createWithBsPrefix'; |
8 | 8 | import NavbarBrand from './NavbarBrand'; |
9 | 9 | import NavbarCollapse from './NavbarCollapse'; |
10 | 10 | import NavbarToggle from './NavbarToggle'; |
11 | | -import { createBootstrapComponent } from './ThemeProvider'; |
| 11 | +import { useBootstrapPrefix } from './ThemeProvider'; |
12 | 12 | import NavbarContext from './NavbarContext'; |
13 | 13 | import SelectableContext from './SelectableContext'; |
14 | 14 |
|
15 | 15 | const propTypes = { |
16 | 16 | /** @default 'navbar' */ |
17 | | - bsPrefix: PropTypes.string.isRequired, |
| 17 | + bsPrefix: PropTypes.string, |
18 | 18 |
|
19 | 19 | /** |
20 | 20 | * The general visual variant a the Navbar. |
@@ -122,103 +122,90 @@ const defaultProps = { |
122 | 122 | collapseOnSelect: false, |
123 | 123 | }; |
124 | 124 |
|
125 | | -class Navbar extends React.Component { |
126 | | - state = { |
127 | | - navbarContext: { |
128 | | - onToggle: () => this.handleToggle(), |
| 125 | +const Navbar = React.forwardRef((props, ref) => { |
| 126 | + let { |
| 127 | + bsPrefix, |
| 128 | + expand, |
| 129 | + variant, |
| 130 | + bg, |
| 131 | + fixed, |
| 132 | + sticky, |
| 133 | + className, |
| 134 | + children, |
| 135 | + // Need to define the default "as" during prop destructuring to be compatible with styled-components github.com/react-bootstrap/react-bootstrap/issues/3595 |
| 136 | + as: Component = 'nav', |
| 137 | + expanded, |
| 138 | + onToggle, |
| 139 | + onSelect, |
| 140 | + collapseOnSelect, |
| 141 | + ...controlledProps |
| 142 | + } = useUncontrolled(props, { |
| 143 | + expanded: 'onToggle', |
| 144 | + }); |
| 145 | + |
| 146 | + bsPrefix = useBootstrapPrefix(bsPrefix, 'navbar'); |
| 147 | + |
| 148 | + const handleCollapse = useCallback( |
| 149 | + (...args) => { |
| 150 | + if (onSelect) onSelect(...args); |
| 151 | + if (collapseOnSelect && expanded) { |
| 152 | + onToggle(false); |
| 153 | + } |
129 | 154 | }, |
130 | | - }; |
131 | | - |
132 | | - static getDerivedStateFromProps({ bsPrefix, expanded }, prevState) { |
133 | | - return { |
134 | | - navbarContext: { |
135 | | - ...prevState.navbarContext, |
136 | | - bsPrefix, |
137 | | - expanded, |
138 | | - }, |
139 | | - }; |
| 155 | + [onSelect, collapseOnSelect, expanded, onToggle], |
| 156 | + ); |
| 157 | + |
| 158 | + // will result in some false positives but that seems better |
| 159 | + // than false negatives. strict `undefined` check allows explicit |
| 160 | + // "nulling" of the role if the user really doesn't want one |
| 161 | + if (controlledProps.role === undefined && Component !== 'nav') { |
| 162 | + controlledProps.role = 'navigation'; |
140 | 163 | } |
| 164 | + let expandClass = `${bsPrefix}-expand`; |
| 165 | + if (typeof expand === 'string') expandClass = `${expandClass}-${expand}`; |
141 | 166 |
|
142 | | - handleCollapse = (...args) => { |
143 | | - const { onToggle, expanded, collapseOnSelect, onSelect } = this.props; |
144 | | - |
145 | | - if (onSelect) onSelect(...args); |
146 | | - if (collapseOnSelect && expanded) { |
147 | | - onToggle(false); |
148 | | - } |
149 | | - }; |
150 | | - |
151 | | - handleToggle = () => { |
152 | | - const { onToggle, expanded } = this.props; |
153 | | - |
154 | | - onToggle(!expanded); |
155 | | - }; |
156 | | - |
157 | | - render() { |
158 | | - const { |
| 167 | + const navbarContext = useMemo( |
| 168 | + () => ({ |
| 169 | + onToggle: () => onToggle(!expanded), |
159 | 170 | bsPrefix, |
160 | | - expand, |
161 | | - variant, |
162 | | - bg, |
163 | | - fixed, |
164 | | - sticky, |
165 | | - className, |
166 | | - children, |
167 | | - // Need to define the default "as" during prop destructuring to be compatible with styled-components github.com/react-bootstrap/react-bootstrap/issues/3595 |
168 | | - as: Component = 'nav', |
169 | | - expanded: _1, |
170 | | - onToggle: _2, |
171 | | - onSelect: _3, |
172 | | - collapseOnSelect: _4, |
173 | | - ...props |
174 | | - } = this.props; |
175 | | - |
176 | | - // will result in some false positives but that seems better |
177 | | - // than false negatives. strict `undefined` check allows explicit |
178 | | - // "nulling" of the role if the user really doesn't want one |
179 | | - if (props.role === undefined && Component !== 'nav') { |
180 | | - props.role = 'navigation'; |
181 | | - } |
182 | | - let expandClass = `${bsPrefix}-expand`; |
183 | | - if (typeof expand === 'string') expandClass = `${expandClass}-${expand}`; |
184 | | - |
185 | | - return ( |
186 | | - <NavbarContext.Provider value={this.state.navbarContext}> |
187 | | - <SelectableContext.Provider value={this.handleCollapse}> |
188 | | - <Component |
189 | | - {...props} |
190 | | - className={classNames( |
191 | | - className, |
192 | | - bsPrefix, |
193 | | - expand && expandClass, |
194 | | - variant && `${bsPrefix}-${variant}`, |
195 | | - bg && `bg-${bg}`, |
196 | | - sticky && `sticky-${sticky}`, |
197 | | - fixed && `fixed-${fixed}`, |
198 | | - )} |
199 | | - > |
200 | | - {children} |
201 | | - </Component> |
202 | | - </SelectableContext.Provider> |
203 | | - </NavbarContext.Provider> |
204 | | - ); |
205 | | - } |
206 | | -} |
| 171 | + expanded, |
| 172 | + }), |
| 173 | + [bsPrefix, expanded, onToggle], |
| 174 | + ); |
| 175 | + |
| 176 | + return ( |
| 177 | + <NavbarContext.Provider value={navbarContext}> |
| 178 | + <SelectableContext.Provider value={handleCollapse}> |
| 179 | + <Component |
| 180 | + ref={ref} |
| 181 | + {...controlledProps} |
| 182 | + className={classNames( |
| 183 | + className, |
| 184 | + bsPrefix, |
| 185 | + expand && expandClass, |
| 186 | + variant && `${bsPrefix}-${variant}`, |
| 187 | + bg && `bg-${bg}`, |
| 188 | + sticky && `sticky-${sticky}`, |
| 189 | + fixed && `fixed-${fixed}`, |
| 190 | + )} |
| 191 | + > |
| 192 | + {children} |
| 193 | + </Component> |
| 194 | + </SelectableContext.Provider> |
| 195 | + </NavbarContext.Provider> |
| 196 | + ); |
| 197 | +}); |
207 | 198 |
|
208 | 199 | Navbar.propTypes = propTypes; |
209 | 200 | Navbar.defaultProps = defaultProps; |
| 201 | +Navbar.displayName = 'Navbar'; |
210 | 202 |
|
211 | | -const DecoratedNavbar = createBootstrapComponent( |
212 | | - uncontrollable(Navbar, { expanded: 'onToggle' }), |
213 | | - 'navbar', |
214 | | -); |
215 | | - |
216 | | -DecoratedNavbar.Brand = NavbarBrand; |
217 | | -DecoratedNavbar.Toggle = NavbarToggle; |
218 | | -DecoratedNavbar.Collapse = NavbarCollapse; |
| 203 | +Navbar.Brand = NavbarBrand; |
| 204 | +Navbar.Toggle = NavbarToggle; |
| 205 | +Navbar.Collapse = NavbarCollapse; |
219 | 206 |
|
220 | | -DecoratedNavbar.Text = createWithBsPrefix('navbar-text', { |
| 207 | +Navbar.Text = createWithBsPrefix('navbar-text', { |
221 | 208 | Component: 'span', |
222 | 209 | }); |
223 | 210 |
|
224 | | -export default DecoratedNavbar; |
| 211 | +export default Navbar; |
0 commit comments