Skip to content

Commit

Permalink
refactor: Add header panel types (carbon-design-system#15096)
Browse files Browse the repository at this point in the history
* refactor: add header panel types

* refactor: Fix README.md

* refactor: remove extra list start

* made props optional

* fix(headerpanel): update typings, add comments

---------

Co-authored-by: Alison Joseph <[email protected]>
Co-authored-by: Andrea N. Cardona <[email protected]>
Co-authored-by: Taylor Jones <[email protected]>
Co-authored-by: Taylor Jones <[email protected]>
  • Loading branch information
5 people authored Dec 4, 2023
1 parent 533e24e commit 93952b9
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 132 deletions.
9 changes: 9 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,15 @@
]
},
{
"login": "danoro96",
"name": "Daniel Castillo",
"avatar_url": "https://avatars.githubusercontent.com/u/52253150?v=4",
"profile": "https://github.com/danoro96",
"contributions": [
"code"
]
},
{
"login": "kuri-sun",
"name": "Ruki",
"avatar_url": "https://avatars.githubusercontent.com/u/62743644?v=4",
Expand Down
132 changes: 0 additions & 132 deletions packages/react/src/components/UIShell/HeaderPanel.js

This file was deleted.

180 changes: 180 additions & 0 deletions packages/react/src/components/UIShell/HeaderPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/**
* Copyright IBM Corp. 2016, 2023
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import cx from 'classnames';
import PropTypes from 'prop-types';
import React, {
useRef,
useState,
ReactNode,
type ComponentProps,
type ForwardedRef,
} from 'react';
import { usePrefix } from '../../internal/usePrefix';
import { keys, match } from '../../internal/keyboard';
import { useWindowEvent } from '../../internal/useEvent';
import { useMergedRefs } from '../../internal/useMergedRefs';

interface HeaderPanelProps {
/**
* Specify whether focus and blur listeners are added. They are by default.
*/
addFocusListeners?: boolean;

/**
* The content that will render inside of the `HeaderPanel`
*/
children?: ReactNode;

/**
* Optionally provide a custom class to apply to the underlying `<li>` node
*/
className?: string;

/**
* Specify whether the panel is expanded
*/
expanded?: boolean;

/**
* Provide the `href` to the id of the element on your package that could
* be target.
*/
href?: string;

/**
* An optional listener that is called a callback to collapse the HeaderPanel
*/
onHeaderPanelFocus?: () => void;
}

const noopFn = () => {};
const HeaderPanel: React.FC<HeaderPanelProps> = React.forwardRef(
function HeaderPanel(
{
children,
className: customClassName,
expanded,
addFocusListeners = true,
onHeaderPanelFocus = noopFn,
href,
...rest
},
ref: ForwardedRef<HTMLDivElement>
) {
const prefix = usePrefix();
const headerPanelReference = useRef<HTMLDivElement>(null);
const headerPanelRef = useMergedRefs([headerPanelReference, ref]);

const controlled = useRef(expanded !== undefined).current;
const [expandedState, setExpandedState] = useState(expanded);
const expandedProp = controlled ? expanded : expandedState;

const [lastClickedElement, setLastClickedElement] =
useState<HTMLElement | null>(null);

const className = cx(`${prefix}--header-panel`, {
[`${prefix}--header-panel--expanded`]: expandedProp,
[customClassName as string]: !!customClassName,
});

const eventHandlers: Partial<
Pick<ComponentProps<'header'>, 'onBlur' | 'onKeyDown'>
> = {};

if (addFocusListeners) {
eventHandlers.onBlur = (event) => {
if (
!event.currentTarget.contains(event.relatedTarget) &&
!lastClickedElement?.classList?.contains('cds--switcher__item-link')
) {
setExpandedState(false);
setLastClickedElement(null);
if (expanded) {
onHeaderPanelFocus();
}
}
};
eventHandlers.onKeyDown = (event) => {
if (match(event, keys.Escape)) {
setExpandedState(false);
onHeaderPanelFocus();
if (href) {
window.location.href = href;
}
}
};
}

useWindowEvent('click', () => {
const focusedElement = document.activeElement as HTMLElement;
setLastClickedElement(focusedElement);

const childJsxElement = children as JSX.Element;

if (
childJsxElement.type?.displayName === 'Switcher' &&
!focusedElement?.closest(`.${prefix}--header-panel--expanded`) &&
!focusedElement?.closest(`.${prefix}--header__action`) &&
!headerPanelReference?.current?.classList.contains(
`${prefix}--switcher`
) &&
expanded
) {
setExpandedState(false);
onHeaderPanelFocus();
}
});

return (
<div
{...rest}
className={className}
ref={headerPanelRef}
{...eventHandlers}>
{children}
</div>
);
}
);

HeaderPanel.propTypes = {
/**
* Specify whether focus and blur listeners are added. They are by default.
*/
addFocusListeners: PropTypes.bool,

/**
* The content that will render inside of the `HeaderPanel`
*/
children: PropTypes.node,

/**
* Optionally provide a custom class to apply to the underlying `<li>` node
*/
className: PropTypes.string,

/**
* Specify whether the panel is expanded
*/
expanded: PropTypes.bool,

/**
* Provide the `href` to the id of the element on your package that could
* be target.
*/
href: PropTypes.string,

/**
* An optional listener that is called a callback to collapse the HeaderPanel
*/
onHeaderPanelFocus: PropTypes.func,
};

HeaderPanel.displayName = 'HeaderPanel';

export default HeaderPanel;

0 comments on commit 93952b9

Please sign in to comment.