diff --git a/superset-frontend/packages/superset-core/src/theme/types.ts b/superset-frontend/packages/superset-core/src/theme/types.ts index e0e782d64972..6f6d61917aa2 100644 --- a/superset-frontend/packages/superset-core/src/theme/types.ts +++ b/superset-frontend/packages/superset-core/src/theme/types.ts @@ -237,6 +237,17 @@ export interface SupersetSpecificTokens { * Fallback: transparent */ buttonSecondaryActiveBorderColor?: string; + + // Component flexibility tokens (sizing, radius, behavior) + selectOptionActiveOutline?: boolean; + labelBorderRadius?: number; + buttonControlHeight?: number; + buttonControlHeightSM?: number; + buttonControlHeightXS?: number; + buttonPaddingInline?: number; + buttonPaddingInlineSM?: number; + buttonFontSize?: number; + buttonBorderRadius?: number; } /** diff --git a/superset-frontend/packages/superset-ui-core/src/components/Button/Button.stories.tsx b/superset-frontend/packages/superset-ui-core/src/components/Button/Button.stories.tsx index 808a16f45b10..21b8167c057f 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/Button/Button.stories.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/Button/Button.stories.tsx @@ -125,6 +125,11 @@ InteractiveButton.argTypes = { options: buttonSizes, control: { type: 'select' }, }, + styleConfig: { + description: + 'Optional visual overrides (controlHeight, paddingInline, fontSize, fontWeight, ctaMinWidth, ctaMinHeight, iconGap).', + control: { type: 'object' }, + }, target: { name: TARGETS.label, control: { type: 'select' }, diff --git a/superset-frontend/packages/superset-ui-core/src/components/Button/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/Button/index.tsx index 78f525e5a7ce..82f093d3d6c6 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/Button/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/Button/index.tsx @@ -25,6 +25,7 @@ import type { SupersetTheme } from '@apache-superset/core/theme'; import type { ButtonColorType, ButtonProps, + ButtonStyleConfig, ButtonStyle, ButtonType, ButtonVariantType, @@ -84,14 +85,13 @@ export const getSecondaryButtonHoverStyles = (theme: SupersetTheme) => ({ }, }); -const BUTTON_STYLE_MAP: Record< - ButtonStyle, - { - type?: ButtonType; - variant?: ButtonVariantType; - color?: ButtonColorType; - } -> = { +type ButtonStyleMapping = { + type?: ButtonType; + variant?: ButtonVariantType; + color?: ButtonColorType; +}; + +const BUTTON_STYLE_MAP: Record = { primary: { type: 'primary', variant: 'solid', color: 'primary' }, secondary: { variant: 'filled', color: 'primary' }, tertiary: { variant: 'outlined', color: 'default' }, @@ -113,30 +113,59 @@ export function Button(props: ButtonProps) { href, showMarginRight = true, icon, + styleConfig, ...restProps } = props; const theme = useTheme(); - const { fontSizeSM, fontWeightStrong } = theme; + const { fontWeightStrong } = theme; + const btnFontSize = theme.buttonFontSize ?? theme.fontSizeSM; + + const themeExt = theme as typeof theme & { + buttonStyleMap?: Partial>>; + }; + const resolvedStyleMap: Record = + themeExt.buttonStyleMap + ? (Object.fromEntries( + Object.entries(BUTTON_STYLE_MAP).map(([key, value]) => [ + key, + { ...value, ...themeExt.buttonStyleMap?.[key as ButtonStyle] }, + ]), + ) as Record) + : BUTTON_STYLE_MAP; - let height = 32; - let padding = 18; + let defaultHeight = theme.buttonControlHeight ?? 32; + let defaultPaddingInline = theme.buttonPaddingInline ?? 18; if (buttonSize === 'xsmall') { - height = 22; - padding = 5; + defaultHeight = theme.buttonControlHeightXS ?? 22; + defaultPaddingInline = 5; } else if (buttonSize === 'small') { - height = 30; - padding = 10; + defaultHeight = theme.buttonControlHeightSM ?? 30; + defaultPaddingInline = theme.buttonPaddingInlineSM ?? 10; } if (buttonStyle === 'link') { - padding = 4; + defaultPaddingInline = 4; } + const resolvedStyleConfig: Required = { + controlHeight: styleConfig?.controlHeight ?? defaultHeight, + paddingInline: styleConfig?.paddingInline ?? defaultPaddingInline, + fontSize: styleConfig?.fontSize ?? btnFontSize, + fontWeight: styleConfig?.fontWeight ?? fontWeightStrong, + ctaMinWidth: styleConfig?.ctaMinWidth ?? theme.sizeUnit * 36, + ctaMinHeight: styleConfig?.ctaMinHeight ?? theme.sizeUnit * 8, + iconGap: styleConfig?.iconGap ?? theme.sizeUnit * 2, + borderRadius: + styleConfig?.borderRadius ?? + theme.buttonBorderRadius ?? + theme.borderRadius, + }; + const { type: antdType = 'default', variant, color, - } = BUTTON_STYLE_MAP[buttonStyle ?? 'primary']; + } = resolvedStyleMap[buttonStyle ?? 'primary'] ?? BUTTON_STYLE_MAP.primary; const element = children as ReactElement; @@ -148,7 +177,9 @@ export function Button(props: ButtonProps) { renderedChildren = Children.toArray(children); } const firstChildMargin = - showMarginRight && renderedChildren.length > 1 ? theme.sizeUnit * 2 : 0; + showMarginRight && renderedChildren.length > 1 + ? resolvedStyleConfig.iconGap + : 0; const effectiveButtonStyle: ButtonStyle = buttonStyle ?? 'primary'; @@ -169,8 +200,6 @@ export function Button(props: ButtonProps) { className={cx( className, 'superset-button', - // A static class name containing the button style is available to - // support customizing button styles in embedded dashboards. `superset-button-${buttonStyle}`, { cta: !!cta }, )} @@ -179,12 +208,13 @@ export function Button(props: ButtonProps) { alignItems: 'center', justifyContent: 'center', lineHeight: 1, - fontSize: fontSizeSM, - fontWeight: fontWeightStrong, - height, - padding: `0px ${padding}px`, - minWidth: cta ? theme.sizeUnit * 36 : undefined, - minHeight: cta ? theme.sizeUnit * 8 : undefined, + fontSize: resolvedStyleConfig.fontSize, + fontWeight: resolvedStyleConfig.fontWeight, + height: resolvedStyleConfig.controlHeight, + padding: `0px ${resolvedStyleConfig.paddingInline}px`, + borderRadius: resolvedStyleConfig.borderRadius, + minWidth: cta ? resolvedStyleConfig.ctaMinWidth : undefined, + minHeight: cta ? resolvedStyleConfig.ctaMinHeight : undefined, marginLeft: 0, '& + .superset-button:not(.ant-btn-compact-item)': { marginLeft: theme.sizeUnit * 2, diff --git a/superset-frontend/packages/superset-ui-core/src/components/Button/types.ts b/superset-frontend/packages/superset-ui-core/src/components/Button/types.ts index d53e90e64e06..4bdee16d9c6b 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/Button/types.ts +++ b/superset-frontend/packages/superset-ui-core/src/components/Button/types.ts @@ -40,6 +40,17 @@ export type ButtonStyle = export type ButtonSize = 'default' | 'small' | 'xsmall'; +export type ButtonStyleConfig = { + controlHeight?: number; + paddingInline?: number; + fontSize?: number; + fontWeight?: number; + ctaMinWidth?: number; + ctaMinHeight?: number; + iconGap?: number; + borderRadius?: number; +}; + export type ButtonProps = Omit & { placement?: TooltipPlacement; tooltip?: ReactNode; @@ -49,4 +60,5 @@ export type ButtonProps = Omit & { cta?: boolean; showMarginRight?: boolean; icon?: ReactNode; + styleConfig?: ButtonStyleConfig; }; diff --git a/superset-frontend/packages/superset-ui-core/src/components/DropdownButton/DropdownButton.stories.tsx b/superset-frontend/packages/superset-ui-core/src/components/DropdownButton/DropdownButton.stories.tsx index 2ebb77f948fd..f0cd956a6ee7 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/DropdownButton/DropdownButton.stories.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/DropdownButton/DropdownButton.stories.tsx @@ -60,6 +60,11 @@ InteractiveDropdownButton.args = { }; InteractiveDropdownButton.argTypes = { + styleConfig: { + description: + 'Optional visual overrides (controlHeight, fontSize, fontWeight, boxShadow).', + control: { type: 'object' }, + }, placement: { defaultValue: 'top', control: { type: 'select' }, diff --git a/superset-frontend/packages/superset-ui-core/src/components/DropdownButton/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/DropdownButton/index.tsx index d9cdb5d46328..2b37f9da98c5 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/DropdownButton/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/DropdownButton/index.tsx @@ -27,6 +27,7 @@ export const DropdownButton = ({ tooltip, tooltipPlacement, children, + styleConfig, ...rest }: DropdownButtonProps) => { const theme = useTheme(); @@ -57,10 +58,10 @@ export const DropdownButton = ({ defaultBtnCss, css` .ant-btn { - height: 30px; - box-shadow: none; - font-size: ${theme.fontSizeSM}px; - font-weight: ${theme.fontWeightStrong}; + height: ${styleConfig?.controlHeight ?? 30}px; + box-shadow: ${styleConfig?.boxShadow ?? 'none'}; + font-size: ${styleConfig?.fontSize ?? theme.fontSizeSM}px; + font-weight: ${styleConfig?.fontWeight ?? theme.fontWeightStrong}; } `, ]} diff --git a/superset-frontend/packages/superset-ui-core/src/components/DropdownButton/types.ts b/superset-frontend/packages/superset-ui-core/src/components/DropdownButton/types.ts index 3789d677336f..fa31c4f1df07 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/DropdownButton/types.ts +++ b/superset-frontend/packages/superset-ui-core/src/components/DropdownButton/types.ts @@ -21,7 +21,15 @@ import { type ComponentProps } from 'react'; import { Dropdown } from 'antd'; import type { TooltipPlacement } from '../Tooltip/types'; +export type DropdownButtonStyleConfig = { + controlHeight?: number; + fontSize?: number; + fontWeight?: number; + boxShadow?: string; +}; + export type DropdownButtonProps = ComponentProps & { tooltip?: string; tooltipPlacement?: TooltipPlacement; + styleConfig?: DropdownButtonStyleConfig; }; diff --git a/superset-frontend/packages/superset-ui-core/src/components/Label/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/Label/index.tsx index 5e974758af3a..bfa4507abb31 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/Label/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/Label/index.tsx @@ -46,27 +46,30 @@ export function Label(props: LabelProps) { const borderColorHover = onClick ? baseColor.borderHover : borderColor; const labelStyles = css` - transition: background-color ${theme.motionDurationMid}; - white-space: nowrap; - cursor: ${onClick ? 'pointer' : 'default'}; - overflow: hidden; - text-overflow: ellipsis; - background-color: ${backgroundColor}; - border-radius: 8px; - border-color: ${borderColor}; - padding: 0.35em 0.8em; - line-height: 1; - color: ${color}; - display: inline-flex; - vertical-align: middle; - align-items: center; - max-width: 100%; - &:hover { - background-color: ${backgroundColorHover}; - border-color: ${borderColorHover}; - opacity: 1; + // NOTE: && bumps specificity to beat antd Tag defaults + && { + transition: background-color ${theme.motionDurationMid}; + white-space: nowrap; + cursor: ${onClick ? 'pointer' : 'default'}; + overflow: hidden; + text-overflow: ellipsis; + background-color: ${backgroundColor}; + border-radius: ${theme.labelBorderRadius ?? 8}px; + border-color: ${borderColor}; + padding: 0.35em 0.8em; + line-height: 1; + color: ${color}; + display: inline-flex; + vertical-align: middle; + align-items: center; + max-width: 100%; + &:hover { + background-color: ${backgroundColorHover}; + border-color: ${borderColorHover}; + opacity: 1; + } + ${monospace ? `font-family: ${theme.fontFamilyCode};` : ''} } - ${monospace ? `font-family: ${theme.fontFamilyCode};` : ''} `; return ( diff --git a/superset-frontend/packages/superset-ui-core/src/components/PageHeaderWithActions/index.tsx b/superset-frontend/packages/superset-ui-core/src/components/PageHeaderWithActions/index.tsx index a8dd727064de..d536f292bea4 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/PageHeaderWithActions/index.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/PageHeaderWithActions/index.tsx @@ -32,10 +32,11 @@ import { CertifiedBadge } from '../CertifiedBadge'; import { Button } from '../Button'; export const menuTriggerStyles = (theme: SupersetTheme) => css` - width: ${theme.sizeUnit * 8}px; - height: ${theme.sizeUnit * 8}px; + width: ${theme.buttonControlHeight ?? theme.sizeUnit * 8}px; + height: ${theme.buttonControlHeight ?? theme.sizeUnit * 8}px; padding: 0; border: 1px solid ${theme.colorPrimary}; + border-radius: ${theme.buttonBorderRadius ?? theme.borderRadius}px; &.ant-btn > span.anticon { line-height: 0; diff --git a/superset-frontend/packages/superset-ui-core/src/components/Select/styles.tsx b/superset-frontend/packages/superset-ui-core/src/components/Select/styles.tsx index 59c6606f6fde..5ed23be326f7 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/Select/styles.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/Select/styles.tsx @@ -45,10 +45,11 @@ export const StyledContainer = styled.div<{ headerPosition: string }>` export const StyledSelect = styled(Select, { shouldForwardProp: prop => prop !== 'headerPosition' && prop !== 'oneLine', })<{ headerPosition?: string; oneLine?: boolean }>` - ${({ theme, headerPosition, oneLine }) => ` + ${({ theme, headerPosition, oneLine }) => { + const useSubtleOptionHover = theme.selectOptionActiveOutline === false; + return ` .ant-select-item-option-active:not(.ant-select-item-option-disabled) { - outline: 2px solid ${theme.colorPrimary}; - outline-offset: -2px; + ${useSubtleOptionHover ? 'outline: none;' : `outline: 2px solid ${theme.colorPrimary}; outline-offset: -2px;`} } flex: ${headerPosition === 'left' ? 1 : 0}; line-height: ${theme.sizeXL}px; @@ -82,7 +83,8 @@ export const StyledSelect = styled(Select, { } ` }; - `} + `; + }} `; export const NoElement = styled.span` diff --git a/superset-frontend/src/components/ListView/CardCollection.tsx b/superset-frontend/src/components/ListView/CardCollection.tsx index d3e8999713a8..b77e9f475f4d 100644 --- a/superset-frontend/src/components/ListView/CardCollection.tsx +++ b/superset-frontend/src/components/ListView/CardCollection.tsx @@ -35,7 +35,7 @@ const CardContainer = styled.div<{ showThumbnails?: boolean }>` display: grid; justify-content: start; grid-gap: ${theme.sizeUnit * 12}px ${theme.sizeUnit * 4}px; - grid-template-columns: repeat(auto-fit, 300px); + grid-template-columns: repeat(auto-fit, ${theme.sizeUnit * 75}px); margin-top: ${theme.sizeUnit * -6}px; padding: ${ showThumbnails diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx index 3fbfc5d10a55..7a86448b9baf 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx @@ -321,6 +321,9 @@ const StyledDashboardContent = styled.div<{ padding: ${theme.sizeUnit * 4}px; box-sizing: border-box; overflow-y: visible; + border: 1px solid ${theme.colorBorder}; + border-radius: ${theme.borderRadius}px; + box-shadow: ${theme.boxShadowTertiary}; // transitionable traits to show filter relevance transition: @@ -337,7 +340,7 @@ const StyledDashboardContent = styled.div<{ &.fade-out { border-radius: ${theme.borderRadius}px; - box-shadow: 0 0 0 1px ${addAlpha(theme.colorBorder, 0.5)}; + box-shadow: ${theme.boxShadowTertiary}; } & .missing-chart-container { diff --git a/superset-frontend/src/dashboard/components/gridComponents/TabsRenderer/TabsRenderer.tsx b/superset-frontend/src/dashboard/components/gridComponents/TabsRenderer/TabsRenderer.tsx index 6d030b06a561..30c9a54679e1 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/TabsRenderer/TabsRenderer.tsx +++ b/superset-frontend/src/dashboard/components/gridComponents/TabsRenderer/TabsRenderer.tsx @@ -50,6 +50,10 @@ const StyledTabsContainer = styled.div<{ isDragging?: boolean }>` width: 100%; background-color: ${({ theme }) => theme.colorBgContainer}; + .ant-tabs > .ant-tabs-nav { + margin-bottom: ${({ theme }) => theme.sizeUnit * 4}px; + } + & .dashboard-component-tabs-content { height: 100%; } diff --git a/superset-frontend/src/features/home/Menu.tsx b/superset-frontend/src/features/home/Menu.tsx index 06d606cc6a40..d08fd9c01e70 100644 --- a/superset-frontend/src/features/home/Menu.tsx +++ b/superset-frontend/src/features/home/Menu.tsx @@ -290,6 +290,9 @@ export function Menu({ }; }; const renderBrand = () => { + if (brand.hide_logo) { + return null; + } let link; if (theme.brandLogoUrl) { link = ( @@ -344,15 +347,17 @@ export function Menu({ > - - {renderBrand()} - - {brand.text && ( + {!brand.hide_logo && ( + + {renderBrand()} + + )} + {brand.text && !brand.hide_logo && ( {brand.text} diff --git a/superset-frontend/src/types/bootstrapTypes.ts b/superset-frontend/src/types/bootstrapTypes.ts index 4b8e646fbbf1..83639e9f67a0 100644 --- a/superset-frontend/src/types/bootstrapTypes.ts +++ b/superset-frontend/src/types/bootstrapTypes.ts @@ -99,6 +99,7 @@ export interface BrandProps { alt: string; tooltip: string; text: string; + hide_logo?: boolean; } export interface NavBarProps { diff --git a/superset/config.py b/superset/config.py index 5380df762de2..be018d82cc8f 100644 --- a/superset/config.py +++ b/superset/config.py @@ -383,6 +383,9 @@ def _try_json_readsha(filepath: str, length: int) -> str | None: # or you can specify a full URL e.g. 'https://foo.bar' LOGO_TARGET_PATH = None +# When True, hide the navbar logo. +HIDE_NAVBAR_LOGO = False + # Specify tooltip that should appear when hovering over the App Icon/Logo LOGO_TOOLTIP = "" diff --git a/superset/views/base.py b/superset/views/base.py index 3220344013d1..45145fa1d9f4 100644 --- a/superset/views/base.py +++ b/superset/views/base.py @@ -290,6 +290,7 @@ def menu_data(user: User) -> dict[str, Any]: "alt": appbuilder.app_name, "tooltip": app.config["LOGO_TOOLTIP"], "text": brand_text, + "hide_logo": app.config.get("HIDE_NAVBAR_LOGO", False), }, "environment_tag": get_environment_tag(), "navbar_right": {