diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-style/Theme.stories.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-style/Theme.stories.tsx index 63ede4a8a..b46c9dfb8 100644 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-style/Theme.stories.tsx +++ b/superset-frontend/packages/superset-ui-demo/storybook/stories/superset-ui-style/Theme.stories.tsx @@ -25,23 +25,96 @@ export default { export const ThemeColors = () => { const { colors } = supersetTheme; - return Object.keys(colors).map(collection => ( + + // Define tones to be displayed in columns + const tones = [ + 'dark2', + 'dark1', + 'base', + 'light1', + 'light2', + 'light3', + 'light4', + 'light5', + ]; + const colorTypes = [ + 'primary', + 'secondary', + 'grayscale', + 'error', + 'warning', + 'alert', + 'success', + 'info', + ]; + return (
-

{collection}

- - {Object.keys(colors[collection]).map(k => { - const hex = colors[collection][k]; - return ( - - -
{k} - {hex} +

Theme Colors

+ + + + + {tones.map(tone => ( + + ))} + + + + {colorTypes.map(category => ( + + - + ); + })} - ); - })} + ))} +
+ Category + + {tone} +
+ {category} + {tones.map(tone => { + const color = colors[category][tone]; + return ( + + {color ? {color} : '-'} +
+

+ text.label: {colors.text.label} +

+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea + commodo consequat. +
+

+ text.help: {colors.text.help} +

+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea + commodo consequat. +
+

The supersetTheme object

+ +
{JSON.stringify(supersetTheme, null, 2)}
+
- )); + ); }; diff --git a/superset-frontend/src/components/AlteredSliceTag/index.tsx b/superset-frontend/src/components/AlteredSliceTag/index.tsx index bd16d1c28..9aca6b46b 100644 --- a/superset-frontend/src/components/AlteredSliceTag/index.tsx +++ b/superset-frontend/src/components/AlteredSliceTag/index.tsx @@ -19,9 +19,11 @@ import { useCallback, useEffect, useMemo, useState, FC } from 'react'; import { isEqual, isEmpty } from 'lodash'; -import { QueryFormData, styled, t } from '@superset-ui/core'; +import { QueryFormData, t } from '@superset-ui/core'; import { sanitizeFormData } from 'src/explore/exploreUtils/formData'; import getControlsForVizType from 'src/utils/getControlsForVizType'; +import Label from 'src/components/Label'; +import Icons from 'src/components/Icons'; import { safeStringify } from 'src/utils/safeStringify'; import { Tooltip } from 'src/components/Tooltip'; import ModalTrigger from '../ModalTrigger'; @@ -68,18 +70,6 @@ export type RowType = { control: string; }; -const StyledLabel = styled.span` - ${({ theme }) => ` - font-size: ${theme.typography.sizes.s}px; - color: ${theme.colors.grayscale.dark1}; - background-color: ${theme.colors.alert.base}; - - &:hover { - background-color: ${theme.colors.alert.dark1}; - } - `} -`; - export const alterForComparison = ( value?: string | null | [], ): string | null => { @@ -228,7 +218,14 @@ const AlteredSliceTag: FC = props => { const triggerNode = useMemo( () => ( - {t('Altered')} + ), [], diff --git a/superset-frontend/src/components/Icons/Icon.tsx b/superset-frontend/src/components/Icons/Icon.tsx index b2e7dbfdc..39e562723 100644 --- a/superset-frontend/src/components/Icons/Icon.tsx +++ b/superset-frontend/src/components/Icons/Icon.tsx @@ -36,6 +36,10 @@ const AntdIconComponent = ({ export const StyledIcon = styled(AntdIconComponent)` ${({ iconColor }) => iconColor && `color: ${iconColor};`}; + span { + // Fixing alignement on some of the icons + line-height: 0px; + } font-size: ${({ iconSize, theme }) => iconSize ? `${theme.typography.sizes[iconSize] || theme.typography.sizes.m}px` diff --git a/superset-frontend/src/components/Label/Label.stories.tsx b/superset-frontend/src/components/Label/Label.stories.tsx index 5c4618246..20225811d 100644 --- a/superset-frontend/src/components/Label/Label.stories.tsx +++ b/superset-frontend/src/components/Label/Label.stories.tsx @@ -17,14 +17,17 @@ * under the License. */ import { action } from '@storybook/addon-actions'; -import Label, { Type } from './index'; +import { Meta, StoryFn } from '@storybook/react'; +import Label, { Type, DatasetTypeLabel, PublishedLabel } from './index'; +// Define the default export with Storybook configuration export default { title: 'Label', component: Label, - excludeStories: 'options', -}; + excludeStories: ['options'], +} as Meta; +// Explicitly type the options array as an array of `Type` export const options: Type[] = [ 'default', 'alert', @@ -36,39 +39,60 @@ export const options: Type[] = [ 'secondary', ]; -export const LabelGallery = () => ( - <> -

Non-interactive

- {Object.values(options).map((opt: Type) => ( - - ))} -
-

Interactive

- {Object.values(options).map((opt: Type) => ( - - ))} - -); +// Define the props for the `LabelGallery` component +interface LabelGalleryProps { + hasOnClick?: boolean; + monospace?: boolean; +} + +// Use the `StoryFn` type for LabelGallery +export const LabelGallery: StoryFn = ( + props: LabelGalleryProps, +) => { + const onClick = props.hasOnClick ? action('clicked') : undefined; -export const InteractiveLabel = (args: any) => { - const { hasOnClick, label, monospace, ...rest } = args; return ( - + <> +

Non-interactive

+ {options.map((opt: Type) => ( + + ))} +
+

Interactive

+ {options.map((opt: Type) => ( + + ))} +

Reusable Labels

+
DatasetType
+
+ + +
+
PublishedLabel
+ + + ); }; -InteractiveLabel.args = { +// Define default arguments for Storybook +LabelGallery.args = { hasOnClick: true, - label: 'Example', - monospace: true, + monospace: false, +}; + +// Define argument types for Storybook controls +LabelGallery.argTypes = { + monospace: { + name: 'monospace', + control: { type: 'boolean' }, + }, + hasOnClick: { + name: 'hasOnClick', + control: { type: 'boolean' }, + }, }; diff --git a/superset-frontend/src/components/Label/Label.test.tsx b/superset-frontend/src/components/Label/Label.test.tsx index fdf85b9c9..ac57250bc 100644 --- a/superset-frontend/src/components/Label/Label.test.tsx +++ b/superset-frontend/src/components/Label/Label.test.tsx @@ -37,7 +37,9 @@ test('works with an onClick handler', () => { // test stories from the storybook! test('renders all the storybook gallery variants', () => { const { container } = render(); + const nonInteractiveLabelCount = 4; + const renderedLabelCount = options.length * 2 + nonInteractiveLabelCount; expect(container.querySelectorAll('.ant-tag')).toHaveLength( - options.length * 2, + renderedLabelCount, ); }); diff --git a/superset-frontend/src/components/Label/index.tsx b/superset-frontend/src/components/Label/index.tsx index d904a445b..b74508407 100644 --- a/superset-frontend/src/components/Label/index.tsx +++ b/superset-frontend/src/components/Label/index.tsx @@ -25,6 +25,8 @@ import { import { Tag } from 'src/components'; import { useTheme } from '@superset-ui/core'; +import DatasetTypeLabel from 'src/components/Label/reusable/DatasetTypeLabel'; +import PublishedLabel from 'src/components/Label/reusable/PublishedLabel'; export type OnClickHandler = MouseEventHandler; @@ -47,6 +49,7 @@ export interface LabelProps extends HTMLAttributes { children?: ReactNode; role?: string; monospace?: boolean; + icon?: ReactNode; } export default function Label(props: LabelProps) { @@ -58,6 +61,7 @@ export default function Label(props: LabelProps) { style, onClick, children, + icon, ...rest } = props; const { @@ -71,37 +75,44 @@ export default function Label(props: LabelProps) { info, } = colors; - let backgroundColor = grayscale.light3; - let backgroundColorHover = onClick ? primary.light2 : grayscale.light3; - let borderColor = onClick ? grayscale.light2 : 'transparent'; - let borderColorHover = onClick ? primary.light1 : 'transparent'; - let color = grayscale.dark1; - - if (type !== 'default') { - color = grayscale.light4; - - let baseColor; - if (type === 'alert') { - color = grayscale.dark1; - baseColor = alert; - } else if (type === 'success') { - baseColor = success; - } else if (type === 'warning') { - baseColor = warning; - } else if (type === 'danger') { - baseColor = error; - } else if (type === 'info') { - baseColor = info; - } else if (type === 'secondary') { - baseColor = secondary; - } else { - baseColor = primary; - } - backgroundColor = baseColor.base; - backgroundColorHover = onClick ? baseColor.dark1 : baseColor.base; - borderColor = onClick ? baseColor.dark1 : 'transparent'; - borderColorHover = onClick ? baseColor.dark2 : 'transparent'; + let baseColor; + if (type === 'primary') { + baseColor = primary; + } else if (type === 'secondary') { + baseColor = secondary; + } else if (type === 'success') { + baseColor = success; + } else if (type === 'alert') { + baseColor = alert; + } else if (type === 'warning') { + baseColor = warning; + } else if (type === 'danger') { + baseColor = error; + } else if (type === 'info') { + baseColor = info; + } else { + baseColor = grayscale; } + const color = baseColor.dark2; + let borderColor = baseColor.light1; + let backgroundColor = baseColor.light2; + + // TODO - REMOVE IF BLOCK LOGIC WHEN shades are fixed to be aligned in terms of brightness + // currently shades for >=light2 are not aligned for primary, default and secondary + if (['default', 'primary', 'secondary'].includes(type)) { + // @ts-ignore + backgroundColor = baseColor.light4; + borderColor = baseColor.light2; + } + + const backgroundColorHover = onClick ? baseColor.light1 : backgroundColor; + const borderColorHover = onClick ? baseColor.base : borderColor; + + if (type === 'default') { + // Lighter for default + backgroundColor = grayscale.light3; + } + const css = { transition: `background-color ${transitionTiming}s`, whiteSpace: 'nowrap', @@ -109,11 +120,14 @@ export default function Label(props: LabelProps) { overflow: 'hidden', textOverflow: 'ellipsis', backgroundColor, + borderRadius: 8, borderColor, - borderRadius: 21, padding: '0.35em 0.8em', lineHeight: 1, color, + display: 'inline-flex', + verticalAlign: 'middle', + alignItems: 'center', maxWidth: '100%', '&:hover': { backgroundColor: backgroundColorHover, @@ -124,12 +138,12 @@ export default function Label(props: LabelProps) { if (monospace) { css['font-family'] = theme.typography.families.monospace; } - return ( @@ -137,3 +151,4 @@ export default function Label(props: LabelProps) { ); } +export { DatasetTypeLabel, PublishedLabel }; diff --git a/superset-frontend/src/components/Label/reusable/DatasetTypeLabel.tsx b/superset-frontend/src/components/Label/reusable/DatasetTypeLabel.tsx new file mode 100644 index 000000000..737966558 --- /dev/null +++ b/superset-frontend/src/components/Label/reusable/DatasetTypeLabel.tsx @@ -0,0 +1,49 @@ +/** + * 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 Icons from 'src/components/Icons'; +import Label from 'src/components/Label'; +import { t } from '@superset-ui/core'; + +// Define the prop types for DatasetTypeLabel +interface DatasetTypeLabelProps { + datasetType: 'physical' | 'virtual'; // Accepts only 'physical' or 'virtual' +} + +const SIZE = 's'; // Define the size as a constant + +const DatasetTypeLabel: React.FC = ({ datasetType }) => { + const label: string = + datasetType === 'physical' ? t('Physical') : t('Virtual'); + const icon = + datasetType === 'physical' ? ( + + ) : ( + + ); + const labelType: 'primary' | 'secondary' = + datasetType === 'physical' ? 'primary' : 'secondary'; + + return ( + + ); +}; + +export default DatasetTypeLabel; diff --git a/superset-frontend/src/components/Label/reusable/PublishedLabel.tsx b/superset-frontend/src/components/Label/reusable/PublishedLabel.tsx new file mode 100644 index 000000000..a73be8609 --- /dev/null +++ b/superset-frontend/src/components/Label/reusable/PublishedLabel.tsx @@ -0,0 +1,48 @@ +/** + * 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 Icons from 'src/components/Icons'; +import Label from 'src/components/Label'; +import { t } from '@superset-ui/core'; + +// Define props for the PublishedLabel component +interface PublishedLabelProps { + isPublished: boolean; // Whether the item is published + onClick?: () => void; // Optional click handler +} + +const PublishedLabel: React.FC = ({ + isPublished, + onClick, +}) => { + const label = isPublished ? t('Published') : t('Draft'); + const icon = isPublished ? ( + + ) : ( + + ); + const labelType = isPublished ? 'primary' : 'secondary'; + + return ( + + ); +}; + +export default PublishedLabel; diff --git a/superset-frontend/src/components/Timer/index.tsx b/superset-frontend/src/components/Timer/index.tsx index 4237c9f1e..f0ae3cf05 100644 --- a/superset-frontend/src/components/Timer/index.tsx +++ b/superset-frontend/src/components/Timer/index.tsx @@ -19,6 +19,7 @@ import { useEffect, useRef, useState } from 'react'; import { styled } from '@superset-ui/core'; import Label, { Type } from 'src/components/Label'; +import Icons from 'src/components/Icons'; import { now, fDuration } from 'src/utils/dates'; @@ -68,7 +69,7 @@ export default function Timer({ }, [endTime, isRunning, startTime]); return ( - + } type={status} role="timer"> {clockStr} ); diff --git a/superset-frontend/src/dashboard/components/PublishedStatus/index.tsx b/superset-frontend/src/dashboard/components/PublishedStatus/index.tsx index 3fdb0de11..29fb30484 100644 --- a/superset-frontend/src/dashboard/components/PublishedStatus/index.tsx +++ b/superset-frontend/src/dashboard/components/PublishedStatus/index.tsx @@ -19,7 +19,7 @@ import { Component } from 'react'; import { t } from '@superset-ui/core'; import { Tooltip } from 'src/components/Tooltip'; -import Label from 'src/components/Label'; +import { PublishedLabel } from 'src/components/Label'; import { HeaderProps, HeaderDropdownProps } from '../Header/types'; export type DashboardPublishedStatusType = { @@ -67,13 +67,12 @@ export default class PublishedStatus extends Component - +
+ +
); } @@ -83,7 +82,9 @@ export default class PublishedStatus extends Component - +
+ +
); } @@ -96,13 +97,12 @@ export default class PublishedStatus extends Component - +
+ +
); } diff --git a/superset-frontend/src/pages/DashboardList/index.tsx b/superset-frontend/src/pages/DashboardList/index.tsx index 361168f41..93d290408 100644 --- a/superset-frontend/src/pages/DashboardList/index.tsx +++ b/superset-frontend/src/pages/DashboardList/index.tsx @@ -34,6 +34,7 @@ import { } from 'src/views/CRUD/utils'; import { useListViewResource, useFavoriteStatus } from 'src/views/CRUD/hooks'; import ConfirmStatusChange from 'src/components/ConfirmStatusChange'; +import { PublishedLabel } from 'src/components/Label'; import { TagsList } from 'src/components/Tags'; import handleResourceExport from 'src/utils/export'; import Loading from 'src/components/Loading'; @@ -343,8 +344,9 @@ function DashboardList(props: DashboardListProps) { row: { original: { status }, }, - }: any) => - status === DashboardStatus.PUBLISHED ? t('Published') : t('Draft'), + }: any) => ( + + ), Header: t('Status'), accessor: 'published', size: 'xl', diff --git a/superset-frontend/src/pages/DatasetList/index.tsx b/superset-frontend/src/pages/DatasetList/index.tsx index d90f60c59..49af61753 100644 --- a/superset-frontend/src/pages/DatasetList/index.tsx +++ b/superset-frontend/src/pages/DatasetList/index.tsx @@ -41,6 +41,7 @@ import ListView, { Filters, FilterOperator, } from 'src/components/ListView'; +import { DatasetTypeLabel } from 'src/components/Label'; import Loading from 'src/components/Loading'; import SubMenu, { SubMenuProps, ButtonProps } from 'src/features/home/SubMenu'; import Owner from 'src/types/Owner'; @@ -279,24 +280,7 @@ const DatasetList: FunctionComponent = ({ row: { original: { kind }, }, - }: any) => { - if (kind === 'physical') { - return ( - - - - ); - } - - return ( - - - - ); - }, + }: any) => null, accessor: 'kind_icon', disableSortBy: true, size: 'xs', @@ -360,7 +344,7 @@ const DatasetList: FunctionComponent = ({ row: { original: { kind }, }, - }: any) => (kind === 'physical' ? t('Physical') : t('Virtual')), + }: any) => , Header: t('Type'), accessor: 'kind', disableSortBy: true,