diff --git a/superset-frontend/src/components/FaveStar/index.tsx b/superset-frontend/src/components/FaveStar/index.tsx index ac5bb6065eab..595307585498 100644 --- a/superset-frontend/src/components/FaveStar/index.tsx +++ b/superset-frontend/src/components/FaveStar/index.tsx @@ -18,7 +18,7 @@ */ import React, { useCallback } from 'react'; -import { t, styled } from '@superset-ui/core'; +import { css, t, styled } from '@superset-ui/core'; import { Tooltip } from 'src/components/Tooltip'; import { useComponentDidMount } from 'src/hooks/useComponentDidMount'; import Icons from 'src/components/Icons'; @@ -32,9 +32,11 @@ interface FaveStarProps { } const StyledLink = styled.a` - font-size: ${({ theme }) => theme.typography.sizes.xl}px; - display: flex; - padding: 0 0 0 0.5em; + ${({ theme }) => css` + font-size: ${theme.typography.sizes.xl}px; + display: flex; + padding: 0 0 0 ${theme.gridUnit * 2}px; + `}; `; const FaveStar = ({ diff --git a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx index ebed661be9e4..c38c1b59ae4f 100644 --- a/superset-frontend/src/explore/components/DatasourcePanel/index.tsx +++ b/superset-frontend/src/explore/components/DatasourcePanel/index.tsx @@ -86,7 +86,7 @@ const DatasourceContainer = styled.div` color: ${theme.colors.grayscale.light1}; } .form-control.input-md { - width: calc(100% - ${theme.gridUnit * 4}px); + width: calc(100% - ${theme.gridUnit * 8}px); height: ${theme.gridUnit * 8}px; margin: ${theme.gridUnit * 2}px auto; } diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/ChartEditableTitle/ChartEditableTitle.test.tsx b/superset-frontend/src/explore/components/ExploreChartHeader/ChartEditableTitle/ChartEditableTitle.test.tsx new file mode 100644 index 000000000000..dd98518c8c41 --- /dev/null +++ b/superset-frontend/src/explore/components/ExploreChartHeader/ChartEditableTitle/ChartEditableTitle.test.tsx @@ -0,0 +1,68 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from 'spec/helpers/testing-library'; +import { ChartEditableTitle } from './index'; + +const createProps = (overrides: Record = {}) => ({ + title: 'Chart title', + placeholder: 'Add the name of the chart', + canEdit: true, + onSave: jest.fn(), + ...overrides, +}); + +describe('Chart editable title', () => { + it('renders chart title', () => { + const props = createProps(); + render(); + expect(screen.getByText('Chart title')).toBeVisible(); + }); + + it('renders placeholder', () => { + const props = createProps({ + title: '', + }); + render(); + expect(screen.getByText('Add the name of the chart')).toBeVisible(); + }); + + it('click, edit and save title', () => { + const props = createProps(); + render(); + const textboxElement = screen.getByRole('textbox'); + userEvent.click(textboxElement); + userEvent.type(textboxElement, ' edited'); + expect(screen.getByText('Chart title edited')).toBeVisible(); + userEvent.type(textboxElement, '{enter}'); + expect(props.onSave).toHaveBeenCalled(); + }); + + it('renders in non-editable mode', () => { + const props = createProps({ canEdit: false }); + render(); + const titleElement = screen.getByLabelText('Chart title'); + expect(screen.queryByRole('textbox')).not.toBeInTheDocument(); + expect(titleElement).toBeVisible(); + userEvent.click(titleElement); + userEvent.type(titleElement, ' edited{enter}'); + expect(props.onSave).not.toHaveBeenCalled(); + }); +}); diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/ChartEditableTitle/index.tsx b/superset-frontend/src/explore/components/ExploreChartHeader/ChartEditableTitle/index.tsx new file mode 100644 index 000000000000..0e2761b6a9de --- /dev/null +++ b/superset-frontend/src/explore/components/ExploreChartHeader/ChartEditableTitle/index.tsx @@ -0,0 +1,213 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { + ChangeEvent, + KeyboardEvent, + useCallback, + useEffect, + useLayoutEffect, + useRef, + useState, +} from 'react'; +import { css, styled, t } from '@superset-ui/core'; +import { Tooltip } from 'src/components/Tooltip'; +import { useResizeDetector } from 'react-resize-detector'; + +export type ChartEditableTitleProps = { + title: string; + placeholder: string; + onSave: (title: string) => void; + canEdit: boolean; +}; + +const Styles = styled.div` + ${({ theme }) => css` + display: flex; + font-size: ${theme.typography.sizes.xl}px; + font-weight: ${theme.typography.weights.bold}; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + & .chart-title, + & .chart-title-input { + display: inline-block; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + & .chart-title { + cursor: default; + } + & .chart-title-input { + border: none; + padding: 0; + outline: none; + + &::placeholder { + color: ${theme.colors.grayscale.light1}; + } + } + + & .input-sizer { + position: absolute; + left: -9999px; + display: inline-block; + } + `} +`; + +export const ChartEditableTitle = ({ + title, + placeholder, + onSave, + canEdit, +}: ChartEditableTitleProps) => { + const [isEditing, setIsEditing] = useState(false); + const [currentTitle, setCurrentTitle] = useState(title || ''); + const contentRef = useRef(null); + const [showTooltip, setShowTooltip] = useState(false); + + const { width: inputWidth, ref: sizerRef } = useResizeDetector(); + const { width: containerWidth, ref: containerRef } = useResizeDetector({ + refreshMode: 'debounce', + }); + + useEffect(() => { + if (isEditing && contentRef?.current) { + contentRef.current.focus(); + // move cursor and scroll to the end + if (contentRef.current.setSelectionRange) { + const { length } = contentRef.current.value; + contentRef.current.setSelectionRange(length, length); + contentRef.current.scrollLeft = contentRef.current.scrollWidth; + } + } + }, [isEditing]); + + // a trick to make the input grow when user types text + // we make additional span component, place it somewhere out of view and copy input + // then we can measure the width of that span to resize the input element + useLayoutEffect(() => { + if (sizerRef?.current) { + sizerRef.current.innerHTML = (currentTitle || placeholder).replace( + /\s/g, + ' ', + ); + } + }, [currentTitle, placeholder, sizerRef]); + + useEffect(() => { + if ( + contentRef.current && + contentRef.current.scrollWidth > contentRef.current.clientWidth + ) { + setShowTooltip(true); + } else { + setShowTooltip(false); + } + }, [inputWidth, containerWidth]); + + const handleClick = useCallback(() => { + if (!canEdit || isEditing) { + return; + } + setIsEditing(true); + }, [canEdit, isEditing]); + + const handleBlur = useCallback(() => { + if (!canEdit) { + return; + } + const formattedTitle = currentTitle.trim(); + setCurrentTitle(formattedTitle); + if (title !== formattedTitle) { + onSave(formattedTitle); + } + setIsEditing(false); + }, [canEdit, currentTitle, onSave, title]); + + const handleChange = useCallback( + (ev: ChangeEvent) => { + if (!canEdit || !isEditing) { + return; + } + setCurrentTitle(ev.target.value); + }, + [canEdit, isEditing], + ); + + const handleKeyPress = useCallback( + (ev: KeyboardEvent) => { + if (!canEdit) { + return; + } + if (ev.key === 'Enter') { + ev.preventDefault(); + contentRef.current?.blur(); + } + }, + [canEdit], + ); + + return ( + + + {canEdit ? ( + 0 && + css` + width: ${inputWidth}px; + `} + `} + /> + ) : ( + + {currentTitle} + + )} + + + + ); +}; diff --git a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx index 665a2512ef27..d9d615dc1f33 100644 --- a/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx +++ b/superset-frontend/src/explore/components/ExploreChartHeader/index.jsx @@ -22,6 +22,7 @@ import { bindActionCreators } from 'redux'; import PropTypes from 'prop-types'; import { CategoricalColorNamespace, + css, SupersetClient, styled, t, @@ -33,7 +34,6 @@ import { } from 'src/reports/actions/reports'; import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; import { chartPropShape } from 'src/dashboard/util/propShapes'; -import EditableTitle from 'src/components/EditableTitle'; import AlteredSliceTag from 'src/components/AlteredSliceTag'; import FaveStar from 'src/components/FaveStar'; import Timer from 'src/components/Timer'; @@ -44,6 +44,7 @@ import CertifiedBadge from 'src/components/CertifiedBadge'; import withToasts from 'src/components/MessageToasts/withToasts'; import RowCountLabel from '../RowCountLabel'; import ExploreAdditionalActionsMenu from '../ExploreAdditionalActionsMenu'; +import { ChartEditableTitle } from './ChartEditableTitle'; const CHART_STATUS_MAP = { failed: 'danger', @@ -53,8 +54,8 @@ const CHART_STATUS_MAP = { const propTypes = { actions: PropTypes.object.isRequired, - can_overwrite: PropTypes.bool.isRequired, - can_download: PropTypes.bool.isRequired, + canOverwrite: PropTypes.bool.isRequired, + canDownload: PropTypes.bool.isRequired, dashboardId: PropTypes.number, isStarred: PropTypes.bool.isRequired, slice: PropTypes.object, @@ -67,37 +68,41 @@ const propTypes = { }; const StyledHeader = styled.div` - display: flex; - flex-direction: row; - align-items: center; - flex-wrap: wrap; - justify-content: space-between; - - span[role='button'] { + ${({ theme }) => css` display: flex; + flex-direction: row; + align-items: center; + flex-wrap: nowrap; + justify-content: space-between; height: 100%; - } - .title-panel { - display: flex; - align-items: center; - } + span[role='button'] { + display: flex; + height: 100%; + } - .right-button-panel { - display: flex; - align-items: center; + .title-panel { + display: flex; + align-items: center; + min-width: 0; + margin-right: ${theme.gridUnit * 6}px; + } + + .right-button-panel { + display: flex; + align-items: center; - > .btn-group { - flex: 0 0 auto; - margin-left: ${({ theme }) => theme.gridUnit}px; + > .btn-group { + flex: 0 0 auto; + margin-left: ${theme.gridUnit}px; + } } - } - .action-button { - color: ${({ theme }) => theme.colors.grayscale.base}; - margin: 0 ${({ theme }) => theme.gridUnit * 1.5}px 0 - ${({ theme }) => theme.gridUnit}px; - } + .action-button { + color: ${theme.colors.grayscale.base}; + margin: 0 ${theme.gridUnit * 1.5}px 0 ${theme.gridUnit}px; + } + `} `; const StyledButtons = styled.span` @@ -173,13 +178,6 @@ export class ExploreChartHeader extends React.PureComponent { .catch(() => {}); } - getSliceName() { - const { sliceName, table_name: tableName } = this.props; - const title = sliceName || t('%s - untitled', tableName); - - return title; - } - postChartFormData() { this.props.actions.postChartFormData( this.props.form_data, @@ -221,22 +219,45 @@ export class ExploreChartHeader extends React.PureComponent { } render() { - const { user, form_data: formData, slice } = this.props; + const { + actions, + chart, + user, + formData, + slice, + canOverwrite, + canDownload, + isStarred, + sliceUpdated, + sliceName, + } = this.props; const { chartStatus, chartUpdateEndTime, chartUpdateStartTime, latestQueryFormData, queriesResponse, - } = this.props.chart; + sliceFormData, + } = chart; // TODO: when will get appropriate design for multi queries use all results and not first only const queryResponse = queriesResponse?.[0]; + const oldSliceName = slice?.slice_name; const chartFinished = ['failed', 'rendered', 'success'].includes( - this.props.chart.chartStatus, + chartStatus, ); return ( - +
+ {slice?.certified_by && ( <> {' '} )} - - - {this.props.slice && ( + {slice && ( {user.userId && ( )} @@ -272,15 +281,15 @@ export class ExploreChartHeader extends React.PureComponent { )} - {this.props.chart.sliceFormData && ( + {sliceFormData && ( )} @@ -306,10 +315,10 @@ export class ExploreChartHeader extends React.PureComponent { status={CHART_STATUS_MAP[chartStatus]} /> diff --git a/superset-frontend/src/explore/components/ExploreChartPanel.jsx b/superset-frontend/src/explore/components/ExploreChartPanel.jsx index 2067d853c7fd..61d03101bf70 100644 --- a/superset-frontend/src/explore/components/ExploreChartPanel.jsx +++ b/superset-frontend/src/explore/components/ExploreChartPanel.jsx @@ -28,7 +28,6 @@ import { setItem, LocalStorageKeys, } from 'src/utils/localStorageHelpers'; -import ConnectedExploreChartHeader from './ExploreChartHeader'; import { DataTablesPane } from './DataTablesPane'; import { buildV1ChartDataPayload } from '../exploreUtils'; @@ -63,7 +62,6 @@ const GUTTER_SIZE_FACTOR = 1.25; const CHART_PANEL_PADDING_HORIZ = 30; const CHART_PANEL_PADDING_VERTICAL = 15; -const HEADER_PADDING = 15; const INITIAL_SIZES = [90, 10]; const MIN_SIZES = [300, 50]; @@ -78,8 +76,8 @@ const Styles = styled.div` box-shadow: none; height: 100%; - & > div:last-of-type { - flex-basis: 100%; + & > div { + height: 100%; } .gutter { @@ -114,10 +112,6 @@ const ExploreChartPanel = props => { const theme = useTheme(); const gutterMargin = theme.gridUnit * GUTTER_SIZE_FACTOR; const gutterHeight = theme.gridUnit * GUTTER_SIZE_FACTOR; - const { height: hHeight, ref: headerRef } = useResizeDetector({ - refreshMode: 'debounce', - refreshRate: 300, - }); const { width: chartPanelWidth, ref: chartPanelRef } = useResizeDetector({ refreshMode: 'debounce', refreshRate: 300, @@ -156,21 +150,10 @@ const ExploreChartPanel = props => { }, [updateQueryContext]); const calcSectionHeight = useCallback( - percent => { - let headerHeight; - if (props.standalone) { - headerHeight = 0; - } else if (hHeight) { - headerHeight = hHeight + HEADER_PADDING; - } else { - headerHeight = 50; - } - const containerHeight = parseInt(props.height, 10) - headerHeight; - return ( - (containerHeight * percent) / 100 - (gutterHeight / 2 + gutterMargin) - ); - }, - [gutterHeight, gutterMargin, props.height, props.standalone, hHeight], + percent => + (parseInt(props.height, 10) * percent) / 100 - + (gutterHeight / 2 + gutterMargin), + [gutterHeight, gutterMargin, props.height, props.standalone], ); const [tableSectionHeight, setTableSectionHeight] = useState( @@ -283,34 +266,12 @@ const ExploreChartPanel = props => { return standaloneChartBody; } - const header = ( - - ); - const elementStyle = (dimension, elementSize, gutterSize) => ({ [dimension]: `calc(${elementSize}% - ${gutterSize + gutterMargin}px)`, }); return ( -
- {header} -
{props.vizType === 'filter_box' ? ( panelBody ) : ( diff --git a/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx b/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx index 527392275c51..57437ea99f88 100644 --- a/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx +++ b/superset-frontend/src/explore/components/ExploreViewContainer/index.jsx @@ -60,6 +60,7 @@ import { LOG_ACTIONS_MOUNT_EXPLORER, LOG_ACTIONS_CHANGE_EXPLORE_CONTROLS, } from '../../../logger/LogUtils'; +import ConnectedExploreChartHeader from '../ExploreChartHeader'; const propTypes = { ...ExploreChartPanel.propTypes, @@ -82,69 +83,96 @@ const propTypes = { vizType: PropTypes.string, }; -const Styles = styled.div` - background: ${({ theme }) => theme.colors.grayscale.light5}; - text-align: left; - position: relative; - width: 100%; - max-height: 100%; +const ExploreContainer = styled.div` display: flex; - flex-direction: row; - flex-wrap: nowrap; - flex-basis: 100vh; - align-items: stretch; - border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; - .explore-column { - display: flex; - flex-direction: column; - padding: ${({ theme }) => 2 * theme.gridUnit}px 0; - max-height: 100%; - } - .data-source-selection { - background-color: ${({ theme }) => theme.colors.grayscale.light5}; - padding: ${({ theme }) => 2 * theme.gridUnit}px 0; - border-right: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; - } - .main-explore-content { - flex: 1; - min-width: ${({ theme }) => theme.gridUnit * 128}px; - border-left: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; - .panel { - margin-bottom: 0; + flex-direction: column; + height: 100%; +`; + +const ExploreHeaderContainer = styled.div` + ${({ theme }) => css` + background-color: ${theme.colors.grayscale.light5}; + height: ${theme.gridUnit * 16}px; + padding: 0 ${theme.gridUnit * 4}px; + + .editable-title { + overflow: hidden; + + & > input[type='button'], + & > span { + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + white-space: nowrap; + } } - } - .controls-column { - align-self: flex-start; - padding: 0; - } - .title-container { + `} +`; + +const ExplorePanelContainer = styled.div` + ${({ theme }) => css` + background: ${theme.colors.grayscale.light5}; + text-align: left; position: relative; + width: 100%; + max-height: 100%; + min-height: 0; display: flex; - flex-direction: row; - padding: 0 ${({ theme }) => 2 * theme.gridUnit}px; - justify-content: space-between; - .horizontal-text { - text-transform: uppercase; - color: ${({ theme }) => theme.colors.grayscale.light1}; - font-size: ${({ theme }) => 4 * theme.typography.sizes.s}; + flex: 1; + flex-wrap: nowrap; + border-top: 1px solid ${theme.colors.grayscale.light2}; + .explore-column { + display: flex; + flex-direction: column; + padding: ${theme.gridUnit * 2}px 0; + max-height: 100%; } - } - .no-show { - display: none; - } - .vertical-text { - writing-mode: vertical-rl; - text-orientation: mixed; - } - .sidebar { - height: 100%; - background-color: ${({ theme }) => theme.colors.grayscale.light4}; - padding: ${({ theme }) => 2 * theme.gridUnit}px; - width: ${({ theme }) => 8 * theme.gridUnit}px; - } - .callpase-icon > svg { - color: ${({ theme }) => theme.colors.primary.base}; - } + .data-source-selection { + background-color: ${theme.colors.grayscale.light5}; + padding: ${theme.gridUnit * 2}px 0; + border-right: 1px solid ${theme.colors.grayscale.light2}; + } + .main-explore-content { + flex: 1; + min-width: ${theme.gridUnit * 128}px; + border-left: 1px solid ${theme.colors.grayscale.light2}; + .panel { + margin-bottom: 0; + } + } + .controls-column { + align-self: flex-start; + padding: 0; + } + .title-container { + position: relative; + display: flex; + flex-direction: row; + padding: 0 ${theme.gridUnit * 4}px; + justify-content: space-between; + .horizontal-text { + text-transform: uppercase; + color: ${theme.colors.grayscale.light1}; + font-size: ${theme.typography.sizes.s * 4}; + } + } + .no-show { + display: none; + } + .vertical-text { + writing-mode: vertical-rl; + text-orientation: mixed; + } + .sidebar { + height: 100%; + background-color: ${theme.colors.grayscale.light4}; + padding: ${theme.gridUnit * 2}px; + width: ${theme.gridUnit * 8}px; + } + .callpase-icon > svg { + color: ${theme.colors.primary.base}; + } + `}; `; const getWindowSize = () => ({ @@ -230,7 +258,7 @@ function ExploreViewContainer(props) { const theme = useTheme(); const width = `${windowSize.width}px`; - const navHeight = props.standalone ? 0 : 90; + const navHeight = props.standalone ? 0 : 120; const height = props.forcedHeight ? `${props.forcedHeight}px` : `${windowSize.height - navHeight}px`; @@ -515,144 +543,164 @@ function ExploreViewContainer(props) { } return ( - - - {showingModal && ( - + + - )} - { - setShouldForceUpdate(d?.width); - setSidebarWidths(LocalStorageKeys.datasource_width, d); - }} - defaultSize={{ - width: getSidebarWidths(LocalStorageKeys.datasource_width), - height: '100%', - }} - minWidth={defaultSidebarsWidth[LocalStorageKeys.datasource_width]} - maxWidth="33%" - enable={{ right: true }} - className={ - isCollapsed ? 'no-show' : 'explore-column data-source-selection' - } - > -
- {t('Dataset')} - - - -
- + + -
- {isCollapsed ? ( -
+ )} + { + setShouldForceUpdate(d?.width); + setSidebarWidths(LocalStorageKeys.datasource_width, d); + }} + defaultSize={{ + width: getSidebarWidths(LocalStorageKeys.datasource_width), + height: '100%', + }} + minWidth={defaultSidebarsWidth[LocalStorageKeys.datasource_width]} + maxWidth="33%" + enable={{ right: true }} + className={ + isCollapsed ? 'no-show' : 'explore-column data-source-selection' + } > - - - + {t('Dataset')} + + - - - +
+ + + {isCollapsed ? ( +
+ + + + + + +
+ ) : null} + + setSidebarWidths(LocalStorageKeys.controls_width, d) + } + defaultSize={{ + width: getSidebarWidths(LocalStorageKeys.controls_width), + height: '100%', + }} + minWidth={defaultSidebarsWidth[LocalStorageKeys.controls_width]} + maxWidth="33%" + enable={{ right: true }} + className="col-sm-3 explore-column controls-column" + > + + + +
+ {renderChartContainer()}
- ) : null} - - setSidebarWidths(LocalStorageKeys.controls_width, d) - } - defaultSize={{ - width: getSidebarWidths(LocalStorageKeys.controls_width), - height: '100%', - }} - minWidth={defaultSidebarsWidth[LocalStorageKeys.controls_width]} - maxWidth="33%" - enable={{ right: true }} - className="col-sm-3 explore-column controls-column" - > - - - -
- {renderChartContainer()} -
-
+ + ); } diff --git a/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx b/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx index f7cda8b75250..b011901eb030 100644 --- a/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx +++ b/superset-frontend/src/explore/components/controls/DatasourceControl/index.jsx @@ -59,7 +59,8 @@ const Styles = styled.div` justify-content: space-between; align-items: center; border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; - padding: ${({ theme }) => 2 * theme.gridUnit}px; + padding: ${({ theme }) => 4 * theme.gridUnit}px; + padding-right: ${({ theme }) => 2 * theme.gridUnit}px; } .error-alert { margin: ${({ theme }) => 2 * theme.gridUnit}px;