diff --git a/packages/grafana-data/src/index.ts b/packages/grafana-data/src/index.ts
index 456665e4f59..b06748151fe 100644
--- a/packages/grafana-data/src/index.ts
+++ b/packages/grafana-data/src/index.ts
@@ -19,6 +19,7 @@ export {
BasicValueMatcherOptions,
RangeValueMatcherOptions,
} from './transformations/matchers/valueMatchers/types';
+export { LayoutModes, LayoutMode } from './types/layout';
export { PanelPlugin, SetFieldConfigOptionsArgs, StandardOptionConfig } from './panel/PanelPlugin';
export { createFieldConfigRegistry } from './panel/registryFactories';
export { QueryRunner, QueryRunnerOptions } from './types/queryRunner';
diff --git a/packages/grafana-data/src/types/layout.ts b/packages/grafana-data/src/types/layout.ts
new file mode 100644
index 00000000000..4249dc34e60
--- /dev/null
+++ b/packages/grafana-data/src/types/layout.ts
@@ -0,0 +1,6 @@
+export type LayoutMode = LayoutModes.Grid | LayoutModes.List;
+
+export enum LayoutModes {
+ Grid = 'grid',
+ List = 'list',
+}
diff --git a/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx b/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx
index e6209e27fe7..9a04f9fb568 100644
--- a/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx
+++ b/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx
@@ -2,11 +2,13 @@ import React from 'react';
import { Global } from '@emotion/react';
import { useTheme2 } from '..';
import { getElementStyles } from './elements';
+import { getCardStyles } from './card';
/** @internal */
export function GlobalStyles() {
const theme = useTheme2();
const types = getElementStyles(theme);
+ const cards = getCardStyles(theme);
- return ;
+ return ;
}
diff --git a/packages/grafana-ui/src/themes/GlobalStyles/card.tsx b/packages/grafana-ui/src/themes/GlobalStyles/card.tsx
new file mode 100644
index 00000000000..74ce61c1ee6
--- /dev/null
+++ b/packages/grafana-ui/src/themes/GlobalStyles/card.tsx
@@ -0,0 +1,187 @@
+import { css } from '@emotion/react';
+import { GrafanaThemeV2 } from '@grafana/data';
+export function getCardStyles(theme: GrafanaThemeV2) {
+ return css`
+ .card-section {
+ margin-bottom: ${theme.spacing(4)};
+ }
+
+ .card-list {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ list-style-type: none;
+ }
+
+ .card-item {
+ display: block;
+ height: 100%;
+ background: ${theme.colors.background.secondary};
+ box-shadow: none;
+ padding: ${theme.spacing(2)};
+ border-radius: 4px;
+
+ &:hover {
+ background: ${theme.colors.emphasize(theme.colors.background.secondary, 0.03)};
+ }
+
+ .label-tag {
+ margin-left: ${theme.spacing(1)};
+ font-size: 11px;
+ padding: 2px 6px;
+ }
+ }
+
+ .card-item-body {
+ display: flex;
+ overflow: hidden;
+ }
+
+ .card-item-details {
+ overflow: hidden;
+ }
+
+ .card-item-header {
+ margin-bottom: ${theme.spacing(2)};
+ }
+
+ .card-item-type {
+ color: ${theme.colors.text.secondary};
+ text-transform: uppercase;
+ font-size: ${theme.typography.size.sm};
+ font-weight: ${theme.typography.fontWeightMedium};
+ }
+
+ .card-item-badge {
+ margin: 6px 0;
+ }
+
+ .card-item-notice {
+ font-size: ${theme.typography.size.sm};
+ }
+
+ .card-item-name {
+ color: ${theme.colors.text.primary};
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 100%;
+ }
+
+ .card-item-label {
+ margin-left: ${theme.spacing(1)};
+ }
+
+ .card-item-sub-name {
+ color: ${theme.colors.text.secondary};
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 100%;
+ }
+
+ .card-item-sub-name--header {
+ color: ${theme.colors.text.secondary};
+ text-transform: uppercase;
+ margin-bottom: ${theme.spacing(2)};
+ font-size: ${theme.typography.size.sm};
+ font-weight: bold;
+ }
+
+ .card-list-layout-grid {
+ .card-item-type {
+ display: inline-block;
+ }
+
+ .card-item-notice {
+ font-size: ${theme.typography.size.sm};
+ display: inline-block;
+ margin-left: ${theme.spacing(2)};
+ }
+
+ .card-item-header-action {
+ float: right;
+ }
+
+ .card-item-wrapper {
+ width: 100%;
+ padding: ${theme.spacing(0, 2, 2, 0)};
+ }
+
+ .card-item-wrapper--clickable {
+ cursor: pointer;
+ }
+
+ .card-item-figure {
+ margin: ${theme.spacing(0, 2, 2, 0)}0;
+ height: 80px;
+
+ img {
+ width: 80px;
+ }
+ }
+
+ .card-item-name {
+ font-size: ${theme.typography.h3.fontSize};
+ }
+
+ ${theme.breakpoints.up('md')} {
+ .card-item-wrapper {
+ width: 50%;
+ }
+ }
+
+ ${theme.breakpoints.up('lg')} {
+ .card-item-wrapper {
+ width: 33.333333%;
+ }
+ }
+
+ &.card-list-layout-grid--max-2-col {
+ ${theme.breakpoints.up('lg')} {
+ .card-item-wrapper {
+ width: 50%;
+ }
+ }
+ }
+ }
+
+ .card-list-layout-list {
+ .card-item-wrapper {
+ padding: 0;
+ width: 100%;
+ margin-bottom: ${theme.spacing(4)};
+ }
+
+ .card-item-wrapper--clickable {
+ cursor: pointer;
+ }
+
+ .card-item {
+ border-radius: 2px;
+ }
+
+ .card-item-header {
+ float: right;
+ text-align: right;
+ }
+
+ .card-item-figure {
+ margin: ${theme.spacing(0, 2, 0, 0)};
+ img {
+ width: 48px;
+ }
+ }
+
+ .card-item-name {
+ font-size: ${theme.typography.h4.fontSize};
+ }
+
+ .card-item-sub-name {
+ font-size: ${theme.typography.size.sm};
+ }
+
+ .layout-selector {
+ margin-right: 0;
+ }
+ }
+ `;
+}
diff --git a/public/app/core/components/LayoutSelector/LayoutSelector.tsx b/public/app/core/components/LayoutSelector/LayoutSelector.tsx
deleted file mode 100644
index 2633ef8838d..00000000000
--- a/public/app/core/components/LayoutSelector/LayoutSelector.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import React, { FC } from 'react';
-import { RadioButtonGroup } from '@grafana/ui';
-
-export type LayoutMode = LayoutModes.Grid | LayoutModes.List;
-
-export enum LayoutModes {
- Grid = 'grid',
- List = 'list',
-}
-
-interface Props {
- mode: LayoutMode;
- onLayoutModeChanged: (mode: LayoutMode) => {};
-}
-
-const options = [
- { icon: 'table', value: LayoutModes.Grid },
- { icon: 'list-ul', value: LayoutModes.List },
-];
-
-const LayoutSelector: FC = ({ mode, onLayoutModeChanged }) => (
-
-
-
-);
-
-export default LayoutSelector;
diff --git a/public/app/features/datasources/DataSourceList.test.tsx b/public/app/features/datasources/DataSourceList.test.tsx
index 0cbe041b949..c6752e996d1 100644
--- a/public/app/features/datasources/DataSourceList.test.tsx
+++ b/public/app/features/datasources/DataSourceList.test.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { render, screen } from '@testing-library/react';
import DataSourcesList from './DataSourcesList';
import { getMockDataSources } from './__mocks__/dataSourcesMocks';
-import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
+import { LayoutModes } from '@grafana/data';
const setup = () => {
const props = {
diff --git a/public/app/features/datasources/DataSourcesList.tsx b/public/app/features/datasources/DataSourcesList.tsx
index ee37265c022..c7c53538d36 100644
--- a/public/app/features/datasources/DataSourcesList.tsx
+++ b/public/app/features/datasources/DataSourcesList.tsx
@@ -2,8 +2,7 @@
import React, { FC } from 'react';
// Types
-import { DataSourceSettings } from '@grafana/data';
-import { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
+import { DataSourceSettings, LayoutMode } from '@grafana/data';
import { Card, Tag, useStyles } from '@grafana/ui';
import { css } from '@emotion/css';
diff --git a/public/app/features/datasources/DataSourcesListPage.test.tsx b/public/app/features/datasources/DataSourcesListPage.test.tsx
index e6735cd9816..bff292de3f3 100644
--- a/public/app/features/datasources/DataSourcesListPage.test.tsx
+++ b/public/app/features/datasources/DataSourcesListPage.test.tsx
@@ -1,9 +1,8 @@
import React from 'react';
import { shallow } from 'enzyme';
-import { DataSourceSettings, NavModel } from '@grafana/data';
+import { DataSourceSettings, NavModel, LayoutModes } from '@grafana/data';
import { DataSourcesListPage, Props } from './DataSourcesListPage';
-import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
import { getMockDataSources } from './__mocks__/dataSourcesMocks';
import { setDataSourcesLayoutMode, setDataSourcesSearchQuery } from './state/reducers';
diff --git a/public/app/features/datasources/DataSourcesListPage.tsx b/public/app/features/datasources/DataSourcesListPage.tsx
index 1a9d88e0e50..77ccb062f6d 100644
--- a/public/app/features/datasources/DataSourcesListPage.tsx
+++ b/public/app/features/datasources/DataSourcesListPage.tsx
@@ -8,10 +8,9 @@ import PageActionBar from 'app/core/components/PageActionBar/PageActionBar';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import DataSourcesList from './DataSourcesList';
// Types
-import { DataSourceSettings, NavModel } from '@grafana/data';
+import { DataSourceSettings, NavModel, LayoutMode } from '@grafana/data';
import { IconName } from '@grafana/ui';
import { StoreState } from 'app/types';
-import { LayoutMode } from 'app/core/components/LayoutSelector/LayoutSelector';
// Actions
import { loadDataSources } from './state/actions';
import { getNavModel } from 'app/core/selectors/navModel';
diff --git a/public/app/features/datasources/state/reducers.test.ts b/public/app/features/datasources/state/reducers.test.ts
index 3f8094a8aa7..8cc844d86e6 100644
--- a/public/app/features/datasources/state/reducers.test.ts
+++ b/public/app/features/datasources/state/reducers.test.ts
@@ -18,9 +18,8 @@ import {
setIsDefault,
} from './reducers';
import { getMockDataSource, getMockDataSources } from '../__mocks__/dataSourcesMocks';
-import { LayoutModes } from 'app/core/components/LayoutSelector/LayoutSelector';
import { DataSourceSettingsState, DataSourcesState } from 'app/types';
-import { PluginMeta, PluginMetaInfo, PluginType } from '@grafana/data';
+import { PluginMeta, PluginMetaInfo, PluginType, LayoutModes } from '@grafana/data';
import { GenericDataSourcePlugin } from '../settings/PluginSettings';
const mockPlugin = () =>
diff --git a/public/app/features/datasources/state/reducers.ts b/public/app/features/datasources/state/reducers.ts
index 0a7c6277cb4..44fe7998bee 100644
--- a/public/app/features/datasources/state/reducers.ts
+++ b/public/app/features/datasources/state/reducers.ts
@@ -1,8 +1,7 @@
import { AnyAction, createAction } from '@reduxjs/toolkit';
-import { DataSourcePluginMeta, DataSourceSettings } from '@grafana/data';
+import { DataSourcePluginMeta, DataSourceSettings, LayoutMode, LayoutModes } from '@grafana/data';
import { DataSourcesState, DataSourceSettingsState, TestingStatus } from 'app/types';
-import { LayoutMode, LayoutModes } from 'app/core/components/LayoutSelector/LayoutSelector';
import { DataSourceTypesLoadedPayload } from './actions';
import { GenericDataSourcePlugin } from '../settings/PluginSettings';
diff --git a/public/app/features/plugins/PluginList.test.tsx b/public/app/features/plugins/PluginList.test.tsx
index 201dd69b9db..2f145706cd7 100644
--- a/public/app/features/plugins/PluginList.test.tsx
+++ b/public/app/features/plugins/PluginList.test.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import PluginList from './PluginList';
import { getMockPlugins } from './__mocks__/pluginMocks';
-import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
+import { LayoutModes } from '@grafana/data';
const setup = (propOverrides?: object) => {
const props = Object.assign(
diff --git a/public/app/types/datasources.ts b/public/app/types/datasources.ts
index a434cf657b1..589624a1f7d 100644
--- a/public/app/types/datasources.ts
+++ b/public/app/types/datasources.ts
@@ -1,5 +1,4 @@
-import { LayoutMode } from '../core/components/LayoutSelector/LayoutSelector';
-import { DataSourcePluginMeta, DataSourceSettings } from '@grafana/data';
+import { DataSourcePluginMeta, DataSourceSettings, LayoutMode } from '@grafana/data';
import { GenericDataSourcePlugin } from 'app/features/datasources/settings/PluginSettings';
import { HealthCheckResultDetails } from '@grafana/runtime/src/utils/DataSourceWithBackend';
diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss
index 392310d631c..1c7ee51d8b7 100644
--- a/public/sass/_grafana.scss
+++ b/public/sass/_grafana.scss
@@ -34,7 +34,6 @@
// COMPONENTS
@import '../app/features/dashboard/components/AddPanelWidget/AddPanelWidget';
@import 'components/scrollbar';
-@import 'components/cards';
@import 'components/buttons';
@import 'components/navs';
@import 'components/tabs';
diff --git a/public/sass/components/_cards.scss b/public/sass/components/_cards.scss
deleted file mode 100644
index 44c56ccc4e7..00000000000
--- a/public/sass/components/_cards.scss
+++ /dev/null
@@ -1,217 +0,0 @@
-.layout-selector {
- @include clearfix();
-
- margin-left: $space-md;
- text-align: right;
-
- button {
- background: $input-label-bg;
- color: $text-color-weak;
- box-shadow: $card-shadow;
- border: none;
- padding: $space-sm;
- line-height: 1;
- font-size: 130%;
- float: right;
-
- &:focus {
- outline: none;
- }
-
- &.active {
- background-color: lighten($input-label-bg, 5%);
- color: $link-color;
- }
-
- &:nth-child(2) {
- border-radius: 3px 0 0 3px;
- border-right: $panel-border;
- }
-
- &:nth-child(1) {
- border-radius: 0 3px 3px 0;
- }
- }
-}
-
-.card-section {
- margin-bottom: $space-xl;
-}
-
-.card-list {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- list-style-type: none;
-}
-
-.card-item {
- display: block;
- height: 100%;
- background: $card-background;
- box-shadow: $card-shadow;
- padding: $space-md;
- border-radius: 4px;
-
- &:hover {
- background: $card-background-hover;
- }
-
- .label-tag {
- margin-left: $space-sm;
- font-size: 11px;
- padding: 2px 6px;
- }
-}
-
-.card-item-body {
- display: flex;
- overflow: hidden;
-}
-
-.card-item-details {
- overflow: hidden;
-}
-
-.card-item-header {
- margin-bottom: $space-md;
-}
-
-.card-item-type {
- color: $text-color-weak;
- text-transform: uppercase;
- font-size: $font-size-sm;
- font-weight: $font-weight-semi-bold;
-}
-
-.card-item-badge {
- margin: 6px 0;
-}
-
-.card-item-notice {
- font-size: $font-size-sm;
-}
-
-.card-item-name {
- color: $headings-color;
- overflow: hidden;
- text-overflow: ellipsis;
- width: 100%;
-}
-
-.card-item-label {
- margin-left: $space-sm;
-}
-
-.card-item-sub-name {
- color: $text-color-weak;
- overflow: hidden;
- text-overflow: ellipsis;
- width: 100%;
-}
-
-.card-item-sub-name--header {
- color: $text-color-weak;
- text-transform: uppercase;
- margin-bottom: $space-md;
- font-size: $font-size-sm;
- font-weight: bold;
-}
-
-.card-list-layout-grid {
- .card-item-type {
- display: inline-block;
- }
-
- .card-item-notice {
- font-size: $font-size-sm;
- display: inline-block;
- margin-left: $space-md;
- }
-
- .card-item-header-action {
- float: right;
- }
-
- .card-item-wrapper {
- width: 100%;
- padding: 0 $space-md $space-md 0;
- }
-
- .card-item-wrapper--clickable {
- cursor: pointer;
- }
-
- .card-item-figure {
- margin: 0 $space-md $space-md 0;
- height: 80px;
-
- img {
- width: 80px;
- }
- }
-
- .card-item-name {
- font-size: $font-size-h3;
- }
-
- @include media-breakpoint-up(md) {
- .card-item-wrapper {
- width: 50%;
- }
- }
-
- @include media-breakpoint-up(lg) {
- .card-item-wrapper {
- width: 33.333333%;
- }
- }
-
- &.card-list-layout-grid--max-2-col {
- @include media-breakpoint-up(lg) {
- .card-item-wrapper {
- width: 50%;
- }
- }
- }
-}
-
-.card-list-layout-list {
- .card-item-wrapper {
- padding: 0;
- width: 100%;
- margin-bottom: $space-xs;
- }
-
- .card-item-wrapper--clickable {
- cursor: pointer;
- }
-
- .card-item {
- border-radius: 2px;
- }
-
- .card-item-header {
- float: right;
- text-align: right;
- }
-
- .card-item-figure {
- margin: 0 $space-md 0 0;
- img {
- width: 48px;
- }
- }
-
- .card-item-name {
- font-size: $font-size-h4;
- }
-
- .card-item-sub-name {
- font-size: $font-size-sm;
- }
-
- .layout-selector {
- margin-right: 0;
- }
-}