diff --git a/babel.config.js b/babel.config.js index cd71bacf4a0..6590e779147 100644 --- a/babel.config.js +++ b/babel.config.js @@ -5,7 +5,6 @@ module.exports = { '@babel/plugin-transform-react-jsx', '@babel/plugin-proposal-class-properties', 'react-hot-loader/babel', - ['import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }], ['babel-plugin-kea', { path: './frontend/src' }], ], presets: ['@babel/preset-env', '@babel/typescript'], diff --git a/cypress/integration/actions.js b/cypress/integration/actions.js index 27ace7e16fe..89f5a7c025f 100644 --- a/cypress/integration/actions.js +++ b/cypress/integration/actions.js @@ -10,17 +10,17 @@ describe('Actions', () => { it('Click on an action', () => { cy.get('[data-attr=action-link-0]').click() - cy.get('h1').should('contain', 'Edit action') + cy.get('h1').should('contain', 'Editing action') }) it('Create action', () => { cy.get('[data-attr=create-action]').click() cy.get('.ant-card-head-title').should('contain', 'event or pageview') cy.get('[data-attr=new-action-pageview]').click() - cy.get('h1').should('contain', 'New Action') + cy.get('h1').should('contain', 'Creating action') cy.get('[data-attr=edit-action-input]').type(Cypress._.random(0, 1e6)) - cy.get('[data-attr=action-step-pageview]').click() + cy.get('.ant-radio-group > :nth-child(3)').click() cy.get('[data-attr=edit-action-url-input]').type(Cypress.config().baseUrl) cy.get('[data-attr=save-action-button]').click() diff --git a/cypress/integration/cohorts.js b/cypress/integration/cohorts.js index 76a149aa70e..271ea343a23 100644 --- a/cypress/integration/cohorts.js +++ b/cypress/integration/cohorts.js @@ -9,10 +9,10 @@ describe('Cohorts', () => { // go to create a new cohort cy.get('[data-attr="create-cohort"]').click() - cy.get('form.card-body > .form-control').type('Test Cohort') + cy.get('[data-attr="cohort-name"]').type('Test Cohort') // select "add filter" and "property" - cy.get('[data-attr="cohort-group-property"]').click() + cy.get('.ant-radio-group > :nth-child(2)').click() cy.get('[data-attr="new-prop-filter-cohort_0"]').click() // select the first property diff --git a/cypress/integration/people.js b/cypress/integration/people.js index 7a793cc98d2..6d27cd2240b 100644 --- a/cypress/integration/people.js +++ b/cypress/integration/people.js @@ -7,13 +7,8 @@ describe('People', () => { cy.get('h1').should('contain', 'Persons') }) - it('Go to new cohort from people screen', () => { - cy.get('[data-attr=create-cohort]').click() - cy.get('span').should('contain', 'New Cohort') - }) - it('All tabs work', () => { - cy.get('.form-control').type('has:email').type('{enter}').should('have.value', 'has:email') + cy.get('[data-attr=persons-search]').type('has:email').type('{enter}').should('have.value', 'has:email') cy.wait(200) cy.get('.ant-tabs-nav-list > :nth-child(2)').click() cy.get('[data-row-key="100"] > :nth-child(2) > .ph-no-capture').should('contain', '@') @@ -24,8 +19,9 @@ describe('People', () => { it('All people route works', () => { cy.get('[data-attr=menu-item-people-cohorts]').click() - cy.get('[data-attr=menu-item-people-persons]').click() + cy.get('h1').should('contain', 'Cohorts') + cy.get('[data-attr=menu-item-people-persons]').click() cy.get('h1').should('contain', 'Persons') }) }) diff --git a/cypress/integration/trendsElements.js b/cypress/integration/trendsElements.js index 793b2c05c77..69911430e9c 100644 --- a/cypress/integration/trendsElements.js +++ b/cypress/integration/trendsElements.js @@ -27,8 +27,13 @@ describe('Trends actions & events', () => { it('Show property select dynamically', () => { cy.get('[data-attr=math-property-selector-0]').should('not.exist') - cy.get('[data-attr=math-selector-0]').click() - cy.get('[data-attr=math-avg-0]').click() + + // Test that the math selector dropdown is shown on hover + cy.get('[data-attr=math-selector-0]').trigger('mouseover') + cy.get('[data-attr=math-total-0]').should('be.visible') + + // Use `force = true` because clicking the element without dragging the mouse makes the dropdown disappear + cy.get('[data-attr=math-avg-0]').click({ force: true }) cy.get('[data-attr=math-property-selector-0]').should('exist') }) diff --git a/frontend/src/antd.less b/frontend/src/antd.less new file mode 100644 index 00000000000..ce3117fad13 --- /dev/null +++ b/frontend/src/antd.less @@ -0,0 +1,20 @@ +/* This file sets theming configuration on Ant Design for PostHog. Ant uses LESS which is incompatible +with SASS, which is why configuration is duplicated. To change any variable here, please update vars.scss too */ +@import 'antd/lib/style/themes/default.less'; +@import 'antd/dist/antd.less'; + +@text-color: #2d2d2d; +@text-muted: #d9d9d9; +@primary-color: #5375ff; +@link-color: #5375ff; +@success-color: #77b96c; +@warning-color: #f7a501; +@error-color: #f96132; +@font-size-base: 14px; +@heading-color: @text-color; +@text-color-secondary: rgba(0, 0, 0, 0.45); +@disabled-color: @text-muted; +@border-radius-base: 2px; +@border-color-base: #d9d9d9; +@body-background: #f2f2f2; +@layout-body-background: #fff; diff --git a/frontend/src/global.scss b/frontend/src/global.scss new file mode 100644 index 00000000000..6acc47f629a --- /dev/null +++ b/frontend/src/global.scss @@ -0,0 +1,334 @@ +/* Only styles that are shared across multiple components (i.e. global) should go here, trying to keep this file +nimble to simplify maintenance. We separate variables and mixins in vars.scss to be able to import those into local +style files without adding already imported styles. */ + +// Global components +@import 'node_modules/react-toastify/dist/ReactToastify'; +@import 'node_modules/react-datepicker/dist/react-datepicker'; +@import './vars'; + +:root { + --primary: #{$primary}; + --success: #{$success}; + --danger: #{$danger}; + --warning: #{$warning}; + --bg-menu: #{$bg_menu}; + --bg-mid: #{$bg_mid}; + // Used for graph series + --blue: #{$blue_500}; + --purple: #{purple_500}; + --salmon: #ff906e; + --yellow: #ffc035; + --green: #{$success}; + --indigo: #{$purple_700}; + --cyan: #17a2b8; + --pink: #e83e8c; + --white: #f4f6ff; +} + +// Text styles +.text-default { + color: $text_default; + font-size: 14px; + line-height: 20px; + font-weight: 400; +} + +.text-small { + @extend .text-default; + font-size: 12px; +} + +.text-extra-small { + @extend .text-default; + font-size: 10px; +} + +.page-title { + font-size: 28px; + line-height: 34px; + margin-top: 32px; + font-weight: 700; + color: $text_default; +} + +.page-caption { + @extend .text-default; + max-width: 640px; + margin-bottom: 32px; +} + +.subtitle { + margin-top: 24px; + font-size: 22px; + line-height: 26px; + font-weight: 700; +} + +.l3 { + /* Level 3 title (ideally H3) */ + font-size: 16px; + font-weight: 700; + line-height: 19px; +} + +.text-right { + text-align: right; +} + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-muted { + color: $text_muted; +} + +// Spacing & layout +.mb { + margin-bottom: $default_spacing; +} + +.mt { + margin-top: $default_spacing; +} + +.mb-05 { + margin-bottom: $default_spacing * 0.5; +} + +.mt-05 { + margin-top: $default_spacing * 0.5; +} + +.mr { + margin-right: $default_spacing; +} + +.ml { + margin-left: $default_spacing; +} + +.pa { + // Padding all + padding: $default_spacing; +} + +.pb { + padding-bottom: $default_spacing; +} + +.pt { + padding-top: $default_spacing; +} + +.pr { + padding-right: $default_spacing; +} + +.pl { + padding-left: $default_spacing; +} + +.full-width { + width: 100%; +} + +.float-right { + float: right; +} + +.float-left { + float: left; +} + +.main-app-content { + padding: $default_spacing $default_spacing * 3; + + @media (min-width: 480px) and (max-width: 639px) { + padding: $default_spacing $default_spacing * 2 !important; + } + + @media (max-width: 480px) { + padding: $default_spacing $default_spacing !important; + } +} + +// Color styles +.bg-primary { + background-color: $primary; +} + +.text-danger { + color: $danger !important; +} + +// Random general styles +.cursor-pointer { + cursor: pointer; +} + +// Toasts +.Toastify__toast-container { + opacity: 1; + transform: none; +} + +.Toastify__toast { + padding: 16px; + border-radius: $radius; + color: $text_default; + font-family: inherit; + background-color: $bg_light; +} + +.Toastify__toast-body { + @extend .l3; + color: $success; + p { + @extend .text-default; + color: $text_default; + } +} + +.Toastify__progress-bar--default { + background: $success; +} + +.Toastify__toast--error { + h1 { + color: $danger; + } + .Toastify__progress-bar { + background: $danger; + } + .error-details { + font-style: italic; + } +} + +// Table styles +.table-bordered td { + border: 1px solid $border; +} + +// Card styles +.card-elevated { + @extend .mixin-elevated; +} + +// Form & input styles +.input-set { + padding-bottom: $default_spacing; + color: $text_default; + + label { + font-weight: bold; + @extend .text-default; + } + + .caption { + color: $text_muted; + @extend .text-small; + } + + &.errored { + .caption { + color: $danger; + } + .ant-input-password, + input[type='text'] { + border-color: $danger !important; + } + } +} + +// Button styles +.btn-close { + color: $text_muted; +} + +.ant-btn-sm { + font-size: 12px !important; +} + +.ant-btn-md { + // Size between `small` & `default` + font-size: 13px !important; + height: 28px !important; + padding: 0px 10px !important; +} + +.info-indicator { + color: $primary !important; + cursor: pointer; + margin-left: 5px; +} + +// Overlays styles +#bottom-notice { + z-index: 1000000000; + display: flex; + flex-direction: row; + position: fixed; + width: 100%; + bottom: 0; + left: 0; + background: #000; + color: #fff; + font-size: 0.75rem; + line-height: 1.5rem; + &.warning div { + height: auto; + background: $danger; + } + + div:nth-child(1) { + background: $purple_700; + } + div:nth-child(2) { + background: $purple_500; + } + div:nth-child(3) { + background: $purple_300; + } + + div { + flex-basis: 0; + flex-grow: 1; + height: 1.5rem; + text-align: center; + } + span { + display: none; + } + button { + border: none; + background: transparent; + width: 1.5rem; + height: 1.5rem; + padding: 0; + font-size: 1rem; + font-weight: bold; + cursor: pointer; + } + @media screen and (min-width: 750px) { + font-size: 1rem; + line-height: 2rem; + div { + height: 2rem; + } + span { + display: inline; + } + button { + width: 2rem; + height: 2rem; + font-size: 1.25rem; + } + } +} diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 8161ac65789..afab3e93866 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,4 +1,6 @@ -import './style.scss' +import '~/global.scss' /* Contains PostHog's main styling configurations */ +import '~/antd.less' /* Imports Ant Design's components */ +import './style.scss' /* DEPRECATED */ import React from 'react' import ReactDOM from 'react-dom' import { Provider } from 'react-redux' diff --git a/frontend/src/initKea.tsx b/frontend/src/initKea.tsx index cbccd6cef85..20b597430ab 100644 --- a/frontend/src/initKea.tsx +++ b/frontend/src/initKea.tsx @@ -5,6 +5,7 @@ import { loadersPlugin } from 'kea-loaders' import { windowValuesPlugin } from 'kea-window-values' import { toast } from 'react-toastify' import React from 'react' +import { identifierToHuman } from 'lib/utils' export function initKea(): void { resetContext({ @@ -16,9 +17,11 @@ export function initKea(): void { onFailure({ error, reducerKey, actionKey }) { toast.error(
Action "{actionKey}" responded with
-"{error.message || error.detail}"
++ Attempting to {identifierToHuman(actionKey, false)} returned an error:{' '} + {error.detail} +
Value needs to be a number. Try "equals" or "contains" instead.
- )} -+ Value needs to be a number. Try "equals" or "contains" instead. +
+ )} + + )} + > ) } diff --git a/frontend/src/lib/components/PropertyFilters/PropertyFilters.js b/frontend/src/lib/components/PropertyFilters/PropertyFilters.js index 596887970cb..e81cca8612d 100644 --- a/frontend/src/lib/components/PropertyFilters/PropertyFilters.js +++ b/frontend/src/lib/components/PropertyFilters/PropertyFilters.js @@ -6,7 +6,8 @@ import { propertyFilterLogic } from './propertyFilterLogic' import { cohortsModel } from '../../../models/cohortsModel' import { keyMapping } from 'lib/components/PropertyKeyInfo' import { Popover, Row } from 'antd' -import { CloseButton, formatPropertyLabel } from 'lib/utils' +import { formatPropertyLabel } from 'lib/utils' +import { CloseButton } from 'lib/components/CloseButton' import '../../../scenes/actions/Actions.scss' const FilterRow = React.memo(function FilterRow({ @@ -31,7 +32,7 @@ const FilterRow = React.memo(function FilterRow({ } return ( -Got any PostHog questions?