diff --git a/UNRELEASED.md b/UNRELEASED.md index 9ce618b9817..7e81dbe74b5 100644 --- a/UNRELEASED.md +++ b/UNRELEASED.md @@ -22,4 +22,6 @@ Use [the changelog guidelines](https://git.io/polaris-changelog-guidelines) to f ### Code quality +- Migrated `ActionMenu.RollupAction`, `Autocomplete`, `Card`, `EmptySearchResult`, `Form`, `SkeletonPage` and `TopBar` to use hooks instead of withAppProvider ([#2065](https://github.com/Shopify/polaris-react/pull/2065)) + ### Deprecations diff --git a/src/components/ActionMenu/components/RollupActions/RollupActions.tsx b/src/components/ActionMenu/components/RollupActions/RollupActions.tsx index b56b8d2b2b4..963f490e1ca 100644 --- a/src/components/ActionMenu/components/RollupActions/RollupActions.tsx +++ b/src/components/ActionMenu/components/RollupActions/RollupActions.tsx @@ -2,11 +2,9 @@ import React from 'react'; import {HorizontalDotsMinor} from '@shopify/polaris-icons'; import {ActionListSection, ActionListItemDescriptor} from '../../../../types'; +import {useI18n} from '../../../../utilities/i18n'; +import {useToggle} from '../../../../utilities/use-toggle'; -import { - withAppProvider, - WithAppProviderProps, -} from '../../../../utilities/with-app-provider'; import {ActionList} from '../../../ActionList'; import {Button} from '../../../Button'; import {Popover} from '../../../Popover'; @@ -20,63 +18,40 @@ export interface RollupActionsProps { sections?: ActionListSection[]; } -type ComposedProps = RollupActionsProps & WithAppProviderProps; +export function RollupActions({items = [], sections = []}: RollupActionsProps) { + const i18n = useI18n(); -interface State { - rollupOpen: boolean; -} - -class RollupActions extends React.PureComponent { - state: State = { - rollupOpen: false, - }; - - render() { - const { - items = [], - sections = [], - polaris: {intl}, - } = this.props; - const {rollupOpen} = this.state; + const [rollupOpen, toggleRollupOpen] = useToggle(false); - if (items.length === 0 && sections.length === 0) { - return null; - } - - const activatorMarkup = ( -
-
- ); - - return ( - - - - ); + if (items.length === 0 && sections.length === 0) { + return null; } - private handleRollupToggle = () => { - this.setState(({rollupOpen}) => ({rollupOpen: !rollupOpen})); - }; + const activatorMarkup = ( +
+
+ ); + + return ( + + + + ); } - -// Use named export once withAppProvider is refactored away -// eslint-disable-next-line import/no-default-export -export default withAppProvider()(RollupActions); diff --git a/src/components/ActionMenu/components/RollupActions/index.ts b/src/components/ActionMenu/components/RollupActions/index.ts index 6d5beb40ea1..483ffad2aa4 100644 --- a/src/components/ActionMenu/components/RollupActions/index.ts +++ b/src/components/ActionMenu/components/RollupActions/index.ts @@ -1,3 +1 @@ -import RollupActions, {RollupActionsProps} from './RollupActions'; - -export {RollupActions, RollupActionsProps}; +export {RollupActions, RollupActionsProps} from './RollupActions'; diff --git a/src/components/ActionMenu/components/RollupActions/tests/RollupActions.test.tsx b/src/components/ActionMenu/components/RollupActions/tests/RollupActions.test.tsx index 39ce26f6912..a1db3f9e5b9 100644 --- a/src/components/ActionMenu/components/RollupActions/tests/RollupActions.test.tsx +++ b/src/components/ActionMenu/components/RollupActions/tests/RollupActions.test.tsx @@ -11,7 +11,7 @@ import { Section as ActionListSection, } from '../../../../ActionList/components'; -import RollupActions, {RollupActionsProps} from '../RollupActions'; +import {RollupActions, RollupActionsProps} from '../RollupActions'; type Wrapper = ReactWrapper; diff --git a/src/components/Autocomplete/Autocomplete.tsx b/src/components/Autocomplete/Autocomplete.tsx index fc2e1ad4c0c..471b1871890 100644 --- a/src/components/Autocomplete/Autocomplete.tsx +++ b/src/components/Autocomplete/Autocomplete.tsx @@ -1,10 +1,7 @@ import React from 'react'; +import {useI18n} from '../../utilities/i18n'; import {ActionListItemDescriptor} from '../../types'; -import { - withAppProvider, - WithAppProviderProps, -} from '../../utilities/with-app-provider'; import {PreferredPosition} from '../PositionedOverlay'; import {OptionDescriptor} from '../OptionList'; import {Spinner} from '../Spinner'; @@ -41,64 +38,55 @@ export interface AutocompleteProps { onLoadMoreResults?(): void; } -type CombinedProps = AutocompleteProps & WithAppProviderProps; +export function Autocomplete({ + id, + options, + selected, + textField, + preferredPosition, + listTitle, + allowMultiple, + loading, + actionBefore, + willLoadMoreResults, + emptyState, + onSelect, + onLoadMoreResults, +}: AutocompleteProps) { + const i18n = useI18n(); -class Autocomplete extends React.PureComponent { - static TextField = TextField; - static ComboBox = ComboBox; - - render() { - const { - id, - options, - selected, - textField, - preferredPosition, - listTitle, - allowMultiple, - loading, - actionBefore, - willLoadMoreResults, - emptyState, - onSelect, - onLoadMoreResults, - polaris: {intl}, - } = this.props; - - const spinnerMarkup = loading ? ( -
- -
- ) : null; + const spinnerMarkup = loading ? ( +
+ +
+ ) : null; - const conditionalOptions = loading && !willLoadMoreResults ? [] : options; - const conditionalAction = - actionBefore && actionBefore !== [] ? [actionBefore] : undefined; + const conditionalOptions = loading && !willLoadMoreResults ? [] : options; + const conditionalAction = + actionBefore && actionBefore !== [] ? [actionBefore] : undefined; - return ( - - ); - } + return ( + + ); } -// Use named export once withAppProvider is refactored away -// eslint-disable-next-line import/no-default-export -export default withAppProvider()(Autocomplete); +Autocomplete.TextField = TextField; +Autocomplete.ComboBox = ComboBox; diff --git a/src/components/Autocomplete/index.ts b/src/components/Autocomplete/index.ts index 2b5820e2f8e..e9a10de0eb3 100644 --- a/src/components/Autocomplete/index.ts +++ b/src/components/Autocomplete/index.ts @@ -1,3 +1 @@ -import Autocomplete, {AutocompleteProps} from './Autocomplete'; - -export {Autocomplete, AutocompleteProps}; +export {Autocomplete, AutocompleteProps} from './Autocomplete'; diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx index 8f32a72b971..b48330ff9dc 100644 --- a/src/components/Card/Card.tsx +++ b/src/components/Card/Card.tsx @@ -1,18 +1,14 @@ import React from 'react'; +import {useI18n} from '../../utilities/i18n'; import {classNames} from '../../utilities/css'; - -import {ButtonGroup} from '../ButtonGroup'; +import {useToggle} from '../../utilities/use-toggle'; import {WithinContentContext} from '../../utilities/within-content-context'; +import {ButtonGroup} from '../ButtonGroup'; import {DisableableAction, ComplexAction} from '../../types'; import {ActionList} from '../ActionList'; import {Button, buttonFrom} from '../Button'; import {Popover} from '../Popover'; -import { - withAppProvider, - WithAppProviderProps, -} from '../../utilities/with-app-provider'; - import {Header, Section, Subsection} from './components'; import styles from './Card.scss'; @@ -35,99 +31,79 @@ export interface CardProps { secondaryFooterActionsDisclosureText?: string; } -export type CombinedProps = CardProps & WithAppProviderProps; - -interface State { - secondaryFooterActionsPopoverOpen: boolean; -} - -class Card extends React.PureComponent { - static Section = Section; - static Header = Header; - static Subsection = Subsection; - - state: State = { - secondaryFooterActionsPopoverOpen: false, - }; - - render() { - const { - children, - title, - subdued, - sectioned, - actions, - primaryFooterAction, - secondaryFooterActions, - secondaryFooterActionsDisclosureText, - polaris: {intl}, - } = this.props; - - const className = classNames(styles.Card, subdued && styles.subdued); - - const headerMarkup = - title || actions ?
: null; - - const content = sectioned ?
{children}
: children; - - const primaryFooterActionMarkup = primaryFooterAction - ? buttonFrom(primaryFooterAction, {primary: true}) - : null; - - let secondaryFooterActionsMarkup = null; - if (secondaryFooterActions && secondaryFooterActions.length) { - if (secondaryFooterActions.length === 1) { - secondaryFooterActionsMarkup = buttonFrom(secondaryFooterActions[0]); - } else { - secondaryFooterActionsMarkup = ( - - - {secondaryFooterActionsDisclosureText || - intl.translate('Polaris.Common.more')} - - } - onClose={this.toggleSecondaryActionsPopover} - > - - - - ); - } +export function Card({ + children, + title, + subdued, + sectioned, + actions, + primaryFooterAction, + secondaryFooterActions, + secondaryFooterActionsDisclosureText, +}: CardProps) { + const i18n = useI18n(); + + const [ + secondaryActionsPopoverOpen, + toggleSecondaryActionsPopoverOpen, + ] = useToggle(false); + + const className = classNames(styles.Card, subdued && styles.subdued); + + const headerMarkup = + title || actions ?
: null; + + const content = sectioned ?
{children}
: children; + + const primaryFooterActionMarkup = primaryFooterAction + ? buttonFrom(primaryFooterAction, {primary: true}) + : null; + + let secondaryFooterActionsMarkup = null; + if (secondaryFooterActions && secondaryFooterActions.length) { + if (secondaryFooterActions.length === 1) { + secondaryFooterActionsMarkup = buttonFrom(secondaryFooterActions[0]); + } else { + secondaryFooterActionsMarkup = ( + + + {secondaryFooterActionsDisclosureText || + i18n.translate('Polaris.Common.more')} + + } + onClose={toggleSecondaryActionsPopoverOpen} + > + + + + ); } - - const footerMarkup = - primaryFooterActionMarkup || secondaryFooterActionsMarkup ? ( -
- - {secondaryFooterActionsMarkup} - {primaryFooterActionMarkup} - -
- ) : null; - - return ( - -
- {headerMarkup} - {content} - {footerMarkup} -
-
- ); } - private toggleSecondaryActionsPopover = () => { - this.setState(({secondaryFooterActionsPopoverOpen}) => { - return { - secondaryFooterActionsPopoverOpen: !secondaryFooterActionsPopoverOpen, - }; - }); - }; + const footerMarkup = + primaryFooterActionMarkup || secondaryFooterActionsMarkup ? ( +
+ + {secondaryFooterActionsMarkup} + {primaryFooterActionMarkup} + +
+ ) : null; + + return ( + +
+ {headerMarkup} + {content} + {footerMarkup} +
+
+ ); } -// Use named export once withAppProvider is refactored away -// eslint-disable-next-line import/no-default-export -export default withAppProvider()(Card); +Card.Section = Section; +Card.Header = Header; +Card.Subsection = Subsection; diff --git a/src/components/Card/index.ts b/src/components/Card/index.ts index ecb4afe192b..9b8a3676a38 100644 --- a/src/components/Card/index.ts +++ b/src/components/Card/index.ts @@ -1,3 +1 @@ -import Card, {CardProps} from './Card'; - -export {Card, CardProps}; +export {Card, CardProps} from './Card'; diff --git a/src/components/EmptySearchResult/EmptySearchResult.tsx b/src/components/EmptySearchResult/EmptySearchResult.tsx index 070850b4415..2568ca1312b 100644 --- a/src/components/EmptySearchResult/EmptySearchResult.tsx +++ b/src/components/EmptySearchResult/EmptySearchResult.tsx @@ -1,9 +1,6 @@ import React from 'react'; -import { - withAppProvider, - WithAppProviderProps, -} from '../../utilities/with-app-provider'; +import {useI18n} from '../../utilities/i18n'; import {DisplayText} from '../DisplayText'; import {TextStyle} from '../TextStyle'; import {Image} from '../Image'; @@ -18,40 +15,30 @@ export interface EmptySearchResultProps { withIllustration?: boolean; } -type CombinedProps = EmptySearchResultProps & WithAppProviderProps; - -class EmptySearchResult extends React.PureComponent { - render() { - const { - title, - description, - withIllustration, - polaris: {intl}, - } = this.props; - - const altText = intl.translate('Polaris.EmptySearchResult.altText'); - - const descriptionMarkup = description ?

