summaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/BackendList.tsx2
-rw-r--r--src/components/Button.module.scss6
-rw-r--r--src/components/Collapsible.tsx23
-rw-r--r--src/components/Config.module.scss2
-rw-r--r--src/components/Config.tsx3
-rw-r--r--src/components/ConnectionCard.tsx2
-rw-r--r--src/components/ConnectionTable.tsx68
-rw-r--r--src/components/Connections.tsx4
-rw-r--r--src/components/ContentHeader.module.scss2
-rw-r--r--src/components/Home.module.scss2
-rw-r--r--src/components/Home.tsx2
-rw-r--r--src/components/Logs.tsx2
-rw-r--r--src/components/Modal.tsx32
-rw-r--r--src/components/ModalCloseAllConnections.tsx18
-rw-r--r--src/components/ModalConnectionDetails.tsx3
-rw-r--r--src/components/ModalManageConnectionColumns.tsx2
-rw-r--r--src/components/Rule.module.scss2
-rw-r--r--src/components/Rule.tsx2
-rw-r--r--src/components/Rules.module.scss2
-rw-r--r--src/components/Rules.tsx32
-rw-r--r--src/components/SideBar.module.scss7
-rw-r--r--src/components/SideBar.tsx2
-rw-r--r--src/components/Sparkline.module.scss2
-rw-r--r--src/components/Sparkline.tsx24
-rw-r--r--src/components/StateProvider.tsx12
-rw-r--r--src/components/StyleGuide.tsx2
-rw-r--r--src/components/TrafficNow.module.scss51
-rw-r--r--src/components/TrafficNow.tsx120
-rw-r--r--src/components/about/About.module.scss2
-rw-r--r--src/components/about/About.tsx4
-rw-r--r--src/components/proxies/Proxies.module.scss2
-rw-r--r--src/components/proxies/Proxy.module.scss3
-rw-r--r--src/components/proxies/ProxyGroup.module.scss4
-rw-r--r--src/components/proxies/ProxyGroup.tsx2
-rw-r--r--src/components/proxies/ProxyLatency.module.scss2
-rw-r--r--src/components/proxies/ProxyList.module.scss2
-rw-r--r--src/components/proxies/ProxyPageFab.tsx2
-rw-r--r--src/components/proxies/ProxyProvider.module.scss2
-rw-r--r--src/components/proxies/ProxyProvider.tsx30
-rw-r--r--src/components/rules/RuleProviderItem.tsx2
-rw-r--r--src/components/shared/BaseModal.tsx11
-rw-r--r--src/components/shared/Basic.module.scss2
-rw-r--r--src/components/shared/Fab.tsx13
-rw-r--r--src/components/shared/FeatherIcons.ts38
-rw-r--r--src/components/shared/RotateIcon.tsx2
-rw-r--r--src/components/shared/ThemeSwitcher.tsx150
46 files changed, 399 insertions, 305 deletions
diff --git a/src/components/BackendList.tsx b/src/components/BackendList.tsx
index 2246dd6..bb9ae53 100644
--- a/src/components/BackendList.tsx
+++ b/src/components/BackendList.tsx
@@ -1,6 +1,6 @@
import cx from 'clsx';
import * as React from 'react';
-import { Eye, EyeOff, X as Close } from 'react-feather';
+import { Eye, EyeOff, X as Close } from '~/components/shared/FeatherIcons';
import { useToggle } from '~/hooks/basic';
import type { ClashAPIConfigWithAddedAt } from '~/store/types';
diff --git a/src/components/Button.module.scss b/src/components/Button.module.scss
index 5bb68be..4f89d4c 100644
--- a/src/components/Button.module.scss
+++ b/src/components/Button.module.scss
@@ -1,4 +1,4 @@
-@import '~/styles/utils/custom-media';
+@use '~/styles/utils/custom-media' as *;
.btn {
-webkit-appearance: none;
@@ -26,10 +26,6 @@
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
- &:active {
- transform: scale(0.97);
- box-shadow: none;
- }
font-size: 0.75em;
padding: 4px 7px;
diff --git a/src/components/Collapsible.tsx b/src/components/Collapsible.tsx
index f43dbd6..6948cef 100644
--- a/src/components/Collapsible.tsx
+++ b/src/components/Collapsible.tsx
@@ -1,7 +1,6 @@
+import { LazyMotion, domAnimation, m } from 'framer-motion';
import React from 'react';
-import { framerMotionResouce } from '../misc/motion';
-
const { memo } = React;
const variantsCollpapsibleWrap = {
@@ -27,17 +26,17 @@ const variantsCollpapsibleWrap = {
};
const Collapsible = memo(({ children, isOpen }: { children: React.ReactNode; isOpen: boolean }) => {
- const module = framerMotionResouce.read();
- const motion = module.motion;
return (
- <motion.div
- initial={isOpen ? 'initialOpen' : 'closed'}
- animate={isOpen ? 'open' : 'closed'}
- variants={variantsCollpapsibleWrap}
- style={{ overflow: 'hidden' }}
- >
- {children}
- </motion.div>
+ <LazyMotion features={domAnimation}>
+ <m.div
+ initial={isOpen ? 'initialOpen' : 'closed'}
+ animate={isOpen ? 'open' : 'closed'}
+ variants={variantsCollpapsibleWrap}
+ style={{ overflow: 'hidden' }}
+ >
+ {children}
+ </m.div>
+ </LazyMotion>
);
});
diff --git a/src/components/Config.module.scss b/src/components/Config.module.scss
index 9bc3f08..1c178b4 100644
--- a/src/components/Config.module.scss
+++ b/src/components/Config.module.scss
@@ -1,4 +1,4 @@
-@import '~/styles/utils/custom-media';
+@use '~/styles/utils/custom-media' as *;
.root {
max-width: 1000px;
diff --git a/src/components/Config.tsx b/src/components/Config.tsx
index 3bf39af..2d3f08b 100644
--- a/src/components/Config.tsx
+++ b/src/components/Config.tsx
@@ -8,7 +8,7 @@ import {
Settings,
Tool,
Trash2,
-} from 'react-feather';
+} from '~/components/shared/FeatherIcons';
import { useTranslation } from 'react-i18next';
import Select from '~/components/shared/Select';
@@ -73,7 +73,6 @@ export default function Config({
return (
<div>
- <ContentHeader />
<div className={s0.root}>
<div className={s0.card}>
<div className={s0.sectionTitle}>
diff --git a/src/components/ConnectionCard.tsx b/src/components/ConnectionCard.tsx
index 8a3a88a..bf16eb6 100644
--- a/src/components/ConnectionCard.tsx
+++ b/src/components/ConnectionCard.tsx
@@ -1,7 +1,7 @@
import { formatDistance, Locale } from 'date-fns';
import { enUS, zhCN, zhTW } from 'date-fns/locale';
import React from 'react';
-import { ArrowDown, ArrowDownCircle, ArrowUp, X } from 'react-feather';
+import { ArrowDown, ArrowDownCircle, ArrowUp, X } from '~/components/shared/FeatherIcons';
import { useTranslation } from 'react-i18next';
import { FormattedConn } from '~/store/connections';
diff --git a/src/components/ConnectionTable.tsx b/src/components/ConnectionTable.tsx
index 8a2f0a6..e296706 100644
--- a/src/components/ConnectionTable.tsx
+++ b/src/components/ConnectionTable.tsx
@@ -4,10 +4,10 @@ import cx from 'clsx';
import { formatDistance, Locale } from 'date-fns';
import { enUS, zhCN, zhTW } from 'date-fns/locale';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import { ArrowDown, ArrowUp, ChevronDown, Sliders, XCircle } from 'react-feather';
+import { ArrowDown, ArrowUp, ChevronDown, Sliders, XCircle } from '~/components/shared/FeatherIcons';
import { useTranslation } from 'react-i18next';
import { useSortBy, useTable } from 'react-table';
-import { FixedSizeList as List } from 'react-window';
+import { List as VirtualList, RowComponentProps } from 'react-window';
import { FormattedConn } from '~/store/connections';
@@ -40,19 +40,6 @@ const COLUMN_WIDTHS = {
const TOTAL_WIDTH = Object.values(COLUMN_WIDTHS).reduce((a, b) => a + b, 0);
-const InnerElement = React.forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(
- ({ style, ...rest }, ref) => (
- <div
- ref={ref}
- style={{
- ...style,
- width: TOTAL_WIDTH,
- }}
- {...rest}
- />
- )
-);
-
const getColumnStyle = (columnId: string) => {
const width = COLUMN_WIDTHS[columnId] || 100;
const style: React.CSSProperties = {
@@ -82,19 +69,6 @@ function Table({ data, columns, hiddenColumns, apiConfig, height }) {
const [isMobile, setIsMobile] = useState(false);
const headerRef = React.useRef<HTMLDivElement>(null);
- const outerRef = React.useRef<HTMLDivElement>(null);
-
- useEffect(() => {
- const outer = outerRef.current;
- if (!outer) return;
- const handleScroll = () => {
- if (headerRef.current) {
- headerRef.current.scrollLeft = outer.scrollLeft;
- }
- };
- outer.addEventListener('scroll', handleScroll);
- return () => outer.removeEventListener('scroll', handleScroll);
- }, []);
useEffect(() => {
const mql = window.matchMedia('(max-width: 768px)');
@@ -193,7 +167,7 @@ function Table({ data, columns, hiddenColumns, apiConfig, height }) {
}, [state.sortBy]);
const MobileRow = useCallback(
- ({ index, style }) => {
+ ({ index, style }: RowComponentProps) => {
const row = rows[index];
const conn = row.original as FormattedConn;
return (
@@ -211,7 +185,7 @@ function Table({ data, columns, hiddenColumns, apiConfig, height }) {
);
const DesktopRow = useCallback(
- ({ index, style }) => {
+ ({ index, style }: RowComponentProps) => {
const row = rows[index];
prepareRow(row);
return (
@@ -259,6 +233,12 @@ function Table({ data, columns, hiddenColumns, apiConfig, height }) {
[prepareRow, rows, renderCell, locale]
);
+ const handleDesktopListScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
+ if (headerRef.current) {
+ headerRef.current.scrollLeft = e.currentTarget.scrollLeft;
+ }
+ }, []);
+
return (
<div className={s.tableWrapper} style={{ height, overflow: 'hidden' }}>
{isMobile ? (
@@ -290,9 +270,13 @@ function Table({ data, columns, hiddenColumns, apiConfig, height }) {
{currentSort.desc ? <ArrowDown size={18} /> : <ArrowUp size={18} />}
</button>
</div>
- <List height={height - 50} itemCount={rows.length} itemSize={120} width="100%">
- {MobileRow}
- </List>
+ <VirtualList
+ style={{ height: height - 50, width: '100%' }}
+ rowCount={rows.length}
+ rowHeight={120}
+ rowComponent={MobileRow}
+ rowProps={{}}
+ />
</div>
) : (
<div
@@ -347,16 +331,14 @@ function Table({ data, columns, hiddenColumns, apiConfig, height }) {
))}
</div>
</div>
- <List
- height={height - 50}
- itemCount={rows.length}
- itemSize={44}
- width="100%"
- outerRef={outerRef}
- innerElementType={InnerElement}
- >
- {DesktopRow}
- </List>
+ <VirtualList
+ style={{ height: height - 50, width: '100%' }}
+ onScroll={handleDesktopListScroll}
+ rowCount={rows.length}
+ rowHeight={44}
+ rowComponent={DesktopRow}
+ rowProps={{}}
+ />
</div>
)}
<MOdalCloseConnection
diff --git a/src/components/Connections.tsx b/src/components/Connections.tsx
index 38da7b8..64a5998 100644
--- a/src/components/Connections.tsx
+++ b/src/components/Connections.tsx
@@ -1,7 +1,7 @@
import './Connections.css';
import React from 'react';
-import { Pause, Play, RefreshCcw, Settings, Tag, X as IconClose } from 'react-feather';
+import { Pause, Play, RefreshCcw, Settings, Tag, X as IconClose } from '~/components/shared/FeatherIcons';
import { useTranslation } from 'react-i18next';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
@@ -114,14 +114,12 @@ export default function Connections({ apiConfig }: Props) {
<Tab>
<span>{t('Active')}</span>
<span className={s.connQty}>
- {/* @ts-expect-error ts-migrate(2786) FIXME: 'ConnQty' cannot be used as a JSX component. */}
<ConnQty qty={filteredConns.length} />
</span>
</Tab>
<Tab>
<span>{t('Closed')}</span>
<span className={s.connQty}>
- {/* @ts-expect-error ts-migrate(2786) FIXME: 'ConnQty' cannot be used as a JSX component. */}
<ConnQty qty={filteredClosedConns.length} />
</span>
</Tab>
diff --git a/src/components/ContentHeader.module.scss b/src/components/ContentHeader.module.scss
index 37a7b86..be1697b 100644
--- a/src/components/ContentHeader.module.scss
+++ b/src/components/ContentHeader.module.scss
@@ -1,4 +1,4 @@
-@import '~/styles/utils/custom-media';
+@use '~/styles/utils/custom-media' as *;
.root {
height: 60px;
diff --git a/src/components/Home.module.scss b/src/components/Home.module.scss
index 8da1d49..7fa6ada 100644
--- a/src/components/Home.module.scss
+++ b/src/components/Home.module.scss
@@ -1,4 +1,4 @@
-@import '~/styles/utils/custom-media';
+@use '~/styles/utils/custom-media' as *;
.root {
padding: 6px 15px;
diff --git a/src/components/Home.tsx b/src/components/Home.tsx
index 195b329..489f4c5 100644
--- a/src/components/Home.tsx
+++ b/src/components/Home.tsx
@@ -2,7 +2,6 @@ import React from 'react';
import { ClashAPIConfig } from '~/types';
-import ContentHeader from './ContentHeader';
import s0 from './Home.module.scss';
import TrafficNow from './TrafficNow';
@@ -14,7 +13,6 @@ type Props = {
export default function Home({ apiConfig, selectedChartStyleIndex }: Props) {
return (
<div>
- <ContentHeader />
<div className={s0.root}>
<TrafficNow apiConfig={apiConfig} selectedChartStyleIndex={selectedChartStyleIndex} />
</div>
diff --git a/src/components/Logs.tsx b/src/components/Logs.tsx
index 0dbb8f0..59abced 100644
--- a/src/components/Logs.tsx
+++ b/src/components/Logs.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { ArrowDown, Pause, Play, Trash2 } from 'react-feather';
+import { ArrowDown, Pause, Play, Trash2 } from '~/components/shared/FeatherIcons';
import { useTranslation } from 'react-i18next';
import ContentHeader from '~/components/ContentHeader';
diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx
index e91523c..dfb9683 100644
--- a/src/components/Modal.tsx
+++ b/src/components/Modal.tsx
@@ -1,6 +1,6 @@
import cx from 'clsx';
import * as React from 'react';
-import Modal, { Props as ReactModalProps } from 'react-modal';
+import ReactModalBase, { Props as ReactModalProps } from 'react-modal';
import s0 from './Modal.module.scss';
@@ -8,10 +8,28 @@ type Props = ReactModalProps & {
isOpen: boolean;
onRequestClose: (...args: any[]) => any;
children: React.ReactNode;
- className?: string;
- overlayClassName?: string;
};
+const ReactModal = ReactModalBase as unknown as React.ComponentType<ReactModalProps>;
+
+function withBaseClass(
+ className: ReactModalProps['className'],
+ baseClassName: string
+): ReactModalProps['className'] {
+ if (!className) {
+ return baseClassName;
+ }
+
+ if (typeof className === 'string') {
+ return cx(className, baseClassName);
+ }
+
+ return {
+ ...className,
+ base: cx(className.base, baseClassName),
+ };
+}
+
function ModalAPIConfig({
isOpen,
onRequestClose,
@@ -20,10 +38,10 @@ function ModalAPIConfig({
children,
...otherProps
}: Props) {
- const contentCls = cx(className, s0.content);
- const overlayCls = cx(overlayClassName, s0.overlay);
+ const contentCls = withBaseClass(className, s0.content);
+ const overlayCls = withBaseClass(overlayClassName, s0.overlay);
return (
- <Modal
+ <ReactModal
isOpen={isOpen}
onRequestClose={onRequestClose}
className={contentCls}
@@ -31,7 +49,7 @@ function ModalAPIConfig({
{...otherProps}
>
{children}
- </Modal>
+ </ReactModal>
);
}
diff --git a/src/components/ModalCloseAllConnections.tsx b/src/components/ModalCloseAllConnections.tsx
index 77bcb59..72efc7c 100644
--- a/src/components/ModalCloseAllConnections.tsx
+++ b/src/components/ModalCloseAllConnections.tsx
@@ -1,7 +1,8 @@
import cx from 'clsx';
import React from 'react';
import { useTranslation } from 'react-i18next';
-import Modal from 'react-modal';
+
+import Modal from './Modal';
import Button from './Button';
import modalStyle from './Modal.module.scss';
@@ -9,16 +10,25 @@ import s from './ModalCloseAllConnections.module.scss';
const { useRef, useCallback, useMemo } = React;
+type Props = {
+ confirm?: string;
+ isOpen: boolean;
+ onRequestClose: () => void;
+ primaryButtonOnTap: (e: React.MouseEvent<HTMLButtonElement>) => unknown;
+};
+
export default function Comp({
confirm = 'close_all_confirm',
isOpen,
onRequestClose,
primaryButtonOnTap,
-}) {
+}: Props) {
const { t } = useTranslation();
- const primaryButtonRef = useRef(null);
+ const primaryButtonRef = useRef<HTMLButtonElement | null>(null);
const onAfterOpen = useCallback(() => {
- primaryButtonRef.current.focus();
+ if (primaryButtonRef.current) {
+ primaryButtonRef.current.focus();
+ }
}, []);
const className = useMemo(
() => ({
diff --git a/src/components/ModalConnectionDetails.tsx b/src/components/ModalConnectionDetails.tsx
index 3274b4e..4fb4c0c 100644
--- a/src/components/ModalConnectionDetails.tsx
+++ b/src/components/ModalConnectionDetails.tsx
@@ -3,7 +3,8 @@ import { formatDistance } from 'date-fns';
import { enUS, zhCN, zhTW } from 'date-fns/locale';
import React from 'react';
import { useTranslation } from 'react-i18next';
-import Modal from 'react-modal';
+
+import Modal from './Modal';
import { FormattedConn } from '~/store/connections';
diff --git a/src/components/ModalManageConnectionColumns.tsx b/src/components/ModalManageConnectionColumns.tsx
index 463f76b..6c687c4 100644
--- a/src/components/ModalManageConnectionColumns.tsx
+++ b/src/components/ModalManageConnectionColumns.tsx
@@ -1,6 +1,6 @@
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import React from 'react';
-import { ChevronDown, ChevronUp, Menu } from 'react-feather';
+import { ChevronDown, ChevronUp, Menu } from '~/components/shared/FeatherIcons';
import { useTranslation } from 'react-i18next';
import BaseModal from '~/components/shared/BaseModal';
diff --git a/src/components/Rule.module.scss b/src/components/Rule.module.scss
index 845fa5e..48a22e7 100644
--- a/src/components/Rule.module.scss
+++ b/src/components/Rule.module.scss
@@ -1,4 +1,4 @@
-@import '~/styles/utils/custom-media';
+@use '~/styles/utils/custom-media' as *;
.rule {
display: flex;
diff --git a/src/components/Rule.tsx b/src/components/Rule.tsx
index 24e2e57..ee3a7ab 100644
--- a/src/components/Rule.tsx
+++ b/src/components/Rule.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { FileText, Globe, Hash, Link, Shield, Zap } from 'react-feather';
+import { FileText, Globe, Hash, Link, Shield, Zap } from '~/components/shared/FeatherIcons';
import s0 from './Rule.module.scss';
diff --git a/src/components/Rules.module.scss b/src/components/Rules.module.scss
index c2a4f39..f764d09 100644
--- a/src/components/Rules.module.scss
+++ b/src/components/Rules.module.scss
@@ -1,4 +1,4 @@
-@import '~/styles/utils/custom-media';
+@use '~/styles/utils/custom-media' as *;
.container {
display: flex;
diff --git a/src/components/Rules.tsx b/src/components/Rules.tsx
index c4ecd90..fb63028 100644
--- a/src/components/Rules.tsx
+++ b/src/components/Rules.tsx
@@ -1,14 +1,14 @@
import cx from 'clsx';
import React from 'react';
import { useTranslation } from 'react-i18next';
-import { areEqual, VariableSizeList } from 'react-window';
+import { List as VirtualList, RowComponentProps } from 'react-window';
import ContentHeader from '~/components/ContentHeader';
import { RuleProviderItem } from '~/components/rules/RuleProviderItem';
import { RulesPageFab } from '~/components/rules/RulesPageFab';
import { TextFilter } from '~/components/shared/TextFitler';
import { useRulesPage } from '~/modules/rules/hooks';
-import { formatQty, getItemSizeFactory, itemKey, RulesListItemData } from '~/modules/rules/utils';
+import { formatQty, getItemSizeFactory, RulesListItemData } from '~/modules/rules/utils';
import { ruleFilterText } from '~/store/rules';
import { ClashAPIConfig } from '~/types';
@@ -17,10 +17,11 @@ import useRemainingViewPortHeight from '../hooks/useRemainingViewPortHeight';
import Rule from './Rule';
import s from './Rules.module.scss';
-const { memo } = React;
+type RulesRowProps = {
+ data: RulesListItemData;
+};
-// @ts-expect-error ts-migrate(2339) FIXME: Property 'index' does not exist on type '{ childre... Remove this comment to see the full error message
-const Row = memo(({ index, style, data }) => {
+function Row({ index, style, data }: RowComponentProps<RulesRowProps>) {
const { rules, provider, apiConfig } = data;
if (!rules) {
@@ -39,7 +40,7 @@ const Row = memo(({ index, style, data }) => {
<Rule {...r} />
</div>
);
-}, areEqual);
+}
type RulesProps = {
apiConfig: ClashAPIConfig;
@@ -86,16 +87,15 @@ export default function Rules({ apiConfig }: RulesProps) {
</div>
</ContentHeader>
<div ref={refRulesContainer} className={s.listWrapper}>
- <VariableSizeList
- height={containerHeight}
- width="100%"
- itemCount={isRulesTab ? rules.length : provider.names.length}
- itemSize={getItemSize}
- itemData={{ rules: isRulesTab ? rules : null, provider, apiConfig } as RulesListItemData}
- itemKey={itemKey}
- >
- {Row}
- </VariableSizeList>
+ <VirtualList
+ style={{ height: containerHeight, width: '100%' }}
+ rowCount={isRulesTab ? rules.length : provider.names.length}
+ rowHeight={getItemSize}
+ rowComponent={Row}
+ rowProps={{
+ data: { rules: isRulesTab ? rules : null, provider, apiConfig } as RulesListItemData,
+ }}
+ />
</div>
{provider && provider.names && provider.names.length > 0 ? (
<RulesPageFab apiConfig={apiConfig} />
diff --git a/src/components/SideBar.module.scss b/src/components/SideBar.module.scss
index 2d73535..74f7087 100644
--- a/src/components/SideBar.module.scss
+++ b/src/components/SideBar.module.scss
@@ -1,4 +1,4 @@
-@import '~/styles/utils/custom-media';
+@use '~/styles/utils/custom-media' as *;
.root {
background: var(--color-bg-sidebar);
@@ -74,7 +74,6 @@
&:hover {
background-color: rgba(176, 206, 255, 0.221);
color: var(--color-focus-blue);
- transform: translateX(2px);
}
@media (--breakpoint-not-small) {
@@ -110,10 +109,6 @@
}
}
-.row:hover svg {
- transform: scale(1.06);
-}
-
.rowActive {
background: linear-gradient(135deg, #60a5fa 0%, var(--color-focus-blue) 100%);
color: #fff;
diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx
index 307a362..a117533 100644
--- a/src/components/SideBar.tsx
+++ b/src/components/SideBar.tsx
@@ -1,7 +1,7 @@
import { Tooltip } from '@reach/tooltip';
import cx from 'clsx';
import * as React from 'react';
-import { Info } from 'react-feather';
+import { Info } from '~/components/shared/FeatherIcons';
import { useTranslation } from 'react-i18next';
import { FcAreaChart, FcDocument, FcGlobe, FcLink, FcRuler, FcSettings } from 'react-icons/fc';
import { useQuery } from 'react-query';
diff --git a/src/components/Sparkline.module.scss b/src/components/Sparkline.module.scss
index bb52657..bc60060 100644
--- a/src/components/Sparkline.module.scss
+++ b/src/components/Sparkline.module.scss
@@ -1,5 +1,5 @@
.sparkline {
width: 100%;
- height: 40px;
+ height: 10vh;
margin-top: auto;
}
diff --git a/src/components/Sparkline.tsx b/src/components/Sparkline.tsx
index b7f35a9..79f8bf5 100644
--- a/src/components/Sparkline.tsx
+++ b/src/components/Sparkline.tsx
@@ -63,9 +63,19 @@ const extraChartOptions: any = {
export default function Sparkline({ data: dataArray, labels, type, styleIndex = 0 }) {
chartJSResource.read();
+ const isMemory = type === 'inuse';
+
const options = useMemo(() => {
return {
...extraChartOptions,
+ scales: {
+ ...extraChartOptions.scales,
+ y: {
+ display: false,
+ // 内存值稳定,不从零开始,让 Y 轴自动适应数据范围以显示波动
+ beginAtZero: !isMemory,
+ },
+ },
plugins: {
...extraChartOptions.plugins,
tooltip: {
@@ -75,9 +85,9 @@ export default function Sparkline({ data: dataArray, labels, type, styleIndex =
title: () => '',
label(context) {
if (context.parsed.y !== null) {
- const suffix = type === 'inuse' ? '' : '/s';
- // 还原 log1p 变换后的真实值
- return prettyBytes(Math.expm1(context.parsed.y)) + suffix;
+ const suffix = isMemory ? '' : '/s';
+ const raw = isMemory ? context.parsed.y : Math.expm1(context.parsed.y);
+ return prettyBytes(raw) + suffix;
}
return '';
},
@@ -85,7 +95,7 @@ export default function Sparkline({ data: dataArray, labels, type, styleIndex =
},
},
};
- }, [type]);
+ }, [type, isMemory]);
const data = useMemo(
() => ({
@@ -93,13 +103,13 @@ export default function Sparkline({ data: dataArray, labels, type, styleIndex =
{
...commonDataSetProps,
...chartStyles[styleIndex][type],
- // log1p 变换:压缩大尖刺,让小流量也可见;log1p(0)=0 不会出现 -Infinity
- data: dataArray.map((v, i) => ({ x: labels[i], y: Math.log1p(v) })),
+ // 内存用原始值(变化幅度小,不需要压缩);流量用 log1p 压缩尖刺
+ data: dataArray.map((v, i) => ({ x: labels[i], y: isMemory ? v : Math.log1p(v) })),
fill: true,
},
],
}),
- [dataArray, labels, type, styleIndex]
+ [dataArray, labels, type, styleIndex, isMemory],
);
return (
diff --git a/src/components/StateProvider.tsx b/src/components/StateProvider.tsx
index 1ef48d7..4b074ef 100644
--- a/src/components/StateProvider.tsx
+++ b/src/components/StateProvider.tsx
@@ -1,15 +1,15 @@
-import produce, * as immer from 'immer';
+import { produce, setAutoFreeze } from 'immer';
import React from 'react';
// in logs store we update logs in place
// outside of immer produce
// this is just workaround
-immer.setAutoFreeze(false);
+setAutoFreeze(false);
const { createContext, memo, useMemo, useRef, useEffect, useCallback, useContext, useState } =
React;
-export { immer };
+export const immer = { produce, setAutoFreeze };
const StateContext = createContext(null);
const DispatchContext = createContext(null);
@@ -33,7 +33,7 @@ export default function Provider({ initialState, actions = {}, children }) {
const [state, setState] = useState(initialState);
const getState = useCallback(() => stateRef.current, []);
useEffect(() => {
- if (process.env.NODE_ENV === 'development') {
+ if (import.meta.env.DEV) {
(window as any).getState2 = getState;
}
}, [getState]);
@@ -43,7 +43,7 @@ export default function Provider({ initialState, actions = {}, children }) {
const stateNext = produce(getState(), fn);
if (stateNext !== stateRef.current) {
- if (process.env.NODE_ENV === 'development') {
+ if (import.meta.env.DEV) {
// eslint-disable-next-line no-console
console.log(actionId, stateNext);
}
@@ -81,7 +81,7 @@ export function connect(mapStateToProps: any) {
// steal from https://github.com/reduxjs/redux/blob/master/src/bindActionCreators.ts
function bindAction(action: any, dispatch: any) {
return function (...args: any[]) {
- return dispatch(action.apply(this, args));
+ return dispatch(action(...args));
};
}
diff --git a/src/components/StyleGuide.tsx b/src/components/StyleGuide.tsx
index 0e5d2a5..f743f89 100644
--- a/src/components/StyleGuide.tsx
+++ b/src/components/StyleGuide.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { Zap } from 'react-feather';
+import { Zap } from '~/components/shared/FeatherIcons';
import Loading from '~/components/Loading';
diff --git a/src/components/TrafficNow.module.scss b/src/components/TrafficNow.module.scss
index e0e3271..2b0bcdf 100644
--- a/src/components/TrafficNow.module.scss
+++ b/src/components/TrafficNow.module.scss
@@ -1,24 +1,63 @@
.TrafficNow {
color: var(--color-text);
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+ display: flex;
+ flex-direction: column;
grid-gap: 20px;
+ gap: 20px;
padding: 10px 0;
+ .overview {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 20px;
+
+ & > div:nth-child(3) {
+ grid-column: 1 / -1;
+ }
+
+ @media (min-width: 768px) {
+ grid-template-columns: 1fr 1fr 1fr;
+
+ & > div:nth-child(3) {
+ grid-column: auto;
+ }
+ }
+ }
+
+ .chartsRow {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+
+ @media (min-width: 768px) {
+ flex-direction: row;
+
+ & > .sec {
+ flex: 1;
+ min-width: 0;
+ }
+ }
+ }
+
.sec {
padding: 20px;
background-color: var(--color-bg-card);
border-radius: 12px;
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
- transition: transform 0.2s ease, box-shadow 0.2s ease;
+ box-shadow:
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+ transition:
+ transform 0.2s ease,
+ box-shadow 0.2s ease;
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 140px;
&:hover {
- transform: translateY(-2px);
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+ box-shadow:
+ 0 10px 15px -3px rgba(0, 0, 0, 0.1),
+ 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.header {
diff --git a/src/components/TrafficNow.tsx b/src/components/TrafficNow.tsx
index 5d48aab..5dc601b 100644
--- a/src/components/TrafficNow.tsx
+++ b/src/components/TrafficNow.tsx
@@ -1,5 +1,12 @@
import * as React from 'react';
-import { Activity, ArrowDown, ArrowUp, Cpu, Link as LinkIcon, Zap } from 'react-feather';
+import {
+ Download,
+ ArrowDown,
+ ArrowUp,
+ Cpu,
+ Link as LinkIcon,
+ Upload,
+} from '~/components/shared/FeatherIcons';
import { useTranslation } from 'react-i18next';
import useMemory from '../hooks/useMemory';
@@ -27,69 +34,70 @@ export default function TrafficNow({ apiConfig, selectedChartStyleIndex }: Props
return (
<div className={s0.TrafficNow}>
- <div className={s0.sec}>
- <div className={s0.header}>
- <Cpu size={16} />
- <span>{t('Memory Usage')}</span>
+ <div className={s0.overview}>
+ <div className={s0.sec}>
+ <div className={s0.header}>
+ <Download size={16} />
+ <span>{t('Download Total')}</span>
+ </div>
+ <div className={s0.value}>{dlTotal}</div>
</div>
- <div className={s0.value}>{mUsage}</div>
- <Sparkline
- data={memory.inuse}
- labels={memory.labels}
- type="inuse"
- styleIndex={selectedChartStyleIndex}
- />
- </div>
-
- <div className={s0.sec}>
- <div className={s0.header}>
- <ArrowDown size={16} />
- <span>{t('Download')}</span>
+ <div className={s0.sec}>
+ <div className={s0.header}>
+ <Upload size={16} />
+ <span>{t('Upload Total')}</span>
+ </div>
+ <div className={s0.value}>{upTotal}</div>
</div>
- <div className={s0.value}>{downStr}</div>
- <Sparkline
- data={traffic.down}
- labels={traffic.labels}
- type="down"
- styleIndex={selectedChartStyleIndex}
- />
- </div>
- <div className={s0.sec}>
- <div className={s0.header}>
- <ArrowUp size={16} />
- <span>{t('Upload')}</span>
+ <div className={s0.sec}>
+ <div className={s0.header}>
+ <LinkIcon size={16} />
+ <span>{t('Active Connections')}</span>
+ </div>
+ <div className={s0.value}>{connNumber}</div>
</div>
- <div className={s0.value}>{upStr}</div>
- <Sparkline
- data={traffic.up}
- labels={traffic.labels}
- type="up"
- styleIndex={selectedChartStyleIndex}
- />
</div>
- <div className={s0.sec}>
- <div className={s0.header}>
- <Activity size={16} />
- <span>{t('Download Total')}</span>
+ <div className={s0.chartsRow}>
+ <div className={s0.sec}>
+ <div className={s0.header}>
+ <ArrowDown size={16} />
+ <span>{t('Download')}</span>
+ </div>
+ <div className={s0.value}>{downStr}</div>
+ <Sparkline
+ data={traffic.down}
+ labels={traffic.labels}
+ type="down"
+ styleIndex={selectedChartStyleIndex}
+ />
</div>
- <div className={s0.value}>{dlTotal}</div>
- </div>
-
- <div className={s0.sec}>
- <div className={s0.header}>
- <Zap size={16} />
- <span>{t('Upload Total')}</span>
+ <div className={s0.sec}>
+ <div className={s0.header}>
+ <ArrowUp size={16} />
+ <span>{t('Upload')}</span>
+ </div>
+ <div className={s0.value}>{upStr}</div>
+ <Sparkline
+ data={traffic.up}
+ labels={traffic.labels}
+ type="up"
+ styleIndex={selectedChartStyleIndex}
+ />
</div>
- <div className={s0.value}>{upTotal}</div>
- </div>
-
- <div className={s0.sec}>
- <div className={s0.header}>
- <LinkIcon size={16} />
- <span>{t('Active Connections')}</span>
+ <div className={s0.sec}>
+ <div className={s0.header}>
+ <Cpu size={16} />
+ <span>{t('Memory Usage')}</span>
+ </div>
+ <div className={s0.value}>{mUsage}</div>
+ <Sparkline
+ data={memory.inuse}
+ labels={memory.labels}
+ type="inuse"
+ styleIndex={selectedChartStyleIndex}
+ />
</div>
- <div className={s0.value}>{connNumber}</div>
</div>
</div>
);
diff --git a/src/components/about/About.module.scss b/src/components/about/About.module.scss
index 7ed1aa5..de20013 100644
--- a/src/components/about/About.module.scss
+++ b/src/components/about/About.module.scss
@@ -1,4 +1,4 @@
-@import '~/styles/utils/custom-media';
+@use '~/styles/utils/custom-media' as *;
.root {
padding: 6px 15px;
diff --git a/src/components/about/About.tsx b/src/components/about/About.tsx
index 8fa3129..cb08218 100644
--- a/src/components/about/About.tsx
+++ b/src/components/about/About.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { GitHub } from 'react-feather';
+import { GitHub } from '~/components/shared/FeatherIcons';
import ContentHeader from '~/components/ContentHeader';
import { useAboutVersionQuery } from '~/modules/about/hooks';
@@ -34,7 +34,7 @@ export function About({ apiConfig }: Props) {
return (
<>
- <ContentHeader title="About" />
+ <ContentHeader>About</ContentHeader>
{coreVersionMeta && version?.version ? (
<Version
name={coreVersionMeta.name}
diff --git a/src/components/proxies/Proxies.module.scss b/src/components/proxies/Proxies.module.scss
index 8990688..af961cf 100644
--- a/src/components/proxies/Proxies.module.scss
+++ b/src/components/proxies/Proxies.module.scss
@@ -1,4 +1,4 @@
-@import '~/styles/utils/custom-media';
+@use '~/styles/utils/custom-media' as *;
.topBar {
position: sticky;
diff --git a/src/components/proxies/Proxy.module.scss b/src/components/proxies/Proxy.module.scss
index 60c589f..066026f 100644
--- a/src/components/proxies/Proxy.module.scss
+++ b/src/components/proxies/Proxy.module.scss
@@ -1,4 +1,4 @@
-@import '~/styles/utils/custom-media';
+@use '~/styles/utils/custom-media' as *;
.proxy {
padding: 5px;
@@ -20,7 +20,6 @@
}
&:hover {
- transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
z-index: 1;
}
diff --git a/src/components/proxies/ProxyGroup.module.scss b/src/components/proxies/ProxyGroup.module.scss
index 06a061f..19529cf 100644
--- a/src/components/proxies/ProxyGroup.module.scss
+++ b/src/components/proxies/ProxyGroup.module.scss
@@ -9,10 +9,6 @@
border-radius: 12px;
box-shadow: var(--shadow-card);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
-
- &:hover {
- border-color: var(--color-focus-blue);
- }
}
.zapWrapper {
diff --git a/src/components/proxies/ProxyGroup.tsx b/src/components/proxies/ProxyGroup.tsx
index 69f2551..7b751fb 100644
--- a/src/components/proxies/ProxyGroup.tsx
+++ b/src/components/proxies/ProxyGroup.tsx
@@ -1,6 +1,6 @@
import cx from 'clsx';
import * as React from 'react';
-import { ChevronDown, Zap } from 'react-feather';
+import { ChevronDown, Zap } from '~/components/shared/FeatherIcons';
import { useQuery } from 'react-query';
import * as proxiesAPI from '~/api/proxies';
diff --git a/src/components/proxies/ProxyLatency.module.scss b/src/components/proxies/ProxyLatency.module.scss
index fce0a2e..ac39100 100644
--- a/src/components/proxies/ProxyLatency.module.scss
+++ b/src/components/proxies/ProxyLatency.module.scss
@@ -1,4 +1,4 @@
-@import '~/styles/utils/custom-media';
+@use '~/styles/utils/custom-media' as *;
.proxyLatency {
display: inline-flex;
diff --git a/src/components/proxies/ProxyList.module.scss b/src/components/proxies/ProxyList.module.scss
index a7e1956..f4c8d87 100644
--- a/src/components/proxies/ProxyList.module.scss
+++ b/src/components/proxies/ProxyList.module.scss
@@ -1,4 +1,4 @@
-@import '~/styles/utils/custom-media';
+@use '~/styles/utils/custom-media' as *;
.list {
margin: 8px 0;
diff --git a/src/components/proxies/ProxyPageFab.tsx b/src/components/proxies/ProxyPageFab.tsx
index ec78650..a8536e0 100644
--- a/src/components/proxies/ProxyPageFab.tsx
+++ b/src/components/proxies/ProxyPageFab.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { Zap } from 'react-feather';
+import { Zap } from '~/components/shared/FeatherIcons';
import { useTranslation } from 'react-i18next';
import { Action, Fab, IsFetching, position as fabPosition } from '~/components/shared/Fab';
diff --git a/src/components/proxies/ProxyProvider.module.scss b/src/components/proxies/ProxyProvider.module.scss
index d222bc5..d22e50b 100644
--- a/src/components/proxies/ProxyProvider.module.scss
+++ b/src/components/proxies/ProxyProvider.module.scss
@@ -1,4 +1,4 @@
-@import '~/styles/utils/custom-media';
+@use '~/styles/utils/custom-media' as *;
.updatedAt {
margin-bottom: 12px;
diff --git a/src/components/proxies/ProxyProvider.tsx b/src/components/proxies/ProxyProvider.tsx
index d1f257a..47ccb8b 100644
--- a/src/components/proxies/ProxyProvider.tsx
+++ b/src/components/proxies/ProxyProvider.tsx
@@ -1,14 +1,14 @@
import cx from 'clsx';
import { formatDistance } from 'date-fns';
import * as React from 'react';
-import { ChevronDown, RotateCw, Zap } from 'react-feather';
+import { ChevronDown, RotateCw, Zap } from '~/components/shared/FeatherIcons';
import Button from '~/components/Button';
import Collapsible from '~/components/Collapsible';
import CollapsibleSectionHeader from '~/components/CollapsibleSectionHeader';
import s0 from '~/components/proxies/ProxyGroup.module.scss';
import { useStoreActions } from '~/components/StateProvider';
-import { framerMotionResouce } from '~/misc/motion';
+import { LazyMotion, domAnimation, m } from 'framer-motion';
import { useFilteredAndSorted, useUpdateProviderItem } from '~/modules/proxies/hooks';
import { healthcheckProviderByName } from '~/store/proxies';
import { DelayMapping, DispatchFn, ProxiesMapping, SubscriptionInfo } from '~/store/types';
@@ -190,19 +190,19 @@ function formatBytes(bytes, decimals = 2) {
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
}
function Refresh() {
- const module = framerMotionResouce.read();
- const motion = module.motion;
return (
- <motion.div
- className={s.refresh}
- variants={button}
- initial="rest"
- whileHover="hover"
- whileTap="pressed"
- >
- <motion.div className="flexCenter" variants={arrow}>
- <RotateCw size={16} />
- </motion.div>
- </motion.div>
+ <LazyMotion features={domAnimation}>
+ <m.div
+ className={s.refresh}
+ variants={button}
+ initial="rest"
+ whileHover="hover"
+ whileTap="pressed"
+ >
+ <m.div className="flexCenter" variants={arrow}>
+ <RotateCw size={16} />
+ </m.div>
+ </m.div>
+ </LazyMotion>
);
}
diff --git a/src/components/rules/RuleProviderItem.tsx b/src/components/rules/RuleProviderItem.tsx
index 7077ed1..5100f3d 100644
--- a/src/components/rules/RuleProviderItem.tsx
+++ b/src/components/rules/RuleProviderItem.tsx
@@ -1,6 +1,6 @@
import { formatDistance } from 'date-fns';
import * as React from 'react';
-import { Activity, Database, RefreshCw } from 'react-feather';
+import { Activity, Database, RefreshCw } from '~/components/shared/FeatherIcons';
import Button from '~/components/Button';
import { useUpdateRuleProviderItem } from '~/modules/rules/hooks';
diff --git a/src/components/shared/BaseModal.tsx b/src/components/shared/BaseModal.tsx
index 72dcba4..f7841f8 100644
--- a/src/components/shared/BaseModal.tsx
+++ b/src/components/shared/BaseModal.tsx
@@ -1,6 +1,7 @@
import cx from 'clsx';
import * as React from 'react';
-import Modal from 'react-modal';
+
+import Modal from '../Modal';
import modalStyle from '../Modal.module.scss';
@@ -8,7 +9,13 @@ import s from './BaseModal.module.scss';
const { useMemo } = React;
-export default function BaseModal({ isOpen, onRequestClose, children }) {
+type BaseModalProps = {
+ isOpen: boolean;
+ onRequestClose: (...args: any[]) => unknown;
+ children: React.ReactNode;
+};
+
+export default function BaseModal({ isOpen, onRequestClose, children }: BaseModalProps) {
const className = useMemo(
() => ({
base: cx(modalStyle.content, s.cnt),
diff --git a/src/components/shared/Basic.module.scss b/src/components/shared/Basic.module.scss
index 79b8a16..df412e5 100644
--- a/src/components/shared/Basic.module.scss
+++ b/src/components/shared/Basic.module.scss
@@ -1,4 +1,4 @@
-@import '~/styles/utils/custom-media';
+@use '~/styles/utils/custom-media' as *;
h2.sectionNameType {
margin: 0;
diff --git a/src/components/shared/Fab.tsx b/src/components/shared/Fab.tsx
index 8e72432..49c9a89 100644
--- a/src/components/shared/Fab.tsx
+++ b/src/components/shared/Fab.tsx
@@ -18,7 +18,7 @@ export const position = {
interface ABProps extends React.HTMLAttributes<HTMLButtonElement> {
text?: string;
- onClick?: (e: React.FormEvent) => void;
+ onClick?: (e: React.MouseEvent<HTMLButtonElement>) => unknown;
'data-testid'?: string;
}
@@ -46,7 +46,7 @@ interface FabProps {
alwaysShowTitle?: boolean;
icon?: React.ReactNode;
mainButtonStyles?: React.CSSProperties;
- onClick?: (e: React.FormEvent) => void;
+ onClick?: (e: React.MouseEvent<HTMLButtonElement>) => unknown;
text?: string;
children?: React.ReactNode;
}
@@ -68,7 +68,7 @@ const Fab: React.FC<FabProps> = ({
const close = () => setIsOpen(false);
const enter = () => event === 'hover' && open();
const leave = () => event === 'hover' && close();
- const toggle = (e: React.FormEvent) => {
+ const toggle = (e: React.MouseEvent<HTMLButtonElement>) => {
if (onClick) {
return onClick(e);
}
@@ -76,7 +76,10 @@ const Fab: React.FC<FabProps> = ({
return event === 'click' ? (isOpen ? close() : open()) : null;
};
- const actionOnClick = (e: React.FormEvent, userFunc: (e: React.FormEvent) => void) => {
+ const actionOnClick = (
+ e: React.MouseEvent<HTMLButtonElement>,
+ userFunc: (e: React.MouseEvent<HTMLButtonElement>) => unknown
+ ) => {
e.persist();
setIsOpen(false);
setTimeout(() => {
@@ -95,7 +98,7 @@ const Fab: React.FC<FabProps> = ({
'aria-hidden': ariaHidden,
tabIndex: isOpen ? 0 : -1,
...ch.props,
- onClick: (e: React.FormEvent) => {
+ onClick: (e: React.MouseEvent<HTMLButtonElement>) => {
if (ch.props.onClick) actionOnClick(e, ch.props.onClick);
},
})}
diff --git a/src/components/shared/FeatherIcons.ts b/src/components/shared/FeatherIcons.ts
new file mode 100644
index 0000000..ee1f410
--- /dev/null
+++ b/src/components/shared/FeatherIcons.ts
@@ -0,0 +1,38 @@
+export {
+ Activity,
+ ArrowDown,
+ ArrowDownCircle,
+ ArrowUp,
+ ChevronDown,
+ ChevronUp,
+ Cpu,
+ Database,
+ Download,
+ DownloadCloud,
+ Eye,
+ EyeOff,
+ FileText,
+ GitHub,
+ Globe,
+ Hash,
+ Info,
+ Link,
+ LogOut,
+ Menu,
+ Monitor,
+ Pause,
+ Play,
+ RefreshCcw,
+ RefreshCw,
+ RotateCw,
+ Settings,
+ Shield,
+ Sliders,
+ Tag,
+ Tool,
+ Trash2,
+ Upload,
+ X,
+ XCircle,
+ Zap,
+} from 'react-feather';
diff --git a/src/components/shared/RotateIcon.tsx b/src/components/shared/RotateIcon.tsx
index 7e3ceae..0a5a018 100644
--- a/src/components/shared/RotateIcon.tsx
+++ b/src/components/shared/RotateIcon.tsx
@@ -1,6 +1,6 @@
import cx from 'clsx';
import * as React from 'react';
-import { RotateCw } from 'react-feather';
+import { RotateCw } from '~/components/shared/FeatherIcons';
import s from './RotateIcon.module.scss';
diff --git a/src/components/shared/ThemeSwitcher.tsx b/src/components/shared/ThemeSwitcher.tsx
index 363d422..dfe248d 100644
--- a/src/components/shared/ThemeSwitcher.tsx
+++ b/src/components/shared/ThemeSwitcher.tsx
@@ -3,7 +3,7 @@ import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { connect } from '~/components/StateProvider';
-import { framerMotionResouce } from '~/misc/motion';
+import { LazyMotion, domAnimation, m } from 'framer-motion';
import { getTheme, switchTheme } from '~/store/app';
import { State } from '~/store/types';
@@ -28,7 +28,7 @@ export function ThemeSwitcherImpl({ theme, dispatch }) {
const onChange = React.useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => dispatch(switchTheme(e.target.value)),
- [dispatch]
+ [dispatch],
);
return (
@@ -46,91 +46,89 @@ export function ThemeSwitcherImpl({ theme, dispatch }) {
}
function MoonA() {
- const module = framerMotionResouce.read();
- const motion = module.motion;
return (
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="20"
- height="20"
- viewBox="0 0 24 24"
- fill="none"
- stroke="currentColor"
- strokeWidth="2"
- strokeLinecap="round"
- strokeLinejoin="round"
- >
- <motion.path
- d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"
- initial={{ rotate: -30 }}
- animate={{ rotate: 0 }}
- transition={{ duration: 0.7 }}
- />
- </svg>
+ <LazyMotion features={domAnimation}>
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="20"
+ height="20"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ strokeWidth="2"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ >
+ <m.path
+ d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"
+ initial={{ rotate: -30 }}
+ animate={{ rotate: 0 }}
+ transition={{ duration: 0.7 }}
+ />
+ </svg>
+ </LazyMotion>
);
}
function Sun() {
- const module = framerMotionResouce.read();
- const motion = module.motion;
-
return (
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="20"
- height="20"
- viewBox="0 0 24 24"
- fill="none"
- stroke="currentColor"
- strokeWidth="2"
- strokeLinecap="round"
- strokeLinejoin="round"
- >
- <circle cx="12" cy="12" r="5"></circle>
- <motion.g initial={{ scale: 0.7 }} animate={{ scale: 1 }} transition={{ duration: 0.5 }}>
- <line x1="12" y1="1" x2="12" y2="3"></line>
- <line x1="12" y1="21" x2="12" y2="23"></line>
- <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
- <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
- <line x1="1" y1="12" x2="3" y2="12"></line>
- <line x1="21" y1="12" x2="23" y2="12"></line>
- <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
- <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
- </motion.g>
- </svg>
+ <LazyMotion features={domAnimation}>
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="20"
+ height="20"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ strokeWidth="2"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ >
+ <circle cx="12" cy="12" r="5"></circle>
+ <m.g initial={{ scale: 0.7 }} animate={{ scale: 1 }} transition={{ duration: 0.5 }}>
+ <line x1="12" y1="1" x2="12" y2="3"></line>
+ <line x1="12" y1="21" x2="12" y2="23"></line>
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
+ <line x1="1" y1="12" x2="3" y2="12"></line>
+ <line x1="21" y1="12" x2="23" y2="12"></line>
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
+ </m.g>
+ </svg>
+ </LazyMotion>
);
}
function Auto() {
- const module = framerMotionResouce.read();
- const motion = module.motion;
-
return (
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="20"
- height="20"
- viewBox="0 0 24 24"
- fill="none"
- stroke="currentColor"
- strokeWidth="2"
- strokeLinecap="round"
- strokeLinejoin="round"
- >
- <circle cx="12" cy="12" r="11" />
- <clipPath id="cut-off-bottom">
- <motion.rect
- x="12"
- y="0"
- width="12"
- height="24"
- initial={{ rotate: -30 }}
- animate={{ rotate: 0 }}
- transition={{ duration: 0.7 }}
- />
- </clipPath>
- <circle cx="12" cy="12" r="6" clipPath="url(#cut-off-bottom)" fill="currentColor" />
- </svg>
+ <LazyMotion features={domAnimation}>
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="20"
+ height="20"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ strokeWidth="2"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ >
+ <circle cx="12" cy="12" r="11" />
+ <clipPath id="cut-off-bottom">
+ <m.rect
+ x="12"
+ y="0"
+ width="12"
+ height="24"
+ initial={{ rotate: -30 }}
+ animate={{ rotate: 0 }}
+ transition={{ duration: 0.7 }}
+ />
+ </clipPath>
+ <circle cx="12" cy="12" r="6" clipPath="url(#cut-off-bottom)" fill="currentColor" />
+ </svg>
+ </LazyMotion>
);
}