Created
August 24, 2021 15:17
-
-
Save ladifire/3fa94c6b6c3eef0b4b99f102dd5cbc3e to your computer and use it in GitHub Desktop.
UIChannelItem.tsx by Ladifire & Cong Nguyen
This file contains hidden or 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