-
-
Save snowdence/17dfe29e1b904a6c7ce72bac177e28a8 to your computer and use it in GitHub Desktop.
UIChannelItem.tsx by Ladifire & Cong Nguyen
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Copyright (c) Ladifire, Inc. and its affiliates. | |
* | |
* This source code is licensed under the MIT license found in the | |
* LICENSE file in the root directory of this source tree. | |
*/ | |
import * as React from 'react'; | |
import {Pressable} from '@ladifire-ui-react/tetra-button'; | |
import {TetraTextPairing} from '@ladifire-ui-react/tetra-text'; | |
import {useCometPreloaderImpl as useCometPreloader, CometPressableOverlay} from '@ladifire-ui-react/utils'; | |
import stylex from '@ladifire-opensource/stylex'; | |
import {useHoverAndFocusState} from 'src/utilities/useHoverAndFocusState'; | |
import {InteractiveElementContext} from './InteractiveElementContext'; | |
import {ChannelFocusableTable} from './ChannelFocusableTable'; | |
const styles = stylex.create({ | |
root: { | |
boxSizing: "border-box", | |
position: "relative", | |
flexGrow: 1, | |
flexShrink: 1, | |
minHeight: 0, | |
minWidth: 0, | |
display: "flex", | |
justifyContent: "flex-start", | |
alignItems: "center", | |
flexDirection: "row", | |
border: "none", | |
paddingRight: "var(--wig-spacing-large)" | |
}, | |
tetraLikeRoot: { | |
paddingRight: 8, | |
marginLeft: 8, | |
marginRight: 8, | |
borderRadius: 8, | |
}, | |
focused: { | |
outline: "1px solid var(--accent)" | |
}, | |
selected: { | |
backgroundColor: "var(--wig-selected-background)" | |
}, | |
tetraLikeSelected: { | |
backgroundColor: "var(--hosted-view-selected-state)" | |
}, | |
contentContainer: { | |
borderStyle: "solid", | |
borderWidth: 0, | |
boxSizing: "border-box", | |
display: "flex", | |
flexGrow: 1, | |
flexShrink: 1, | |
margin: 0, | |
minHeight: 0, | |
minWidth: 0, | |
padding: 0, | |
position: "relative", | |
zIndex: 0, | |
justifyContent: "flex-start", | |
alignItems: "center", | |
flexDirection: "row" | |
}, | |
tetraLikeContentContainer: { | |
position: "static" | |
}, | |
content: { | |
borderStyle: "solid", | |
borderWidth: 0, | |
boxSizing: "border-box", | |
display: "flex", | |
flexGrow: 1, | |
flexShrink: 1, | |
margin: 0, | |
minHeight: 0, | |
minWidth: 0, | |
padding: 0, | |
position: "relative", | |
zIndex: 0, | |
justifyContent: "flex-start", | |
alignItems: "center", | |
flexDirection: "row", | |
outline: "none", | |
":hover": { | |
textDecoration: "none" | |
} | |
}, | |
textPairing: { | |
flexGrow: 1, | |
flexBasis: 0, | |
minWidth: 0, | |
paddingTop: 8, | |
paddingBottom: 8, | |
overflow: "hidden", | |
textOverflow: "ellipsis" | |
}, | |
addOnPrimary: { | |
borderStyle: "solid", | |
borderWidth: 0, | |
boxSizing: "border-box", | |
display: "flex", | |
flexDirection: "column", | |
flexShrink: 1, | |
justifyContent: "space-between", | |
marginLeft: 0, | |
minHeight: 0, | |
minWidth: 0, | |
paddingBottom: 0, | |
paddingRight: 0, | |
paddingLeft: 0, | |
paddingTop: 0, | |
zIndex: 0, | |
alignItems: "center", | |
flexGrow: 0, | |
marginBottom: "var(--wig-spacing-small)", | |
marginRight: 12, | |
marginTop: "var(--wig-spacing-small)", | |
position: "relative" | |
}, | |
addOnSecondary: { | |
borderStyle: "solid", | |
borderWidth: 0, | |
boxSizing: "border-box", | |
display: "flex", | |
flexDirection: "column", | |
flexShrink: 1, | |
margin: 0, | |
minHeight: 0, | |
minWidth: 0, | |
padding: 0, | |
zIndex: 0, | |
position: "absolute", | |
left: 13, | |
top: 0, | |
bottom: 0, | |
alignItems: "center", | |
justifyContent: "center", | |
flexGrow: 0 | |
}, | |
tetraLikeAddOnSecondary: { | |
display: "flex", | |
justifyContent: "center", | |
alignItems: "center" | |
}, | |
addOnSecondaryOffset: { | |
transform: "translateX(-50%)" | |
}, | |
addOnSecondaryOffsetRTL: { | |
transform: "translateX(50%)" | |
}, | |
indentationLevel1: { | |
paddingLeft: 16 | |
}, | |
indentationLevel2: { | |
paddingLeft: 26 | |
}, | |
indentationLevel3: { | |
paddingLeft: 60 | |
}, | |
tetraLikeIndentationLevel1: { | |
paddingLeft: 8 | |
}, | |
tetraLikeIndentationLevel2: { | |
paddingLeft: 8 | |
}, | |
tetraLikeIndentationLevel3: { | |
paddingLeft: 42 | |
}, | |
addOnTertiary: { | |
borderStyle: "solid", | |
borderWidth: 0, | |
boxSizing: "border-box", | |
display: "flex", | |
marginBottom: 0, | |
marginRight: 0, | |
marginTop: 0, | |
minHeight: 0, | |
paddingBottom: 0, | |
paddingRight: 0, | |
paddingLeft: 0, | |
paddingTop: 0, | |
position: "relative", | |
zIndex: 0, | |
flexGrow: 0, | |
flexShrink: 0, | |
minWidth: "auto", | |
alignItems: "center", | |
justifyContent: "flex-end", | |
flexDirection: "row", | |
marginLeft: "var(--wig-spacing-medium)" | |
}, | |
tetraLikeFocusRing: { | |
position: "static", | |
":focus-visible::after": { | |
border: "1px solid var(--accent)", | |
borderRadius: 8, | |
bottom: 0, | |
content: "", | |
left: 0, | |
position: "absolute", | |
right: 0, | |
top: 0 | |
} | |
} | |
}); | |
interface Props { | |
addOnPrimary?: React.ReactElement; | |
addOnSecondary?: React.ReactElement; | |
addOnTertiary?: React.ReactElement; | |
disabled?: boolean; | |
emphasized?: boolean; | |
selected?: boolean; | |
indentationLevel?: number; | |
linkProps?: any; | |
body?: string | React.ReactElement; | |
bodyColor?: string; | |
bodyLineLimit?: number; | |
headline?: string | React.ReactElement; | |
headlineAddOn?: any; | |
headlineColor?: string; | |
headlineLineLimit?: number; | |
meta?: string | React.ReactElement; | |
metaColor?: string; | |
metaLineLimit?: number; | |
metaLocation?: string; | |
onPress?: (event: any) => void; | |
onPressIn?: (event: any) => void; | |
onHoverIn?: (event: any) => void; | |
onHoverOut?: (event: any) => void; | |
onFocusIn?: (event: any) => void; | |
onFocusOut?: (event: any) => void; | |
isSemanticListItem?: boolean; | |
wrapperRef?: any; | |
onPreload?: () => void; | |
} | |
export const UIChannelItem = React.forwardRef((props: Props, ref) => { | |
const { | |
addOnPrimary, | |
addOnSecondary, | |
addOnTertiary, | |
disabled = false, | |
emphasized = false, | |
selected = false, | |
indentationLevel = 2, | |
linkProps = {}, | |
body, | |
bodyColor, | |
bodyLineLimit = 1, | |
headline, | |
headlineAddOn, | |
headlineColor, | |
headlineLineLimit = 1, | |
meta, | |
metaColor, | |
metaLineLimit, | |
metaLocation, | |
onPress, | |
onPressIn, | |
onHoverIn, | |
onHoverOut, | |
onFocusIn, | |
onFocusOut, | |
isSemanticListItem = true, | |
wrapperRef, | |
onPreload, | |
...otherProps | |
} = props; | |
const { | |
url, | |
..._otherLinkProps | |
} = linkProps; | |
const [pressed, setPressed] = React.useState(false); | |
const { | |
focused, | |
hovered, | |
onFocusIn: _onFocusIn, | |
onFocusOut: _onFocusOut, | |
onHoverIn: _onHoverIn, | |
onHoverOut: _onHoverOut, | |
} = useHoverAndFocusState(); | |
const _interactiveElementContext = React.useMemo(() => { | |
return { | |
hovered: hovered, | |
focused: focused, | |
pressed: pressed, | |
} | |
}, [hovered, focused, pressed]); | |
const handlePreload = React.useCallback(() => { | |
if (onPreload) { | |
onPreload(); | |
} | |
}, [onPreload]); | |
const [triggerPreload, triggerOutPreload] = useCometPreloader("button_aggressive", undefined, handlePreload); | |
const handleHoverIn = React.useCallback((event: any) => { | |
triggerPreload(event); | |
if (onHoverIn) { | |
onHoverIn(event); | |
} | |
}, [onHoverIn, triggerPreload]); | |
const handleHoverOut = React.useCallback((event: any) => { | |
triggerOutPreload(); | |
if (onHoverOut) { | |
onHoverOut(event); | |
} | |
}, [onHoverOut, triggerOutPreload]); | |
const handleFocusIn = React.useCallback((event: any) => { | |
_onFocusIn(event); | |
if (onFocusIn) { | |
onFocusIn(event); | |
} | |
}, [_onFocusIn, onFocusIn]); | |
const handleFocusOut = React.useCallback((event: any) => { | |
_onFocusOut(event); | |
if (onFocusOut) { | |
onFocusOut(event); | |
} | |
}, [_onFocusOut, onFocusOut]); | |
const handlePressIn = React.useCallback((event: any) => { | |
setPressed(true); | |
if (onPressIn) { | |
onPressIn(event); | |
} | |
}, [onPressIn]); | |
const handlePressOut = React.useCallback(() => { | |
setPressed(false); | |
}, []); | |
const content = ( | |
<React.Fragment> | |
{ | |
addOnPrimary && ( | |
<div className={stylex(styles.addOnPrimary)}> | |
{addOnPrimary} | |
</div> | |
) | |
} | |
<div | |
data-testid="UIChannelItem" // should be replaced to undefined in build script | |
className={stylex(styles.textPairing)} | |
> | |
<TetraTextPairing | |
body={body} | |
bodyColor={bodyColor} | |
bodyLineLimit={bodyLineLimit} | |
headline={headline} | |
headlineAddOn={headlineAddOn} | |
headlineColor={headlineColor} | |
headlineLineLimit={headlineLineLimit} | |
level={4} | |
meta={meta} | |
metaColor={metaColor} | |
metaLineLimit={metaLineLimit} | |
metaLocation={metaLocation} | |
reduceEmphasis={!emphasized} | |
/> | |
</div> | |
</React.Fragment> | |
) | |
const WrapperComponent = isSemanticListItem ? "li" : "div"; | |
const pressable = onPress || url !== null; | |
return ( | |
<ChannelFocusableTable.ChannelFocusableTableRow> | |
<InteractiveElementContext.Provider value={_interactiveElementContext}> | |
<WrapperComponent | |
ref={wrapperRef} | |
role={pressable && isSemanticListItem ? 'row' : undefined} | |
onMouseEnter={_onHoverIn} | |
onMouseLeave={_onHoverOut} | |
className={stylex(styles.root, | |
getIndentationClassName({indentationLevel: indentationLevel}), | |
focused && styles.focused, | |
selected && styles.selected, | |
)} | |
> | |
{pressable && ( | |
<CometPressableOverlay | |
focusVisible={focused} | |
useHoverAndFocusState={hovered} | |
pressed={pressed} | |
/> | |
)} | |
{ | |
pressable ? ( | |
<ChannelFocusableTable.ChannelFocusableTableCell> | |
<div | |
className={stylex(styles.contentContainer)} | |
role={isSemanticListItem ? 'gridcell' : undefined} | |
> | |
<Pressable | |
{...otherProps} | |
display="block" | |
disabled={disabled} | |
linkProps={url ? { | |
..._otherLinkProps, | |
url: url, | |
prefetchQueries: true, | |
} : undefined} | |
onHoverIn={handleHoverIn} | |
onHoverOut={handleHoverOut} | |
onFocusIn={handleFocusIn} | |
onFocusOut={handleFocusOut} | |
onPress={onPress} | |
onPressIn={handlePressIn} | |
onPressOut={handlePressOut} | |
overlayDisabled={true} | |
ref={ref} | |
xstyle={[styles.content]} | |
> | |
{content} | |
</Pressable> | |
</div> | |
</ChannelFocusableTable.ChannelFocusableTableCell> | |
) : ( | |
<div className={stylex(styles.contentContainer)}> | |
{content} | |
</div> | |
) | |
} | |
{addOnSecondary && ( | |
<div className={stylex(styles.addOnSecondary, styles.addOnSecondaryOffset)}> | |
{addOnSecondary} | |
</div> | |
)} | |
{addOnTertiary && ( | |
<div className={stylex(styles.addOnTertiary)}> | |
{addOnTertiary} | |
</div> | |
)} | |
</WrapperComponent> | |
</InteractiveElementContext.Provider> | |
</ChannelFocusableTable.ChannelFocusableTableRow> | |
); | |
}) | |
const getIndentationClassName = (props: {indentationLevel: number}) => { | |
const {indentationLevel} = props; | |
if (indentationLevel === 1) return styles.indentationLevel1; | |
else if (indentationLevel === 2) return styles.indentationLevel2; | |
else if (indentationLevel === 3) return styles.indentationLevel3; | |
return styles.indentationLevel1; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment