{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 = ({
+
+ 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 (