summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorSebastian Holmin <sebastian.holmin@mullvad.net>2025-03-24 14:28:19 +0100
committerSebastian Holmin <sebastian.holmin@mullvad.net>2025-03-24 14:28:19 +0100
commit41ef1ad87d45ea9d8ff2941280ee5ef5291e0869 (patch)
tree9cff15214cbbbe0d9d7d2402dcea7590448b4def
parent8a2516663b61c2eb5bc6e947d8998cec531f8a6c (diff)
parentd72754dd2d07e5cfa7416c78815ab1b7e1db86ab (diff)
downloadmullvadvpn-41ef1ad87d45ea9d8ff2941280ee5ef5291e0869.tar.xz
mullvadvpn-41ef1ad87d45ea9d8ff2941280ee5ef5291e0869.zip
Merge branch 'create-listitem-component-des-1895'
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/ListItem.tsx40
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/ListItemContext.tsx28
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemContent.tsx47
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemFooter.tsx7
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemGroup.tsx7
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemItem.tsx19
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemLabel.tsx14
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemText.tsx16
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemTrigger.tsx40
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/index.ts7
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/levels.ts10
12 files changed, 236 insertions, 0 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/ListItem.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/ListItem.tsx
new file mode 100644
index 0000000000..72d25f2b6c
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/ListItem.tsx
@@ -0,0 +1,40 @@
+import { Flex } from '../flex';
+import {
+ ListItemContent,
+ ListItemFooter,
+ ListItemGroup,
+ ListItemItem,
+ ListItemLabel,
+ ListItemText,
+ ListItemTrigger,
+} from './components';
+import { levels } from './levels';
+import { ListItemProvider } from './ListItemContext';
+
+export interface ListItemProps {
+ level?: keyof typeof levels;
+ disabled?: boolean;
+ children: React.ReactNode;
+}
+
+const ListItem = ({ level = 0, disabled, children }: ListItemProps) => {
+ return (
+ <ListItemProvider level={level} disabled={disabled}>
+ <Flex $flexDirection="column" $gap="small">
+ {children}
+ </Flex>
+ </ListItemProvider>
+ );
+};
+
+const ListItemNamespace = Object.assign(ListItem, {
+ Content: ListItemContent,
+ Label: ListItemLabel,
+ Group: ListItemGroup,
+ Text: ListItemText,
+ Trigger: ListItemTrigger,
+ Item: ListItemItem,
+ Footer: ListItemFooter,
+});
+
+export { ListItemNamespace as ListItem };
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/ListItemContext.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/ListItemContext.tsx
new file mode 100644
index 0000000000..348f916a41
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/ListItemContext.tsx
@@ -0,0 +1,28 @@
+import { createContext, ReactNode, useContext } from 'react';
+
+import { levels } from './levels';
+
+interface ListItemContextType {
+ level: keyof typeof levels;
+ disabled?: boolean;
+}
+
+const ListItemContext = createContext<ListItemContextType | undefined>(undefined);
+
+interface ListItemProviderProps extends ListItemContextType {
+ children: ReactNode;
+}
+
+export const ListItemProvider = ({ level, disabled, children }: ListItemProviderProps) => {
+ return (
+ <ListItemContext.Provider value={{ level, disabled }}>{children}</ListItemContext.Provider>
+ );
+};
+
+export const useListItem = (): ListItemContextType => {
+ const context = useContext(ListItemContext);
+ if (!context) {
+ throw new Error('useListItem must be used within a ListItemProvider');
+ }
+ return context;
+};
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemContent.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemContent.tsx
new file mode 100644
index 0000000000..612b7ce6bc
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemContent.tsx
@@ -0,0 +1,47 @@
+import styled from 'styled-components';
+
+import { Flex, FlexProps } from '../../flex';
+import { levels } from '../levels';
+import { useListItem } from '../ListItemContext';
+
+const sizes = {
+ full: '100%',
+ small: '44px',
+};
+
+export const StyledFlex = styled(Flex)<{
+ $level: keyof typeof levels;
+ $disabled?: boolean;
+ $size: 'full' | 'small';
+}>`
+ width: ${({ $size }) => sizes[$size]};
+ height: 100%;
+ background-color: ${({ $disabled, $level }) =>
+ $disabled ? levels[$level].disabled : levels[$level].enabled};
+ &&:has(> :last-child:nth-child(1)) {
+ &&:has(img) {
+ justify-content: center;
+ }
+ }
+`;
+
+export interface ListItemContainerProps extends FlexProps {
+ size?: 'full' | 'small';
+}
+
+export const ListItemContent = ({ size = 'full', ...props }: ListItemContainerProps) => {
+ const { level } = useListItem();
+ return (
+ <StyledFlex
+ $size={size}
+ $level={level}
+ $alignItems="center"
+ $justifyContent="space-between"
+ $gap="small"
+ $padding={{
+ horizontal: 'medium',
+ }}
+ {...props}
+ />
+ );
+};
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemFooter.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemFooter.tsx
new file mode 100644
index 0000000000..669b785cc8
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemFooter.tsx
@@ -0,0 +1,7 @@
+import { Flex, FlexProps } from '../../flex';
+
+export type ListIOtemFooterProps = FlexProps;
+
+export const ListItemFooter = (props: ListIOtemFooterProps) => {
+ return <Flex $padding={{ horizontal: 'medium' }} {...props} />;
+};
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemGroup.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemGroup.tsx
new file mode 100644
index 0000000000..6e7b52ef49
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemGroup.tsx
@@ -0,0 +1,7 @@
+import { Flex, FlexProps } from '../../flex';
+
+export type ListItemGroupProps = FlexProps;
+
+export const ListItemGroup = (props: ListItemGroupProps) => {
+ return <Flex $alignItems="center" $gap="small" {...props} />;
+};
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemItem.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemItem.tsx
new file mode 100644
index 0000000000..9bee51b0e9
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemItem.tsx
@@ -0,0 +1,19 @@
+import styled from 'styled-components';
+
+export interface ListItemItemProps {
+ children: React.ReactNode;
+}
+
+const StyledDiv = styled.div`
+ min-height: 44px;
+ width: 100%;
+ display: grid;
+ grid-template-columns: 1fr;
+ &&:has(> :last-child:nth-child(2)) {
+ grid-template-columns: 1fr 44px;
+ }
+`;
+
+export const ListItemItem = ({ children }: ListItemItemProps) => {
+ return <StyledDiv>{children}</StyledDiv>;
+};
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemLabel.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemLabel.tsx
new file mode 100644
index 0000000000..3dcfdaa59f
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemLabel.tsx
@@ -0,0 +1,14 @@
+import { Colors } from '../../../foundations';
+import { LabelTinyProps, TitleMedium } from '../../typography';
+import { useListItem } from '../ListItemContext';
+
+export type ListItemLabelProps = LabelTinyProps;
+
+export const ListItemLabel = ({ children, ...props }: ListItemLabelProps) => {
+ const { disabled } = useListItem();
+ return (
+ <TitleMedium color={disabled ? Colors.white40 : Colors.white} {...props}>
+ {children}
+ </TitleMedium>
+ );
+};
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemText.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemText.tsx
new file mode 100644
index 0000000000..3b0ac46359
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemText.tsx
@@ -0,0 +1,16 @@
+import { Colors } from '../../../foundations';
+import { Text, TextProps } from '../../typography';
+import { useListItem } from '../ListItemContext';
+
+export type ListItemProps<E extends React.ElementType = 'span'> = Omit<TextProps<E>, 'variant'> & {
+ variant?: Extract<TextProps<E>['variant'], 'labelTiny' | 'footnoteMini'>;
+};
+
+export const ListItemText = ({ variant = 'labelTiny', children, ...props }: ListItemProps) => {
+ const { disabled } = useListItem();
+ return (
+ <Text variant={variant} color={disabled ? Colors.white40 : Colors.white60} {...props}>
+ {children}
+ </Text>
+ );
+};
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemTrigger.tsx b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemTrigger.tsx
new file mode 100644
index 0000000000..7450fe1d49
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/ListItemTrigger.tsx
@@ -0,0 +1,40 @@
+import styled, { css } from 'styled-components';
+
+import { Colors } from '../../../foundations';
+import { ButtonBase } from '../../button';
+import { useListItem } from '../ListItemContext';
+import { StyledFlex } from './ListItemContent';
+
+// TODO: Colors should be replace with
+// with new color tokens once they are implemented.
+const StyledButton = styled(ButtonBase)<{ $disabled?: boolean }>`
+ display: flex;
+ width: 100%;
+ ${({ $disabled }) =>
+ !$disabled &&
+ css`
+ &:hover ${StyledFlex} {
+ background-color: rgba(56, 86, 116, 1);
+ }
+ &:active ${StyledFlex} {
+ background-color: rgba(62, 95, 129, 1);
+ }
+ `}
+
+ &&:focus-visible {
+ outline: 2px solid ${Colors.white};
+ outline-offset: -1px;
+ z-index: 10;
+ }
+`;
+
+export type ListItemTriggerProps = React.HtmlHTMLAttributes<HTMLButtonElement>;
+
+export const ListItemTrigger = ({ children, ...props }: ListItemTriggerProps) => {
+ const { disabled } = useListItem();
+ return (
+ <StyledButton $disabled={disabled} {...props}>
+ {children}
+ </StyledButton>
+ );
+};
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/index.ts
new file mode 100644
index 0000000000..69139b76ad
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/components/index.ts
@@ -0,0 +1,7 @@
+export * from './ListItemContent';
+export * from './ListItemItem';
+export * from './ListItemGroup';
+export * from './ListItemLabel';
+export * from './ListItemText';
+export * from './ListItemTrigger';
+export * from './ListItemFooter';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/index.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/index.ts
new file mode 100644
index 0000000000..c851539802
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/index.ts
@@ -0,0 +1 @@
+export * from './ListItem';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/levels.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/levels.ts
new file mode 100644
index 0000000000..adcf80a740
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/components/list-item/levels.ts
@@ -0,0 +1,10 @@
+// TODO: These are colors are from the new colors tokens
+// which are currently not implemented, should be replaced
+// with the new color tokens once they are.
+export const levels = {
+ 0: { enabled: 'rgba(41, 77, 115, 1)', disabled: 'rgba(31, 58, 87, 1)' },
+ 1: { enabled: 'rgba(35, 65, 97, 1)', disabled: 'rgba(31, 58, 87, 1)' },
+ 2: { enabled: 'rgba(31, 58, 87, 1)', disabled: 'rgba(28, 52, 78, 1)' },
+ 3: { enabled: 'rgba(28, 52, 78, 1)', disabled: 'rgba(27, 49, 74, 1)' },
+ 4: { enabled: 'rgba(27, 49, 74, 1)', disabled: 'rgba(27, 49, 74, 1)' },
+} as const;