From 3927f7b4eafc872f9049c4fc1e7a2a49c980d1d0 Mon Sep 17 00:00:00 2001 From: Aronov Aleksandr Date: Sat, 9 Nov 2024 18:50:19 +0100 Subject: [PATCH] Added loading screen placeholder and removed old icons --- package-lock.json | 40 +++++++++ package.json | 1 + src/popup/components/AppLoadingSkeleton.tsx | 15 ++++ .../_stories/AppLoadingSkeleton.stories.tsx | 21 +++++ src/popup/hooks/PopupContext.tsx | 5 +- src/popup/hooks/useTimeStore.ts | 12 ++- .../preferences/components/ThemeSelector.tsx | 32 +++---- src/shared/services/i18n.ts | 17 +++- src/shared/ui/progress.tsx | 26 ++++++ src/shared/ui/skeleton.tsx | 17 ++++ src/tailwind.css | 86 ++----------------- 11 files changed, 169 insertions(+), 103 deletions(-) create mode 100644 src/popup/components/AppLoadingSkeleton.tsx create mode 100644 src/popup/components/_stories/AppLoadingSkeleton.stories.tsx create mode 100644 src/shared/ui/progress.tsx create mode 100644 src/shared/ui/skeleton.tsx diff --git a/package-lock.json b/package-lock.json index 24b0d7e..bb646e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "GPL3", "dependencies": { "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-tabs": "^1.1.1", @@ -2576,6 +2577,45 @@ } } }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.0.tgz", + "integrity": "sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", diff --git a/package.json b/package.json index 42fc540..fb96d08 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ }, "dependencies": { "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-tabs": "^1.1.1", diff --git a/src/popup/components/AppLoadingSkeleton.tsx b/src/popup/components/AppLoadingSkeleton.tsx new file mode 100644 index 0000000..4156f18 --- /dev/null +++ b/src/popup/components/AppLoadingSkeleton.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +import { Skeleton } from '@shared/ui/skeleton'; + +export const AppLoadingSkeleton = () => { + return ( +
+ + + + + +
+ ); +}; diff --git a/src/popup/components/_stories/AppLoadingSkeleton.stories.tsx b/src/popup/components/_stories/AppLoadingSkeleton.stories.tsx new file mode 100644 index 0000000..76048f9 --- /dev/null +++ b/src/popup/components/_stories/AppLoadingSkeleton.stories.tsx @@ -0,0 +1,21 @@ +import { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; + +import { AppLoadingSkeleton } from '../AppLoadingSkeleton'; + +export default { + title: 'popup/components/AppLoadingSkeleton', + component: AppLoadingSkeleton, + parameters: { + layout: 'centered', + }, +} satisfies Meta; + +type Story = StoryObj>; + +export const Default: Story = { + render: () => , + parameters: { + layout: 'centered', + }, +}; diff --git a/src/popup/hooks/PopupContext.tsx b/src/popup/hooks/PopupContext.tsx index 0a8444c..f74e59a 100644 --- a/src/popup/hooks/PopupContext.tsx +++ b/src/popup/hooks/PopupContext.tsx @@ -4,6 +4,7 @@ import { DeepReadonly } from 'utility-types'; import { Preferences } from '@shared/db/types'; import { DEFAULT_PREFERENCES } from '@shared/preferences'; +import { AppLoadingSkeleton } from '@popup/components/AppLoadingSkeleton'; import { getFilteredWebsiteTimeStoreSlice } from '@popup/services/time-store'; import { useActiveTabHostname } from './useActiveTab'; @@ -29,7 +30,7 @@ const PopupContext = React.createContext(DEFAULT_CONTEXT); export const usePopupContext = () => React.useContext(PopupContext); export const PopupContextProvider = ({ children }: React.PropsWithChildren) => { - const store = useTimeStore(); + const [store, isLoaded] = useTimeStore(); const host = useActiveTabHostname(); const [settings, updateSettings] = useSettings(); @@ -51,7 +52,7 @@ export const PopupContextProvider = ({ children }: React.PropsWithChildren) => { updateSettings, }} > - {children} + {isLoaded ? children : } ); }; diff --git a/src/popup/hooks/useTimeStore.ts b/src/popup/hooks/useTimeStore.ts index f0b5c47..5f34180 100644 --- a/src/popup/hooks/useTimeStore.ts +++ b/src/popup/hooks/useTimeStore.ts @@ -10,8 +10,10 @@ export { TimeStore }; export const useTimeStore = () => { const [store, setStore] = React.useState({}); + const [isLoaded, setIsLoaded] = React.useState(false); React.useEffect(() => { + const startDate = new Date(); Promise.all([getTotalActivity(), getActiveTabRecord()]).then( ([activity, activeRecord]) => { if (activeRecord?.hostname) { @@ -24,9 +26,17 @@ export const useTimeStore = () => { } setStore(activity); + + const endDate = new Date(); + // Should be at least 150ms to avoid flickering + const delay = Math.max( + 150 - (endDate.getTime() - startDate.getTime()), + 0, + ); + setTimeout(() => setIsLoaded(true), delay); }, ); }, []); - return store; + return [store, isLoaded] as const; }; diff --git a/src/popup/pages/preferences/components/ThemeSelector.tsx b/src/popup/pages/preferences/components/ThemeSelector.tsx index fd9a392..2f51f9c 100644 --- a/src/popup/pages/preferences/components/ThemeSelector.tsx +++ b/src/popup/pages/preferences/components/ThemeSelector.tsx @@ -1,10 +1,9 @@ +import { Eclipse, Moon, Sun } from 'lucide-react'; import * as React from 'react'; -import { twMerge } from 'tailwind-merge'; -import { Button } from '@shared/blocks/Button'; -import { Icon, IconType } from '@shared/blocks/Icon'; import { i18n } from '@shared/services/i18n'; import { ColorScheme, ThemeService } from '@shared/services/theme'; +import { Button } from '@shared/ui/button'; export const ThemeSelector = () => { const [theme, setTheme] = React.useState(() => ThemeService.getAppTheme()); @@ -35,30 +34,27 @@ export const ThemeSelector = () => {
diff --git a/src/shared/services/i18n.ts b/src/shared/services/i18n.ts index a771bbc..e23b62b 100644 --- a/src/shared/services/i18n.ts +++ b/src/shared/services/i18n.ts @@ -13,19 +13,28 @@ export const i18n = ( ) => { const values = placeholders[0]; if (chrome?.i18n?.getMessage) { - return chrome.i18n.getMessage( - message, - values ? Object.values(values) : undefined, + return ( + chrome.i18n.getMessage( + message, + values ? Object.values(values) : undefined, + ) ?? getMessageFromFallback(message, values) ); } + return getMessageFromFallback(message, values); +}; + +function getMessageFromFallback( + message: T, + values?: Record, +) { const messageTemplate = fallback[message].message; if (!values) { return messageTemplate; } return formatI18NMessage(messageTemplate, values); -}; +} export function formatI18NMessage( message: string, diff --git a/src/shared/ui/progress.tsx b/src/shared/ui/progress.tsx new file mode 100644 index 0000000..444c884 --- /dev/null +++ b/src/shared/ui/progress.tsx @@ -0,0 +1,26 @@ +import * as ProgressPrimitive from '@radix-ui/react-progress'; +import * as React from 'react'; + +import { cn } from '@shared/utils'; + +const Progress = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, value, ...props }, ref) => ( + + + +)); +Progress.displayName = ProgressPrimitive.Root.displayName; + +export { Progress }; diff --git a/src/shared/ui/skeleton.tsx b/src/shared/ui/skeleton.tsx new file mode 100644 index 0000000..920f448 --- /dev/null +++ b/src/shared/ui/skeleton.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +import { cn } from '@shared/utils'; + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ); +} + +export { Skeleton }; diff --git a/src/tailwind.css b/src/tailwind.css index 31a4fdf..63eb615 100644 --- a/src/tailwind.css +++ b/src/tailwind.css @@ -1,77 +1,7 @@ -@import url('https://cdn-uicons.flaticon.com/uicons-regular-rounded/css/uicons-regular-rounded.css'); - @tailwind base; @tailwind components; @tailwind utilities; -/* @layer base { - :root { - --background: 0 0% 100%; - --foreground: 222.2 47.4% 11.2%; - - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - - --popover: 0 0% 100%; - --popover-foreground: 222.2 47.4% 11.2%; - - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - - --card: 0 0% 100%; - --card-foreground: 222.2 47.4% 11.2%; - - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; - - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - - --destructive: 0 100% 50%; - --destructive-foreground: 210 40% 98%; - - --ring: 215 20.2% 65.1%; - - --radius: 0.5rem; - } - - .dark { - --background: 224 71% 4%; - --foreground: 213 31% 91%; - - --muted: 223 47% 11%; - --muted-foreground: 215.4 16.3% 56.9%; - - --accent: 216 34% 17%; - --accent-foreground: 210 40% 98%; - - --popover: 224 71% 4%; - --popover-foreground: 215 20.2% 65.1%; - - --border: 216 34% 17%; - --input: 216 34% 17%; - - --card: 224 71% 4%; - --card-foreground: 213 31% 91%; - - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 1.2%; - - --secondary: 222.2 47.4% 11.2%; - --secondary-foreground: 210 40% 98%; - - --destructive: 0 63% 31%; - --destructive-foreground: 210 40% 98%; - - --ring: 216 34% 17%; - - --radius: 0.5rem; - } -} */ - @layer base { :root { --background: 0 0% 100%; @@ -80,8 +10,8 @@ --card-foreground: 20 14.3% 4.1%; --popover: 0 0% 100%; --popover-foreground: 20 14.3% 4.1%; - --primary: 47.9 95.8% 53.1%; - --primary-foreground: 26 83.3% 14.1%; + --primary: 24.6 95% 53.1%; + --primary-foreground: 60 9.1% 97.8%; --secondary: 60 4.8% 95.9%; --secondary-foreground: 24 9.8% 10%; --muted: 60 4.8% 95.9%; @@ -92,8 +22,8 @@ --destructive-foreground: 60 9.1% 97.8%; --border: 20 5.9% 90%; --input: 20 5.9% 90%; - --ring: 20 14.3% 4.1%; - --radius: 0.75rem; + --ring: 24.6 95% 53.1%; + --radius: 0.5rem; --chart-1: 12 76% 61%; --chart-2: 173 58% 39%; --chart-3: 197 37% 24%; @@ -108,19 +38,19 @@ --card-foreground: 60 9.1% 97.8%; --popover: 20 14.3% 4.1%; --popover-foreground: 60 9.1% 97.8%; - --primary: 47.9 95.8% 53.1%; - --primary-foreground: 26 83.3% 14.1%; + --primary: 20.5 90.2% 48.2%; + --primary-foreground: 60 9.1% 97.8%; --secondary: 12 6.5% 15.1%; --secondary-foreground: 60 9.1% 97.8%; --muted: 12 6.5% 15.1%; --muted-foreground: 24 5.4% 63.9%; --accent: 12 6.5% 15.1%; --accent-foreground: 60 9.1% 97.8%; - --destructive: 0 62.8% 30.6%; + --destructive: 0 72.2% 50.6%; --destructive-foreground: 60 9.1% 97.8%; --border: 12 6.5% 15.1%; --input: 12 6.5% 15.1%; - --ring: 35.5 91.7% 32.9%; + --ring: 20.5 90.2% 48.2%; --chart-1: 220 70% 50%; --chart-2: 160 60% 45%; --chart-3: 30 80% 55%;