From 319990078e6530bb50d843c51ebbdd7b692f4a1a Mon Sep 17 00:00:00 2001 From: Nick Fitzpatrick <nick@metabase.com> Date: Wed, 14 Dec 2022 13:33:47 -0400 Subject: [PATCH] Transition InputBlurChange to use core Input component (#27122) * Convert to TypeScript, handle ColumnItem and MetadataTable * more progress * handled numeric inputs * Transition InputBlurChange to core Input component * Removing input class from legacy date picker just incase * Small style adjustments in Admin and Viz Settings * working on tests * Tests passing * Adjusting styling * PR Cleanup * add input size=large (#27217) * Suppress console log when using `with-log-messages-for-level` (#26468) * disable additivity when using "with-log-level" * only set when parent is root * wording * remove a test that is no longer needed * fix indents * one missing indent fix * [CI] Add `concurrency` to the `drivers` workflow (#27223) * Fix datetime-diff helper text (#27224) * Improve datetimeDiff type error message (#27225) * Update test * Update implementations * Relieve db pressure on api/health check (#27192) * Relieve db pressure on api/health check https://github.com/metabase/metabase/issues/26266 Servers under heavy load can be slow to respond to the api/health check. This can lead to k8s killing healthy instances happily humming along serving requests. One idea floated was to use QoSFilters https://www.eclipse.org/jetty/javadoc/jetty-9/org/eclipse/jetty/servlets/QoSFilter.html to prioritize those requests in front of others. But I suspect this might not be our bottleneck. Our health endpoint was updated to see if it could acquire an endpoint when we were dealing with connection pool issues. We were reporting the instance was healthy once it has finished the init process, but would report healthy if 60/15 app-db connections were used and no actual queries could complete. The remedy was adding `(sql-jdbc.conn/can-connect-with-spec? {:datasource (mdb.connection/data-source)})` to the endpoint. But now to get information about the health of the system we have to wait in the queue to get a datasource. The hope is that this change which monitors for recent db checkins (query success) and checkouts (query begun) can be a proxy for db activity without having to wait for a connection and hit the db ourselves. Some simple and crude benchmarking: - use `siege` to hit `api/database/<app-db>/sync_schema` - in a separate tab, use `siege` to hit `api/health` Three trials with unconditional db access and conditional db access (look for recent activity set by the new `ConnectionCustomizer`). One siege client is synching the app-db's schema with 80 clients each sending 60 requests. the other has 1 client sending 60 requests to api/health. Run | Elapsed Time | max tx | tx rate before change | 7.16s | 0.79s | 8.38 tx/s before change | 23.91s | 1.44s | 2.51 tx/s before change | 13.00s | 0.50s | 4.62 tx/s ---------------------------------------------------- after change | 4.46s | 0.27s | 13.45 tx/s after change | 5.81s | 0.61s | 10.33 tx/s after change | 4.54s | 0.44s | 13.22 tx/s Full(er) results below: ``` Unconditional db access ======================= siege -c80 -r 40 "http://localhost:3000/api/database/2/sync_schema POST" -H "Cookie: $SESSION" siege -c 1 -r 60 "http://localhost:3000/api/health" Elapsed time: 7.16 secs Response time: 0.12 secs Transaction rate: 8.38 trans/sec Longest transaction: 0.79 Shortest transaction: 0.01 Elapsed time: 23.91 secs Response time: 0.40 secs Transaction rate: 2.51 trans/sec Longest transaction: 1.44 Shortest transaction: 0.02 Elapsed time: 13.00 secs Response time: 0.22 secs Transaction rate: 4.62 trans/sec Longest transaction: 0.50 Shortest transaction: 0.06 Conditional db access ============================================================== Elapsed time: 4.46 secs Response time: 0.07 secs Transaction rate: 13.45 trans/sec Longest transaction: 0.27 Shortest transaction: 0.01 Elapsed time: 5.81 secs Response time: 0.10 secs Transaction rate: 10.33 trans/sec Longest transaction: 0.61 Shortest transaction: 0.00 Elapsed time: 4.54 secs Response time: 0.08 secs Transaction rate: 13.22 trans/sec Longest transaction: 0.44 Shortest transaction: 0.01 ``` * Remove reflection in `.put` call (not the reflections trategy) also remove the call to `classloader/the-classloader` as it did nothing * Comment and settle on a single method * tests * select from db twice had a failure in CI. give it time to do its thing with another db call * block to wait for timestamp update? * unflake the tests tasks and events from outside the thread can hit the db. the ConnectionCustomizer is also run from c3p0 controlled threads so we can't easily isolate everything to our thread Was running ```clojure (comment (dotimes [n 5] (dotimes [_ 100] (recent-activity-test) (CheckinTracker-test)) (println (* (inc n) 100))) ) ``` to run the tests 500 times and would keep getting flakes at a rate ~1/100 to 1/500. Just frustration for the future. * typehint * Switch it up a bit Tests were flaking in h2 and I don't know why. I'm switching to just updating recent activity on most methods. * final touches Co-authored-by: Aleksandr Lesnenko <alxnddr@users.noreply.github.com> Co-authored-by: Ngoc Khuat <qn.khuat@gmail.com> Co-authored-by: Nemanja Glumac <31325167+nemanjaglumac@users.noreply.github.com> Co-authored-by: Cal Herries <39073188+calherries@users.noreply.github.com> Co-authored-by: dpsutton <dan@dpsutton.com> --- .../FontFilesWidget/FontFilesWidget.tsx | 1 - .../datamodel/components/FieldRemapping.jsx | 6 +- .../components/FieldRemapping.styled.tsx | 5 ++ .../database/ColumnItem/ColumnItem.jsx | 5 +- .../database/ColumnItem/ColumnItem.styled.tsx | 6 +- .../MetadataTable/MetadataTable.styled.tsx | 43 +++++------ .../admin/datamodel/containers/FieldApp.jsx | 5 +- .../datamodel/containers/FieldApp.styled.tsx | 6 +- .../admin/people/components/GroupsListing.jsx | 6 +- .../FilterableTree/FilterableTree.tsx | 1 - .../PermissionsEditorContent.jsx | 1 - .../widgets/SettingCommaDelimitedInput.jsx | 10 +-- .../widgets/SettingInput.styled.tsx} | 5 +- .../components/widgets/SettingInput.tsx | 10 +-- .../metabase/components/InputBlurChange.jsx | 74 ------------------- .../components/InputBlurChange.styled.tsx | 20 ----- .../metabase/components/InputBlurChange.tsx | 58 +++++++++++++++ .../components/InputWithSelectPrefix.jsx | 7 +- .../InputWithSelectPrefix.styled.tsx | 12 +++ .../components/ListField/ListField.tsx | 1 - .../metabase/components/ListSearchField.jsx | 4 +- .../src/metabase/components/NumericInput.jsx | 5 +- .../components/NumericInput.styled.tsx | 7 ++ .../SingleSelectListField.tsx | 1 - .../core/components/ColorInput/ColorInput.tsx | 4 +- .../core/components/FormInput/FormInput.tsx | 1 + .../core/components/Input/Input.styled.tsx | 20 +++-- .../metabase/core/components/Input/Input.tsx | 2 +- .../metabase/core/components/Input/types.ts | 1 - .../SelectButton/SelectButton.styled.tsx | 2 +- frontend/src/metabase/core/style/input.ts | 39 ++++++++-- frontend/src/metabase/core/style/types.ts | 1 + .../LinkOptions/CustomLinkText.tsx | 2 +- .../LinkOptions/CustomURLPicker.tsx | 2 +- .../components/ParameterSidebar.jsx | 2 +- .../BulkFilterSelect/BulkFilterSelect.tsx | 1 + .../DatePicker/HoursMinutesInput.styled.tsx | 10 +-- .../pickers/DatePicker/HoursMinutesInput.tsx | 29 ++++---- .../pickers/DatePicker/RelativeDatePicker.tsx | 5 +- .../DatePicker/SpecificDatePicker.styled.tsx | 15 +--- .../pickers/DatePicker/SpecificDatePicker.tsx | 9 +-- .../LegacyDatePicker/HoursMinutesInput.jsx | 1 - .../LegacyDatePicker/RelativeDatePicker.jsx | 2 +- .../template_tags/TagEditorParam.jsx | 4 +- .../template_tags/TagEditorParam.styled.tsx | 11 --- .../DimensionList/DimensionList.jsx | 1 - .../reference/components/ReferenceHeader.css | 3 +- .../ChartNestedSettingSeries.styled.tsx | 5 ++ .../ChartNestedSettingSeriesMultiple.jsx | 11 +-- .../ChartNestedSettingSeriesSingle.tsx | 9 ++- .../settings/ChartSettingGaugeSegments.jsx | 4 +- .../components/settings/ChartSettingInput.tsx | 4 +- .../settings/ChartSettingInputGroup.jsx | 2 +- .../ChartSettingInputNumeric.styled.tsx | 8 +- .../settings/ChartSettingsTableFormatting.jsx | 6 +- .../admin/datamodel/metadata.cy.spec.js | 4 +- .../18384-field-settings-breaks-ui.cy.spec.js | 2 +- .../visualizations/line_chart.cy.spec.js | 2 +- ...52-xhr-on-every-char-for-rename.cy.spec.js | 3 +- 59 files changed, 245 insertions(+), 281 deletions(-) rename frontend/src/metabase/{visualizations/components/settings/ChartSettingInput.styled.tsx => admin/settings/components/widgets/SettingInput.styled.tsx} (51%) delete mode 100644 frontend/src/metabase/components/InputBlurChange.jsx delete mode 100644 frontend/src/metabase/components/InputBlurChange.styled.tsx create mode 100644 frontend/src/metabase/components/InputBlurChange.tsx create mode 100644 frontend/src/metabase/components/InputWithSelectPrefix.styled.tsx create mode 100644 frontend/src/metabase/components/NumericInput.styled.tsx delete mode 100644 frontend/src/metabase/core/components/Input/types.ts create mode 100644 frontend/src/metabase/core/style/types.ts diff --git a/enterprise/frontend/src/metabase-enterprise/whitelabel/components/FontFilesWidget/FontFilesWidget.tsx b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/FontFilesWidget/FontFilesWidget.tsx index 7c32be103eb..3f935dacc1a 100644 --- a/enterprise/frontend/src/metabase-enterprise/whitelabel/components/FontFilesWidget/FontFilesWidget.tsx +++ b/enterprise/frontend/src/metabase-enterprise/whitelabel/components/FontFilesWidget/FontFilesWidget.tsx @@ -95,7 +95,6 @@ const FontFileRow = ({ <TableBodyCell> <Input defaultValue={url} - size="small" placeholder="https://some.trusted.location/font-file.woff2" fullWidth onBlur={handleBlur} diff --git a/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx b/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx index 9b1ecddee5b..363a6026000 100644 --- a/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx +++ b/frontend/src/metabase/admin/datamodel/components/FieldRemapping.jsx @@ -6,7 +6,6 @@ import _ from "underscore"; import Select from "metabase/core/components/Select"; import PopoverWithTrigger from "metabase/components/PopoverWithTrigger"; -import InputBlurChange from "metabase/components/InputBlurChange"; import ButtonWithStatus from "metabase/components/ButtonWithStatus"; import * as MetabaseAnalytics from "metabase/lib/analytics"; @@ -22,6 +21,7 @@ import { FieldMappingRoot, FieldSelectButton, ForeignKeyList, + FieldValueMappingInput, } from "./FieldRemapping.styled"; const MAP_OPTIONS = { @@ -442,8 +442,8 @@ export class FieldValueMapping extends React.Component { return ( <div className="flex align-center"> <h3>{original}</h3> - <InputBlurChange - className="AdminInput input ml-auto" + <FieldValueMappingInput + className="ml-auto" value={mapped} onChange={this.onInputChange} placeholder={t`Enter value`} diff --git a/frontend/src/metabase/admin/datamodel/components/FieldRemapping.styled.tsx b/frontend/src/metabase/admin/datamodel/components/FieldRemapping.styled.tsx index 71853163aeb..2b4f5dacbe8 100644 --- a/frontend/src/metabase/admin/datamodel/components/FieldRemapping.styled.tsx +++ b/frontend/src/metabase/admin/datamodel/components/FieldRemapping.styled.tsx @@ -2,6 +2,7 @@ import styled from "@emotion/styled"; import { alpha, color } from "metabase/lib/colors"; import SelectButton from "metabase/core/components/SelectButton"; import FieldList from "metabase/query_builder/components/FieldList"; +import InputBlurChange from "metabase/components/InputBlurChange"; export const FieldMappingRoot = styled.div` padding: 1rem 4rem; @@ -26,3 +27,7 @@ export const FieldSelectButton = styled(SelectButton)<FieldSelectButtonProps>` export const ForeignKeyList = styled(FieldList)` color: ${color("filter")}; `; + +export const FieldValueMappingInput = styled(InputBlurChange)` + width: auto; +`; diff --git a/frontend/src/metabase/admin/datamodel/components/database/ColumnItem/ColumnItem.jsx b/frontend/src/metabase/admin/datamodel/components/database/ColumnItem/ColumnItem.jsx index 579f30f38b8..0c0dedb3f04 100644 --- a/frontend/src/metabase/admin/datamodel/components/database/ColumnItem/ColumnItem.jsx +++ b/frontend/src/metabase/admin/datamodel/components/database/ColumnItem/ColumnItem.jsx @@ -59,7 +59,7 @@ class Column extends Component { <ColumnItemInput variant="primary" style={{ minWidth: 420 }} - className="AdminInput TableEditor-field-name float-left inline-block rounded text-bold" + className="float-left inline-block" type="text" value={this.props.field.display_name || ""} onBlurChange={this.handleChangeName} @@ -93,11 +93,12 @@ class Column extends Component { <div className="MetadataTable-title flex flex-column flex-full mt1 mr1"> <ColumnItemInput variant="secondary" - className="AdminInput TableEditor-field-description rounded" + className="TableEditor-field-description rounded" type="text" value={this.props.field.description || ""} onBlurChange={this.handleChangeDescription} placeholder={t`No column description yet`} + fullWidth /> </div> </div> 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 b7a3d1af201..1583d8650d9 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 @@ -1,14 +1,10 @@ import styled from "@emotion/styled"; import InputBlurChange from "metabase/components/InputBlurChange"; -import { color } from "metabase/lib/colors"; interface ColumnItemInputProps { variant: "primary" | "secondary"; } export const ColumnItemInput = styled(InputBlurChange)<ColumnItemInputProps>` - border-color: ${color("border")}; - - background-color: ${props => - color(props.variant === "primary" ? "white" : "bg-light")}; + width: auto; `; diff --git a/frontend/src/metabase/admin/datamodel/components/database/MetadataTable/MetadataTable.styled.tsx b/frontend/src/metabase/admin/datamodel/components/database/MetadataTable/MetadataTable.styled.tsx index c1c7e565245..633e28a3042 100644 --- a/frontend/src/metabase/admin/datamodel/components/database/MetadataTable/MetadataTable.styled.tsx +++ b/frontend/src/metabase/admin/datamodel/components/database/MetadataTable/MetadataTable.styled.tsx @@ -3,6 +3,7 @@ import { css } from "@emotion/react"; import { color } from "metabase/lib/colors"; import InputBlurChange from "metabase/components/InputBlurChange"; import { focusOutlineStyle } from "metabase/core/style/input"; +import Input from "metabase/core/components/Input"; interface VisibilityTypeProps { isSelected: boolean; @@ -20,36 +21,26 @@ export const VisibilityType = styled.span<VisibilityTypeProps>` } `; -const headerInputsStyles = css` - background-color: ${color("bg-light")}; - padding: 0.75rem 1.5rem; - z-index: 1; - outline: none; - border-color: ${color("border")}; - - &:hover, - &:focus { - z-index: 2; - } - - ${focusOutlineStyle("brand")}; -`; - export const TableNameInput = styled(InputBlurChange)` - ${headerInputsStyles} - font-weight: 700; - font-size: 20px; - color: ${color("text-dark")}; - border-radius: 8px 8px 0 0; + ${Input.Field} { + font-size: 20px; + color: ${color("text-dark")}; + border-radius: 8px 8px 0 0; + background-color: ${color("bg-light")}; + padding: 0.75rem 1.5rem; + } `; export const TableDescriptionInput = styled(InputBlurChange)` - ${headerInputsStyles} - color: ${color("text-dark")}; - margin-top: -1px; - border-radius: 0 0 8px 8px; - font-weight: 400; - font-size: 14px; + ${Input.Field} { + color: ${color("text-dark")}; + margin-top: -1px; + border-radius: 0 0 8px 8px; + font-weight: 400; + font-size: 14px; + background-color: ${color("bg-light")}; + padding: 0.75rem 1.5rem; + } `; export const TableName = styled.div` diff --git a/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx b/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx index 5fed7083fe5..f10f40bbd83 100644 --- a/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx +++ b/frontend/src/metabase/admin/datamodel/containers/FieldApp.jsx @@ -433,17 +433,18 @@ export class FieldHeader extends React.Component { <div> <FieldNameInput name="display_name" - className="h2 AdminInput" + className="h2 " value={this.props.field.display_name} onBlurChange={this.onNameChange} placeholder={this.props.field.name} /> <InputBlurChange name="description" - className="text AdminInput bordered input text-measure block full" + className="text-measure" value={this.props.field.description} onBlurChange={this.onDescriptionChange} placeholder={t`No description for this field yet`} + fullWidth /> </div> ); diff --git a/frontend/src/metabase/admin/datamodel/containers/FieldApp.styled.tsx b/frontend/src/metabase/admin/datamodel/containers/FieldApp.styled.tsx index 0302b28fce5..4fab7fef7d9 100644 --- a/frontend/src/metabase/admin/datamodel/containers/FieldApp.styled.tsx +++ b/frontend/src/metabase/admin/datamodel/containers/FieldApp.styled.tsx @@ -1,13 +1,11 @@ import styled from "@emotion/styled"; import { Link } from "react-router"; import InputBlurChange from "metabase/components/InputBlurChange"; -import { alpha, color } from "metabase/lib/colors"; +import { color } from "metabase/lib/colors"; export const FieldNameInput = styled(InputBlurChange)` - display: block; + width: auto; margin-bottom: 0.5rem; - border: 1px solid ${alpha("accent2", 0.2)}; - border-radius: 0.5rem; `; export const BackButtonLink = styled(Link)` diff --git a/frontend/src/metabase/admin/people/components/GroupsListing.jsx b/frontend/src/metabase/admin/people/components/GroupsListing.jsx index 78d37214ba2..1a100cbd515 100644 --- a/frontend/src/metabase/admin/people/components/GroupsListing.jsx +++ b/frontend/src/metabase/admin/people/components/GroupsListing.jsx @@ -16,7 +16,7 @@ import { import { KEYCODE_ENTER } from "metabase/lib/keyboard"; import Icon from "metabase/components/Icon"; -import InputBlurChange from "metabase/components/InputBlurChange"; +import Input from "metabase/core/components/Input"; import ModalContent from "metabase/components/ModalContent"; import Alert from "metabase/components/Alert"; import ModalWithTrigger from "metabase/components/ModalWithTrigger"; @@ -113,8 +113,8 @@ function EditingGroupRow({ return ( <tr className="bordered border-brand rounded"> <td> - <InputBlurChange - className="AdminInput h3" + <Input + className="h3" type="text" autoFocus={true} value={group.name} diff --git a/frontend/src/metabase/admin/permissions/components/FilterableTree/FilterableTree.tsx b/frontend/src/metabase/admin/permissions/components/FilterableTree/FilterableTree.tsx index 5cbc0b37ec1..5289567e147 100644 --- a/frontend/src/metabase/admin/permissions/components/FilterableTree/FilterableTree.tsx +++ b/frontend/src/metabase/admin/permissions/components/FilterableTree/FilterableTree.tsx @@ -57,7 +57,6 @@ export const FilterableTree = ({ fullWidth placeholder={placeholder} value={filter} - size="small" leftIcon="search" colorScheme="filter" onChange={handleFilterChange} diff --git a/frontend/src/metabase/admin/permissions/components/PermissionsEditor/PermissionsEditorContent.jsx b/frontend/src/metabase/admin/permissions/components/PermissionsEditor/PermissionsEditorContent.jsx index 0405491df66..21817dc89ef 100644 --- a/frontend/src/metabase/admin/permissions/components/PermissionsEditor/PermissionsEditorContent.jsx +++ b/frontend/src/metabase/admin/permissions/components/PermissionsEditor/PermissionsEditorContent.jsx @@ -81,7 +81,6 @@ export function PermissionsEditorContent({ onChange={handleFilterChange} onResetClick={() => setFilter("")} value={filter} - size="small" leftIcon="search" /> </EditorFilterContainer> diff --git a/frontend/src/metabase/admin/settings/components/widgets/SettingCommaDelimitedInput.jsx b/frontend/src/metabase/admin/settings/components/widgets/SettingCommaDelimitedInput.jsx index 39ccf4defd8..f43829d579b 100644 --- a/frontend/src/metabase/admin/settings/components/widgets/SettingCommaDelimitedInput.jsx +++ b/frontend/src/metabase/admin/settings/components/widgets/SettingCommaDelimitedInput.jsx @@ -1,7 +1,6 @@ /* eslint-disable react/prop-types */ import React from "react"; -import cx from "classnames"; -import InputBlurChange from "metabase/components/InputBlurChange"; +import { SettingInputBlurChange } from "./SettingInput.styled"; const maybeSingletonList = value => (value ? [value] : null); @@ -16,11 +15,8 @@ const SettingCommaDelimitedInput = ({ type = "text", }) => { return ( - <InputBlurChange - className={cx("Form-input", { - SettingsInput: true, - "border-error bg-error-input": errorMessage, - })} + <SettingInputBlurChange + error={!!errorMessage} id={id} type={type} // TOOD: change this to support multiple email addresses diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingInput.styled.tsx b/frontend/src/metabase/admin/settings/components/widgets/SettingInput.styled.tsx similarity index 51% rename from frontend/src/metabase/visualizations/components/settings/ChartSettingInput.styled.tsx rename to frontend/src/metabase/admin/settings/components/widgets/SettingInput.styled.tsx index 4f700b5a45e..e77cdcdd027 100644 --- a/frontend/src/metabase/visualizations/components/settings/ChartSettingInput.styled.tsx +++ b/frontend/src/metabase/admin/settings/components/widgets/SettingInput.styled.tsx @@ -1,7 +1,6 @@ import styled from "@emotion/styled"; import InputBlurChange from "metabase/components/InputBlurChange"; -export const ChartSettingInputBlurChange = styled(InputBlurChange)` - display: block; - width: 100%; +export const SettingInputBlurChange = styled(InputBlurChange)` + width: 400px; `; diff --git a/frontend/src/metabase/admin/settings/components/widgets/SettingInput.tsx b/frontend/src/metabase/admin/settings/components/widgets/SettingInput.tsx index 43d34769a49..8f632b33c0a 100644 --- a/frontend/src/metabase/admin/settings/components/widgets/SettingInput.tsx +++ b/frontend/src/metabase/admin/settings/components/widgets/SettingInput.tsx @@ -1,7 +1,6 @@ import React from "react"; - import cx from "classnames"; -import InputBlurChange from "metabase/components/InputBlurChange"; +import { SettingInputBlurChange } from "./SettingInput.styled"; const getValue = (value: string, type: string) => { if (type === "number") { @@ -42,12 +41,13 @@ const SettingInput = ({ }; return ( - <InputBlurChange - className={cx("Form-input", { + <SettingInputBlurChange + className={cx({ SettingsInput: type !== "password", SettingsPassword: type === "password", - "border-error bg-error-input": errorMessage, })} + size="large" + error={!!errorMessage} id={id} type={type} value={setting.value || ""} diff --git a/frontend/src/metabase/components/InputBlurChange.jsx b/frontend/src/metabase/components/InputBlurChange.jsx deleted file mode 100644 index e97dc1ee5aa..00000000000 --- a/frontend/src/metabase/components/InputBlurChange.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { Component } from "react"; -import PropTypes from "prop-types"; -import _ from "underscore"; -import { Input } from "./InputBlurChange.styled"; - -/** - * A small wrapper around <input>, primarily should be used for the - * `onBlurChange` feature, otherwise you should use <input> directly - */ -export default class InputBlurChange extends Component { - constructor(props, context) { - super(props, context); - this.onBlur = this.onBlur.bind(this); - this.onChange = this.onChange.bind(this); - this.state = { value: props.value }; - } - - static propTypes = { - type: PropTypes.string, - value: PropTypes.string, - defaultValue: PropTypes.string, - className: PropTypes.string, - name: PropTypes.string, - placeholder: PropTypes.string, - autoFocus: PropTypes.bool, - onFocus: PropTypes.func, - onChange: PropTypes.func, - onBlurChange: PropTypes.func, - }; - - static defaultProps = { - type: "text", - }; - - UNSAFE_componentWillReceiveProps(newProps) { - if (newProps.value !== this.state.value) { - this.setState({ value: newProps.value }); - } - } - - onChange(event) { - this.setState({ value: event.target.value }); - if (this.props.onChange) { - this.props.onChange(event); - } - } - - onBlur(event) { - if ( - this.props.onBlurChange && - (this.props.value || "") !== event.target.value - ) { - this.props.onBlurChange(event); - } - } - - render() { - const props = _.omit( - this.props, - "onBlurChange", - "value", - "onBlur", - "onChange", - ); - return ( - <Input - {...props} - value={this.state.value} - onBlur={this.onBlur} - onChange={this.onChange} - /> - ); - } -} diff --git a/frontend/src/metabase/components/InputBlurChange.styled.tsx b/frontend/src/metabase/components/InputBlurChange.styled.tsx deleted file mode 100644 index 94f498a8b34..00000000000 --- a/frontend/src/metabase/components/InputBlurChange.styled.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import styled from "@emotion/styled"; -import { - focusOutlineStyle, - inputPadding, - inputTypography, - numericInputReset, -} from "metabase/core/style/input"; -import { color } from "metabase/lib/colors"; - -export const Input = styled.input` - ${inputPadding}; - ${inputTypography}; - ${focusOutlineStyle("brand")}; - ${numericInputReset}; - - border: 1px solid ${() => color("border")}; - border-radius: 0.5rem; - color: ${() => color("text-dark")}; - transition: border 0.3s; -`; diff --git a/frontend/src/metabase/components/InputBlurChange.tsx b/frontend/src/metabase/components/InputBlurChange.tsx new file mode 100644 index 00000000000..f2ef03a65d8 --- /dev/null +++ b/frontend/src/metabase/components/InputBlurChange.tsx @@ -0,0 +1,58 @@ +import React, { useState, useLayoutEffect, useCallback } from "react"; +import _ from "underscore"; +import Input, { InputProps } from "metabase/core/components/Input"; + +/** + * A small wrapper around <input>, primarily should be used for the + * `onBlurChange` feature, otherwise you should use <input> directly + */ + +interface InputBlurChangeProps extends Omit<InputProps, "onBlur"> { + onBlurChange?: (event: React.ChangeEvent<HTMLInputElement>) => void; +} + +const InputBlurChange = ({ + value, + onChange, + onBlurChange, + ...props +}: InputBlurChangeProps) => { + const [internalValue, setInternalValue] = useState(value); + + useLayoutEffect(() => { + setInternalValue(value); + }, [value]); + + const handleChange = useCallback( + (event: React.ChangeEvent<HTMLInputElement>) => { + setInternalValue(event.target.value); + if (onChange) { + onChange(event); + } + }, + [onChange], + ); + + const handleBlur = useCallback( + (event: React.ChangeEvent<HTMLInputElement>) => { + if (onBlurChange && (value || "") !== event.target.value) { + onBlurChange(event); + } + }, + [value, onBlurChange], + ); + + const inputProps = _.omit(props, "onBlur", "onBlurChange", "onChange"); + + return ( + <Input + {...inputProps} + value={internalValue} + onBlur={handleBlur} + onChange={handleChange} + fullWidth + /> + ); +}; + +export default InputBlurChange; diff --git a/frontend/src/metabase/components/InputWithSelectPrefix.jsx b/frontend/src/metabase/components/InputWithSelectPrefix.jsx index 9dea10f8ab3..d236e0c10b2 100644 --- a/frontend/src/metabase/components/InputWithSelectPrefix.jsx +++ b/frontend/src/metabase/components/InputWithSelectPrefix.jsx @@ -2,7 +2,7 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import Select, { Option } from "metabase/core/components/Select"; -import InputBlurChange from "./InputBlurChange"; +import { SelectPrefixInput } from "./InputWithSelectPrefix.styled"; function splitValue({ value, @@ -77,12 +77,13 @@ export default class InputWithSelectPrefix extends Component { </Option> ))} </Select> - <InputBlurChange + <SelectPrefixInput type="text" - className="Form-input flex-full borderless" + className="flex-full" value={rest} placeholder={this.props.placeholder} onBlurChange={e => this.setState({ rest: e.target.value })} + size="large" /> </div> ); diff --git a/frontend/src/metabase/components/InputWithSelectPrefix.styled.tsx b/frontend/src/metabase/components/InputWithSelectPrefix.styled.tsx new file mode 100644 index 00000000000..368b0b21e7a --- /dev/null +++ b/frontend/src/metabase/components/InputWithSelectPrefix.styled.tsx @@ -0,0 +1,12 @@ +import styled from "@emotion/styled"; +import Input from "metabase/core/components/Input"; +import InputBlurChange from "./InputBlurChange"; + +export const SelectPrefixInput = styled(InputBlurChange)` + width: auto; + + ${Input.Field} { + border: none; + outline: none; + } +`; diff --git a/frontend/src/metabase/components/ListField/ListField.tsx b/frontend/src/metabase/components/ListField/ListField.tsx index f9863d9d07a..884c2f887b6 100644 --- a/frontend/src/metabase/components/ListField/ListField.tsx +++ b/frontend/src/metabase/components/ListField/ListField.tsx @@ -114,7 +114,6 @@ const ListField = ({ <Input fullWidth autoFocus - size="small" placeholder={placeholder} value={filter} onChange={handleFilterChange} diff --git a/frontend/src/metabase/components/ListSearchField.jsx b/frontend/src/metabase/components/ListSearchField.jsx index e0e905693d5..8457177bd45 100644 --- a/frontend/src/metabase/components/ListSearchField.jsx +++ b/frontend/src/metabase/components/ListSearchField.jsx @@ -18,9 +18,7 @@ export default function ListSearchField({ autoFocus, ...props }) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - return ( - <Input autoFocus ref={inputRef} {...props} size="small" leftIcon="search" /> - ); + return <Input autoFocus ref={inputRef} {...props} leftIcon="search" />; } ListSearchField.propTypes = Input.propTypes; diff --git a/frontend/src/metabase/components/NumericInput.jsx b/frontend/src/metabase/components/NumericInput.jsx index 68cb2d9074e..5106c65051a 100644 --- a/frontend/src/metabase/components/NumericInput.jsx +++ b/frontend/src/metabase/components/NumericInput.jsx @@ -1,10 +1,9 @@ /* eslint-disable react/prop-types */ import React from "react"; - -import InputBlurChange from "metabase/components/InputBlurChange"; +import { NumericInputBlurChange } from "./NumericInput.styled"; const NumericInput = ({ value, onChange, ...props }) => ( - <InputBlurChange + <NumericInputBlurChange value={value == null ? "" : String(value)} onBlurChange={({ target: { value } }) => { value = value ? parseFloat(value) : null; diff --git a/frontend/src/metabase/components/NumericInput.styled.tsx b/frontend/src/metabase/components/NumericInput.styled.tsx new file mode 100644 index 00000000000..2a9b2124cb6 --- /dev/null +++ b/frontend/src/metabase/components/NumericInput.styled.tsx @@ -0,0 +1,7 @@ +import styled from "@emotion/styled"; + +import InputBlurChange from "./InputBlurChange"; + +export const NumericInputBlurChange = styled(InputBlurChange)` + width: auto; +`; diff --git a/frontend/src/metabase/components/SingleSelectListField/SingleSelectListField.tsx b/frontend/src/metabase/components/SingleSelectListField/SingleSelectListField.tsx index 17af1027995..44a849244fa 100644 --- a/frontend/src/metabase/components/SingleSelectListField/SingleSelectListField.tsx +++ b/frontend/src/metabase/components/SingleSelectListField/SingleSelectListField.tsx @@ -112,7 +112,6 @@ const SingleSelectListField = ({ <Input fullWidth autoFocus - size="small" placeholder={placeholder} value={filter} onChange={handleFilterChange} diff --git a/frontend/src/metabase/core/components/ColorInput/ColorInput.tsx b/frontend/src/metabase/core/components/ColorInput/ColorInput.tsx index ec4b5c7bd08..f5a8814b051 100644 --- a/frontend/src/metabase/core/components/ColorInput/ColorInput.tsx +++ b/frontend/src/metabase/core/components/ColorInput/ColorInput.tsx @@ -8,6 +8,7 @@ import React, { useMemo, useState, } from "react"; +import _ from "underscore"; import Color from "color"; import Input from "metabase/core/components/Input"; @@ -58,10 +59,9 @@ const ColorInput = forwardRef(function ColorInput( return ( <Input - {...props} + {..._.omit(props, "size")} ref={ref} value={isFocused ? inputText : colorText} - size="small" onFocus={handleFocus} onBlur={handleBlur} onChange={handleChange} diff --git a/frontend/src/metabase/core/components/FormInput/FormInput.tsx b/frontend/src/metabase/core/components/FormInput/FormInput.tsx index 9a4d57e6ee0..649a067775e 100644 --- a/frontend/src/metabase/core/components/FormInput/FormInput.tsx +++ b/frontend/src/metabase/core/components/FormInput/FormInput.tsx @@ -54,6 +54,7 @@ const FormInput = forwardRef(function FormInput( error={touched ? error : undefined} > <Input + size="large" {...props} id={id} name={name} diff --git a/frontend/src/metabase/core/components/Input/Input.styled.tsx b/frontend/src/metabase/core/components/Input/Input.styled.tsx index 4647b5d6552..df9ce419027 100644 --- a/frontend/src/metabase/core/components/Input/Input.styled.tsx +++ b/frontend/src/metabase/core/components/Input/Input.styled.tsx @@ -3,8 +3,12 @@ import { css } from "@emotion/react"; import { color } from "metabase/lib/colors"; import { monospaceFontFamily, space } from "metabase/styled-components/theme"; import IconButtonWrapper from "metabase/components/IconButtonWrapper"; -import { focusOutlineStyle } from "metabase/core/style/input"; -import { InputSize } from "./types"; +import { + focusOutlineStyle, + inputPadding, + inputTypography, +} from "metabase/core/style/input"; +import { InputSize } from "../../style/types"; export interface InputProps { fieldSize?: InputSize; @@ -47,11 +51,10 @@ export const InputRoot = styled.div<InputRootProps>` `; export const InputField = styled.input<InputProps>` + ${props => inputPadding(props.fieldSize)} + ${props => inputTypography(props.fieldSize)} font-family: inherit; - font-weight: 700; - font-size: 1rem; color: ${color("text-dark")}; - padding: 0.75rem; border: 1px solid ${color("border")}; border-radius: ${space(1)}; background-color: ${props => color(props.readOnly ? "bg-light" : "bg-white")}; @@ -104,12 +107,13 @@ export const InputField = styled.input<InputProps>` type InputButtonProps = { size: InputSize; }; + export const InputButton = styled(IconButtonWrapper)<InputButtonProps>` position: absolute; color: ${color("text-light")}; - padding: 0.75rem; + padding: ${props => (props.size === "small" ? "0.5rem" : "0.75rem")}; border-radius: 50%; - bottom: ${props => (props.size === "medium" ? "0.125rem" : 0)}; + bottom: ${props => (props.size === "large" ? "0.125rem" : 0)}; &:disabled { cursor: default; @@ -129,7 +133,7 @@ type InputResetButtonProps = { }; export const InputResetButton = styled(InputButton)<InputResetButtonProps>` - right: ${props => (props.hasRightIcon ? "0.75rem" : 0)}; + right: ${props => (props.hasRightIcon ? "1.25rem" : 0)}; `; export const InputSubtitle = styled.div` diff --git a/frontend/src/metabase/core/components/Input/Input.tsx b/frontend/src/metabase/core/components/Input/Input.tsx index d3f119f4e64..e7022a01371 100644 --- a/frontend/src/metabase/core/components/Input/Input.tsx +++ b/frontend/src/metabase/core/components/Input/Input.tsx @@ -8,6 +8,7 @@ import React, { import { t } from "ttag"; import Icon from "metabase/components/Icon"; import Tooltip from "metabase/components/Tooltip"; +import { InputSize } from "../../style/types"; import { InputField, InputLeftButton, @@ -16,7 +17,6 @@ import { InputSubtitle, InputResetButton, } from "./Input.styled"; -import { InputSize } from "./types"; export type InputColorScheme = "brand" | "filter"; diff --git a/frontend/src/metabase/core/components/Input/types.ts b/frontend/src/metabase/core/components/Input/types.ts deleted file mode 100644 index cb47f8b08c3..00000000000 --- a/frontend/src/metabase/core/components/Input/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type InputSize = "small" | "medium"; diff --git a/frontend/src/metabase/core/components/SelectButton/SelectButton.styled.tsx b/frontend/src/metabase/core/components/SelectButton/SelectButton.styled.tsx index 03c23d8ab60..bdd528e099e 100644 --- a/frontend/src/metabase/core/components/SelectButton/SelectButton.styled.tsx +++ b/frontend/src/metabase/core/components/SelectButton/SelectButton.styled.tsx @@ -18,7 +18,7 @@ const getColor = ({ hasValue, highlighted }: SelectButtonRootProps) => { }; export const SelectButtonRoot = styled.button<SelectButtonRootProps>` - ${inputPadding} + ${inputPadding()} cursor: pointer; display: flex; width: ${props => (props.fullWidth ? "100%" : "unset")}; diff --git a/frontend/src/metabase/core/style/input.ts b/frontend/src/metabase/core/style/input.ts index 6eb610ca9f7..13e2a17656e 100644 --- a/frontend/src/metabase/core/style/input.ts +++ b/frontend/src/metabase/core/style/input.ts @@ -1,14 +1,39 @@ import { css } from "@emotion/react"; import { getFocusColor } from "metabase/lib/colors"; +import { InputSize } from "./types"; -export const inputPadding = css` - padding: 0.625rem 0.75rem; -`; +const inputPaddingBySize = { + small: css` + padding: 0.5rem 0.625rem; + `, + medium: css` + padding: 0.625rem 0.75rem; + `, + large: css` + padding: 0.75rem; + `, +} as const; -export const inputTypography = css` - font-size: 0.875rem; - font-weight: 700; -`; +export const inputPadding = (size: InputSize = "medium") => + inputPaddingBySize[size]; + +const inputTypographyBySize = { + small: css` + font-size: 0.875rem; + font-weight: 700; + `, + medium: css` + font-size: 0.875rem; + font-weight: 700; + `, + large: css` + font-size: 1rem; + font-weight: 700; + `, +} as const; + +export const inputTypography = (size: InputSize = "medium") => + inputTypographyBySize[size]; export const numericInputReset = () => css` &::-webkit-outer-spin-button, diff --git a/frontend/src/metabase/core/style/types.ts b/frontend/src/metabase/core/style/types.ts new file mode 100644 index 00000000000..195fb40750c --- /dev/null +++ b/frontend/src/metabase/core/style/types.ts @@ -0,0 +1 @@ +export type InputSize = "small" | "medium" | "large"; diff --git a/frontend/src/metabase/dashboard/components/ClickBehaviorSidebar/LinkOptions/CustomLinkText.tsx b/frontend/src/metabase/dashboard/components/ClickBehaviorSidebar/LinkOptions/CustomLinkText.tsx index 5e64226bf77..74470eb9566 100644 --- a/frontend/src/metabase/dashboard/components/ClickBehaviorSidebar/LinkOptions/CustomLinkText.tsx +++ b/frontend/src/metabase/dashboard/components/ClickBehaviorSidebar/LinkOptions/CustomLinkText.tsx @@ -30,7 +30,7 @@ const CustomLinkText = ({ clickBehavior, updateSettings }: Props) => { <div className="mt2 mb1"> <Heading>{t`Customize link text (optional)`}</Heading> <InputBlurChange - className="input block full" + className="block full" placeholder={t`E.x. Details for {{Column Name}}`} value={clickBehavior.linkTextTemplate} onBlurChange={handleChange} diff --git a/frontend/src/metabase/dashboard/components/ClickBehaviorSidebar/LinkOptions/CustomURLPicker.tsx b/frontend/src/metabase/dashboard/components/ClickBehaviorSidebar/LinkOptions/CustomURLPicker.tsx index 116c8e090d9..7fc0fa30790 100644 --- a/frontend/src/metabase/dashboard/components/ClickBehaviorSidebar/LinkOptions/CustomURLPicker.tsx +++ b/frontend/src/metabase/dashboard/components/ClickBehaviorSidebar/LinkOptions/CustomURLPicker.tsx @@ -89,7 +89,7 @@ function CustomURLPicker({ value={clickBehavior.linkTemplate} placeholder={t`e.g. http://acme.com/id/\{\{user_id\}\}`} onChange={handleLinkTemplateChange} - className="input block full" + className="block full" /> {isTableDisplay(dashcard) && ( <CustomLinkText diff --git a/frontend/src/metabase/parameters/components/ParameterSidebar.jsx b/frontend/src/metabase/parameters/components/ParameterSidebar.jsx index 357e52319f0..ee0e05d68b5 100644 --- a/frontend/src/metabase/parameters/components/ParameterSidebar.jsx +++ b/frontend/src/metabase/parameters/components/ParameterSidebar.jsx @@ -80,7 +80,7 @@ class ParameterSidebar extends React.Component { <div className="py2"> <label className="mt2 mb1 block text-bold">{t`Label`}</label> <InputBlurChange - className="input block full" + className="block full" value={parameter.name} onBlurChange={e => setName(e.target.value)} /> diff --git a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterSelect/BulkFilterSelect.tsx b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterSelect/BulkFilterSelect.tsx index 8a0f78681d4..d5e0834d7bc 100644 --- a/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterSelect/BulkFilterSelect.tsx +++ b/frontend/src/metabase/query_builder/components/filters/modals/BulkFilterSelect/BulkFilterSelect.tsx @@ -80,6 +80,7 @@ export const BulkFilterSelect = ({ </SelectFilterButton> ) } + maxWidth={370} popoverContent={({ closePopover }) => ( <SelectFilterPopover query={query} diff --git a/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/HoursMinutesInput.styled.tsx b/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/HoursMinutesInput.styled.tsx index 9418e6a3ea8..41f25a986a7 100644 --- a/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/HoursMinutesInput.styled.tsx +++ b/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/HoursMinutesInput.styled.tsx @@ -6,12 +6,8 @@ export interface AmPmLabelProps { } export const AmPmLabel = styled.span<AmPmLabelProps>` - color: ${props => props.isSelected && color("filter")}; - font-weight: ${props => props.isSelected && 900}; + color: ${color("brand")}; + font-weight: 900; margin-right: 0.5rem; - cursor: ${props => !props.isSelected && "pointer"}; - - &:hover { - color: ${color("filter")}; - } + cursor: pointer; `; diff --git a/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/HoursMinutesInput.tsx b/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/HoursMinutesInput.tsx index eb11050dc33..2cf9d6f6e02 100644 --- a/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/HoursMinutesInput.tsx +++ b/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/HoursMinutesInput.tsx @@ -28,7 +28,6 @@ const HoursMinutesInput = ({ }: Props) => ( <div className="flex align-center"> <NumericInput - className="input" style={{ height: 36 }} size={2} maxLength={2} @@ -47,7 +46,6 @@ const HoursMinutesInput = ({ /> <span className="px1">:</span> <NumericInput - className="input" style={{ height: 36 }} size={2} maxLength={2} @@ -56,18 +54,21 @@ const HoursMinutesInput = ({ /> {!is24HourMode && ( <div className="flex align-center pl1"> - <AmPmLabel - isSelected={hours < 12} - onClick={hours >= 12 ? () => onChangeHours(hours - 12) : undefined} - > - {moment.localeData().meridiem(0, 0, false)} - </AmPmLabel> - <AmPmLabel - isSelected={hours >= 12} - onClick={hours < 12 ? () => onChangeHours(hours + 12) : undefined} - > - {moment.localeData().meridiem(12, 0, false)} - </AmPmLabel> + {hours < 12 ? ( + <AmPmLabel + isSelected={hours < 12} + onClick={() => onChangeHours(hours + 12)} + > + {moment.localeData().meridiem(0, 0, false)} + </AmPmLabel> + ) : ( + <AmPmLabel + isSelected={hours >= 12} + onClick={() => onChangeHours(hours - 12)} + > + {moment.localeData().meridiem(12, 0, false)} + </AmPmLabel> + )} </div> )} {onClear && ( diff --git a/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/RelativeDatePicker.tsx b/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/RelativeDatePicker.tsx index 5516569d629..c180dca972b 100644 --- a/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/RelativeDatePicker.tsx +++ b/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/RelativeDatePicker.tsx @@ -65,7 +65,6 @@ const SELECT_STYLE = { width: 65, fontSize: 14, fontWeight: 700, - padding: 8, }; const isSmallerUnit = (unit: string, unitToCompare: string) => { @@ -197,7 +196,7 @@ const RelativeDatePicker = (props: RelativeDatePickerProps) => { <GridText>{intervals < 0 ? t`Past` : t`Next`}</GridText> ) : null} <NumericInput - className="input text-right" + className="text-right" primaryColor={primaryColor} style={SELECT_STYLE} data-ui-tag="relative-date-input" @@ -237,7 +236,7 @@ const RelativeDatePicker = (props: RelativeDatePickerProps) => { <> <GridText>{t`Starting from`}</GridText> <NumericInput - className="input text-right" + className="text-right" primaryColor={primaryColor} style={SELECT_STYLE} data-ui-tag="relative-date-input" diff --git a/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/SpecificDatePicker.styled.tsx b/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/SpecificDatePicker.styled.tsx index 4a8322d25e1..ae78a15bd9e 100644 --- a/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/SpecificDatePicker.styled.tsx +++ b/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/SpecificDatePicker.styled.tsx @@ -1,10 +1,10 @@ import styled from "@emotion/styled"; import { color } from "metabase/lib/colors"; import Icon from "metabase/components/Icon"; -import InputBlurChange from "metabase/components/InputBlurChange"; export const CalendarIcon = styled(Icon)` margin-right: 0.5rem; + margin-left: 0.5rem; cursor: pointer; &:hover { @@ -12,16 +12,6 @@ export const CalendarIcon = styled(Icon)` } `; -export const DateInput = styled(InputBlurChange)` - font-size: 1rem; - font-weight: 700; - width: 100%; - padding: 0.5rem; - border: none; - outline: none; - background: none; -`; - interface DateInputContainerProps { isActive?: boolean; } @@ -31,9 +21,6 @@ export const DateInputContainer = styled.div<DateInputContainerProps>` align-items: center; width: 100%; margin-bottom: 1rem; - border: 1px solid - ${({ isActive }) => (isActive ? color("brand") : color("border"))}; - border-radius: 0.5rem; &:focus-within { border-color: ${color("brand")}; diff --git a/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/SpecificDatePicker.tsx b/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/SpecificDatePicker.tsx index 90d9d5571c9..036a285173f 100644 --- a/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/SpecificDatePicker.tsx +++ b/frontend/src/metabase/query_builder/components/filters/pickers/DatePicker/SpecificDatePicker.tsx @@ -4,6 +4,7 @@ import { t } from "ttag"; import moment, { Moment } from "moment-timezone"; import { getDateStyleFromSettings } from "metabase/lib/time"; import Calendar, { SelectAll } from "metabase/components/Calendar"; +import InputBlurChange from "metabase/components/InputBlurChange"; import ExpandingContent from "metabase/components/ExpandingContent"; import { getTimeComponent, @@ -11,11 +12,7 @@ import { } from "metabase-lib/queries/utils/query-time"; import HoursMinutesInput from "./HoursMinutesInput"; -import { - CalendarIcon, - DateInput, - DateInputContainer, -} from "./SpecificDatePicker.styled"; +import { CalendarIcon, DateInputContainer } from "./SpecificDatePicker.styled"; interface SpecificDatePickerProps { className?: string; @@ -64,7 +61,7 @@ const SpecificDatePicker = ({ return ( <div className={className} data-testid="specific-date-picker"> <DateInputContainer isActive={isActive}> - <DateInput + <InputBlurChange placeholder={moment().format(dateFormat)} value={date ? date.format(dateFormat) : ""} autoFocus={autoFocus} diff --git a/frontend/src/metabase/query_builder/components/filters/pickers/LegacyDatePicker/HoursMinutesInput.jsx b/frontend/src/metabase/query_builder/components/filters/pickers/LegacyDatePicker/HoursMinutesInput.jsx index 284450ff252..7b7083c31d8 100644 --- a/frontend/src/metabase/query_builder/components/filters/pickers/LegacyDatePicker/HoursMinutesInput.jsx +++ b/frontend/src/metabase/query_builder/components/filters/pickers/LegacyDatePicker/HoursMinutesInput.jsx @@ -19,7 +19,6 @@ const HoursMinutesInput = ({ <div className="flex align-center"> <NumericInput data-testid="hours-input" - className="input" style={{ height: 36 }} size={2} maxLength={2} diff --git a/frontend/src/metabase/query_builder/components/filters/pickers/LegacyDatePicker/RelativeDatePicker.jsx b/frontend/src/metabase/query_builder/components/filters/pickers/LegacyDatePicker/RelativeDatePicker.jsx index 33f5f71e917..75588cfb015 100644 --- a/frontend/src/metabase/query_builder/components/filters/pickers/LegacyDatePicker/RelativeDatePicker.jsx +++ b/frontend/src/metabase/query_builder/components/filters/pickers/LegacyDatePicker/RelativeDatePicker.jsx @@ -30,7 +30,7 @@ export default class RelativeDatePicker extends Component { return ( <div className={cx(className, "flex align-center")}> <IntervalInput - className="mr2 input text-right" + className="mr2 text-right" style={{ width: 65, // needed to match Select's AdminSelect classes :-/ diff --git a/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx b/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx index ed389538b94..46d6d4c2f02 100644 --- a/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx +++ b/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.jsx @@ -8,6 +8,7 @@ import { Link } from "react-router"; import Schemas from "metabase/entities/schemas"; import Toggle from "metabase/core/components/Toggle"; +import InputBlurChange from "metabase/components/InputBlurChange"; import Select, { Option } from "metabase/core/components/Select"; import { getParameterOptionsForField } from "metabase/parameters/utils/template-tag-options"; @@ -23,7 +24,6 @@ import { TagContainer, ContainerLabel, InputContainer, - WidgetLabelInput, DefaultParameterValueWidget, } from "./TagEditorParam.styled"; @@ -255,7 +255,7 @@ export class TagEditorParam extends Component { {t`Filter widget label`} {hasNoWidgetLabel && <ErrorSpan>{t`(required)`}</ErrorSpan>} </ContainerLabel> - <WidgetLabelInput + <InputBlurChange type="text" value={tag["display-name"]} onBlurChange={e => diff --git a/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.styled.tsx b/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.styled.tsx index 5fb33db7c5e..754155b7990 100644 --- a/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.styled.tsx +++ b/frontend/src/metabase/query_builder/components/template_tags/TagEditorParam.styled.tsx @@ -1,5 +1,4 @@ import styled from "@emotion/styled"; -import InputBlurChange from "metabase/components/InputBlurChange"; import { color } from "metabase/lib/colors"; import ParameterValueWidget from "metabase/parameters/components/ParameterValueWidget"; @@ -36,16 +35,6 @@ export const InputContainer = styled.div<InputContainerProps>` padding-bottom: ${props => (props.lessBottomPadding ? "1.5rem" : "2rem")}; `; -export const WidgetLabelInput = styled(InputBlurChange)` - font-weight: 700; - padding: 0.5rem; - border: 1px solid ${color("border-dark")}; - border-radius: 0.5rem; - width: 100%; - color: ${color("text-dark")}; - font-size: 0.875rem; -`; - export const DefaultParameterValueWidget = styled(ParameterValueWidget)` padding: 0.5rem; font-weight: 700; diff --git a/frontend/src/metabase/query_builder/components/view/sidebars/SummarizeSidebar/DimensionList/DimensionList.jsx b/frontend/src/metabase/query_builder/components/view/sidebars/SummarizeSidebar/DimensionList/DimensionList.jsx index 4e21cd7a8de..d966e469359 100644 --- a/frontend/src/metabase/query_builder/components/view/sidebars/SummarizeSidebar/DimensionList/DimensionList.jsx +++ b/frontend/src/metabase/query_builder/components/view/sidebars/SummarizeSidebar/DimensionList/DimensionList.jsx @@ -93,7 +93,6 @@ export const DimensionList = ({ fullWidth placeholder={t`Find...`} value={filter} - size="small" leftIcon="search" onResetClick={() => setFilter("")} onChange={handleFilterChange} diff --git a/frontend/src/metabase/reference/components/ReferenceHeader.css b/frontend/src/metabase/reference/components/ReferenceHeader.css index 609c4a78cb6..3f8e18b66ce 100644 --- a/frontend/src/metabase/reference/components/ReferenceHeader.css +++ b/frontend/src/metabase/reference/components/ReferenceHeader.css @@ -10,10 +10,9 @@ } :local(.headerTextInput) { - composes: input p1 pl2 pr2 from "style"; + composes: p1 from "style"; font-size: 18px; color: var(--title-color); - width: 100%; max-width: 550px; } diff --git a/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingSeries.styled.tsx b/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingSeries.styled.tsx index 232a160d3d7..2c6f036e9f6 100644 --- a/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingSeries.styled.tsx +++ b/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingSeries.styled.tsx @@ -1,6 +1,7 @@ import styled from "@emotion/styled"; import { color } from "metabase/lib/colors"; import Icon from "metabase/components/Icon"; +import InputBlurChange from "metabase/components/InputBlurChange"; export const OptionsIcon = styled(Icon)` color: ${color("text-medium")}; @@ -10,3 +11,7 @@ export const OptionsIcon = styled(Icon)` color: ${color("brand")}; } `; + +export const SeriesNameInput = styled(InputBlurChange)` + width: auto; +`; diff --git a/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingSeriesMultiple.jsx b/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingSeriesMultiple.jsx index 5aa754b700e..20afb4cba96 100644 --- a/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingSeriesMultiple.jsx +++ b/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingSeriesMultiple.jsx @@ -4,8 +4,10 @@ import React from "react"; import { getAccentColors } from "metabase/lib/colors/groups"; import ColorSelector from "metabase/core/components/ColorSelector"; import IconWrapper from "metabase/components/IconWrapper"; -import InputBlurChange from "metabase/components/InputBlurChange"; -import { OptionsIcon } from "./ChartNestedSettingSeries.styled"; +import { + OptionsIcon, + SeriesNameInput, +} from "./ChartNestedSettingSeries.styled"; // various props injected by chartSettingNestedSettings HOC export default class ChartNestedSettingSeriesMultiple extends React.Component { @@ -46,11 +48,10 @@ export default class ChartNestedSettingSeriesMultiple extends React.Component { onChangeObjectSettings(single, { color: value }) } /> - <InputBlurChange - className="input flex-full ml1 align-self-stretch" + <SeriesNameInput + className="flex-full ml1 align-self-stretch" // set vertical padding to 0 and use align-self-stretch to match siblings style={{ paddingTop: 0, paddingBottom: 0 }} - size={1} value={settings.title} onBlurChange={e => onChangeObjectSettings(single, { title: e.target.value }) diff --git a/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingSeriesSingle.tsx b/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingSeriesSingle.tsx index 6b941dc024b..a4d65971510 100644 --- a/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingSeriesSingle.tsx +++ b/frontend/src/metabase/visualizations/components/settings/ChartNestedSettingSeriesSingle.tsx @@ -3,10 +3,11 @@ import React from "react"; import { getAccentColors } from "metabase/lib/colors/groups"; import ColorSelector from "metabase/core/components/ColorSelector"; -import InputBlurChange from "metabase/components/InputBlurChange"; import { Series } from "metabase-types/types/Visualization"; import { VisualizationSettings } from "metabase-types/api/card"; +import { SeriesNameInput } from "./ChartNestedSettingSeries.styled"; + export interface ChartNestedSettingsSeriesSingleProps { object: Series; getObjectKey: (object: Series) => string; @@ -34,10 +35,10 @@ const ChartNestedSettingsSeriesSingle = ({ colors={getAccentColors()} onChange={value => onChangeObjectSettings(object, { color: value })} /> - <InputBlurChange - className="input flex-full ml1 align-self-stretch" - size={1} + <SeriesNameInput + className="flex-full ml1 align-self-stretch" value={computedSettings.title} + data-testid="series-name-input" onBlurChange={(e: React.ChangeEvent<HTMLInputElement>) => onChangeObjectSettings(object, { title: e.target.value }) } diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingGaugeSegments.jsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingGaugeSegments.jsx index 24ff29d12a1..9f17c11ff92 100644 --- a/frontend/src/metabase/visualizations/components/settings/ChartSettingGaugeSegments.jsx +++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingGaugeSegments.jsx @@ -44,7 +44,7 @@ const ChartSettingGaugeSegments = ({ value: segments, onChange }) => { <td> <NumericInput type="number" - className="input full" + className="full" value={segment.min} onChange={value => onChangeProperty(index, "min", value)} placeholder={t`Min`} @@ -53,7 +53,7 @@ const ChartSettingGaugeSegments = ({ value: segments, onChange }) => { <td> <NumericInput type="number" - className="input full" + className="full" value={segment.max} onChange={value => onChangeProperty(index, "max", value)} placeholder={t`Max`} diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingInput.tsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingInput.tsx index 510ab80c5d1..f2c6cf9c759 100644 --- a/frontend/src/metabase/visualizations/components/settings/ChartSettingInput.tsx +++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingInput.tsx @@ -1,6 +1,6 @@ import React from "react"; import _ from "underscore"; -import { ChartSettingInputBlurChange } from "./ChartSettingInput.styled"; +import InputBlurChange from "metabase/components/InputBlurChange"; interface ChartSettingInputProps { value: string; @@ -13,7 +13,7 @@ const ChartSettingInput = ({ onChange, ...props }: ChartSettingInputProps) => ( - <ChartSettingInputBlurChange + <InputBlurChange {..._.omit(props, "onChangeSettings")} data-testid={props.id} value={value} diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingInputGroup.jsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingInputGroup.jsx index b0c2b3d2200..79a39efbd16 100644 --- a/frontend/src/metabase/visualizations/components/settings/ChartSettingInputGroup.jsx +++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingInputGroup.jsx @@ -8,7 +8,7 @@ export default function ChartSettingInputGroup({ value: values, onChange }) { const inputs = values.map((str, i) => ( <InputBlurChange key={i} - className="input block full mb1" + className="block full mb1" value={str} onBlurChange={e => { const newStr = e.target.value.trim(); diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingInputNumeric.styled.tsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingInputNumeric.styled.tsx index ac6526474e6..f4cd90bbbb7 100644 --- a/frontend/src/metabase/visualizations/components/settings/ChartSettingInputNumeric.styled.tsx +++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingInputNumeric.styled.tsx @@ -1,18 +1,12 @@ import styled from "@emotion/styled"; import Input from "metabase/core/components/Input"; -import { - inputPadding, - inputTypography, - numericInputReset, -} from "metabase/core/style/input"; +import { numericInputReset } from "metabase/core/style/input"; export const ChartSettingNumericInput = styled(Input)` display: block; ${Input.Field} { width: 100%; - ${inputPadding}; - ${inputTypography}; ${numericInputReset}; } `; diff --git a/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx b/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx index 1972b54056c..6379a3702fa 100644 --- a/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx +++ b/frontend/src/metabase/visualizations/components/settings/ChartSettingsTableFormatting.jsx @@ -18,8 +18,10 @@ import Toggle from "metabase/core/components/Toggle"; import ColorRange from "metabase/core/components/ColorRange"; import ColorSelector from "metabase/core/components/ColorSelector"; import ColorRangeSelector from "metabase/core/components/ColorRangeSelector"; +import Input from "metabase/core/components/Input"; import NumericInput from "metabase/components/NumericInput"; + import { SortableContainer, SortableElement, @@ -82,7 +84,7 @@ const DEFAULTS_BY_TYPE = { // predicate for columns that can be formatted export const isFormattable = field => isNumeric(field) || isString(field); -const INPUT_CLASSNAME = "AdminSelect input mt1 full"; +const INPUT_CLASSNAME = "mt1 full"; const getValueForDescription = rule => ["is-null", "not-null"].includes(rule.operator) ? "" : ` ${rule.value}`; @@ -379,7 +381,7 @@ const RuleEditor = ({ placeholder="0" /> ) : hasOperand ? ( - <input + <Input data-testid="conditional-formatting-value-input" className={INPUT_CLASSNAME} value={rule.value} diff --git a/frontend/test/metabase/scenarios/admin/datamodel/metadata.cy.spec.js b/frontend/test/metabase/scenarios/admin/datamodel/metadata.cy.spec.js index 7e54761e78e..89b8c591704 100644 --- a/frontend/test/metabase/scenarios/admin/datamodel/metadata.cy.spec.js +++ b/frontend/test/metabase/scenarios/admin/datamodel/metadata.cy.spec.js @@ -21,7 +21,7 @@ describe("scenarios > admin > datamodel > metadata", () => { cy.visit(`/admin/datamodel/database/${SAMPLE_DB_ID}`); // edit "Product ID" column in "Orders" table cy.findByText("Orders").click(); - cy.findByDisplayValue("Product ID").parent().find(".Icon-gear").click(); + cy.findByTestId("column-PRODUCT_ID").find(".Icon-gear").click(); // remap its original value to use foreign key cy.findByText("Use original value").click(); @@ -52,7 +52,7 @@ describe("scenarios > admin > datamodel > metadata", () => { cy.visit(`/admin/datamodel/database/${SAMPLE_DB_ID}`); // edit "Rating" values in "Reviews" table cy.findByText("Reviews").click(); - cy.findByDisplayValue("Rating").parent().find(".Icon-gear").click(); + cy.findByTestId("column-RATING").find(".Icon-gear").click(); // apply custom remapping for "Rating" values 1-5 cy.findByText("Use original value").click(); diff --git a/frontend/test/metabase/scenarios/admin/datamodel/reproductions/18384-field-settings-breaks-ui.cy.spec.js b/frontend/test/metabase/scenarios/admin/datamodel/reproductions/18384-field-settings-breaks-ui.cy.spec.js index 15a8a3f4d0e..5573d0509af 100644 --- a/frontend/test/metabase/scenarios/admin/datamodel/reproductions/18384-field-settings-breaks-ui.cy.spec.js +++ b/frontend/test/metabase/scenarios/admin/datamodel/reproductions/18384-field-settings-breaks-ui.cy.spec.js @@ -19,7 +19,7 @@ describe("issue 18384", () => { it("should be able to open field properties even when one of the tables is hidden (metabase#18384)", () => { cy.visit(`/admin/datamodel/database/${SAMPLE_DB_ID}/table/${PEOPLE_ID}`); - cy.findByDisplayValue("Address").parent().find(".Icon-gear").click(); + cy.findByTestId("column-ADDRESS").find(".Icon-gear").click(); cy.location("pathname").should( "eq", diff --git a/frontend/test/metabase/scenarios/visualizations/line_chart.cy.spec.js b/frontend/test/metabase/scenarios/visualizations/line_chart.cy.spec.js index b5f671933f9..92b712404a4 100644 --- a/frontend/test/metabase/scenarios/visualizations/line_chart.cy.spec.js +++ b/frontend/test/metabase/scenarios/visualizations/line_chart.cy.spec.js @@ -167,7 +167,7 @@ describe("scenarios > visualizations > line chart", () => { // Now do the same for the input with no value openSeriesSettings("Unknown", true); popover().within(() => { - cy.get("input[type=text]").type("cat2").blur(); + cy.findAllByTestId("series-name-input").type("cat2").blur(); cy.findByDisplayValue("cat2"); }); cy.button("Done").click(); diff --git a/frontend/test/metabase/scenarios/visualizations/reproductions/21452-xhr-on-every-char-for-rename.cy.spec.js b/frontend/test/metabase/scenarios/visualizations/reproductions/21452-xhr-on-every-char-for-rename.cy.spec.js index 55ab2844c32..2b891c59c73 100644 --- a/frontend/test/metabase/scenarios/visualizations/reproductions/21452-xhr-on-every-char-for-rename.cy.spec.js +++ b/frontend/test/metabase/scenarios/visualizations/reproductions/21452-xhr-on-every-char-for-rename.cy.spec.js @@ -33,7 +33,8 @@ describe("issue 21452", () => { it("should not fire POST request after every character during display name change (metabase#21452)", () => { openSeriesSettings("Sum of Quantity"); - cy.findByDisplayValue("Sum of Quantity").clear().type("Foo").blur(); + cy.findByDisplayValue("Sum of Quantity").clear().type("Foo"); + cy.findByText("Display type").click(); // Blur will result in another POST request which is expected cy.wait("@dataset"); // Dismiss the popup and close settings -- GitLab