diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d4cfc12..8dd1f076 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## v1.1.24 (2023-02-06) +## Unreleased ### Fixed - Design polishing ([1290](https://github.com/grafana/oncall/pull/1290)) - Not showing contact details in User tooltip if User does not have edit/admin access +### Changes + +- Incidents - Removed buttons column and replaced status with toggler ([#1237](https://github.com/grafana/oncall/issues/1237)) +- Responsiveness changes across multiple pages (Incidents, Integrations, Schedules) ([#1237](https://github.com/grafana/oncall/issues/1237)) + ## v1.1.23 (2023-02-06) ### Fixed diff --git a/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx b/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx new file mode 100644 index 00000000..6199712c --- /dev/null +++ b/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx @@ -0,0 +1,53 @@ +import React, { FC, useEffect, useState } from 'react'; + +import { Tooltip } from '@grafana/ui'; +import { debounce } from 'throttle-debounce'; + +interface MatchMediaTooltipProps { + placement: 'top' | 'bottom' | 'right' | 'left'; + content: string; + children: JSX.Element; + + maxWidth?: number; + minWidth?: number; +} + +const DEBOUNCE_MS = 200; + +export const MatchMediaTooltip: FC = ({ minWidth, maxWidth, placement, content, children }) => { + const [match, setMatch] = useState(getMatch()); + + useEffect(() => { + const debouncedResize = debounce(DEBOUNCE_MS, onWindowResize); + window.addEventListener('resize', debouncedResize); + return () => { + window.removeEventListener('resize', debouncedResize); + }; + }, []); + + if (match?.matches) { + return ( + + {children} + + ); + } + + return <>{children}; + + function onWindowResize() { + setMatch(getMatch()); + } + + function getMatch() { + if (minWidth && maxWidth) { + return window.matchMedia(`(min-width: ${minWidth}px) and (max-width: ${maxWidth}px)`); + } else if (minWidth) { + return window.matchMedia(`(min-width: ${minWidth}px)`); + } else if (maxWidth) { + return window.matchMedia(`(max-width: ${maxWidth}px)`); + } + + return undefined; + } +}; diff --git a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.css b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.css deleted file mode 100644 index e7bbaff6..00000000 --- a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.root { - display: inline-flex; - align-items: center; -} diff --git a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.scss b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.scss new file mode 100644 index 00000000..0d35e21d --- /dev/null +++ b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.scss @@ -0,0 +1,13 @@ +.right { + display: flex; + flex-wrap: wrap; + row-gap: 4px; + column-gap: 8px; +} + +@media screen and (max-width: 1600px) { + .right { + order: 3; + width: 100%; + } +} diff --git a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx index ce99674f..25fb810b 100644 --- a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx +++ b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx @@ -1,60 +1,95 @@ -import React, { useCallback, useMemo } from 'react'; +import React, { ChangeEvent, useCallback } from 'react'; -import { DatePickerWithInput, Field, HorizontalGroup, RadioButtonGroup } from '@grafana/ui'; +import { Field, Icon, Input, RadioButtonGroup } from '@grafana/ui'; import cn from 'classnames/bind'; -import moment from 'moment-timezone'; -import { dateStringToOption, optionToDateString } from './SchedulesFilters.helpers'; +import { ScheduleType } from 'models/schedule/schedule.types'; + +import styles from './SchedulesFilters.module.scss'; import { SchedulesFiltersType } from './SchedulesFilters.types'; -import styles from './SchedulesFilters.module.css'; - const cx = cn.bind(styles); interface SchedulesFiltersProps { value: SchedulesFiltersType; onChange: (filters: SchedulesFiltersType) => void; - className?: string; } -const SchedulesFilters = ({ value, onChange, className }: SchedulesFiltersProps) => { - const handleDateChange = useCallback( - (date: Date) => { - onChange({ selectedDate: moment(date).format('YYYY-MM-DD') }); +const SchedulesFilters = (props: SchedulesFiltersProps) => { + const { value, onChange } = props; + + const onSearchTermChangeCallback = useCallback( + (e: ChangeEvent) => { + onChange({ ...value, searchTerm: e.currentTarget.value }); }, - [onChange] + [value] + ); + const handleStatusChange = useCallback( + (status) => { + onChange({ ...value, status }); + }, + [value] ); - const option = useMemo(() => dateStringToOption(value.selectedDate), [value]); - - const handleOptionChange = useCallback( - (option: string) => { - onChange({ ...value, selectedDate: optionToDateString(option) }); + const handleTypeChange = useCallback( + (type) => { + onChange({ ...value, type }); }, - [onChange, value] + [value] ); - const datePickerValue = useMemo(() => moment(value.selectedDate).toDate(), [value]); - return ( -
- - - +
+ + } + placeholder="Search..." + value={value.searchTerm} + onChange={onSearchTermChangeCallback} /> - - +
+
+ + - -
+ + + +
+ ); }; diff --git a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.types.ts b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.types.ts index 4ec857f9..ec0ab632 100644 --- a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.types.ts +++ b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.types.ts @@ -1,3 +1,7 @@ +import { ScheduleType } from 'models/schedule/schedule.types'; + export interface SchedulesFiltersType { - selectedDate: string; + searchTerm: string; + type: ScheduleType; + status: string; } diff --git a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.helpers.ts b/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.helpers.ts deleted file mode 100644 index 1b35ebb3..00000000 --- a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.helpers.ts +++ /dev/null @@ -1,25 +0,0 @@ -import moment from 'moment-timezone'; - -export function optionToDateString(option: string) { - switch (option) { - case 'today': - return moment().startOf('day').format('YYYY-MM-DD'); - case 'tomorrow': - return moment().add(1, 'day').startOf('day').format('YYYY-MM-DD'); - default: - return moment().add(2, 'day').startOf('day').format('YYYY-MM-DD'); - } -} - -export function dateStringToOption(dateString: string) { - const today = moment().startOf('day').format('YYYY-MM-DD'); - if (dateString === today) { - return 'today'; - } - const tomorrow = moment().add(1, 'day').startOf('day').format('YYYY-MM-DD'); - if (dateString === tomorrow) { - return 'tomorrow'; - } - - return 'custom'; -} diff --git a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.module.css b/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.module.css deleted file mode 100644 index e7bbaff6..00000000 --- a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.root { - display: inline-flex; - align-items: center; -} diff --git a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx b/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx deleted file mode 100644 index cc6ee209..00000000 --- a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { ChangeEvent, useCallback } from 'react'; - -import { Field, HorizontalGroup, Icon, Input, RadioButtonGroup } from '@grafana/ui'; -import cn from 'classnames/bind'; - -import { ScheduleType } from 'models/schedule/schedule.types'; - -import { SchedulesFiltersType } from './SchedulesFilters.types'; - -import styles from './SchedulesFilters.module.css'; - -const cx = cn.bind(styles); - -interface SchedulesFiltersProps { - value: SchedulesFiltersType; - onChange: (filters: SchedulesFiltersType) => void; -} - -const SchedulesFilters = (props: SchedulesFiltersProps) => { - const { value, onChange } = props; - - const onSearchTermChangeCallback = useCallback( - (e: ChangeEvent) => { - onChange({ ...value, searchTerm: e.currentTarget.value }); - }, - [value] - ); - const handleStatusChange = useCallback( - (status) => { - onChange({ ...value, status }); - }, - [value] - ); - - const handleTypeChange = useCallback( - (type) => { - onChange({ ...value, type }); - }, - [value] - ); - - return ( -
- - - } - placeholder="Search..." - value={value.searchTerm} - onChange={onSearchTermChangeCallback} - /> - - - - - - - - -
- ); -}; - -export default SchedulesFilters; diff --git a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.types.ts b/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.types.ts deleted file mode 100644 index ec0ab632..00000000 --- a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ScheduleType } from 'models/schedule/schedule.types'; - -export interface SchedulesFiltersType { - searchTerm: string; - type: ScheduleType; - status: string; -} diff --git a/grafana-plugin/src/components/Tag/Tag.tsx b/grafana-plugin/src/components/Tag/Tag.tsx index 6ade4339..61037524 100644 --- a/grafana-plugin/src/components/Tag/Tag.tsx +++ b/grafana-plugin/src/components/Tag/Tag.tsx @@ -8,15 +8,22 @@ interface TagProps { color: string; className?: string; children?: any; + onClick?: (ev) => void; + forwardedRef?: React.MutableRefObject; } const cx = cn.bind(styles); const Tag: FC = (props) => { - const { children, color, className } = props; + const { children, color, className, onClick } = props; return ( - + {children} ); diff --git a/grafana-plugin/src/components/Tutorial/Tutorial.module.css b/grafana-plugin/src/components/Tutorial/Tutorial.module.css index bb3b2c79..67d607aa 100644 --- a/grafana-plugin/src/components/Tutorial/Tutorial.module.css +++ b/grafana-plugin/src/components/Tutorial/Tutorial.module.css @@ -25,12 +25,6 @@ text-align: center; } -@media (min-width: 1540px) { - .step { - width: 170px; - } -} - .icon { width: 60px; height: 60px; @@ -55,3 +49,9 @@ :global(.theme-dark) .arrow svg { fill-opacity: 0.15; } + +@media (min-width: 1540px) { + .step { + width: 170px; + } +} diff --git a/grafana-plugin/src/components/WithContextMenu/WithContextMenu.tsx b/grafana-plugin/src/components/WithContextMenu/WithContextMenu.tsx new file mode 100644 index 00000000..c28a213c --- /dev/null +++ b/grafana-plugin/src/components/WithContextMenu/WithContextMenu.tsx @@ -0,0 +1,61 @@ +import React, { useEffect, useState } from 'react'; + +import { ContextMenu } from '@grafana/ui'; + +export interface WithContextMenuProps { + children: (props: { openMenu: React.MouseEventHandler }) => JSX.Element; + renderMenuItems: () => React.ReactNode; + forceIsOpen?: boolean; + focusOnOpen?: boolean; +} + +const query = '[class$="-page-container"] .scrollbar-view'; + +export const WithContextMenu: React.FC = ({ + children, + renderMenuItems, + forceIsOpen = false, + focusOnOpen = true, +}) => { + const [isMenuOpen, setIsMenuOpen] = useState(false || forceIsOpen); + const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 }); + + useEffect(() => { + setIsMenuOpen(forceIsOpen); + }, [forceIsOpen]); + + useEffect(() => { + const onScrollOrResizeFn = () => setIsMenuOpen(false); + document.querySelector(query)?.addEventListener('scroll', onScrollOrResizeFn); + window.addEventListener('resize', onScrollOrResizeFn); + + return () => { + document.querySelector(query)?.removeEventListener('scroll', onScrollOrResizeFn); + window.removeEventListener('resize', onScrollOrResizeFn); + }; + }, []); + + return ( + <> + {children({ + openMenu: (e) => { + setIsMenuOpen(true); + setMenuPosition({ + x: e.pageX, + y: e.pageY, + }); + }, + })} + + {isMenuOpen && ( + setIsMenuOpen(false)} + x={menuPosition.x} + y={menuPosition.y} + renderMenuItems={renderMenuItems} + focusOnOpen={focusOnOpen} + /> + )} + + ); +}; diff --git a/grafana-plugin/src/containers/AlertRules/AlertRules.module.css b/grafana-plugin/src/containers/AlertRules/AlertRules.module.css index e85a4acb..cb6bfe72 100644 --- a/grafana-plugin/src/containers/AlertRules/AlertRules.module.css +++ b/grafana-plugin/src/containers/AlertRules/AlertRules.module.css @@ -16,12 +16,6 @@ margin-bottom: 10px; } -.header { - display: flex; - justify-content: space-between; - align-items: center; -} - .verbal-name { font-weight: 500; } @@ -55,6 +49,8 @@ display: flex; justify-content: space-between; align-items: center; + flex-wrap: wrap; + gap: 8px; } .channel-filter-header-title { @@ -116,3 +112,27 @@ .description-style a { color: var(--primary-text-link); } + +.integration__heading-text { + display: flex; + gap: 8px; +} + +.integration__heading-container { + display: flex; + flex-wrap: wrap; +} + +.integration__heading-container-left { + margin-bottom: 12px; +} + +.integration__heading-container-left, +.integration__heading-container-right { + flex-grow: 1; +} + +.integration__heading-container-right { + display: flex; + justify-content: flex-end; +} diff --git a/grafana-plugin/src/containers/AlertRules/AlertRules.tsx b/grafana-plugin/src/containers/AlertRules/AlertRules.tsx index fe9f35ee..b88ce8a6 100644 --- a/grafana-plugin/src/containers/AlertRules/AlertRules.tsx +++ b/grafana-plugin/src/containers/AlertRules/AlertRules.tsx @@ -155,20 +155,103 @@ class AlertRules extends React.Component { <>
-
- - - Escalate -
{parseEmojis(alertReceiveChannel?.verbal_name || '')}
- +
+
+ +
+
{parseEmojis(alertReceiveChannel?.verbal_name || '')}
+ + + +
+
+
+ +
+
+ + + + +
+ {maintenanceMode === MaintenanceMode.Debug || maintenanceMode === MaintenanceMode.Maintenance ? ( + +
+
+
{editIntegrationName !== undefined && ( {
)} -
- - - - -
- {maintenanceMode === MaintenanceMode.Debug || maintenanceMode === MaintenanceMode.Maintenance ? ( - -
-
{alertReceiveChannel.description && ( diff --git a/grafana-plugin/src/containers/AlertRules/parts/index.tsx b/grafana-plugin/src/containers/AlertRules/parts/index.tsx index fdff1405..bcf2f0b4 100644 --- a/grafana-plugin/src/containers/AlertRules/parts/index.tsx +++ b/grafana-plugin/src/containers/AlertRules/parts/index.tsx @@ -26,7 +26,7 @@ export const ChatOpsConnectors = (props: ChatOpsConnectorsProps) => { } return ( - + {isSlackInstalled && } {isTelegramInstalled && } diff --git a/grafana-plugin/src/containers/EscalationChainSteps/EscalationChainSteps.tsx b/grafana-plugin/src/containers/EscalationChainSteps/EscalationChainSteps.tsx index f7bb9688..5b7cf07c 100644 --- a/grafana-plugin/src/containers/EscalationChainSteps/EscalationChainSteps.tsx +++ b/grafana-plugin/src/containers/EscalationChainSteps/EscalationChainSteps.tsx @@ -92,7 +92,10 @@ const EscalationChainSteps = observer((props: EscalationChainStepsProps) => { ) : ( )} - + onSelect(Number(value))} + options={getOptions()} + /> + + ); + + function getOptions() { + return silenceOptions.map((silenceOption: SelectOption) => ({ + value: silenceOption.value, + label: silenceOption.display_name, + })); + } +}); diff --git a/grafana-plugin/src/pages/schedule/Schedule.module.css b/grafana-plugin/src/pages/schedule/Schedule.module.css index 0d8937a1..53c468e0 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.module.css +++ b/grafana-plugin/src/pages/schedule/Schedule.module.css @@ -1,7 +1,4 @@ .root { - max-width: 1600px; - margin: 0 auto; - --rotations-border: var(--border-weak); --rotations-background: var(--background-secondary); } diff --git a/grafana-plugin/src/pages/schedules/Schedules.module.css b/grafana-plugin/src/pages/schedules/Schedules.module.css index bb044563..bd5b3bc9 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.module.css +++ b/grafana-plugin/src/pages/schedules/Schedules.module.css @@ -11,3 +11,26 @@ .root .buttons { padding-right: 10px; } + +.schedules__filters-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + row-gap: 4px; + column-gap: 8px; + width: 100%; +} + +.schedules__actions { + display: flex; + justify-content: flex-end; + flex-grow: 1; + gap: 8px; + padding-top: 19px; +} + +.schedules__user-on-call { + display: flex; + flex-wrap: nowrap; + gap: 4px; +} diff --git a/grafana-plugin/src/pages/schedules/Schedules.tsx b/grafana-plugin/src/pages/schedules/Schedules.tsx index 4d1f2e28..00c00dcc 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules/Schedules.tsx @@ -8,12 +8,13 @@ import { observer } from 'mobx-react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; import Avatar from 'components/Avatar/Avatar'; +import { MatchMediaTooltip } from 'components/MatchMediaTooltip/MatchMediaTooltip'; import NewScheduleSelector from 'components/NewScheduleSelector/NewScheduleSelector'; import PluginLink from 'components/PluginLink/PluginLink'; import ScheduleCounter from 'components/ScheduleCounter/ScheduleCounter'; import ScheduleWarning from 'components/ScheduleWarning/ScheduleWarning'; -import SchedulesFilters from 'components/SchedulesFilters_NEW/SchedulesFilters'; -import { SchedulesFiltersType } from 'components/SchedulesFilters_NEW/SchedulesFilters.types'; +import SchedulesFilters from 'components/SchedulesFilters/SchedulesFilters'; +import { SchedulesFiltersType } from 'components/SchedulesFilters/SchedulesFilters.types'; import Table from 'components/Table/Table'; import Text from 'components/Text/Text'; import TimelineMarks from 'components/TimelineMarks/TimelineMarks'; @@ -29,7 +30,7 @@ import { getStartOfWeek } from 'pages/schedule/Schedule.helpers'; import { WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; import { UserActions } from 'utils/authorization'; -import { PLUGIN_ROOT } from 'utils/consts'; +import { PLUGIN_ROOT, TABLE_COLUMN_MAX_WIDTH } from 'utils/consts'; import styles from './Schedules.module.css'; @@ -137,9 +138,9 @@ class SchedulesPage extends React.Component
- +
- +
{users && ( - - +
+
{ if (item.on_call_now?.length > 0) { return ( - - {item.on_call_now.map((user, _index) => { - return ( - -
- - {user.username} -
-
- ); - })} -
+
+ + {item.on_call_now.map((user) => { + return ( + +
+
+ +
+ + {user.username} + +
+
+ ); + })} +
+
); } return null; diff --git a/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx b/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx index aa36713f..9c0dbd3f 100644 --- a/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx +++ b/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx @@ -54,6 +54,7 @@ dayjs.extend(customParseFormat); import 'style/vars.css'; import 'style/global.css'; import 'style/utils.css'; +import 'style/responsive.css'; import { getQueryParams, isTopNavbar } from './GrafanaPluginRootPage.helpers'; import PluginSetup from './PluginSetup'; diff --git a/grafana-plugin/src/style/global.css b/grafana-plugin/src/style/global.css index fe3e65a2..85cf4a32 100644 --- a/grafana-plugin/src/style/global.css +++ b/grafana-plugin/src/style/global.css @@ -43,3 +43,8 @@ .page-title { margin-bottom: 16px; } + +.rc-table-cell { + padding-left: 4px; + padding-right: 4px; +} diff --git a/grafana-plugin/src/style/responsive.css b/grafana-plugin/src/style/responsive.css new file mode 100644 index 00000000..e57034a5 --- /dev/null +++ b/grafana-plugin/src/style/responsive.css @@ -0,0 +1,23 @@ +/* + Make sure if you chage max-width here + You also change it in consts.ts +*/ +@media screen and (max-width: 1500px) { + .table__email-column { + max-width: 175px; + } + + .table__email-content { + text-overflow: ellipsis; + overflow: hidden; + } + + .incident__title-column { + overflow-wrap: anywhere; + white-space: pre-wrap; + } +} + +.table__wrap-column { + word-break: break-word; +} diff --git a/grafana-plugin/src/style/utils.css b/grafana-plugin/src/style/utils.css index 314abf57..fd5afa40 100644 --- a/grafana-plugin/src/style/utils.css +++ b/grafana-plugin/src/style/utils.css @@ -26,6 +26,10 @@ height: 100%; } +.u-display-block { + display: block; +} + .u-flex { display: flex; flex-direction: row; @@ -43,3 +47,9 @@ .u-align-items-center { align-items: center; } + +.u-disabled { + opacity: var(--opacity); + cursor: not-allowed !important; + pointer-events: none; +} diff --git a/grafana-plugin/src/style/vars.css b/grafana-plugin/src/style/vars.css index 9e9b920c..dc153338 100644 --- a/grafana-plugin/src/style/vars.css +++ b/grafana-plugin/src/style/vars.css @@ -15,6 +15,13 @@ --gradient-brandVertical: linear-gradient(0.01deg, #f53e4c -31.2%, #f83 113.07%); --always-gray: #ccccdc; --title-marginBottom: 16px; + --opacity: 0.5; + + /* These seem to slightly differ from warning/success/error colors from below */ + --tag-danger: #e02f44; + --tag-warning: #c69b06; + --tag-primary: #299c46; + --tag-secondary: #464c54; } .theme-light { diff --git a/grafana-plugin/src/utils/consts.ts b/grafana-plugin/src/utils/consts.ts index f38d49ee..57e004ba 100644 --- a/grafana-plugin/src/utils/consts.ts +++ b/grafana-plugin/src/utils/consts.ts @@ -31,7 +31,5 @@ export const FARO_ENDPOINT_PROD = export const DOCS_SLACK_SETUP = 'https://grafana.com/docs/grafana-cloud/oncall/open-source/#slack-setup'; export const DOCS_TELEGRAM_SETUP = 'https://grafana.com/docs/grafana-cloud/oncall/chat-options/configure-telegram/'; -export const COLOR_DANGER = '#E02F44'; -export const COLOR_WARNING = '#C69B06'; -export const COLOR_PRIMARY = '#299C46'; -export const COLOR_SECONDARY = '#464C54'; +// Make sure if you chage max-width here you also change it in responsive.css +export const TABLE_COLUMN_MAX_WIDTH = 1500;