diff --git a/deps.edn b/deps.edn index 9adaccabab82f..d095776e323bc 100644 --- a/deps.edn +++ b/deps.edn @@ -117,7 +117,7 @@ org.clojure/tools.namespace {:mvn/version "1.3.0"} org.clojure/tools.reader {:mvn/version "1.3.6"} org.clojure/tools.trace {:mvn/version "0.7.11"} ; function tracing - org.eclipse.jetty/jetty-server {:mvn/version "9.4.44.v20210927"} ; web server + org.eclipse.jetty/jetty-server {:mvn/version "9.4.48.v20220622"} ; web server org.flatland/ordered {:mvn/version "1.15.10"} ; ordered maps & sets org.graalvm.js/js {:mvn/version "22.0.0.2"} ; JavaScript engine org.liquibase/liquibase-core {:mvn/version "4.10.0" ; migration management (Java lib) diff --git a/docs/troubleshooting-guide/sync-fingerprint-scan.md b/docs/troubleshooting-guide/sync-fingerprint-scan.md index 6ab2fc39ca350..792fb1327b922 100644 --- a/docs/troubleshooting-guide/sync-fingerprint-scan.md +++ b/docs/troubleshooting-guide/sync-fingerprint-scan.md @@ -17,7 +17,7 @@ Metabase needs to know what's in your database in order to show tables and field 2. Metabase *fingerprints* the column the first time it synchronizes. Fingerprinting fetches the first 10,000 rows from each column and uses that data to guesstimate how many unique values each column has, what the minimum and maximum values are for numeric and timestamp columns, and so on. Metabase only fingerprints each column once, unless the administrator explicitly tells it to fingerprint the column again, or in the rare event that a new release of Metabase changes the fingerprinting logic. -3. A *scan* is similar to fingerprinting, but is done every 24 hours (unless it's configured to run less often or disabled). Scanning looks at the first 1000 distinct records ordered ascending, when a field is set to "A list of all values" in the Data Model, which is used to display options in dropdowns. If the textual result of scanning a column is more than 10 kilobytes long, for example, we display a search box instead of a dropdown. +3. A *scan* is similar to fingerprinting. Metabase will scan a database by default every 24 hours (though you can configure Metabase to run a scan less frequently, or disable scanning entirely). When you set a field to "A list of all values" in the [Data Model](../administration-guide/03-metadata-editing.md), which is used to display options in dropdown menus, scanning looks at the first 1,000 distinct records (ordered ascending). For each field scanned, Metabase stores only the first 100 kilobytes of text. If more values exist, Metabase displays the stored values in the dropdown menus, and only triggers a database search query to look for more values when people type in the search box for that filter widget.

Metabase can't sync, fingerprint, or scan

