F: Upgrade hover animations pt2
Some checks are pending
Frontend CI / build (22.x) (push) Waiting to run
Frontend CI / notify-failure (push) Blocked by required conditions

This commit is contained in:
Ivan 2025-06-19 12:44:58 +03:00
parent 7df3c4b7b2
commit dd8d4fe622
39 changed files with 65 additions and 78 deletions

View File

@ -25,8 +25,12 @@ export function ToggleNavigation() {
data-tooltip-content={noNavigationAnimation ? 'Показать навигацию' : 'Скрыть навигацию'}
aria-label={noNavigationAnimation ? 'Показать навигацию' : 'Скрыть навигацию'}
>
{!noNavigationAnimation ? <IconPin size='0.75rem' className='hover:text-primary' /> : null}
{noNavigationAnimation ? <IconUnpin size='0.75rem' className='hover:text-primary' /> : null}
{!noNavigationAnimation ? (
<IconPin size='0.75rem' className='hover:text-primary cc-animate-color cc-hover-pulse' />
) : null}
{noNavigationAnimation ? (
<IconUnpin size='0.75rem' className='hover:text-primary cc-animate-color cc-hover-pulse' />
) : null}
</button>
{!noNavigationAnimation ? (
<button
@ -38,8 +42,12 @@ export function ToggleNavigation() {
data-tooltip-content={darkMode ? 'Тема: Темная' : 'Тема: Светлая'}
aria-label={darkMode ? 'Тема: Темная' : 'Тема: Светлая'}
>
{darkMode ? <IconDarkTheme size='0.75rem' className='hover:text-primary' /> : null}
{!darkMode ? <IconLightTheme size='0.75rem' className='hover:text-primary' /> : null}
{darkMode ? (
<IconDarkTheme size='0.75rem' className='hover:text-primary cc-animate-color cc-hover-pulse' />
) : null}
{!darkMode ? (
<IconLightTheme size='0.75rem' className='hover:text-primary cc-animate-color cc-hover-pulse' />
) : null}
</button>
) : null}
</div>

View File

@ -1,8 +1,7 @@
import clsx from 'clsx';
import { globalIDs } from '@/utils/constants';
import { type Button } from '../props';
import { cn } from '../utils';
interface MiniButtonProps extends Button {
/** Button type. */
@ -37,11 +36,12 @@ export function MiniButton({
<button
type={type}
tabIndex={tabIndex ?? -1}
className={clsx(
className={cn(
'rounded-lg',
'cc-controls cc-animate-background',
'cursor-pointer disabled:cursor-auto',
noHover ? 'outline-hidden' : 'cc-hover-bg',
(!tabIndex || tabIndex === -1) && 'outline-hidden',
!noHover && 'cc-hover-pulse',
!noPadding && 'px-1 py-1',
className
)}

View File

@ -92,7 +92,12 @@ export function ComboBox<Option>({
hidden={hidden && !open}
>
<span className='truncate'>{value ? labelValueFunc(value) : placeholder}</span>
<ChevronDownIcon className={cn('text-muted-foreground', clearable && !!value && 'opacity-0')} />
<ChevronDownIcon
className={cn(
'text-muted-foreground cc-hover-pulse hover:text-primary',
clearable && !!value && 'opacity-0'
)}
/>
{clearable && !!value ? (
<IconRemove
tabIndex={-1}

View File

@ -115,7 +115,7 @@ export function ComboMulti<Option>({
<IconRemove
tabIndex={-1}
size='1rem'
className='cc-remove absolute pointer-events-auto right-3'
className='cc-remove absolute pointer-events-auto right-3 cc-hover-pulse hover:text-primary'
onClick={handleClear}
/>
) : null}

View File

@ -101,9 +101,8 @@ export function SelectTree<ItemType>({
{foldable.has(item) ? (
<MiniButton
aria-label={!folded.includes(item) ? 'Свернуть' : 'Развернуть'}
className={clsx('absolute left-1', !folded.includes(item) ? 'top-1.5' : 'top-1')}
className={clsx('absolute left-1 hover:text-primary', !folded.includes(item) ? 'top-1.5' : 'top-1')}
noPadding
noHover
icon={!folded.includes(item) ? <IconDropArrow size='1rem' /> : <IconPageRight size='1.25rem' />}
onClick={event => handleClickFold(event, item)}
/>

View File

@ -46,7 +46,7 @@ function SelectTrigger({
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className='size-4' />
<ChevronDownIcon className='size-4 cc-hover-pulse hover:text-primary' />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
);
@ -154,7 +154,7 @@ function SelectScrollDownButton({
className={cn('flex cursor-default items-center justify-center py-1', className)}
{...props}
>
<ChevronDownIcon className='size-4' />
<ChevronDownIcon className='size-4 cc-hover-pulse hover:text-primary' />
</SelectPrimitive.ScrollDownButton>
);
}

View File

@ -57,7 +57,7 @@ export function ValueIcon({
data-tooltip-hidden={hideTitle}
aria-label={title}
>
{onClick ? <MiniButton noHover noPadding icon={icon} onClick={onClick} disabled={disabled} /> : icon}
{onClick ? <MiniButton noPadding icon={icon} onClick={onClick} disabled={disabled} /> : icon}
<span id={id}>{value}</span>
</div>
);

View File

@ -42,7 +42,7 @@ export function BadgeHelp({ topic, padding = 'p-1', className, contentClass, sty
}
return (
<div tabIndex={-1} id={`help-${topic}`} className={cn(padding, className)} style={style}>
<IconHelp size='1.25rem' className='text-muted-foreground hover:text-primary' />
<IconHelp size='1.25rem' className='text-muted-foreground hover:text-primary cc-animate-color' />
<Tooltip
clickable
anchorSelect={`#help-${topic}`}

View File

@ -86,7 +86,6 @@ export function EditorLibraryItem({ schema, isAttachedToOSS }: EditorLibraryItem
<div className='relative flex justify-stretch sm:mb-1 max-w-120 gap-3'>
<MiniButton
title='Открыть в библиотеке'
noHover
noPadding
icon={<IconFolderOpened size='1.25rem' className='icon-primary' />}
onClick={handleOpenLibrary}

View File

@ -30,7 +30,6 @@ export function MenuRole({ isOwned, isEditor }: MenuRoleProps) {
if (isAnonymous) {
return (
<MiniButton
noHover
noPadding
titleHtml='<b>Анонимный режим</b><br />Войти в Портал'
hideTitle={accessMenu.isOpen}
@ -48,7 +47,7 @@ export function MenuRole({ isOwned, isEditor }: MenuRoleProps) {
noPadding
title={`Режим ${labelUserRole(role)}`}
hideTitle={accessMenu.isOpen}
className='h-full pr-2 bg-transparent text-muted-foreground hover:text-primary'
className='h-full pr-2 text-muted-foreground hover:text-primary cc-animate-color'
icon={<IconRole value={role} size='1.25rem' className='' />}
onClick={accessMenu.toggle}
/>

View File

@ -73,7 +73,6 @@ export function SelectLocation({ value, dense, prefix, onClick, className, style
{item.children.size > 0 ? (
<MiniButton
noPadding
noHover
icon={
folded.includes(item) ? (
item.filesInside ? (

View File

@ -48,9 +48,8 @@ export function DlgEditEditors() {
<span>Всего редакторов [{selected.length}]</span>
<MiniButton
title='Очистить список'
noHover
className='py-0 align-middle'
icon={<IconRemove size='1.5rem' className='cc-remove' />}
icon={<IconRemove size='1.25rem' className='cc-remove' />}
onClick={() => setSelected([])}
disabled={selected.length === 0}
/>

View File

@ -63,7 +63,6 @@ export function TableVersions({ processing, items, onDelete, selected, onSelect
<MiniButton
title='Удалить версию'
className='align-middle'
noHover
noPadding
icon={<IconRemove size='1.25rem' className='cc-remove' />}
onClick={event => handleDeleteVersion(event, props.row.original.id)}

View File

@ -56,7 +56,6 @@ export function LibraryPage() {
<ToolbarSearch className='top-0 h-9' total={libraryItems.length} filtered={filtered.length} />
<div className='relative flex'>
<MiniButton
noHover
title='Выгрузить в формате CSV'
className='absolute z-tooltip -top-8 right-6 hidden sm:block'
icon={<IconCSV size='1.25rem' className='text-muted-foreground hover:text-constructive' />}

View File

@ -39,10 +39,9 @@ export function useLibraryColumns() {
titleHtml='Переключение в режим Проводник'
aria-label='Переключатель режима Проводник'
noPadding
noHover
className='pl-2 max-h-4 -translate-y-0.5'
className='ml-2 max-h-4 -translate-y-0.5'
onClick={handleToggleFolder}
icon={<IconFolderTree size='1.25rem' className='text-muted-foreground hover:text-primary' />}
icon={<IconFolderTree size='1.25rem' className='text-primary' />}
/>
),
size: 50,

View File

@ -86,9 +86,8 @@ export function ViewSideLocation({ isVisible, onRenameLocation }: ViewSideLocati
/>
) : null}
<MiniButton
noHover
title='Переключение в режим Таблица'
icon={<IconFolderTree size='1.25rem' className='text-muted-foreground hover:text-primary' />}
icon={<IconFolderTree size='1.25rem' className='text-primary' />}
onClick={toggleFolderMode}
/>
</div>

View File

@ -98,21 +98,18 @@ export function PickContents({
<div className='flex w-fit'>
<MiniButton
title='Удалить'
noHover
className='px-0'
icon={<IconRemove size='1rem' className='icon-red' />}
onClick={() => handleDelete(props.row.original)}
/>
<MiniButton
title='Переместить выше'
noHover
className='px-0'
icon={<IconMoveUp size='1rem' className='icon-primary' />}
onClick={() => handleMoveUp(props.row.original)}
/>
<MiniButton
title='Переместить ниже'
noHover
className='px-0'
icon={<IconMoveDown size='1rem' className='icon-primary' />}
onClick={() => handleMoveDown(props.row.original)}

View File

@ -82,21 +82,18 @@ export function PickMultiOperation({ rows, items, value, onChange, className, ..
<div className='flex w-fit'>
<MiniButton
title='Удалить'
noHover
className='px-0'
icon={<IconRemove size='1rem' className='icon-red' />}
onClick={() => handleDelete(props.row.original.id)}
/>
<MiniButton
title='Переместить выше'
noHover
className='px-0'
icon={<IconMoveUp size='1rem' className='icon-primary' />}
onClick={() => handleMoveUp(props.row.original.id)}
/>
<MiniButton
title='Переместить ниже'
noHover
className='px-0'
icon={<IconMoveDown size='1rem' className='icon-primary' />}
onClick={() => handleMoveDown(props.row.original.id)}

View File

@ -61,7 +61,6 @@ export function DlgChangeInputSchema() {
<Label text='Загружаемая концептуальная схема' />
<MiniButton
title='Сбросить выбор схемы'
noHover
noPadding
icon={<IconReset size='1.25rem' className='icon-primary' />}
onClick={() => setValue('input', null)}

View File

@ -97,7 +97,6 @@ export function TabInputOperation() {
<Label text='Загружаемая концептуальная схема' />
<MiniButton
title='Сбросить выбор схемы'
noHover
noPadding
icon={<IconReset size='1.25rem' className='icon-primary' />}
onClick={() => setValue('item_data.result', null)}

View File

@ -36,7 +36,7 @@ export function MenuEditOss() {
noPadding
title='Редактирование'
hideTitle={menu.isOpen}
className='h-full px-3 bg-transparent text-muted-foreground hover:text-primary'
className='h-full px-3 text-muted-foreground hover:text-primary cc-animate-color'
icon={<IconEdit2 size='1.25rem' />}
onClick={menu.toggle}
/>

View File

@ -53,7 +53,7 @@ export function MenuMain() {
title='Меню'
hideTitle={menu.isOpen}
icon={<IconMenu size='1.25rem' />}
className='h-full pl-2 text-muted-foreground hover:text-primary bg-transparent'
className='h-full pl-2 text-muted-foreground hover:text-primary cc-animate-color'
onClick={menu.toggle}
/>
<Dropdown isOpen={menu.isOpen} margin='mt-3'>

View File

@ -196,13 +196,11 @@ export function PickSubstitutions({
<div className='max-w-fit'>
<MiniButton
title='Принять предложение'
noHover
icon={<IconAccept size='1rem' className='icon-green' />}
onClick={() => handleAcceptSuggestion(props.row.original)}
/>
<MiniButton
title='Игнорировать предложение'
noHover
icon={<IconRemove size='1rem' className='icon-red' />}
onClick={() => handleDeclineSuggestion(props.row.original)}
/>
@ -211,7 +209,6 @@ export function PickSubstitutions({
<div className='max-w-fit'>
<MiniButton
title='Удалить'
noHover
icon={<IconRemove size='1rem' className='icon-red' />}
onClick={() => handleDeleteSubstitution(props.row.original)}
/>

View File

@ -87,7 +87,6 @@ export function TabArguments() {
<MiniButton
title='Очистить значение'
noPadding
noHover
className='align-middle'
icon={<IconRemove size='1.25rem' className='cc-remove' />}
onClick={() => handleClearArgument(props.row.original)}
@ -129,7 +128,6 @@ export function TabArguments() {
<div className='flex'>
<MiniButton
title='Подставить значение аргумента'
noHover
className='py-0'
icon={<IconAccept size='1.5rem' className='icon-green' />}
onClick={() => handleAssignArgument(selectedArgument!, argumentValue)}
@ -137,7 +135,6 @@ export function TabArguments() {
/>
<MiniButton
title='Очистить поле'
noHover
className='py-0'
onClick={handleReset}
icon={<IconReset size='1.5rem' className='icon-primary' />}

View File

@ -144,14 +144,12 @@ export function DlgEditWordForms() {
<div className='flex flex-col self-center gap-1'>
<MiniButton
title='Определить граммемы'
noHover
icon={<IconMoveRight size='1.25rem' className='icon-primary' />}
onClick={handleParse}
disabled={isProcessing || !inputText}
/>
<MiniButton
title='Генерировать словоформу'
noHover
icon={<IconMoveLeft size='1.25rem' className='icon-primary' />}
onClick={handleInflect}
disabled={isProcessing || inputGrams.length == 0}
@ -169,14 +167,12 @@ export function DlgEditWordForms() {
<div className='cc-icons'>
<MiniButton
title='Внести словоформу'
noHover
icon={<IconAccept size='1.5rem' className='icon-green' />}
onClick={handleAddForm}
disabled={isProcessing || !inputText || inputGrams.length == 0}
/>
<MiniButton
title='Генерировать стандартные словоформы'
noHover
icon={<IconMoveDown size='1.5rem' className='icon-primary' />}
onClick={handleGenerateLexeme}
disabled={isProcessing || !inputText}
@ -186,7 +182,6 @@ export function DlgEditWordForms() {
<span>Заданные вручную словоформы [{forms.length}]</span>
<MiniButton
title='Сбросить все словоформы'
noHover
className='py-0 align-middle'
icon={<IconRemove size='1.5rem' className='cc-remove' />}
onClick={handleResetAll}

View File

@ -48,7 +48,6 @@ export function TableWordForms({ forms, setForms, onFormSelect }: TableWordForms
cell: props => (
<MiniButton
title='Удалить словоформу'
noHover
noPadding
className='align-middle'
icon={<IconRemove size='1.25rem' className='cc-remove' />}

View File

@ -165,10 +165,9 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
<MiniButton
title={isModified ? tooltipText.unsaved : 'Редактировать словоформы термина'}
aria-label='Редактировать словоформы термина'
noHover
onClick={handleEditTermForms}
className='absolute z-pop top-0 left-[calc(7ch+4px)]'
icon={<IconEdit size='1rem' className='hover:icon-primary' />}
icon={<IconEdit size='1rem' className='icon-primary' />}
disabled={isModified}
/>
) : null}
@ -182,9 +181,8 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
<MiniButton
title={isModified ? tooltipText.unsaved : 'Переименовать конституенту'}
aria-label='Переименовать конституенту'
noHover
onClick={handleRenameCst}
icon={<IconEdit size='1rem' className='hover:icon-primary' />}
icon={<IconEdit size='1rem' className='icon-primary' />}
disabled={isModified}
/>
) : null}

View File

@ -151,13 +151,12 @@ export function ToolbarConstituenta({
) : null}
<MiniButton
noHover
title='Отображение списка конституент'
icon={
showList ? (
<IconList size='1.25rem' className='hover:icon-primary' />
<IconList size='1.25rem' className='icon-primary' />
) : (
<IconListOff size='1.25rem' className='hover:icon-primary' />
<IconListOff size='1.25rem' className='icon-primary' />
)
}
onClick={toggleList}

View File

@ -24,20 +24,17 @@ export function ToolbarRSExpression({ className, disabled, showTypeGraph, showAS
<div className={clsx('cc-icons', className)}>
{!disabled || isProcessing ? (
<MiniButton
noHover
title='Отображение специальной клавиатуры'
icon={<IconShowKeyboard value={showControls} size='1.25rem' className='hover:text-primary' />}
onClick={toggleControls}
/>
) : null}
<MiniButton
noHover
title='Граф ступеней типизации'
icon={<IconTypeGraph size='1.25rem' className='hover:text-primary' />}
onClick={showTypeGraph}
/>
<MiniButton
noHover
title='Дерево разбора выражения'
onClick={showAST}
icon={<IconTree size='1.25rem' className='hover:text-primary' />}

View File

@ -149,7 +149,7 @@ export function EditorRSList() {
<MiniButton
className='absolute z-pop right-4 hidden sm:block top-18'
title='Выгрузить в формате CSV'
icon={<IconCSV size='1.25rem' className='text-muted-foreground hover:text-constructive' />}
icon={<IconCSV size='1.25rem' className='text-constructive' />}
onClick={handleDownloadCSV}
/>

View File

@ -157,8 +157,7 @@ export function ToolbarTermGraph() {
/>
) : null}
<MiniButton
noHover
icon={<IconTypeGraph size='1.25rem' className='hover:icon-primary' />}
icon={<IconTypeGraph size='1.25rem' className='icon-primary' />}
title='Граф ступеней'
onClick={handleShowTypeGraph}
/>

View File

@ -49,7 +49,6 @@ export function ViewHidden({ items }: ViewHiddenProps) {
<MiniButton
className='absolute right-[calc(1rem-4px)] top-3 pointer-events-auto'
noPadding
noHover
title={!isFolded ? 'Свернуть' : 'Развернуть'}
icon={!isFolded ? <IconDropArrowUp size='1rem' /> : <IconDropArrow size='1rem' />}
onClick={toggleFolded}

View File

@ -104,7 +104,6 @@ export function MenuEditSchema() {
if (isArchive) {
return (
<MiniButton
noHover
noPadding
titleHtml='<b>Архив</b>: Редактирование запрещено<br />Перейти к актуальной версии'
hideTitle={menu.isOpen}
@ -122,7 +121,7 @@ export function MenuEditSchema() {
noPadding
title='Редактирование'
hideTitle={menu.isOpen}
className='h-full px-3 bg-transparent text-muted-foreground hover:text-primary'
className='h-full px-3 text-muted-foreground hover:text-primary cc-animate-color'
icon={<IconEdit2 size='1.25rem' />}
onClick={menu.toggle}
/>

View File

@ -134,7 +134,7 @@ export function MenuMain() {
title='Меню'
hideTitle={menu.isOpen}
icon={<IconMenu size='1.25rem' />}
className='h-full pl-2 text-muted-foreground hover:text-primary bg-transparent'
className='h-full pl-2 text-muted-foreground hover:text-primary cc-animate-color bg-transparent'
onClick={menu.toggle}
/>
<Dropdown isOpen={menu.isOpen} margin='mt-3'>

View File

@ -39,7 +39,6 @@ export function ConstituentsSearch({ dense }: ConstituentsSearchProps) {
<SelectGraphFilter value={filterSource} onChange={setSource} dense={dense} />
{schema.stats.count_inherited > 0 ? (
<MiniButton
noHover
titleHtml={`Наследованные: <b>${includeInherited ? 'отображать' : 'скрывать'}</b>`}
aria-label={`Отображение наследованных: ${includeInherited ? 'отображать' : 'скрывать'}`}
icon={<IconChild size='1rem' className={includeInherited ? 'icon-primary' : 'cc-controls'} />}

View File

@ -32,7 +32,6 @@ export function TableUsers({ items, onDelete }: TableUsersProps) {
<MiniButton
title='Удалить из списка'
className='align-middle'
noHover
noPadding
icon={<IconRemove size='1.25rem' className='cc-remove' />}
onClick={() => onDelete(props.row.original.id)}

View File

@ -98,7 +98,7 @@ export function FormSignup() {
<IconHelp
id={globalIDs.email_tooltip}
className='absolute top-0 right-0 text-muted-foreground hover:text-primary'
className='absolute top-0 right-0 text-muted-foreground hover:text-primary cc-animate-color'
size='1.25rem'
/>
<Tooltip anchorSelect={`#${globalIDs.email_tooltip}`} offset={6}>

View File

@ -14,15 +14,8 @@
transition-duration: 500ms;
&:hover {
background-color: var(--color-accent);
color: var(--color-foreground);
}
.dark & {
&:hover {
color: var(--color-foreground);
}
}
}
@utility cc-btn-primary {

View File

@ -26,6 +26,27 @@
}
}
@utility cc-hover-pulse {
&:hover:not(:disabled) {
animation: pulse-scale 1s infinite;
transition: var(--duration-move);
}
}
@keyframes pulse-scale {
0% {
transform: scale(1);
}
70% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
@utility cc-hover-text {
&:hover:not(:disabled) {
color: var(--color-foreground);