feat(ui) - assist ui - wip

This commit is contained in:
Shekar Siri 2022-02-21 16:41:04 +01:00
parent 40b88446d1
commit 250eaf1eb6
24 changed files with 325 additions and 140 deletions

View file

@ -2,8 +2,10 @@ import React from 'react';
import LiveSessionList from 'Shared/LiveSessionList';
import LiveSessionSearch from 'Shared/LiveSessionSearch';
import cn from 'classnames'
import withPageTitle from 'HOCs/withPageTitle';
export default function Assist() {
// @withPageTitle("Assist - OpenReplay")
function Assist() {
return (
<div className="page-margin container-90 flex relative">
<div className="flex-1 flex">
@ -18,3 +20,5 @@ export default function Assist() {
</div>
)
}
export default withPageTitle("Assist - OpenReplay")(Assist);

View file

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'
import { Popup, Icon } from 'UI'
import { Popup, Icon, IconButton } from 'UI'
import { connect } from 'react-redux'
import cn from 'classnames'
import { toggleChatWindow } from 'Duck/sessions';
@ -77,9 +77,28 @@ function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus
const onCall = calling === CallingState.OnCall || calling === CallingState.Reconnecting
const cannotCall = (peerConnectionStatus !== ConnectionStatus.Connected) || (isEnterprise && !hasPermission)
const remoteActive = remoteControlStatus === RemoteControlStatus.Enabled
return (
<div className="flex items-center">
<div
className={
cn(
'cursor-pointer p-2 mr-2 flex items-center',
)
}
onClick={ requestReleaseRemoteControl }
role="button"
>
{/* <Icon
name="remote-control"
size="20"
color={ remoteControlStatus === RemoteControlStatus.Enabled ? "green" : "gray-darkest"}
/>
<span className={cn("ml-2", { 'color-green' : remoteControlStatus === RemoteControlStatus.Enabled })}>{ 'Remote Control' }</span> */}
<IconButton label={`${remoteActive ? 'Stop ' : ''} Remote Control`} icon="remote-control" primaryText redText={remoteActive} />
</div>
<div className="divider" />
<Popup
trigger={
<div
@ -92,12 +111,13 @@ function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus
onClick={ onCall ? callObject?.end : confirmCall}
role="button"
>
<Icon
{/* <Icon
name="headset"
size="20"
color={ onCall ? "red" : "gray-darkest" }
/>
<span className={cn("ml-2", { 'color-red' : onCall })}>{ onCall ? 'End Call' : 'Call' }</span>
<span className={cn("ml-2", { 'color-red' : onCall })}>{ onCall ? 'End Call' : 'Call' }</span> */}
<IconButton size="small" primary={!onCall} red={onCall} label="Call" icon="headset" />
</div>
}
content={ cannotCall ? "You dont have the permissions to perform this action." : `Call ${userId ? userId : 'User'}` }
@ -105,22 +125,7 @@ function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus
inverted
position="top right"
/>
<div
className={
cn(
'cursor-pointer p-2 mr-2 flex items-center',
)
}
onClick={ requestReleaseRemoteControl }
role="button"
>
<Icon
name="remote-control"
size="20"
color={ remoteControlStatus === RemoteControlStatus.Enabled ? "green" : "gray-darkest"}
/>
<span className={cn("ml-2", { 'color-green' : remoteControlStatus === RemoteControlStatus.Enabled })}>{ 'Remote Control' }</span>
</div>
<div className="fixed ml-3 left-0 top-0" style={{ zIndex: 999 }}>
{ onCall && callObject && <ChatWindow endCall={callObject.end} userId={userId} incomeStream={incomeStream} localStream={localStream} /> }
</div>

View file

@ -15,16 +15,15 @@ const AssistTabs = (props: Props) => {
<div className="flex items-center">
{props.userId && (
<>
<div className="flex items-center mr-3">
<Icon name="user-alt" color="gray-darkest" />
<div className="ml-2">{props.userId}</div>
</div>
<div
className={stl.btnLink}
onClick={() => setShowMenu(!showMenu)}
>
More Live Sessions
</div>
<span className="mx-3 color-gray-medium">by</span>
<div className="flex items-center">
<Icon name="user-alt" color="gray-darkest" />
<div className="ml-2">{props.userId}</div>
All Active Sessions
</div>
</>
)}

View file

@ -102,13 +102,13 @@ export default class BugFinder extends React.PureComponent {
// };
// });
// // TODO should cache the response
props.fetchIntegrationVariables().then(() => {
defaultFilters[5] = {
category: 'Metadata',
type: 'custom',
keys: this.props.variables.map(({ key }) => ({ type: 'METADATA', key, label: key, icon: 'filters/metadata', isFilter: true })).toJS()
};
});
// props.fetchIntegrationVariables().then(() => {
// defaultFilters[5] = {
// category: 'Metadata',
// type: 'custom',
// keys: this.props.variables.map(({ key }) => ({ type: 'METADATA', key, label: key, icon: 'filters/metadata', isFilter: true })).toJS()
// };
// });
props.fetchSessions();
props.resetFunnel();

View file

@ -10,6 +10,7 @@ import styles from './siteDropdown.css';
import cn from 'classnames';
import NewSiteForm from '../Client/Sites/NewSiteForm';
import { clearSearch } from 'Duck/search';
import { fetchList as fetchIntegrationVariables } from 'Duck/customField';
@withRouter
@connect(state => ({
@ -21,6 +22,7 @@ import { clearSearch } from 'Duck/search';
pushNewSite,
init,
clearSearch,
fetchIntegrationVariables,
})
export default class SiteDropdown extends React.PureComponent {
state = { showProductModal: false }
@ -37,6 +39,7 @@ export default class SiteDropdown extends React.PureComponent {
switchSite = (siteId) => {
this.props.setSiteId(siteId);
this.props.clearSearch();
this.props.fetchIntegrationVariables();
}
render() {

View file

@ -3,19 +3,21 @@ import { withRouter } from 'react-router-dom';
import { browserIcon, osIcon, deviceTypeIcon } from 'App/iconNames';
import { formatTimeOrDate } from 'App/date';
import { sessions as sessionsRoute, withSiteId } from 'App/routes';
import { Icon, CountryFlag, IconButton, BackLink } from 'UI';
import { Icon, CountryFlag, IconButton, BackLink, Popup } from 'UI';
import { toggleFavorite, setSessionPath } from 'Duck/sessions';
import cn from 'classnames';
import { connectPlayer } from 'Player';
import HeaderInfo from './HeaderInfo';
import SharePopup from '../shared/SharePopup/SharePopup';
import { fetchList as fetchListIntegration } from 'Duck/integrations/actions';
import { countries } from 'App/constants';
import stl from './playerBlockHeader.css';
import Issues from './Issues/Issues';
import Autoplay from './Autoplay';
import AssistActions from '../Assist/components/AssistActions';
import AssistTabs from '../Assist/components/AssistTabs';
import SessionInfoItem from './SessionInfoItem'
const SESSIONS_ROUTE = sessionsRoute();
@ -53,11 +55,13 @@ export default class PlayerBlockHeader extends React.PureComponent {
this.props.fetchListIntegration('issues')
}
getDimension = (width, height) => (
<div className="flex items-center">
{ width || 'x' } <Icon name="close" size="12" className="mx-1" /> { height || 'x' }
</div>
);
getDimension = (width, height) => {
return width && height ? (
<div className="flex items-center">
{ width || 'x' } <Icon name="close" size="12" className="mx-1" /> { height || 'x' }
</div>
) : <span className="text-sm">Not Available</span>;
}
backHandler = () => {
const { history, siteId, sessionPath } = this.props;
@ -85,6 +89,7 @@ export default class PlayerBlockHeader extends React.PureComponent {
startedAt,
userBrowser,
userOs,
userOsVersion,
userDevice,
userBrowserVersion,
userDeviceType,
@ -102,12 +107,13 @@ export default class PlayerBlockHeader extends React.PureComponent {
return (
<div className={ cn(stl.header, "flex justify-between", { "hidden" : fullscreen}) }>
<div className="flex w-full">
<div className="flex w-full items-center">
<BackLink onClick={this.backHandler} label="Back" />
<div className={ stl.divider } />
{ _live && <AssistTabs userId={userId} />}
<div className="mx-4 flex items-center">
{/* <div className="mx-4 flex items-center">
<CountryFlag country={ userCountry } />
<div className="ml-2 font-normal color-gray-dark mt-1 text-sm">
{ formatTimeOrDate(startedAt) } <span>{ this.props.local === 'UTC' ? 'UTC' : ''}</span>
@ -117,15 +123,33 @@ export default class PlayerBlockHeader extends React.PureComponent {
<HeaderInfo icon={ browserIcon(userBrowser) } label={ `v${ userBrowserVersion }` } />
<HeaderInfo icon={ deviceTypeIcon(userDeviceType) } label={ capitalise(userDevice) } />
<HeaderInfo icon="expand-wide" label={ this.getDimension(width, height) } />
<HeaderInfo icon={ osIcon(userOs) } label={ userOs } />
<HeaderInfo icon={ osIcon(userOs) } label={ userOs } /> */}
<div className='ml-auto flex items-center'>
<div className={ stl.divider } />
<Popup
trigger={(
<IconButton icon="info-circle" primaryText label="More Info" disabled={disabled} />
)}
content={(
<div className=''>
<SessionInfoItem comp={<CountryFlag country={ userCountry } />} label={countries[userCountry]} value={ formatTimeOrDate(startedAt) } />
<SessionInfoItem icon={browserIcon(userBrowser)} label={userBrowser} value={ `v${ userBrowserVersion }` } />
<SessionInfoItem icon={osIcon(userOs)} label={userOs} value={ userOsVersion } />
<SessionInfoItem icon={deviceTypeIcon(userDeviceType)} label={userDeviceType} value={ this.getDimension(width, height) } isLast />
</div>
)}
on="click"
position="top center"
hideOnScroll
/>
<div className={ stl.divider } />
{ live && hasSessionsPath && (
<div className={stl.liveSwitchButton} onClick={() => this.props.setSessionPath('')}>
This Session is Now Continuing Live
</div>
)}
{ _live && <AssistTabs userId={userId} />}
{ _live && <AssistActions isLive userId={userId} /> }
{ !_live && (
<>
@ -164,3 +188,4 @@ export default class PlayerBlockHeader extends React.PureComponent {
);
}
}

View file

@ -0,0 +1,24 @@
import React from 'react'
import { Icon } from 'UI'
import cn from 'classnames'
interface Props {
label: string,
icon?: string,
comp?: React.ReactNode,
value: string,
isLast?: boolean,
}
export default function SessionInfoItem(props: Props) {
const { label, icon, value, comp, isLast = false } = props
return (
<div className={cn("flex items-center w-full py-2", {'border-b' : !isLast})}>
<div className="px-2 capitalize" style={{ width: '30px' }}>
{ icon && <Icon name={icon} size="16" /> }
{ comp && comp }
</div>
<div className="px-2 whitespace-nowrap capitalize" style={{ minWidth: '130px' }}>{label}</div>
<div className="color-gray-medium px-2" style={{ minWidth: '100px' }}>{value}</div>
</div>
)
}

View file

@ -0,0 +1 @@
export { default } from './SessionInfoItem';

View file

@ -1,13 +1,13 @@
.header {
height: 50px;
border-bottom: solid thin $gray-light;
padding: 10px 15px;
padding: 0px 15px;
background-color: white;
}
.divider {
width: 1px;
height: 100%;
height: 49px;
margin: 0 15px;
background-color: $gray-light;
}

View file

@ -0,0 +1,3 @@
.bar {
height: 2px;
}

View file

@ -1,5 +1,6 @@
import React from 'react'
import cn from 'classnames'
import stl from './ErrorBars.css'
const GOOD = 'Good'
const LESS_CRITICAL = 'Less Critical'
@ -16,21 +17,23 @@ interface Props {
}
export default function ErrorBars(props: Props) {
const { count = 2 } = props
const state = React.useCallback(() => getErrorState(count), [count])()
const state = React.useMemo(() => getErrorState(count), [count])
const showSecondBar = (state === GOOD || state === LESS_CRITICAL || state === CRITICAL)
const showThirdBar = (state === GOOD || state === CRITICAL);
const bgColor = { 'bg-red' : state === CRITICAL, 'bg-green' : state === GOOD, 'bg-red2' : state === LESS_CRITICAL }
return (
<div className="relative" style={{ width: '80px' }}>
<div className="relative" style={{ width: '60px' }}>
<div className="grid grid-cols-3 gap-1 absolute inset-0" style={{ opacity: '1'}}>
<div className={cn("h-1 rounded-tl rounded-bl", bgColor)}></div>
{ (state === GOOD || state === LESS_CRITICAL || state === CRITICAL) && <div className={cn("h-1 rounded-tl rounded-bl", bgColor)}></div> }
{ (state === GOOD || state === CRITICAL) && <div className={cn("h-1 rounded-tl rounded-bl", bgColor)}></div> }
<div className={cn("rounded-tl rounded-bl", bgColor, stl.bar)}></div>
{ showSecondBar && <div className={cn("rounded-tl rounded-bl", bgColor, stl.bar)}></div> }
{ showThirdBar && <div className={cn("rounded-tl rounded-bl", bgColor, stl.bar)}></div> }
</div>
<div className="grid grid-cols-3 gap-1" style={{ opacity: '0.3'}}>
<div className={cn("h-1 rounded-tl rounded-bl", bgColor)}></div>
<div className={cn("h-1", bgColor)}></div>
<div className={cn("h-1 rounded-tr rounded-br", bgColor)}></div>
<div className={cn("rounded-tl rounded-bl", bgColor, stl.bar)}></div>
<div className={cn(bgColor, stl.bar)}></div>
<div className={cn("rounded-tr rounded-br", bgColor, stl.bar)}></div>
</div>
<div className="mt-2">{state}</div>
<div className="mt-1 color-gray-medium text-sm">{state}</div>
</div>
)
}

View file

@ -0,0 +1,22 @@
import React from 'react'
import cn from 'classnames'
import { TextEllipsis } from 'UI'
interface Props {
className?: string,
label: string,
value?: string,
}
export default function MetaItem(props: Props) {
const { className = '', label, value } = props
return (
<div className={cn("flex items-center rounded", className)}>
<span className="rounded-tl rounded-bl bg-gray-light-shade px-2 color-gray-medium capitalize" style={{ maxWidth: "80px"}}>
<TextEllipsis text={label} className="p-0" popupProps={{ size: 'small' }} />
</span>
<span className="rounded-tr rounded-br bg-gray-lightest px-2 color-gray-dark capitalize" style={{ maxWidth: "100px"}}>
<TextEllipsis text={value} className="p-0" popupProps={{ size: 'small' }} />
</span>
</div>
)
}

View file

@ -0,0 +1 @@
export { default } from './MetaItem';

View file

@ -0,0 +1,32 @@
import React from 'react'
import { Popup } from 'UI'
import MetaItem from '../MetaItem'
interface Props {
list: any[],
maxLength: number,
}
export default function MetaMoreButton(props: Props) {
const { list, maxLength } = props
return (
<Popup
trigger={ (
<div className="flex items-center">
<span className="rounded bg-active-blue color-teal px-2 color-gray-dark cursor-pointer">
+{list.length - maxLength} More
</span>
</div>
) }
content={
<div className="flex flex-col">
{list.slice(maxLength).map(({ label, value }, index) => (
<MetaItem key={index} label={label} value={value} className="mb-2" />
))}
</div>
}
on="click"
position="top right"
hideOnScroll
/>
)
}

View file

@ -0,0 +1 @@
export { default } from './MetaMoreButton';

View file

@ -20,13 +20,15 @@ import Counter from './Counter'
import { withRouter } from 'react-router-dom';
import SessionMetaList from './SessionMetaList';
import ErrorBars from './ErrorBars';
import { assist as assistRoute, isRoute } from "App/routes";
const ASSIST_ROUTE = assistRoute();
const Label = ({ label = '', color = 'color-gray-medium'}) => (
<div className={ cn('font-light text-sm', color)}>{label}</div>
)
@connect(state => ({
timezone: state.getIn(['sessions', 'timezone']),
isAssist: state.getIn(['sessions', 'activeTab']).type === 'live',
siteId: state.getIn([ 'user', 'siteId' ]),
}), { toggleFavorite, setSessionPath })
@withRouter
@ -52,7 +54,8 @@ export default class SessionItem extends React.PureComponent {
userDeviceType,
userUuid,
userNumericHash,
live
live,
metadata,
},
timezone,
onUserClick = () => null,
@ -61,71 +64,89 @@ export default class SessionItem extends React.PureComponent {
} = this.props;
const formattedDuration = durationFormatted(duration);
const hasUserId = userId || userAnonymousId;
const isAssist = isRoute(ASSIST_ROUTE, this.props.location.pathname);
console.log('metadata', metadata);
const _metaList = Object.keys(metadata).map(key => {
const value = metadata[key];
return { label: key, value };
});
console.log('SessionItem', _metaList);
return (
<div className={ cn(stl.sessionItem, "flex flex-col bg-white p-3 mb-3") } id="session-item" >
<div className="flex items-start">
<div className={ cn('flex items-center w-full')}>
<div className="flex items-center" style={{ width: "40%"}}>
<div><Avatar seed={ userNumericHash } /></div>
<div className="flex flex-col overflow-hidden color-gray-medium ml-3">
<div className="flex items-center pr-2" style={{ width: "30%"}}>
<div><Avatar seed={ userNumericHash } isAssist={isAssist} /></div>
{/* <div className="flex flex-col overflow-hidden color-gray-medium ml-3"> */}
<div style={{ height: "38px" }} className="flex flex-col overflow-hidden color-gray-medium ml-3 justify-between">
<div
className={cn({'color-teal cursor-pointer': !disableUser && hasUserId, 'color-gray-medium' : disableUser || !hasUserId})}
onClick={() => (!disableUser && !hasUserFilter && hasUserId) && onUserClick(userId, userAnonymousId)}
>
{userDisplayName}
</div>
<div className="color-gray-medium">30 Sessions</div>
</div>
</div>
<div style={{ width: "20%"}}>
<div>{formatTimeOrDate(startedAt, timezone) }</div>
<div className="flex items-center color-gray-medium">
{!live && (
<div className="color-gray-medium">
<span className="mr-1">{ eventsCount }</span>
<span>{ eventsCount === 0 || eventsCount > 1 ? 'Events' : 'Event' }</span>
</div>
)}
<span className="mx-1">-</span>
<div>{ live ? <Counter startTime={startedAt} /> : formattedDuration }</div>
</div>
</div>
<div style={{ width: "20%"}}>
<div className="">
<CountryFlag country={ userCountry } className="mr-6" />
<div className="color-gray-medium">
<span>{userBrowser}</span> -
<span>{userOs}</span> -
<span>{userDeviceType}</span>
<div
className="color-gray-medium text-dotted-underline cursor-pointer"
onClick={() => (!disableUser && !hasUserFilter && hasUserId) && onUserClick(userId, userAnonymousId)}
>
30 Sessions
</div>
</div>
</div>
<div style={{ width: "10%"}} className="self-center">
<ErrorBars count={errorsCount} />
<div style={{ width: "20%", height: "38px" }} className="px-2 flex flex-col justify-between">
<div>{formatTimeOrDate(startedAt, timezone) }</div>
<div className="flex items-center color-gray-medium">
{!isAssist && (
<>
<div className="color-gray-medium">
<span className="mr-1">{ eventsCount }</span>
<span>{ eventsCount === 0 || eventsCount > 1 ? 'Events' : 'Event' }</span>
</div>
<div className="mx-2 text-4xl">·</div>
</>
)}
<div>{ live ? <Counter startTime={startedAt} /> : formattedDuration }</div>
</div>
</div>
<div style={{ width: "40%", height: "38px" }} className="px-2 flex flex-col justify-between">
{/* <div className="flex flex-col"> */}
<CountryFlag country={ userCountry } className="mr-2" label />
<div className="color-gray-medium flex items-center">
<span className="capitalize" style={{ maxWidth: '70px'}}>
<TextEllipsis text={ userBrowser } popupProps={{ inverted: true, size: "tiny" }} />
</span>
<div className="mx-2 text-4xl">·</div>
<span className="capitalize" style={{ maxWidth: '70px'}}>
<TextEllipsis text={ userOs } popupProps={{ inverted: true, size: "tiny" }} />
</span>
<div className="mx-2 text-4xl">·</div>
<span className="capitalize" style={{ maxWidth: '70px'}}>
<TextEllipsis text={ userDeviceType } popupProps={{ inverted: true, size: "tiny" }} />
</span>
</div>
{/* </div> */}
</div>
{ !isAssist && (
<div style={{ width: "10%"}} className="self-center px-2 flex items-center">
<ErrorBars count={errorsCount} />
</div>
)}
</div>
<div className="flex items-center">
{/* { live && <LiveTag isLive={true} /> } */}
<div className={ cn(stl.iconDetails, stl.favorite, 'px-4') } data-favourite={favorite} >
<Bookmark sessionId={sessionId} favorite={favorite} />
</div>
<div className={ stl.playLink } id="play-button" data-viewed={ viewed }>
<Link to={ sessionRoute(sessionId) }>
<Icon name={ viewed ? 'play-fill' : 'play-circle-light' } size="30" color="teal" />
<Icon name={ viewed ? 'play-fill' : 'play-circle-light' } size="30" color={isAssist ? "tealx" : "teal"} />
</Link>
</div>
</div>
</div>
<SessionMetaList className="pt-3" metaList={[
{ label: 'Pages', value: pagesCount },
{ label: 'Errors', value: errorsCount },
{ label: 'Events', value: eventsCount },
{ label: 'Events', value: eventsCount },
{ label: 'Events', value: eventsCount },
]} />
{ isAssist && (
<SessionMetaList className="mt-4" metaList={_metaList} />
)}
</div>
);
}

View file

@ -1,6 +1,8 @@
import React from 'react'
import { Popup } from 'UI'
import cn from 'classnames'
import MetaItem from '../MetaItem';
import MetaMoreButton from '../MetaMoreButton';
interface Props {
className?: string,
@ -12,36 +14,11 @@ export default function SessionMetaList(props: Props) {
return (
<div className={cn("text-sm flex items-start", className)}>
{metaList.slice(0, MAX_LENGTH).map(({ label, value }, index) => (
<div key={index} className="flex items-center rounded mr-3">
<span className="rounded-tl rounded-bl bg-gray-light-shade px-2 color-gray-medium">{label}</span>
<span className="rounded-tr rounded-br bg-gray-lightest px-2 color-gray-dark">{value}</span>
</div>
<MetaItem key={index} label={label} value={''+value} className="mr-3" />
))}
{metaList.length > MAX_LENGTH && (
<Popup
trigger={ (
<div className="flex items-center">
<span className="rounded bg-active-blue color-teal px-2 color-gray-dark cursor-pointer">
+{metaList.length - MAX_LENGTH} More
</span>
</div>
) }
content={
<div className="flex flex-col">
{metaList.slice(MAX_LENGTH).map(({ label, value }, index) => (
<div key={index} className="flex items-center rounded mb-2">
<span className="rounded-tl rounded-bl bg-gray-light-shade px-2 color-gray-medium">{label}</span>
<span className="rounded-tr rounded-br bg-gray-lightest px-2 color-gray-dark">{value}</span>
</div>
))}
</div>
}
on="click"
position="top right"
// className={ styles.popup }
hideOnScroll
/>
<MetaMoreButton list={metaList} maxLength={MAX_LENGTH} />
)}
</div>
)

View file

@ -11,14 +11,15 @@ const ICON_LIST = ['icn_chameleon', 'icn_fox', 'icn_gorilla', 'icn_hippo', 'icn_
'icn_wild1', 'icn_wild_bore']
const Avatar = ({ className, width = "38px", height = "38px", iconSize = 26, seed }) => {
const Avatar = ({ isAssist = false, className, width = "38px", height = "38px", iconSize = 26, seed }) => {
var iconName = avatarIconName(seed);
return (
<div
className={ cn(stl.wrapper, "p-2 border flex items-center justify-center rounded-full")}
className={ cn(stl.wrapper, "p-2 border flex items-center justify-center rounded-full relative")}
style={{ width, height }}
>
<Icon name={iconName} size={iconSize} color="tealx"/>
{isAssist && <div className="w-2 h-2 bg-green rounded-full absolute right-0 bottom-0" style={{ marginRight: '3px', marginBottom: '3px'}} /> }
</div>
);
};

View file

@ -3,22 +3,26 @@ import { countries } from 'App/constants';
import { Popup } from 'UI';
import stl from './countryFlag.css';
const CountryFlag = ({ country, className }) => {
const CountryFlag = React.memo(({ country, className, style = {}, label = false }) => {
const knownCountry = !!country && country !== 'UN';
const countryFlag = knownCountry ? country.toLowerCase() : '';
const countryName = knownCountry ? countries[ country ] : 'Unknown Country';
const countryFlag = knownCountry ? country.toLowerCase() : '';
const countryName = knownCountry ? countries[ country ] : 'Unknown Country';
return (
<Popup
trigger={ knownCountry
? <span className={ cn(`flag flag-${ countryFlag }`, className, stl.default) } />
: <span className={ className } >{ "N/A" }</span>
}
content={ countryName }
inverted
size="tiny"
/>
<div className="flex items-center" style={style}>
<Popup
trigger={ knownCountry
? <div className={ cn(`flag flag-${ countryFlag }`, className, stl.default) } />
: <div className={ cn('text-sm', className) }>{ "N/A" }</div>
}
content={ countryName }
inverted
size="tiny"
/>
{ knownCountry && label && <div className={ stl.label }>{ countryName }</div> }
</div>
);
}
})
CountryFlag.displayName = "CountryFlag";

View file

@ -1,4 +1,8 @@
.default {
width: 22px !important;
height: 14px !important;
}
.label {
line-height: 0 !important;
}

View file

@ -9,8 +9,10 @@ const IconButton = React.forwardRef(({
onClick,
plain = false,
shadow = false,
red = false,
primary = false,
primaryText = false,
redText = false,
outline = false,
loading = false,
roundedOutline = false,
@ -40,7 +42,9 @@ const IconButton = React.forwardRef(({
[ stl.active ]: active,
[ stl.shadow ]: shadow,
[ stl.primary ]: primary,
[ stl.red ]: red,
[ stl.primaryText ]: primaryText,
[ stl.redText ]: redText,
[ stl.outline ]: outline,
[ stl.circle ]: circle,
[ stl.roundedOutline ]: roundedOutline,

View file

@ -73,11 +73,41 @@
fill: white;
}
& svg {
fill: white;
}
& .label {
color: white !important;
}
&:hover {
background-color: $teal-dark;
}
}
&.red {
background-color: $red;
box-shadow: 0 0 0 1px $red inset !important;
& .icon {
fill: white;
}
& svg {
fill: white;
}
& .label {
color: white !important;
}
&:hover {
background-color: $red;
filter: brightness(90%);
}
}
&.outline {
box-shadow: 0 0 0 1px $teal inset !important;
& .label {
@ -116,4 +146,14 @@
.primaryText .label {
color: $teal !important;
}
.redText {
& .label {
color: $red !important;
}
& svg {
fill: $red;
}
}

View file

@ -1,7 +1,7 @@
.textEllipsis {
text-overflow: ellipsis;
overflow: hidden;
display: inline-block;
/* display: inline-block; */
white-space: nowrap;
max-width: 100%;
}

View file

@ -123,4 +123,15 @@
&:hover {
background-color: $active-blue;
}
}
.text-dotted-underline {
text-decoration: underline dotted !important;
}
.divider {
width: 1px;
height: 49px;
margin: 0 15px;
background-color: $gray-light;
}