♻️(frontend) icon component refactoring

- Add variant to IconComponent and remove $isMaterialIcon prop
- Replace all Text component used as icon with the Icon component.
This commit is contained in:
Nathan Panchout
2025-03-27 10:52:26 +01:00
committed by Anthony LC
parent 8aab007ad1
commit 0537572542
15 changed files with 55 additions and 145 deletions

View File

@@ -30,6 +30,7 @@
"@sentry/nextjs": "9.3.0", "@sentry/nextjs": "9.3.0",
"@tanstack/react-query": "5.67.1", "@tanstack/react-query": "5.67.1",
"canvg": "4.0.3", "canvg": "4.0.3",
"clsx": "2.1.1",
"cmdk": "1.0.4", "cmdk": "1.0.4",
"crisp-sdk-web": "1.0.25", "crisp-sdk-web": "1.0.25",
"docx": "9.1.1", "docx": "9.1.1",

View File

@@ -1,42 +1,24 @@
import clsx from 'clsx';
import { css } from 'styled-components'; import { css } from 'styled-components';
import { Text, TextType } from '@/components'; import { Text, TextType } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
type IconProps = TextType & { type IconProps = TextType & {
iconName: string; iconName: string;
variant?: 'filled' | 'outlined';
}; };
export const Icon = ({ iconName, ...textProps }: IconProps) => { export const Icon = ({
return ( iconName,
<Text $isMaterialIcon {...textProps}> variant = 'outlined',
{iconName} ...textProps
</Text> }: IconProps) => {
);
};
interface IconBGProps extends TextType {
iconName: string;
}
export const IconBG = ({ iconName, ...textProps }: IconBGProps) => {
const { colorsTokens } = useCunninghamTheme();
return ( return (
<Text <Text
$isMaterialIcon
$size="36px"
$theme="primary"
$variation="600"
$background={colorsTokens()['primary-bg']}
$css={`
border: 1px solid ${colorsTokens()['primary-200']};
user-select: none;
`}
$radius="12px"
$padding="4px"
$margin="auto"
{...textProps} {...textProps}
className={`--docs--icon-bg ${textProps.className || ''}`} className={clsx('--docs--icon-bg', textProps.className, {
'material-icons-filled': variant === 'filled',
'material-icons': variant === 'outlined',
})}
> >
{iconName} {iconName}
</Text> </Text>
@@ -49,15 +31,13 @@ type IconOptionsProps = TextType & {
export const IconOptions = ({ isHorizontal, ...props }: IconOptionsProps) => { export const IconOptions = ({ isHorizontal, ...props }: IconOptionsProps) => {
return ( return (
<Text <Icon
{...props} {...props}
$isMaterialIcon iconName={isHorizontal ? 'more_horiz' : 'more_vert'}
$css={css` $css={css`
user-select: none; user-select: none;
${props.$css} ${props.$css}
`} `}
> />
{isHorizontal ? 'more_horiz' : 'more_vert'}
</Text>
); );
}; };

View File

@@ -11,7 +11,6 @@ type TextSizes = keyof typeof sizes;
export interface TextProps extends BoxProps { export interface TextProps extends BoxProps {
as?: 'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; as?: 'p' | 'span' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
$elipsis?: boolean; $elipsis?: boolean;
$isMaterialIcon?: boolean;
$weight?: CSSProperties['fontWeight']; $weight?: CSSProperties['fontWeight'];
$textAlign?: CSSProperties['textAlign']; $textAlign?: CSSProperties['textAlign'];
$size?: TextSizes | (string & {}); $size?: TextSizes | (string & {});
@@ -57,14 +56,14 @@ export const TextStyled = styled(Box)<TextProps>`
`; `;
const Text = forwardRef<HTMLElement, ComponentPropsWithRef<typeof TextStyled>>( const Text = forwardRef<HTMLElement, ComponentPropsWithRef<typeof TextStyled>>(
({ className, $isMaterialIcon, ...props }, ref) => { ({ className, ...props }, ref) => {
return ( return (
<TextStyled <TextStyled
ref={ref} ref={ref}
as="span" as="span"
$theme="greyscale" $theme="greyscale"
$variation="text" $variation="text"
className={`${className || ''}${$isMaterialIcon ? ' material-icons' : ''}`} className={className}
{...props} {...props}
/> />
); );

View File

@@ -14,7 +14,7 @@ import { PropsWithChildren, ReactNode, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { isAPIError } from '@/api'; import { isAPIError } from '@/api';
import { Box, Text } from '@/components'; import { Box, Icon } from '@/components';
import { useDocOptions, useDocStore } from '@/docs/doc-management/'; import { useDocOptions, useDocStore } from '@/docs/doc-management/';
import { import {
@@ -108,11 +108,7 @@ export function AIGroupButton() {
data-test="ai-actions" data-test="ai-actions"
label="AI" label="AI"
mainTooltip={t('AI Actions')} mainTooltip={t('AI Actions')}
icon={ icon={<Icon iconName="auto_awesome" $size="l" />}
<Text $isMaterialIcon $size="l">
auto_awesome
</Text>
}
/> />
</Components.Generic.Menu.Trigger> </Components.Generic.Menu.Trigger>
<Components.Generic.Menu.Dropdown <Components.Generic.Menu.Dropdown
@@ -124,66 +120,42 @@ export function AIGroupButton() {
<AIMenuItemTransform <AIMenuItemTransform
action="prompt" action="prompt"
docId={currentDoc.id} docId={currentDoc.id}
icon={ icon={<Icon iconName="text_fields" $size="s" />}
<Text $isMaterialIcon $size="s">
text_fields
</Text>
}
> >
{t('Use as prompt')} {t('Use as prompt')}
</AIMenuItemTransform> </AIMenuItemTransform>
<AIMenuItemTransform <AIMenuItemTransform
action="rephrase" action="rephrase"
docId={currentDoc.id} docId={currentDoc.id}
icon={ icon={<Icon iconName="refresh" $size="s" />}
<Text $isMaterialIcon $size="s">
refresh
</Text>
}
> >
{t('Rephrase')} {t('Rephrase')}
</AIMenuItemTransform> </AIMenuItemTransform>
<AIMenuItemTransform <AIMenuItemTransform
action="summarize" action="summarize"
docId={currentDoc.id} docId={currentDoc.id}
icon={ icon={<Icon iconName="summarize" $size="s" />}
<Text $isMaterialIcon $size="s">
summarize
</Text>
}
> >
{t('Summarize')} {t('Summarize')}
</AIMenuItemTransform> </AIMenuItemTransform>
<AIMenuItemTransform <AIMenuItemTransform
action="correct" action="correct"
docId={currentDoc.id} docId={currentDoc.id}
icon={ icon={<Icon iconName="check" $size="s" />}
<Text $isMaterialIcon $size="s">
check
</Text>
}
> >
{t('Correct')} {t('Correct')}
</AIMenuItemTransform> </AIMenuItemTransform>
<AIMenuItemTransform <AIMenuItemTransform
action="beautify" action="beautify"
docId={currentDoc.id} docId={currentDoc.id}
icon={ icon={<Icon iconName="draw" $size="s" />}
<Text $isMaterialIcon $size="s">
draw
</Text>
}
> >
{t('Beautify')} {t('Beautify')}
</AIMenuItemTransform> </AIMenuItemTransform>
<AIMenuItemTransform <AIMenuItemTransform
action="emojify" action="emojify"
docId={currentDoc.id} docId={currentDoc.id}
icon={ icon={<Icon iconName="emoji_emotions" $size="s" />}
<Text $isMaterialIcon $size="s">
emoji_emotions
</Text>
}
> >
{t('Emojify')} {t('Emojify')}
</AIMenuItemTransform> </AIMenuItemTransform>
@@ -197,9 +169,7 @@ export function AIGroupButton() {
subTrigger={true} subTrigger={true}
> >
<Box $direction="row" $gap="0.6rem"> <Box $direction="row" $gap="0.6rem">
<Text $isMaterialIcon $size="s"> <Icon iconName="translate" $size="s" />
translate
</Text>
{t('Language')} {t('Language')}
</Box> </Box>
</Components.Generic.Menu.Item> </Components.Generic.Menu.Item>

View File

@@ -1,7 +1,7 @@
import { Button, Modal, ModalSize } from '@openfun/cunningham-react'; import { Button, Modal, ModalSize } from '@openfun/cunningham-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Box, Text } from '@/components'; import { Box, Icon, Text } from '@/components';
interface ModalConfirmDownloadUnsafeProps { interface ModalConfirmDownloadUnsafeProps {
onClose: () => void; onClose: () => void;
@@ -52,9 +52,7 @@ export const ModalConfirmDownloadUnsafe = ({
$variation="1000" $variation="1000"
$direction="row" $direction="row"
> >
<Text $isMaterialIcon $theme="warning"> <Icon iconName="warning" $theme="warning" />
warning
</Text>
{t('Warning')} {t('Warning')}
</Text> </Text>
} }

View File

@@ -2,7 +2,7 @@ import { insertOrUpdateBlock } from '@blocknote/core';
import { createReactBlockSpec } from '@blocknote/react'; import { createReactBlockSpec } from '@blocknote/react';
import { TFunction } from 'i18next'; import { TFunction } from 'i18next';
import { Box, Text } from '@/components'; import { Box, Icon } from '@/components';
import { useCunninghamTheme } from '@/cunningham'; import { useCunninghamTheme } from '@/cunningham';
import { DocsBlockNoteEditor } from '../../types'; import { DocsBlockNoteEditor } from '../../types';
@@ -45,11 +45,7 @@ export const getDividerReactSlashMenuItems = (
}, },
aliases: ['divider', 'hr', 'horizontal rule', 'line', 'separator'], aliases: ['divider', 'hr', 'horizontal rule', 'line', 'separator'],
group, group,
icon: ( icon: <Icon iconName="remove" $size="18px" />,
<Text $isMaterialIcon $size="18px">
remove
</Text>
),
subtext: t('Add a horizontal line'), subtext: t('Add a horizontal line'),
}, },
]; ];

View File

@@ -1,9 +1,8 @@
import { defaultProps, insertOrUpdateBlock } from '@blocknote/core'; import { defaultProps, insertOrUpdateBlock } from '@blocknote/core';
import { BlockTypeSelectItem, createReactBlockSpec } from '@blocknote/react'; import { BlockTypeSelectItem, createReactBlockSpec } from '@blocknote/react';
import { TFunction } from 'i18next'; import { TFunction } from 'i18next';
import React from 'react';
import { Box, Text } from '@/components'; import { Box, Icon } from '@/components';
import { useCunninghamTheme } from '@/cunningham'; import { useCunninghamTheme } from '@/cunningham';
import { DocsBlockNoteEditor } from '../../types'; import { DocsBlockNoteEditor } from '../../types';
@@ -54,11 +53,7 @@ export const getQuoteReactSlashMenuItems = (
}, },
aliases: ['quote', 'blockquote', 'citation'], aliases: ['quote', 'blockquote', 'citation'],
group, group,
icon: ( icon: <Icon iconName="format_quote" $size="18px" />,
<Text $isMaterialIcon $size="18px">
format_quote
</Text>
),
subtext: t('Add a quote block'), subtext: t('Add a quote block'),
}, },
]; ];
@@ -68,10 +63,6 @@ export const getQuoteFormattingToolbarItems = (
): BlockTypeSelectItem => ({ ): BlockTypeSelectItem => ({
name: t('Quote'), name: t('Quote'),
type: 'quote', type: 'quote',
icon: () => ( icon: () => <Icon iconName="format_quote" $size="16px" />,
<Text $isMaterialIcon $size="16px">
format_quote
</Text>
),
isSelected: (block) => block.type === 'quote', isSelected: (block) => block.type === 'quote',
}); });

View File

@@ -3,7 +3,14 @@ import { DateTime } from 'luxon';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { APIError } from '@/api'; import { APIError } from '@/api';
import { Box, BoxButton, InfiniteScroll, Text, TextErrors } from '@/components'; import {
Box,
BoxButton,
Icon,
InfiniteScroll,
Text,
TextErrors,
} from '@/components';
import { Doc } from '@/docs/doc-management'; import { Doc } from '@/docs/doc-management';
import { useDate } from '@/hook'; import { useDate } from '@/hook';
@@ -68,9 +75,7 @@ const VersionListState = ({
causes={error.cause} causes={error.cause}
icon={ icon={
error.status === 502 ? ( error.status === 502 ? (
<Text $isMaterialIcon $theme="danger"> <Icon iconName="wifi_off" $theme="danger" />
wifi_off
</Text>
) : undefined ) : undefined
} }
/> />

View File

@@ -76,11 +76,7 @@ export default function HomeBanner() {
) : ( ) : (
<Button <Button
onClick={() => gotoLogin()} onClick={() => gotoLogin()}
icon={ icon={<Icon iconName="bolt" $color="white" />}
<Text $isMaterialIcon $color="white">
bolt
</Text>
}
> >
{t('Start Writing')} {t('Start Writing')}
</Button> </Button>

View File

@@ -2,7 +2,7 @@ import { Button } from '@openfun/cunningham-react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { css } from 'styled-components'; import { css } from 'styled-components';
import { Box, Text } from '@/components'; import { Box, Icon, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham'; import { useCunninghamTheme } from '@/cunningham';
import { Footer } from '@/features/footer'; import { Footer } from '@/features/footer';
import { LeftPanel } from '@/features/left-panel'; import { LeftPanel } from '@/features/left-panel';
@@ -155,11 +155,7 @@ export function HomeContent() {
$margin={{ top: 'small' }} $margin={{ top: 'small' }}
> >
<Button <Button
icon={ icon={<Icon iconName="chat" $color="white" />}
<Text $isMaterialIcon $color="white">
chat
</Text>
}
href="https://matrix.to/#/#docs-official:matrix.org" href="https://matrix.to/#/#docs-official:matrix.org"
target="_blank" target="_blank"
> >

View File

@@ -3,7 +3,7 @@ import { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { css } from 'styled-components'; import { css } from 'styled-components';
import { DropdownMenu, Text } from '@/components/'; import { DropdownMenu, Icon, Text } from '@/components/';
import { useConfig } from '@/core'; import { useConfig } from '@/core';
import { useLanguageSynchronizer } from './hooks/useLanguageSynchronizer'; import { useLanguageSynchronizer } from './hooks/useLanguageSynchronizer';
@@ -72,9 +72,7 @@ export const LanguagePicker = () => {
$gap="0.5rem" $gap="0.5rem"
className="--docs--language-picker-text" className="--docs--language-picker-text"
> >
<Text $isMaterialIcon $color="inherit" $size="xl"> <Icon iconName="translate" $color="inherit" $size="xl" />
translate
</Text>
{currentLanguageLabel} {currentLanguageLabel}
</Text> </Text>
</DropdownMenu> </DropdownMenu>

View File

@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import styled from 'styled-components'; import styled from 'styled-components';
import img403 from '@/assets/icons/icon-403.png'; import img403 from '@/assets/icons/icon-403.png';
import { Box, StyledLink, Text } from '@/components'; import { Box, Icon, StyledLink, Text } from '@/components';
import { PageLayout } from '@/layouts'; import { PageLayout } from '@/layouts';
import { NextPageWithLayout } from '@/types/next'; import { NextPageWithLayout } from '@/types/next';
@@ -38,13 +38,7 @@ const Page: NextPageWithLayout = () => {
</Text> </Text>
<StyledLink href="/"> <StyledLink href="/">
<StyledButton <StyledButton icon={<Icon iconName="house" $color="white" />}>
icon={
<Text $isMaterialIcon $color="white">
house
</Text>
}
>
{t('Home')} {t('Home')}
</StyledButton> </StyledButton>
</StyledLink> </StyledLink>

View File

@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import styled from 'styled-components'; import styled from 'styled-components';
import Icon404 from '@/assets/icons/icon-404.svg'; import Icon404 from '@/assets/icons/icon-404.svg';
import { Box, StyledLink, Text } from '@/components'; import { Box, Icon, StyledLink, Text } from '@/components';
import { PageLayout } from '@/layouts'; import { PageLayout } from '@/layouts';
import { NextPageWithLayout } from '@/types/next'; import { NextPageWithLayout } from '@/types/next';
@@ -33,13 +33,7 @@ const Page: NextPageWithLayout = () => {
<Box $margin={{ top: 'large' }}> <Box $margin={{ top: 'large' }}>
<StyledLink href="/"> <StyledLink href="/">
<StyledButton <StyledButton icon={<Icon iconName="house" $color="white" />}>
icon={
<Text $isMaterialIcon $color="white">
house
</Text>
}
>
{t('Home')} {t('Home')}
</StyledButton> </StyledButton>
</StyledLink> </StyledLink>

View File

@@ -5,7 +5,7 @@ import { useRouter } from 'next/router';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Box, Text, TextErrors } from '@/components'; import { Box, Icon, TextErrors } from '@/components';
import { DocEditor } from '@/docs/doc-editor'; import { DocEditor } from '@/docs/doc-editor';
import { import {
Doc, Doc,
@@ -126,9 +126,7 @@ const DocPage = ({ id }: DocProps) => {
causes={error.cause} causes={error.cause}
icon={ icon={
error.status === 502 ? ( error.status === 502 ? (
<Text $isMaterialIcon $theme="danger"> <Icon iconName="wifi_off" $theme="danger" />
wifi_off
</Text>
) : undefined ) : undefined
} }
/> />

View File

@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import styled from 'styled-components'; import styled from 'styled-components';
import Icon404 from '@/assets/icons/icon-404.svg'; import Icon404 from '@/assets/icons/icon-404.svg';
import { Box, StyledLink, Text } from '@/components'; import { Box, Icon, StyledLink, Text } from '@/components';
import { MainLayout } from '@/layouts'; import { MainLayout } from '@/layouts';
import { NextPageWithLayout } from '@/types/next'; import { NextPageWithLayout } from '@/types/next';
@@ -31,13 +31,7 @@ const Page: NextPageWithLayout = () => {
<Box $margin={{ top: 'large' }}> <Box $margin={{ top: 'large' }}>
<StyledLink href="/"> <StyledLink href="/">
<StyledButton <StyledButton icon={<Icon iconName="house" $color="white" />}>
icon={
<Text $isMaterialIcon $color="white">
house
</Text>
}
>
{t('Home')} {t('Home')}
</StyledButton> </StyledButton>
</StyledLink> </StyledLink>