diff options
| author | Sebastian Holmin <sebastian.holmin@mullvad.net> | 2025-03-24 14:28:19 +0100 |
|---|---|---|
| committer | Sebastian Holmin <sebastian.holmin@mullvad.net> | 2025-03-24 14:28:19 +0100 |
| commit | 41ef1ad87d45ea9d8ff2941280ee5ef5291e0869 (patch) | |
| tree | 9cff15214cbbbe0d9d7d2402dcea7590448b4def | |
| parent | 8a2516663b61c2eb5bc6e947d8998cec531f8a6c (diff) | |
| parent | d72754dd2d07e5cfa7416c78815ab1b7e1db86ab (diff) | |
| download | mullvadvpn-41ef1ad87d45ea9d8ff2941280ee5ef5291e0869.tar.xz mullvadvpn-41ef1ad87d45ea9d8ff2941280ee5ef5291e0869.zip | |
Merge branch 'create-listitem-component-des-1895'
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; |
