diff --git a/frontend/src/index.css b/frontend/src/index.css index 2697573042..a2056fbc15 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -25,12 +25,19 @@ --color-filetype-xlsx: #28a745; --color-filetype-json: #1f5f30; - /* `text-tiny`; the other sizes map onto tailwind defaults: - xs 12px, sm 14px, base 16px, xl 20px, 2xl 24px */ - --text-tiny: 11px; + /* Font size only, line-height stays inherited (as the old theme did it). + Only tailwind's own sizes, anything else is a literal like `text-[11px]` */ + --text-xs--line-height: initial; + --text-sm--line-height: initial; + --text-base--line-height: initial; + --text-lg--line-height: initial; + --text-xl--line-height: initial; + --text-2xl--line-height: initial; /* bare `rounded` */ --radius: 3px; + + --animate-spin-fast: spin 0.5s linear infinite; } @layer base { diff --git a/frontend/src/js/button/BadgeToggleButton.tsx b/frontend/src/js/button/BadgeToggleButton.tsx index c8823e480e..ae8ad3f3ce 100644 --- a/frontend/src/js/button/BadgeToggleButton.tsx +++ b/frontend/src/js/button/BadgeToggleButton.tsx @@ -1,47 +1,37 @@ -import { css } from "@emotion/react"; -import styled from "@emotion/styled"; import type { ReactNode } from "react"; import { useHotkeys } from "react-hotkeys-hook"; -import BasicButton from "./BasicButton"; - -const common = css` - font-weight: 700; - padding: 1px 4px; - white-space: nowrap; -`; - -const ActiveButton = styled(BasicButton)` - border-radius: ${({ theme }) => theme.borderRadius}; - border: 2px solid ${({ theme }) => theme.col.blueGrayDark}; - background-color: white; - font-size: ${({ theme }) => theme.font.sm}; - color: ${({ theme }) => theme.col.blueGrayDark}; - ${common}; +import { tv } from "tailwind-variants"; - &:hover { - background-color: ${({ theme }) => theme.col.grayVeryLight}; - } -`; - -const InactiveButton = styled(BasicButton)` - border-radius: ${({ theme }) => theme.borderRadius}; - border: 2px dotted ${({ theme }) => theme.col.grayLight}; - font-size: ${({ theme }) => theme.font.sm}; - color: ${({ theme }) => theme.col.gray}; - ${common}; - &:hover { - background-color: ${({ theme }) => theme.col.bg}; - } -`; +import BasicButton from "./BasicButton"; -const SuperScript = styled("span")` - padding-left: 3px; - font-size: ${({ theme }) => theme.font.tiny}; - transform: translate(1px, -2px); - display: inline-block; - color: ${({ theme }) => theme.col.gray}; -`; +const badgeToggleButton = tv({ + base: ["rounded", "px-1 py-px", "text-sm", "font-bold", "whitespace-nowrap"], + 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", + ], + }, + }, +}); + +const superScript = tv({ + base: [ + "inline-block", + "pl-[3px]", + "translate-x-px -translate-y-[2px]", + "text-[11px]", + "text-gray-500", + ], +}); interface Props { className?: string; @@ -58,15 +48,16 @@ export const BadgeToggleButton = ({ children, hotkey, }: Props) => { - const Component = active ? ActiveButton : InactiveButton; - useHotkeys(hotkey || "", onClick, { enabled: !!hotkey }, [hotkey, onClick]); return ( - + {!active && "+ "} {children} - {hotkey && {hotkey}} - + {hotkey && {hotkey}} + ); }; diff --git a/frontend/src/js/button/BasicButton.tsx b/frontend/src/js/button/BasicButton.tsx index 2a6ae5558c..11a999fb65 100644 --- a/frontend/src/js/button/BasicButton.tsx +++ b/frontend/src/js/button/BasicButton.tsx @@ -1,6 +1,7 @@ -import styled from "@emotion/styled"; import type { ButtonHTMLAttributes, Ref } from "react"; +import { tv } from "tailwind-variants"; + export interface BasicButtonProps extends ButtonHTMLAttributes { bare?: boolean; @@ -9,42 +10,54 @@ export interface BasicButtonProps large?: boolean; active?: boolean; secondary?: boolean; - autoFocus?: boolean; // Should actually be within the extends, not sure why I had to declare this. } -const Button = styled("button")` - cursor: pointer; - font-weight: ${({ active, secondary }) => - active || secondary ? "700" : "400"}; - padding: ${({ small, tiny, bare, large }) => - bare - ? "0" - : tiny - ? "4px 6px" - : small - ? "6px 8px" - : large - ? "12px 18px" - : "8px 15px"}; - font-size: ${({ theme, small, tiny, large }) => - tiny || small ? theme.font.xs : large ? theme.font.lg : theme.font.sm}; - transition: all 0.2s; - border-radius: ${({ theme }) => theme.borderRadius}; - - &:disabled { - cursor: not-allowed; - opacity: 0.4; - } -`; +const button = tv({ + base: [ + "cursor-pointer", + "rounded", + "px-[15px] py-2", + "text-sm", + "font-normal", + "transition-all duration-200", + "disabled:cursor-not-allowed disabled:opacity-40", + ], + variants: { + active: { true: "font-bold" }, + secondary: { true: "font-bold" }, + // later wins when several are set + large: { true: "px-[18px] py-3 text-xl" }, + small: { true: "px-2 py-[6px] text-xs" }, + tiny: { true: "px-[6px] py-1 text-xs" }, + bare: { true: "p-0" }, + }, +}); const BasicButton = ({ ref, - children, + className, + bare, + tiny, + small, + large, + active, + secondary, ...props }: BasicButtonProps & { ref?: Ref }) => ( - + + ); }; diff --git a/frontend/src/js/button/PrimaryButton.tsx b/frontend/src/js/button/PrimaryButton.tsx index 2e4780c39e..34ece7cc0e 100644 --- a/frontend/src/js/button/PrimaryButton.tsx +++ b/frontend/src/js/button/PrimaryButton.tsx @@ -1,14 +1,23 @@ -import styled from "@emotion/styled"; +import type { Ref } from "react"; -import BasicButton from "./BasicButton"; +import { tv } from "tailwind-variants"; -export default styled(BasicButton)` - color: white; - background-color: ${({ theme }) => theme.col.blueGrayDark}; - background-clip: padding-box; - border: 1px solid ${({ theme }) => theme.col.blueGrayDark}; +import BasicButton, { type BasicButtonProps } from "./BasicButton"; - &:hover { - opacity: 0.9; - } -`; +const primaryButton = tv({ + base: [ + "text-white", + "bg-primary-500 bg-clip-padding", + "border border-primary-500", + "hover:opacity-90", + ], +}); + +const PrimaryButton = ({ + className, + ...props +}: BasicButtonProps & { ref?: Ref }) => ( + +); + +export default PrimaryButton; diff --git a/frontend/src/js/button/QueryResultHistoryButton.tsx b/frontend/src/js/button/QueryResultHistoryButton.tsx index d3145734f4..3491c2300f 100644 --- a/frontend/src/js/button/QueryResultHistoryButton.tsx +++ b/frontend/src/js/button/QueryResultHistoryButton.tsx @@ -1,4 +1,3 @@ -import styled from "@emotion/styled"; import { faListUl, faSpinner } from "@fortawesome/free-solid-svg-icons"; import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; @@ -8,11 +7,6 @@ import { useGetAuthorizedUrl } from "../authorization/useAuthorizedUrl"; import { openHistory, useNewHistorySession } from "../entity-history/actions"; import IconButton from "./IconButton"; -const SxIconButton = styled(IconButton)` - white-space: nowrap; - height: 35px; -`; - export const QueryResultHistoryButton = ({ url, label, @@ -32,7 +26,8 @@ export const QueryResultHistoryButton = ({ const newHistorySession = useNewHistorySession(); return ( - { @@ -41,6 +36,6 @@ export const QueryResultHistoryButton = ({ }} > {t("history.history")} - + ); }; diff --git a/frontend/src/js/button/SelectFileButton.tsx b/frontend/src/js/button/SelectFileButton.tsx index d31269783e..15eee1b7fc 100644 --- a/frontend/src/js/button/SelectFileButton.tsx +++ b/frontend/src/js/button/SelectFileButton.tsx @@ -1,18 +1,25 @@ -import styled from "@emotion/styled"; +import type { Ref } from "react"; -import BasicButton from "./BasicButton"; +import { tv } from "tailwind-variants"; -export const SelectFileButton = styled(BasicButton)` - color: ${({ theme }) => theme.col.gray}; - background-color: transparent; - font-weight: 300; - border: none; - font-size: ${({ theme }) => theme.font.tiny}; - display: flex; - align-items: center; - gap: 5px; +import BasicButton, { type BasicButtonProps } from "./BasicButton"; - &:hover { - text-decoration: underline; - } -`; +const selectFileButton = tv({ + base: [ + "flex items-center", + "gap-[5px]", + "bg-transparent", + "border-0", + "text-[11px]", + "text-gray-500", + "font-light", + "hover:underline", + ], +}); + +export const SelectFileButton = ({ + className, + ...props +}: BasicButtonProps & { ref?: Ref }) => ( + +); diff --git a/frontend/src/js/button/TransparentButton.tsx b/frontend/src/js/button/TransparentButton.tsx index 3c5632274d..27519c7fb6 100644 --- a/frontend/src/js/button/TransparentButton.tsx +++ b/frontend/src/js/button/TransparentButton.tsx @@ -1,20 +1,25 @@ -import styled from "@emotion/styled"; +import type { Ref } from "react"; -import BasicButton from "./BasicButton"; +import { tv } from "tailwind-variants"; -export const TransparentButton = styled(BasicButton)<{ light?: boolean }>` - color: ${({ theme, light }) => (light ? theme.col.gray : theme.col.black)}; - background-color: transparent; - border-radius: ${({ theme }) => theme.borderRadius}; - border: 1px solid - ${({ theme, light }) => (light ? theme.col.grayLight : theme.col.gray)}; +import BasicButton, { type BasicButtonProps } from "./BasicButton"; - &:hover { - background-color: ${({ theme }) => theme.col.grayVeryLight}; - } +const transparentButton = tv({ + base: [ + "rounded", + "bg-transparent hover:bg-gray-50 focus:bg-gray-50", + "border border-gray-500 focus:border-green", + "text-gray-800", + ], + variants: { + light: { true: "border-gray-100 text-gray-500" }, + }, +}); - &:focus { - border: 1px solid ${({ theme }) => theme.col.green}; - background-color: ${({ theme }) => theme.col.grayVeryLight}; - } -`; +export const TransparentButton = ({ + className, + light, + ...props +}: BasicButtonProps & { light?: boolean; ref?: Ref }) => ( + +); diff --git a/frontend/src/js/editor-v2/TreeNode.tsx b/frontend/src/js/editor-v2/TreeNode.tsx index 985f5504ef..cde8a0d0fe 100644 --- a/frontend/src/js/editor-v2/TreeNode.tsx +++ b/frontend/src/js/editor-v2/TreeNode.tsx @@ -5,7 +5,7 @@ import { type DOMAttributes, memo } from "react"; import { useTranslation } from "react-i18next"; import { DNDType } from "../common/constants/dndTypes"; -import { Icon } from "../icon/FaIcon"; +import FaIcon from "../icon/FaIcon"; import { nodeIsConceptQueryNode, useActiveState } from "../model/node"; import { getRootNodeLabel } from "../standard-query-editor/helper"; import type { @@ -296,7 +296,7 @@ export function TreeNode({ )} {tree.dates?.excluded && ( - + {t("editorV2.datesExcluded")} )} diff --git a/frontend/src/js/editor-v2/date-restriction/DateModal.tsx b/frontend/src/js/editor-v2/date-restriction/DateModal.tsx index 6ae1ad9f65..eb20c96827 100644 --- a/frontend/src/js/editor-v2/date-restriction/DateModal.tsx +++ b/frontend/src/js/editor-v2/date-restriction/DateModal.tsx @@ -8,7 +8,7 @@ import { useTranslation } from "react-i18next"; import type { DateRangeT } from "../../api/types"; import IconButton from "../../button/IconButton"; import type { DateStringMinMax } from "../../common/helpers/dateHelper"; -import { Icon } from "../../icon/FaIcon"; +import FaIcon from "../../icon/FaIcon"; import Modal from "../../modal/Modal"; import InputCheckbox from "../../ui-components/InputCheckbox"; import InputDateRange from "../../ui-components/InputDateRange"; @@ -101,7 +101,7 @@ export const DateModal = ({ />
- + {t("queryNodeEditor.excludeTimestamps")} ["style"]; } export interface FaIconPropsT extends IconStyleProps { @@ -28,61 +26,72 @@ export interface FaIconPropsT extends IconStyleProps { className?: string; } -const spin = keyframes` - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -`; - -const shouldForwardProp = (prop: keyof FaIconPropsT) => - isPropValid(prop) || prop === "icon" || prop === "className"; +const icon = tv({ + base: [ + "w-[initial]!", + "text-sm", + "text-gray-800", + "[&.fa-spinner]:animate-spin-fast", + ], + variants: { + left: { true: "pr-[10px]" }, + right: { true: "pl-[10px]" }, + center: { true: "text-center" }, + // later wins when both are set + tiny: { true: "text-[11px]" }, + large: { true: "text-base" }, + disabled: { true: "cursor-not-allowed" }, + }, +}); // First matching flag wins, in this order -const iconColor = (theme: Theme, props: IconStyleProps) => { - if (props.disabled) return theme.col.grayMediumLight; - if (props.red) return theme.col.red; - if (props.gray) return theme.col.gray; - if (props.active) return theme.col.blueGrayDark; - if (props.white) return "#fff"; - if (props.light) return theme.col.blueGrayLight; - if (props.main) return theme.col.blueGray; - return theme.col.black; +const colorClass = (p: IconStyleProps) => { + if (p.disabled) return "text-gray-400"; + if (p.red) return "text-red"; + if (p.gray) return "text-gray-500"; + if (p.active) return "text-primary-500"; + if (p.white) return "text-white"; + if (p.light) return "text-primary-100"; + if (p.main) return "text-primary-200"; + return undefined; }; -// @ts-ignore TODO: Figure out how to avoid a type error with styled here -export const Icon = styled(FontAwesomeIcon, { - shouldForwardProp, -})` - padding-right: ${({ left }) => (left ? "10px" : "0")}; - padding-left: ${({ right }) => (right ? "10px" : "0")}; - text-align: ${({ center }) => (center ? "center" : "left")}; - font-size: ${({ theme, large, tiny }) => - large ? theme.font.md : tiny ? theme.font.tiny : theme.font.sm}; - color: ${({ theme, ...props }) => iconColor(theme, props)}; - cursor: ${({ disabled }) => (disabled ? "not-allowed" : "inherit")}; - width: initial !important; - - &.fa-spinner { - animation: ${spin} 0.5s linear 0s infinite; - } -`; - const FaIcon = ({ ref, - icon, + icon: iconProp, className, - ...restProps + left, + center, + right, + white, + red, + light, + gray, + main, + active, + disabled, + tiny, + large, + small: _small, // only meaningful to IconButton + style, }: FaIconPropsT & { ref?: Ref }) => { return ( - ); }; diff --git a/frontend/src/js/pane/TabNavigation.tsx b/frontend/src/js/pane/TabNavigation.tsx index c2ac05ac21..a576b0a35d 100644 --- a/frontend/src/js/pane/TabNavigation.tsx +++ b/frontend/src/js/pane/TabNavigation.tsx @@ -1,6 +1,5 @@ import styled from "@emotion/styled"; import { faSpinner } from "@fortawesome/free-solid-svg-icons"; - import { tv } from "tailwind-variants"; import FaIcon from "../icon/FaIcon"; import { HoverNavigatable } from "../small-tab-navigation/HoverNavigatable"; diff --git a/frontend/src/js/previous-queries/upload/CSVColumnPicker.tsx b/frontend/src/js/previous-queries/upload/CSVColumnPicker.tsx index 50550167e6..8c8d4af716 100644 --- a/frontend/src/js/previous-queries/upload/CSVColumnPicker.tsx +++ b/frontend/src/js/previous-queries/upload/CSVColumnPicker.tsx @@ -13,7 +13,6 @@ import type { TFunction } from "i18next"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { tv } from "tailwind-variants"; - import type { QueryUploadConfigT, UploadQueryResponseT } from "../../api/types"; import IconButton from "../../button/IconButton"; import PrimaryButton from "../../button/PrimaryButton"; diff --git a/frontend/src/js/ui-components/DropzoneWithFileInput.tsx b/frontend/src/js/ui-components/DropzoneWithFileInput.tsx index 883e89fd85..46d7622a86 100644 --- a/frontend/src/js/ui-components/DropzoneWithFileInput.tsx +++ b/frontend/src/js/ui-components/DropzoneWithFileInput.tsx @@ -39,7 +39,9 @@ const SxDropzone = styled(Dropzone)<{ isInitial?: boolean; tight?: boolean }>` } `; -const SxSelectFileButton = styled(SelectFileButton)<{ outside?: boolean }>` +const SxSelectFileButton = styled(SelectFileButton, { + shouldForwardProp: (prop) => prop !== "outside", +})<{ outside?: boolean }>` position: absolute; top: ${({ outside }) => (outside ? "-26px" : "3px")}; right: ${({ outside }) => (outside ? "-12px" : "0")}; diff --git a/frontend/src/js/ui-components/InputDateRange.tsx b/frontend/src/js/ui-components/InputDateRange.tsx index fd2266f306..a5a4bba947 100644 --- a/frontend/src/js/ui-components/InputDateRange.tsx +++ b/frontend/src/js/ui-components/InputDateRange.tsx @@ -14,7 +14,7 @@ import { parseDateToState, } from "../common/helpers/dateHelper"; import { exists } from "../common/helpers/exists"; -import { Icon } from "../icon/FaIcon"; +import FaIcon from "../icon/FaIcon"; import InfoTooltip from "../tooltip/InfoTooltip"; import InputDate from "./InputDate/InputDate"; @@ -172,7 +172,7 @@ const InputDateRange = ({ return ( - + {exists(indexPrefix) && # {indexPrefix}} {label} # {filterIdx} ) : ( -