UBER-376 Implement new style for the tooltips (#3418)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-06-09 18:06:15 +06:00 committed by GitHub
parent 95ffbd6d33
commit 626d36db7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 149 additions and 120 deletions

View File

@ -1315,7 +1315,7 @@ export function createModel (builder: Builder): void {
group: 'create' group: 'create'
} }
}, },
tracker.action.NewSubIssue tracker.action.NewIssue
) )
createAction( createAction(

View File

@ -154,6 +154,10 @@
--theme-calendar-holiday-bgcolor: rgba(235, 87, 87, .1); --theme-calendar-holiday-bgcolor: rgba(235, 87, 87, .1);
--theme-calendar-weekend-bgcolor: rgba(242, 153, 74, .05); --theme-calendar-weekend-bgcolor: rgba(242, 153, 74, .05);
--theme-tooltip-color: rgba(255, 255, 255, .8);
--theme-tooltip-bg: #353347;
--theme-tooltip-key-bg: rgba(255, 255, 255, .08);
--theme-inbox-notify: #F47758; --theme-inbox-notify: #F47758;
--theme-inbox-people-notify: #2B5190; --theme-inbox-people-notify: #2B5190;
--theme-inbox-activity-bgcolor: #1A1A28; --theme-inbox-activity-bgcolor: #1A1A28;
@ -347,6 +351,10 @@
--theme-calendar-holiday-bgcolor: rgba(235, 87, 87, .1); --theme-calendar-holiday-bgcolor: rgba(235, 87, 87, .1);
--theme-calendar-weekend-bgcolor: rgba(242, 153, 74, .1); --theme-calendar-weekend-bgcolor: rgba(242, 153, 74, .1);
--theme-tooltip-color: #FFF;
--theme-tooltip-bg: #444248;
--theme-tooltip-key-bg: rgba(255, 255, 255, .08);
--theme-inbox-notify: #F47758; --theme-inbox-notify: #F47758;
--theme-inbox-people-notify: #2B5190; --theme-inbox-people-notify: #2B5190;
--theme-inbox-activity-bgcolor: #fff; --theme-inbox-activity-bgcolor: #fff;

View File

@ -19,6 +19,7 @@
import type { TooltipAlignment } from '../types' import type { TooltipAlignment } from '../types'
import Component from './Component.svelte' import Component from './Component.svelte'
import Label from './Label.svelte' import Label from './Label.svelte'
import { capitalizeFirstLetter, formatKey } from '../utils'
let tooltipHTML: HTMLElement let tooltipHTML: HTMLElement
let nubHTML: HTMLElement let nubHTML: HTMLElement
@ -236,6 +237,25 @@
{:else if $tooltip.label && $tooltip.kind !== 'submenu'} {:else if $tooltip.label && $tooltip.kind !== 'submenu'}
<div class="tooltip {dir ?? ''}" bind:this={tooltipHTML}> <div class="tooltip {dir ?? ''}" bind:this={tooltipHTML}>
<Label label={$tooltip.label} params={$tooltip.props ?? {}} /> <Label label={$tooltip.label} params={$tooltip.props ?? {}} />
{#if $tooltip.keys !== undefined}
<div class="keys">
{#each $tooltip.keys as key, i}
{#if i !== 0}
<div class="mr-1 ml-1">/</div>
{/if}
{#each formatKey(key) as k, jj}
<div class="key">
{#each k as kk, j}
{#if j !== 0}
+
{/if}
{capitalizeFirstLetter(kk.trim())}
{/each}
</div>
{/each}
{/each}
</div>
{/if}
</div> </div>
{:else if $tooltip.kind === 'submenu'} {:else if $tooltip.kind === 'submenu'}
<div <div
@ -361,25 +381,36 @@
} }
} }
.keys {
margin-left: 0.5rem;
display: flex;
align-items: center;
gap: 0.125rem;
}
.key {
border-radius: 0.125rem;
font-size: 0.75rem;
min-width: 1.5rem;
padding: 0.25rem;
background-color: var(--theme-tooltip-key-bg);
}
.tooltip { .tooltip {
position: fixed; position: fixed;
padding: 0.5rem 0.75rem; padding: 0.5rem;
text-align: center; text-align: center;
color: var(--theme-content-color); font-size: 0.75rem;
background-color: var(--theme-popup-color); color: var(--theme-tooltip-color);
background-color: var(--theme-tooltip-bg);
border: 1px solid var(--theme-popup-divider); border: 1px solid var(--theme-popup-divider);
border-radius: 0.75rem; border-radius: 0.25rem;
box-shadow: var(--theme-popup-shadow); box-shadow: var(--theme-popup-shadow);
user-select: none; user-select: none;
z-index: 10000; z-index: 10000;
display: flex;
align-items: center;
&::after,
&::before {
content: '';
position: absolute;
width: 18px;
height: 7px;
}
&::before { &::before {
background-color: var(--theme-popup-color); background-color: var(--theme-popup-color);
clip-path: url('#nub-bg'); clip-path: url('#nub-bg');
@ -390,55 +421,6 @@
clip-path: url('#nub-border'); clip-path: url('#nub-border');
z-index: 2; z-index: 2;
} }
&.top::after,
&.bottom::after,
&.top::before,
&.bottom::before {
left: 50%;
margin-left: -9px;
}
&.top {
bottom: 100%;
&::after,
&::before {
bottom: -6px;
transform: rotate(180deg);
}
}
&.bottom {
top: 100%;
&::after,
&::before {
top: -7px;
}
}
&.right::after,
&.left::after,
&.right::before,
&.left::before {
top: 50%;
margin-top: -9px;
}
&.right {
left: 100%;
&::after,
&::before {
transform-origin: right top;
left: -24px;
transform: rotate(-90deg);
}
}
&.left {
right: 100%;
&::after,
&::before {
transform-origin: left top;
right: -24px;
transform: rotate(90deg);
}
}
} }
.no-arrow { .no-arrow {
box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.75); box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.75);

View File

@ -27,6 +27,7 @@
TCellStyle, TCellStyle,
ICell ICell
} from './internal/DateUtils' } from './internal/DateUtils'
import { capitalizeFirstLetter } from '../../utils'
export let currentDate: Date | null export let currentDate: Date | null
export let mondayStart: boolean = true export let mondayStart: boolean = true
@ -42,7 +43,6 @@
if (areDatesEqual(today, new Date(viewDate.getFullYear(), viewDate.getMonth(), n))) return true if (areDatesEqual(today, new Date(viewDate.getFullYear(), viewDate.getMonth(), n))) return true
return false return false
} }
const capitalizeFirstLetter = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1)
let days: Array<ICell> = [] let days: Array<ICell> = []
const getDateStyle = (date: Date): TCellStyle => { const getDateStyle = (date: Date): TCellStyle => {

View File

@ -18,6 +18,7 @@
import IconNavNext from '../icons/NavNext.svelte' import IconNavNext from '../icons/NavNext.svelte'
import Icon from '../Icon.svelte' import Icon from '../Icon.svelte'
import { firstDay, day, getWeekDayName, areDatesEqual, getMonthName, weekday, isWeekend } from './internal/DateUtils' import { firstDay, day, getWeekDayName, areDatesEqual, getMonthName, weekday, isWeekend } from './internal/DateUtils'
import { capitalizeFirstLetter } from '../../utils'
export let currentDate: Date | null export let currentDate: Date | null
export let viewDate: Date export let viewDate: Date
@ -32,7 +33,6 @@
$: firstDayOfCurrentMonth = firstDay(viewDate, mondayStart) $: firstDayOfCurrentMonth = firstDay(viewDate, mondayStart)
let monthYear: string let monthYear: string
const today: Date = new Date(Date.now()) const today: Date = new Date(Date.now())
const capitalizeFirstLetter = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1)
afterUpdate(() => { afterUpdate(() => {
monthYear = capitalizeFirstLetter(getMonthName(viewDate)) + ' ' + viewDate.getFullYear() monthYear = capitalizeFirstLetter(getMonthName(viewDate)) + ' ' + viewDate.getFullYear()

View File

@ -10,6 +10,7 @@ const emptyTooltip: LabelAndProps = {
props: undefined, props: undefined,
anchor: undefined, anchor: undefined,
onUpdate: undefined, onUpdate: undefined,
keys: undefined,
kind: 'tooltip' kind: 'tooltip'
} }
let storedValue: LabelAndProps = emptyTooltip let storedValue: LabelAndProps = emptyTooltip
@ -27,10 +28,30 @@ export function tooltip (node: HTMLElement, options?: LabelAndProps): any {
if (opt.kind !== 'submenu') { if (opt.kind !== 'submenu') {
clearTimeout(toHandler) clearTimeout(toHandler)
toHandler = setTimeout(() => { toHandler = setTimeout(() => {
showTooltip(opt.label, node, opt.direction, opt.component, opt.props, opt.anchor, opt.onUpdate, opt.kind) showTooltip(
opt.label,
node,
opt.direction,
opt.component,
opt.props,
opt.anchor,
opt.onUpdate,
opt.kind,
opt.keys
)
}, 250) }, 250)
} else { } else {
showTooltip(opt.label, node, opt.direction, opt.component, opt.props, opt.anchor, opt.onUpdate, opt.kind) showTooltip(
opt.label,
node,
opt.direction,
opt.component,
opt.props,
opt.anchor,
opt.onUpdate,
opt.kind,
opt.keys
)
} }
} }
} }
@ -45,7 +66,17 @@ export function tooltip (node: HTMLElement, options?: LabelAndProps): any {
if (node !== storedValue.element) return if (node !== storedValue.element) return
const shown = !!(storedValue.label !== undefined || storedValue.component !== undefined) const shown = !!(storedValue.label !== undefined || storedValue.component !== undefined)
if (shown) { if (shown) {
showTooltip(opt.label, node, opt.direction, opt.component, opt.props, opt.anchor, opt.onUpdate, opt.kind) showTooltip(
opt.label,
node,
opt.direction,
opt.component,
opt.props,
opt.anchor,
opt.onUpdate,
opt.kind,
opt.keys
)
} }
}, },
@ -64,7 +95,8 @@ export function showTooltip (
props?: any, props?: any,
anchor?: HTMLElement, anchor?: HTMLElement,
onUpdate?: (result: any) => void, onUpdate?: (result: any) => void,
kind?: 'tooltip' | 'submenu' | 'popup' kind?: 'tooltip' | 'submenu' | 'popup',
keys?: string[]
): void { ): void {
storedValue = { storedValue = {
label, label,
@ -74,7 +106,8 @@ export function showTooltip (
props, props,
anchor, anchor,
onUpdate, onUpdate,
kind kind,
keys
} }
tooltipstore.update((old) => { tooltipstore.update((old) => {
if (old.component === storedValue.component) { if (old.component === storedValue.component) {

View File

@ -238,6 +238,7 @@ export interface LabelAndProps {
anchor?: HTMLElement anchor?: HTMLElement
onUpdate?: (result: any) => void onUpdate?: (result: any) => void
kind?: 'tooltip' | 'submenu' | 'popup' kind?: 'tooltip' | 'submenu' | 'popup'
keys?: string[]
} }
export interface ListItem { export interface ListItem {

View File

@ -197,3 +197,36 @@ export interface IModeSelector {
config: Array<[string, IntlString, object]> config: Array<[string, IntlString, object]>
onChange: (_mode: string) => void onChange: (_mode: string) => void
} }
/**
* @public
*/
export function capitalizeFirstLetter (str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1)
}
const isMac = /Macintosh/i.test(navigator.userAgent)
/**
* @public
*/
export function formatKey (key: string): string[][] {
const thens = key.split('->')
const result: string[][] = []
for (const r of thens) {
result.push(
r.split('+').map((it) =>
it
.replace(/key/g, '')
.replace(/Meta|meta/g, isMac ? '⌘' : 'Ctrl')
.replace(/ArrowUp/g, '↑')
.replace(/ArrowDown/g, '↓')
.replace(/ArrowLeft/g, '←')
.replace(/ArrowRight/g, '→')
.replace(/Backspace/g, '⌫')
.toLocaleLowerCase()
)
)
}
return result
}

View File

@ -14,8 +14,9 @@
--> -->
<script lang="ts"> <script lang="ts">
import { Ref, Space } from '@hcengineering/core' import { Ref, Space } from '@hcengineering/core'
import { MultipleDraftController } from '@hcengineering/presentation' import { MultipleDraftController, getClient } from '@hcengineering/presentation'
import { Button, IconAdd, showPopup } from '@hcengineering/ui' import { Button, IconAdd, showPopup } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { onDestroy } from 'svelte' import { onDestroy } from 'svelte'
import tracker from '../plugin' import tracker from '../plugin'
import CreateIssue from './CreateIssue.svelte' import CreateIssue from './CreateIssue.svelte'
@ -38,18 +39,31 @@
closed = true closed = true
}) })
} }
$: label = draftExists || !closed ? tracker.string.ResumeDraft : tracker.string.NewIssue
const client = getClient()
let keys: string[] | undefined = undefined
client.findOne(view.class.Action, { _id: tracker.action.NewIssue }).then((p) => (keys = p?.keyBinding))
</script> </script>
<div class="antiNav-subheader"> <div class="antiNav-subheader">
<Button <Button
icon={IconAdd} icon={IconAdd}
label={draftExists || !closed ? tracker.string.ResumeDraft : tracker.string.NewIssue} {label}
justify={'left'} justify={'left'}
kind={'primary'} kind={'primary'}
width={'100%'} width={'100%'}
size={'large'} size={'large'}
on:click={newIssue} on:click={newIssue}
id="new-issue" id="new-issue"
showTooltip={{
direction: 'bottom',
label,
keys
}}
> >
<div slot="content" class="draft-circle-container"> <div slot="content" class="draft-circle-container">
{#if draftExists} {#if draftExists}

View File

@ -481,6 +481,7 @@ export default plugin(trackerId, {
MoveToProject: '' as Ref<Action>, MoveToProject: '' as Ref<Action>,
Duplicate: '' as Ref<Action>, Duplicate: '' as Ref<Action>,
Relations: '' as Ref<Action>, Relations: '' as Ref<Action>,
NewIssue: '' as Ref<Action>,
NewSubIssue: '' as Ref<Action>, NewSubIssue: '' as Ref<Action>,
EditWorkflowStatuses: '' as Ref<Action>, EditWorkflowStatuses: '' as Ref<Action>,
EditProject: '' as Ref<Action>, EditProject: '' as Ref<Action>,

View File

@ -25,7 +25,9 @@
Label, Label,
EditWithIcon, EditWithIcon,
IconSearch, IconSearch,
deviceOptionsStore deviceOptionsStore,
capitalizeFirstLetter,
formatKey
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { Action, ViewContext } from '@hcengineering/view' import { Action, ViewContext } from '@hcengineering/view'
import { filterActions, getSelection } from '../actions' import { filterActions, getSelection } from '../actions'
@ -167,30 +169,7 @@
handleSelection(key, selection) handleSelection(key, selection)
} }
} }
const capitalizeFirstLetter = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1)
const isMac = /Macintosh/i.test(navigator.userAgent)
function formatKey (key: string): string[][] {
const thens = key.split('->')
const result: string[][] = []
for (const r of thens) {
result.push(
r.split('+').map((it) =>
it
.replaceAll('key', '')
.replaceAll(/Meta|meta/g, isMac ? '⌘' : 'Ctrl')
.replaceAll('ArrowUp', '↑')
.replaceAll('ArrowDown', '↓')
.replaceAll('ArrowLeft', '←')
.replaceAll('ArrowRight', '→')
.replaceAll('Backspace', '⌫')
.toLocaleLowerCase()
)
)
}
return result
}
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
</script> </script>

