diff --git a/docs/src/components/Sidebar/Sidebar.tsx b/docs/src/components/Sidebar/Sidebar.tsx index be019d4886..ad2bc8263d 100644 --- a/docs/src/components/Sidebar/Sidebar.tsx +++ b/docs/src/components/Sidebar/Sidebar.tsx @@ -265,6 +265,14 @@ class Sidebar extends React.Component { Chat Pane + + Chat message with popover + { + state = { + focused: false, + } + + changeFocusState = (isFocused: boolean) => { + this.state.focused !== isFocused && this.setState({ focused: isFocused }) + } + + handleFocus = () => { + this.changeFocusState(true) + } + + handleBlur = e => { + const shouldPreserveFocusState = e.currentTarget.contains(e.relatedTarget) + this.changeFocusState(shouldPreserveFocusState) + } + + render() { + return ( + + + Link Hover me to see the actions Some Link + + ), + }} + avatar={janeAvatar} + onFocus={this.handleFocus} + onBlur={this.handleBlur} + className={cx(this.props.className, this.state.focused ? 'focused' : '')} + /> + ) + } +} + +export default ChatMessageWithPopover diff --git a/docs/src/prototypes/chatMessageWithPopover/ChatWithPopover.tsx b/docs/src/prototypes/chatMessageWithPopover/ChatWithPopover.tsx new file mode 100644 index 0000000000..6796bae0fa --- /dev/null +++ b/docs/src/prototypes/chatMessageWithPopover/ChatWithPopover.tsx @@ -0,0 +1,56 @@ +import { Chat, Provider } from '@stardust-ui/react' +import * as React from 'react' +import ChatMessageWithPopover from './ChatMessageWithPopover' + +const ChatWithPopover = () => ( + ({ + position: 'relative', + + '&.focused .actions': { + opacity: 1, + }, + ':hover .actions': { + opacity: 1, + }, + '& a': { + color: siteVariables.brand, + }, + }), + }, + ContextMenu: { + root: ({ theme: { siteVariables } }) => ({ + background: siteVariables.white, + boxShadow: '0 0.2rem 1.6rem 0 rgba(37,36,35,.3)', + borderRadius: '.3rem', + marginTop: '5px', + }), + }, + Menu: { + root: { + '& a:focus': { + textDecoration: 'none', + color: 'inherit', + }, + '& a': { + color: 'inherit', + }, + }, + }, + }, + }} + > + }, + { key: 'b', content: }, + { key: 'c', content: }, + ]} + /> + +) + +export default ChatWithPopover diff --git a/docs/src/prototypes/chatMessageWithPopover/Popover.tsx b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx new file mode 100644 index 0000000000..64634a72f9 --- /dev/null +++ b/docs/src/prototypes/chatMessageWithPopover/Popover.tsx @@ -0,0 +1,184 @@ +import { + Menu, + Popup, + toolbarBehavior, + popupFocusTrapBehavior, + createComponent, + ComponentSlotStyle, + ComponentVariablesInput, + toolbarButtonBehavior, +} from '@stardust-ui/react' +import { ReactChildren } from 'types/utils' +import * as React from 'react' +import * as cx from 'classnames' + +export interface PopoverProps { + className?: string +} + +interface PopoverState { + focused: boolean + popupOpened: boolean +} + +class Popover extends React.Component { + state = { + focused: false, + popupOpened: false, + } + + changeFocusState = (isFocused: boolean) => { + this.state.focused !== isFocused && this.setState({ focused: isFocused }) + } + + handleFocus = () => { + this.changeFocusState(true) + } + + handleBlur = e => { + // if e.relatedTarget === null it means the click was outside this container + if (!this.state.popupOpened || e.relatedTarget === null) { + const shouldPreserveFocusState = e.currentTarget.contains(e.relatedTarget) + this.changeFocusState(shouldPreserveFocusState) + } else { + e.stopPropagation() + } + } + + handleMenuClick = () => { + // close popup when other MenuItem clicked, but the event propagation was stopped + this.state.popupOpened && this.setState({ popupOpened: false }) + } + + popoverStyles = ({ theme: { siteVariables } }) => ({ + transition: 'opacity 0.2s', + position: 'absolute', + top: '-20px', + right: '5px', + background: siteVariables.white, + boxShadow: '0px 2px 4px #ddd', + borderRadius: '.3rem', + opacity: 0, + + '& .smile-emoji': { + display: 'none', + }, + + '&.focused .smile-emoji': { + display: 'flex', + }, + + '&:hover .smile-emoji': { + display: 'flex', + }, + }) + + render() { + return ( + render => + render(itemShorthandValue, this.renderItemOrContextMenu), + )} + onFocus={this.handleFocus} + onBlur={this.handleBlur} + onClick={this.handleMenuClick} + accessibility={toolbarBehavior} + data-is-focusable={true} + /> + ) + } + + renderItemOrContextMenu = (MenuItem, props) => { + if (props.icon !== 'ellipsis horizontal') { + return + } + + return ( + { + this.setState(prev => ({ popupOpened: !prev.popupOpened })) + }} + /> + } + open={this.state.popupOpened} + onOpenChange={(e, newProps) => { + this.setState({ popupOpened: newProps.open }) + }} + content={ + + + + } + /> + ) + } +} + +export default Popover + +const ContextMenu = createComponent({ + displayName: 'ContextMenu', + render: ({ stardust, className, children }) => { + const { classes } = stardust + return
{children}
+ }, +}) + +interface ContextMenuProps { + className?: string + styles?: ComponentSlotStyle + variables?: ComponentVariablesInput + children?: ReactChildren +} diff --git a/docs/src/prototypes/chatMessageWithPopover/index.tsx b/docs/src/prototypes/chatMessageWithPopover/index.tsx new file mode 100644 index 0000000000..1207c36d78 --- /dev/null +++ b/docs/src/prototypes/chatMessageWithPopover/index.tsx @@ -0,0 +1 @@ +export { default } from './ChatWithPopover' diff --git a/docs/src/routes.tsx b/docs/src/routes.tsx index 890fffb174..e4517d1cc2 100644 --- a/docs/src/routes.tsx +++ b/docs/src/routes.tsx @@ -30,6 +30,12 @@ const Router = () => ( path="/prototype-chat-pane" component={require('./prototypes/chatPane/index').default} />, + ,