From c758dd9cbbb6f401d2512216eab974fde15146b2 Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Thu, 3 Sep 2026 15:09:08 +0200 Subject: [PATCH 01/16] refactor(frontend): Button without className; toggles, adornments and special shapes on react-aria --- frontend/src/js/authorization/LoginPage.tsx | 28 +++++---- frontend/src/js/button/BadgeToggleButton.tsx | 44 ++++++------- frontend/src/js/button/DownloadButton.tsx | 2 +- .../js/button/QueryResultHistoryButton.tsx | 1 - frontend/src/js/concept-trees/ConceptTree.tsx | 6 +- .../js/concept-trees/ConceptTreeSearchBox.tsx | 3 +- frontend/src/js/editor-v2/EditorV2.tsx | 54 +++++++++------- .../editor-v2/date-restriction/DateModal.tsx | 19 ++---- .../src/js/entity-history/ContentControl.tsx | 51 +++++++-------- .../src/js/entity-history/DetailControl.tsx | 48 +++++++------- frontend/src/js/entity-history/History.tsx | 9 ++- .../js/entity-history/InteractionControl.tsx | 19 ++---- frontend/src/js/entity-history/Navigation.tsx | 62 +++++++------------ .../src/js/entity-history/SearchEntities.tsx | 23 +++---- .../src/js/entity-history/SidebarControl.tsx | 31 ++++++++++ .../js/entity-history/VisibilityControl.tsx | 11 ++-- .../timeline-search/SearchControl.tsx | 12 ++-- .../src/js/error-fallback/ErrorFallback.tsx | 24 +++---- frontend/src/js/external-forms/FormHeader.tsx | 4 +- .../src/js/external-forms/FormsNavigation.tsx | 29 ++++----- .../form-components/DropzoneList.tsx | 17 ++--- .../form-components/DynamicInputGroup.tsx | 19 +++--- .../form-concept-group/FormConceptGroup.tsx | 17 ++--- .../form/fields/DisclosureListField.tsx | 58 +++++++++-------- frontend/src/js/info-pane/InfoPane.tsx | 11 ++-- .../src/js/info-pane/InfoPaneCollapsed.tsx | 15 +++-- frontend/src/js/info-pane/InfoPaneHeader.tsx | 20 +++--- frontend/src/js/modal/DeleteModal.tsx | 8 ++- frontend/src/js/preview/ScrollBox.tsx | 14 +++-- .../previous-queries/list/AddFolderModal.tsx | 3 +- .../src/js/previous-queries/list/Folders.tsx | 25 ++++---- .../list/FoldersToggleButton.tsx | 1 - .../js/previous-queries/list/ProjectItem.tsx | 3 +- .../previous-queries/list/ProjectItemsTab.tsx | 2 +- .../list/ShareProjectItemModal.tsx | 25 ++++---- .../upload/CSVColumnPicker.tsx | 33 ++++------ .../js/query-group-modal/QueryGroupModal.tsx | 20 ++---- .../src/js/query-node-editor/ConceptEntry.tsx | 1 - .../src/js/query-runner/QueryRunnerButton.tsx | 21 +++++-- frontend/src/js/search-bar/SearchBar.tsx | 16 +++-- .../src/js/snack-message/SnackMessage.tsx | 14 +++-- .../QueryGroupActions.tsx | 38 ++++++------ .../QueryNodeActions.tsx | 4 +- frontend/src/js/ui-components/BaseInput.tsx | 17 +++-- frontend/src/js/ui-components/Button.tsx | 22 +++---- .../ui-components/DropzoneWithFileInput.tsx | 12 ++-- .../src/js/ui-components/EditableTagsForm.tsx | 29 ++++----- .../src/js/ui-components/EditableText.tsx | 25 ++++---- .../src/js/ui-components/EditableTextForm.tsx | 29 ++++----- .../src/js/ui-components/Icon.stories.tsx | 2 +- .../js/ui-components/InputDate/InputDate.tsx | 17 +++-- .../InputSelect/InputSelectComponents.tsx | 27 +++++--- .../InputTextarea/InputTextarea.tsx | 15 +++-- .../UploadConceptListModal.tsx | 7 +-- frontend/src/localization/de.json | 3 +- frontend/src/localization/en.json | 3 +- 56 files changed, 541 insertions(+), 532 deletions(-) create mode 100644 frontend/src/js/entity-history/SidebarControl.tsx diff --git a/frontend/src/js/authorization/LoginPage.tsx b/frontend/src/js/authorization/LoginPage.tsx index b819d57c3e..c908448716 100644 --- a/frontend/src/js/authorization/LoginPage.tsx +++ b/frontend/src/js/authorization/LoginPage.tsx @@ -42,9 +42,7 @@ const form = tv({ ], }); -const submitButton = tv({ - base: ["flex items-center justify-center", "mt-[35px]", "w-[255px]"], -}); +const submitButton = tv({ base: ["grid", "mt-[35px]", "w-[255px]"] }); const LoginPage = () => { const [user, setUser] = useState(""); @@ -115,16 +113,20 @@ const LoginPage = () => { disabled: loading, }} /> - +
+ +
diff --git a/frontend/src/js/button/BadgeToggleButton.tsx b/frontend/src/js/button/BadgeToggleButton.tsx index 82bf3c0e94..ca6139eef1 100644 --- a/frontend/src/js/button/BadgeToggleButton.tsx +++ b/frontend/src/js/button/BadgeToggleButton.tsx @@ -1,26 +1,22 @@ import type { ReactNode } from "react"; +import { ToggleButton } from "react-aria-components"; import { useHotkeys } from "react-hotkeys-hook"; - import { tv } from "tailwind-variants"; -import { Button } from "../ui-components/Button"; - +// a chip that toggles: dotted while off, solid in the primary color while on const badgeToggleButton = tv({ - base: ["h-auto px-1 py-px", "text-sm", "font-bold"], - variants: { - active: { - true: [ - "border-2 border-primary-500", - "bg-white hover:bg-gray-50", - "text-primary-500", - ], - false: [ - "border-2 border-dotted border-gray-100", - "hover:bg-bg-50", - "text-gray-500", - ], - }, - }, + base: [ + "inline-flex items-center", + "rounded", + "px-1 py-px", + "border-2 border-dotted border-gray-100", + "text-sm font-bold text-gray-500 whitespace-nowrap", + "cursor-pointer", + "hover:bg-bg-50", + "data-selected:border-solid data-selected:border-primary-500", + "data-selected:bg-white data-selected:hover:bg-gray-50", + "data-selected:text-primary-500", + ], }); const superScript = tv({ @@ -51,16 +47,14 @@ export const BadgeToggleButton = ({ useHotkeys(hotkey || "", onClick, { enabled: !!hotkey }, [hotkey, onClick]); return ( - + ); }; diff --git a/frontend/src/js/button/DownloadButton.tsx b/frontend/src/js/button/DownloadButton.tsx index 4dbd6587cd..6cd6dd3db0 100644 --- a/frontend/src/js/button/DownloadButton.tsx +++ b/frontend/src/js/button/DownloadButton.tsx @@ -68,7 +68,7 @@ const DownloadButton = ({ return ( - {t("conceptTreeList.error", { tree: label })} diff --git a/frontend/src/js/concept-trees/ConceptTreeSearchBox.tsx b/frontend/src/js/concept-trees/ConceptTreeSearchBox.tsx index 097e1245a4..d3e00e3942 100644 --- a/frontend/src/js/concept-trees/ConceptTreeSearchBox.tsx +++ b/frontend/src/js/concept-trees/ConceptTreeSearchBox.tsx @@ -83,7 +83,7 @@ const ConceptTreeSearchBox = ({ className }: { className?: string }) => { duration: (search.duration / 1000.0).toFixed(2), })}

