diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_properties.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/edit_properties.test.ts
index 42cde01dd..ad20010a8 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_properties.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/edit_properties.test.ts
@@ -91,7 +91,7 @@ describe('Dashboard edit action', () => {
cy.get('.ant-modal-body')
.should('be.visible')
.contains('Title')
- .siblings('input')
+ .get('[data-test="dashboard-title-input"]')
.type(`{selectall}{backspace}${dashboardTitle}`);
// save edit changes
diff --git a/superset-frontend/spec/javascripts/explore/components/BoundsControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/BoundsControl_spec.jsx
index 0af3b5eac..37fac69b0 100644
--- a/superset-frontend/spec/javascripts/explore/components/BoundsControl_spec.jsx
+++ b/superset-frontend/spec/javascripts/explore/components/BoundsControl_spec.jsx
@@ -16,12 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
-/* eslint-disable no-unused-expressions */
import React from 'react';
import { FormControl } from 'react-bootstrap';
import sinon from 'sinon';
-import { mount } from 'enzyme';
-
+import { styledMount as mount } from 'spec/helpers/theming';
import BoundsControl from 'src/explore/components/controls/BoundsControl';
const defaultProps = {
diff --git a/superset-frontend/spec/javascripts/explore/components/SaveModal_spec.jsx b/superset-frontend/spec/javascripts/explore/components/SaveModal_spec.jsx
index 04a30919d..258c725bb 100644
--- a/superset-frontend/spec/javascripts/explore/components/SaveModal_spec.jsx
+++ b/superset-frontend/spec/javascripts/explore/components/SaveModal_spec.jsx
@@ -24,7 +24,6 @@ import { Provider } from 'react-redux';
import { shallow } from 'enzyme';
import { styledMount as mount } from 'spec/helpers/theming';
-import { FormControl } from 'react-bootstrap';
import { Radio } from 'src/components/Radio';
import Button from 'src/components/Button';
import sinon from 'sinon';
@@ -91,7 +90,6 @@ describe('SaveModal', () => {
it('renders a Modal with the right set of components', () => {
const wrapper = getWrapper();
expect(wrapper.find(StyledModal)).toExist();
- expect(wrapper.find(FormControl)).toExist();
expect(wrapper.find(Radio)).toHaveLength(2);
const footerWrapper = shallow(wrapper.find(StyledModal).props().footer);
diff --git a/superset-frontend/src/CRUD/Field.jsx b/superset-frontend/src/CRUD/Field.jsx
index 1ed8a5e37..49f377d22 100644
--- a/superset-frontend/src/CRUD/Field.jsx
+++ b/superset-frontend/src/CRUD/Field.jsx
@@ -21,7 +21,7 @@ import PropTypes from 'prop-types';
import { FormGroup, HelpBlock, FormControl } from 'react-bootstrap';
import { Tooltip } from 'src/components/Tooltip';
-import FormLabel from 'src/components/FormLabel';
+import { FormLabel } from 'src/components/Form';
import './crud.less';
const propTypes = {
diff --git a/superset-frontend/src/SqlLab/components/SaveQuery.tsx b/superset-frontend/src/SqlLab/components/SaveQuery.tsx
index 7c9edf153..e4e52cf04 100644
--- a/superset-frontend/src/SqlLab/components/SaveQuery.tsx
+++ b/superset-frontend/src/SqlLab/components/SaveQuery.tsx
@@ -22,7 +22,7 @@ import { FormControl, FormGroup } from 'react-bootstrap';
import { t, supersetTheme, styled } from '@superset-ui/core';
import Button from 'src/components/Button';
-import FormLabel from 'src/components/FormLabel';
+import { FormLabel } from 'src/components/Form';
import Modal from 'src/components/Modal';
import Icon from 'src/components/Icon';
diff --git a/superset-frontend/src/SqlLab/components/ScheduleQueryButton.tsx b/superset-frontend/src/SqlLab/components/ScheduleQueryButton.tsx
index 24e13cdc9..fc87885f2 100644
--- a/superset-frontend/src/SqlLab/components/ScheduleQueryButton.tsx
+++ b/superset-frontend/src/SqlLab/components/ScheduleQueryButton.tsx
@@ -23,7 +23,7 @@ import { FormControl, FormGroup } from 'react-bootstrap';
import { t, styled } from '@superset-ui/core';
import * as chrono from 'chrono-node';
import ModalTrigger from 'src/components/ModalTrigger';
-import FormLabel from 'src/components/FormLabel';
+import { FormLabel } from 'src/components/Form';
import './ScheduleQueryButton.less';
import Button from 'src/components/Button';
diff --git a/superset-frontend/src/components/DeleteModal/index.tsx b/superset-frontend/src/components/DeleteModal/index.tsx
index a4a29fb81..e86d025e6 100644
--- a/superset-frontend/src/components/DeleteModal/index.tsx
+++ b/superset-frontend/src/components/DeleteModal/index.tsx
@@ -20,7 +20,7 @@ import { t, styled } from '@superset-ui/core';
import React, { useState } from 'react';
import { FormGroup, FormControl, FormControlProps } from 'react-bootstrap';
import Modal from 'src/components/Modal';
-import FormLabel from 'src/components/FormLabel';
+import { FormLabel } from 'src/components/Form';
const StyleFormGroup = styled(FormGroup)`
padding-top: 8px;
diff --git a/superset-frontend/src/components/FormLabel/index.tsx b/superset-frontend/src/components/Form/Form.tsx
similarity index 57%
rename from superset-frontend/src/components/FormLabel/index.tsx
rename to superset-frontend/src/components/Form/Form.tsx
index 1b0559897..946324549 100644
--- a/superset-frontend/src/components/FormLabel/index.tsx
+++ b/superset-frontend/src/components/Form/Form.tsx
@@ -16,32 +16,19 @@
* specific language governing permissions and limitations
* under the License.
*/
-// import { styled } from '@superset-ui/core';
-import React, { ReactNode } from 'react';
-import { ControlLabel } from 'react-bootstrap';
+import React from 'react';
+import AntDForm, { FormProps } from 'antd/lib/form';
+import { styled } from '@superset-ui/core';
-export type FormLabelProps = {
- children: ReactNode;
- htmlFor?: string;
- required?: boolean;
- className?: string;
-};
+const StyledForm = styled(AntDForm)`
+ &.ant-form label {
+ font-size: ${({ theme }) => theme.typography.sizes.s}px;
+ }
+ .ant-form-item {
+ margin-bottom: ${({ theme }) => theme.gridUnit * 4}px;
+ }
+`;
-export default function FormLabel({
- children,
- htmlFor,
- required = false,
-}: FormLabelProps) {
- return (
- <>
-
- {children}
- {required && (
-
- *
-
- )}
-
- >
- );
+export default function Form(props: FormProps) {
+ return ;
}
diff --git a/superset-frontend/src/components/FormLabel/FormLabel.test.tsx b/superset-frontend/src/components/Form/FormItem.tsx
similarity index 50%
rename from superset-frontend/src/components/FormLabel/FormLabel.test.tsx
rename to superset-frontend/src/components/Form/FormItem.tsx
index 0f901785e..ce02c555f 100644
--- a/superset-frontend/src/components/FormLabel/FormLabel.test.tsx
+++ b/superset-frontend/src/components/Form/FormItem.tsx
@@ -17,36 +17,29 @@
* under the License.
*/
import React from 'react';
-import { render, screen } from 'spec/helpers/testing-library';
-import { FormControl } from 'react-bootstrap';
-import FormLabel from './index';
+import Form, { FormItemProps } from 'antd/lib/form';
+import { styled } from '@superset-ui/core';
-const mockedProps = {
- children: 'Form label',
- required: false,
- htmlFor: 'form-control',
-};
+const StyledItem = styled(Form.Item)`
+ .ant-form-item-label > label {
+ text-transform: uppercase;
+ font-size: ${({ theme }) => theme.typography.sizes.s}px;
+ color: ${({ theme }) => theme.colors.grayscale.base};
-test('should render', () => {
- const { container } = render();
- expect(container).toBeInTheDocument();
-});
+ &.ant-form-item-required:not(.ant-form-item-required-mark-optional) {
+ &::before {
+ display: none;
+ }
+ &::after {
+ display: inline-block;
+ color: ${({ theme }) => theme.colors.error.base};
+ font-size: ${({ theme }) => theme.typography.sizes.m}px;
+ content: '*';
+ }
+ }
+ }
+`;
-test('should render a Label', () => {
- render(
- <>
-
-
- >,
- );
- expect(screen.getByLabelText('Form label')).toBeInTheDocument();
-});
-
-test('should be shown as required', () => {
- const requiredProps = {
- ...mockedProps,
- required: true,
- };
- render();
- expect(screen.getByText('*').parentNode).toBeInTheDocument();
-});
+export default function FormItem(props: FormItemProps) {
+ return ;
+}
diff --git a/superset-frontend/src/components/Form/FormLabel.tsx b/superset-frontend/src/components/Form/FormLabel.tsx
new file mode 100644
index 000000000..cb4cba079
--- /dev/null
+++ b/superset-frontend/src/components/Form/FormLabel.tsx
@@ -0,0 +1,61 @@
+/**
+ * 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, { ReactNode } from 'react';
+import { styled } from '@superset-ui/core';
+
+export type FormLabelProps = {
+ children: ReactNode;
+ htmlFor?: string;
+ required?: boolean;
+ className?: string;
+};
+
+const Label = styled.label`
+ text-transform: uppercase;
+ font-size: ${({ theme }) => theme.typography.sizes.s}px;
+ color: ${({ theme }) => theme.colors.grayscale.base};
+ margin-bottom: ${({ theme }) => theme.gridUnit}px;
+`;
+
+const RequiredLabel = styled.label`
+ text-transform: uppercase;
+ font-size: ${({ theme }) => theme.typography.sizes.s}px;
+ color: ${({ theme }) => theme.colors.grayscale.base};
+ margin-bottom: ${({ theme }) => theme.gridUnit}px;
+ &::after {
+ display: inline-block;
+ color: ${({ theme }) => theme.colors.error.base};
+ font-size: ${({ theme }) => theme.typography.sizes.m}px;
+ content: '*';
+ }
+`;
+
+export default function FormLabel({
+ children,
+ htmlFor,
+ required = false,
+ className,
+}: FormLabelProps) {
+ const StyledLabel = required ? RequiredLabel : Label;
+ return (
+
+ {children}
+
+ );
+}
diff --git a/superset-frontend/src/components/Form/index.tsx b/superset-frontend/src/components/Form/index.tsx
new file mode 100644
index 000000000..f0734a254
--- /dev/null
+++ b/superset-frontend/src/components/Form/index.tsx
@@ -0,0 +1,23 @@
+/**
+ * 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 Form from './Form';
+import FormItem from './FormItem';
+import FormLabel from './FormLabel';
+
+export { Form, FormItem, FormLabel };
diff --git a/superset-frontend/src/components/TableSelector/index.tsx b/superset-frontend/src/components/TableSelector/index.tsx
index f6f4020b2..56d3b2157 100644
--- a/superset-frontend/src/components/TableSelector/index.tsx
+++ b/superset-frontend/src/components/TableSelector/index.tsx
@@ -25,7 +25,7 @@ import React, {
import { styled, SupersetClient, t } from '@superset-ui/core';
import { AsyncSelect, CreatableSelect, Select } from 'src/components/Select';
-import FormLabel from 'src/components/FormLabel';
+import { FormLabel } from 'src/components/Form';
import DatabaseSelector from 'src/components/DatabaseSelector';
import RefreshLabel from 'src/components/RefreshLabel';
diff --git a/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx b/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx
index 871dad648..18af6f5a8 100644
--- a/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx
+++ b/superset-frontend/src/dashboard/components/ColorSchemeControlWrapper.jsx
@@ -25,6 +25,7 @@ import ColorSchemeControl from 'src/explore/components/controls/ColorSchemeContr
const propTypes = {
onChange: PropTypes.func,
+ labelMargin: PropTypes.number,
colorScheme: PropTypes.string,
};
@@ -47,13 +48,14 @@ class ColorSchemeControlWrapper extends React.PureComponent {
}
render() {
- const { colorScheme } = this.props;
+ const { colorScheme, labelMargin = 0 } = this.props;
return (
{t('Access')}
- {t('Owners')}
-
-
- {t(
- 'Owners is a list of users who can alter the dashboard. Searchable by name or username.',
- )}
-
+
+
+
+ {t(
+ 'Owners is a list of users who can alter the dashboard. Searchable by name or username.',
+ )}
+
+
{t('Colors')}
@@ -367,42 +368,44 @@ class PropertiesModal extends React.PureComponent {
- {t('Owners')}
-
-
- {t(
- 'Owners is a list of users who can alter the dashboard. Searchable by name or username.',
- )}
-
+
+
+
+ {t(
+ 'Owners is a list of users who can alter the dashboard. Searchable by name or username.',
+ )}
+
+
- {t('Roles')}
-
-
- {t(
- 'Roles is a list which defines access to the dashboard. Granting a role access to a dashboard will bypass dataset level checks. If no roles defined then the dashboard is available to all roles.',
- )}
-
+
+
+
+ {t(
+ 'Roles is a list which defines access to the dashboard. Granting a role access to a dashboard will bypass dataset level checks. If no roles defined then the dashboard is available to all roles.',
+ )}
+
+
@@ -410,6 +413,7 @@ class PropertiesModal extends React.PureComponent {
@@ -453,7 +457,11 @@ class PropertiesModal extends React.PureComponent {
}
responsive
>
-
+
);
}
diff --git a/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx b/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx
index 1e21a453c..aa97ff872 100644
--- a/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx
+++ b/superset-frontend/src/dashboard/components/RefreshIntervalModal.tsx
@@ -23,7 +23,7 @@ import Alert from 'src/components/Alert';
import Button from 'src/components/Button';
import ModalTrigger from 'src/components/ModalTrigger';
-import FormLabel from 'src/components/FormLabel';
+import { FormLabel } from 'src/components/Form';
export const options = [
[0, t("Don't refresh")],
diff --git a/superset-frontend/src/dashboard/components/filterscope/FilterFieldItem.jsx b/superset-frontend/src/dashboard/components/filterscope/FilterFieldItem.jsx
index b42d0f6a3..78364a71e 100644
--- a/superset-frontend/src/dashboard/components/filterscope/FilterFieldItem.jsx
+++ b/superset-frontend/src/dashboard/components/filterscope/FilterFieldItem.jsx
@@ -19,7 +19,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
-import FormLabel from 'src/components/FormLabel';
+import { FormLabel } from 'src/components/Form';
const propTypes = {
label: PropTypes.string.isRequired,
diff --git a/superset-frontend/src/explore/components/ControlHeader.jsx b/superset-frontend/src/explore/components/ControlHeader.jsx
index 522ac94a0..3e0f91260 100644
--- a/superset-frontend/src/explore/components/ControlHeader.jsx
+++ b/superset-frontend/src/explore/components/ControlHeader.jsx
@@ -21,7 +21,7 @@ import PropTypes from 'prop-types';
import { t } from '@superset-ui/core';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import { Tooltip } from 'src/components/Tooltip';
-import FormLabel from 'src/components/FormLabel';
+import { FormLabel } from 'src/components/Form';
const propTypes = {
name: PropTypes.string,
@@ -85,7 +85,7 @@ export default class ControlHeader extends React.Component {
return (