diff --git a/frontend/src/metabase-lib/lib/Dimension.ts b/frontend/src/metabase-lib/lib/Dimension.ts index 4a9ff65fce681..6ab4ec6405874 100644 --- a/frontend/src/metabase-lib/lib/Dimension.ts +++ b/frontend/src/metabase-lib/lib/Dimension.ts @@ -274,6 +274,10 @@ export default class Dimension { ); } + isExpression(): boolean { + return isExpressionDimension(this); + } + foreign(dimension: Dimension): FieldDimension { return null; } diff --git a/frontend/src/metabase-lib/lib/DimensionOptions/DimensionOptions.ts b/frontend/src/metabase-lib/lib/DimensionOptions/DimensionOptions.ts index 63db181375896..b5bda926c3571 100644 --- a/frontend/src/metabase-lib/lib/DimensionOptions/DimensionOptions.ts +++ b/frontend/src/metabase-lib/lib/DimensionOptions/DimensionOptions.ts @@ -36,7 +36,9 @@ export default class DimensionOptions { } sections({ extraItems = [] } = {}): DimensionOptionsSection[] { - const [dimension] = this.dimensions; + const dimension = + this.dimensions.find(dimension => !dimension.isExpression()) ?? + this.dimensions[0]; const table = dimension && dimension.field().table; const tableName = table ? table.objectName() : null; const mainSection: DimensionOptionsSection = { diff --git a/frontend/src/metabase/admin/datamodel/components/database/ColumnItem/ColumnItem.styled.tsx b/frontend/src/metabase/admin/datamodel/components/database/ColumnItem/ColumnItem.styled.tsx index 66be6c6c4fe2f..b7a3d1af20108 100644 --- a/frontend/src/metabase/admin/datamodel/components/database/ColumnItem/ColumnItem.styled.tsx +++ b/frontend/src/metabase/admin/datamodel/components/database/ColumnItem/ColumnItem.styled.tsx @@ -7,7 +7,7 @@ interface ColumnItemInputProps { } export const ColumnItemInput = styled(InputBlurChange)` - border-color: ${color("border-dark")}; + border-color: ${color("border")}; background-color: ${props => color(props.variant === "primary" ? "white" : "bg-light")}; diff --git a/frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx b/frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx index d5f9bd2ed91b0..6a3e4f13b9511 100644 --- a/frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx +++ b/frontend/src/metabase/admin/settings/components/SettingsBatchForm.jsx @@ -21,7 +21,7 @@ const VALIDATIONS = { }, email_list: { validate: value => value.every(MetabaseUtils.isEmail), - message: t`That's not a valid list of email addresses`, + message: t`That's not a valid email address`, }, integer: { validate: value => !isNaN(parseInt(value)), diff --git a/frontend/src/metabase/admin/settings/components/widgets/SettingCommaDelimitedInput.jsx b/frontend/src/metabase/admin/settings/components/widgets/SettingCommaDelimitedInput.jsx index a254db13a0b27..178b4edf93be2 100644 --- a/frontend/src/metabase/admin/settings/components/widgets/SettingCommaDelimitedInput.jsx +++ b/frontend/src/metabase/admin/settings/components/widgets/SettingCommaDelimitedInput.jsx @@ -3,6 +3,8 @@ import React from "react"; import InputBlurChange from "metabase/components/InputBlurChange"; import cx from "classnames"; +const maybeSingletonList = value => (value ? [value] : null); + const SettingCommaDelimitedInput = ({ setting, onChange, @@ -25,8 +27,13 @@ const SettingCommaDelimitedInput = ({ // https://github.com/metabase/metabase/issues/22540 value={setting.value ? setting.value[0] : ""} placeholder={setting.placeholder} - onChange={fireOnChange ? e => onChange([e.target.value]) : null} - onBlurChange={!fireOnChange ? e => onChange([e.target.value]) : null} + // If the input's value is empty, setting.value should be null + onChange={ + fireOnChange ? e => onChange(maybeSingletonList(e.target.value)) : null + } + onBlurChange={ + !fireOnChange ? e => onChange(maybeSingletonList(e.target.value)) : null + } autoFocus={autoFocus} /> ); diff --git a/frontend/src/metabase/admin/settings/selectors.js b/frontend/src/metabase/admin/settings/selectors.js index 74a5271b416a1..5b797b3c44bad 100644 --- a/frontend/src/metabase/admin/settings/selectors.js +++ b/frontend/src/metabase/admin/settings/selectors.js @@ -214,7 +214,7 @@ const SECTIONS = updateSectionsWithPlugins({ type: "string", required: false, widget: SettingCommaDelimitedInput, - validations: [["email_list", t`That's not a valid email addresses`]], + validations: [["email_list", t`That's not a valid email address`]], }, ], }, diff --git a/frontend/src/metabase/components/InputBlurChange.styled.tsx b/frontend/src/metabase/components/InputBlurChange.styled.tsx index 48118c715ba9b..5d67d80d1132c 100644 --- a/frontend/src/metabase/components/InputBlurChange.styled.tsx +++ b/frontend/src/metabase/components/InputBlurChange.styled.tsx @@ -2,5 +2,5 @@ import styled from "@emotion/styled"; import { color } from "metabase/lib/colors"; export const Input = styled.input` - border: 1px solid ${color("border-dark")}; + border: 1px solid ${color("border")}; `; diff --git a/frontend/src/metabase/components/TextInput/TextInput.styled.tsx b/frontend/src/metabase/components/TextInput/TextInput.styled.tsx index a6b9a547d1317..48aa4f75a1263 100644 --- a/frontend/src/metabase/components/TextInput/TextInput.styled.tsx +++ b/frontend/src/metabase/components/TextInput/TextInput.styled.tsx @@ -33,7 +33,7 @@ const getBorderColor = (colorScheme: ColorScheme, invalid?: boolean) => { return color("error"); } - return colorScheme === "transparent" ? "transparent" : color("border-dark"); + return colorScheme === "transparent" ? "transparent" : color("border"); }; export const Input = styled.input` diff --git a/frontend/src/metabase/components/TokenField/TokenField.styled.tsx b/frontend/src/metabase/components/TokenField/TokenField.styled.tsx index 6037a13ac4c2f..b18fb3b738536 100644 --- a/frontend/src/metabase/components/TokenField/TokenField.styled.tsx +++ b/frontend/src/metabase/components/TokenField/TokenField.styled.tsx @@ -21,7 +21,7 @@ export const TokenFieldContainer = styled.ul` overflow-x: auto; overflow-y: auto; border-radius: ${space(1)}; - border: 1px solid ${color("border-dark")}; + border: 1px solid ${color("border")}; `; export const TokenInputItem = styled.li` diff --git a/frontend/src/metabase/components/TokenField/TokenField.tsx b/frontend/src/metabase/components/TokenField/TokenField.tsx index 30b0093e882a5..d4281f1f81637 100644 --- a/frontend/src/metabase/components/TokenField/TokenField.tsx +++ b/frontend/src/metabase/components/TokenField/TokenField.tsx @@ -459,18 +459,12 @@ export default class TokenField extends Component< } else { onChange(valueToAdd.slice(0, 1)); } - // reset the input value - // setTimeout(() => - // this.setInputValue("") - // ) } removeValue(valueToRemove: any) { const { value, onChange } = this.props; const values = value.filter(v => !this._valueIsEqual(v, valueToRemove)); onChange(values); - // reset the input value - // this.setInputValue(""); } _valueIsEqual(v1: any, v2: any) { @@ -596,6 +590,7 @@ export default class TokenField extends Component< onClick={e => { e.preventDefault(); this.removeValue(v); + this.inputRef?.current?.blur(); }} onMouseDown={e => e.preventDefault()} > diff --git a/frontend/src/metabase/core/components/Input/Input.styled.tsx b/frontend/src/metabase/core/components/Input/Input.styled.tsx index fbec00cb46ab6..29bb3bf31299c 100644 --- a/frontend/src/metabase/core/components/Input/Input.styled.tsx +++ b/frontend/src/metabase/core/components/Input/Input.styled.tsx @@ -26,7 +26,7 @@ export const InputField = styled.input` font-size: 1rem; color: ${color("text-dark")}; padding: 0.75rem; - border: 1px solid ${color("border-dark")}; + border: 1px solid ${color("border")}; border-radius: ${space(1)}; background-color: ${props => color(props.readOnly ? "bg-light" : "bg-white")}; outline: none; diff --git a/frontend/src/metabase/core/components/SelectButton/SelectButton.styled.tsx b/frontend/src/metabase/core/components/SelectButton/SelectButton.styled.tsx index bd9ad521eb3fc..2410391b6dfce 100644 --- a/frontend/src/metabase/core/components/SelectButton/SelectButton.styled.tsx +++ b/frontend/src/metabase/core/components/SelectButton/SelectButton.styled.tsx @@ -24,7 +24,7 @@ export const SelectButtonRoot = styled.button` padding: 0.6em; border: 1px solid ${({ hasValue, highlighted }) => - hasValue && highlighted ? color("brand") : color("border-dark")}; + hasValue && highlighted ? color("brand") : color("border")}; background-color: ${({ hasValue, highlighted }) => hasValue && highlighted ? color("brand") : color("white")}; border-radius: ${space(1)}; diff --git a/frontend/src/metabase/core/components/Tab/Tab.styled.tsx b/frontend/src/metabase/core/components/Tab/Tab.styled.tsx index 5384c1f8be1e6..a65d9154c8bcf 100644 --- a/frontend/src/metabase/core/components/Tab/Tab.styled.tsx +++ b/frontend/src/metabase/core/components/Tab/Tab.styled.tsx @@ -1,5 +1,6 @@ import styled from "@emotion/styled"; -import { color } from "metabase/lib/colors"; +import { color, alpha } from "metabase/lib/colors"; +import { space } from "metabase/styled-components/theme"; import Icon from "metabase/components/Icon"; import Ellipsified from "../Ellipsified"; @@ -8,19 +9,20 @@ export interface TabProps { } export const TabRoot = styled.button` - display: inline-flex; - align-items: center; - color: ${props => - props.isSelected ? color("text-dark") : color("text-light")}; + display: flex; + width: 100%; + flex: 1; + text-align: left; + + color: ${props => (props.isSelected ? color("brand") : color("text-light"))}; + background-color: ${props => + props.isSelected ? alpha("brand", 0.1) : "transparent"}; cursor: pointer; margin-bottom: 0.75rem; - padding-bottom: 0.25rem; - - &:first-of-type { - padding-right: 1.5rem; - border-right: ${color("border")} 1px solid; - } + padding: 0.75rem 1rem; + margin-right: ${space(1)}; + border-radius: ${space(0)}; &:hover { color: ${color("brand")}; @@ -38,10 +40,13 @@ export const TabRoot = styled.button` export const TabIcon = styled(Icon)` width: 0.8rem; height: 0.8rem; + margin-top: 0.2rem; margin-right: 0.5rem; `; -export const TabLabel = styled(Ellipsified)` +export const TabLabel = styled.div` + width: 100%; font-weight: bold; - max-width: 16rem; + overflow: hidden; + text-overflow: ellipsis; `; diff --git a/frontend/src/metabase/core/components/TabList/TabList.stories.tsx b/frontend/src/metabase/core/components/TabList/TabList.stories.tsx index 8f205cfd47a61..e3915b3859edb 100644 --- a/frontend/src/metabase/core/components/TabList/TabList.stories.tsx +++ b/frontend/src/metabase/core/components/TabList/TabList.stories.tsx @@ -11,7 +11,7 @@ export default { }; const sampleStyle = { - maxWidth: "400px", + maxWidth: "200px", padding: "10px", border: "1px solid #ccc", }; @@ -25,7 +25,7 @@ const Template: ComponentStory = args => { Tab 1 Tab 2 - Tab3supercalifragilisticexpialidocious + Tab3_supercal_ifragilisticexpia_lidocious Tab 4 With a Very Long Name that may cause this component to wrap diff --git a/frontend/src/metabase/core/components/TabList/TabList.styled.tsx b/frontend/src/metabase/core/components/TabList/TabList.styled.tsx index 88f8f860073e0..ca37b7c084bff 100644 --- a/frontend/src/metabase/core/components/TabList/TabList.styled.tsx +++ b/frontend/src/metabase/core/components/TabList/TabList.styled.tsx @@ -1,42 +1,10 @@ import styled from "@emotion/styled"; -import { alpha, color } from "metabase/lib/colors"; -import { space } from "metabase/styled-components/theme"; export const TabListRoot = styled.div` position: relative; - display: flex; - align-items: center; `; export const TabListContent = styled.div` - overflow-x: hidden; - display: flex; - align-items: flex-start; - gap: 1.5rem; scroll-behavior: smooth; -`; - -interface ScrollButtonProps { - directionIcon: "left" | "right"; -} - -export const ScrollButton = styled.button` - position: absolute; - cursor: pointer; height: 100%; - width: 3rem; - padding-bottom: ${space(2)}; - text-align: ${props => props.directionIcon}; - color: ${color("text-light")}; - &:hover { - color: ${color("brand")}; - } - ${props => props.directionIcon}: 0; - background: linear-gradient( - to ${props => props.directionIcon}, - ${alpha("white", 0.1)}, - ${alpha("white", 0.5)}, - 30%, - ${color("white")} - ); `; diff --git a/frontend/src/metabase/core/components/TabList/TabList.tsx b/frontend/src/metabase/core/components/TabList/TabList.tsx index 0a556a50ede14..d04a83dbf599a 100644 --- a/frontend/src/metabase/core/components/TabList/TabList.tsx +++ b/frontend/src/metabase/core/components/TabList/TabList.tsx @@ -12,7 +12,7 @@ import React, { import Icon from "metabase/components/Icon"; import { useUniqueId } from "metabase/hooks/use-unique-id"; import { TabContext, TabContextType } from "../Tab"; -import { TabListContent, TabListRoot, ScrollButton } from "./TabList.styled"; +import { TabListContent, TabListRoot } from "./TabList.styled"; const UNDERSCROLL_PIXELS = 32; @@ -30,9 +30,6 @@ const TabList = forwardRef(function TabGroup( const idPrefix = useUniqueId(); const outerContext = useContext(TabContext); - const [scrollPosition, setScrollPosition] = useState(0); - const [showScrollRight, setShowScrollRight] = useState(false); - const tabListContentRef = useRef(null); const innerContext = useMemo(() => { @@ -41,31 +38,6 @@ const TabList = forwardRef(function TabGroup( const activeContext = outerContext.isDefault ? innerContext : outerContext; - const scroll = (direction: string) => { - if (tabListContentRef.current) { - const container = tabListContentRef.current as HTMLDivElement; - - const scrollDistance = - (container.offsetWidth - UNDERSCROLL_PIXELS) * - (direction === "left" ? -1 : 1); - container.scrollBy(scrollDistance, 0); - setScrollPosition(container.scrollLeft + scrollDistance); - } - }; - - const showScrollLeft = scrollPosition > 0; - - useEffect(() => { - if (!tabListContentRef.current) { - return; - } - - const container = tabListContentRef.current as HTMLDivElement; - setShowScrollRight( - scrollPosition + container.offsetWidth < container.scrollWidth, - ); - }, [scrollPosition]); - return ( @@ -73,29 +45,8 @@ const TabList = forwardRef(function TabGroup( {children} - {showScrollLeft && ( - scroll("left")} /> - )} - {showScrollRight && ( - scroll("right")} /> - )} ); }); -interface ScrollArrowProps { - direction: "left" | "right"; - onClick: () => void; -} - -const ScrollArrow = ({ direction, onClick }: ScrollArrowProps) => ( - - - -); - export default TabList; diff --git a/frontend/src/metabase/css/components/form.css b/frontend/src/metabase/css/components/form.css index f9a6f4e45656b..ed4d0a690d1dc 100644 --- a/frontend/src/metabase/css/components/form.css +++ b/frontend/src/metabase/css/components/form.css @@ -1,5 +1,5 @@ :root { - --form-field-border-color: var(--color-border-dark); + --form-field-border-color: var(--color-border); --input-border-radius: 8px; } ::-webkit-input-placeholder { @@ -56,7 +56,7 @@ } .Form-file-input::before { background: transparent; - border: 1px solid color-mod(var(--color-border-dark) blackness(5%)); + border: 1px solid color-mod(var(--color-border) blackness(5%)); border-radius: 6px; box-sizing: border-box; color: var(--color-text-dark); diff --git a/frontend/src/metabase/css/core/colors.css b/frontend/src/metabase/css/core/colors.css index d9ef59906255d..01c3c89d08a17 100644 --- a/frontend/src/metabase/css/core/colors.css +++ b/frontend/src/metabase/css/core/colors.css @@ -31,7 +31,6 @@ --color-focus: #cbe2f7; --color-shadow: rgba(0, 0, 0, 0.13); --color-border: #eeecec; - --color-border-dark: #c9ced3; /* Saturated colors for the SQL editor. Shouldn't be used elsewhere since they're not white-labelable. */ --color-saturated-blue: #2d86d4; diff --git a/frontend/src/metabase/css/core/inputs.css b/frontend/src/metabase/css/core/inputs.css index 23c7b2f600600..de09c6e19ed6b 100644 --- a/frontend/src/metabase/css/core/inputs.css +++ b/frontend/src/metabase/css/core/inputs.css @@ -1,5 +1,5 @@ :root { - --input-border-color: var(--color-border-dark); + --input-border-color: var(--color-border); --input-border-active-color: var(--color-brand); --input-border-radius: 8px; } diff --git a/frontend/src/metabase/dashboard/containers/DashboardHeader.styled.jsx b/frontend/src/metabase/dashboard/containers/DashboardHeader.styled.jsx index c55ae92e9bdf3..976f098352be5 100644 --- a/frontend/src/metabase/dashboard/containers/DashboardHeader.styled.jsx +++ b/frontend/src/metabase/dashboard/containers/DashboardHeader.styled.jsx @@ -8,7 +8,7 @@ export const DashboardHeaderActionDivider = styled.div` padding-left: 0.5rem; margin-left: 0.5rem; width: 0px; - border-left: 1px solid ${color("border-dark")}; + border-left: 1px solid ${color("border")}; `; export const DashboardHeaderButton = styled(Button)` diff --git a/frontend/src/metabase/lib/colors/palette.ts b/frontend/src/metabase/lib/colors/palette.ts index faac2718e7aaf..29151f21bba7a 100644 --- a/frontend/src/metabase/lib/colors/palette.ts +++ b/frontend/src/metabase/lib/colors/palette.ts @@ -38,7 +38,6 @@ export const colors: ColorPalette = { "bg-night": "#42484E", shadow: "rgba(0,0,0,0.08)", border: "#EEECEC", - "border-dark": "#C9CED3", /* Saturated colors for the SQL editor. Shouldn't be used elsewhere since they're not white-labelable. */ "saturated-blue": "#2D86D4", diff --git a/frontend/src/metabase/lib/formatting.js b/frontend/src/metabase/lib/formatting.js index ae1f6d97d4062..8f92b60bfb528 100644 --- a/frontend/src/metabase/lib/formatting.js +++ b/frontend/src/metabase/lib/formatting.js @@ -180,6 +180,15 @@ export function formatNumber(number, options = {}) { formatted = replaceNumberSeparators(formatted, separators); } + // fixes issue where certain symbols, such as + // czech Kč, and Bitcoin ₿, are not displayed + if (options["currency_style"] === "symbol") { + formatted = formatted.replace( + options["currency"], + getCurrencySymbol(options["currency"]), + ); + } + return formatted; } catch (e) { console.warn("Error formatting number", e); diff --git a/frontend/src/metabase/query_builder/components/QuestionActions.styled.tsx b/frontend/src/metabase/query_builder/components/QuestionActions.styled.tsx index 29aeeead62b54..b5868329ea193 100644 --- a/frontend/src/metabase/query_builder/components/QuestionActions.styled.tsx +++ b/frontend/src/metabase/query_builder/components/QuestionActions.styled.tsx @@ -3,7 +3,7 @@ import { color } from "metabase/lib/colors"; import DatasetMetadataStrengthIndicator from "./view/sidebars/DatasetManagementSection/DatasetMetadataStrengthIndicator"; export const QuestionActionsDivider = styled.div` - border-left: 1px solid ${color("border-dark")}; + border-left: 1px solid ${color("border")}; margin-left: 0.5rem; margin-right: 0.5rem; height: 1.25rem; diff --git a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/BulkFilterItem.tsx b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/BulkFilterItem.tsx index c795819ef45a9..207f777e7e6ae 100644 --- a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/BulkFilterItem.tsx +++ b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterItem/BulkFilterItem.tsx @@ -97,12 +97,10 @@ export const BulkFilterItem = ({ onChange={changeOperator} /> ); diff --git a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.styled.tsx b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.styled.tsx index e70ca3c14eecc..24b873ae4748f 100644 --- a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.styled.tsx +++ b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.styled.tsx @@ -1,26 +1,29 @@ import styled from "@emotion/styled"; import { color } from "metabase/lib/colors"; -import { - space, - breakpointMinHeightMedium, -} from "metabase/styled-components/theme"; +import { space, breakpointMinSmall } from "metabase/styled-components/theme"; export const ListRoot = styled.div` margin-bottom: 1rem; `; export const ListRow = styled.div` - padding: 1.5rem 3rem; - ${breakpointMinHeightMedium} { - padding: 2.5rem 3rem; - } + padding: 1.5rem 2rem; border-bottom: 1px solid ${color("border")}; &:last-of-type { border-bottom: none; } + &:hover, + :focus-within { + background-color: ${color("bg-light")}; + } `; export const FilterContainer = styled.div` + ${breakpointMinSmall} { + display: grid; + grid-template-columns: 1fr 2fr; + } + gap: 1rem; &:not(:last-of-type) { border-bottom: 1px solid ${color("border")}; margin-bottom: ${space(2)}; diff --git a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.tsx b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.tsx index 05cf520db3c46..5f25bae6735e0 100644 --- a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.tsx +++ b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterList/BulkFilterList.tsx @@ -168,8 +168,8 @@ const SegmentListItem = ({ onRemoveFilter, onClearSegments, }: SegmentListItemProps): JSX.Element => ( - <> - + + - - + + ); export default BulkFilterList; diff --git a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterModal/BulkFilterModal.styled.tsx b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterModal/BulkFilterModal.styled.tsx index 78570a2365570..2ff8910d32540 100644 --- a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterModal/BulkFilterModal.styled.tsx +++ b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterModal/BulkFilterModal.styled.tsx @@ -11,31 +11,33 @@ import TabPanel from "metabase/core/components/TabPanel"; import Ellipsified from "metabase/core/components/Ellipsified"; import IconButtonWrapper from "metabase/components/IconButtonWrapper"; -export const ModalRoot = styled.div` +interface ModalRootProps { + hasSideNav?: boolean; +} + +export const ModalRoot = styled.div` display: flex; flex-direction: column; - height: 90vh; - width: min(98vw, 50rem); + width: min(98vw, ${props => (props.hasSideNav ? "70rem" : "55rem")}); +`; + +export const ModalMain = styled.div` + height: calc(90vh - 10rem); ${breakpointMaxSmall} { - height: 98vh; + height: calc(98vh - 10rem); + flex-direction: column; } + display: flex; `; export const ModalHeader = styled.div` display: flex; align-items: center; - padding: 1rem 3rem 0 3rem; - ${breakpointMinHeightMedium} { - padding: 2rem 3rem 0 3rem; - } + padding: 1.5rem 2rem; + border-bottom: 1px solid ${color("border")}; `; export const ModalBody = styled.div` - border-top: 1px solid ${color("border")}; - margin-top: 1rem; - ${breakpointMinHeightMedium} { - margin-top: 1.5rem; - } overflow-y: auto; flex: 1; `; @@ -44,27 +46,31 @@ export const ModalFooter = styled.div` display: flex; justify-content: space-between; gap: 1rem; - padding: 1.5rem 3rem; + padding: 1.5rem 2rem; `; export const ModalTitle = styled(Ellipsified)` flex: 1 1 auto; color: ${color("text-dark")}; - font-size: 1rem; - ${breakpointMinHeightMedium} { - font-size: 1.25rem; - } + font-size: 1.25rem; line-height: 1.5rem; font-weight: bold; `; export const ModalTabList = styled(TabList)` - font-size: 0.875rem; + padding: 1rem; + width: 15rem; + border-right: 1px solid ${color("border")}; + overflow-y: auto; + + ${breakpointMaxSmall} { + width: 100%; + height: 5rem; + } + ${breakpointMinHeightMedium} { font-size: 1rem; } - margin: 1.5rem 3rem 0 3rem; - flex-shrink: 0; `; export const ModalTabPanel = styled(TabPanel)` diff --git a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterModal/BulkFilterModal.tsx b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterModal/BulkFilterModal.tsx index 55ddede4b1d60..e9abb43ebc25d 100644 --- a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterModal/BulkFilterModal.tsx +++ b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterModal/BulkFilterModal.tsx @@ -25,6 +25,7 @@ import { ModalFooter, ModalHeader, ModalRoot, + ModalMain, ModalTabList, ModalTabPanel, ModalTitle, @@ -107,8 +108,10 @@ const BulkFilterModal = ({ setIsChanged(true); }; + const hasSideNav = sections.length > 1; + return ( - + {getTitle(query, sections.length === 1)} {showSearch ? ( @@ -119,28 +122,30 @@ const BulkFilterModal = ({ )} - {sections.length === 1 || searchItems ? ( - - ) : ( - - )} + + {!hasSideNav || searchItems ? ( + + ) : ( + + )} +