-
+
{showMismatches ? t("conceptTreeList.showingMismatches") @@ -91,7 +91,6 @@ const ConceptTreeSearchBox = ({ className }: { className?: string }) => { + )} {featureConnectorRotate && selectedNode && connection && ( @@ -425,21 +435,19 @@ export function EditorV2({ )} - - - - - {t("editorV2.clear")} - + + + {t("editorV2.clear")} + +
)} diff --git a/frontend/src/js/editor-v2/date-restriction/DateModal.tsx b/frontend/src/js/editor-v2/date-restriction/DateModal.tsx index ab69219fb6..321bbb61fb 100644 --- a/frontend/src/js/editor-v2/date-restriction/DateModal.tsx +++ b/frontend/src/js/editor-v2/date-restriction/DateModal.tsx @@ -22,10 +22,6 @@ const sectionHeadline = tv({ ], }); -const resetAll = tv({ - base: ["ml-5", "text-primary-500", "font-bold"], -}); - export const DateModal = ({ onClose, dateRange = {}, @@ -53,15 +49,12 @@ export const DateModal = ({ const labelSuffix = useMemo(() => { return hasActiveDate ? ( - + + + ) : null; }, [t, hasActiveDate, onResetDates]); diff --git a/frontend/src/js/entity-history/ContentControl.tsx b/frontend/src/js/entity-history/ContentControl.tsx index d4fa8635f4..100ccc6fed 100644 --- a/frontend/src/js/entity-history/ContentControl.tsx +++ b/frontend/src/js/entity-history/ContentControl.tsx @@ -5,11 +5,11 @@ import { faInfo, } from "@fortawesome/free-solid-svg-icons"; import { memo, useMemo, useState } from "react"; +import { ToggleButtonGroup } from "react-aria-components"; import { useTranslation } from "react-i18next"; -import { Button } from "../ui-components/Button"; import { Icon } from "../ui-components/Icon"; - import { Tooltip, TooltipTrigger } from "../ui-components/Tooltip"; +import { SidebarToggle } from "./SidebarControl"; export type ContentType = | "groupId" @@ -55,29 +55,30 @@ const ContentControl = ({ value, onChange }: Props) => { ); return ( -
- {options.map((option) => { - const active = value[option.key]; - return ( - - - {option.tooltip} - - ); - })} -
+ value[o.key]).map((o) => o.key)} + onSelectionChange={(keys) => + onChange({ + ...value, + ...Object.fromEntries(options.map((o) => [o.key, keys.has(o.key)])), + }) + } + > + {options.map((option) => ( + + + + + {option.tooltip} + + ))} + ); }; diff --git a/frontend/src/js/entity-history/DetailControl.tsx b/frontend/src/js/entity-history/DetailControl.tsx index 9c1140255d..79baeea773 100644 --- a/frontend/src/js/entity-history/DetailControl.tsx +++ b/frontend/src/js/entity-history/DetailControl.tsx @@ -5,14 +5,18 @@ import { faCircleDot, } from "@fortawesome/free-solid-svg-icons"; import { type Dispatch, memo, type SetStateAction, useMemo } from "react"; +import { type Key, ToggleButtonGroup } from "react-aria-components"; import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; -import { Button } from "../ui-components/Button"; import { Icon } from "../ui-components/Icon"; - import { Tooltip, TooltipTrigger } from "../ui-components/Tooltip"; +import { SidebarToggle } from "./SidebarControl"; const root = tv({ base: "flex flex-col items-center" }); + +const detailLevels: DetailLevel[] = ["summary", "detail", "full"]; +const isDetailLevel = (key: Key): key is DetailLevel => + detailLevels.some((level) => level === key); export type DetailLevel = "summary" | "detail" | "full"; interface Props { @@ -53,26 +57,26 @@ export const DetailControl = memo( ({ className, detailLevel, setDetailLevel }: Props) => { const navOptions = useButtonConfig(); return ( -
- {navOptions.map(({ value, icon, tooltip }) => { - const selected = value === detailLevel; - - return ( - - - {tooltip} - - ); - })} -
+ { + const [key] = keys; + if (key !== undefined && isDetailLevel(key)) setDetailLevel(key); + }} + > + {navOptions.map(({ value, icon, tooltip }) => ( + + + + + {tooltip} + + ))} + ); }, ); diff --git a/frontend/src/js/entity-history/History.tsx b/frontend/src/js/entity-history/History.tsx index 4f698d4eb0..9608d72de9 100644 --- a/frontend/src/js/entity-history/History.tsx +++ b/frontend/src/js/entity-history/History.tsx @@ -1,4 +1,5 @@ import { useCallback, useMemo, useState } from "react"; +import { Toolbar } from "react-aria-components"; import { ErrorBoundary } from "react-error-boundary"; import { useHotkeys } from "react-hotkeys-hook"; import { useTranslation } from "react-i18next"; @@ -188,7 +189,11 @@ export const History = () => { )}
-
+ { /> )}
-
+ - + {t("history.closeAll")} - + {t("history.openAll")} diff --git a/frontend/src/js/entity-history/Navigation.tsx b/frontend/src/js/entity-history/Navigation.tsx index 9babc6aba5..34cf574946 100644 --- a/frontend/src/js/entity-history/Navigation.tsx +++ b/frontend/src/js/entity-history/Navigation.tsx @@ -61,14 +61,6 @@ const loadHistoryDropzone = tv({ ], }); -const containedButton = tv({ - base: ["grow", "justify-center"], -}); - -const fullWidthButton = tv({ - base: ["w-full", "justify-center"], -}); - export const Navigation = memo( ({ blurred, @@ -145,27 +137,27 @@ export const Navigation = memo( }} >
- - - {backButtonWarning} - - {!empty && ( - - - + {backButtonWarning} + +
+ {!empty && ( +
+ + + +
)} {!empty && ( @@ -179,13 +171,12 @@ export const Navigation = memo( )}
{!empty && ( -
+
@@ -211,26 +202,21 @@ export const Navigation = memo( {!empty && ( <> -
+
{`${t("history.nextButtonLabel")} (shift + ⬇)`}
-
+
- diff --git a/frontend/src/js/entity-history/SearchEntities.tsx b/frontend/src/js/entity-history/SearchEntities.tsx index 31679ac5d8..554258367c 100644 --- a/frontend/src/js/entity-history/SearchEntities.tsx +++ b/frontend/src/js/entity-history/SearchEntities.tsx @@ -212,10 +212,6 @@ const root = tv({ ], }); -const submitButton = tv({ - base: ["flex items-center justify-center", "w-full", "gap-[14px]"], -}); - const noop = () => {}; export const SearchEntitiesComponent = ({ @@ -245,15 +241,16 @@ export const SearchEntitiesComponent = ({ onSwitchFilterMode={noop} onLoadFilterSuggestions={loadFilterSuggestions} /> - +
+ +
); }; diff --git a/frontend/src/js/entity-history/SidebarControl.tsx b/frontend/src/js/entity-history/SidebarControl.tsx new file mode 100644 index 0000000000..1fa2f93d12 --- /dev/null +++ b/frontend/src/js/entity-history/SidebarControl.tsx @@ -0,0 +1,31 @@ +import { + Button as RacButton, + type ButtonProps as RacButtonProps, + ToggleButton as RacToggleButton, + type ToggleButtonProps as RacToggleButtonProps, +} from "react-aria-components"; +import { tv } from "tailwind-variants"; + +// The history sidebar's vertical toolbar: square controls, a selected one +// shows in the primary color. Local to the history, not a design-system part. +const control = tv({ + base: [ + "inline-flex items-center justify-center", + "size-[30px]", + "rounded", + "border border-transparent", + "text-gray-800", + "cursor-pointer", + "hover:bg-gray-50", + "data-selected:bg-gray-100 data-selected:text-primary-500", + "transition-colors duration-100", + ], +}); + +export const SidebarToggle = (props: RacToggleButtonProps) => ( + +); + +export const SidebarAction = (props: RacButtonProps) => ( + +); diff --git a/frontend/src/js/entity-history/VisibilityControl.tsx b/frontend/src/js/entity-history/VisibilityControl.tsx index 1f605a8366..275154c5eb 100644 --- a/frontend/src/js/entity-history/VisibilityControl.tsx +++ b/frontend/src/js/entity-history/VisibilityControl.tsx @@ -1,10 +1,9 @@ import { faEye, faEyeSlash } from "@fortawesome/free-regular-svg-icons"; import { memo } from "react"; import { useTranslation } from "react-i18next"; -import { Button } from "../ui-components/Button"; import { Icon } from "../ui-components/Icon"; - import { Tooltip, TooltipTrigger } from "../ui-components/Tooltip"; +import { SidebarToggle } from "./SidebarControl"; const VisibilityControl = ({ blurred, @@ -18,13 +17,13 @@ const VisibilityControl = ({ return (
- + {t("history.blurred")}
diff --git a/frontend/src/js/entity-history/timeline-search/SearchControl.tsx b/frontend/src/js/entity-history/timeline-search/SearchControl.tsx index f5fc79ce12..c29d648c44 100644 --- a/frontend/src/js/entity-history/timeline-search/SearchControl.tsx +++ b/frontend/src/js/entity-history/timeline-search/SearchControl.tsx @@ -1,28 +1,26 @@ import { faSearch } from "@fortawesome/free-solid-svg-icons"; import { memo } from "react"; import { useTranslation } from "react-i18next"; -import { Button } from "../../ui-components/Button"; import { Icon } from "../../ui-components/Icon"; import { Tooltip, TooltipTrigger } from "../../ui-components/Tooltip"; +import { SidebarToggle } from "../SidebarControl"; import { useTimelineSearch } from "./timelineSearchState"; const SearchControl = () => { const { t } = useTranslation(); const { searchVisible, setSearchVisible } = useTimelineSearch(); - const toggleSearchVisible = () => setSearchVisible(!searchVisible); return (
- + {t("history.search")}
diff --git a/frontend/src/js/error-fallback/ErrorFallback.tsx b/frontend/src/js/error-fallback/ErrorFallback.tsx index c08c19f8a5..e27162136b 100644 --- a/frontend/src/js/error-fallback/ErrorFallback.tsx +++ b/frontend/src/js/error-fallback/ErrorFallback.tsx @@ -35,25 +35,21 @@ const ErrorFallback = ({ {allowFullRefresh && ( <>

{t("error.reloadDescription")}

- +
+ +
)} {onReset && ( <>

{t("error.resetDescription")}

- +
+ +
)}
diff --git a/frontend/src/js/external-forms/FormHeader.tsx b/frontend/src/js/external-forms/FormHeader.tsx index 3465c33428..35831d1189 100644 --- a/frontend/src/js/external-forms/FormHeader.tsx +++ b/frontend/src/js/external-forms/FormHeader.tsx @@ -28,8 +28,8 @@ const FormHeader = ({
); diff --git a/frontend/src/js/external-forms/form-components/DropzoneList.tsx b/frontend/src/js/external-forms/form-components/DropzoneList.tsx index 54e6f5f4fd..3f9d367882 100644 --- a/frontend/src/js/external-forms/form-components/DropzoneList.tsx +++ b/frontend/src/js/external-forms/form-components/DropzoneList.tsx @@ -93,14 +93,15 @@ const DropzoneList = ({ /> )}
- +
+ +
{item}
diff --git a/frontend/src/js/external-forms/form-components/DynamicInputGroup.tsx b/frontend/src/js/external-forms/form-components/DynamicInputGroup.tsx index ac4a296147..e86321dabb 100644 --- a/frontend/src/js/external-forms/form-components/DynamicInputGroup.tsx +++ b/frontend/src/js/external-forms/form-components/DynamicInputGroup.tsx @@ -18,7 +18,7 @@ const container = tv({ }); const removeButton = tv({ - base: ["absolute -top-[7px] -right-[7px]", "z-1", "bg-white"], + base: ["absolute -top-[7px] -right-[7px]", "z-1", "rounded bg-white"], }); const groupItem = tv({ @@ -51,14 +51,15 @@ const DynamicInputGroup = ({ you can also just delete the following constraint: */} {limit !== 1 && ( - +
+ +
)}
))} diff --git a/frontend/src/js/external-forms/form-concept-group/FormConceptGroup.tsx b/frontend/src/js/external-forms/form-concept-group/FormConceptGroup.tsx index 8c5e054af1..1ec00cbc26 100644 --- a/frontend/src/js/external-forms/form-concept-group/FormConceptGroup.tsx +++ b/frontend/src/js/external-forms/form-concept-group/FormConceptGroup.tsx @@ -308,14 +308,15 @@ const FormConceptGroup = (props: Props) => { <> {props.label} {allowExtendedCopying && ( - + + + )} } diff --git a/frontend/src/js/external-forms/form/fields/DisclosureListField.tsx b/frontend/src/js/external-forms/form/fields/DisclosureListField.tsx index 99bd4990b7..6b66130557 100644 --- a/frontend/src/js/external-forms/form/fields/DisclosureListField.tsx +++ b/frontend/src/js/external-forms/form/fields/DisclosureListField.tsx @@ -81,14 +81,11 @@ const DisclosureField = ({ )} {field.creatable && canRemove && ( - +
+ +
)}
@@ -224,28 +221,29 @@ export const DisclosureListField = ({ /> ))} {field.creatable && ( - +
+ +
)}
); diff --git a/frontend/src/js/info-pane/InfoPane.tsx b/frontend/src/js/info-pane/InfoPane.tsx index 98b1dfcdb8..64f1e02067 100644 --- a/frontend/src/js/info-pane/InfoPane.tsx +++ b/frontend/src/js/info-pane/InfoPane.tsx @@ -50,10 +50,14 @@ const head = tv({ const typeIcon = tv({ base: ["mt-px", "mr-[6px]", "text-primary-500"] }); -const tackButton = tv({ base: "ml-[5px]" }); - const pinnedLabel = tv({ - base: ["flex flex-row items-start", "m-0", "leading-[1.2]", "text-sm"], + base: [ + "flex flex-row items-start", + "gap-[5px]", + "m-0", + "leading-[1.2]", + "text-sm", + ], }); const descriptionText = tv({ @@ -189,7 +193,6 @@ const InfoPane = () => { size="sm" aria-pressed onPress={onToggleAdditionalInfos} - className={tackButton()} > diff --git a/frontend/src/js/info-pane/InfoPaneCollapsed.tsx b/frontend/src/js/info-pane/InfoPaneCollapsed.tsx index 56395adc4e..320e5ac661 100644 --- a/frontend/src/js/info-pane/InfoPaneCollapsed.tsx +++ b/frontend/src/js/info-pane/InfoPaneCollapsed.tsx @@ -1,18 +1,21 @@ import { faAngleRight } from "@fortawesome/free-solid-svg-icons"; +import { Button as RacButton } from "react-aria-components"; import { useDispatch } from "react-redux"; import { tv } from "tailwind-variants"; -import { Button } from "../ui-components/Button"; import { Icon } from "../ui-components/Icon"; import { toggleInfoPane } from "./actions"; +// a strip that fills the collapsed pane; the chevron sits at its top const button = tv({ base: [ "absolute top-[40px] right-0 bottom-0", - "w-full h-auto", + "w-full", + "flex justify-center items-start", "pt-3", - "rounded-none", - "items-start", + "text-gray-800", + "cursor-pointer", + "hover:bg-gray-50", ], }); @@ -22,9 +25,9 @@ const InfoPaneCollapsed = () => { return (
- +
); }; diff --git a/frontend/src/js/info-pane/InfoPaneHeader.tsx b/frontend/src/js/info-pane/InfoPaneHeader.tsx index 8149c68d08..fc711366b1 100644 --- a/frontend/src/js/info-pane/InfoPaneHeader.tsx +++ b/frontend/src/js/info-pane/InfoPaneHeader.tsx @@ -1,9 +1,9 @@ import { faAngleLeft } from "@fortawesome/free-solid-svg-icons"; import { memo } from "react"; +import { Button as RacButton } from "react-aria-components"; import { useTranslation } from "react-i18next"; import { useDispatch } from "react-redux"; import { tv } from "tailwind-variants"; -import { Button } from "../ui-components/Button"; import { Icon } from "../ui-components/Icon"; import { toggleInfoPane } from "./actions"; @@ -23,8 +23,16 @@ const header = tv({ ], }); +// as tall as the header it sits in const toggleButton = tv({ - base: ["absolute top-[40px] right-0", "h-[39px]", "rounded-none"], + base: [ + "absolute top-[40px] right-0", + "h-[39px] w-[30px]", + "flex items-center justify-center", + "text-gray-800", + "cursor-pointer", + "hover:bg-gray-50", + ], }); export const InfoPaneHeader = memo(() => { @@ -35,13 +43,9 @@ export const InfoPaneHeader = memo(() => { return ( <> - +

{t("infoPane.headline")}

); diff --git a/frontend/src/js/modal/DeleteModal.tsx b/frontend/src/js/modal/DeleteModal.tsx index 14a01725f2..51532daf52 100644 --- a/frontend/src/js/modal/DeleteModal.tsx +++ b/frontend/src/js/modal/DeleteModal.tsx @@ -29,9 +29,11 @@ const DeleteModal = ({
{description &&

{description}

}
- +
+ +
diff --git a/frontend/src/js/preview/ScrollBox.tsx b/frontend/src/js/preview/ScrollBox.tsx index 12b08b0370..e0d74bd8ba 100644 --- a/frontend/src/js/preview/ScrollBox.tsx +++ b/frontend/src/js/preview/ScrollBox.tsx @@ -6,8 +6,8 @@ import { useRef, useState, } from "react"; +import { Button as RacButton } from "react-aria-components"; import { tv } from "tailwind-variants"; -import { Button } from "../ui-components/Button"; import { Icon } from "../ui-components/Icon"; const root = tv({ base: "overflow-auto" }); @@ -18,9 +18,12 @@ const scrollTopButton = tv({ "z-3", "flex justify-center", "h-[50px] w-[50px]", + "items-center", "rounded-full", "border border-gray-500", - "bg-white", + "bg-white hover:bg-gray-50", + "text-gray-800", + "cursor-pointer", "shadow-[0_0_5px_0_rgba(0,0,0,0.2)]", ], }); @@ -50,15 +53,14 @@ export default function ScrollBox({ return (
{showButton && ( - + )} {children}
diff --git a/frontend/src/js/previous-queries/list/AddFolderModal.tsx b/frontend/src/js/previous-queries/list/AddFolderModal.tsx index 23b9c1fcd6..167e841cec 100644 --- a/frontend/src/js/previous-queries/list/AddFolderModal.tsx +++ b/frontend/src/js/previous-queries/list/AddFolderModal.tsx @@ -31,13 +31,12 @@ const AddFolderModal = ({ onClose, onSubmit, isValidName }: Props) => { onChange={(value) => setFolderName((value as string | null) || "")} inputProps={{ autoFocus: true }} /> -
+
+
+ +
{t("common.delete")} diff --git a/frontend/src/js/previous-queries/list/FoldersToggleButton.tsx b/frontend/src/js/previous-queries/list/FoldersToggleButton.tsx index d9973b3ca3..402b22c30f 100644 --- a/frontend/src/js/previous-queries/list/FoldersToggleButton.tsx +++ b/frontend/src/js/previous-queries/list/FoldersToggleButton.tsx @@ -21,7 +21,6 @@ const FoldersToggleButton = ({ intent="secondary" onPress={onClick} aria-pressed={active} - className="mr-[5px]" > diff --git a/frontend/src/js/previous-queries/list/ProjectItem.tsx b/frontend/src/js/previous-queries/list/ProjectItem.tsx index bd552596f9..c5c63305ce 100644 --- a/frontend/src/js/previous-queries/list/ProjectItem.tsx +++ b/frontend/src/js/previous-queries/list/ProjectItem.tsx @@ -263,14 +263,13 @@ const ProjectItem = ({ )}
-
+
- {shareLabel} - +
+ + + {shareLabel} + +
diff --git a/frontend/src/js/previous-queries/upload/CSVColumnPicker.tsx b/frontend/src/js/previous-queries/upload/CSVColumnPicker.tsx index 08f082ca73..6bcc37559a 100644 --- a/frontend/src/js/previous-queries/upload/CSVColumnPicker.tsx +++ b/frontend/src/js/previous-queries/upload/CSVColumnPicker.tsx @@ -350,27 +350,24 @@ const CSVColumnPicker = ({ )}
)} -
+
{uploadResult && (uploadResult.unreadableDate.length > 0 || uploadResult.unresolvedId.length > 0) && ( - +
+ +
)} {uploadResult && ( )} {uploadResult ? ( - ) : ( + + + ) : null; }, [t, hasActiveDate, onResetAllDates]); diff --git a/frontend/src/js/query-node-editor/ConceptEntry.tsx b/frontend/src/js/query-node-editor/ConceptEntry.tsx index e1052c3ce8..d79b397bc3 100644 --- a/frontend/src/js/query-node-editor/ConceptEntry.tsx +++ b/frontend/src/js/query-node-editor/ConceptEntry.tsx @@ -62,7 +62,6 @@ const ConceptEntry = ({ intent="tertiary" size="sm" onPress={() => onRemoveConcept(conceptId)} - className="shrink-0" > diff --git a/frontend/src/js/query-runner/QueryRunnerButton.tsx b/frontend/src/js/query-runner/QueryRunnerButton.tsx index 67d38f574f..8e9b74c607 100644 --- a/frontend/src/js/query-runner/QueryRunnerButton.tsx +++ b/frontend/src/js/query-runner/QueryRunnerButton.tsx @@ -1,9 +1,8 @@ import { faPlay, faSpinner, faStop } from "@fortawesome/free-solid-svg-icons"; import type { Ref } from "react"; +import { Button as RacButton } from "react-aria-components"; import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; - -import { Button } from "../ui-components/Button"; import { Icon } from "../ui-components/Icon"; const left = tv({ @@ -33,8 +32,19 @@ const runnerLabel = tv({ ], }); +// two-tone: the icon part is filled, the label part stays white const button = tv({ - base: ["group/runner", "p-0", "overflow-hidden", "border-primary-500"], + base: [ + "group/runner", + "inline-flex items-center", + "h-[30px]", + "overflow-hidden", + "rounded", + "border border-primary-500", + "text-sm", + "cursor-pointer", + "disabled:cursor-not-allowed disabled:opacity-40", + ], }); function getIcon(loading: boolean, running: boolean) { @@ -63,8 +73,7 @@ const QueryRunnerButton = ({ return (
- +
); }; diff --git a/frontend/src/js/search-bar/SearchBar.tsx b/frontend/src/js/search-bar/SearchBar.tsx index 251de7bc78..da164e94bb 100644 --- a/frontend/src/js/search-bar/SearchBar.tsx +++ b/frontend/src/js/search-bar/SearchBar.tsx @@ -1,9 +1,9 @@ import { faSearch } from "@fortawesome/free-solid-svg-icons"; import { memo, useEffect, useState } from "react"; +import { Button as RacButton } from "react-aria-components"; import { tv } from "tailwind-variants"; import { exists } from "../common/helpers/exists"; import BaseInput from "../ui-components/BaseInput"; -import { Button } from "../ui-components/Button"; import { Icon } from "../ui-components/Icon"; const inputContainer = tv({ base: ["relative", "grow"] }); @@ -26,7 +26,14 @@ const right = tv({ ], }); -const searchButton = tv({ base: "text-gray-500" }); +const searchButton = tv({ + base: [ + "inline-flex items-center justify-center", + "size-[30px]", + "text-gray-500 hover:text-gray-800", + "cursor-pointer", + ], +}); interface Props { className?: string; @@ -71,14 +78,13 @@ const SearchBar = ({ /> {exists(localSearchTerm) && (
- +
)}
diff --git a/frontend/src/js/snack-message/SnackMessage.tsx b/frontend/src/js/snack-message/SnackMessage.tsx index b18d69c4a0..ae1ceb72d7 100644 --- a/frontend/src/js/snack-message/SnackMessage.tsx +++ b/frontend/src/js/snack-message/SnackMessage.tsx @@ -1,11 +1,11 @@ import { faTimes } from "@fortawesome/free-solid-svg-icons"; import { memo, useRef } from "react"; +import { Button as RacButton } from "react-aria-components"; import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; import { tv } from "tailwind-variants"; import type { StateT } from "../app/reducers"; import { useClickOutside } from "../common/helpers/useClickOutside"; -import { Button } from "../ui-components/Button"; import { Icon } from "../ui-components/Icon"; import { resetMessage as resetMessageAction } from "./actions"; import type { SnackMessageStateT } from "./reducer"; @@ -28,7 +28,12 @@ const root = tv({ }); const clearZone = tv({ - base: ["absolute top-3 right-4", "z-11", "opacity-80 hover:opacity-100"], + base: [ + "absolute top-3 right-4", + "z-11", + "opacity-80 hover:opacity-100", + "cursor-pointer", + ], }); export const SnackMessage = memo(function SnackMessageComponent() { @@ -53,14 +58,13 @@ export const SnackMessage = memo(function SnackMessageComponent() {
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: messages are our own i18n text */}
- +
)} diff --git a/frontend/src/js/standard-query-editor/QueryGroupActions.tsx b/frontend/src/js/standard-query-editor/QueryGroupActions.tsx index ae18517cd5..1a0debd919 100644 --- a/frontend/src/js/standard-query-editor/QueryGroupActions.tsx +++ b/frontend/src/js/standard-query-editor/QueryGroupActions.tsx @@ -1,6 +1,7 @@ import { faCalendar } from "@fortawesome/free-regular-svg-icons"; import { faBan, faTimes } from "@fortawesome/free-solid-svg-icons"; import { memo } from "react"; +import { ToggleButton } from "react-aria-components"; import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; import { Button } from "../ui-components/Button"; @@ -17,19 +18,19 @@ const actions = tv({ base: ["mb-[6px]", "h-[18px]", "text-left"], }); -const dateButton = tv({ - base: "mr-[5px]", - variants: { - active: { - true: "underline", - false: "no-underline", - }, - }, +// excluding is a warning state: selected shows red, not the usual primary +const excludeToggle = tv({ + base: [ + "inline-flex items-center gap-[5px]", + "h-6 px-2", + "rounded border border-transparent", + "text-xs text-gray-800", + "cursor-pointer", + "hover:bg-gray-50", + "data-selected:text-red", + ], }); -// excluding is a warning state: pressed shows red, not the usual primary -const excludeButton = tv({ base: ["mr-[5px]", "aria-pressed:text-red"] }); - interface PropsT { excludeActive: boolean; dateActive: boolean; @@ -49,18 +50,16 @@ const QueryGroupActions = ({ return (
-
+
- + {t("help.queryEditorExclude")} @@ -69,7 +68,6 @@ const QueryGroupActions = ({ size="sm" aria-pressed={dateActive} onPress={onDateClick} - className={dateButton({ active: dateActive })} > {t("queryEditor.date")} diff --git a/frontend/src/js/standard-query-editor/QueryNodeActions.tsx b/frontend/src/js/standard-query-editor/QueryNodeActions.tsx index fc36aa6b16..efef2cc068 100644 --- a/frontend/src/js/standard-query-editor/QueryNodeActions.tsx +++ b/frontend/src/js/standard-query-editor/QueryNodeActions.tsx @@ -66,11 +66,11 @@ const QueryNodeActions = (props: Props) => { diff --git a/frontend/src/js/ui-components/BaseInput.tsx b/frontend/src/js/ui-components/BaseInput.tsx index 34d4d368f7..a2d4ffd996 100644 --- a/frontend/src/js/ui-components/BaseInput.tsx +++ b/frontend/src/js/ui-components/BaseInput.tsx @@ -9,12 +9,12 @@ import { type Ref, useCallback, } from "react"; +import { Button as RacButton } from "react-aria-components"; import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; import type { CurrencyConfigT } from "../api/types"; import { isEmpty } from "../common/helpers/commonHelper"; import { exists } from "../common/helpers/exists"; -import { Button } from "./Button"; import CurrencyInput from "./CurrencyInput"; import { Icon } from "./Icon"; import { @@ -50,13 +50,14 @@ const redIcon = tv({ base: ["opacity-80", "text-red"] }); const absoluteWrap = tv({ base: "absolute top-[5px] right-[35px]" }); -const clearZoneIconButton = tv({ +const clearButton = tv({ base: [ "absolute top-0 right-[10px]", - "h-full", + "h-full px-1", "flex items-center", + "text-gray-800 hover:text-red", "cursor-pointer", - "hover:text-red", + "disabled:cursor-not-allowed disabled:opacity-40", ], }); @@ -204,17 +205,15 @@ const BaseInput = ({ {invalidText} )} - + )}
diff --git a/frontend/src/js/ui-components/Button.tsx b/frontend/src/js/ui-components/Button.tsx index 360bcc074b..33dec43026 100644 --- a/frontend/src/js/ui-components/Button.tsx +++ b/frontend/src/js/ui-components/Button.tsx @@ -9,7 +9,7 @@ import { Button as RacButton, type ButtonProps as RacButtonProps, } from "react-aria-components"; -import { type ClassValue, tv } from "tailwind-variants"; +import { tv } from "tailwind-variants"; import { Icon } from "./Icon"; @@ -72,7 +72,6 @@ export interface ButtonProps /** what the button does in its context; the look follows */ intent?: "primary" | "secondary" | "tertiary" | "danger" | "link"; size?: "sm" | "md" | "lg"; - className?: ClassValue; style?: CSSProperties; children?: ReactNode; ref?: Ref; @@ -99,21 +98,14 @@ const isIconOnly = (children: ReactNode) => { * A button whose only children are icons is square; give it an `aria-label`. * A pressed state (toggles) is `aria-pressed`, which colors the button. * `link` is for a button that reads as a text link. + * + * There is no className: layout belongs to the parent (a `grid` wrapper + * stretches a button to full width), and anything that needs another look is + * not this button but a react-aria Button styled where it lives. */ -export const Button = ({ - intent, - size, - className, - children, - ...props -}: ButtonProps) => ( +export const Button = ({ intent, size, children, ...props }: ButtonProps) => ( {children} diff --git a/frontend/src/js/ui-components/DropzoneWithFileInput.tsx b/frontend/src/js/ui-components/DropzoneWithFileInput.tsx index 0533c6b33c..c3c65aa754 100644 --- a/frontend/src/js/ui-components/DropzoneWithFileInput.tsx +++ b/frontend/src/js/ui-components/DropzoneWithFileInput.tsx @@ -139,13 +139,11 @@ const DropzoneWithFileInput = < /> )} {showImportButton && onImportLines && ( - +
+ +
)} {onSelectFile && ( - - - {t("common.save")} - +
+ + + {t("common.save")} + +
); }; diff --git a/frontend/src/js/ui-components/EditableText.tsx b/frontend/src/js/ui-components/EditableText.tsx index a709544499..d98a0e13dc 100644 --- a/frontend/src/js/ui-components/EditableText.tsx +++ b/frontend/src/js/ui-components/EditableText.tsx @@ -63,18 +63,19 @@ const EditableText = ({ /> ) : (
- - - {tooltip} - + + + + {tooltip} + + {highlightedWords && highlightedWords.length > 0 ? ( {!saveOnClickoutside && ( - - - {t("common.save")} - +
+ + + {t("common.save")} + +
)} ); diff --git a/frontend/src/js/ui-components/Icon.stories.tsx b/frontend/src/js/ui-components/Icon.stories.tsx index e059345055..ecc7886ed4 100644 --- a/frontend/src/js/ui-components/Icon.stories.tsx +++ b/frontend/src/js/ui-components/Icon.stories.tsx @@ -80,7 +80,7 @@ export const InheritsColor: Story = { - diff --git a/frontend/src/js/ui-components/InputDate/InputDate.tsx b/frontend/src/js/ui-components/InputDate/InputDate.tsx index fdaf242cf6..2c4bd89287 100644 --- a/frontend/src/js/ui-components/InputDate/InputDate.tsx +++ b/frontend/src/js/ui-components/InputDate/InputDate.tsx @@ -1,7 +1,7 @@ import { faCalendar } from "@fortawesome/free-regular-svg-icons"; import { type Ref, useRef } from "react"; +import { Button as RacButton } from "react-aria-components"; import ReactDatePicker from "react-datepicker"; -import { Button } from "../Button"; import { Icon } from "../Icon"; import "react-datepicker/dist/react-datepicker.css"; import { mergeRefs } from "react-merge-refs"; @@ -19,7 +19,13 @@ const root = tv({ }); const calendarButton = tv({ - base: "absolute top-0 left-0", + base: [ + "absolute top-0 left-0", + "h-[30px] px-[10px]", + "flex items-center", + "text-gray-800", + "cursor-pointer", + ], }); const baseInput = tv({ @@ -69,13 +75,12 @@ const InputDate = ({ }, }} /> - + ) => ( ); +// the select box's own small square buttons +const adornment = tv({ + base: [ + "inline-flex items-center justify-center", + "size-6 shrink-0", + "rounded", + "text-gray-800", + "cursor-pointer", + "hover:bg-gray-50", + "disabled:cursor-not-allowed disabled:opacity-40", + ], +}); + export const DropdownToggleButton = ({ className, ...props -}: ComponentProps) => ( - + )}
diff --git a/frontend/src/js/upload-concept-list-modal/UploadConceptListModal.tsx b/frontend/src/js/upload-concept-list-modal/UploadConceptListModal.tsx index 65fa4fc6a4..71998e2a03 100644 --- a/frontend/src/js/upload-concept-list-modal/UploadConceptListModal.tsx +++ b/frontend/src/js/upload-concept-list-modal/UploadConceptListModal.tsx @@ -570,12 +570,7 @@ const UploadConceptListModal = ({ label={t("uploadConceptListModal.includeUnresolved")} /> )} - {t("conceptTreeList.error", { tree: label })} diff --git a/frontend/src/js/editor-v2/EditorV2.tsx b/frontend/src/js/editor-v2/EditorV2.tsx index 8a82590204..beb9a1099b 100644 --- a/frontend/src/js/editor-v2/EditorV2.tsx +++ b/frontend/src/js/editor-v2/EditorV2.tsx @@ -10,7 +10,6 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { createId } from "@paralleldrive/cuid2"; import { useCallback, useEffect, useMemo, useState } from "react"; -import { ToggleButton } from "react-aria-components"; import { useHotkeys } from "react-hotkeys-hook"; import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; @@ -43,19 +42,6 @@ import { useTimeConnectionEditing } from "./time-connection/useTimeConnectionEdi import type { Tree, TreeChildrenTime } from "./types"; import { findNodeById, useGetTranslatedConnection } from "./util"; -// negating is a warning state: selected shows red, not the usual primary -const negateToggle = tv({ - base: [ - "inline-flex items-center gap-[5px]", - "h-6 px-2", - "rounded border border-transparent", - "text-xs text-gray-800", - "cursor-pointer", - "hover:bg-gray-50", - "data-selected:text-red", - ], -}); - const main = tv({ base: [ "grow", @@ -345,14 +331,16 @@ export function EditorV2({ )} {featureNegate && selectedNode && ( - {t("editorV2.negate")} - + )} {featureConnectorRotate && selectedNode && connection && ( diff --git a/frontend/src/js/entity-history/timeline/TimelineEmptyPlaceholder.tsx b/frontend/src/js/entity-history/timeline/TimelineEmptyPlaceholder.tsx index 4fc6796688..8792941096 100644 --- a/frontend/src/js/entity-history/timeline/TimelineEmptyPlaceholder.tsx +++ b/frontend/src/js/entity-history/timeline/TimelineEmptyPlaceholder.tsx @@ -22,7 +22,7 @@ const message = tv({ }); const bigIcon = tv({ - base: ["text-[120px]", "text-gray-100"], + base: ["size-[120px]", "text-gray-100"], }); export const TimelineEmptyPlaceholder = ({ diff --git a/frontend/src/js/modal/DeleteModal.tsx b/frontend/src/js/modal/DeleteModal.tsx index 51532daf52..012a3a2428 100644 --- a/frontend/src/js/modal/DeleteModal.tsx +++ b/frontend/src/js/modal/DeleteModal.tsx @@ -34,7 +34,7 @@ const DeleteModal = ({ {t("common.cancel")}
-
diff --git a/frontend/src/js/preview/SelectBox.tsx b/frontend/src/js/preview/SelectBox.tsx index a25f102bcf..37b8fa0ca9 100644 --- a/frontend/src/js/preview/SelectBox.tsx +++ b/frontend/src/js/preview/SelectBox.tsx @@ -42,7 +42,7 @@ const listItem = tv({ }); const arrow = tv({ - base: ["mt-[5px]", "text-[17px]", "text-gray-500", "cursor-pointer"], + base: ["mt-[5px]", "size-[17px]", "text-gray-500", "cursor-pointer"], }); export default function SelectBox({ diff --git a/frontend/src/js/previous-queries/upload/UploadQueryResultsModal.tsx b/frontend/src/js/previous-queries/upload/UploadQueryResultsModal.tsx index d7a7f2efb5..08ab0f6eda 100644 --- a/frontend/src/js/previous-queries/upload/UploadQueryResultsModal.tsx +++ b/frontend/src/js/previous-queries/upload/UploadQueryResultsModal.tsx @@ -12,7 +12,7 @@ import InfoTooltip from "../../ui-components/InfoTooltip"; import CSVColumnPicker, { type QueryToUploadT } from "./CSVColumnPicker"; const successIcon = tv({ - base: ["block", "mx-auto mb-[10px]", "text-[40px]", "text-green"], + base: ["block", "mx-auto mb-[10px]", "size-10", "text-green"], }); const dropzone = tv({ diff --git a/frontend/src/js/query-runner/DownloadResultsDropdownButton.tsx b/frontend/src/js/query-runner/DownloadResultsDropdownButton.tsx index e39b197d74..2882612aa8 100644 --- a/frontend/src/js/query-runner/DownloadResultsDropdownButton.tsx +++ b/frontend/src/js/query-runner/DownloadResultsDropdownButton.tsx @@ -1,34 +1,39 @@ import { faCaretDown, faDownload } from "@fortawesome/free-solid-svg-icons"; import { memo, useContext, useEffect, useMemo, useState } from "react"; -import { MenuTrigger } from "react-aria-components"; +import { MenuTrigger, Button as RacButton } from "react-aria-components"; import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; import type { ResultUrlWithLabel } from "../api/types"; import { AuthTokenContext } from "../authorization/AuthTokenProvider"; -import DownloadButton, { getFileIcon } from "../button/DownloadButton"; -import { Button } from "../ui-components/Button"; +import { getFileIcon } from "../button/DownloadButton"; 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"; +// a split button: the chosen format downloads, the caret opens the list const frame = tv({ - base: [ - "flex items-center justify-center", - "rounded", - "border border-gray-500", - "transition-opacity duration-100", - ], + base: ["inline-flex items-stretch", "h-[30px]", "rounded", "overflow-hidden"], variants: { - noborder: { true: "border-none" }, + bordered: { true: "border border-gray-500" }, }, }); -const downloadButton = tv({ - base: ["[&_button]:w-full", "[&_button]:px-[14px] [&_button]:py-2"], +const part = tv({ + base: [ + "inline-flex items-center", + "gap-[10px]", + "h-full", + "text-sm font-medium text-gray-800 whitespace-nowrap", + "cursor-pointer", + "hover:bg-gray-50", + ], + variants: { + caret: { true: "px-2", false: "px-[14px]" }, + }, }); -const separator = tv({ base: ["h-[33px] w-px", "bg-gray-500"] }); +const separator = tv({ base: ["w-px self-stretch", "bg-gray-500"] }); interface FileChoice { label: string; @@ -91,24 +96,28 @@ const DownloadResultsDropdownButton = ({ }, [fileChoice]); return ( -
+
{!tiny && ( <> - - {truncChosenLabel} - + + + {truncChosenLabel} + +
)} - + { diff --git a/frontend/src/js/query-runner/QueryRunnerButton.tsx b/frontend/src/js/query-runner/QueryRunnerButton.tsx index 8e9b74c607..4834ae9a7c 100644 --- a/frontend/src/js/query-runner/QueryRunnerButton.tsx +++ b/frontend/src/js/query-runner/QueryRunnerButton.tsx @@ -24,7 +24,7 @@ const runnerLabel = tv({ base: [ "px-[15px]", "bg-white group-hover/runner:bg-gray-50", - "text-gray-800", + "text-gray-800 font-medium", "self-stretch", "flex items-center", "whitespace-nowrap", diff --git a/frontend/src/js/standard-query-editor/EmptyQueryEditorDropzone.tsx b/frontend/src/js/standard-query-editor/EmptyQueryEditorDropzone.tsx index 764ab8a173..7d1fd55072 100644 --- a/frontend/src/js/standard-query-editor/EmptyQueryEditorDropzone.tsx +++ b/frontend/src/js/standard-query-editor/EmptyQueryEditorDropzone.tsx @@ -24,7 +24,7 @@ const textInitial = tv({ }); const arrowRight = tv({ - base: ["[grid-area:arrow]", "text-[140px]", "text-gray-100"], + base: ["[grid-area:arrow]", "size-[140px]", "text-gray-100"], }); const headline = tv({ diff --git a/frontend/src/js/standard-query-editor/QueryGroupActions.tsx b/frontend/src/js/standard-query-editor/QueryGroupActions.tsx index 1a0debd919..63720a1ae8 100644 --- a/frontend/src/js/standard-query-editor/QueryGroupActions.tsx +++ b/frontend/src/js/standard-query-editor/QueryGroupActions.tsx @@ -1,7 +1,6 @@ import { faCalendar } from "@fortawesome/free-regular-svg-icons"; import { faBan, faTimes } from "@fortawesome/free-solid-svg-icons"; import { memo } from "react"; -import { ToggleButton } from "react-aria-components"; import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; import { Button } from "../ui-components/Button"; @@ -18,19 +17,6 @@ const actions = tv({ base: ["mb-[6px]", "h-[18px]", "text-left"], }); -// excluding is a warning state: selected shows red, not the usual primary -const excludeToggle = tv({ - base: [ - "inline-flex items-center gap-[5px]", - "h-6 px-2", - "rounded border border-transparent", - "text-xs text-gray-800", - "cursor-pointer", - "hover:bg-gray-50", - "data-selected:text-red", - ], -}); - interface PropsT { excludeActive: boolean; dateActive: boolean; @@ -52,14 +38,16 @@ const QueryGroupActions = ({
- {t("queryEditor.exclude")} - + {t("help.queryEditorExclude")} diff --git a/frontend/src/js/standard-query-editor/QueryNodeActions.tsx b/frontend/src/js/standard-query-editor/QueryNodeActions.tsx index efef2cc068..6e358dbeec 100644 --- a/frontend/src/js/standard-query-editor/QueryNodeActions.tsx +++ b/frontend/src/js/standard-query-editor/QueryNodeActions.tsx @@ -66,7 +66,8 @@ const QueryNodeActions = (props: Props) => { + ))} + +
+ ), +}; + export const Sizes: Story = { render: () => (
diff --git a/frontend/src/js/ui-components/Button.tsx b/frontend/src/js/ui-components/Button.tsx index 33dec43026..36ce4b6dae 100644 --- a/frontend/src/js/ui-components/Button.tsx +++ b/frontend/src/js/ui-components/Button.tsx @@ -19,7 +19,7 @@ const button = tv({ "shrink-0", "rounded", "border", - "leading-none font-normal whitespace-nowrap", + "leading-none font-medium whitespace-nowrap", "cursor-pointer", "transition-[color,background-color,border-color,opacity] duration-100", "disabled:cursor-not-allowed disabled:opacity-40", @@ -39,10 +39,6 @@ const button = tv({ "bg-transparent text-gray-800 border-transparent", "hover:bg-gray-50", ], - danger: [ - "bg-transparent text-red border-red", - "hover:bg-red hover:text-white", - ], // reads as a text link, aligns with text: no padding, no box link: [ "bg-transparent border-transparent px-0", @@ -58,8 +54,18 @@ const button = tv({ }, // an icon-only button is a square iconOnly: { true: "px-0" }, + // a destructive or warning action, in the look of its intent + danger: { true: "aria-pressed:text-red" }, }, compoundVariants: [ + { danger: true, intent: "primary", class: "bg-red border-red" }, + { + danger: true, + intent: "secondary", + class: "text-red border-red hover:bg-red hover:text-white", + }, + { danger: true, intent: "tertiary", class: "text-red hover:text-red" }, + { danger: true, intent: "link", class: "text-red hover:text-red" }, { iconOnly: true, size: "sm", class: "w-6" }, { iconOnly: true, size: "md", class: "w-[30px]" }, { iconOnly: true, size: "lg", class: "w-9" }, @@ -70,7 +76,9 @@ const button = tv({ export interface ButtonProps extends Omit { /** what the button does in its context; the look follows */ - intent?: "primary" | "secondary" | "tertiary" | "danger" | "link"; + intent?: "primary" | "secondary" | "tertiary" | "link"; + /** a destructive or warning action: red, in the look of the intent */ + danger?: boolean; size?: "sm" | "md" | "lg"; style?: CSSProperties; children?: ReactNode; @@ -97,15 +105,27 @@ const isIconOnly = (children: ReactNode) => { * * A button whose only children are icons is square; give it an `aria-label`. * A pressed state (toggles) is `aria-pressed`, which colors the button. - * `link` is for a button that reads as a text link. + * `link` is for a button that reads as a text link. `danger` turns any intent + * red for destructive or warning actions. * * There is no className: layout belongs to the parent (a `grid` wrapper * stretches a button to full width), and anything that needs another look is * not this button but a react-aria Button styled where it lives. */ -export const Button = ({ intent, size, children, ...props }: ButtonProps) => ( +export const Button = ({ + intent, + size, + danger, + children, + ...props +}: ButtonProps) => ( {children} diff --git a/frontend/src/js/ui-components/EditableTextForm.tsx b/frontend/src/js/ui-components/EditableTextForm.tsx index 5e9887d3cf..367529e2bd 100644 --- a/frontend/src/js/ui-components/EditableTextForm.tsx +++ b/frontend/src/js/ui-components/EditableTextForm.tsx @@ -8,7 +8,7 @@ import { Icon } from "./Icon"; import { Tooltip, TooltipTrigger } from "./Tooltip"; const input = tv({ - base: ["h-[28px]", "px-2", "rounded", "border border-gray-500", "text-sm"], + base: ["h-[30px]", "px-2", "rounded", "border border-gray-500", "text-sm"], }); const form = tv({ diff --git a/frontend/src/js/ui-components/Icon.stories.tsx b/frontend/src/js/ui-components/Icon.stories.tsx index ecc7886ed4..380383a58f 100644 --- a/frontend/src/js/ui-components/Icon.stories.tsx +++ b/frontend/src/js/ui-components/Icon.stories.tsx @@ -80,7 +80,7 @@ export const InheritsColor: Story = { - From d2dd114c2f8465f139157cf0717b282e233b3ee4 Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Thu, 3 Sep 2026 17:31:09 +0200 Subject: [PATCH 03/16] link buttons flow with the text: auto height, inherited size and line-height --- frontend/src/js/ui-components/Button.stories.tsx | 14 ++++++++++++++ frontend/src/js/ui-components/Button.tsx | 8 +++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/frontend/src/js/ui-components/Button.stories.tsx b/frontend/src/js/ui-components/Button.stories.tsx index 721811b4d1..38336e879d 100644 --- a/frontend/src/js/ui-components/Button.stories.tsx +++ b/frontend/src/js/ui-components/Button.stories.tsx @@ -32,6 +32,20 @@ export const Intents: Story = { ), }; +export const LinkInText: Story = { + render: () => ( +

+ A link button sits in flowing text, takes the text's size and line height, + and wraps with it: or{" "} + + . +

+ ), +}; + export const Danger: Story = { render: () => (
diff --git a/frontend/src/js/ui-components/Button.tsx b/frontend/src/js/ui-components/Button.tsx index 36ce4b6dae..5a8b2c453b 100644 --- a/frontend/src/js/ui-components/Button.tsx +++ b/frontend/src/js/ui-components/Button.tsx @@ -39,9 +39,11 @@ const button = tv({ "bg-transparent text-gray-800 border-transparent", "hover:bg-gray-50", ], - // reads as a text link, aligns with text: no padding, no box + // reads as a text link and sits in flowing text: no box, no padding, + // no fixed height, the surrounding text's size and line-height link: [ "bg-transparent border-transparent px-0", + "h-auto leading-[inherit] text-[length:inherit] align-baseline", "text-gray-500", "hover:text-gray-800 hover:underline", ], @@ -105,8 +107,8 @@ const isIconOnly = (children: ReactNode) => { * * A button whose only children are icons is square; give it an `aria-label`. * A pressed state (toggles) is `aria-pressed`, which colors the button. - * `link` is for a button that reads as a text link. `danger` turns any intent - * red for destructive or warning actions. + * `link` is for a button that reads as a text link and sits in flowing text. + * `danger` turns any intent red for destructive or warning actions. * * There is no className: layout belongs to the parent (a `grid` wrapper * stretches a button to full width), and anything that needs another look is From ec6489f0ce62a00d727cd1dd621f7887cb6be3c1 Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Thu, 3 Sep 2026 17:37:22 +0200 Subject: [PATCH 04/16] ToggleButton in Button's look; danger only for secondary and tertiary; link buttons flow with text --- frontend/src/js/button/DownloadButton.tsx | 7 +- frontend/src/js/editor-v2/EditorV2.tsx | 29 +++-- .../form-concept-group/FormConceptGroup.tsx | 4 +- .../form/fields/ConceptListField.tsx | 4 +- frontend/src/js/info-pane/InfoPane.tsx | 10 +- .../list/FoldersToggleButton.tsx | 10 +- .../js/query-node-editor/MenuColumnItem.tsx | 9 +- .../ResetAllSettingsButton.tsx | 2 +- .../QueryGroupActions.tsx | 23 ++-- .../SecondaryIdSelector.tsx | 5 +- .../src/js/ui-components/Button.stories.tsx | 47 ++------ frontend/src/js/ui-components/Button.tsx | 43 ++++--- .../ui-components/InputDate/CustomHeader.tsx | 7 +- frontend/src/js/ui-components/InputRange.tsx | 6 +- .../js/ui-components/ToggleButton.stories.tsx | 46 +++++++ .../src/js/ui-components/ToggleButton.tsx | 113 +++++++++--------- .../js/ui-components/ToggleButtonGroup.tsx | 71 +++++++++++ 17 files changed, 269 insertions(+), 167 deletions(-) create mode 100644 frontend/src/js/ui-components/ToggleButton.stories.tsx create mode 100644 frontend/src/js/ui-components/ToggleButtonGroup.tsx diff --git a/frontend/src/js/button/DownloadButton.tsx b/frontend/src/js/button/DownloadButton.tsx index 6cd6dd3db0..d8193e9f85 100644 --- a/frontend/src/js/button/DownloadButton.tsx +++ b/frontend/src/js/button/DownloadButton.tsx @@ -43,12 +43,13 @@ export function getFileIcon(url: string): FileIcon { return { icon: faFileDownload }; } -interface Props extends Omit { +interface Props { resultUrl: ResultUrlWithLabel; className?: string; children?: ReactNode; simpleIcon?: boolean; showColoredIcon?: boolean; + size?: ButtonProps["size"]; } const DownloadButton = ({ @@ -58,7 +59,7 @@ const DownloadButton = ({ className, children, showColoredIcon, - ...restProps + size, }: Props & { ref?: Ref }) => { const { authToken } = useContext(AuthTokenContext); @@ -68,7 +69,7 @@ const DownloadButton = ({ return ( - @@ -319,28 +324,34 @@ export function EditorV2({ )} {featureNegate && selectedNode && ( - + )} {featureConnectorRotate && selectedNode && connection && ( diff --git a/frontend/src/js/external-forms/form-concept-group/FormConceptGroup.tsx b/frontend/src/js/external-forms/form-concept-group/FormConceptGroup.tsx index 1ec00cbc26..bebf938b3a 100644 --- a/frontend/src/js/external-forms/form-concept-group/FormConceptGroup.tsx +++ b/frontend/src/js/external-forms/form-concept-group/FormConceptGroup.tsx @@ -22,7 +22,7 @@ import { import DropzoneWithFileInput, { type DragItemFile, } from "../../ui-components/DropzoneWithFileInput"; -import ToggleButton from "../../ui-components/ToggleButton"; +import ToggleButtonGroup from "../../ui-components/ToggleButtonGroup"; import UploadConceptListModal from "../../upload-concept-list-modal/UploadConceptListModal"; import type { ConceptListDefaults as ConceptListDefaultsType } from "../config-types"; import { Description } from "../form-components/Description"; @@ -362,7 +362,7 @@ const FormConceptGroup = (props: Props) => { {t("externalForms.common.connectedWith")}: - { props.onChange( diff --git a/frontend/src/js/external-forms/form/fields/ConceptListField.tsx b/frontend/src/js/external-forms/form/fields/ConceptListField.tsx index 1807960f52..b1ecdb3fa3 100644 --- a/frontend/src/js/external-forms/form/fields/ConceptListField.tsx +++ b/frontend/src/js/external-forms/form/fields/ConceptListField.tsx @@ -2,7 +2,7 @@ import type { ComponentProps } from "react"; import { useTranslation } from "react-i18next"; import { exists } from "../../../common/helpers/exists"; import { nodeIsInvalid } from "../../../model/node"; -import ToggleButton from "../../../ui-components/ToggleButton"; +import ToggleButtonGroup from "../../../ui-components/ToggleButtonGroup"; import type { ConceptListField as ConceptListFieldT } from "../../config-types"; import FormConceptGroup from "../../form-concept-group/FormConceptGroup"; import type { FormConceptGroupT } from "../../form-concept-group/formConceptGroupState"; @@ -77,7 +77,7 @@ export const ConceptListField = ({ renderRowPrefix={ exists(field.rowPrefixField) ? ({ value: fieldValue, onChange, row, i }) => ( - ({ label: option.label[locale] || "", diff --git a/frontend/src/js/info-pane/InfoPane.tsx b/frontend/src/js/info-pane/InfoPane.tsx index 64f1e02067..295c0bc8c9 100644 --- a/frontend/src/js/info-pane/InfoPane.tsx +++ b/frontend/src/js/info-pane/InfoPane.tsx @@ -11,8 +11,8 @@ import remarkGfm from "remark-gfm"; import { tv } from "tailwind-variants"; import type { StateT } from "../app/reducers"; import { Highlighter } from "../common/components/Highlighter"; -import { Button } from "../ui-components/Button"; import { Icon } from "../ui-components/Icon"; +import { ToggleButton } from "../ui-components/ToggleButton"; import { toggleAdditionalInfos as toggleInfos } from "./actions"; import InfoPaneCollapsed from "./InfoPaneCollapsed"; import { InfoPaneHeader } from "./InfoPaneHeader"; @@ -188,14 +188,14 @@ const InfoPane = () => { conceptIcon={mainIcon} tackIcon={ toggleAdditionalInfos && ( - + ) } /> diff --git a/frontend/src/js/previous-queries/list/FoldersToggleButton.tsx b/frontend/src/js/previous-queries/list/FoldersToggleButton.tsx index 402b22c30f..da1e472745 100644 --- a/frontend/src/js/previous-queries/list/FoldersToggleButton.tsx +++ b/frontend/src/js/previous-queries/list/FoldersToggleButton.tsx @@ -1,7 +1,7 @@ import { faFolder } from "@fortawesome/free-solid-svg-icons"; import { useTranslation } from "react-i18next"; -import { Button } from "../../ui-components/Button"; import { Icon } from "../../ui-components/Icon"; +import { ToggleButton } from "../../ui-components/ToggleButton"; import { Tooltip, TooltipTrigger } from "../../ui-components/Tooltip"; @@ -16,14 +16,14 @@ const FoldersToggleButton = ({ return ( - + {t("previousQueriesFolderButton.tooltip")} ); diff --git a/frontend/src/js/query-node-editor/MenuColumnItem.tsx b/frontend/src/js/query-node-editor/MenuColumnItem.tsx index 81f6df2af4..bdf78e2e3d 100644 --- a/frontend/src/js/query-node-editor/MenuColumnItem.tsx +++ b/frontend/src/js/query-node-editor/MenuColumnItem.tsx @@ -7,6 +7,7 @@ import { tableHasFilterValues, tableIsDisabled } from "../model/table"; import type { TableWithFilterValueT } from "../standard-query-editor/types"; import { Button } from "../ui-components/Button"; import { Icon } from "../ui-components/Icon"; +import { ToggleButton } from "../ui-components/ToggleButton"; import { Tooltip, TooltipTrigger } from "../ui-components/Tooltip"; const container = tv({ @@ -90,11 +91,11 @@ const MenuColumnItem = ({
{isFilterActive && ( - + {t("queryNodeEditor.clearSettings")} )} diff --git a/frontend/src/js/query-node-editor/ResetAllSettingsButton.tsx b/frontend/src/js/query-node-editor/ResetAllSettingsButton.tsx index 987b5e3ae5..ee6021a26d 100644 --- a/frontend/src/js/query-node-editor/ResetAllSettingsButton.tsx +++ b/frontend/src/js/query-node-editor/ResetAllSettingsButton.tsx @@ -17,7 +17,7 @@ const ResetAllSettingsButton = ({ const confirmationText = t("queryNodeEditor.clearAllSettingsConfirm"); const trigger = ( - diff --git a/frontend/src/js/standard-query-editor/QueryGroupActions.tsx b/frontend/src/js/standard-query-editor/QueryGroupActions.tsx index 63720a1ae8..8272b0b604 100644 --- a/frontend/src/js/standard-query-editor/QueryGroupActions.tsx +++ b/frontend/src/js/standard-query-editor/QueryGroupActions.tsx @@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; import { Button } from "../ui-components/Button"; import { Icon } from "../ui-components/Icon"; +import { ToggleButton } from "../ui-components/ToggleButton"; import { Tooltip, @@ -38,26 +39,24 @@ const QueryGroupActions = ({
- + {t("help.queryEditorExclude")} - {t("help.queryEditorDate")} diff --git a/frontend/src/js/standard-query-editor/SecondaryIdSelector.tsx b/frontend/src/js/standard-query-editor/SecondaryIdSelector.tsx index f66079dcf9..e936e390de 100644 --- a/frontend/src/js/standard-query-editor/SecondaryIdSelector.tsx +++ b/frontend/src/js/standard-query-editor/SecondaryIdSelector.tsx @@ -3,14 +3,13 @@ import { memo, useCallback, useEffect, useMemo, useRef } from "react"; import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; import { tv } from "tailwind-variants"; - import type { SecondaryId } from "../api/types"; import type { StateT } from "../app/reducers"; import { exists } from "../common/helpers/exists"; import { nodeIsConceptQueryNode } from "../model/node"; import { Icon } from "../ui-components/Icon"; import InfoTooltip from "../ui-components/InfoTooltip"; -import ToggleButton from "../ui-components/ToggleButton"; +import ToggleButtonGroup from "../ui-components/ToggleButtonGroup"; import { setSelectedSecondaryId } from "./actions"; import type { StandardQueryStateT } from "./queryReducer"; @@ -167,7 +166,7 @@ const SecondaryIdSelectorUI = memo( {t("queryEditor.secondaryId")} - (
- {intents.map((intent) => ( - - ))} - +
), @@ -84,32 +80,3 @@ export const Sizes: Story = {
), }; - -const Pressed = () => { - const [pressed, setPressed] = useState(true); - return ( -
- - - - A pressed icon button - -
- ); -}; - -export const PressedState: Story = { render: () => }; diff --git a/frontend/src/js/ui-components/Button.tsx b/frontend/src/js/ui-components/Button.tsx index 5a8b2c453b..dc5dec271a 100644 --- a/frontend/src/js/ui-components/Button.tsx +++ b/frontend/src/js/ui-components/Button.tsx @@ -13,7 +13,7 @@ import { tv } from "tailwind-variants"; import { Icon } from "./Icon"; -const button = tv({ +export const buttonStyle = tv({ base: [ "inline-flex items-center justify-center", "shrink-0", @@ -23,7 +23,6 @@ const button = tv({ "cursor-pointer", "transition-[color,background-color,border-color,opacity] duration-100", "disabled:cursor-not-allowed disabled:opacity-40", - "aria-pressed:text-primary-500", ], variants: { intent: { @@ -39,11 +38,9 @@ const button = tv({ "bg-transparent text-gray-800 border-transparent", "hover:bg-gray-50", ], - // reads as a text link and sits in flowing text: no box, no padding, - // no fixed height, the surrounding text's size and line-height + // reads as a text link, see the compound variant for how it flows with text link: [ - "bg-transparent border-transparent px-0", - "h-auto leading-[inherit] text-[length:inherit] align-baseline", + "bg-transparent border-transparent", "text-gray-500", "hover:text-gray-800 hover:underline", ], @@ -57,17 +54,22 @@ const button = tv({ // an icon-only button is a square iconOnly: { true: "px-0" }, // a destructive or warning action, in the look of its intent - danger: { true: "aria-pressed:text-red" }, + danger: { true: "" }, }, compoundVariants: [ - { danger: true, intent: "primary", class: "bg-red border-red" }, + // a link sits in flowing text: no box, no padding, no fixed height, + // the surrounding text's size and line-height (after the size variant) + { + intent: "link", + class: + "h-auto px-0 leading-[inherit] text-[length:inherit] align-baseline", + }, { danger: true, intent: "secondary", class: "text-red border-red hover:bg-red hover:text-white", }, { danger: true, intent: "tertiary", class: "text-red hover:text-red" }, - { danger: true, intent: "link", class: "text-red hover:text-red" }, { iconOnly: true, size: "sm", class: "w-6" }, { iconOnly: true, size: "md", class: "w-[30px]" }, { iconOnly: true, size: "lg", class: "w-9" }, @@ -75,18 +77,25 @@ const button = tv({ defaultVariants: { intent: "secondary", size: "md" }, }); -export interface ButtonProps +interface CommonProps extends Omit { - /** what the button does in its context; the look follows */ - intent?: "primary" | "secondary" | "tertiary" | "link"; - /** a destructive or warning action: red, in the look of the intent */ - danger?: boolean; size?: "sm" | "md" | "lg"; style?: CSSProperties; children?: ReactNode; ref?: Ref; } +/** what the button does in its context; the look follows */ +export type ButtonProps = CommonProps & + ( + | { + intent?: "secondary" | "tertiary"; + /** a destructive or warning action: red, in the look of the intent */ + danger?: boolean; + } + | { intent: "primary" | "link"; danger?: never } + ); + const isIconOnly = (children: ReactNode) => { const items = Children.toArray(children); return ( @@ -106,9 +115,9 @@ const isIconOnly = (children: ReactNode) => { * * * A button whose only children are icons is square; give it an `aria-label`. - * A pressed state (toggles) is `aria-pressed`, which colors the button. * `link` is for a button that reads as a text link and sits in flowing text. - * `danger` turns any intent red for destructive or warning actions. + * `danger` turns a secondary or tertiary button red for destructive or + * warning actions. Something that is on or off is a ToggleButton. * * There is no className: layout belongs to the parent (a `grid` wrapper * stretches a button to full width), and anything that needs another look is @@ -122,7 +131,7 @@ export const Button = ({ ...props }: ButtonProps) => ( {options.map((option) => ( - + ))}
diff --git a/frontend/src/js/ui-components/InputRange.tsx b/frontend/src/js/ui-components/InputRange.tsx index f3ae54c205..5eaafe025a 100644 --- a/frontend/src/js/ui-components/InputRange.tsx +++ b/frontend/src/js/ui-components/InputRange.tsx @@ -1,12 +1,10 @@ import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; - import type { CurrencyConfigT } from "../api/types"; import { exists } from "../common/helpers/exists"; - import InputPlain from "./InputPlain/InputPlain"; import InputRangeHeader from "./InputRangeHeader"; -import ToggleButton from "./ToggleButton"; +import ToggleButtonGroup from "./ToggleButtonGroup"; const container = tv({ base: ["flex flex-row", "w-full", "-mt-[3px]"] }); @@ -119,7 +117,7 @@ const InputRange = ({ unit={unit} tooltip={tooltip} /> - onSwitchMode(mode as ModeT)} options={[ diff --git a/frontend/src/js/ui-components/ToggleButton.stories.tsx b/frontend/src/js/ui-components/ToggleButton.stories.tsx new file mode 100644 index 0000000000..a4cdf79f7c --- /dev/null +++ b/frontend/src/js/ui-components/ToggleButton.stories.tsx @@ -0,0 +1,46 @@ +import { + faBan, + faFolder, + faThumbtack, +} from "@fortawesome/free-solid-svg-icons"; +import type { Meta, StoryObj } from "@storybook/react"; +import { useState } from "react"; + +import { Icon } from "./Icon"; +import { ToggleButton } from "./ToggleButton"; +import { Tooltip, TooltipTrigger } from "./Tooltip"; + +export default { + title: "UiComponents/ToggleButton", + component: ToggleButton, + parameters: { layout: "centered" }, +} as Meta; + +type Story = StoryObj; + +const Demo = () => { + const [pinned, setPinned] = useState(true); + const [open, setOpen] = useState(false); + const [negated, setNegated] = useState(true); + + return ( +
+ + + + + Tertiary, icon only + + + + folders + + + + negate + +
+ ); +}; + +export const Default: Story = { render: () => }; diff --git a/frontend/src/js/ui-components/ToggleButton.tsx b/frontend/src/js/ui-components/ToggleButton.tsx index dccd194477..cfc8ac36ef 100644 --- a/frontend/src/js/ui-components/ToggleButton.tsx +++ b/frontend/src/js/ui-components/ToggleButton.tsx @@ -1,71 +1,70 @@ -import { Focusable } from "react-aria-components"; +import { Children, isValidElement, type ReactNode, type Ref } from "react"; +import { + ToggleButton as RacToggleButton, + type ToggleButtonProps as RacToggleButtonProps, +} from "react-aria-components"; import { tv } from "tailwind-variants"; -import { Tooltip, TooltipTrigger } from "./Tooltip"; +import { buttonStyle } from "./Button"; +import { Icon } from "./Icon"; -const root = tv({ base: ["m-0", "flex flex-wrap items-center"] }); - -const option = tv({ - base: [ - "inline-block", - "px-2 py-1", - "-ml-px mb-[2px]", - "cursor-pointer", - "border border-gray-500", - "text-xs", - "transition-[color,background-color] duration-100", - ], +// selected: the primary color, or red for a warning toggle +const toggleStyle = tv({ + extend: buttonStyle, variants: { - active: { - true: ["text-gray-800", "bg-white hover:bg-white"], - false: ["text-gray-500", "bg-gray-50 hover:bg-bg-50"], + danger: { + true: "data-selected:text-red", + false: "data-selected:text-primary-500", }, - isFirst: { true: ["ml-0", "rounded-l-[2px]"] }, - isLast: { true: "rounded-r-[2px]" }, }, }); -interface OptionsT { - label: string; - value: string; - description?: string; +interface CommonProps + extends Omit { + size?: "sm" | "md" | "lg"; + children?: ReactNode; + ref?: Ref; } -const ToggleButton = ({ - options, - value: inputValue, - onChange, - className, -}: { - className?: string; - options: OptionsT[]; - value: string; - onChange: (value: string) => void; -}) => { +export type ToggleButtonProps = CommonProps & { + intent?: "secondary" | "tertiary"; + /** a warning state: red while selected */ + danger?: boolean; +}; + +const isIconOnly = (children: ReactNode) => { + const items = Children.toArray(children); return ( -
- {options.map(({ value, label, description }, i) => ( - - - - - {description} - - ))} -
+ items.length > 0 && + items.every((child) => isValidElement(child) && child.type === Icon) ); }; -export default ToggleButton; +/** + * A button that is on or off, in Button's look. react-aria's ToggleButton + * underneath: `isSelected` / `onChange`, and it works inside a + * ToggleButtonGroup and as a tooltip trigger. + * + * + * + * + */ +export const ToggleButton = ({ + intent = "tertiary", + size, + danger = false, + children, + ...props +}: ToggleButtonProps) => ( + + {children} + +); diff --git a/frontend/src/js/ui-components/ToggleButtonGroup.tsx b/frontend/src/js/ui-components/ToggleButtonGroup.tsx new file mode 100644 index 0000000000..8203dbc038 --- /dev/null +++ b/frontend/src/js/ui-components/ToggleButtonGroup.tsx @@ -0,0 +1,71 @@ +import { Focusable } from "react-aria-components"; +import { tv } from "tailwind-variants"; + +import { Tooltip, TooltipTrigger } from "./Tooltip"; + +const root = tv({ base: ["m-0", "flex flex-wrap items-center"] }); + +const option = tv({ + base: [ + "inline-block", + "px-2 py-1", + "-ml-px mb-[2px]", + "cursor-pointer", + "border border-gray-500", + "text-xs", + "transition-[color,background-color] duration-100", + ], + variants: { + active: { + true: ["text-gray-800", "bg-white hover:bg-white"], + false: ["text-gray-500", "bg-gray-50 hover:bg-bg-50"], + }, + isFirst: { true: ["ml-0", "rounded-l-[2px]"] }, + isLast: { true: "rounded-r-[2px]" }, + }, +}); + +interface OptionsT { + label: string; + value: string; + description?: string; +} + +const ToggleButtonGroup = ({ + options, + value: inputValue, + onChange, + className, +}: { + className?: string; + options: OptionsT[]; + value: string; + onChange: (value: string) => void; +}) => { + return ( +
+ {options.map(({ value, label, description }, i) => ( + + + + + {description} + + ))} +
+ ); +}; + +export default ToggleButtonGroup; From bf6638c6070e2cc0b0ca9bdb0dd1fea4de0f1eb8 Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Thu, 3 Sep 2026 17:38:15 +0200 Subject: [PATCH 05/16] editor actions: hoist the state icon helper, keeps the component under the complexity limit --- frontend/src/js/editor-v2/EditorV2.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/frontend/src/js/editor-v2/EditorV2.tsx b/frontend/src/js/editor-v2/EditorV2.tsx index 8c3c07fda1..b0dbab492c 100644 --- a/frontend/src/js/editor-v2/EditorV2.tsx +++ b/frontend/src/js/editor-v2/EditorV2.tsx @@ -102,6 +102,10 @@ const useResetOnDatasetChange = (onReset: () => void) => { }, [datasetId, onReset]); }; +// an action's icon shows in the primary color while its setting is in effect +const stateIcon = (active: boolean) => + active ? "text-primary-500" : undefined; + export function EditorV2({ featureDates, featureNegate, @@ -311,9 +315,7 @@ export function EditorV2({ > {t("editorV2.edit")} @@ -330,11 +332,7 @@ export function EditorV2({ > {t("editorV2.dates")} From 485116498c439af36ccfbf19cbcdf106315438df Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Thu, 3 Sep 2026 17:45:15 +0200 Subject: [PATCH 06/16] the results download in the queries list is a link --- frontend/src/js/button/DownloadButton.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/js/button/DownloadButton.tsx b/frontend/src/js/button/DownloadButton.tsx index d8193e9f85..270e06af08 100644 --- a/frontend/src/js/button/DownloadButton.tsx +++ b/frontend/src/js/button/DownloadButton.tsx @@ -13,7 +13,7 @@ import { tv } from "tailwind-variants"; import type { ResultUrlWithLabel } from "../api/types"; import { AuthTokenContext } from "../authorization/AuthTokenProvider"; import { getEnding } from "../query-runner/DownloadResultsDropdownButton"; -import { Button, type ButtonProps } from "../ui-components/Button"; +import { Button } from "../ui-components/Button"; import { Icon } from "../ui-components/Icon"; const link = tv({ base: "leading-none" }); @@ -49,7 +49,6 @@ interface Props { children?: ReactNode; simpleIcon?: boolean; showColoredIcon?: boolean; - size?: ButtonProps["size"]; } const DownloadButton = ({ @@ -59,7 +58,6 @@ const DownloadButton = ({ className, children, showColoredIcon, - size, }: Props & { ref?: Ref }) => { const { authToken } = useContext(AuthTokenContext); @@ -69,7 +67,7 @@ const DownloadButton = ({ return (
- + )} {featureDates && selectedNode && ( - + )} {featureNegate && selectedNode && ( @@ -343,7 +335,7 @@ export function EditorV2({ diff --git a/frontend/src/js/standard-query-editor/QueryGroupActions.tsx b/frontend/src/js/standard-query-editor/QueryGroupActions.tsx index 8272b0b604..1bdeac572b 100644 --- a/frontend/src/js/standard-query-editor/QueryGroupActions.tsx +++ b/frontend/src/js/standard-query-editor/QueryGroupActions.tsx @@ -42,7 +42,7 @@ const QueryGroupActions = ({ @@ -52,13 +52,15 @@ const QueryGroupActions = ({ {t("help.queryEditorExclude")} - + {t("help.queryEditorDate")}
diff --git a/frontend/src/js/ui-components/ToggleButton.stories.tsx b/frontend/src/js/ui-components/ToggleButton.stories.tsx index a4cdf79f7c..8a70f4f5e8 100644 --- a/frontend/src/js/ui-components/ToggleButton.stories.tsx +++ b/frontend/src/js/ui-components/ToggleButton.stories.tsx @@ -35,7 +35,12 @@ const Demo = () => { folders - + negate diff --git a/frontend/src/js/ui-components/ToggleButton.tsx b/frontend/src/js/ui-components/ToggleButton.tsx index cfc8ac36ef..9dad45c90b 100644 --- a/frontend/src/js/ui-components/ToggleButton.tsx +++ b/frontend/src/js/ui-components/ToggleButton.tsx @@ -8,13 +8,13 @@ import { tv } from "tailwind-variants"; import { buttonStyle } from "./Button"; import { Icon } from "./Icon"; -// selected: the primary color, or red for a warning toggle +// while selected, the button shows its highlight color const toggleStyle = tv({ extend: buttonStyle, variants: { - danger: { - true: "data-selected:text-red", - false: "data-selected:text-primary-500", + highlight: { + primary: "data-selected:text-primary-500", + danger: "data-selected:text-red", }, }, }); @@ -26,11 +26,12 @@ interface CommonProps ref?: Ref; } -export type ToggleButtonProps = CommonProps & { +export interface ToggleButtonProps extends CommonProps { + /** Button's intents that can be switched on and off */ intent?: "secondary" | "tertiary"; - /** a warning state: red while selected */ - danger?: boolean; -}; + /** how it shows while selected: the primary color, or red for a warning state */ + highlight?: "primary" | "danger"; +} const isIconOnly = (children: ReactNode) => { const items = Children.toArray(children); @@ -41,9 +42,10 @@ const isIconOnly = (children: ReactNode) => { }; /** - * A button that is on or off, in Button's look. react-aria's ToggleButton - * underneath: `isSelected` / `onChange`, and it works inside a - * ToggleButtonGroup and as a tooltip trigger. + * A button whose look reflects a state that is on or off, in Button's look. + * react-aria's ToggleButton underneath: `isSelected` / `onChange`, and it + * works inside a ToggleButtonGroup and as a tooltip trigger. Pressing may + * flip the state or open an editor for it. * * * @@ -52,7 +54,7 @@ const isIconOnly = (children: ReactNode) => { export const ToggleButton = ({ intent = "tertiary", size, - danger = false, + highlight = "primary", children, ...props }: ToggleButtonProps) => ( @@ -60,7 +62,7 @@ export const ToggleButton = ({ className={toggleStyle({ intent, size, - danger, + highlight, iconOnly: isIconOnly(children), })} {...props} From 23b13ee3061d6573cfd3d1d4e83a6c36477b40f1 Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Thu, 3 Sep 2026 18:00:34 +0200 Subject: [PATCH 08/16] ToggleButton stories: intents, highlights, sizes, icon-only, state with editor --- .../js/ui-components/ToggleButton.stories.tsx | 146 +++++++++++++++--- 1 file changed, 124 insertions(+), 22 deletions(-) diff --git a/frontend/src/js/ui-components/ToggleButton.stories.tsx b/frontend/src/js/ui-components/ToggleButton.stories.tsx index 8a70f4f5e8..7506513bc3 100644 --- a/frontend/src/js/ui-components/ToggleButton.stories.tsx +++ b/frontend/src/js/ui-components/ToggleButton.stories.tsx @@ -1,5 +1,7 @@ import { faBan, + faCalendar, + faEdit, faFolder, faThumbtack, } from "@fortawesome/free-solid-svg-icons"; @@ -7,7 +9,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { useState } from "react"; import { Icon } from "./Icon"; -import { ToggleButton } from "./ToggleButton"; +import { ToggleButton, type ToggleButtonProps } from "./ToggleButton"; import { Tooltip, TooltipTrigger } from "./Tooltip"; export default { @@ -18,34 +20,134 @@ export default { type Story = StoryObj; -const Demo = () => { - const [pinned, setPinned] = useState(true); - const [open, setOpen] = useState(false); - const [negated, setNegated] = useState(true); - +// each example keeps its own state so it can be switched in the story +const Toggle = ({ + children, + defaultSelected = false, + ...props +}: Omit & { + defaultSelected?: boolean; +}) => { + const [selected, setSelected] = useState(defaultSelected); return ( -
+ + {children} + + ); +}; + +const Row = ({ + label, + children, +}: { + label: string; + children: React.ReactNode; +}) => ( +
+ {label} + {children} +
+); + +export const Intents: Story = { + render: () => ( +
+ + + + folders + + + + folders + + + + + + folders + + + + folders + + +
+ ), +}; + +export const Highlights: Story = { + render: () => ( +
+ + + + date + + + + + + exclude + + +
+ ), +}; + +export const Sizes: Story = { + render: () => ( +
+ {(["sm", "md", "lg"] as const).map((size) => ( + + + + edit + + + + edit + + + + + + ))} +
+ ), +}; + +export const IconOnlyWithTooltip: Story = { + render: () => ( + - + - - Tertiary, icon only + + Pin - - - folders - + + ), +}; + +const OpensAnEditor = () => { + const [restriction, setRestriction] = useState(null); + return ( + + setRestriction((current) => + current === null ? window.prompt("Date restriction") : null, + ) + } > - - negate + + {restriction ?? "date"} -
+ ); }; -export const Default: Story = { render: () => }; +/** The selected look reflects a state the user set; pressing edits it. */ +export const StateWithEditor: Story = { render: () => }; From c892dc0a34b8c262ec7fe4353da120f9f121c3b4 Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Thu, 3 Sep 2026 18:01:34 +0200 Subject: [PATCH 09/16] link buttons keep their icon close: gap-1 --- frontend/src/js/ui-components/Button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/js/ui-components/Button.tsx b/frontend/src/js/ui-components/Button.tsx index dc5dec271a..6fbaeb2fe8 100644 --- a/frontend/src/js/ui-components/Button.tsx +++ b/frontend/src/js/ui-components/Button.tsx @@ -62,7 +62,7 @@ export const buttonStyle = tv({ { intent: "link", class: - "h-auto px-0 leading-[inherit] text-[length:inherit] align-baseline", + "h-auto px-0 gap-1 leading-[inherit] text-[length:inherit] align-baseline", }, { danger: true, From b34afae0512891deae1ef95226ba2260f4e183ab Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Thu, 3 Sep 2026 18:03:08 +0200 Subject: [PATCH 10/16] toggle buttons are bold while selected --- frontend/src/js/ui-components/ToggleButton.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/js/ui-components/ToggleButton.tsx b/frontend/src/js/ui-components/ToggleButton.tsx index 9dad45c90b..3ced06a0e0 100644 --- a/frontend/src/js/ui-components/ToggleButton.tsx +++ b/frontend/src/js/ui-components/ToggleButton.tsx @@ -8,9 +8,10 @@ import { tv } from "tailwind-variants"; import { buttonStyle } from "./Button"; import { Icon } from "./Icon"; -// while selected, the button shows its highlight color +// while selected, the button is bold and shows its highlight color const toggleStyle = tv({ extend: buttonStyle, + base: "data-selected:font-bold", variants: { highlight: { primary: "data-selected:text-primary-500", From de701a3647e1b92831c4924fef9f475cb1f875b5 Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Thu, 3 Sep 2026 18:06:10 +0200 Subject: [PATCH 11/16] button icon gap: 8 px for sm and md, 12 px for lg --- frontend/src/js/ui-components/Button.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/js/ui-components/Button.tsx b/frontend/src/js/ui-components/Button.tsx index 6fbaeb2fe8..04f63f27cc 100644 --- a/frontend/src/js/ui-components/Button.tsx +++ b/frontend/src/js/ui-components/Button.tsx @@ -47,9 +47,9 @@ export const buttonStyle = tv({ }, // every size is a fixed height, so text and icon-only buttons line up size: { - sm: "h-6 px-2 gap-[5px] text-xs", - md: "h-[30px] px-[15px] gap-[10px] text-sm", - lg: "h-9 px-[18px] gap-[10px] text-base", + sm: "h-6 px-2 gap-2 text-xs", + md: "h-[30px] px-[15px] gap-2 text-sm", + lg: "h-9 px-[18px] gap-3 text-base", }, // an icon-only button is a square iconOnly: { true: "px-0" }, From 4a31fa807b8f25b308efc8d93cec0d81fa501868 Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Thu, 3 Sep 2026 18:08:33 +0200 Subject: [PATCH 12/16] editor-v2 toolbar buttons are regular sized --- frontend/src/js/editor-v2/EditorV2.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/frontend/src/js/editor-v2/EditorV2.tsx b/frontend/src/js/editor-v2/EditorV2.tsx index ac35bca776..d214ab2cea 100644 --- a/frontend/src/js/editor-v2/EditorV2.tsx +++ b/frontend/src/js/editor-v2/EditorV2.tsx @@ -300,7 +300,6 @@ export function EditorV2({ > { onOpen(); @@ -334,7 +332,6 @@ export function EditorV2({
From adf31d2479e5afd497f9bf4150da99b04f04b17d Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Thu, 3 Sep 2026 18:13:48 +0200 Subject: [PATCH 14/16] query name row without the extra vertical margin --- frontend/src/js/previous-queries/list/ProjectItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/js/previous-queries/list/ProjectItem.tsx b/frontend/src/js/previous-queries/list/ProjectItem.tsx index c5c63305ce..d0f77ea10b 100644 --- a/frontend/src/js/previous-queries/list/ProjectItem.tsx +++ b/frontend/src/js/previous-queries/list/ProjectItem.tsx @@ -67,7 +67,7 @@ const ownerName = tv({ const tooltipText = tv({ base: ["flex flex-col items-start", "font-normal"] }); const labelRow = tv({ - base: ["flex justify-between", "w-full", "leading-6", "my-[2px]"], + base: ["flex justify-between", "w-full", "leading-6"], }); const content = tv({ From 9240816a4011428f5e32e16c6161524c3f945628 Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Thu, 3 Sep 2026 18:37:42 +0200 Subject: [PATCH 15/16] edit button wrapper is flex, the query name row is 24 px --- frontend/src/js/ui-components/EditableText.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/js/ui-components/EditableText.tsx b/frontend/src/js/ui-components/EditableText.tsx index d98a0e13dc..cc36a4acba 100644 --- a/frontend/src/js/ui-components/EditableText.tsx +++ b/frontend/src/js/ui-components/EditableText.tsx @@ -7,7 +7,9 @@ import EditableTextForm from "./EditableTextForm"; import { Icon } from "./Icon"; import { Tooltip, TooltipTrigger } from "./Tooltip"; +// a flex wrapper, so the button does not sit on a text baseline and grow the row const editButton = tv({ + base: "flex shrink-0", variants: { large: { true: "mr-[10px]", From b1c8f89bcd7bb8130247bff0fb0ef6cc64187057 Mon Sep 17 00:00:00 2001 From: Kai Rollmann Date: Thu, 3 Sep 2026 22:48:35 +0200 Subject: [PATCH 16/16] history sidebar on ToggleButton and Button, no local control style --- .../src/js/entity-history/ContentControl.tsx | 11 +++---- .../src/js/entity-history/DetailControl.tsx | 6 ++-- .../js/entity-history/InteractionControl.tsx | 18 ++++++++--- .../src/js/entity-history/SidebarControl.tsx | 31 ------------------- .../js/entity-history/VisibilityControl.tsx | 6 ++-- .../timeline-search/SearchControl.tsx | 6 ++-- 6 files changed, 26 insertions(+), 52 deletions(-) delete mode 100644 frontend/src/js/entity-history/SidebarControl.tsx diff --git a/frontend/src/js/entity-history/ContentControl.tsx b/frontend/src/js/entity-history/ContentControl.tsx index 100ccc6fed..f36349178a 100644 --- a/frontend/src/js/entity-history/ContentControl.tsx +++ b/frontend/src/js/entity-history/ContentControl.tsx @@ -8,8 +8,8 @@ import { memo, useMemo, useState } from "react"; import { ToggleButtonGroup } from "react-aria-components"; import { useTranslation } from "react-i18next"; import { Icon } from "../ui-components/Icon"; +import { ToggleButton } from "../ui-components/ToggleButton"; import { Tooltip, TooltipTrigger } from "../ui-components/Tooltip"; -import { SidebarToggle } from "./SidebarControl"; export type ContentType = | "groupId" @@ -69,12 +69,9 @@ const ContentControl = ({ value, onChange }: Props) => { > {options.map((option) => ( - - - + + + {option.tooltip} ))} diff --git a/frontend/src/js/entity-history/DetailControl.tsx b/frontend/src/js/entity-history/DetailControl.tsx index 79baeea773..9b860615a7 100644 --- a/frontend/src/js/entity-history/DetailControl.tsx +++ b/frontend/src/js/entity-history/DetailControl.tsx @@ -9,8 +9,8 @@ import { type Key, ToggleButtonGroup } from "react-aria-components"; import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; import { Icon } from "../ui-components/Icon"; +import { ToggleButton } from "../ui-components/ToggleButton"; import { Tooltip, TooltipTrigger } from "../ui-components/Tooltip"; -import { SidebarToggle } from "./SidebarControl"; const root = tv({ base: "flex flex-col items-center" }); @@ -70,9 +70,9 @@ export const DetailControl = memo( > {navOptions.map(({ value, icon, tooltip }) => ( - + - + {tooltip} ))} diff --git a/frontend/src/js/entity-history/InteractionControl.tsx b/frontend/src/js/entity-history/InteractionControl.tsx index 218c5b9d32..9e7332e58b 100644 --- a/frontend/src/js/entity-history/InteractionControl.tsx +++ b/frontend/src/js/entity-history/InteractionControl.tsx @@ -1,9 +1,9 @@ import { faChevronRight, faHome } from "@fortawesome/free-solid-svg-icons"; import { memo } from "react"; import { useTranslation } from "react-i18next"; +import { Button } from "../ui-components/Button"; import { Icon } from "../ui-components/Icon"; import { Tooltip, TooltipTrigger } from "../ui-components/Tooltip"; -import { SidebarAction } from "./SidebarControl"; const InteractionControl = ({ onCloseAll, @@ -17,15 +17,23 @@ const InteractionControl = ({ return (
- + {t("history.closeAll")} - + {t("history.openAll")}
diff --git a/frontend/src/js/entity-history/SidebarControl.tsx b/frontend/src/js/entity-history/SidebarControl.tsx deleted file mode 100644 index 1fa2f93d12..0000000000 --- a/frontend/src/js/entity-history/SidebarControl.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { - Button as RacButton, - type ButtonProps as RacButtonProps, - ToggleButton as RacToggleButton, - type ToggleButtonProps as RacToggleButtonProps, -} from "react-aria-components"; -import { tv } from "tailwind-variants"; - -// The history sidebar's vertical toolbar: square controls, a selected one -// shows in the primary color. Local to the history, not a design-system part. -const control = tv({ - base: [ - "inline-flex items-center justify-center", - "size-[30px]", - "rounded", - "border border-transparent", - "text-gray-800", - "cursor-pointer", - "hover:bg-gray-50", - "data-selected:bg-gray-100 data-selected:text-primary-500", - "transition-colors duration-100", - ], -}); - -export const SidebarToggle = (props: RacToggleButtonProps) => ( - -); - -export const SidebarAction = (props: RacButtonProps) => ( - -); diff --git a/frontend/src/js/entity-history/VisibilityControl.tsx b/frontend/src/js/entity-history/VisibilityControl.tsx index 275154c5eb..c87ea0efd1 100644 --- a/frontend/src/js/entity-history/VisibilityControl.tsx +++ b/frontend/src/js/entity-history/VisibilityControl.tsx @@ -2,8 +2,8 @@ import { faEye, faEyeSlash } from "@fortawesome/free-regular-svg-icons"; import { memo } from "react"; import { useTranslation } from "react-i18next"; import { Icon } from "../ui-components/Icon"; +import { ToggleButton } from "../ui-components/ToggleButton"; import { Tooltip, TooltipTrigger } from "../ui-components/Tooltip"; -import { SidebarToggle } from "./SidebarControl"; const VisibilityControl = ({ blurred, @@ -17,13 +17,13 @@ const VisibilityControl = ({ return (
- - + {t("history.blurred")}
diff --git a/frontend/src/js/entity-history/timeline-search/SearchControl.tsx b/frontend/src/js/entity-history/timeline-search/SearchControl.tsx index c29d648c44..746a128cf4 100644 --- a/frontend/src/js/entity-history/timeline-search/SearchControl.tsx +++ b/frontend/src/js/entity-history/timeline-search/SearchControl.tsx @@ -2,8 +2,8 @@ import { faSearch } from "@fortawesome/free-solid-svg-icons"; import { memo } from "react"; import { useTranslation } from "react-i18next"; import { Icon } from "../../ui-components/Icon"; +import { ToggleButton } from "../../ui-components/ToggleButton"; import { Tooltip, TooltipTrigger } from "../../ui-components/Tooltip"; -import { SidebarToggle } from "../SidebarControl"; import { useTimelineSearch } from "./timelineSearchState"; const SearchControl = () => { @@ -14,13 +14,13 @@ const SearchControl = () => { return (
- - + {t("history.search")}