Skip to content

Instantly share code, notes, and snippets.

@ladifire
Created August 24, 2021 15:17
Show Gist options
  • Save ladifire/3fa94c6b6c3eef0b4b99f102dd5cbc3e to your computer and use it in GitHub Desktop.
Save ladifire/3fa94c6b6c3eef0b4b99f102dd5cbc3e to your computer and use it in GitHub Desktop.
UIChannelItem.tsx by Ladifire & Cong Nguyen
/**
* 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