♻️(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",
"@tanstack/react-query": "5.67.1",
"canvg": "4.0.3",
"clsx": "2.1.1",
"cmdk": "1.0.4",
"crisp-sdk-web": "1.0.25",
"docx": "9.1.1",

View File

@@ -1,42 +1,24 @@
import clsx from 'clsx';
import { css } from 'styled-components';
import { Text, TextType } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
type IconProps = TextType & {
iconName: string;
variant?: 'filled' | 'outlined';
};
export const Icon = ({ iconName, ...textProps }: IconProps) => {
return (
<Text $isMaterialIcon {...textProps}>
{iconName}
</Text>
);
};
interface IconBGProps extends TextType {
iconName: string;
}
export const IconBG = ({ iconName, ...textProps }: IconBGProps) => {
const { colorsTokens } = useCunninghamTheme();
export const Icon = ({
iconName,
variant = 'outlined',
...textProps
}: IconProps) => {
return (
<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}
className={`--docs--icon-bg ${textProps.className || ''}`}
className={clsx('--docs--icon-bg', textProps.className, {
'material-icons-filled': variant === 'filled',
'material-icons': variant === 'outlined',
})}
>
{iconName}
</Text>
@@ -49,15 +31,13 @@ type IconOptionsProps = TextType & {
export const IconOptions = ({ isHorizontal, ...props }: IconOptionsProps) => {
return (
<Text
<Icon
{...props}
$isMaterialIcon
iconName={isHorizontal ? 'more_horiz' : 'more_vert'}
$css={css`
user-select: none;
${props.$css}
`}
>
{isHorizontal ? 'more_horiz' : 'more_vert'}
</Text>
/>
);
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,14 @@ import { DateTime } from 'luxon';
import { useTranslation } from 'react-i18next';
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 { useDate } from '@/hook';
@@ -68,9 +75,7 @@ const VersionListState = ({
causes={error.cause}
icon={
error.status === 502 ? (
<Text $isMaterialIcon $theme="danger">
wifi_off
</Text>
<Icon iconName="wifi_off" $theme="danger" />
) : undefined
}
/>

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
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 { NextPageWithLayout } from '@/types/next';
@@ -38,13 +38,7 @@ const Page: NextPageWithLayout = () => {
</Text>
<StyledLink href="/">
<StyledButton
icon={
<Text $isMaterialIcon $color="white">
house
</Text>
}
>
<StyledButton icon={<Icon iconName="house" $color="white" />}>
{t('Home')}
</StyledButton>
</StyledLink>

View File

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

View File

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

View File

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