summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOliver <oliver@mohlin.dev>2025-10-09 13:57:20 +0200
committerTobias Järvelöv <tobias.jarvelov@mullvad.net>2025-10-10 13:38:06 +0200
commit2903681572d2b0e816e4b7706b92af47b79df570 (patch)
tree99c88d73589246c34c9110280788af9055f3029d
parent541d8a1f6c85f84084179b50fe3e5f2463d962e7 (diff)
downloadmullvadvpn-2903681572d2b0e816e4b7706b92af47b79df570.tar.xz
mullvadvpn-2903681572d2b0e816e4b7706b92af47b79df570.zip
Add EmptyState component
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/EmptyState.tsx35
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/EmptyStateContext.tsx26
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-button/EmptyStateButton.tsx20
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-button/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-status-icon/EmptyStateStatusIcon.tsx14
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-status-icon/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-subtitle/EmptyStateSubtitle.tsx10
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-subtitle/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-text-container/EmptyStateTextContainer.tsx11
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-text-container/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-title/EmptyStateTitle.tsx11
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-title/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/index.ts5
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/index.ts1
14 files changed, 138 insertions, 0 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/EmptyState.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/EmptyState.tsx
new file mode 100644
index 0000000000..7d94385ebe
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/EmptyState.tsx
@@ -0,0 +1,35 @@
+import { Flex, FlexProps } from '../flex';
+import {
+ EmptyStateButton,
+ EmptyStateStatusIcon,
+ EmptyStateSubtitle,
+ EmptyStateTextContainer,
+ EmptyStateTitle,
+} from './components';
+import { EmptyStateProvider } from './EmptyStateContext';
+
+type EmptyStateVariant = 'success' | 'error' | 'loading';
+
+export type EmptyStateProps = FlexProps & {
+ variant?: EmptyStateVariant;
+};
+
+function EmptyState({ variant = 'error', children, ...props }: EmptyStateProps) {
+ return (
+ <EmptyStateProvider variant={variant}>
+ <Flex $flexDirection="column" $gap="medium" $alignItems="center" {...props}>
+ {children}
+ </Flex>
+ </EmptyStateProvider>
+ );
+}
+
+const EmptyStateNamespace = Object.assign(EmptyState, {
+ StatusIcon: EmptyStateStatusIcon,
+ Subtitle: EmptyStateSubtitle,
+ Title: EmptyStateTitle,
+ Button: EmptyStateButton,
+ TextContainer: EmptyStateTextContainer,
+});
+
+export { EmptyStateNamespace as EmptyState };
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/EmptyStateContext.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/EmptyStateContext.tsx
new file mode 100644
index 0000000000..ea89b73515
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/EmptyStateContext.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+
+import { EmptyStateProps } from './EmptyState';
+
+type EmptyStateContextProps = {
+ variant: EmptyStateProps['variant'];
+};
+
+const EmptyStateContext = React.createContext<EmptyStateContextProps | undefined>(undefined);
+
+export const useEmptyStateContext = (): EmptyStateContextProps => {
+ const context = React.useContext(EmptyStateContext);
+ if (!context) {
+ throw new Error('useEmptyStateContext must be used within a EmptyStateProvider');
+ }
+ return context;
+};
+
+interface EmptyStateProviderProps {
+ variant: EmptyStateProps['variant'];
+ children: React.ReactNode;
+}
+
+export function EmptyStateProvider({ variant, children }: EmptyStateProviderProps) {
+ return <EmptyStateContext.Provider value={{ variant }}>{children}</EmptyStateContext.Provider>;
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-button/EmptyStateButton.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-button/EmptyStateButton.tsx
new file mode 100644
index 0000000000..c2de7604bd
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-button/EmptyStateButton.tsx
@@ -0,0 +1,20 @@
+import { Button, ButtonProps } from '../../../button';
+import { useEmptyStateContext } from '../../EmptyStateContext';
+
+export type EmpptyStateButtonProps = ButtonProps;
+
+function EmptyStateButton({ children, ...props }: EmpptyStateButtonProps) {
+ const { variant } = useEmptyStateContext();
+ const disabled = variant === 'loading';
+ return (
+ <Button disabled={disabled} {...props}>
+ {children}
+ </Button>
+ );
+}
+
+const EmptyStateButtonNamespace = Object.assign(EmptyStateButton, {
+ Text: Button.Text,
+});
+
+export { EmptyStateButtonNamespace as EmptyStateButton };
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-button/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-button/index.ts
new file mode 100644
index 0000000000..675721619a
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-button/index.ts
@@ -0,0 +1 @@
+export * from './EmptyStateButton';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-status-icon/EmptyStateStatusIcon.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-status-icon/EmptyStateStatusIcon.tsx
new file mode 100644
index 0000000000..8ef16a2ab5
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-status-icon/EmptyStateStatusIcon.tsx
@@ -0,0 +1,14 @@
+import { IconBadge } from '../../../../icon-badge';
+import { Spinner } from '../../../spinner';
+import { useEmptyStateContext } from '../../EmptyStateContext';
+
+export function EmptyStateStatusIcon() {
+ const { variant } = useEmptyStateContext();
+ return (
+ <>
+ {variant === 'success' && <IconBadge state={'positive'} />}
+ {variant === 'error' && <IconBadge state={'negative'} />}
+ {variant === 'loading' && <Spinner size="big" />}
+ </>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-status-icon/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-status-icon/index.ts
new file mode 100644
index 0000000000..0bd3c665bb
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-status-icon/index.ts
@@ -0,0 +1 @@
+export * from './EmptyStateStatusIcon';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-subtitle/EmptyStateSubtitle.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-subtitle/EmptyStateSubtitle.tsx
new file mode 100644
index 0000000000..61a4c02c41
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-subtitle/EmptyStateSubtitle.tsx
@@ -0,0 +1,10 @@
+import { Text, TextProps } from '../../../text';
+
+export type EmptyStateSubtitleProps = TextProps;
+export function EmptyStateSubtitle({ children, ...props }: EmptyStateSubtitleProps) {
+ return (
+ <Text variant="labelTiny" color="whiteAlpha60" textAlign="center" {...props}>
+ {children}
+ </Text>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-subtitle/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-subtitle/index.ts
new file mode 100644
index 0000000000..f210c0dc29
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-subtitle/index.ts
@@ -0,0 +1 @@
+export * from './EmptyStateSubtitle';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-text-container/EmptyStateTextContainer.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-text-container/EmptyStateTextContainer.tsx
new file mode 100644
index 0000000000..0f964527a8
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-text-container/EmptyStateTextContainer.tsx
@@ -0,0 +1,11 @@
+import { Flex, FlexProps } from '../../../flex';
+
+export type EmptyStateTextContainerProps = FlexProps;
+
+export function EmptyStateTextContainer({ children, ...props }: EmptyStateTextContainerProps) {
+ return (
+ <Flex $flexDirection="column" $alignItems="center" $gap="tiny" {...props}>
+ {children}
+ </Flex>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-text-container/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-text-container/index.ts
new file mode 100644
index 0000000000..6db950b516
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-text-container/index.ts
@@ -0,0 +1 @@
+export * from './EmptyStateTextContainer';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-title/EmptyStateTitle.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-title/EmptyStateTitle.tsx
new file mode 100644
index 0000000000..0de48a2f39
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-title/EmptyStateTitle.tsx
@@ -0,0 +1,11 @@
+import { Text, TextProps } from '../../../text';
+
+export type EmptyStateTitleProps = TextProps;
+
+export function EmptyStateTitle({ children, ...props }: EmptyStateTitleProps) {
+ return (
+ <Text variant="titleMedium" textAlign="center" {...props}>
+ {children}
+ </Text>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-title/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-title/index.ts
new file mode 100644
index 0000000000..ce04a342db
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/empty-state-title/index.ts
@@ -0,0 +1 @@
+export * from './EmptyStateTitle';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/index.ts
new file mode 100644
index 0000000000..060505932e
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/components/index.ts
@@ -0,0 +1,5 @@
+export * from './empty-state-button';
+export * from './empty-state-status-icon';
+export * from './empty-state-subtitle';
+export * from './empty-state-text-container';
+export * from './empty-state-title';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/index.ts
new file mode 100644
index 0000000000..c54a68a9de
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/empty-state/index.ts
@@ -0,0 +1 @@
+export * from './EmptyState';