{description}

: null; - - const illustrationMarkup = withIllustration ? ( - {altText} - ) : null; - - return ( - - {illustrationMarkup} - {title} - {descriptionMarkup} - - ); - } +export function EmptySearchResult({ + title, + description, + withIllustration, +}: EmptySearchResultProps) { + const i18n = useI18n(); + const altText = i18n.translate('Polaris.EmptySearchResult.altText'); + + const descriptionMarkup = description ?

{description}

: null; + + const illustrationMarkup = withIllustration ? ( + {altText} + ) : null; + + return ( + + {illustrationMarkup} + {title} + {descriptionMarkup} + + ); } - -// Use named export once withAppProvider is refactored away -// eslint-disable-next-line import/no-default-export -export default withAppProvider()(EmptySearchResult); diff --git a/src/components/EmptySearchResult/index.ts b/src/components/EmptySearchResult/index.ts index 17728ce8a7d..060c6491b20 100644 --- a/src/components/EmptySearchResult/index.ts +++ b/src/components/EmptySearchResult/index.ts @@ -1,3 +1 @@ -import EmptySearchResult, {EmptySearchResultProps} from './EmptySearchResult'; - -export {EmptySearchResult, EmptySearchResultProps}; +export {EmptySearchResult, EmptySearchResultProps} from './EmptySearchResult'; diff --git a/src/components/EmptySearchResult/tests/EmptySearchResult.test.tsx b/src/components/EmptySearchResult/tests/EmptySearchResult.test.tsx index a25fa7f1a4d..bbc1617e5d0 100644 --- a/src/components/EmptySearchResult/tests/EmptySearchResult.test.tsx +++ b/src/components/EmptySearchResult/tests/EmptySearchResult.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {mountWithAppProvider} from 'test-utilities/legacy'; import {DisplayText, TextStyle} from 'components'; -import EmptySearchResult from '../EmptySearchResult'; +import {EmptySearchResult} from '../EmptySearchResult'; import {emptySearch} from '../illustrations'; describe('', () => { diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index 56bab8fe2b4..f9f7cc6f956 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -1,10 +1,7 @@ -import React from 'react'; +import React, {useCallback} from 'react'; import {VisuallyHidden} from '../VisuallyHidden'; -import { - withAppProvider, - WithAppProviderProps, -} from '../../utilities/with-app-provider'; +import {useI18n} from '../../utilities/i18n'; export type Enctype = | 'application/x-www-form-urlencoded' @@ -42,61 +39,60 @@ export interface FormProps { onSubmit(event: React.FormEvent): void; } -type CombinedProps = FormProps & WithAppProviderProps; +export function Form({ + acceptCharset, + action, + autoComplete, + children, + encType, + implicitSubmit = true, + method = 'post', + name, + noValidate, + preventDefault = true, + target, + onSubmit, +}: FormProps) { + const i18n = useI18n(); -class Form extends React.PureComponent { - render() { - const { - acceptCharset, - action, - autoComplete, - children, - encType, - implicitSubmit = true, - method = 'post', - name, - noValidate, - target, - polaris: {intl}, - } = this.props; - const autoCompleteInputs = normalizeAutoComplete(autoComplete); + const handleSubmit = useCallback( + (event: React.FormEvent) => { + if (!preventDefault) { + return; + } - const submitMarkup = implicitSubmit ? ( - - - - ) : null; + event.preventDefault(); + onSubmit(event); + }, + [onSubmit, preventDefault], + ); - return ( -
- {children} - {submitMarkup} -
- ); - } - - private handleSubmit = (event: React.FormEvent) => { - const {preventDefault = true, onSubmit} = this.props; + const autoCompleteInputs = normalizeAutoComplete(autoComplete); - if (!preventDefault) { - return; - } + const submitMarkup = implicitSubmit ? ( + + + + ) : null; - event.preventDefault(); - onSubmit(event); - }; + return ( +
+ {children} + {submitMarkup} +
+ ); } function normalizeAutoComplete(autoComplete?: boolean) { @@ -106,7 +102,3 @@ function normalizeAutoComplete(autoComplete?: boolean) { return autoComplete ? 'on' : 'off'; } - -// Use named export once withAppProvider is refactored away -// eslint-disable-next-line import/no-default-export -export default withAppProvider()(Form); diff --git a/src/components/Form/index.ts b/src/components/Form/index.ts index 231b5d017d3..52ccf1629a5 100644 --- a/src/components/Form/index.ts +++ b/src/components/Form/index.ts @@ -1,3 +1 @@ -import Form, {FormProps, Enctype, Method, Target} from './Form'; - -export {Form, FormProps, Enctype, Method, Target}; +export {Form, FormProps, Enctype, Method, Target} from './Form'; diff --git a/src/components/Form/tests/Form.test.tsx b/src/components/Form/tests/Form.test.tsx index 94e8f9f5fdb..104a2bc1b7a 100644 --- a/src/components/Form/tests/Form.test.tsx +++ b/src/components/Form/tests/Form.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {mountWithAppProvider} from 'test-utilities/legacy'; -import Form from '../Form'; +import {Form} from '../Form'; const name = 'form-name'; const noValidate = true; diff --git a/src/components/SkeletonPage/SkeletonPage.tsx b/src/components/SkeletonPage/SkeletonPage.tsx index 777b0f3831b..87a027eac6d 100644 --- a/src/components/SkeletonPage/SkeletonPage.tsx +++ b/src/components/SkeletonPage/SkeletonPage.tsx @@ -1,13 +1,11 @@ import React from 'react'; +import {useAppBridge} from '../../utilities/app-bridge'; import {classNames} from '../../utilities/css'; +import {useI18n} from '../../utilities/i18n'; import {DisplayText} from '../DisplayText'; import {SkeletonDisplayText} from '../SkeletonDisplayText'; import {SkeletonBodyText} from '../SkeletonBodyText'; -import { - withAppProvider, - WithAppProviderProps, -} from '../../utilities/with-app-provider'; import styles from './SkeletonPage.scss'; export interface SkeletonPageProps { @@ -34,83 +32,79 @@ interface DeprecatedProps { singleColumn?: boolean; } -export type CombinedProps = SkeletonPageProps & - DeprecatedProps & - WithAppProviderProps; - -class SkeletonPage extends React.PureComponent { - render() { - const { - children, - fullWidth, - narrowWidth, - singleColumn, - primaryAction, - secondaryActions, - title = '', - breadcrumbs, - polaris: {intl}, - } = this.props; - - if (singleColumn) { - // eslint-disable-next-line no-console - console.warn( - 'Deprecation: The singleColumn prop has been renamed to narrowWidth to better represents its use and will be removed in v5.0.', - ); - } - - const className = classNames( - styles.Page, - fullWidth && styles.fullWidth, - (narrowWidth || singleColumn) && styles.narrowWidth, +export type CombinedProps = SkeletonPageProps & DeprecatedProps; + +export function SkeletonPage({ + children, + fullWidth, + narrowWidth, + singleColumn, + primaryAction, + secondaryActions, + title = '', + breadcrumbs, +}: CombinedProps) { + if (singleColumn) { + // eslint-disable-next-line no-console + console.warn( + 'Deprecation: The singleColumn prop has been renamed to narrowWidth to better represents its use and will be removed in v5.0.', ); + } - const headerClassName = classNames( - styles.Header, - breadcrumbs && styles['Header-hasBreadcrumbs'], - secondaryActions && styles['Header-hasSecondaryActions'], - ); + const i18n = useI18n(); + const appBridge = useAppBridge(); - const titleMarkup = title !== null ? renderTitle(title) : null; + const className = classNames( + styles.Page, + fullWidth && styles.fullWidth, + (narrowWidth || singleColumn) && styles.narrowWidth, + ); - const primaryActionMarkup = primaryAction ? ( -
- -
- ) : null; + const headerClassName = classNames( + styles.Header, + breadcrumbs && styles['Header-hasBreadcrumbs'], + secondaryActions && styles['Header-hasSecondaryActions'], + ); - const secondaryActionsMarkup = secondaryActions - ? renderSecondaryActions(secondaryActions) - : null; + const titleMarkup = title !== null ? renderTitle(title) : null; - const breadcrumbMarkup = breadcrumbs ? ( -
- -
- ) : null; - - const headerMarkup = !this.props.polaris.appBridge ? ( -
- {breadcrumbMarkup} -
- {titleMarkup} - {primaryActionMarkup} -
- {secondaryActionsMarkup} -
- ) : null; - - return ( -
- {headerMarkup} -
{children}
+ const primaryActionMarkup = primaryAction ? ( +
+ +
+ ) : null; + + const secondaryActionsMarkup = secondaryActions + ? renderSecondaryActions(secondaryActions) + : null; + + const breadcrumbMarkup = breadcrumbs ? ( +
+ +
+ ) : null; + + const headerMarkup = !appBridge ? ( +
+ {breadcrumbMarkup} +
+ {titleMarkup} + {primaryActionMarkup}
- ); - } + {secondaryActionsMarkup} +
+ ) : null; + + return ( +
+ {headerMarkup} +
{children}
+
+ ); } function renderSecondaryActions(actionCount: number) { @@ -137,9 +131,3 @@ function renderTitle(title: string) { ); return
{titleContent}
; } - -// Use named export once withAppProvider is refactored away -// eslint-disable-next-line import/no-default-export -export default withAppProvider()( - SkeletonPage, -); diff --git a/src/components/SkeletonPage/index.ts b/src/components/SkeletonPage/index.ts index a927fbc979c..da52a845896 100644 --- a/src/components/SkeletonPage/index.ts +++ b/src/components/SkeletonPage/index.ts @@ -1,3 +1 @@ -import SkeletonPage, {SkeletonPageProps} from './SkeletonPage'; - -export {SkeletonPage, SkeletonPageProps}; +export {SkeletonPage, SkeletonPageProps} from './SkeletonPage'; diff --git a/src/components/SkeletonPage/tests/SkeletonPage.test.tsx b/src/components/SkeletonPage/tests/SkeletonPage.test.tsx index 8454969e128..7fa3e3cdb6d 100644 --- a/src/components/SkeletonPage/tests/SkeletonPage.test.tsx +++ b/src/components/SkeletonPage/tests/SkeletonPage.test.tsx @@ -7,7 +7,7 @@ import { DisplayText, SkeletonDisplayText, } from 'components'; -import SkeletonPage from '../SkeletonPage'; +import {SkeletonPage} from '../SkeletonPage'; describe('', () => { it('renders its children', () => { diff --git a/src/components/TopBar/TopBar.tsx b/src/components/TopBar/TopBar.tsx index 41c0f53d045..74fbfbda65b 100644 --- a/src/components/TopBar/TopBar.tsx +++ b/src/components/TopBar/TopBar.tsx @@ -1,12 +1,10 @@ import React from 'react'; import {MobileHamburgerMajorMonotone} from '@shopify/polaris-icons'; import {classNames} from '../../utilities/css'; - import {getWidth} from '../../utilities/get-width'; -import { - withAppProvider, - WithAppProviderProps, -} from '../../utilities/with-app-provider'; +import {useI18n} from '../../utilities/i18n'; +import {useTheme} from '../../utilities/theme'; +import {useForcibleToggle} from '../../utilities/use-toggle'; import {Icon} from '../Icon'; import {Image} from '../Image'; import {UnstyledLink} from '../UnstyledLink'; @@ -35,122 +33,98 @@ export interface TopBarProps { onNavigationToggle?(): void; } -export type ComposedProps = TopBarProps & WithAppProviderProps; - -interface State { - focused: boolean; -} - -class TopBar extends React.PureComponent { - static UserMenu = UserMenu; - static SearchField = SearchField; - static Menu = Menu; - - state: State = { - focused: false, - }; - - render() { - const { - showNavigationToggle, - userMenu, - searchResults, - searchField, - secondaryMenu, - searchResultsVisible, - onNavigationToggle, - onSearchResultsDismiss, - contextControl, - polaris: {intl, theme}, - } = this.props; - const logo = theme && theme.logo; - const {focused} = this.state; - - const className = classNames( - styles.NavigationIcon, - focused && styles.focused, +export function TopBar({ + showNavigationToggle, + userMenu, + searchResults, + searchField, + secondaryMenu, + searchResultsVisible, + onNavigationToggle, + onSearchResultsDismiss, + contextControl, +}: TopBarProps) { + const i18n = useI18n(); + const {logo} = useTheme(); + + const [ + focused, + {forceTrue: forceTrueFocused, forceFalse: forceFalseFocused}, + ] = useForcibleToggle(false); + + const className = classNames( + styles.NavigationIcon, + focused && styles.focused, + ); + + const navigationButtonMarkup = showNavigationToggle ? ( + + ) : null; + + const width = getWidth(logo, 104); + let contextMarkup; + + if (contextControl) { + contextMarkup = ( +
+ {contextControl} +
); - - const navigationButtonMarkup = showNavigationToggle ? ( - - ) : null; - - const width = getWidth(logo, 104); - let contextMarkup; - - if (contextControl) { - contextMarkup = ( -
- {contextControl} -
- ); - } else if (logo) { - contextMarkup = ( -
- - {logo.accessibilityLabel - -
- ); - } - - const searchResultsMarkup = - searchResults && searchResultsVisible ? ( - + - {searchResults} - - ) : null; - - const searchMarkup = searchField ? ( - - {searchField} - {searchResultsMarkup} - - ) : null; - - return ( -
- {navigationButtonMarkup} - {contextMarkup} -
-
{searchMarkup}
-
{secondaryMenu}
- {userMenu} -
+ {logo.accessibilityLabel +
); } - private handleFocus = () => { - this.setState({focused: true}); - }; + const searchResultsMarkup = + searchResults && searchResultsVisible ? ( + + {searchResults} + + ) : null; - private handleBlur = () => { - this.setState({focused: false}); - }; + const searchMarkup = searchField ? ( + + {searchField} + {searchResultsMarkup} + + ) : null; + + return ( +
+ {navigationButtonMarkup} + {contextMarkup} +
+
{searchMarkup}
+
{secondaryMenu}
+ {userMenu} +
+
+ ); } -// Use named export once withAppProvider is refactored away -// eslint-disable-next-line import/no-default-export -export default withAppProvider()(TopBar); +TopBar.UserMenu = UserMenu; +TopBar.SearchField = SearchField; +TopBar.Menu = Menu; diff --git a/src/components/TopBar/index.ts b/src/components/TopBar/index.ts index 83759f1eb5e..86bf3a41b23 100644 --- a/src/components/TopBar/index.ts +++ b/src/components/TopBar/index.ts @@ -1,4 +1,2 @@ -import TopBar, {TopBarProps} from './TopBar'; - -export {TopBar, TopBarProps}; +export {TopBar, TopBarProps} from './TopBar'; export {UserMenuProps, SearchFieldProps} from './components'; diff --git a/src/components/TopBar/tests/TopBar.test.tsx b/src/components/TopBar/tests/TopBar.test.tsx index 88a0d0e4c49..1cb34774db6 100644 --- a/src/components/TopBar/tests/TopBar.test.tsx +++ b/src/components/TopBar/tests/TopBar.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {mountWithAppProvider, findByTestID} from 'test-utilities/legacy'; import {Image, UnstyledLink} from 'components'; -import TopBar from '../TopBar'; +import {TopBar} from '../TopBar'; import {Menu, SearchField, UserMenu, Search} from '../components'; const actions = [ diff --git a/src/utilities/use-toggle.ts b/src/utilities/use-toggle.ts new file mode 100644 index 00000000000..fcb9beefc10 --- /dev/null +++ b/src/utilities/use-toggle.ts @@ -0,0 +1,24 @@ +import {useState, useCallback} from 'react'; + +export function useToggle(initialState: boolean) { + const [state, setState] = useState(initialState); + const toggle = useCallback(() => setState((state) => !state), []); + + // cast needed to say this returns a two item array with the items in + // their specific positions instead of `(typeof state | typeof toggle)[]` + return [state, toggle] as [typeof state, typeof toggle]; +} + +export function useForcibleToggle(initialState: boolean) { + const [state, setState] = useState(initialState); + + const toggles = { + toggle: useCallback(() => setState((state) => !state), []), + forceTrue: useCallback(() => setState(true), []), + forceFalse: useCallback(() => setState(false), []), + }; + + // cast needed to say this returns a two item array with the items in + // their specific positions instead of `(typeof state | typeof toggles)[]` + return [state, toggles] as [typeof state, typeof toggles]; +}