Refactor tooltips

This commit is contained in:
IRBorisov 2023-12-21 00:12:24 +03:00
parent 8e84d570e3
commit 4d4d0a611d
24 changed files with 130 additions and 48 deletions

View File

@ -1,5 +1,7 @@
import clsx from 'clsx';
import { globalIDs } from '@/utils/constants';
import { CProps } from '../props';
interface ButtonProps
@ -12,8 +14,8 @@ extends CProps.Control, CProps.Colors, CProps.Button {
}
function Button({
text, icon, loading,
dense, disabled, noBorder, noOutline,
text, icon, title,
loading, dense, disabled, noBorder, noOutline,
colors = 'clr-btn-default',
className,
...restProps
@ -36,6 +38,8 @@ function Button({
className,
colors
)}
data-tooltip-id={title ? (globalIDs.tooltip) : undefined}
data-tooltip-content={title}
{...restProps}
>
{icon ? icon : null}

View File

@ -1,6 +1,8 @@
import clsx from 'clsx';
import { useMemo } from 'react';
import { globalIDs } from '@/utils/constants';
import { CheckboxCheckedIcon } from '../Icons';
import { CProps } from '../props';
import Label from './Label';
@ -15,7 +17,7 @@ extends Omit<CProps.Button, 'value' | 'onClick'> {
}
function Checkbox({
id, disabled, label,
id, disabled, label, title,
className, value, setValue, ...restProps
}: CheckboxProps) {
const cursor = useMemo(
@ -48,6 +50,8 @@ function Checkbox({
)}
disabled={disabled}
onClick={handleClick}
data-tooltip-id={title ? (globalIDs.tooltip) : undefined}
data-tooltip-content={title}
{...restProps}
>
<div className={clsx(

View File

@ -2,12 +2,17 @@ import clsx from 'clsx';
import type { TabProps } from 'react-tabs';
import { Tab } from 'react-tabs';
import { globalIDs } from '@/utils/constants';
interface ConceptTabProps
extends Omit<TabProps, 'children'> {
label?: string
}
function ConceptTab({ label, className, ...otherProps }: ConceptTabProps) {
function ConceptTab({
label, title, className,
...otherProps
}: ConceptTabProps) {
return (
<Tab
className={clsx(
@ -18,6 +23,8 @@ function ConceptTab({ label, className, ...otherProps }: ConceptTabProps) {
'select-none hover:cursor-pointer',
className
)}
data-tooltip-id={title ? (globalIDs.tooltip) : undefined}
data-tooltip-content={title}
{...otherProps}
>
{label}

View File

@ -1,6 +1,7 @@
'use client';
import clsx from 'clsx';
import { ReactNode } from 'react';
import { createPortal } from 'react-dom';
import { ITooltip, Tooltip } from 'react-tooltip';
@ -9,9 +10,11 @@ import { useConceptTheme } from '@/context/ThemeContext';
interface ConceptTooltipProps
extends Omit<ITooltip, 'variant'> {
layer?: string
text?: string
}
function ConceptTooltip({
text, children,
layer='z-tooltip',
place='bottom',
className,
@ -24,20 +27,25 @@ function ConceptTooltip({
return null;
}
return createPortal(
<Tooltip
delayShow={500}
(<Tooltip
delayShow={1000}
delayHide={100}
opacity={0.97}
className={clsx(
'overflow-auto',
'overflow-hidden',
'border shadow-md',
layer,
className
)}
classNameArrow={layer}
style={{...{ paddingTop: '2px', paddingBottom: '2px'}, ...style}}
variant={(darkMode ? 'dark' : 'light')}
place={place}
{...restProps}
/>, document.body);
>
{text}
{children as ReactNode}
</Tooltip>), document.body);
}
export default ConceptTooltip;

View File

@ -1,5 +1,7 @@
import clsx from 'clsx';
import { globalIDs } from '@/utils/constants';
import { CProps } from '../props';
interface DropdownButtonProps
@ -11,7 +13,9 @@ extends CProps.Button {
}
function DropdownButton({
text, icon, className, onClick,
text, icon,
className, title,
onClick,
children,
...restProps
}: DropdownButtonProps) {
@ -29,6 +33,8 @@ function DropdownButton({
},
className
)}
data-tooltip-id={title ? (globalIDs.tooltip) : undefined}
data-tooltip-content={title}
{...restProps}
>
{children ? children : null}

View File

@ -1,5 +1,7 @@
import clsx from 'clsx';
import { globalIDs } from '@/utils/constants';
import { CProps } from '../props';
interface MiniButtonProps
@ -10,7 +12,7 @@ extends CProps.Button {
function MiniButton({
icon, noHover, tabIndex,
className,
title, className,
...restProps
}: MiniButtonProps) {
return (
@ -27,6 +29,8 @@ function MiniButton({
},
className
)}
data-tooltip-id={title ? (globalIDs.tooltip) : undefined}
data-tooltip-content={title}
{...restProps}
>
{icon}

View File

@ -1,5 +1,7 @@
import clsx from 'clsx';
import { globalIDs } from '@/utils/constants';
import { CProps } from '../props';
interface SelectorButtonProps
@ -12,7 +14,7 @@ extends CProps.Button {
}
function SelectorButton({
text, icon,
text, icon, title,
colors = 'clr-btn-default',
className,
transparent,
@ -20,6 +22,8 @@ function SelectorButton({
}: SelectorButtonProps) {
return (
<button type='button'
data-tooltip-id={title ? (globalIDs.tooltip) : undefined}
data-tooltip-content={title}
className={clsx(
'px-1 flex flex-start items-center gap-1',
'text-sm small-caps select-none',

View File

@ -16,7 +16,8 @@ function preventEnterCapture(event: React.KeyboardEvent<HTMLInputElement>) {
}
function TextInput({
id, label, dense, noBorder, noOutline, allowEnter, disabled,
id, label,
dense, noBorder, noOutline, allowEnter, disabled,
className,
colors = 'clr-input',
onKeyDown,

View File

@ -2,6 +2,8 @@
import clsx from 'clsx';
import { useMemo } from 'react';
import { globalIDs } from '@/utils/constants';
import { CheckboxCheckedIcon, CheckboxNullIcon } from '../Icons';
import { CheckboxProps } from './Checkbox';
import Label from './Label';
@ -13,7 +15,7 @@ extends Omit<CheckboxProps, 'value' | 'setValue'> {
}
function Tristate({
id, disabled, label,
id, disabled, label, title,
className,
value, setValue,
...restProps
@ -53,6 +55,8 @@ function Tristate({
)}
disabled={disabled}
onClick={handleClick}
data-tooltip-id={title ? (globalIDs.tooltip) : undefined}
data-tooltip-content={title}
{...restProps}
>
<div className={clsx(

View File

@ -28,7 +28,7 @@ function HelpTermGraph() {
<Divider margins='mt-2' />
<InfoCstClass title='Классы конституент' />
<InfoCstClass header='Классы конституент' />
</div>
</div>);
}

View File

@ -42,19 +42,19 @@ function Navigation () {
<div className='flex'>
<NavigationButton
text='Новая схема'
description='Создать новую схему'
title='Создать новую схему'
icon={<FaSquarePlus size='1.5rem' />}
onClick={navigateCreateNew}
/>
<NavigationButton
text='Библиотека'
description='Библиотека концептуальных схем'
title='Список схем'
icon={<IoLibrary size='1.5rem' />}
onClick={navigateLibrary}
/>
<NavigationButton
text='Справка'
description='Справочные материалы и обучение'
title='Справочные материалы'
icon={<EducationIcon />}
onClick={navigateHelp}
/>

View File

@ -1,17 +1,19 @@
import clsx from 'clsx';
import { globalIDs } from '@/utils/constants';
interface NavigationButtonProps {
id?: string
text?: string
icon: React.ReactNode
description?: string
title?: string
onClick?: () => void
}
function NavigationButton({ id, icon, description, onClick, text }: NavigationButtonProps) {
function NavigationButton({ icon, title, onClick, text }: NavigationButtonProps) {
return (
<button id={id} type='button' tabIndex={-1}
title={description}
<button type='button' tabIndex={-1}
data-tooltip-id={title ? (globalIDs.tooltip) : undefined}
data-tooltip-content={title}
onClick={onClick}
className={clsx(
'mr-1 h-full',

View File

@ -18,13 +18,12 @@ function UserMenu() {
<div ref={menu.ref} className='h-full'>
{!user ?
<NavigationButton
description='Перейти на страницу логина'
icon={<InDoorIcon />}
title='Перейти на страницу логина'
icon={<InDoorIcon size='1.5rem' className='clr-text-primary' />}
onClick={navigateLogin}
/> : null}
{user ?
<NavigationButton
description={`Пользователь ${user?.username}`}
icon={<FaCircleUser size='1.5rem' />}
onClick={menu.toggle}
/> : null}

View File

@ -7,15 +7,15 @@ import { prefixes } from '@/utils/constants';
import { describeCstClass, labelCstClass } from '@/utils/labels';
interface InfoCstClassProps {
title?: string
header?: string
}
function InfoCstClass({ title }: InfoCstClassProps) {
function InfoCstClass({ header }: InfoCstClassProps) {
const { colors } = useConceptTheme();
return (
<div className='flex flex-col gap-1 mb-2'>
{title ? <h1>{title}</h1> : null}
{header ? <h1>{header}</h1> : null}
{Object.values(CstClass).map(
(cclass, index) => {
return (

View File

@ -1,9 +1,12 @@
'use client';
import clsx from 'clsx';
import { createContext, useContext, useLayoutEffect, useMemo, useState } from 'react';
import ConceptTooltip from '@/components/Common/ConceptTooltip';
import useLocalStorage from '@/hooks/useLocalStorage';
import { darkT, IColorTheme, lightT } from '@/utils/color';
import { globalIDs } from '@/utils/constants';
interface IThemeContext {
viewportHeight: string
@ -85,6 +88,17 @@ export const ThemeState = ({ children }: ThemeStateProps) => {
setNoFooter, setShowScroll,
viewportHeight, mainHeight
}}>
<>
<ConceptTooltip float
id={`${globalIDs.tooltip}`}
layer='z-topmost'
place='right-start'
className={clsx(
'mt-3 translate-y-1/2',
'max-w-[20rem]'
)}
/>
{children}
</>
</ThemeContext.Provider>);
}

View File

@ -83,6 +83,9 @@
.z-modal-tooltip {
z-index: 90;
}
.z-topmost {
z-index: 99;
}
:root {

View File

@ -1,4 +1,7 @@
import clsx from 'clsx';
import { TokenID } from '@/models/rslang';
import { globalIDs } from '@/utils/constants';
interface RSLocalButtonProps {
text: string
@ -11,8 +14,14 @@ function RSLocalButton({ text, title, disabled, onInsert }: RSLocalButtonProps)
return (
<button type='button' tabIndex={-1}
disabled={disabled}
title={title}
className='w-[2rem] h-6 cursor-pointer disabled:cursor-default rounded-none clr-hover clr-btn-clear'
data-tooltip-id={title ? globalIDs.tooltip: undefined}
data-tooltip-content={title}
className={clsx(
'w-[2rem] h-6',
'cursor-pointer disabled:cursor-default',
'rounded-none',
'clr-hover clr-btn-clear'
)}
onClick={() => onInsert(TokenID.ID_LOCAL, text)}
>
{text}

View File

@ -1,4 +1,7 @@
import clsx from 'clsx';
import { TokenID } from '@/models/rslang';
import { globalIDs } from '@/utils/constants';
import { describeToken, labelToken } from '@/utils/labels';
interface RSTokenButtonProps {
@ -9,13 +12,23 @@ interface RSTokenButtonProps {
function RSTokenButton({ token, disabled, onInsert }: RSTokenButtonProps) {
const label = labelToken(token);
const width = label.length > 3 ? 'w-[4.5rem]' : 'w-[2.25rem]';
return (
<button type='button' tabIndex={-1}
disabled={disabled}
onClick={() => onInsert(token)}
title={describeToken(token)}
className={`px-1 cursor-pointer disabled:cursor-default h-6 ${width} outline-none clr-hover clr-btn-clear`}
className={clsx(
'h-6',
'px-1',
'outline-none',
'clr-hover clr-btn-clear',
'cursor-pointer disabled:cursor-default',
{
'w-[4.5rem]': label.length > 3,
'w-[2.25rem]': label.length <= 3
}
)}
data-tooltip-id={globalIDs.tooltip}
data-tooltip-content={describeToken(token)}
>
{label ? <span className='whitespace-nowrap'>{label}</span> : null}
</button>);

View File

@ -10,6 +10,7 @@ import { type IConstituenta } from '@/models/rsform';
import { inferStatus } from '@/models/rsformAPI';
import { IExpressionParse, ParsingStatus } from '@/models/rslang';
import { colorbgCstStatus } from '@/utils/color';
import { globalIDs } from '@/utils/constants';
import { labelExpressionStatus } from '@/utils/labels';
import StatusIcon from './StatusIcon';
@ -37,7 +38,6 @@ function StatusBar({ isModified, processing, constituenta, parseData, onAnalyze
return (
<div
title='Проверить определение [Ctrl + Q]'
className={clsx(
'w-[10rem] h-[1.75rem]',
'px-2 flex items-center justify-center gap-2',
@ -47,6 +47,8 @@ function StatusBar({ isModified, processing, constituenta, parseData, onAnalyze
'duration-500 transition-colors'
)}
style={{backgroundColor: processing ? colors.bgDefault : colorbgCstStatus(status, colors)}}
data-tooltip-id={globalIDs.tooltip}
data-tooltip-content='Проверить определение [Ctrl + Q]'
onClick={onAnalyze}
>
{processing ?

View File

@ -52,7 +52,7 @@ function RSFormToolbar({
onClick={onDownload}
/>
<MiniButton
title={'отслеживание: ' + (isSubscribed ? '[включено]' : '[выключено]')}
title={'Отслеживание ' + (isSubscribed ? 'включено' : 'выключено')}
disabled={anonymous || processing}
icon={isSubscribed
? <FiBell size='1.25rem' className='clr-text-primary' />
@ -62,7 +62,7 @@ function RSFormToolbar({
onClick={onToggleSubscribe}
/>
<MiniButton
title={claimable ? 'Стать владельцем' : 'Невозможно стать владельцем' }
title='Стать владельцем'
icon={<LuCrown size='1.25rem' className={!claimable || anonymous ? '' : 'clr-text-success'}/>}
disabled={!claimable || anonymous || processing}
onClick={onClaim}

View File

@ -419,10 +419,7 @@ function RSTabs() {
/>
<ConceptTab
label='Содержание'
title={[
`Всего конституент: ${schema.stats?.count_all ?? 0}`,
`Количество ошибок: ${schema.stats?.count_errors ?? 0}`
].join('\n')}
title={`Конституент: ${schema.stats?.count_all ?? 0} | Ошибок: ${schema.stats?.count_errors ?? 0}`}
/>
<ConceptTab label='Редактор' />
<ConceptTab label='Граф термов' />

View File

@ -172,7 +172,7 @@ function RSTabsMenu({
<div ref={accessMenu.ref}>
<Button dense noBorder tabIndex={-1}
title={`режим ${labelAccessMode(mode)}`}
title={`Режим ${labelAccessMode(mode)}`}
className='h-full pr-2'
style={{outlineColor: 'transparent'}}
icon={

View File

@ -189,9 +189,9 @@ export function domTooltipEntityReference(ref: IEntityReference, cst: IConstitue
'select-none cursor-auto'
);
const title = document.createElement('p');
title.innerHTML = '<b>Ссылка на конституенту</b>';
dom.appendChild(title);
const header = document.createElement('p');
header.innerHTML = '<b>Ссылка на конституенту</b>';
dom.appendChild(header);
const term = document.createElement('p');
term.innerHTML = `<b>${ref.entity}:</b> ${describeConstituentaTerm(cst)}`;
@ -237,9 +237,9 @@ export function domTooltipSyntacticReference(ref: ISyntacticReference, masterRef
'select-none cursor-auto'
);
const title = document.createElement('p');
title.innerHTML = '<b>Связывание слов</b>';
dom.appendChild(title);
const header = document.createElement('p');
header.innerHTML = '<b>Связывание слов</b>';
dom.appendChild(header);
const offset = document.createElement('p');
offset.innerHTML = `<b>Смещение:</b> ${ref.offset}`;

View File

@ -83,6 +83,7 @@ export const urls = {
* Global unique IDs.
*/
export const globalIDs = {
tooltip: 'global-tooltip',
password_tooltip: 'password-tooltip',
main_scroll: 'main-scroll',
library_item_editor: 'library-item-editor',