feat(frontend): replace forkawesome with bootstrap icons

These icon replace fork awesome. A linter informs the user about the deprecation.

See https://github.com/hedgedoc/hedgedoc/issues/2929

Co-authored-by: Philip Molares <philip.molares@udo.edu>
Co-authored-by: Tilman Vatteroth <git@tilmanvatteroth.de>
Signed-off-by: Philip Molares <philip.molares@udo.edu>
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Philip Molares 2023-02-05 22:05:02 +01:00 committed by Tilman Vatteroth
parent e7246f1484
commit 1c16e25e14
179 changed files with 4974 additions and 1943 deletions

View file

@ -5,9 +5,7 @@ exports[`Async loading boundary shows a waiting spinner if loading 1`] = `
<div
class="m-3 d-flex align-items-center justify-content-center"
>
<i
class="fa fa-spinner fa-spin "
/>
BootstrapIconMock_ArrowRepeat
</div>
</div>
`;

View file

@ -7,9 +7,7 @@ exports[`Copy to clipboard button show an error text if clipboard api isn't avai
title="renderer.highlightCode.copyCode"
type="button"
>
<i
class="fa fa-files-o "
/>
BootstrapIconMock_Files
</button>
</div>
`;
@ -22,9 +20,7 @@ exports[`Copy to clipboard button show an error text if clipboard api isn't avai
title="renderer.highlightCode.copyCode"
type="button"
>
<i
class="fa fa-files-o "
/>
BootstrapIconMock_Files
</button>
</div>
`;
@ -36,9 +32,7 @@ exports[`Copy to clipboard button shows an error text if writing failed 1`] = `
title="renderer.highlightCode.copyCode"
type="button"
>
<i
class="fa fa-files-o "
/>
BootstrapIconMock_Files
</button>
</div>
`;
@ -51,9 +45,7 @@ exports[`Copy to clipboard button shows an error text if writing failed 2`] = `
title="renderer.highlightCode.copyCode"
type="button"
>
<i
class="fa fa-files-o "
/>
BootstrapIconMock_Files
</button>
</div>
`;
@ -65,9 +57,7 @@ exports[`Copy to clipboard button shows an success text if writing succeeded 1`]
title="renderer.highlightCode.copyCode"
type="button"
>
<i
class="fa fa-files-o "
/>
BootstrapIconMock_Files
</button>
</div>
`;
@ -80,9 +70,7 @@ exports[`Copy to clipboard button shows an success text if writing succeeded 2`]
title="renderer.highlightCode.copyCode"
type="button"
>
<i
class="fa fa-files-o "
/>
BootstrapIconMock_Files
</button>
</div>
`;

View file

@ -5,10 +5,11 @@
*/
import type { PropsWithDataCypressId } from '../../../../utils/cypress-attribute'
import { cypressId } from '../../../../utils/cypress-attribute'
import { ForkAwesomeIcon } from '../../fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../icons/ui-icon'
import { useCopyOverlay } from '../hooks/use-copy-overlay'
import React, { Fragment, useRef } from 'react'
import { Button } from 'react-bootstrap'
import { Files as IconFiles } from 'react-bootstrap-icons'
import type { Variant } from 'react-bootstrap/types'
import { useTranslation } from 'react-i18next'
@ -46,7 +47,7 @@ export const CopyToClipboardButton: React.FC<CopyToClipboardButtonProps> = ({
title={t('renderer.highlightCode.copyCode') ?? undefined}
onClick={copyToClipboard}
{...cypressId(props)}>
<ForkAwesomeIcon icon='files-o' />
<UiIcon icon={IconFiles} />
</Button>
{overlayElement}
</Fragment>

View file

@ -5,11 +5,12 @@
*/
import { isClientSideRendering } from '../../../../utils/is-client-side-rendering'
import { Logger } from '../../../../utils/logger'
import { ForkAwesomeIcon } from '../../fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../icons/ui-icon'
import { ShowIf } from '../../show-if/show-if'
import { CopyToClipboardButton } from '../copy-to-clipboard-button/copy-to-clipboard-button'
import React, { useCallback, useMemo } from 'react'
import { Button, FormControl, InputGroup } from 'react-bootstrap'
import { Share as IconShare } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
export interface CopyableFieldProps {
@ -57,7 +58,7 @@ export const CopyableField: React.FC<CopyableFieldProps> = ({ content, shareOrig
<ShowIf condition={sharingSupported}>
<InputGroup.Text>
<Button variant='secondary' title={'Share'} onClick={doShareAction}>
<ForkAwesomeIcon icon='share-alt' />
<UiIcon icon={IconShare} />
</Button>
</InputGroup.Text>
</ShowIf>

View file

@ -1,65 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[` 1`] = `
<div>
<i
class="fa fa-heart fa-stack-1x"
/>
</div>
`;
exports[`ForkAwesomeIcon renders a heart correctly 1`] = `
<div>
<i
class="fa fa-heart "
/>
</div>
`;
exports[`ForkAwesomeIcon renders in size 2x 1`] = `
<div>
<i
class="fa fa-heart fa-2x"
/>
</div>
`;
exports[`ForkAwesomeIcon renders in size 3x 1`] = `
<div>
<i
class="fa fa-heart fa-3x"
/>
</div>
`;
exports[`ForkAwesomeIcon renders in size 4x 1`] = `
<div>
<i
class="fa fa-heart fa-4x"
/>
</div>
`;
exports[`ForkAwesomeIcon renders in size 5x 1`] = `
<div>
<i
class="fa fa-heart fa-5x"
/>
</div>
`;
exports[`ForkAwesomeIcon renders with additional className 1`] = `
<div>
<i
class="fa fa-heart testClass "
/>
</div>
`;
exports[`ForkAwesomeIcon renders with fixed width icon 1`] = `
<div>
<i
class="fa fa-fw fa-heart "
/>
</div>
`;

View file

@ -1,76 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ForkAwesomeStack renders a heart and a download icon stack 1`] = `
<div>
<span
class="fa-stack "
>
<i
class="fa fa-heart fa-stack-1x"
/>
<i
class="fa fa-download fa-stack-1x"
/>
</span>
</div>
`;
exports[`ForkAwesomeStack renders in size 2x 1`] = `
<div>
<span
class="fa-stack fa-2x"
>
<i
class="fa fa-heart fa-stack-1x"
/>
<i
class="fa fa-download fa-stack-1x"
/>
</span>
</div>
`;
exports[`ForkAwesomeStack renders in size 3x 1`] = `
<div>
<span
class="fa-stack fa-3x"
>
<i
class="fa fa-heart fa-stack-1x"
/>
<i
class="fa fa-download fa-stack-1x"
/>
</span>
</div>
`;
exports[`ForkAwesomeStack renders in size 4x 1`] = `
<div>
<span
class="fa-stack fa-4x"
>
<i
class="fa fa-heart fa-stack-1x"
/>
<i
class="fa fa-download fa-stack-1x"
/>
</span>
</div>
`;
exports[`ForkAwesomeStack renders in size 5x 1`] = `
<div>
<span
class="fa-stack fa-5x"
>
<i
class="fa fa-heart fa-stack-1x"
/>
<i
class="fa fa-download fa-stack-1x"
/>
</span>
</div>
`;

View file

@ -1,46 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ForkAwesomeIcon } from './fork-awesome-icon'
import type { IconName } from './types'
import { render } from '@testing-library/react'
describe('ForkAwesomeIcon', () => {
const icon: IconName = 'heart'
it('renders a heart correctly', () => {
const view = render(<ForkAwesomeIcon icon={icon} />)
expect(view.container).toMatchSnapshot()
})
it('renders with fixed width icon', () => {
const view = render(<ForkAwesomeIcon icon={icon} fixedWidth={true} />)
expect(view.container).toMatchSnapshot()
})
it('renders with additional className', () => {
const view = render(<ForkAwesomeIcon icon={icon} className={'testClass'} />)
expect(view.container).toMatchSnapshot()
})
describe('renders in size', () => {
it('2x', () => {
const view = render(<ForkAwesomeIcon icon={icon} size={'2x'} />)
expect(view.container).toMatchSnapshot()
})
it('3x', () => {
const view = render(<ForkAwesomeIcon icon={icon} size={'3x'} />)
expect(view.container).toMatchSnapshot()
})
it('4x', () => {
const view = render(<ForkAwesomeIcon icon={icon} size={'4x'} />)
expect(view.container).toMatchSnapshot()
})
it('5x', () => {
const view = render(<ForkAwesomeIcon icon={icon} size={'5x'} />)
expect(view.container).toMatchSnapshot()
})
})
describe('renders in stack', () => {
const view = render(<ForkAwesomeIcon icon={icon} stacked={true} />)
expect(view.container).toMatchSnapshot()
})
})

View file

@ -1,39 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { IconName, IconSize } from './types'
import React from 'react'
export interface ForkAwesomeIconProps {
icon: IconName
className?: string
fixedWidth?: boolean
size?: IconSize
stacked?: boolean
}
/**
* Renders a fork awesome icon.
*
* @param icon The icon that should be rendered.
* @param fixedWidth If the icon should be rendered with a fixed width.
* @param size The size class the icon should be rendered in.
* @param className Additional classes the icon should get.
* @param stacked If the icon is part of a {@link ForkAwesomeStack stack}.
* @see https://forkaweso.me
*/
export const ForkAwesomeIcon: React.FC<ForkAwesomeIconProps> = ({
icon,
fixedWidth = false,
size,
className,
stacked = false
}) => {
const fixedWithClass = fixedWidth ? 'fa-fw' : ''
const sizeClass = size ? `-${size}` : stacked ? '-1x' : ''
const stackClass = stacked ? '-stack' : ''
const extraClasses = `${className ?? ''} ${sizeClass || stackClass ? `fa${stackClass}${sizeClass}` : ''}`
return <i className={`fa ${fixedWithClass} fa-${icon} ${extraClasses}`} />
}

View file

@ -1,804 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const ForkAwesomeIcons = [
'500px',
'activitypub',
'address-book-o',
'address-book',
'address-card-o',
'address-card',
'adjust',
'adn',
'align-center',
'align-justify',
'align-left',
'align-right',
'amazon',
'ambulance',
'american-sign-language-interpreting',
'anchor',
'android',
'angellist',
'angle-double-down',
'angle-double-left',
'angle-double-right',
'angle-double-up',
'angle-down',
'angle-left',
'angle-right',
'angle-up',
'apple',
'archive-org',
'archive',
'archlinux',
'area-chart',
'arrow-circle-down',
'arrow-circle-left',
'arrow-circle-o-down',
'arrow-circle-o-left',
'arrow-circle-o-right',
'arrow-circle-o-up',
'arrow-circle-right',
'arrow-circle-up',
'arrow-down',
'arrow-left',
'arrow-right',
'arrows-alt',
'arrows-h',
'arrows',
'arrows-v',
'arrow-up',
'artstation',
'askfm',
'assistive-listening-systems',
'asterisk',
'at',
'att',
'audio-description',
'backward',
'balance-scale',
'bandcamp',
'ban',
'bar-chart',
'barcode',
'bars',
'bath',
'battery-empty',
'battery-full',
'battery-half',
'battery-quarter',
'battery-three-quarters',
'bed',
'beer',
'behance-square',
'behance',
'bell-o',
'bell-rigning-o',
'bell-ringing',
'bell-slash-o',
'bell-slash',
'bell',
'bicycle',
'binoculars',
'biometric',
'birthday-cake',
'bitbucket-square',
'bitbucket',
'black-tie',
'blind',
'blockstack',
'bluetooth-b',
'bluetooth',
'boardgamegeek',
'bold',
'bolt',
'bomb',
'bookmark-o',
'bookmark',
'book',
'bootstrap',
'braille',
'briefcase',
'btc',
'bug',
'building-o',
'building',
'bullhorn',
'bullseye',
'bunny',
'bus',
'buymeacoffee',
'buysellads',
'calculator',
'calendar-check-o',
'calendar-minus-o',
'calendar-o',
'calendar-plus-o',
'calendar',
'calendar-times-o',
'camera-retro',
'camera',
'caret-down',
'caret-left',
'caret-right',
'caret-square-o-down',
'caret-square-o-left',
'caret-square-o-right',
'caret-square-o-up',
'caret-up',
'car',
'cart-arrow-down',
'cart-plus',
'cc-amex',
'cc-by',
'cc-cc',
'cc-diners-club',
'cc-discover',
'cc-jcb',
'cc-mastercard',
'cc-nc-eu',
'cc-nc-jp',
'cc-nc',
'cc-nd',
'cc-paypal',
'cc-pd',
'cc-remix',
'cc-sa',
'cc-share',
'cc-stripe',
'cc',
'cc-visa',
'cc-zero',
'certificate',
'chain-broken',
'check-circle-o',
'check-circle',
'check-square-o',
'check-square',
'check',
'chevron-circle-down',
'chevron-circle-left',
'chevron-circle-right',
'chevron-circle-up',
'chevron-down',
'chevron-left',
'chevron-right',
'chevron-up',
'child',
'chrome',
'circle-o-notch',
'circle-o',
'circle',
'circle-thin',
'classicpress-circle',
'classicpress',
'clipboard',
'clock-o',
'clone',
'cloud-download',
'cloud',
'cloud-upload',
'code-fork',
'codepen',
'code',
'codiepie',
'coffee',
'cogs',
'cog',
'columns',
'commenting-o',
'commenting',
'comment-o',
'comments-o',
'comments',
'comment',
'compass',
'compress',
'connectdevelop',
'contao',
'conway-glider',
'copyright',
'creative-commons',
'credit-card-alt',
'credit-card',
'crop',
'crosshairs',
'csharp',
'css3',
'c',
'cubes',
'cube',
'cutlery',
'dashcube',
'database',
'deaf',
'debian',
'delicious',
'desktop',
'deviantart',
'dev-to',
'diamond',
'diaspora',
'digg',
'digitalocean',
'discord-alt',
'discord',
'dogmazic',
'dot-circle-o',
'download',
'dribbble',
'dropbox',
'drupal',
'edge',
'eercast',
'eject',
'ellipsis-h',
'ellipsis-v',
'email-bulk-o',
'email-bulk',
'emby',
'empire',
'envelope-open-o',
'envelope-open',
'envelope-o',
'envelope-square',
'envelope',
'envira',
'eraser',
'ethereum',
'etsy',
'eur',
'exchange',
'exclamation-circle',
'exclamation',
'exclamation-triangle',
'expand',
'expeditedssl',
'external-link-square',
'external-link',
'eyedropper',
'eye-slash',
'eye',
'facebook-messenger',
'facebook-official',
'facebook-square',
'facebook',
'fast-backward',
'fast-forward',
'fax',
'f-droid',
'female',
'ffmpeg',
'fighter-jet',
'file-archive-o',
'file-audio-o',
'file-code-o',
'file-epub',
'file-excel-o',
'file-image-o',
'file-o',
'file-pdf-o',
'file-powerpoint-o',
'files-o',
'file',
'file-text-o',
'file-text',
'file-video-o',
'file-word-o',
'film',
'filter',
'fire-extinguisher',
'firefox',
'fire',
'first-order',
'flag-checkered',
'flag-o',
'flag',
'flask',
'flickr',
'floppy-o',
'folder-open-o',
'folder-open',
'folder-o',
'folder',
'font-awesome',
'fonticons',
'font',
'fork-awesome',
'fort-awesome',
'forumbee',
'forward',
'foursquare',
'free-code-camp',
'freedombox',
'friendica',
'frown-o',
'funkwhale',
'futbol-o',
'gamepad',
'gavel',
'gbp',
'genderless',
'get-pocket',
'gg-circle',
'gg',
'gift',
'gimp',
'gitea',
'github-alt',
'github-square',
'github',
'gitlab',
'git-square',
'git',
'glass',
'glide-g',
'glide',
'globe-e',
'globe',
'globe-w',
'gnupg',
'gnu-social',
'gnu',
'google-play',
'google-plus-official',
'google-plus-square',
'google-plus',
'google',
'google-wallet',
'graduation-cap',
'gratipay',
'grav',
'hackaday',
'hacker-news',
'hackster',
'hal',
'hand-lizard-o',
'hand-o-down',
'hand-o-left',
'hand-o-right',
'hand-o-up',
'hand-paper-o',
'hand-peace-o',
'hand-pointer-o',
'hand-rock-o',
'hand-scissors-o',
'handshake-o',
'hand-spock-o',
'hashnode',
'hashtag',
'hdd-o',
'header',
'headphones',
'heartbeat',
'heart-o',
'heart',
'heroku',
'history',
'home-assistant',
'home',
'hospital-o',
'hourglass-end',
'hourglass-half',
'hourglass-o',
'hourglass-start',
'hourglass',
'houzz',
'h-square',
'html5',
'hubzilla',
'i-cursor',
'id-badge',
'id-card-o',
'id-card',
'ils',
'imdb',
'inbox',
'indent',
'industry',
'info-circle',
'info',
'inkscape',
'inr',
'instagram',
'internet-explorer',
'ioxhost',
'italic',
'java',
'jirafeau',
'joomla',
'joplin',
'jpy',
'jsfiddle',
'julia',
'jupyter',
'keybase',
'keyboard-o',
'key-modern',
'key',
'krw',
'language',
'laptop',
'laravel',
'lastfm-square',
'lastfm',
'leaf',
'leanpub',
'lemon-o',
'level-down',
'level-up',
'liberapay-square',
'liberapay',
'life-ring',
'lightbulb-o',
'line-chart',
'linkedin-square',
'linkedin',
'link',
'linode',
'linux',
'list-alt',
'list-ol',
'list',
'list-ul',
'location-arrow',
'lock',
'long-arrow-down',
'long-arrow-left',
'long-arrow-right',
'long-arrow-up',
'low-vision',
'magic',
'magnet',
'male',
'map-marker',
'map-o',
'map-pin',
'map-signs',
'map',
'mariadb',
'markdown',
'mars-double',
'mars-stroke-h',
'mars-stroke',
'mars-stroke-v',
'mars',
'mastodon-alt',
'mastodon-square',
'mastodon',
'matrix-org',
'maxcdn',
'meanpath',
'medium-square',
'medium',
'medkit',
'meetup',
'meh-o',
'mercury',
'microchip',
'microphone-slash',
'microphone',
'minus-circle',
'minus-square-o',
'minus-square',
'minus',
'mixcloud',
'mobile',
'modx',
'money',
'moon-o',
'moon',
'motorcycle',
'mouse-pointer',
'music',
'mysql',
'neuter',
'newspaper-o',
'nextcloud-square',
'nextcloud',
'nodejs',
'nordcast',
'object-group',
'object-ungroup',
'odnoklassniki-square',
'odnoklassniki',
'opencart',
'open-collective',
'openid',
'opera',
'optin-monster',
'orcid',
'outdent',
'pagelines',
'paint-brush',
'paperclip',
'paper-plane-o',
'paper-plane',
'paragraph',
'patreon',
'pause-circle-o',
'pause-circle',
'pause',
'paw',
'paypal',
'peertube',
'pencil-square-o',
'pencil-square',
'pencil',
'percent',
'phone-square',
'phone',
'php',
'picture-o',
'pie-chart',
'pinterest-p',
'pinterest-square',
'pinterest',
'pixelfed',
'plane',
'play-circle-o',
'play-circle',
'play',
'pleroma',
'plug',
'plume',
'plus-circle',
'plus-square-o',
'plus-square',
'plus',
'podcast',
'postgresql',
'power-off',
'print',
'product-hunt',
'puzzle-piece',
'python',
'qq',
'qrcode',
'question-circle-o',
'question-circle',
'question',
'quora',
'quote-left',
'quote-right',
'random',
'ravelry',
'react',
'rebel',
'recycle',
'reddit-alien',
'reddit-square',
'reddit',
'refresh',
'registered',
'renren',
'repeat',
'reply-all',
'reply',
'researchgate',
'retweet',
'road',
'rocket',
'rss-square',
'rss',
'rub',
'safari',
'sass-alt',
'sass',
'scissors',
'scribd',
'scuttlebutt',
'search-minus',
'search-plus',
'search',
'sellsy',
'server',
'shaarli-o',
'shaarli',
'share-alt-square',
'share-alt',
'share-square-o',
'share-square',
'share',
'shield',
'ship',
'shirtsinbulk',
'shopping-bag',
'shopping-basket',
'shopping-cart',
'shower',
'signalapp',
'signal',
'sign-in',
'sign-language',
'sign-out',
'simplybuilt',
'sitemap',
'skate',
'sketchfab',
'skyatlas',
'skype',
'slack',
'sliders',
'slideshare',
'smile-o',
'snapchat-ghost',
'snapchat-square',
'snapchat',
'snowdrift',
'snowflake-o',
'social-home',
'sort-alpha-asc',
'sort-alpha-desc',
'sort-amount-asc',
'sort-amount-desc',
'sort-asc',
'sort-desc',
'sort-numeric-asc',
'sort-numeric-desc',
'sort',
'soundcloud',
'space-shuttle',
'spell-check',
'spinner',
'spoon',
'spotify',
'square-o',
'square',
'stack-exchange',
'stack-overflow',
'star-half-o',
'star-half',
'star-o',
'star',
'steam-square',
'steam',
'step-backward',
'step-forward',
'stethoscope',
'sticky-note-o',
'sticky-note',
'stop-circle-o',
'stop-circle',
'stop',
'street-view',
'strikethrough',
'stumbleupon-circle',
'stumbleupon',
'subscript',
'subway',
'suitcase',
'sun-o',
'sun',
'superpowers',
'superscript',
'syncthing',
'table',
'tablet',
'tachometer',
'tags',
'tag',
'tasks',
'taxi',
'telegram',
'television',
'tencent-weibo',
'terminal',
'tex',
'text-height',
'textpattern',
'text-width',
'themeisle',
'thermometer-empty',
'thermometer-full',
'thermometer-half',
'thermometer-quarter',
'thermometer-three-quarters',
'th-large',
'th-list',
'th',
'thumbs-down',
'thumbs-o-down',
'thumbs-o-up',
'thumbs-up',
'thumb-tack',
'ticket',
'times-circle-o',
'times-circle',
'times',
'tint',
'tipeee',
'toggle-off',
'toggle-on',
'tor-onion',
'trademark',
'train',
'transgender-alt',
'transgender',
'trash-o',
'trash',
'tree',
'trello',
'tripadvisor',
'trophy',
'truck',
'try',
'tty',
'tumblr-square',
'tumblr',
'twitch',
'twitter-square',
'twitter',
'umbrella',
'underline',
'undo',
'unity',
'universal-access',
'university',
'unlock-alt',
'unlock',
'unslpash',
'upload',
'usb',
'usd',
'user-circle-o',
'user-circle',
'user-md',
'user-o',
'user-plus',
'user-secret',
'users',
'user',
'user-times',
'venus-double',
'venus-mars',
'venus',
'viacoin',
'viadeo-square',
'viadeo',
'video-camera',
'vimeo-square',
'vimeo',
'vine',
'vk',
'volume-control-phone',
'volume-down',
'volume-mute',
'volume-off',
'volume-up',
'weibo',
'weixin',
'whatsapp',
'wheelchair-alt',
'wheelchair',
'wifi',
'wikidata',
'wikipedia-w',
'window-close-o',
'window-close',
'window-maximize',
'window-minimize',
'window-restore',
'windows',
'wire',
'wordpress',
'wpbeginner',
'wpexplorer',
'wpforms',
'wrench',
'xing-square',
'xing',
'xmpp',
'yahoo',
'y-combinator',
'yelp',
'yoast',
'youtube-play',
'youtube-square',
'youtube',
'zotero'
] as const

View file

@ -1,39 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { ForkAwesomeIconProps } from './fork-awesome-icon'
import { ForkAwesomeIcon } from './fork-awesome-icon'
import { ForkAwesomeStack } from './fork-awesome-stack'
import { render } from '@testing-library/react'
import type { ReactElement } from 'react'
describe('ForkAwesomeStack', () => {
const stack: Array<ReactElement<ForkAwesomeIconProps>> = [
<ForkAwesomeIcon icon={'heart'} key={'heart'} />,
<ForkAwesomeIcon icon={'download'} key={'download'} />
]
it('renders a heart and a download icon stack', () => {
const view = render(<ForkAwesomeStack>{stack}</ForkAwesomeStack>)
expect(view.container).toMatchSnapshot()
})
describe('renders in size', () => {
it('2x', () => {
const view = render(<ForkAwesomeStack size={'2x'}>{stack}</ForkAwesomeStack>)
expect(view.container).toMatchSnapshot()
})
it('3x', () => {
const view = render(<ForkAwesomeStack size={'3x'}>{stack}</ForkAwesomeStack>)
expect(view.container).toMatchSnapshot()
})
it('4x', () => {
const view = render(<ForkAwesomeStack size={'4x'}>{stack}</ForkAwesomeStack>)
expect(view.container).toMatchSnapshot()
})
it('5x', () => {
const view = render(<ForkAwesomeStack size={'5x'}>{stack}</ForkAwesomeStack>)
expect(view.container).toMatchSnapshot()
})
})
})

View file

@ -1,34 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { ForkAwesomeIconProps } from './fork-awesome-icon'
import { ForkAwesomeIcon } from './fork-awesome-icon'
import type { IconSize } from './types'
import type { ReactElement } from 'react'
import React from 'react'
export interface ForkAwesomeStackProps {
size?: IconSize
children: ReactElement<ForkAwesomeIconProps> | Array<ReactElement<ForkAwesomeIconProps>>
}
/**
* A stack of {@link ForkAwesomeIcon ForkAwesomeIcons}.
*
* @param size Which size the stack should have.
* @param children One or more {@link ForkAwesomeIcon ForkAwesomeIcons} to be stacked.
*/
export const ForkAwesomeStack: React.FC<ForkAwesomeStackProps> = ({ size, children }) => {
return (
<span className={`fa-stack ${size ? 'fa-' : ''}${size ?? ''}`}>
{React.Children.map(children, (child) => {
if (!React.isValidElement<ForkAwesomeIconProps>(child)) {
return null
}
return <ForkAwesomeIcon {...child.props} stacked={true} />
})}
</span>
)
}

View file

@ -1,9 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { ForkAwesomeIcons } from './fork-awesome-icons'
export type IconName = (typeof ForkAwesomeIcons)[number]
export type IconSize = '2x' | '3x' | '4x' | '5x'

View file

@ -6,8 +6,7 @@
import LogoBwHorizontal from './logo_text_bw_horizontal.svg'
import LogoColorVertical from './logo_text_color_vertical.svg'
import LogoWbHorizontal from './logo_text_wb_horizontal.svg'
import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import React from 'react'
export enum HedgeDocLogoSize {
SMALL = 32,
@ -33,17 +32,13 @@ export enum HedgeDocLogoType {
* @param logoType The logo type to be used.
*/
export const HedgeDocLogoWithText: React.FC<HedgeDocLogoProps> = ({ size = HedgeDocLogoSize.MEDIUM, logoType }) => {
const { t } = useTranslation()
const altText = useMemo(() => t('app.icon'), [t])
const style = useMemo(() => ({ height: size }), [size])
switch (logoType) {
case HedgeDocLogoType.COLOR_VERTICAL:
return <LogoColorVertical className={'w-auto'} title={altText} style={style} />
return <LogoColorVertical className={'w-auto'} height={`${size}px`} width={'auto'} />
case HedgeDocLogoType.BW_HORIZONTAL:
return <LogoBwHorizontal className={'w-auto'} title={altText} style={style} />
return <LogoBwHorizontal className={'w-auto'} height={`${size}px`} width={'auto'} />
case HedgeDocLogoType.WB_HORIZONTAL:
return <LogoWbHorizontal className={'w-auto'} title={altText} style={style} />
return <LogoWbHorizontal className={'w-auto'} height={`${size}px`} width={'auto'} />
default:
return null
}

View file

@ -10,9 +10,7 @@ exports[`IconButton correctly uses the onClick callback 1`] = `
<span
class="icon-part d-flex align-items-center"
>
<i
class="fa fa-heart icon "
/>
BootstrapIconMock_Heart
</span>
<span
class="text-part d-flex align-items-center"
@ -33,9 +31,7 @@ exports[`IconButton renders heart icon 1`] = `
<span
class="icon-part d-flex align-items-center"
>
<i
class="fa fa-heart icon "
/>
BootstrapIconMock_Heart
</span>
<span
class="text-part d-flex align-items-center"
@ -56,9 +52,7 @@ exports[`IconButton renders with additional className 1`] = `
<span
class="icon-part d-flex align-items-center"
>
<i
class="fa fa-heart icon "
/>
BootstrapIconMock_Heart
</span>
<span
class="text-part d-flex align-items-center"
@ -79,9 +73,7 @@ exports[`IconButton renders with border 1`] = `
<span
class="icon-part d-flex align-items-center"
>
<i
class="fa fa-heart icon "
/>
BootstrapIconMock_Heart
</span>
<span
class="text-part d-flex align-items-center"
@ -91,26 +83,3 @@ exports[`IconButton renders with border 1`] = `
</button>
</div>
`;
exports[`IconButton renders with fixed width icon 1`] = `
<div>
<button
class="btn-icon p-0 d-inline-flex align-items-stretch btn btn-primary"
data-testid="icon-button"
type="button"
>
<span
class="icon-part d-flex align-items-center"
>
<i
class="fa fa-fw fa-heart icon "
/>
</span>
<span
class="text-part d-flex align-items-center"
>
test with fixed with icon
</span>
</button>
</div>
`;

View file

@ -3,35 +3,26 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { IconName } from '../fork-awesome/types'
import { IconButton } from './icon-button'
import { fireEvent, render, screen } from '@testing-library/react'
import { Heart as IconHeart } from 'react-bootstrap-icons'
describe('IconButton', () => {
const icon: IconName = 'heart'
it('renders heart icon', () => {
const view = render(<IconButton icon={icon}>test</IconButton>)
const view = render(<IconButton icon={IconHeart}>test</IconButton>)
expect(view.container).toMatchSnapshot()
})
it('renders with border', () => {
const view = render(
<IconButton icon={icon} border={true}>
<IconButton icon={IconHeart} border={true}>
test with border
</IconButton>
)
expect(view.container).toMatchSnapshot()
})
it('renders with fixed width icon', () => {
const view = render(
<IconButton icon={icon} iconFixedWidth={true}>
test with fixed with icon
</IconButton>
)
expect(view.container).toMatchSnapshot()
})
it('renders with additional className', () => {
const view = render(
<IconButton icon={icon} className={'testClass'}>
<IconButton icon={IconHeart} className={'testClass'}>
test with additional className
</IconButton>
)
@ -40,7 +31,7 @@ describe('IconButton', () => {
it('correctly uses the onClick callback', async () => {
const onClick = jest.fn()
const view = render(
<IconButton icon={icon} onClick={onClick}>
<IconButton icon={IconHeart} onClick={onClick}>
test with onClick
</IconButton>
)

View file

@ -5,37 +5,38 @@
*/
import type { PropsWithDataTestId } from '../../../utils/test-id'
import { testId } from '../../../utils/test-id'
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
import type { IconName } from '../fork-awesome/types'
import { UiIcon } from '../icons/ui-icon'
import { ShowIf } from '../show-if/show-if'
import styles from './icon-button.module.scss'
import React from 'react'
import type { ButtonProps } from 'react-bootstrap'
import { Button } from 'react-bootstrap'
import type { Icon } from 'react-bootstrap-icons'
export interface IconButtonProps extends ButtonProps, PropsWithDataTestId {
icon: IconName
icon: Icon
onClick?: () => void
border?: boolean
iconFixedWidth?: boolean
iconSize?: number | string
}
/**
* A generic {@link Button button} with an {@link ForkAwesomeIcon icon} in it.
* A generic {@link Button button} with an icon in it.
*
* @param icon Which icon should be used
* @param children The children that will be added as the content of the button.
* @param iconFixedWidth If the icon should be of fixed width.
* @param border Should the button have a border.
* @param className Additional class names added to the button.
* @param iconSize Size of the icon
* @param props Additional props for the button.
*/
export const IconButton: React.FC<IconButtonProps> = ({
icon,
children,
iconFixedWidth = false,
border = false,
className,
iconSize,
...props
}) => {
return (
@ -46,7 +47,7 @@ export const IconButton: React.FC<IconButtonProps> = ({
}`}
{...testId('icon-button')}>
<span className={`${styles['icon-part']} d-flex align-items-center`}>
<ForkAwesomeIcon icon={icon} fixedWidth={iconFixedWidth} className={'icon'} />
<UiIcon size={iconSize} icon={icon} className={'icon'} />
</span>
<ShowIf condition={!!children}>
<span className={`${styles['text-part']} d-flex align-items-center`}>{children}</span>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { testId } from '../../../utils/test-id'
import { BootstrapLazyIcons } from './bootstrap-icons'
import type { BootstrapIconName } from './bootstrap-icons'
import React, { Suspense, useMemo } from 'react'
export interface LazyBootstrapIconProps {
icon: BootstrapIconName
size?: number
className?: string
}
/**
* Renders a bootstrap icon.
*
* @param iconName the internal name of the icon
* @param size the size of the icon - the default is 1
* @see https://icons.getbootstrap.com/
*/
export const LazyBootstrapIcon: React.FC<LazyBootstrapIconProps> = ({ icon, size, className }) => {
const fullSize = useMemo(() => `${size ?? 1}em`, [size])
const Icon = BootstrapLazyIcons[icon]
return (
<Suspense fallback={<></>}>
<Icon
width={fullSize}
height={fullSize}
fill={'currentColor'}
className={className}
{...testId(`lazy-bootstrap-icon-${icon}`)}></Icon>
</Suspense>
)
}

View file

@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import styles from './ui-icons.module.scss'
import React, { Fragment, useMemo } from 'react'
import type { Icon } from 'react-bootstrap-icons'
export interface UiIconProps {
icon: Icon | undefined
nbsp?: boolean
size?: number | string
className?: string
spin?: boolean
}
export const UiIcon: React.FC<UiIconProps> = ({ icon, nbsp, className, size, spin }) => {
const finalSize = useMemo(() => {
if (size === undefined) {
return '1em'
} else if (typeof size === 'number') {
return `${size}em`
} else {
return size
}
}, [size])
const finalClassName = useMemo(() => {
return `${spin ? styles.spin : ''} ${className ?? ''}`
}, [className, spin])
if (icon) {
return (
<Fragment>
{React.createElement(icon, {
className: finalClassName,
width: finalSize,
height: finalSize
})}
{nbsp ? <Fragment>&nbsp;</Fragment> : null}
</Fragment>
)
} else {
return null
}
}

View file

@ -0,0 +1,20 @@
/*!
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
.spin {
transform-origin: center center;
@keyframes rotation {
0% {
transform: rotateZ(0deg);
}
100% {
transform: rotateZ(360deg);
}
}
animation: rotation linear 2s infinite;
}

View file

@ -52,9 +52,7 @@ exports[`ExternalLink renders an external link with an icon 1`] = `
rel="noopener noreferrer"
target="_blank"
>
<i
class="fa fa-fw fa-heart "
/>
BootstrapIconMock_Heart
 
testText
</a>

View file

@ -40,9 +40,7 @@ exports[`InternalLink renders an internal link with an icon 1`] = `
class="text-light"
href="/test"
>
<i
class="fa fa-fw fa-heart "
/>
BootstrapIconMock_Heart
 
testText
</a>

View file

@ -5,6 +5,7 @@
*/
import { ExternalLink } from './external-link'
import { render } from '@testing-library/react'
import { Heart as IconHeart } from 'react-bootstrap-icons'
describe('ExternalLink', () => {
const href = 'https://example.com'
@ -14,7 +15,7 @@ describe('ExternalLink', () => {
expect(view.container).toMatchSnapshot()
})
it('renders an external link with an icon', () => {
const view = render(<ExternalLink text={text} href={href} icon={'heart'} />)
const view = render(<ExternalLink text={text} href={href} icon={IconHeart} />)
expect(view.container).toMatchSnapshot()
})
it('renders an external link with an id', () => {

View file

@ -3,9 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
import type { IconName } from '../fork-awesome/types'
import { ShowIf } from '../show-if/show-if'
import { UiIcon } from '../icons/ui-icon'
import type { LinkWithTextProps } from './types'
import React from 'react'
@ -31,10 +29,7 @@ export const ExternalLink: React.FC<LinkWithTextProps> = ({
}) => {
return (
<a href={href} target='_blank' rel='noopener noreferrer' id={id} className={className} title={title} dir='auto'>
<ShowIf condition={!!icon}>
<ForkAwesomeIcon icon={icon as IconName} fixedWidth={true} />
&nbsp;
</ShowIf>
<UiIcon icon={icon} nbsp={true} />
{text}
</a>
)

View file

@ -5,6 +5,7 @@
*/
import { InternalLink } from './internal-link'
import { render } from '@testing-library/react'
import { Heart as IconHeart } from 'react-bootstrap-icons'
describe('InternalLink', () => {
const href = '/test'
@ -14,7 +15,7 @@ describe('InternalLink', () => {
expect(view.container).toMatchSnapshot()
})
it('renders an internal link with an icon', () => {
const view = render(<InternalLink text={text} href={href} icon={'heart'} />)
const view = render(<InternalLink text={text} href={href} icon={IconHeart} />)
expect(view.container).toMatchSnapshot()
})
it('renders an internal link with an id', () => {

View file

@ -3,9 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
import type { IconName } from '../fork-awesome/types'
import { ShowIf } from '../show-if/show-if'
import { UiIcon } from '../icons/ui-icon'
import type { LinkWithTextProps } from './types'
import Link from 'next/link'
import React from 'react'
@ -31,10 +29,7 @@ export const InternalLink: React.FC<LinkWithTextProps> = ({
}) => {
return (
<Link href={href} className={className} id={id} title={title}>
<ShowIf condition={!!icon}>
<ForkAwesomeIcon icon={icon as IconName} fixedWidth={true} />
&nbsp;
</ShowIf>
<UiIcon icon={icon} nbsp={true} />
{text}
</Link>
)

View file

@ -3,12 +3,12 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { IconName } from '../fork-awesome/fork-awesome-icon'
import type { TOptionsBase } from 'i18next'
import type { Icon } from 'react-bootstrap-icons'
interface GeneralLinkProp {
href: string
icon?: IconName
icon?: Icon
id?: string
className?: string
title?: string

View file

@ -1,29 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
import React from 'react'
import { Button } from 'react-bootstrap'
export interface LockButtonProps {
locked: boolean
onLockedChanged: (newState: boolean) => void
title: string
}
/**
* A button with a lock icon.
*
* @param locked If the button should be shown as locked or not
* @param onLockedChanged The callback to change the state from locked to unlocked and vise-versa.
* @param title The title for the button.
*/
export const LockButton: React.FC<LockButtonProps> = ({ locked, onLockedChanged, title }) => {
return (
<Button variant='dark' size='sm' onClick={() => onLockedChanged(!locked)} title={title}>
{locked ? <ForkAwesomeIcon icon='lock' /> : <ForkAwesomeIcon icon='unlock' />}
</Button>
)
}

View file

@ -148,9 +148,7 @@ exports[`CommonModal render correctly with title icon 1`] = `
<div
class="modal-title h4"
>
<i
class="fa fa-heart "
/>
BootstrapIconMock_Heart
 
<span />
</div>

View file

@ -7,6 +7,7 @@ import { mockI18n } from '../../markdown-renderer/test-utils/mock-i18n'
import { CommonModal } from './common-modal'
import { fireEvent, render, screen } from '@testing-library/react'
import React from 'react'
import { Heart as IconHeart } from 'react-bootstrap-icons'
describe('CommonModal', () => {
afterAll(() => {
@ -65,7 +66,7 @@ describe('CommonModal', () => {
it('render correctly with title icon', async () => {
render(
<CommonModal show={true} titleIcon={'heart'}>
<CommonModal show={true} titleIcon={IconHeart}>
testText
</CommonModal>
)

View file

@ -6,12 +6,12 @@
import type { PropsWithDataCypressId } from '../../../utils/cypress-attribute'
import { cypressId } from '../../../utils/cypress-attribute'
import { testId } from '../../../utils/test-id'
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
import type { IconName } from '../fork-awesome/types'
import { UiIcon } from '../icons/ui-icon'
import { ShowIf } from '../show-if/show-if'
import type { PropsWithChildren } from 'react'
import React, { useMemo } from 'react'
import { Modal } from 'react-bootstrap'
import type { Icon } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
export interface ModalVisibilityProps {
@ -23,7 +23,7 @@ export interface ModalContentProps {
titleI18nKey?: string
title?: string
showCloseButton?: boolean
titleIcon?: IconName
titleIcon?: Icon
modalSize?: 'lg' | 'sm' | 'xl'
additionalClasses?: string
}
@ -74,10 +74,7 @@ export const CommonModal: React.FC<PropsWithChildren<CommonModalProps>> = ({
size={modalSize}>
<Modal.Header closeButton={!!showCloseButton}>
<Modal.Title>
<ShowIf condition={!!titleIcon}>
<ForkAwesomeIcon icon={titleIcon as IconName} />
&nbsp;
</ShowIf>
<UiIcon icon={titleIcon} nbsp={true} />
{titleElement}
</Modal.Title>
</Modal.Header>

View file

@ -1,20 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`create non existing note hint shows success message when the note has been created 1`] = `
<div>
<div
class="fade mt-5 alert alert-info show"
data-testid="noteCreated"
role="alert"
>
<i
class="fa fa-check-circle me-2 "
/>
noteLoadingBoundary.createNote.success
</div>
</div>
`;
exports[`create non existing note hint renders a waiting message when button is clicked 1`] = `
<div>
<div
@ -22,9 +7,7 @@ exports[`create non existing note hint renders a waiting message when button is
data-testid="loadingMessage"
role="alert"
>
<i
class="fa fa-spinner fa-spin me-2 "
/>
BootstrapIconMock_ArrowRepeat
noteLoadingBoundary.createNote.creating
</div>
</div>
@ -62,10 +45,21 @@ exports[`create non existing note hint shows an error message if note couldn't b
data-testid="failedMessage"
role="alert"
>
<i
class="fa fa-exclamation-triangle me-1 "
/>
BootstrapIconMock_ExclamationTriangle
noteLoadingBoundary.createNote.error
</div>
</div>
`;
exports[`create non existing note hint shows success message when the note has been created 1`] = `
<div>
<div
class="fade mt-5 alert alert-info show"
data-testid="noteCreated"
role="alert"
>
BootstrapIconMock_CheckCircle
noteLoadingBoundary.createNote.success
</div>
</div>
`;

View file

@ -6,10 +6,13 @@
import { createNoteWithPrimaryAlias } from '../../../api/notes'
import { useSingleStringUrlParameter } from '../../../hooks/common/use-single-string-url-parameter'
import { testId } from '../../../utils/test-id'
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
import { UiIcon } from '../icons/ui-icon'
import { ShowIf } from '../show-if/show-if'
import React, { useCallback, useEffect } from 'react'
import { Alert, Button } from 'react-bootstrap'
import { ArrowRepeat as IconArrowRepeat } from 'react-bootstrap-icons'
import { CheckCircle as IconCheckCircle } from 'react-bootstrap-icons'
import { ExclamationTriangle as IconExclamationTriangle } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
import { useAsyncFn } from 'react-use'
@ -49,21 +52,21 @@ export const CreateNonExistingNoteHint: React.FC<CreateNonExistingNoteHintProps>
} else if (returnState.value) {
return (
<Alert variant={'info'} {...testId('noteCreated')} className={'mt-5'}>
<ForkAwesomeIcon icon={'check-circle'} className={'me-2'} />
<UiIcon icon={IconCheckCircle} className={'me-2'} />
<Trans i18nKey={'noteLoadingBoundary.createNote.success'} />
</Alert>
)
} else if (returnState.loading) {
return (
<Alert variant={'info'} {...testId('loadingMessage')} className={'mt-5'}>
<ForkAwesomeIcon icon={'spinner'} className={'fa-spin me-2'} />
<UiIcon icon={IconArrowRepeat} className={'me-2'} spin={true} />
<Trans i18nKey={'noteLoadingBoundary.createNote.creating'} />
</Alert>
)
} else if (returnState.error !== undefined) {
return (
<Alert variant={'danger'} {...testId('failedMessage')} className={'mt-5'}>
<ForkAwesomeIcon icon={'exclamation-triangle'} className={'me-1'} />
<UiIcon icon={IconExclamationTriangle} className={'me-1'} />
<Trans i18nKey={'noteLoadingBoundary.createNote.error'} />
</Alert>
)
@ -82,7 +85,7 @@ export const CreateNonExistingNoteHint: React.FC<CreateNonExistingNoteHintProps>
onClick={onClickHandler}
{...testId('createNoteButton')}>
<ShowIf condition={returnState.loading}>
<ForkAwesomeIcon icon={'spinner'} className={'fa-spin me-2'} />
<UiIcon icon={IconArrowRepeat} className={'me-2'} spin={true} />
</ShowIf>
<Trans i18nKey={'noteLoadingBoundary.createNote.create'} />
</Button>

View file

@ -3,8 +3,9 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
import { UiIcon } from '../icons/ui-icon'
import React from 'react'
import { ArrowRepeat as IconArrowRepeat } from 'react-bootstrap-icons'
/**
* Renders a indefinitely spinning spinner.
@ -12,7 +13,7 @@ import React from 'react'
export const WaitSpinner: React.FC = () => {
return (
<div className={'m-3 d-flex align-items-center justify-content-center'}>
<ForkAwesomeIcon icon={'spinner'} className={'fa-spin'} />
<UiIcon icon={IconArrowRepeat} spin={true} />
</div>
)
}