Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(themes): allow custom animations on popups #352

Merged
merged 10 commits into from
Jan 17, 2025
1 change: 1 addition & 0 deletions src/apps/seelen_rofi/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export function App() {
theme={{
token: {
colorPrimary: isDarkMode ? colors.accent_light : colors.accent_dark,
motion: false,
},
algorithm: isDarkMode ? theme.darkAlgorithm : theme.defaultAlgorithm,
}}
Expand Down
58 changes: 37 additions & 21 deletions src/apps/seelen_rofi/modules/launcher/infra/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import { SeelenCommand } from '@seelen-ui/lib';
import { path } from '@tauri-apps/api';
import { convertFileSrc, invoke } from '@tauri-apps/api/core';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { Dropdown, Menu } from 'antd';
import { Menu } from 'antd';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';

import { OverflowTooltip } from 'src/apps/shared/components/OverflowTooltip';
import { useIcon } from 'src/apps/shared/hooks';
import { BackgroundByLayersV2 } from '../../../../seelenweg/components/BackgroundByLayers/infra';

import { StartMenuApp } from '../../shared/store/domain';

import { AnimatedDropdown } from '../../../../shared/components/AnimatedWrappers';
import { OverflowTooltip } from '../../../../shared/components/OverflowTooltip';

const MISSING_ICON_SRC = convertFileSrc(await path.resolveResource('static/icons/missing.png'));

export const Item = memo(({ item, hidden }: { item: StartMenuApp; hidden: boolean }) => {
Expand All @@ -32,27 +34,41 @@ export const Item = memo(({ item, hidden }: { item: StartMenuApp; hidden: boolea
const displayName = filename?.slice(0, filename.lastIndexOf('.')) || filename || '';

return (
<Dropdown
<AnimatedDropdown
animationDescription={{
maxAnimationTimeMs: 500,
openAnimationName: 'launcher-item-context-menu-open',
closeAnimationName: 'launcher-item-context-menu-close',
}}
trigger={['contextMenu']}
dropdownRender={() => (
<Menu
items={[
{
label: t('item.pin'),
key: 'pin',
onClick() {
invoke(SeelenCommand.WegPinItem, { path });
<BackgroundByLayersV2
className="launcher-item-context-menu"
prefix="menu"
onContextMenu={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Menu
items={[
{
label: t('item.pin'),
key: 'pin',
onClick() {
invoke(SeelenCommand.WegPinItem, { path });
},
},
},
{
label: t('item.open_location'),
key: 'open',
onClick() {
invoke(SeelenCommand.SelectFileOnExplorer, { path });
{
label: t('item.open_location'),
key: 'open',
onClick() {
invoke(SeelenCommand.SelectFileOnExplorer, { path });
},
},
},
]}
/>
]}
/>
</BackgroundByLayersV2>
)}
>
<button
Expand All @@ -64,6 +80,6 @@ export const Item = memo(({ item, hidden }: { item: StartMenuApp; hidden: boolea
<OverflowTooltip className="launcher-item-label" text={displayName} />
<OverflowTooltip className="launcher-item-path" text={shortPath} />
</button>
</Dropdown>
</AnimatedDropdown>
);
});
1 change: 1 addition & 0 deletions src/apps/seelenweg/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export function App() {
theme={{
token: {
colorPrimary: isDarkMode ? colors.accent_light : colors.accent_dark,
motion: false,
},
algorithm: isDarkMode ? theme.darkAlgorithm : theme.defaultAlgorithm,
}}
Expand Down
14 changes: 10 additions & 4 deletions src/apps/seelenweg/components/WithContextMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Dropdown, Menu } from 'antd';
import { Menu } from 'antd';
import { ItemType, MenuItemType } from 'antd/es/menu/interface';
import { PropsWithChildren, useState } from 'react';

import { BackgroundByLayersV2 } from './BackgroundByLayers/infra';

import { useWindowFocusChange } from 'src/apps/shared/hooks';
import { AnimatedDropdown } from '../../shared/components/AnimatedWrappers';
import { useWindowFocusChange } from '../../shared/hooks';

interface Props extends PropsWithChildren {
items: ItemType<MenuItemType>[];
Expand All @@ -21,7 +22,12 @@ export function WithContextMenu({ children, items, onOpenChange }: Props) {
});

return (
<Dropdown
<AnimatedDropdown
animationDescription={{
maxAnimationTimeMs: 500,
openAnimationName: 'weg-context-menu-container-open',
closeAnimationName: 'weg-context-menu-container-close',
}}
placement="topLeft"
open={openContextMenu}
onOpenChange={(isOpen) => {
Expand Down Expand Up @@ -49,6 +55,6 @@ export function WithContextMenu({ children, items, onOpenChange }: Props) {
)}
>
{children}
</Dropdown>
</AnimatedDropdown>
);
}
11 changes: 8 additions & 3 deletions src/apps/seelenweg/modules/item/infra/UserApplication.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { SeelenCommand, SeelenWegSide } from '@seelen-ui/lib';
import { convertFileSrc, invoke } from '@tauri-apps/api/core';
import { Popover } from 'antd';
import moment from 'moment';
import { memo, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
Expand All @@ -15,6 +14,7 @@ import { useIcon, useWindowFocusChange } from 'src/apps/shared/hooks';

import { PinnedWegItem, RootState, TemporalWegItem } from '../../shared/store/domain';

import { AnimatedPopover } from '../../../../shared/components/AnimatedWrappers';
import { cx } from '../../../../shared/styles';
import { WithContextMenu } from '../../../components/WithContextMenu';
import { DraggableItem } from './DraggableItem';
Expand Down Expand Up @@ -101,7 +101,12 @@ export const UserApplication = memo(({ item, onAssociatedViewOpenChanged }: Prop
}
}}
>
<Popover
<AnimatedPopover
animationDescription={{
maxAnimationTimeMs: 500,
openAnimationName: 'weg-item-preview-container-open',
closeAnimationName: 'weg-item-preview-container-close',
}}
open={openPreview}
mouseEnterDelay={0.4}
placement={calculatePlacement(settings.position)}
Expand Down Expand Up @@ -163,7 +168,7 @@ export const UserApplication = memo(({ item, onAssociatedViewOpenChanged }: Prop
})}
/>
</div>
</Popover>
</AnimatedPopover>
</WithContextMenu>
</DraggableItem>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Dropdown, DropdownProps } from 'antd';
import { useState } from 'react';

import { CustomAnimationProps } from '../domain';

import { useTimeout } from '../../../hooks';
import { cx } from '../../../styles';

export interface AnimatedDropwonProps extends DropdownProps {
animationDescription: CustomAnimationProps;
}

export function AnimatedDropdown({ children, open, onOpenChange, dropdownRender, animationDescription, ...dropdownProps }: AnimatedDropwonProps) {
const [delayedOpenPopover, setDelayedOpenPopover] = useState(false);
const [openReplacement, setOpenReplacement] = useState(false);

useTimeout(() => {
setDelayedOpenPopover((open || openReplacement));
}, animationDescription.maxAnimationTimeMs, [open || openReplacement]);

const animationObject = {};
if (animationDescription.openAnimationName) {
animationObject[animationDescription.openAnimationName] = (open || openReplacement) && !delayedOpenPopover;
}
if (animationDescription.closeAnimationName) {
animationObject[animationDescription.closeAnimationName] = delayedOpenPopover && !(open || openReplacement);
}

return (
<Dropdown
open={open || openReplacement || delayedOpenPopover}
onOpenChange={(open, info) => {
if (onOpenChange) {
onOpenChange(open, info);
} else {
setOpenReplacement(open);
}
}}
{...dropdownProps}
dropdownRender={(origin) => dropdownRender &&
<div className={cx(animationObject)}>
{dropdownRender(origin)}
</div>
}
>
{children}
</Dropdown>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Popover, PopoverProps } from 'antd';
import { useState } from 'react';

import { CustomAnimationProps } from '../domain';

import { useTimeout } from '../../../hooks';
import { cx } from '../../../styles';

export interface AnimatedPopoverProps extends PopoverProps {
animationDescription: CustomAnimationProps;
}

export function AnimatedPopover({ children, open, onOpenChange, content, animationDescription, ...popoverProps }: AnimatedPopoverProps) {
const [delayedOpenPopover, setDelayedOpenPopover] = useState(false);
const [openReplacement, setOpenReplacement] = useState(false);

useTimeout(() => {
setDelayedOpenPopover((open || openReplacement));
}, animationDescription.maxAnimationTimeMs, [open || openReplacement]);

const animationObject = {};
if (animationDescription.openAnimationName) {
animationObject[animationDescription.openAnimationName] = (open || openReplacement) && !delayedOpenPopover;
}
if (animationDescription.closeAnimationName) {
animationObject[animationDescription.closeAnimationName] = delayedOpenPopover && !(open || openReplacement);
}

return (
<Popover
open={open || delayedOpenPopover}
onOpenChange={(open, info) => {
if (onOpenChange) {
onOpenChange(open, info);
} else {
setOpenReplacement(open);
}
}}
{...popoverProps}
content={content &&
<div className={cx(animationObject)}>
<>{content}</>
</div>
}
>
{children}
</Popover>
);
}
5 changes: 5 additions & 0 deletions src/apps/shared/components/AnimatedWrappers/domain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface CustomAnimationProps {
maxAnimationTimeMs: number;
openAnimationName: String;
closeAnimationName: String;
}
2 changes: 2 additions & 0 deletions src/apps/shared/components/AnimatedWrappers/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { AnimatedDropdown } from './AnimatedDropdown';
export { AnimatedPopover } from './AnimatedPopover';
1 change: 1 addition & 0 deletions src/apps/toolbar/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export function App() {
theme={{
token: {
colorPrimary: isDarkMode ? colors.accent_light : colors.accent_dark,
motion: false,
},
components: {
Calendar: {
Expand Down
12 changes: 9 additions & 3 deletions src/apps/toolbar/modules/Date/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Calendar, Popover, Row } from 'antd';
import { Calendar, Row } from 'antd';
import { CalendarMode, HeaderRender } from 'antd/es/calendar/generateCalendar';
import moment from 'moment';
import momentGenerateConfig from 'rc-picker/es/generate/moment';
Expand All @@ -10,6 +10,7 @@ import { BackgroundByLayersV2 } from '../../../seelenweg/components/BackgroundBy

import { useWindowFocusChange } from 'src/apps/shared/hooks';

import { AnimatedPopover } from '../../../shared/components/AnimatedWrappers';
import { Icon } from '../../../shared/components/Icon';
import { cx } from '../../../shared/styles';

Expand Down Expand Up @@ -181,7 +182,12 @@ export function WithDateCalendar({ children }: PropsWithChildren) {
});

return (
<Popover
<AnimatedPopover
animationDescription={{
maxAnimationTimeMs: 500,
openAnimationName: 'calendar-open',
closeAnimationName: 'calendar-close',
}}
style={{ width: 300 }}
open={openPreview}
trigger="click"
Expand All @@ -190,6 +196,6 @@ export function WithDateCalendar({ children }: PropsWithChildren) {
content={<DateCalendar />}
>
{children}
</Popover>
</AnimatedPopover>
);
}
18 changes: 14 additions & 4 deletions src/apps/toolbar/modules/Notifications/infra/Module.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useWindowFocusChange } from 'src/apps/shared/hooks';

import { RootState } from '../../shared/store/domain';

import { AnimatedPopover } from '../../../../shared/components/AnimatedWrappers';
import { ArrivalPreview } from './ArrivalPreview';
import { Notifications } from './Notifications';

Expand All @@ -33,16 +34,25 @@ export function NotificationsModule({ module }: Props) {
}, []);

return (
<Popover open={!openPreview} arrow={false} content={<ArrivalPreview />}>
<Popover
<Popover
open={!openPreview}
arrow={false}
content={<ArrivalPreview />}
>
<AnimatedPopover
animationDescription={{
maxAnimationTimeMs: 500,
openAnimationName: 'notification-open',
closeAnimationName: 'notification-close',
}}
open={openPreview}
trigger="click"
onOpenChange={setOpenPreview}
arrow={false}
content={<Notifications />}
>
<Item extraVars={{ count }} module={module} />
</Popover>
<Item extraVars={{ count }} module={module} active={openPreview} />
</AnimatedPopover>
</Popover>
);
}
Loading
Loading