View File

@ -56,7 +56,7 @@
icon={view.icon.ViewButton} icon={view.icon.ViewButton}
label={view.string.View} label={view.string.View}
{kind} {kind}
showTooltip={{ label: view.string.CustomizeView }} showTooltip={{ label: view.string.CustomizeView, direction: 'bottom' }}
bind:input={btn} bind:input={btn}
on:click={clickHandler} on:click={clickHandler}
/> />

View File

@ -37,7 +37,7 @@
icon={view.icon.Configure} icon={view.icon.Configure}
label={view.string.Show} label={view.string.Show}
{kind} {kind}
showTooltip={{ label: view.string.CustomizeView }} showTooltip={{ label: view.string.CustomizeView, direction: 'bottom' }}
bind:input={btn} bind:input={btn}
on:click={clickHandler} on:click={clickHandler}
/> />

View File

@ -3,7 +3,9 @@
import setting, { settingId } from '@hcengineering/setting' import setting, { settingId } from '@hcengineering/setting'
import { import {
Button, Button,
capitalizeFirstLetter,
closePopup, closePopup,
formatKey,
getCurrentResolvedLocation, getCurrentResolvedLocation,
Icon, Icon,
IconArrowLeft, IconArrowLeft,
@ -34,28 +36,6 @@
navigate(loc) navigate(loc)
} }
const capitalizeFirstLetter = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1)
function formatKey (key: string): string[][] {
const thens = key.split('->')
const result: string[][] = []
for (const r of thens) {
result.push(
r.split('+').map((it) =>
it
.replaceAll('key', '')
.replaceAll(/Meta|meta/g, isMac ? '⌘' : 'Ctrl')
.replaceAll('ArrowUp', '↑')
.replaceAll('ArrowDown', '↓')
.replaceAll('ArrowLeft', '←')
.replaceAll('ArrowRight', '→')
.replaceAll('Backspace', '⌫')
)
)
}
return result
}
async function getActions () { async function getActions () {
categories = await getClient().findAll(view.class.ActionCategory, []) categories = await getClient().findAll(view.class.ActionCategory, [])
const rawActions = await client.findAll(view.class.Action, []) const rawActions = await client.findAll(view.class.Action, [])
@ -82,8 +62,6 @@
} }
getActions() getActions()
const isMac = /Macintosh/i.test(navigator.userAgent)
const cards = [ const cards = [
{ {
icon: DocumentationIcon, icon: DocumentationIcon,