From 30b74e31cd8bcd1be99c66ec70e47bf708d55b7e Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Thu, 3 Sep 2026 12:10:09 +0200 Subject: [PATCH] refactor(frontend): one Icon component, color inherited, one 14px square frame --- frontend/src/index.css | 2 + frontend/src/js/authorization/LoginPage.tsx | 8 +- frontend/src/js/button/HistoryButton.tsx | 2 +- frontend/src/js/button/IconButton.tsx | 123 +++++------------- frontend/src/js/concept-trees/ConceptTree.tsx | 4 +- .../js/concept-trees/ConceptTreeNodeText.tsx | 26 +++- .../js/concept-trees/ConceptTreesLoading.tsx | 4 +- .../js/concept-trees/ConceptsProgressBar.tsx | 4 +- frontend/src/js/editor-v2/TreeNode.tsx | 7 +- .../editor-v2/date-restriction/DateModal.tsx | 4 +- .../src/js/entity-history/EntityIdsList.tsx | 4 +- .../src/js/entity-history/SearchEntities.tsx | 4 +- .../TimeStratifiedConceptChart.tsx | 4 +- .../entity-history/timeline/ConceptName.tsx | 4 +- .../js/entity-history/timeline/EventCard.tsx | 22 ++-- .../js/entity-history/timeline/Quarter.tsx | 7 +- .../timeline/TimelineEmptyPlaceholder.tsx | 6 +- .../js/entity-history/timeline/YearHead.tsx | 7 +- .../external-forms/form/fields/DateField.tsx | 4 +- .../form/fields/DisclosureListField.tsx | 7 +- frontend/src/js/header/HelpMenu.tsx | 18 +-- frontend/src/js/header/LogoutButton.tsx | 7 +- frontend/src/js/icon/FaIcon.tsx | 99 -------------- frontend/src/js/info-pane/InfoPane.tsx | 4 +- frontend/src/js/info-pane/MatchingStats.tsx | 12 +- frontend/src/js/pane/TabNavigation.tsx | 4 +- frontend/src/js/preview/Preview.tsx | 8 +- frontend/src/js/preview/SelectBox.tsx | 4 +- .../src/js/previous-queries/list/Folder.tsx | 7 +- .../js/previous-queries/list/ProjectItem.tsx | 6 +- .../upload/CSVColumnPicker.tsx | 24 ++-- .../upload/UploadQueryResultsModal.tsx | 4 +- .../UploadFilterListModal.tsx | 16 +-- .../DownloadResultsDropdownButton.tsx | 8 +- frontend/src/js/query-runner/QueryResults.tsx | 4 +- .../src/js/query-runner/QueryRunnerButton.tsx | 7 +- .../src/js/snack-message/SnackMessage.tsx | 4 +- .../EmptyQueryEditorDropzone.tsx | 12 +- .../QueryNodeActions.tsx | 4 +- .../SecondaryIdSelector.tsx | 7 +- frontend/src/js/symbols/FormSymbol.tsx | 4 +- frontend/src/js/symbols/QuerySymbol.tsx | 7 +- frontend/src/js/ui-components/BaseInput.tsx | 12 +- frontend/src/js/ui-components/ConfirmMenu.tsx | 8 +- .../ui-components/DropzoneWithFileInput.tsx | 8 +- .../src/js/ui-components/EditableText.tsx | 1 - .../src/js/ui-components/Icon.stories.tsx | 94 +++++++++++++ frontend/src/js/ui-components/Icon.tsx | 34 +++++ frontend/src/js/ui-components/InfoTooltip.tsx | 4 +- .../src/js/ui-components/InputCheckbox.tsx | 4 +- .../src/js/ui-components/InputDateRange.tsx | 4 +- .../InputMultiSelect/InputMultiSelect.tsx | 4 +- .../src/js/ui-components/Menu.stories.tsx | 24 +--- frontend/src/js/ui-components/Menu.tsx | 8 +- .../src/js/ui-components/Tooltip.stories.tsx | 6 +- .../DropdownOption.tsx | 10 +- .../UploadConceptListModal.tsx | 16 +-- 57 files changed, 362 insertions(+), 398 deletions(-) delete mode 100644 frontend/src/js/icon/FaIcon.tsx create mode 100644 frontend/src/js/ui-components/Icon.stories.tsx create mode 100644 frontend/src/js/ui-components/Icon.tsx diff --git a/frontend/src/index.css b/frontend/src/index.css index b6457dc326..3f66224afe 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,4 +1,6 @@ @import "tailwindcss"; +/* into a layer so tailwind utilities can size icons */ +@import "@fortawesome/fontawesome-svg-core/styles.css" layer(base); @theme { --color-primary-50: #dadedb; diff --git a/frontend/src/js/authorization/LoginPage.tsx b/frontend/src/js/authorization/LoginPage.tsx index 4ba713971b..65f7d8eab9 100644 --- a/frontend/src/js/authorization/LoginPage.tsx +++ b/frontend/src/js/authorization/LoginPage.tsx @@ -7,7 +7,7 @@ import { usePostLogin } from "../api/api"; import { useAppTheme } from "../app-theme-context"; import PrimaryButton from "../button/PrimaryButton"; import ErrorMessage from "../error-message/ErrorMessage"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; import InputPlain from "../ui-components/InputPlain/InputPlain"; import { AuthTokenContext } from "./AuthTokenProvider"; @@ -121,11 +121,9 @@ const LoginPage = () => { large type="submit" > - {t("login.submit")} diff --git a/frontend/src/js/button/HistoryButton.tsx b/frontend/src/js/button/HistoryButton.tsx index c6691a13b1..b6697c21d8 100644 --- a/frontend/src/js/button/HistoryButton.tsx +++ b/frontend/src/js/button/HistoryButton.tsx @@ -18,7 +18,7 @@ export const HistoryButton = () => { return ( - + {t("history.history")} ); diff --git a/frontend/src/js/button/IconButton.tsx b/frontend/src/js/button/IconButton.tsx index ae77c50e1b..0f7527ba5f 100644 --- a/frontend/src/js/button/IconButton.tsx +++ b/frontend/src/js/button/IconButton.tsx @@ -1,7 +1,8 @@ import type { IconProp } from "@fortawesome/fontawesome-svg-core"; -import { memo, type Ref, useMemo } from "react"; +import { memo, type Ref } from "react"; import { tv } from "tailwind-variants"; -import FaIcon, { type IconStyleProps } from "../icon/FaIcon"; + +import { Icon } from "../ui-components/Icon"; import BasicButton, { type BasicButtonProps } from "./BasicButton"; @@ -18,7 +19,6 @@ const iconButton = tv({ ], variants: { // later wins when several are set - secondary: { true: "text-orange" }, active: { true: "text-primary-500" }, red: { true: "text-red" }, frame: { true: "opacity-100 border border-gray-500 hover:bg-bg-100" }, @@ -28,33 +28,22 @@ const iconButton = tv({ }, }); +// The button's text color reaches the icon; this is the exception. const buttonIcon = tv({ - base: "text-sm", variants: { - // later wins when several are set - secondary: { true: "text-orange" }, light: { true: "text-gray-500" }, - active: { true: "text-primary-500" }, - red: { true: "text-red" }, - small: { true: "text-xs" }, - large: { true: "text-base" }, }, }); -export interface IconButtonPropsT extends BasicButtonProps { - iconProps?: IconStyleProps; +export interface IconButtonPropsT extends Omit { + icon: IconProp; active?: boolean; large?: boolean; - small?: boolean; - icon: IconProp; - secondary?: boolean; tight?: boolean; red?: boolean; - left?: boolean; frame?: boolean; bare?: boolean; light?: boolean; - fixedIconWidth?: number; bgHover?: boolean; iconColor?: string; } @@ -66,87 +55,39 @@ const IconButton = ({ active, red, large, - left, children, tight, - iconProps, - small, - secondary, light, - fixedIconWidth, bgHover, iconColor, frame, className, ...restProps -}: IconButtonPropsT & { ref?: Ref }) => { - const iconElement = useMemo(() => { - const iconEl = ( - - ); - - return fixedIconWidth ? ( - - {iconEl} - - ) : ( - iconEl - ); - }, [ - icon, - active, - red, - large, - left, - iconProps, - small, - secondary, - light, - fixedIconWidth, - iconColor, - ]); - - return ( - - {iconElement} - {children && ( - {children} - )} - - ); -}; +}: IconButtonPropsT & { ref?: Ref }) => ( + + + {children && ( + {children} + )} + +); export default memo(IconButton); diff --git a/frontend/src/js/concept-trees/ConceptTree.tsx b/frontend/src/js/concept-trees/ConceptTree.tsx index ea4f16c6e6..04eef73fa2 100644 --- a/frontend/src/js/concept-trees/ConceptTree.tsx +++ b/frontend/src/js/concept-trees/ConceptTree.tsx @@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; import type { ConceptIdT, ConceptT } from "../api/types"; import IconButton from "../button/IconButton"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; import ConceptTreeNode from "./ConceptTreeNode"; import ConceptTreeNodeText from "./ConceptTreeNodeText"; import type { SearchT } from "./reducer"; @@ -46,7 +46,7 @@ const ConceptTree = ({ return (

- + {label}

diff --git a/frontend/src/js/concept-trees/ConceptTreeNodeText.tsx b/frontend/src/js/concept-trees/ConceptTreeNodeText.tsx index e46ce35bed..1de17ab6eb 100644 --- a/frontend/src/js/concept-trees/ConceptTreeNodeText.tsx +++ b/frontend/src/js/concept-trees/ConceptTreeNodeText.tsx @@ -7,7 +7,7 @@ import type { Ref } from "react"; import { tv } from "tailwind-variants"; import { Highlighter } from "../common/components/Highlighter"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; // Root with transparent background. // relative: needed to fix a drag & drop issue in Safari @@ -114,20 +114,34 @@ const ConceptTreeNodeText = ({ {hasChildren && ( <> - - + )} {!hasChildren && ( - + )} {resultCount && {resultCount}} diff --git a/frontend/src/js/concept-trees/ConceptTreesLoading.tsx b/frontend/src/js/concept-trees/ConceptTreesLoading.tsx index a55f5006dd..614b8534e3 100644 --- a/frontend/src/js/concept-trees/ConceptTreesLoading.tsx +++ b/frontend/src/js/concept-trees/ConceptTreesLoading.tsx @@ -2,14 +2,14 @@ import { faSpinner } from "@fortawesome/free-solid-svg-icons"; import { memo } from "react"; import { useTranslation } from "react-i18next"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; const ConceptTreesLoading = () => { const { t } = useTranslation(); return (
- + {t("conceptTreeList.loading")}
); diff --git a/frontend/src/js/concept-trees/ConceptsProgressBar.tsx b/frontend/src/js/concept-trees/ConceptsProgressBar.tsx index 0bf08b2097..c6dd81a891 100644 --- a/frontend/src/js/concept-trees/ConceptsProgressBar.tsx +++ b/frontend/src/js/concept-trees/ConceptsProgressBar.tsx @@ -2,7 +2,7 @@ import { faSpinner } from "@fortawesome/free-solid-svg-icons"; import { useTranslation } from "react-i18next"; import ProgressBar from "../common/components/ProgressBar"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; import type { TreesT } from "./reducer"; @@ -21,7 +21,7 @@ const ConceptsProgressBar = ({ trees }: PropsT) => { return (
- +

{t("conceptTreeList.loading")} {doneCount} / {treeIds.length}

diff --git a/frontend/src/js/editor-v2/TreeNode.tsx b/frontend/src/js/editor-v2/TreeNode.tsx index 7a19f38d50..30e05c6dfb 100644 --- a/frontend/src/js/editor-v2/TreeNode.tsx +++ b/frontend/src/js/editor-v2/TreeNode.tsx @@ -5,7 +5,6 @@ import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; import { DNDType } from "../common/constants/dndTypes"; -import FaIcon from "../icon/FaIcon"; import { nodeIsConceptQueryNode, useActiveState } from "../model/node"; import { getRootNodeLabel } from "../standard-query-editor/helper"; import type { @@ -16,6 +15,7 @@ import Dropzone, { type DropzoneProps, type PossibleDroppableObject, } from "../ui-components/Dropzone"; +import { Icon } from "../ui-components/Icon"; import { Tooltip, TooltipTarget, @@ -301,7 +301,10 @@ export function TreeNode({ )} {tree.dates?.excluded && (
- + {t("editorV2.datesExcluded")}
)} diff --git a/frontend/src/js/editor-v2/date-restriction/DateModal.tsx b/frontend/src/js/editor-v2/date-restriction/DateModal.tsx index 65b24a7a3e..06274d2947 100644 --- a/frontend/src/js/editor-v2/date-restriction/DateModal.tsx +++ b/frontend/src/js/editor-v2/date-restriction/DateModal.tsx @@ -8,8 +8,8 @@ import { tv } from "tailwind-variants"; import type { DateRangeT } from "../../api/types"; import IconButton from "../../button/IconButton"; import type { DateStringMinMax } from "../../common/helpers/dateHelper"; -import FaIcon from "../../icon/FaIcon"; import Modal from "../../modal/Modal"; +import { Icon } from "../../ui-components/Icon"; import InputCheckbox from "../../ui-components/InputCheckbox"; import InputDateRange from "../../ui-components/InputDateRange"; @@ -99,7 +99,7 @@ export const DateModal = ({ />

- + {t("queryNodeEditor.excludeTimestamps")} ({entityId.kind})

{loadingId === entityId.id && ( - + )}
{entityIdsStatus[entityId.id] && diff --git a/frontend/src/js/entity-history/SearchEntities.tsx b/frontend/src/js/entity-history/SearchEntities.tsx index 6dcb8f0662..011d2ca2a4 100644 --- a/frontend/src/js/entity-history/SearchEntities.tsx +++ b/frontend/src/js/entity-history/SearchEntities.tsx @@ -14,7 +14,6 @@ import type { StateT } from "../app/reducers"; import PrimaryButton from "../button/PrimaryButton"; import { getConceptById } from "../concept-trees/globalTreeStoreHelper"; import { useDatasetId } from "../dataset/selectors"; -import FaIcon from "../icon/FaIcon"; import { isMultiSelectFilter, resetFilters } from "../model/filter"; import { nodeIsElement } from "../model/node"; import { filterSuggestionToSelectOption } from "../query-node-editor/suggestionsHelper"; @@ -23,6 +22,7 @@ import type { BigMultiSelectFilterWithValueType, MultiSelectFilterWithValueType, } from "../standard-query-editor/types"; +import { Icon } from "../ui-components/Icon"; import type { LoadingPayload } from "./LoadHistoryDropzone"; import { useDefaultStatusOptions } from "./useDefaultStatusOptions"; @@ -251,7 +251,7 @@ export const SearchEntitiesComponent = ({ onClick={onSubmitSearch} disabled={!hasFiltersSet || loading} > - {loading && } + {loading && } {t("history.searchEntitiesButton")}
diff --git a/frontend/src/js/entity-history/TimeStratifiedConceptChart.tsx b/frontend/src/js/entity-history/TimeStratifiedConceptChart.tsx index 52e9aef77a..52a81d3a96 100644 --- a/frontend/src/js/entity-history/TimeStratifiedConceptChart.tsx +++ b/frontend/src/js/entity-history/TimeStratifiedConceptChart.tsx @@ -8,7 +8,7 @@ import type { TimeStratifiedInfo, } from "../api/types"; import { getConceptById } from "../concept-trees/globalTreeStoreHelper"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; import { Tooltip, TooltipTarget, @@ -90,7 +90,7 @@ export const TimeStratifiedConceptChart = ({ return (

- + {t("history.noData")}

diff --git a/frontend/src/js/entity-history/timeline/ConceptName.tsx b/frontend/src/js/entity-history/timeline/ConceptName.tsx index f1bb630f9d..aecf1ac511 100644 --- a/frontend/src/js/entity-history/timeline/ConceptName.tsx +++ b/frontend/src/js/entity-history/timeline/ConceptName.tsx @@ -4,7 +4,7 @@ import { tv } from "tailwind-variants"; import type { ConceptIdT, ConceptT } from "../../api/types"; import { Highlighter } from "../../common/components/Highlighter"; import { getConceptById } from "../../concept-trees/globalTreeStoreHelper"; -import FaIcon from "../../icon/FaIcon"; +import { Icon } from "../../ui-components/Icon"; import { useTimelineSearch } from "../timeline-search/timelineSearchState"; const conceptRoot = tv({ @@ -92,7 +92,7 @@ const ConceptName = ({ className, title, rootConceptId, conceptId }: Props) => { return (
- + {rootConcept && ( diff --git a/frontend/src/js/entity-history/timeline/EventCard.tsx b/frontend/src/js/entity-history/timeline/EventCard.tsx index 69d1e48b8c..c0b45c0b3e 100644 --- a/frontend/src/js/entity-history/timeline/EventCard.tsx +++ b/frontend/src/js/entity-history/timeline/EventCard.tsx @@ -13,7 +13,7 @@ import type { } from "../../api/types"; import { Highlighter } from "../../common/components/Highlighter"; import { exists } from "../../common/helpers/exists"; -import FaIcon from "../../icon/FaIcon"; +import { Icon } from "../../ui-components/Icon"; import { Tooltip, TooltipTarget, @@ -74,8 +74,7 @@ const rawDataBadge = tv({ }); const bucketIcon = tv({ - // w-6! beats FaIcon's own w-[initial]! via merge, like the old !important did - base: ["w-6!", "text-center", "mx-[5px] my-2", "text-base"], + base: ["w-6", "mx-[5px] my-2"], }); const bullet = tv({ @@ -150,11 +149,9 @@ const EventCard = ({ aria-label={moneyTooltip} excludeFromTabOrder > - {moneyTooltip} @@ -198,7 +195,10 @@ const EventCard = ({ aria-label={restTooltip} excludeFromTabOrder > - + {restTooltip} @@ -229,11 +229,9 @@ const EventCard = ({ aria-label={groupableIdsTooltip} excludeFromTabOrder > - {groupableIdsTooltip} diff --git a/frontend/src/js/entity-history/timeline/Quarter.tsx b/frontend/src/js/entity-history/timeline/Quarter.tsx index fc0373e0ff..ca5494219b 100644 --- a/frontend/src/js/entity-history/timeline/Quarter.tsx +++ b/frontend/src/js/entity-history/timeline/Quarter.tsx @@ -8,7 +8,7 @@ import type { ConceptIdT, CurrencyConfigT, } from "../../api/types"; -import FaIcon from "../../icon/FaIcon"; +import { Icon } from "../../ui-components/Icon"; import type { ContentFilterValue } from "../ContentControl"; import type { DetailLevel } from "../DetailControl"; import type { EntityEvent } from "../reducer"; @@ -109,7 +109,10 @@ export const Quarter = memo( className={inlineGrid()} onClick={() => toggleOpenQuarter(year, quarter)} > - + Q{quarter} – {totalEventsPerQuarter}{" "} diff --git a/frontend/src/js/entity-history/timeline/TimelineEmptyPlaceholder.tsx b/frontend/src/js/entity-history/timeline/TimelineEmptyPlaceholder.tsx index 35c5a61409..4fc6796688 100644 --- a/frontend/src/js/entity-history/timeline/TimelineEmptyPlaceholder.tsx +++ b/frontend/src/js/entity-history/timeline/TimelineEmptyPlaceholder.tsx @@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; import { tv } from "tailwind-variants"; import type { StateT } from "../../app/reducers"; -import FaIcon from "../../icon/FaIcon"; +import { Icon } from "../../ui-components/Icon"; import type { EntityHistoryStateT } from "../reducer"; const root = tv({ @@ -59,9 +59,9 @@ export const TimelineEmptyPlaceholder = ({ return (
-

diff --git a/frontend/src/js/entity-history/timeline/YearHead.tsx b/frontend/src/js/entity-history/timeline/YearHead.tsx index db8d8f4a0e..6e5e6a1c86 100644 --- a/frontend/src/js/entity-history/timeline/YearHead.tsx +++ b/frontend/src/js/entity-history/timeline/YearHead.tsx @@ -9,7 +9,7 @@ import type { } from "../../api/types"; import { exists } from "../../common/helpers/exists"; import { getConceptById } from "../../concept-trees/globalTreeStoreHelper"; -import FaIcon from "../../icon/FaIcon"; +import { Icon } from "../../ui-components/Icon"; import { Tooltip, TooltipTarget, @@ -211,7 +211,10 @@ const YearHead = ({ {/* biome-ignore lint/a11y/noStaticElementInteractions: TODO make this a button */} {/* biome-ignore lint/a11y/useKeyWithClickEvents: TODO make this a button */}
- +
{year}
diff --git a/frontend/src/js/external-forms/form/fields/DateField.tsx b/frontend/src/js/external-forms/form/fields/DateField.tsx index 7a66524606..9e1dd6695e 100644 --- a/frontend/src/js/external-forms/form/fields/DateField.tsx +++ b/frontend/src/js/external-forms/form/fields/DateField.tsx @@ -8,7 +8,7 @@ import { parseDateToState, } from "../../../common/helpers/dateHelper"; import { exists } from "../../../common/helpers/exists"; -import FaIcon from "../../../icon/FaIcon"; +import { Icon } from "../../../ui-components/Icon"; import InfoTooltip from "../../../ui-components/InfoTooltip"; import InputDate from "../../../ui-components/InputDate/InputDate"; import Label from "../../../ui-components/Label"; @@ -50,7 +50,7 @@ export const DateField = ({ return (
diff --git a/frontend/src/js/external-forms/form/fields/DisclosureListField.tsx b/frontend/src/js/external-forms/form/fields/DisclosureListField.tsx index 357da74033..9c783ede1f 100644 --- a/frontend/src/js/external-forms/form/fields/DisclosureListField.tsx +++ b/frontend/src/js/external-forms/form/fields/DisclosureListField.tsx @@ -4,7 +4,6 @@ import { faChevronRight, faTimes, } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { type ComponentProps, useEffect, useState } from "react"; import { useFieldArray } from "react-hook-form"; import { tv } from "tailwind-variants"; @@ -12,7 +11,7 @@ import IconButton from "../../../button/IconButton"; import { TransparentButton } from "../../../button/TransparentButton"; import { exists } from "../../../common/helpers/exists"; import { usePrevious } from "../../../common/helpers/usePrevious"; -import FaIcon from "../../../icon/FaIcon"; +import { Icon } from "../../../ui-components/Icon"; import InfoTooltip from "../../../ui-components/InfoTooltip"; import type { DisclosureListField as DisclosureListFieldT } from "../../config-types"; import { @@ -75,7 +74,7 @@ const DisclosureField = ({
- + {field.label[locale]} {exists(field.tooltip) && ( @@ -241,7 +240,7 @@ export const DisclosureListField = ({ ); }} > - + {field.createNewLabel ? field.createNewLabel[locale] : undefined} )} diff --git a/frontend/src/js/header/HelpMenu.tsx b/frontend/src/js/header/HelpMenu.tsx index a131d057f3..e18bedfdae 100644 --- a/frontend/src/js/header/HelpMenu.tsx +++ b/frontend/src/js/header/HelpMenu.tsx @@ -9,8 +9,8 @@ import { useTranslation } from "react-i18next"; import { useAbout } from "../app/About"; import IconButton from "../button/IconButton"; -import FaIcon from "../icon/FaIcon"; -import { Menu, MenuItem, menuItemIcon } from "../ui-components/Menu"; +import { Icon } from "../ui-components/Icon"; +import { Menu, MenuItem } from "../ui-components/Menu"; interface Props { contactEmail?: string; @@ -24,7 +24,7 @@ export const HelpMenu = ({ contactEmail, manualUrl }: Props) => { return ( { rel="noopener noreferrer" data-test-id="help-email" > - - - + {t("common.contact")} { rel="noopener noreferrer" data-test-id="help-manual" > - - - + {t("common.manual")} - - - + {t("common.version")} diff --git a/frontend/src/js/header/LogoutButton.tsx b/frontend/src/js/header/LogoutButton.tsx index 3e4016a130..06f9ba7e5b 100644 --- a/frontend/src/js/header/LogoutButton.tsx +++ b/frontend/src/js/header/LogoutButton.tsx @@ -36,7 +36,12 @@ const LogoutButton = () => { return ( - + {t("common.logout")} ); diff --git a/frontend/src/js/icon/FaIcon.tsx b/frontend/src/js/icon/FaIcon.tsx deleted file mode 100644 index e50b2b6d11..0000000000 --- a/frontend/src/js/icon/FaIcon.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import type { IconProp } from "@fortawesome/fontawesome-svg-core"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import type { ComponentProps, Ref } from "react"; - -import { tv } from "tailwind-variants"; - -export interface IconStyleProps { - left?: boolean; - center?: boolean; - right?: boolean; - white?: boolean; - red?: boolean; - light?: boolean; - gray?: boolean; - main?: boolean; - active?: boolean; - disabled?: boolean; - tiny?: boolean; - large?: boolean; - small?: boolean; - style?: ComponentProps["style"]; -} - -export interface FaIconPropsT extends IconStyleProps { - icon: IconProp; - className?: string; -} - -const icon = tv({ - base: [ - "w-[initial]!", - "text-sm", - "text-gray-800", - "[&.fa-spinner]:animate-spin-fast", - ], - variants: { - left: { true: "pr-[10px]" }, - right: { true: "pl-[10px]" }, - center: { true: "text-center" }, - // later wins when both are set - tiny: { true: "text-[11px]" }, - large: { true: "text-base" }, - disabled: { true: "cursor-not-allowed" }, - }, -}); - -// First matching flag wins, in this order -const colorClass = (p: IconStyleProps) => { - if (p.disabled) return "text-gray-400"; - if (p.red) return "text-red"; - if (p.gray) return "text-gray-500"; - if (p.active) return "text-primary-500"; - if (p.white) return "text-white"; - if (p.light) return "text-primary-100"; - if (p.main) return "text-primary-200"; - return undefined; -}; - -const FaIcon = ({ - ref, - icon: iconProp, - className, - left, - center, - right, - white, - red, - light, - gray, - main, - active, - disabled, - tiny, - large, - small: _small, // only meaningful to IconButton - style, -}: FaIconPropsT & { ref?: Ref }) => { - return ( - - ); -}; - -export default FaIcon; diff --git a/frontend/src/js/info-pane/InfoPane.tsx b/frontend/src/js/info-pane/InfoPane.tsx index 0c3574493f..4ba7f30bf0 100644 --- a/frontend/src/js/info-pane/InfoPane.tsx +++ b/frontend/src/js/info-pane/InfoPane.tsx @@ -12,7 +12,7 @@ import { tv } from "tailwind-variants"; import type { StateT } from "../app/reducers"; import IconButton from "../button/IconButton"; import { Highlighter } from "../common/components/Highlighter"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; import { toggleAdditionalInfos as toggleInfos } from "./actions"; import InfoPaneCollapsed from "./InfoPaneCollapsed"; import { InfoPaneHeader } from "./InfoPaneHeader"; @@ -113,7 +113,7 @@ const ConceptLabel = ({ return (

- {conceptIcon && } + {conceptIcon && } {label ? ( diff --git a/frontend/src/js/info-pane/MatchingStats.tsx b/frontend/src/js/info-pane/MatchingStats.tsx index 23be514bfb..aafd8b711f 100644 --- a/frontend/src/js/info-pane/MatchingStats.tsx +++ b/frontend/src/js/info-pane/MatchingStats.tsx @@ -11,7 +11,7 @@ import type { DateRangeT } from "../api/types"; import { numberToThreeDigitArray } from "../common/helpers/commonHelper"; import { formatDate, parseDate } from "../common/helpers/dateHelper"; import { exists } from "../common/helpers/exists"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; const dateText = tv({ base: [ @@ -35,7 +35,7 @@ const text = tv({ }); const icon = tv({ - base: ["text-[30px]", "text-gray-400", "justify-self-center"], + base: ["size-[30px]", "text-gray-400", "justify-self-center"], }); const numberText = tv({ @@ -93,7 +93,7 @@ const MatchingStats = (props: Props) => {

{idLabel && ( <> - +

{idLabel}

@@ -102,7 +102,7 @@ const MatchingStats = (props: Props) => {

)} - +

{exists(matchingEntries) ? ( @@ -122,7 +122,7 @@ const MatchingStats = (props: Props) => { )}

- +

{exists(matchingEntities) ? ( @@ -142,7 +142,7 @@ const MatchingStats = (props: Props) => { )}

- +

{fromDate} diff --git a/frontend/src/js/pane/TabNavigation.tsx b/frontend/src/js/pane/TabNavigation.tsx index 43241fa26a..8cfe8d2d65 100644 --- a/frontend/src/js/pane/TabNavigation.tsx +++ b/frontend/src/js/pane/TabNavigation.tsx @@ -1,8 +1,8 @@ import { faSpinner } from "@fortawesome/free-solid-svg-icons"; import { Focusable } from "react-aria-components"; import { tv } from "tailwind-variants"; -import FaIcon from "../icon/FaIcon"; import { HoverNavigatable } from "../small-tab-navigation/HoverNavigatable"; +import { Icon } from "../ui-components/Icon"; import { Tooltip, TooltipTrigger, @@ -75,7 +75,7 @@ const TabNavigation = ({ onClick={createClickHandler(key)} > {label} - {loading && } + {loading && } {tooltip} diff --git a/frontend/src/js/preview/Preview.tsx b/frontend/src/js/preview/Preview.tsx index 7a27d76707..0df5912053 100644 --- a/frontend/src/js/preview/Preview.tsx +++ b/frontend/src/js/preview/Preview.tsx @@ -8,7 +8,7 @@ import { tv } from "tailwind-variants"; import type { PreviewStatistics, SecondaryId } from "../api/types"; import type { StateT } from "../app/reducers"; import { TransparentButton } from "../button/TransparentButton"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; import { closePreview } from "./actions"; import Charts from "./Charts"; import DiagramModal from "./DiagramModal"; @@ -56,9 +56,7 @@ const chartLoadingBlocker = tv({ ], }); -// the old styles also set `width: 30px`, but FaIcon forces `width: initial -// !important`, so it never applied — only the height did -const spinnerIcon = tv({ base: "h-[30px]" }); +const spinnerIcon = tv({ base: "size-[30px]" }); const selectBox = tv({ base: ["rounded", "bg-white", "shadow-[0_0_5px_0_rgba(0,0,0,0.2)]"], @@ -126,7 +124,7 @@ export default function Preview() { /> ) : (

- +
)} {popOver && ( diff --git a/frontend/src/js/preview/SelectBox.tsx b/frontend/src/js/preview/SelectBox.tsx index ba9cb156d8..a25f102bcf 100644 --- a/frontend/src/js/preview/SelectBox.tsx +++ b/frontend/src/js/preview/SelectBox.tsx @@ -2,7 +2,7 @@ import { faCaretDown, faCaretUp } from "@fortawesome/free-solid-svg-icons"; import { type SetStateAction, useMemo, useRef, useState } from "react"; import { tv } from "tailwind-variants"; import { useClickOutside } from "../common/helpers/useClickOutside"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; import { Input } from "../ui-components/InputSelect/InputSelectComponents"; export interface SelectItem { @@ -89,7 +89,7 @@ export default function SelectBox({ spellCheck={false} />
- +
diff --git a/frontend/src/js/previous-queries/list/Folder.tsx b/frontend/src/js/previous-queries/list/Folder.tsx index d9f325d2cd..863339b816 100644 --- a/frontend/src/js/previous-queries/list/Folder.tsx +++ b/frontend/src/js/previous-queries/list/Folder.tsx @@ -4,7 +4,7 @@ import { tv } from "tailwind-variants"; import { Highlighter } from "../../common/components/Highlighter"; import { exists } from "../../common/helpers/exists"; -import FaIcon from "../../icon/FaIcon"; +import { Icon } from "../../ui-components/Icon"; const root = tv({ base: [ @@ -63,10 +63,9 @@ const Folder = ({ className={root({ active, special, className })} title={folder} > - {exists(count) && {count}}
diff --git a/frontend/src/js/previous-queries/list/ProjectItem.tsx b/frontend/src/js/previous-queries/list/ProjectItem.tsx index c69b6523cb..6463213ede 100644 --- a/frontend/src/js/previous-queries/list/ProjectItem.tsx +++ b/frontend/src/js/previous-queries/list/ProjectItem.tsx @@ -22,9 +22,9 @@ import { Highlighter } from "../../common/components/Highlighter"; import { formatDate } from "../../common/helpers/dateHelper"; import { exists } from "../../common/helpers/exists"; import { useFormLabelByType } from "../../external-forms/stateSelectors"; -import FaIcon from "../../icon/FaIcon"; import FormSymbol from "../../symbols/FormSymbol"; import QuerySymbol from "../../symbols/QuerySymbol"; +import { Icon } from "../../ui-components/Icon"; import { Tooltip, TooltipTarget, @@ -185,7 +185,6 @@ const ResultsLabel = ({ - + {t("previousQuery.hasNoDates")} diff --git a/frontend/src/js/previous-queries/upload/CSVColumnPicker.tsx b/frontend/src/js/previous-queries/upload/CSVColumnPicker.tsx index 2df67ddb28..4a338f9ba3 100644 --- a/frontend/src/js/previous-queries/upload/CSVColumnPicker.tsx +++ b/frontend/src/js/previous-queries/upload/CSVColumnPicker.tsx @@ -17,9 +17,9 @@ import IconButton from "../../button/IconButton"; import PrimaryButton from "../../button/PrimaryButton"; import { TransparentButton } from "../../button/TransparentButton"; import { parseCSV, toCSV } from "../../file/csv"; -import FaIcon from "../../icon/FaIcon"; import { useActiveLang } from "../../localization/useActiveLang"; import ScrollableList from "../../scrollable-list/ScrollableList"; +import { Icon } from "../../ui-components/Icon"; import InputSelect from "../../ui-components/InputSelect/InputSelect"; import { Tooltip, TooltipTrigger } from "../../ui-components/Tooltip"; @@ -294,9 +294,9 @@ const CSVColumnPicker = ({

{uploadResult.resolved > 0 && ( - )} {t("csvColumnPicker.resolved", { count: uploadResult.resolved })} @@ -304,9 +304,9 @@ const CSVColumnPicker = ({ {uploadResult.unreadableDate.length > 0 && ( <>

- {t("csvColumnPicker.unreadableDate", { count: uploadResult.unreadableDate.length, @@ -326,9 +326,9 @@ const CSVColumnPicker = ({ {uploadResult.unresolvedId.length > 0 && ( <>

- {t("csvColumnPicker.unresolvedId", { count: uploadResult.unresolvedId.length, @@ -351,7 +351,7 @@ const CSVColumnPicker = ({ (uploadResult.unreadableDate.length > 0 || uploadResult.unresolvedId.length > 0) && ( - {" "} + {" "} {t("uploadQueryResultsModal.downloadUnresolved", { count: uploadResult.unreadableDate.length + @@ -366,9 +366,9 @@ const CSVColumnPicker = ({ onClick={uploadQuery} > {loading ? ( - + ) : ( - + )}{" "} {t("uploadQueryResultsModal.uploadAgain")} @@ -388,9 +388,9 @@ const CSVColumnPicker = ({ onClick={uploadQuery} > {loading ? ( - + ) : ( - + )}{" "} {t("uploadQueryResultsModal.upload")} diff --git a/frontend/src/js/previous-queries/upload/UploadQueryResultsModal.tsx b/frontend/src/js/previous-queries/upload/UploadQueryResultsModal.tsx index 01c990e314..d7a7f2efb5 100644 --- a/frontend/src/js/previous-queries/upload/UploadQueryResultsModal.tsx +++ b/frontend/src/js/previous-queries/upload/UploadQueryResultsModal.tsx @@ -4,9 +4,9 @@ import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; import type { QueryUploadConfigT, UploadQueryResponseT } from "../../api/types"; -import FaIcon from "../../icon/FaIcon"; import Modal from "../../modal/Modal"; import DropzoneWithFileInput from "../../ui-components/DropzoneWithFileInput"; +import { Icon } from "../../ui-components/Icon"; import InfoTooltip from "../../ui-components/InfoTooltip"; import CSVColumnPicker, { type QueryToUploadT } from "./CSVColumnPicker"; @@ -60,7 +60,7 @@ const UploadQueryResultsModal = ({

{fullUploadSuccess ? (
- +

{t("uploadQueryResultsModal.uploadSucceeded", { count: uploadResult?.resolved || 0, diff --git a/frontend/src/js/query-node-editor/UploadFilterListModal.tsx b/frontend/src/js/query-node-editor/UploadFilterListModal.tsx index e4939776ca..5bc9e4f9a0 100644 --- a/frontend/src/js/query-node-editor/UploadFilterListModal.tsx +++ b/frontend/src/js/query-node-editor/UploadFilterListModal.tsx @@ -8,9 +8,9 @@ import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; import type { PostFilterResolveResponseT } from "../api/types"; import PrimaryButton from "../button/PrimaryButton"; -import FaIcon from "../icon/FaIcon"; import Modal from "../modal/Modal"; import ScrollableList from "../scrollable-list/ScrollableList"; +import { Icon } from "../ui-components/Icon"; import InputCheckbox from "../ui-components/InputCheckbox"; const root = tv({ @@ -79,12 +79,12 @@ const UploadFilterListModal = ({ headline={t("uploadFilterListModal.headline")} >

- {loading && } + {loading && } {error && (

- {t("uploadConceptListModal.error")}

@@ -92,9 +92,9 @@ const UploadFilterListModal = ({ {hasUnresolvedItems && (

- {hasResolvedItems && (

- {t("uploadConceptListModal.resolvedCodes", { count: resolvedItemsCount, diff --git a/frontend/src/js/query-runner/DownloadResultsDropdownButton.tsx b/frontend/src/js/query-runner/DownloadResultsDropdownButton.tsx index 861bf314c3..705bef818d 100644 --- a/frontend/src/js/query-runner/DownloadResultsDropdownButton.tsx +++ b/frontend/src/js/query-runner/DownloadResultsDropdownButton.tsx @@ -8,8 +8,8 @@ import type { ResultUrlWithLabel } from "../api/types"; import { AuthTokenContext } from "../authorization/AuthTokenProvider"; import DownloadButton, { getFileIcon } from "../button/DownloadButton"; import IconButton from "../button/IconButton"; -import FaIcon from "../icon/FaIcon"; -import { Menu, MenuItem, menuItemIcon } from "../ui-components/Menu"; +import { Icon } from "../ui-components/Icon"; +import { Menu, MenuItem } from "../ui-components/Menu"; import { Tooltip, TooltipTrigger } from "../ui-components/Tooltip"; import { getUserSettings, storeUserSettings } from "../user/userSettings"; @@ -137,9 +137,7 @@ const DownloadResultsDropdownButton = ({ href={`${resultUrl.url}?access_token=${encodeURIComponent(authToken)}`} textValue={resultUrl.label} > - - - + {truncate(resultUrl.label)} ); diff --git a/frontend/src/js/query-runner/QueryResults.tsx b/frontend/src/js/query-runner/QueryResults.tsx index bf432704b1..15f4cc20cd 100644 --- a/frontend/src/js/query-runner/QueryResults.tsx +++ b/frontend/src/js/query-runner/QueryResults.tsx @@ -9,7 +9,7 @@ import PreviewButton from "../button/PreviewButton"; import { QueryResultHistoryButton } from "../button/QueryResultHistoryButton"; import { isEmpty } from "../common/helpers/commonHelper"; import { exists } from "../common/helpers/exists"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; import { canViewEntityPreview, canViewQueryPreview } from "../user/selectors"; import DownloadResultsDropdownButton from "./DownloadResultsDropdownButton"; @@ -49,7 +49,7 @@ const QueryResults = ({

{isEmpty(resultCount) ? (

- + {t("queryRunner.endSuccess")}

) : ( diff --git a/frontend/src/js/query-runner/QueryRunnerButton.tsx b/frontend/src/js/query-runner/QueryRunnerButton.tsx index 74a21dc534..0b8befaa49 100644 --- a/frontend/src/js/query-runner/QueryRunnerButton.tsx +++ b/frontend/src/js/query-runner/QueryRunnerButton.tsx @@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; import BasicButton from "../button/BasicButton"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; const left = tv({ base: ["px-[15px]", "transition-[color,background-color] duration-100"], @@ -75,7 +75,10 @@ const QueryRunnerButton = ({ data-test-id="query-runner-button" > - + {label} diff --git a/frontend/src/js/snack-message/SnackMessage.tsx b/frontend/src/js/snack-message/SnackMessage.tsx index 9a1d1293d4..e37af837f0 100644 --- a/frontend/src/js/snack-message/SnackMessage.tsx +++ b/frontend/src/js/snack-message/SnackMessage.tsx @@ -4,7 +4,7 @@ import { useDispatch, useSelector } from "react-redux"; import { tv } from "tailwind-variants"; import type { StateT } from "../app/reducers"; import { useClickOutside } from "../common/helpers/useClickOutside"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; import { resetMessage as resetMessageAction } from "./actions"; import type { SnackMessageStateT } from "./reducer"; @@ -55,7 +55,7 @@ export const SnackMessage = memo(function SnackMessageComponent() { className={clearZone()} onClick={resetMessage} > - +
diff --git a/frontend/src/js/standard-query-editor/EmptyQueryEditorDropzone.tsx b/frontend/src/js/standard-query-editor/EmptyQueryEditorDropzone.tsx index a525642d00..764ab8a173 100644 --- a/frontend/src/js/standard-query-editor/EmptyQueryEditorDropzone.tsx +++ b/frontend/src/js/standard-query-editor/EmptyQueryEditorDropzone.tsx @@ -9,7 +9,7 @@ import { memo } from "react"; import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; const textInitial = tv({ base: [ @@ -54,28 +54,28 @@ export const EmptyQueryEditorDropzone = memo(() => { return (

{t("dropzone.explanation")}

- +

{t("dropzone.dropIntoThisArea")}

- +
- +
{t("dropzone.aConcept")}
- +
{t("dropzone.aQuery")}
- +
{t("dropzone.aConceptList")} diff --git a/frontend/src/js/standard-query-editor/QueryNodeActions.tsx b/frontend/src/js/standard-query-editor/QueryNodeActions.tsx index 4c8e298dec..1b07cd4f34 100644 --- a/frontend/src/js/standard-query-editor/QueryNodeActions.tsx +++ b/frontend/src/js/standard-query-editor/QueryNodeActions.tsx @@ -10,7 +10,7 @@ import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; import IconButton from "../button/IconButton"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; import { Tooltip, TooltipTarget, @@ -85,7 +85,7 @@ const QueryNodeActions = (props: Props) => { aria-label={t("queryEditor.loadingPreviousQuery")} excludeFromTabOrder > - + {t("queryEditor.loadingPreviousQuery")} diff --git a/frontend/src/js/standard-query-editor/SecondaryIdSelector.tsx b/frontend/src/js/standard-query-editor/SecondaryIdSelector.tsx index 169fdaa593..f66079dcf9 100644 --- a/frontend/src/js/standard-query-editor/SecondaryIdSelector.tsx +++ b/frontend/src/js/standard-query-editor/SecondaryIdSelector.tsx @@ -7,8 +7,8 @@ import { tv } from "tailwind-variants"; import type { SecondaryId } from "../api/types"; import type { StateT } from "../app/reducers"; import { exists } from "../common/helpers/exists"; -import FaIcon from "../icon/FaIcon"; import { nodeIsConceptQueryNode } from "../model/node"; +import { Icon } from "../ui-components/Icon"; import InfoTooltip from "../ui-components/InfoTooltip"; import ToggleButton from "../ui-components/ToggleButton"; @@ -160,10 +160,9 @@ const SecondaryIdSelectorUI = memo( return (

- {t("queryEditor.secondaryId")} diff --git a/frontend/src/js/symbols/FormSymbol.tsx b/frontend/src/js/symbols/FormSymbol.tsx index aa053986d2..4b76bd2c0a 100644 --- a/frontend/src/js/symbols/FormSymbol.tsx +++ b/frontend/src/js/symbols/FormSymbol.tsx @@ -1,13 +1,13 @@ import { faChartColumn } from "@fortawesome/free-solid-svg-icons"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; import { InABox } from "./InABox"; const FormSymbol = ({ className }: { className?: string }) => { return ( - + ); }; diff --git a/frontend/src/js/symbols/QuerySymbol.tsx b/frontend/src/js/symbols/QuerySymbol.tsx index dd10ff5215..9d76938034 100644 --- a/frontend/src/js/symbols/QuerySymbol.tsx +++ b/frontend/src/js/symbols/QuerySymbol.tsx @@ -1,13 +1,16 @@ import { faDiagramProject } from "@fortawesome/free-solid-svg-icons"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "../ui-components/Icon"; import { InABox } from "./InABox"; const QuerySymbol = ({ className }: { className?: string }) => { return ( - + ); }; diff --git a/frontend/src/js/ui-components/BaseInput.tsx b/frontend/src/js/ui-components/BaseInput.tsx index 2e06785347..e53ecb38b8 100644 --- a/frontend/src/js/ui-components/BaseInput.tsx +++ b/frontend/src/js/ui-components/BaseInput.tsx @@ -16,8 +16,8 @@ import type { CurrencyConfigT } from "../api/types"; import IconButton from "../button/IconButton"; import { isEmpty } from "../common/helpers/commonHelper"; import { exists } from "../common/helpers/exists"; -import FaIcon from "../icon/FaIcon"; import CurrencyInput from "./CurrencyInput"; +import { Icon } from "./Icon"; import { Tooltip, TooltipTarget, @@ -190,9 +190,7 @@ const BaseInput = ({ )} {exists(value) && !isEmpty(value) && ( <> - {valid && !invalid && ( - - )} + {valid && !invalid && } {invalid && ( - + {invalidText} diff --git a/frontend/src/js/ui-components/ConfirmMenu.tsx b/frontend/src/js/ui-components/ConfirmMenu.tsx index 479c4e2a02..6ae86b69c1 100644 --- a/frontend/src/js/ui-components/ConfirmMenu.tsx +++ b/frontend/src/js/ui-components/ConfirmMenu.tsx @@ -4,9 +4,9 @@ import type { ReactNode } from "react"; import { MenuTrigger } from "react-aria-components"; import { useTranslation } from "react-i18next"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "./Icon"; -import { Menu, MenuItem, menuItemIcon } from "./Menu"; +import { Menu, MenuItem } from "./Menu"; /** * Asks for confirmation before calling onConfirm: a menu with a single item. @@ -35,9 +35,7 @@ export const ConfirmMenu = ({ {children} - - - + {label} diff --git a/frontend/src/js/ui-components/DropzoneWithFileInput.tsx b/frontend/src/js/ui-components/DropzoneWithFileInput.tsx index b7785bce97..9d22086079 100644 --- a/frontend/src/js/ui-components/DropzoneWithFileInput.tsx +++ b/frontend/src/js/ui-components/DropzoneWithFileInput.tsx @@ -6,12 +6,11 @@ import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; import { SelectFileButton } from "../button/SelectFileButton"; -import FaIcon from "../icon/FaIcon"; - import Dropzone, { type ChildArgs, type PossibleDroppableObject, } from "./Dropzone"; +import { Icon } from "./Icon"; import { ImportModal } from "./ImportModal"; export interface DragItemFile { @@ -149,7 +148,10 @@ const DropzoneWithFileInput = < className={selectFileButton({ outside: !!importButtonOutside })} onClick={() => setImportModalOpen(true)} > - + {t("common.import")} )} diff --git a/frontend/src/js/ui-components/EditableText.tsx b/frontend/src/js/ui-components/EditableText.tsx index 7b6d1b0124..2b8cc0c825 100644 --- a/frontend/src/js/ui-components/EditableText.tsx +++ b/frontend/src/js/ui-components/EditableText.tsx @@ -70,7 +70,6 @@ const EditableText = ({ bare icon={faPen} onClick={onToggleEdit} - small large={large} /> {tooltip} diff --git a/frontend/src/js/ui-components/Icon.stories.tsx b/frontend/src/js/ui-components/Icon.stories.tsx new file mode 100644 index 0000000000..d66caf285c --- /dev/null +++ b/frontend/src/js/ui-components/Icon.stories.tsx @@ -0,0 +1,94 @@ +import { + faArrowsLeftRightToLine, + faCheck, + faEllipsisV, + faFolder, + faPaperPlane, + faSpinner, + faTrash, + faUser, +} from "@fortawesome/free-solid-svg-icons"; +import type { Meta, StoryObj } from "@storybook/react"; + +import IconButton from "../button/IconButton"; +import PrimaryButton from "../button/PrimaryButton"; + +import { Icon } from "./Icon"; + +export default { + title: "UiComponents/Icon", + component: Icon, + parameters: { layout: "centered" }, +} as Meta; + +type Story = StoryObj; + +const glyphs = [ + faEllipsisV, + faUser, + faFolder, + faPaperPlane, + faArrowsLeftRightToLine, +]; + +export const Frame: Story = { + render: () => ( +
+
+ {glyphs.map((icon, i) => ( + + ))} + narrow to wide glyphs, one 14 px frame each +
+
    +
  • + icons line up +
  • +
  • + in lists and menus +
  • +
  • + whatever their shape +
  • +
+
+ ), +}; + +export const Picture: Story = { + render: () => ( +
+
+ + default +
+
+ + size-[30px], a picture +
+
+ ), +}; + +export const InheritsColor: Story = { + render: () => ( +
+ + muted text + + + danger text + + + inside a button + + + red icon button + +
+ ), +}; + +export const Spinner: Story = { + render: () => , +}; diff --git a/frontend/src/js/ui-components/Icon.tsx b/frontend/src/js/ui-components/Icon.tsx new file mode 100644 index 0000000000..97b32053cf --- /dev/null +++ b/frontend/src/js/ui-components/Icon.tsx @@ -0,0 +1,34 @@ +import { config } from "@fortawesome/fontawesome-svg-core"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import type { ComponentProps } from "react"; +import { type ClassValue, tv } from "tailwind-variants"; + +// Font Awesome's stylesheet is imported in index.css into tailwind's base +// layer, so size utilities apply to icons. Keep it from injecting a second, +// unlayered copy. +config.autoAddCss = false; + +const icon = tv({ + base: [ + // one square frame for all icons; a picture-like icon overrides it with a size-* class + "size-3.5 shrink-0", + "[&.fa-spinner]:animate-spin-fast", + ], +}); + +/** + * Font Awesome icon in the app's frame. + * + * - frame: 14 by 14 px, the glyph centered in it, so icons line up in + * rows, lists and menus regardless of glyph shape. Override with a + * `size-*` class only where the icon is a picture (empty states, big + * status marks). + * - color: `currentColor`, inherited from the surrounding text. A button + * colors its icon through its own text color. + */ +export const Icon = ({ + className, + ...props +}: Omit, "className"> & { + className?: ClassValue; +}) => ; diff --git a/frontend/src/js/ui-components/InfoTooltip.tsx b/frontend/src/js/ui-components/InfoTooltip.tsx index 28b3506533..14e85e5c79 100644 --- a/frontend/src/js/ui-components/InfoTooltip.tsx +++ b/frontend/src/js/ui-components/InfoTooltip.tsx @@ -2,7 +2,7 @@ import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons"; import type { ReactElement } from "react"; import { tv } from "tailwind-variants"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "./Icon"; import { Tooltip, @@ -35,7 +35,7 @@ const InfoTooltip = ({ aria-label="Info" className={spanContainer({ className })} > - + {text ? ( diff --git a/frontend/src/js/ui-components/InputCheckbox.tsx b/frontend/src/js/ui-components/InputCheckbox.tsx index 6674c4d230..b9b9fab31f 100644 --- a/frontend/src/js/ui-components/InputCheckbox.tsx +++ b/frontend/src/js/ui-components/InputCheckbox.tsx @@ -1,7 +1,7 @@ import { faCheck } from "@fortawesome/free-solid-svg-icons"; import { tv } from "tailwind-variants"; import { exists } from "../common/helpers/exists"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "./Icon"; import InfoTooltip from "./InfoTooltip"; import { Tooltip, @@ -79,7 +79,7 @@ const InputCheckbox = ({ > {!!value && (
- +
)} diff --git a/frontend/src/js/ui-components/InputDateRange.tsx b/frontend/src/js/ui-components/InputDateRange.tsx index e3240f53df..53611c0fde 100644 --- a/frontend/src/js/ui-components/InputDateRange.tsx +++ b/frontend/src/js/ui-components/InputDateRange.tsx @@ -13,7 +13,7 @@ import { parseDateToState, } from "../common/helpers/dateHelper"; import { exists } from "../common/helpers/exists"; -import FaIcon from "../icon/FaIcon"; +import { Icon } from "./Icon"; import InfoTooltip from "./InfoTooltip"; import InputDate from "./InputDate/InputDate"; @@ -171,7 +171,7 @@ const InputDateRange = ({ return (