[Milestone 2] Re-design transparent theme for static embedding and public links (#46428)

* Update popover dark theme on part of ParameterFieldWidget (#45813)

* Add filter visual tests

* Simplify embed CSS colors

* [WIP] theme filter popover

* Move theme to global node, so it affects portal too

* Fix embed colors

* Theme part of ParameterFieldWidget

* Add story for parameter with dropdown

* Update loki snapshots

* Update more popover style

* Update loki snapshots

* Remove outdated snapshots

* Update loki snapshots with production build

* Fix loki not working with `userEvent`

* Theme mantine popover

* Improve dark theme text-brand visibility

* Update loki snapshots from master change

* Fix new colors not working in SDK

* Fix flaky Loki tests

* Filter popover dark theme part 2 (#46284)

* Theme Date all options

* wip add month and year filter

* Fix wrong CSS custom property declaration

* Test hover state

* Update Loki snapshots

* Theme date month and year

* Add quarter and year tests

* Theme date single and date range

* Style relative date

* Update frontend/src/metabase/admin/datamodel/components/filters/pickers/DatePicker/DatePickerHeader.styled.tsx

Co-authored-by: Denis Berezin <denis.berezin@metabase.com>

---------

Co-authored-by: Denis Berezin <denis.berezin@metabase.com>

* Complete theming filter popover night mode (#46365)

* Style unit of time and number widget

* Attempt to fix flaky Loki snapshots

* Address white background checkbox on dark theme

* Style search filter single value

* Fix mis color

* Update snapshots

* Remove duplication on play functions since they're long

* Milestone 2 polishing (#46382)

* Matches the footer padding with header padding

* Update snapshots

* Add E2E to prevent further problem

* Try to reduce flaky Loki tests

* Remove unused color shades (#46421)

* Update no results illustration to work better on dark theme

* Update loki snapshots

---------

Co-authored-by: Denis Berezin <denis.berezin@metabase.com>
This commit is contained in:
Mahatthana (Kelvin) Nomsawadi
2024-08-05 17:20:34 +07:00
committed by GitHub
parent a05452ef2e
commit 3d5233bc6a
102 changed files with 1984 additions and 373 deletions

View File

@@ -52,7 +52,9 @@ jobs:
run: yarn build-pure:cljs
- name: Build Storybook
run: yarn build-storybook
# We won't be able to use Storybook play's `userEvent` with production build
# see https://github.com/storybookjs/storybook/issues/19758
run: NODE_ENV=development yarn build-storybook
- name: Serve Storybook
run: yarn http-server storybook-static -p 6006 &

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -3,6 +3,7 @@ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const appConfig = require("../webpack.config");
const fs = require("fs");
const path = require("path");
const { features } = require("process");
const isEmbeddingSDK = process.env.IS_EMBEDDING_SDK === "true";
@@ -34,7 +35,12 @@ module.exports = {
"@storybook/addon-essentials",
"@storybook/addon-links",
"@storybook/addon-a11y",
"@storybook/addon-interactions",
"storybook-addon-pseudo-states",
],
features: {
interactionsDebugger: true,
},
babel: () => {},
typescript: {
reactDocgen: "react-docgen-typescript-plugin",

View File

@@ -28,6 +28,8 @@ import {
assertEmbeddingParameter,
multiAutocompleteInput,
dismissDownloadStatus,
createDashboardWithTabs,
createQuestion,
} from "e2e/support/helpers";
import { createMockParameter } from "metabase-types/api/mocks";
@@ -758,6 +760,132 @@ describeEE("scenarios > embedding > dashboard appearance", () => {
cy.get("@previewEmbedSpy").should("have.callCount", 1);
});
});
it("should not rerender the static dashboard with tabs preview unnecessarily (metabase#46378)", () => {
const textFilter = createMockParameter({
id: "3",
name: "Text filter",
slug: "filter-text",
type: "string/contains",
sectionId: "string",
});
const TAB_1 = { id: "11", name: "Tab 1" };
const TAB_2 = { id: "12", name: "Tab 2" };
const dashboardDetails = {
name: "dashboard name",
enable_embedding: true,
embedding_params: {
/**
* Make sure the parameter is shown in embed preview, because it previously
* caused the iframe to rerender even when only the hash part of the embed
* preview URL is changed.
*
* @see useSyncedQueryString in frontend/src/metabase/hooks/use-synced-query-string.ts
*/
[textFilter.slug]: "enabled",
},
parameters: [textFilter],
tabs: [TAB_1, TAB_2],
};
const questionDetails = {
name: "Orders",
query: {
"source-table": ORDERS_ID,
},
};
createQuestion(questionDetails)
.then(({ body: { id: card_id } }) => {
createDashboardWithTabs({
...dashboardDetails,
dashcards: [
{
id: -1,
card_id,
dashboard_tab_id: TAB_1.id,
row: 0,
col: 0,
size_x: 8,
size_y: 12,
},
],
});
})
.then(dashboard => {
visitDashboard(dashboard.id);
});
cy.intercept(
"GET",
"api/preview_embed/dashboard/*",
cy.spy().as("previewEmbedSpy"),
).as("previewEmbed");
openStaticEmbeddingModal({
activeTab: "parameters",
previewMode: "preview",
// EE users don't have to accept terms
acceptTerms: false,
});
cy.wait("@previewEmbed");
modal().within(() => {
cy.findByRole("tab", { name: "Look and Feel" }).click();
cy.get("@previewEmbedSpy").should("have.callCount", 1);
cy.log("Assert dashboard theme");
getIframeBody()
.findByTestId("embed-frame")
.invoke("attr", "data-embed-theme")
.then(embedTheme => {
expect(embedTheme).to.eq("light"); // default value
});
// We're getting an input element which is 0x0 in size
cy.findByLabelText("Dark").click({ force: true });
cy.wait(1000);
getIframeBody()
.findByTestId("embed-frame")
.invoke("attr", "data-embed-theme")
.then(embedTheme => {
expect(embedTheme).to.eq("night");
});
cy.get("@previewEmbedSpy").should("have.callCount", 1);
cy.log("Assert dashboard title");
getIframeBody().findByText(dashboardDetails.name).should("exist");
// We're getting an input element which is 0x0 in size
cy.findByLabelText("Dashboard title").click({ force: true });
getIframeBody().findByText(dashboardDetails.name).should("not.exist");
cy.get("@previewEmbedSpy").should("have.callCount", 1);
cy.log("Assert dashboard border");
getIframeBody()
.findByTestId("embed-frame")
.should("have.css", "border-top-width", "1px");
// We're getting an input element which is 0x0 in size
cy.findByLabelText("Dashboard border").click({ force: true });
getIframeBody()
.findByTestId("embed-frame")
.should("have.css", "border-top-width", "0px");
cy.get("@previewEmbedSpy").should("have.callCount", 1);
cy.log("Assert font");
getIframeBody().should("have.css", "font-family", "Lato, sans-serif");
cy.findByLabelText("Font").click();
});
// Since the select popover is rendered outside of the modal, we need to exit the modal context first.
popover().findByText("Oswald").click();
modal().within(() => {
getIframeBody().should("have.css", "font-family", "Oswald, sans-serif");
cy.get("@previewEmbedSpy").should("have.callCount", 1);
});
});
});
function openFilterOptions(name) {

View File

@@ -1,7 +1,6 @@
import styled from "@emotion/styled";
import { aceEditorStyles } from "metabase/query_builder/components/NativeQueryEditor/NativeQueryEditor.styled";
import { getMetabaseCssVariables } from "metabase/styled-components/theme/css-variables";
import { saveDomImageStyles } from "metabase/visualizations/lib/save-chart-image";
/**
@@ -19,8 +18,6 @@ export const PublicComponentStylesWrapper = styled.div`
color: var(--mb-color-text-dark);
font-family: var(--mb-default-font-family), sans-serif;
${({ theme }) => getMetabaseCssVariables(theme)}
${aceEditorStyles}
${saveDomImageStyles}

View File

@@ -1,5 +1,6 @@
import { css } from "@emotion/react";
import { Global, css } from "@emotion/react";
import styled from "@emotion/styled";
import { useMantineTheme } from "@mantine/core";
import type { HTMLAttributes } from "react";
import { rootStyle } from "metabase/css/core/base.styled";
@@ -18,13 +19,16 @@ export function SdkGlobalStylesWrapper({
...divProps
}: SdkContentWrapperProps & HTMLAttributes<HTMLDivElement>) {
const fontFiles = useSelector(getFontFiles);
const theme = useMantineTheme();
return (
<SdkGlobalStylesInner
baseUrl={baseUrl}
fontFiles={fontFiles}
{...divProps}
/>
<>
<SdkGlobalStylesInner
baseUrl={baseUrl}
fontFiles={fontFiles}
{...divProps}
/>
<Global styles={getMetabaseCssVariables(theme)} />
</>
);
}
@@ -35,8 +39,6 @@ const SdkGlobalStylesInner = styled.div<
>`
font-size: ${({ theme }) => theme.other.fontSize};
${({ theme }) => getMetabaseCssVariables(theme)}
${rootStyle}
${({ baseUrl }) => defaultFontFiles({ baseUrl })}

View File

@@ -286,7 +286,6 @@ export function FilterPopover({
className={className}
filter={filter}
dateShortcutOptions={dateShortcutOptions}
primaryColor={primaryColor}
minWidth={MIN_WIDTH}
maxWidth={MAX_WIDTH}
onBack={onBack}

View File

@@ -134,7 +134,6 @@ type Props = {
disableChangingDimension?: boolean;
supportsExpressions?: boolean;
primaryColor?: string;
minWidth?: number | null;
maxWidth?: number | null;
@@ -152,7 +151,6 @@ const DatePicker: React.FC<React.PropsWithChildren<Props>> = props => {
disableOperatorSelection,
disableChangingDimension,
supportsExpressions,
primaryColor,
onCommit,
children,
hideTimeSelectors,
@@ -182,7 +180,6 @@ const DatePicker: React.FC<React.PropsWithChildren<Props>> = props => {
{!operator || showShortcuts ? (
<DatePickerShortcuts
className={CS.p2}
primaryColor={primaryColor}
dateShortcutOptions={dateShortcutOptions}
onFilterChange={filter => {
setShowShortcuts(false);
@@ -199,7 +196,6 @@ const DatePicker: React.FC<React.PropsWithChildren<Props>> = props => {
filter={filter}
onBack={onBack}
operators={operators}
primaryColor={primaryColor}
onFilterChange={onFilterChange}
/>
) : null}
@@ -209,7 +205,6 @@ const DatePicker: React.FC<React.PropsWithChildren<Props>> = props => {
className={cx(CS.flexFull, CS.p2)}
filter={filter}
onCommit={onCommit}
primaryColor={primaryColor}
supportsExpressions={supportsExpressions}
onFilterChange={(filter: Filter) => {
if (!isStartingFrom(filter) && operator && operator.init) {
@@ -222,7 +217,6 @@ const DatePicker: React.FC<React.PropsWithChildren<Props>> = props => {
)}
<DatePickerFooter
filter={filter}
primaryColor={primaryColor}
onFilterChange={onFilterChange}
hideTimeSelectors={hideTimeSelectors}
>

View File

@@ -12,11 +12,7 @@ export const Container = styled.div`
padding: ${space(1)} ${space(2)} ${space(2)} ${space(2)};
`;
type ToggleButtonProps = {
primaryColor?: string;
};
export const ToggleButton = styled(Button)<ToggleButtonProps>`
export const ToggleButton = styled(Button)`
border: none;
border-radius: 0;
background: none;
@@ -25,7 +21,7 @@ export const ToggleButton = styled(Button)<ToggleButtonProps>`
font-weight: normal;
&:hover {
color: ${props => `${props.primaryColor || "var(--mb-color-brand)"}`};
color: var(--mb-color-text-brand);
background: none;
}
`;

View File

@@ -17,7 +17,6 @@ import {
import { Container, Interval, ToggleButton } from "./DatePickerFooter.styled";
type Props = {
primaryColor?: string;
hideTimeSelectors?: boolean;
filter: Filter;
@@ -37,7 +36,6 @@ const getIntervalString = (filter: Filter) => {
const DatePickerFooter: React.FC<React.PropsWithChildren<Props>> = ({
filter,
primaryColor,
onFilterChange,
hideTimeSelectors,
children,
@@ -87,11 +85,7 @@ const DatePickerFooter: React.FC<React.PropsWithChildren<Props>> = ({
!isStartingFrom(filter)
) {
content = (
<ToggleButton
primaryColor={primaryColor}
onClick={enableTimeSelectors}
icon="clock"
>
<ToggleButton onClick={enableTimeSelectors} icon="clock">
{t`Add a time`}
</ToggleButton>
);

View File

@@ -1,7 +1,7 @@
import { css } from "@emotion/react";
import styled from "@emotion/styled";
import Button from "metabase/core/components/Button";
import { color } from "metabase/lib/colors";
import { space } from "metabase/styled-components/theme";
export const Container = styled.div`
@@ -13,7 +13,6 @@ export const Container = styled.div`
type TabButtonProps = {
selected?: boolean;
primaryColor?: string;
};
export const TabButton = styled(Button)<TabButtonProps>`
@@ -23,16 +22,21 @@ export const TabButton = styled(Button)<TabButtonProps>`
padding-right: 0;
margin-left: ${space(2)};
margin-right: ${space(2)};
border-bottom: ${({ primaryColor = "var(--mb-color-brand)", selected }) =>
selected ? `2px solid ${primaryColor}` : "2px solid transparent"};
color: ${({ primaryColor = "var(--mb-color-brand)", selected }) =>
selected ? primaryColor : color("text-medium")};
${({ selected }) =>
selected
? css`
border-bottom: 2px solid var(--mb-color-text-brand);
color: var(--mb-color-text-brand);
`
: css`
border-bottom: 2px solid transparent;
color: var(--mb-color-text-secondary);
`}
&:hover {
background: none;
color: ${({ primaryColor = "var(--mb-color-brand)" }) => primaryColor};
border-color: ${({ primaryColor = "var(--mb-color-brand)" }) =>
primaryColor};
color: var(--mb-color-text-brand);
border-color: var(--mb-color-text-brand);
}
`;
@@ -40,9 +44,9 @@ export const BackButton = styled(TabButton)`
border: none;
border-radius: 0;
margin-left: ${space(1)};
color: var(--mb-color-text-medium);
color: var(--mb-color-text-secondary);
&:hover {
color: ${({ primaryColor }) => primaryColor};
color: var(--mb-color-text-brand);
}
`;

View File

@@ -10,7 +10,6 @@ import { getHeaderText } from "./ExcludeDatePicker";
type Props = {
className?: string;
isSidebar?: boolean;
primaryColor?: string;
filter: Filter;
operators?: DateOperator[];
@@ -22,7 +21,6 @@ type Props = {
export default function DatePickerHeader({
operators = DATE_OPERATORS,
filter,
primaryColor,
onFilterChange,
onBack,
}: Props) {
@@ -36,7 +34,6 @@ export default function DatePickerHeader({
return onBack || hasTemporalUnit ? (
<Container>
<BackButton
primaryColor={primaryColor}
onClick={() => {
if (hasTemporalUnit) {
onFilterChange([
@@ -57,17 +54,10 @@ export default function DatePickerHeader({
return (
<Container>
{onBack ? (
<BackButton
primaryColor={primaryColor}
onClick={onBack}
icon="chevronleft"
/>
) : null}
{onBack ? <BackButton onClick={onBack} icon="chevronleft" /> : null}
{tabs.map(({ test, displayName, init }) => (
<TabButton
selected={!!test(filter)}
primaryColor={primaryColor}
key={displayName}
onClick={() => {
onFilterChange(init(filter));

View File

@@ -2,16 +2,13 @@ import styled from "@emotion/styled";
import Button from "metabase/core/components/Button";
type ShortcutButtonProps = {
primaryColor?: string;
};
export const ShortcutButton = styled(Button)<ShortcutButtonProps>`
export const ShortcutButton = styled(Button)`
transition: none;
display: block;
border: none;
&:hover {
color: ${props => props.primaryColor || "var(--mb-color-brand)"};
color: var(--mb-color-text-brand);
background: none;
}
`;

View File

@@ -12,7 +12,6 @@ import { ShortcutButton, Separator } from "./DatePickerShortcuts.styled";
type Props = {
className?: string;
primaryColor?: string;
dateShortcutOptions?: DateShortcutOptions;
filter: Filter;
@@ -29,7 +28,6 @@ export default function DatePickerShortcuts({
dateShortcutOptions,
onCommit,
onBack,
primaryColor,
}: Props) {
const dimension = filter.dimension?.();
let title = "";
@@ -55,7 +53,6 @@ export default function DatePickerShortcuts({
{DAY_OPTIONS.map(({ displayName, init }) => (
<ShortcutButton
key={displayName}
primaryColor={primaryColor}
onClick={() => {
onCommit(init(filter));
}}
@@ -67,7 +64,6 @@ export default function DatePickerShortcuts({
{MONTH_OPTIONS.map(({ displayName, init }) => (
<ShortcutButton
key={displayName}
primaryColor={primaryColor}
onClick={() => {
onCommit(init(filter));
}}
@@ -79,7 +75,6 @@ export default function DatePickerShortcuts({
{MISC_OPTIONS.map(({ displayName, init }) => (
<ShortcutButton
key={displayName}
primaryColor={primaryColor}
onClick={() => {
onFilterChange(init(filter));
}}

View File

@@ -18,7 +18,7 @@
}
.CalendarDayNames {
opacity: 0.12;
color: var(--mb-color-text-tertiary);
}
.CalendarDay,
@@ -38,7 +38,6 @@
.CalendarDayName {
cursor: inherit;
color: inherit !important;
}
.CalendarDayThisMonth {

View File

@@ -1,3 +1,4 @@
import { css } from "@emotion/react";
import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
@@ -29,10 +30,15 @@ export const MonthRoot = styled.div<MonthRootProps>`
padding: 0.5rem 1rem;
border-radius: 99px;
color: ${props => props.isSelected && color("text-white")};
background-color: ${props => props.isSelected && color("brand")};
background-color: ${props =>
props.isSelected && "var(--mb-color-background-brand)"};
&:hover {
background-color: ${props =>
!props.isSelected && "var(--mb-color-bg-light)"};
${props =>
!props.isSelected &&
css`
color: var(--mb-color-text-selected);
background-color: var(--mb-color-background-selected);
`}
}
`;

View File

@@ -0,0 +1,18 @@
.shortcut:hover {
color: var(--mb-color-text-selected);
background-color: var(--mb-color-background-selected);
}
.sectionLine {
padding: 0;
border: 0;
border-top: 1px solid var(--mb-color-border);
margin-bottom: 0.5rem;
}
.sectionLabel {
padding: 0.3rem 1rem;
margin: auto;
font-size: 0.625rem;
font-weight: 700;
}

View File

@@ -7,6 +7,8 @@ import ButtonsS from "metabase/css/components/buttons.module.css";
import CS from "metabase/css/core/index.css";
import { DATE_MBQL_FILTER_MAPPING } from "metabase-lib/v1/parameters/constants";
import DateRelativeWidgetStyle from "./DateRelativeWidget.module.css";
type Shortcut = {
name: string;
operator: string | string[];
@@ -107,6 +109,7 @@ export class PredefinedRelativeDatePicker extends Component<PredefinedRelativeDa
CS.textNormal,
CS.textCentered,
CS.full,
DateRelativeWidgetStyle.shortcut,
)}
onClick={() => this.onSetShortcut(s)}
>
@@ -117,24 +120,18 @@ export class PredefinedRelativeDatePicker extends Component<PredefinedRelativeDa
</section>
{Object.keys(RELATIVE_SHORTCUTS).map(sectionName => (
<section key={sectionName}>
<div
<fieldset
className={cx(
CS.borderBottom,
CS.textUppercase,
CS.flex,
CS.layoutCentered,
CS.mb2,
DateRelativeWidgetStyle.sectionLine,
)}
>
<h6
style={{
top: "6px",
}}
className={cx(CS.px2, CS.bgWhite, CS.relative)}
>
<legend className={DateRelativeWidgetStyle.sectionLabel}>
{sectionName}
</h6>
</div>
</legend>
</fieldset>
<div className={CS.flex}>
{RELATIVE_SHORTCUTS[sectionName].map((s, index) => (
<button

View File

@@ -5,12 +5,11 @@ import { Component } from "react";
import ReactDOM from "react-dom";
import _ from "underscore";
import { waitTimeContext } from "metabase/context/wait-time";
import CS from "metabase/css/core/index.css";
import { isCypressActive } from "metabase/env";
import resizeObserver from "metabase/lib/resize-observer";
import { explicitSizeRefreshModeContext } from "./context";
const WAIT_TIME = 300;
const REFRESH_MODE = {
@@ -51,7 +50,7 @@ function ExplicitSize<T extends BaseInnerProps>({
const displayName = ComposedComponent.displayName || ComposedComponent.name;
class WrappedComponent extends Component<T> {
static contextType = explicitSizeRefreshModeContext;
static contextType = waitTimeContext;
static displayName = `ExplicitSize[${displayName}]`;
@@ -75,8 +74,8 @@ function ExplicitSize<T extends BaseInnerProps>({
};
this._printMediaQuery = window.matchMedia && window.matchMedia("print");
if (this.context) {
this._refreshMode = this.context as RefreshMode;
if (this.context === 0) {
this._refreshMode = "none";
} else {
this._refreshMode =
typeof refreshMode === "string" ? refreshMode : "throttle";

View File

@@ -1,8 +0,0 @@
import { createContext } from "react";
import type { RefreshMode } from "./ExplicitSize";
// Only used for Loki snapshots
export const explicitSizeRefreshModeContext = createContext<RefreshMode | null>(
null,
);

View File

@@ -1,3 +1,2 @@
// eslint-disable-next-line import/no-default-export -- deprecated usage
export { default } from "./ExplicitSize";
export { explicitSizeRefreshModeContext } from "./context";

View File

@@ -522,7 +522,7 @@ export function FieldValuesWidgetInner({
function (option: FieldValue): {
label: string;
value: string;
customLabel?: string;
customlabel?: string;
} {
const value = getValue(option);
const column = fields[0];
@@ -541,7 +541,7 @@ export function FieldValuesWidgetInner({
return {
value: value?.toString() ?? "",
label,
customLabel: getLabel(option),
customlabel: getLabel(option),
};
},
[fields, formatOptions],
@@ -549,22 +549,22 @@ export function FieldValuesWidgetInner({
const CustomItemComponent = useMemo(
() =>
forwardRef<HTMLDivElement, SelectItemProps & { customLabel?: string }>(
forwardRef<HTMLDivElement, SelectItemProps & { customlabel?: string }>(
function CustomItem(props, ref) {
const customLabel =
const customlabel =
props.value &&
renderValue({
fields,
formatOptions,
value: props.value,
displayValue: props.customLabel,
displayValue: props.customlabel,
});
return (
<ItemWrapper
ref={ref}
{...props}
label={customLabel ?? (props.label || "")}
label={customlabel ?? (props.label || "")}
/>
);
},

View File

@@ -1,13 +1,13 @@
import type * as React from "react";
import { useMemo, useState } from "react";
import { useMemo, useState, useContext } from "react";
import { t } from "ttag";
import _ from "underscore";
import EmptyState from "metabase/components/EmptyState";
import { waitTimeContext } from "metabase/context/wait-time";
import type { InputProps } from "metabase/core/components/Input";
import Input from "metabase/core/components/Input";
import { useDebouncedValue } from "metabase/hooks/use-debounced-value";
import { SEARCH_DEBOUNCE_DURATION } from "metabase/lib/constants";
import { Checkbox } from "metabase/ui";
import type { RowValue } from "metabase-types/api";
@@ -60,7 +60,8 @@ export const ListField = ({
}, [augmentedOptions.length]);
const [filter, setFilter] = useState("");
const debouncedFilter = useDebouncedValue(filter, SEARCH_DEBOUNCE_DURATION);
const waitTime = useContext(waitTimeContext);
const debouncedFilter = useDebouncedValue(filter, waitTime);
const filteredOptions = useMemo(() => {
const formattedFilter = debouncedFilter.trim().toLowerCase();

View File

@@ -16,9 +16,9 @@
}
.PopoverBody.PopoverBodyWithBackground {
border: 1px solid #edf2f5;
border: 1px solid var(--mb-color-border);
box-shadow: 0 4px 10px var(--mb-color-shadow);
background-color: var(--mb-color-bg-white);
background-color: var(--mb-color-background);
border-radius: 6px;
overflow: auto;
}
@@ -33,7 +33,7 @@
:global(.tippy-box[data-theme~="tooltip"]) {
color: white;
font-weight: bold;
background-color: var(--mb-color-bg-black);
background-color: var(--mb-color-background-inverse);
border: none;
pointer-events: none;
line-height: 1.26;
@@ -55,7 +55,7 @@
font-size: inherit;
border: 1px solid var(--mb-color-border);
box-shadow: 0 4px 10px var(--mb-color-shadow);
background-color: var(--mb-color-bg-white);
background-color: var(--mb-color-background);
border-radius: 6px;
overflow: auto;
}

View File

@@ -1,7 +1,5 @@
import styled from "@emotion/styled";
import { color } from "metabase/lib/colors";
export const EmptyStateContainer = styled.div`
padding: 2rem 2rem 0 2rem;
`;
@@ -42,12 +40,20 @@ export const OptionItem = styled.div<OptionItemProps>`
padding: 0.5rem 0.6rem;
width: 100%;
background-color: ${props =>
props.selected ? color(props.selectedColor) : color("bg-white")};
props.selected ? props.selectedColor : "var(--mb-color-background)"};
color: ${props =>
props.selected ? color("text-white") : "var(--mb-color-text-dark)"};
props.selected
? "var(--mb-color-text-white)"
: "var(--mb-color-text-primary)"};
&:hover {
background-color: ${props =>
props.selected ? color(props.selectedColor) : "var(--mb-color-bg-light)"};
props.selected
? props.selectedColor
: "var(--mb-color-background-selected)"};
color: ${props =>
props.selected
? "var(--mb-color-text-white)"
: "var(--mb-color-text-selected)"};
}
`;

View File

@@ -1,13 +1,13 @@
import type * as React from "react";
import { useMemo, useState } from "react";
import { useMemo, useState, useContext } from "react";
import { t } from "ttag";
import _ from "underscore";
import EmptyState from "metabase/components/EmptyState";
import { waitTimeContext } from "metabase/context/wait-time";
import type { InputProps } from "metabase/core/components/Input";
import Input from "metabase/core/components/Input";
import { useDebouncedValue } from "metabase/hooks/use-debounced-value";
import { SEARCH_DEBOUNCE_DURATION } from "metabase/lib/constants";
import type { RowValue } from "metabase-types/api";
import {
@@ -63,7 +63,8 @@ const SingleSelectListField = ({
}, [augmentedOptions.length]);
const [filter, setFilter] = useState("");
const debouncedFilter = useDebouncedValue(filter, SEARCH_DEBOUNCE_DURATION);
const waitTime = useContext(waitTimeContext);
const debouncedFilter = useDebouncedValue(filter, waitTime);
const filteredOptions = useMemo(() => {
const formattedFilter = debouncedFilter.trim().toLowerCase();
@@ -138,7 +139,9 @@ const SingleSelectListField = ({
<OptionItem
data-testid={`${option[0]}-filter-value`}
selectedColor={
checkedColor ?? isDashboardFilter ? "brand" : "filter"
checkedColor ?? isDashboardFilter
? "var(--mb-color-background-brand)"
: "var(--mb-color-filter)"
}
selected={selectedValue === option[0]}
onClick={() => onClickOption(option[0])}

View File

@@ -0,0 +1,6 @@
import { createContext } from "react";
import { SEARCH_DEBOUNCE_DURATION } from "metabase/lib/constants";
// Used to disable wait time in loki snapshots
export const waitTimeContext = createContext<number>(SEARCH_DEBOUNCE_DURATION);

View File

@@ -56,11 +56,13 @@ export const InputField = styled.input<InputProps>`
${props => inputPadding(props.fieldSize)}
${props => inputTypography(props.fieldSize)}
font-family: inherit;
color: var(--mb-color-text-dark);
color: var(--mb-color-text-primary);
border: 1px solid var(--mb-color-border);
border-radius: ${space(1)};
background-color: ${props =>
props.readOnly ? "var(--mb-color-bg-light)" : "var(--mb-color-bg-white)"};
props.readOnly
? "var(--mb-color-background-disabled)"
: "var(--mb-color-background)"};
outline: none;
text-align: inherit;

View File

@@ -9,19 +9,19 @@
padding: 0.5rem 0.75rem;
background: transparent;
border: 1px solid color-mix(in srgb, var(--mb-color-border), black 5%);
color: var(--mb-color-text-dark);
color: var(--mb-color-text-primary);
cursor: pointer;
font-weight: bold;
font-family: var(--mb-default-font-family), sans-serif;
border-radius: var(--default-button-border-radius);
transition: all 200ms linear;
transition-property: color, border-color, background-color;
}
.Button:hover {
color: var(--mb-color-brand);
color: var(--mb-color-text-brand);
border-color: color-mix(in srgb, var(--mb-color-border), black 12%);
background: var(--mb-color-bg-light);
transition: all 200ms linear;
transition-property: color, border-color, background-color;
}
.Button:focus {

View File

@@ -7,8 +7,7 @@
*/
:root {
/* Semantic colors */
--mb-color-text-white: var(--mb-base-color-white);
--mb-color-brand: var(--mb-base-color-blue);
--mb-color-brand: var(--mb-base-color-blue-40);
--mb-color-brand-light: #f9fbfc;
--mb-color-brand-lighter: #eef6fc;
--mb-color-success: #84bb4c;
@@ -18,7 +17,7 @@
--mb-color-text-dark: #4c5773;
--mb-color-text-medium: #696e7b;
--mb-color-text-light: #949aab;
--mb-color-bg-black: #2e353b;
--mb-color-bg-black: var(--mb-base-color-gray-80);
--mb-color-bg-dark: #93a1ab;
--mb-color-bg-medium: #edf2f5;
--mb-color-bg-light: #f9fbfc;
@@ -33,6 +32,14 @@
--mb-color-text-primary: var(--mb-color-text-dark);
--mb-color-text-secondary: var(--mb-color-text-medium);
--mb-color-text-tertiary: var(--mb-color-text-light);
--mb-color-text-selected: var(--mb-base-color-brand-40);
--mb-color-text-brand: var(--mb-base-color-brand-40);
--mb-color-text-white: var(--mb-base-color-white);
--mb-color-background: var(--mb-base-color-white);
--mb-color-background-selected: var(--mb-base-color-brand-10);
--mb-color-background-disabled: var(--mb-color-bg-light);
--mb-color-background-inverse: var(--mb-color-bg-black);
--mb-color-background-brand: var(--mb-base-color-brand-40);
/**
* Base colors
@@ -58,7 +65,39 @@
* <Component someColorProp="var(--mb-base-color-white)" />
*/
--mb-base-color-white: #fff;
--mb-base-color-blue: #509ee3;
/* Brand */
--mb-base-color-brand-70: color-mix(
in srgb,
var(--mb-color-brand),
black 50%
);
--mb-base-color-brand-60: color-mix(
in srgb,
var(--mb-color-brand),
black 25%
);
/* brand */
--mb-base-color-brand-40: var(--mb-color-brand);
--mb-base-color-brand-30: color-mix(
in srgb,
var(--mb-color-brand),
white 45%
);
--mb-base-color-brand-10: color-mix(
in srgb,
var(--mb-color-brand),
white 90%
);
/* Blue */
/* brand */
--mb-base-color-blue-40: #509ee3;
/* Gray */
/* bg-black */
--mb-base-color-gray-80: #2e353b;
--mb-base-color-gray-70: hsl(208deg, 12%, 30%);
}
.textDefault {

View File

@@ -58,8 +58,7 @@
color: var(--mb-color-text-secondary);
}
.DashboardNight .parameter.noPopover input:focus,
.ThemeNight .parameter.noPopover input:focus {
.DashboardNight .parameter.noPopover input:focus {
color: var(--mb-color-text-white);
}

View File

@@ -9,7 +9,7 @@ interface ParameterFieldSetProps {
}
export const ParameterFieldSet = styled(FieldSet)<ParameterFieldSetProps>`
background: var(--mb-color-embed-filter-bg-color);
background: var(--mb-color-background);
display: flex;
align-items: center;
transition: opacity 500ms linear;

View File

@@ -2,21 +2,15 @@ body {
background-color: transparent;
}
.NoBackground {
--mb-color-embed-bg-color-override: transparent;
}
.EmbedFrame {
[data-metabase-theme="light"][data-metabase-theme="light"] {
--mb-color-text-primary: var(--mb-color-text-dark);
--mb-color-text-secondary: var(--mb-color-text-medium);
--mb-color-text-tertiary: var(--mb-color-text-light);
--mb-color-embed-filter-bg-color: var(--mb-color-bg-white);
}
.EmbedFrame {
color: var(--mb-color-text-primary);
background-color: var(
--mb-color-embed-bg-color-override,
var(--mb-color-bg-dashboard)
);
background-color: var(--mb-color-bg-dashboard);
}
/* A temporary solution until we migrated all colors in visualization to use semantic colors
e.g. `text-primary`, `text-secondary`, `text-tertiary`. */
@@ -27,16 +21,10 @@ body {
.EmbedFrameHeader,
.EmbedFrameFooter {
color: var(--mb-color-text-primary);
background-color: var(
--mb-color-embed-bg-color-override,
var(--mb-color-bg-dashboard)
);
background-color: var(--mb-color-bg-dashboard);
}
.ThemeNight.EmbedFrame,
/* this is to make it work when exporting to pdfs,
where the EmbedFrame is not part of the exported dom */
.ThemeNight.EmbedFrame .WithThemeBackground {
[data-metabase-theme="night"][data-metabase-theme="night"] {
--mb-color-text-primary: color-mix(
in srgb,
var(--mb-color-text-white) 90%,
@@ -52,47 +40,57 @@ where the EmbedFrame is not part of the exported dom */
var(--mb-color-text-white) 45%,
transparent
);
--mb-color-embed-filter-bg-color: var(--mb-color-bg-black);
--mb-color-text-selected: var(--mb-base-color-brand-30);
--mb-color-border: var(--mb-color-text-medium);
--mb-color-bg-dashboard: var(--mb-color-bg-black);
--mb-color-background: var(--mb-color-bg-black);
--mb-color-background-selected: var(--mb-base-color-brand-70);
--mb-color-background-disabled: var(--mb-base-color-gray-70);
--mb-color-background-brand: var(--mb-base-color-brand-60);
background-color: var(
--mb-color-embed-bg-color-override,
var(--mb-color-bg-black)
);
border-color: var(--mb-color-bg-dark);
.EmbedFrame,
/* this is to make it work when exporting to pdfs,
where the EmbedFrame is not part of the exported dom */
.WithThemeBackground {
background-color: var(--mb-color-bg-dashboard);
border-color: var(--mb-color-bg-dark);
}
.EmbedFrameHeader,
.EmbedFrameFooter {
background-color: var(--mb-color-bg-dashboard);
border-color: var(--mb-color-bg-dark);
}
.EmbedFrame .fullscreenNightText {
transition: color 1s linear;
}
.EmbedFrame svg text {
stroke: none !important;
}
.EmbedFrame .DashCard .Card {
background-color: var(--mb-color-bg-black);
border: 1px solid var(--mb-color-border);
}
}
.ThemeNight .EmbedFrameHeader,
.ThemeNight .EmbedFrameFooter {
background-color: var(
--mb-color-embed-bg-color-override,
var(--mb-color-bg-black)
);
border-color: var(--mb-color-bg-dark);
.NoBackground {
--mb-color-bg-dashboard: transparent;
}
.ThemeNight.EmbedFrame .fullscreenNightText {
transition: color 1s linear;
}
[data-metabase-theme="transparent"][data-metabase-theme="transparent"] {
.EmbedFrame {
background-color: transparent;
.ThemeNight.EmbedFrame svg text {
stroke: none !important;
}
.DashCard .Card {
background-color: transparent;
}
}
.ThemeNight.EmbedFrame .DashCard .Card {
background-color: var(--mb-color-bg-black);
border: 1px solid var(--mb-color-border);
}
.ThemeTransparent.EmbedFrame {
background-color: transparent;
}
.ThemeTransparent .EmbedFrameHeader,
.ThemeTransparent .EmbedFrameFooter {
background-color: transparent;
}
.ThemeTransparent.EmbedFrame .DashCard .Card {
background-color: transparent;
.EmbedFrameHeader,
.EmbedFrameFooter {
background-color: transparent;
}
}

View File

@@ -7,7 +7,6 @@ import { FullWidthContainer } from "metabase/styled-components/layout/FullWidthC
import {
breakpointMinSmall,
breakpointMinLarge,
breakpointMinMedium,
space,
breakpointMaxSmall,
} from "metabase/styled-components/theme";
@@ -109,7 +108,7 @@ function getParameterPanelBackgroundColor(
isSticky: boolean,
) {
if (theme === "night") {
return `color-mix(in srgb, var(--mb-color-bg-black), var(--mb-color-embed-bg-color-override, var(--mb-color-bg-black)) ${
return `color-mix(in srgb, var(--mb-color-bg-black), var(--mb-color-bg-dashboard) ${
isSticky ? 15 : 100
}%)`;
}
@@ -120,7 +119,7 @@ function getParameterPanelBackgroundColor(
}%)`;
}
return `color-mix(in srgb, var(--mb-color-bg-white), var(--mb-color-embed-bg-color-override, var(--mb-color-bg-white)) ${
return `color-mix(in srgb, var(--mb-color-bg-white), var(--mb-color-bg-dashboard) ${
isSticky ? 15 : 100
}%)`;
}
@@ -165,16 +164,16 @@ export const Footer = styled.footer<{ variant: FooterVariant }>`
align-items: center;
${props => footerVariantStyles[props.variant]}
height: calc(50 / 16 * 1rem);
padding: 0 0.5rem;
padding: 0 1em;
${breakpointMinMedium} {
${breakpointMinSmall} {
height: calc(65 / 16 * 1rem);
padding: 0 1rem;
padding: 0 1.5rem;
}
${breakpointMinLarge} {
height: calc(80 / 16 * 1rem);
padding: 0 1.5rem;
padding: 0 2rem;
}
`;

View File

@@ -24,6 +24,7 @@ import { isUuid } from "metabase/lib/uuid";
import { FilterApplyButton } from "metabase/parameters/components/FilterApplyButton";
import { ParametersList } from "metabase/parameters/components/ParametersList";
import { getVisibleParameters } from "metabase/parameters/utils/ui";
import type { DisplayTheme } from "metabase/public/lib/types";
import { SyncedParametersList } from "metabase/query_builder/components/SyncedParametersList";
import { getIsEmbeddingSdk } from "metabase/selectors/embed";
import { getSetting } from "metabase/selectors/settings";
@@ -43,7 +44,6 @@ import type {
} from "metabase-types/api";
import type { DashboardUrlHashOptions } from "../../../dashboard/types";
import ParameterValueWidgetS from "../../../parameters/components/ParameterValueWidget.module.css";
import EmbedFrameS from "./EmbedFrame.module.css";
import type { FooterVariant } from "./EmbedFrame.styled";
@@ -86,20 +86,6 @@ type WithRequired<T, K extends keyof T> = T & Required<Pick<T, K>>;
export type EmbedFrameProps = EmbedFrameBaseProps &
WithRequired<DashboardUrlHashOptions, "background">;
const EMBED_THEME_CLASSES = (theme: DashboardUrlHashOptions["theme"]) => {
if (!theme) {
return null;
}
if (theme === "night") {
return cx(ParameterValueWidgetS.ThemeNight, EmbedFrameS.ThemeNight);
}
if (theme === "transparent") {
return EmbedFrameS.ThemeTransparent;
}
};
export const EmbedFrame = ({
className,
children,
@@ -124,6 +110,7 @@ export const EmbedFrame = ({
hide_parameters,
downloadsEnabled = true,
}: EmbedFrameProps) => {
useGlobalTheme(theme);
const isEmbeddingSdk = useSelector(getIsEmbeddingSdk);
const hasEmbedBranding = useSelector(
state => !getSetting(state, "hide-embed-branding?"),
@@ -179,14 +166,9 @@ export const EmbedFrame = ({
<Root
hasScroll={hasFrameScroll}
isBordered={bordered}
className={cx(
EmbedFrameS.EmbedFrame,
className,
EMBED_THEME_CLASSES(theme),
{
[EmbedFrameS.NoBackground]: !background,
},
)}
className={cx(EmbedFrameS.EmbedFrame, className, {
[EmbedFrameS.NoBackground]: !background,
})}
data-testid="embed-frame"
data-embed-theme={theme}
>
@@ -296,6 +278,32 @@ export const EmbedFrame = ({
);
};
function useGlobalTheme(theme: DisplayTheme | undefined) {
const isEmbeddingSdk = useSelector(getIsEmbeddingSdk);
useEffect(() => {
// We don't want to modify user application DOM when using the SDK.
if (isEmbeddingSdk || theme == null) {
return;
}
const originalTheme = document.documentElement.getAttribute(
"data-metabase-theme",
);
document.documentElement.setAttribute("data-metabase-theme", theme);
return () => {
if (originalTheme == null) {
document.documentElement.removeAttribute("data-metabase-theme");
} else {
document.documentElement.setAttribute(
"data-metabase-theme",
originalTheme,
);
}
};
}, [isEmbeddingSdk, theme]);
}
function isParametersWidgetContainersSticky(parameterCount: number) {
if (!isSmallScreen()) {
return true;

View File

@@ -3,7 +3,7 @@
--mb-color-background: var(--mb-color-bg-light);
/* SVG logo */
--mb-color-logo-text: #5a6072;
--mb-color-logo-primary: var(--mb-base-color-blue);
--mb-color-logo-primary: var(--mb-base-color-blue-40);
--mb-color-logo-secondary: #c2daf0;
display: flex;

View File

@@ -0,0 +1,875 @@
// @ts-expect-error There is no type definition
import createAsyncCallback from "@loki/create-async-callback";
import type { ComponentStory, Story, StoryContext } from "@storybook/react";
import { within, userEvent } from "@storybook/testing-library";
import { useEffect, type ComponentProps } from "react";
import { Provider } from "react-redux";
import { getStore } from "__support__/entities-store";
import { createMockMetadata } from "__support__/metadata";
import { getNextId } from "__support__/utils";
import { NumberColumn, StringColumn } from "__support__/visualizations";
import { waitTimeContext } from "metabase/context/wait-time";
import { getDashboardUiParameters } from "metabase/parameters/utils/dashboards";
import { publicReducers } from "metabase/reducers-public";
import TABLE_RAW_SERIES from "metabase/visualizations/components/TableSimple/stories-data/table-simple-orders-with-people.json";
import type { UiParameter } from "metabase-lib/v1/parameters/types";
import {
createMockCard,
createMockColumn,
createMockDashboard,
createMockDashboardCard,
createMockDataset,
createMockDatasetData,
createMockParameter,
} from "metabase-types/api/mocks";
import {
PRODUCTS,
createSampleDatabase,
} from "metabase-types/api/mocks/presets";
import {
createMockDashboardState,
createMockSettingsState,
createMockState,
} from "metabase-types/store/mocks";
import { PublicOrEmbeddedDashboardView } from "./PublicOrEmbeddedDashboardView";
export default {
title: "embed/PublicOrEmbeddedDashboardView/filters",
component: PublicOrEmbeddedDashboardView,
decorators: [
ReduxDecorator,
FasterExplicitSizeUpdateDecorator,
WaitForResizeToStopDecorator,
MockIsEmbeddingDecorator,
],
parameters: {
layout: "fullscreen",
},
argTypes: {
parameterType: {
options: ["text", "dropdown_multiple"],
control: { type: "select" },
},
},
};
function ReduxDecorator(Story: Story, context: StoryContext) {
const parameterType: ParameterType = context.args.parameterType;
const initialState = createMockState({
settings: createMockSettingsState({
"hide-embed-branding?": false,
}),
dashboard: createMockDashboardState({
dashcardData: {
[DASHCARD_BAR_ID]: {
[CARD_BAR_ID]: createMockDataset({
data: createMockDatasetData({
cols: [
createMockColumn(StringColumn({ name: "Dimension" })),
createMockColumn(NumberColumn({ name: "Count" })),
],
rows: [
["foo", 1],
["bar", 2],
],
}),
}),
},
[DASHCARD_TABLE_ID]: {
// Couldn't really figure out the type here.
[CARD_TABLE_ID]: createMockDataset(TABLE_RAW_SERIES[0] as any),
},
},
}),
parameters: {
parameterValuesCache: {
[`{"paramId":"${CATEGORY_FILTER.id}","dashId":${DASHBOARD_ID}}`]: {
values: [["Doohickey"], ["Gadget"], ["Gizmo"], ["Widget"]],
has_more_values: parameterType === "search" ? true : false,
},
[`{"paramId":"${CATEGORY_FILTER.id}","dashId":${DASHBOARD_ID},"query":"g"}`]:
{
values: [["Gadget"], ["Gizmo"], ["Widget"]],
has_more_values: parameterType === "search" ? true : false,
},
},
},
});
const store = getStore(publicReducers, initialState);
return (
<Provider store={store}>
<Story />
</Provider>
);
}
function FasterExplicitSizeUpdateDecorator(Story: Story) {
return (
<waitTimeContext.Provider value={0}>
<Story />
</waitTimeContext.Provider>
);
}
/**
* This is an arbitrary number, it should be big enough to pass CI tests.
* This value works together with FasterExplicitSizeUpdateDecorator which
* make sure we finish resizing any ExplicitSize components the fastest.
*/
const TIME_UNTIL_ALL_ELEMENTS_STOP_RESIZING = 1000;
function WaitForResizeToStopDecorator(Story: Story) {
const asyncCallback = createAsyncCallback();
useEffect(() => {
setTimeout(asyncCallback, TIME_UNTIL_ALL_ELEMENTS_STOP_RESIZING);
}, [asyncCallback]);
return <Story />;
}
declare global {
interface Window {
overrideIsWithinIframe?: boolean;
}
}
function MockIsEmbeddingDecorator(Story: Story) {
window.overrideIsWithinIframe = true;
return <Story />;
}
const DASHBOARD_ID = getNextId();
const DASHCARD_BAR_ID = getNextId();
const DASHCARD_TABLE_ID = getNextId();
const CARD_BAR_ID = getNextId();
const CARD_TABLE_ID = getNextId();
const TAB_ID = getNextId();
const CATEGORY_FILTER = createMockParameter({
id: "category-hex",
name: "Category",
slug: "category",
});
const CATEGORY_SINGLE_FILTER = createMockParameter({
id: "category-hex",
name: "Category",
slug: "category",
isMultiSelect: false,
});
const NUMBER_FILTER_ID = "number-hex";
const DATE_FILTER_ID = "date-hex";
const UNIT_OF_TIME_FILTER_ID = "unit-of-time-hex";
interface CreateDashboardOpts {
hasScroll?: boolean;
}
function createDashboard({ hasScroll }: CreateDashboardOpts = {}) {
return createMockDashboard({
id: DASHBOARD_ID,
name: "My dashboard",
width: "full",
dashcards: [
createMockDashboardCard({
id: DASHCARD_BAR_ID,
dashboard_tab_id: TAB_ID,
card: createMockCard({ id: CARD_BAR_ID, name: "Bar", display: "bar" }),
size_x: 12,
size_y: 8,
parameter_mappings: [
{
card_id: CARD_BAR_ID,
parameter_id: CATEGORY_FILTER.id,
target: [
"dimension",
["field", PRODUCTS.CATEGORY, { "base-type": "type/Text" }],
],
},
{
card_id: CARD_BAR_ID,
parameter_id: DATE_FILTER_ID,
target: [
"dimension",
["field", PRODUCTS.CREATED_AT, { "base-type": "type/DateTime" }],
],
},
{
card_id: CARD_BAR_ID,
parameter_id: UNIT_OF_TIME_FILTER_ID,
target: [
"dimension",
[
"field",
PRODUCTS.CREATED_AT,
{ "base-type": "type/DateTime", "temporal-unit": "month" },
],
],
},
{
card_id: CARD_BAR_ID,
parameter_id: NUMBER_FILTER_ID,
target: [
"dimension",
["field", PRODUCTS.RATING, { "base-type": "type/Float" }],
],
},
],
}),
createMockDashboardCard({
id: DASHCARD_TABLE_ID,
dashboard_tab_id: TAB_ID,
card: createMockCard({
id: CARD_TABLE_ID,
name: "Table",
display: "table",
}),
...(!hasScroll ? { col: 12 } : { row: 8 }),
size_x: 12,
size_y: 8,
}),
],
});
}
const Template: ComponentStory<typeof PublicOrEmbeddedDashboardView> = args => {
// @ts-expect-error -- custom prop to support non JSON-serializable value as args
const parameterType: ParameterType = args.parameterType;
const dashboard = args.dashboard;
if (!dashboard) {
return <>Please pass `dashboard`</>;
}
const PARAMETER_MAPPING: Record<ParameterType, UiParameter[]> = {
text: getDashboardUiParameters(
dashboard.dashcards,
[CATEGORY_FILTER],
createMockMetadata({}),
{},
),
number: getDashboardUiParameters(
dashboard.dashcards,
[
createMockParameter({
id: NUMBER_FILTER_ID,
name: "Number Equals",
sectionId: "number",
slug: "number_equals",
type: "number/=",
}),
],
createMockMetadata({}),
{},
),
dropdown_multiple: getDashboardUiParameters(
dashboard.dashcards,
[CATEGORY_FILTER],
createMockMetadata({
databases: [createSampleDatabase()],
}),
{},
),
dropdown_single: getDashboardUiParameters(
dashboard.dashcards,
[CATEGORY_SINGLE_FILTER],
createMockMetadata({
databases: [createSampleDatabase()],
}),
{},
),
search: getDashboardUiParameters(
dashboard.dashcards,
[CATEGORY_FILTER],
createMockMetadata({
databases: [createSampleDatabase()],
}),
{},
),
date_all_options: getDashboardUiParameters(
dashboard.dashcards,
[
createMockParameter({
id: DATE_FILTER_ID,
name: "Date all options",
sectionId: "date",
slug: "date_all_options",
type: "date/all-options",
}),
],
createMockMetadata({}),
{},
),
date_month_year: getDashboardUiParameters(
dashboard.dashcards,
[
createMockParameter({
id: DATE_FILTER_ID,
name: "Date Month and Year",
sectionId: "date",
slug: "date_month_and_year",
type: "date/month-year",
}),
],
createMockMetadata({}),
{},
),
date_quarter_year: getDashboardUiParameters(
dashboard.dashcards,
[
createMockParameter({
id: DATE_FILTER_ID,
name: "Date Quarter and Year",
sectionId: "date",
slug: "date_quarter_and_year",
type: "date/quarter-year",
}),
],
createMockMetadata({}),
{},
),
date_single: getDashboardUiParameters(
dashboard.dashcards,
[
createMockParameter({
id: DATE_FILTER_ID,
name: "Date single",
sectionId: "date",
slug: "date_single",
type: "date/single",
}),
],
createMockMetadata({}),
{},
),
date_range: getDashboardUiParameters(
dashboard.dashcards,
[
createMockParameter({
id: DATE_FILTER_ID,
name: "Date range",
sectionId: "date",
slug: "date_range",
type: "date/range",
}),
],
createMockMetadata({}),
{},
),
date_relative: getDashboardUiParameters(
dashboard.dashcards,
[
createMockParameter({
id: DATE_FILTER_ID,
name: "Date relative",
sectionId: "date",
slug: "date_relative",
type: "date/relative",
}),
],
createMockMetadata({}),
{},
),
temporal_unit: getDashboardUiParameters(
dashboard.dashcards,
[
createMockParameter({
id: UNIT_OF_TIME_FILTER_ID,
name: "Unit of Time",
sectionId: "temporal-unit",
slug: "unit_of_time",
type: "temporal-unit",
}),
],
createMockMetadata({}),
{},
),
};
return (
<PublicOrEmbeddedDashboardView
{...args}
parameters={PARAMETER_MAPPING[parameterType]}
/>
);
};
type ArgType = Partial<ComponentProps<typeof PublicOrEmbeddedDashboardView>>;
type ParameterType =
| "text"
| "number"
| "dropdown_multiple"
| "dropdown_single"
| "search"
| "date_all_options"
| "date_month_year"
| "date_quarter_year"
| "date_single"
| "date_range"
| "date_relative"
| "temporal_unit";
const createDefaultArgs = (
args: ArgType & { parameterType?: ParameterType } = {},
): ArgType & { parameterType: ParameterType } => {
const dashboard = createDashboard();
return {
dashboard,
titled: true,
bordered: true,
background: true,
slowCards: {},
selectedTabId: TAB_ID,
parameterType: "text",
...args,
};
};
function getLastPopover() {
const lastPopover = Array.from(
document.documentElement.querySelectorAll(
'[data-element-id="mantine-popover"]',
),
).at(-1) as HTMLElement;
return within(lastPopover);
}
function getLastPopoverElement() {
const lastPopover = Array.from(
document.documentElement.querySelectorAll(
'[data-element-id="mantine-popover"]',
),
).at(-1) as HTMLElement;
return lastPopover;
}
// Light theme
export const LightThemeText = Template.bind({});
LightThemeText.args = createDefaultArgs();
LightThemeText.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", { name: "Category" });
await userEvent.click(filter);
};
export const LightThemeTextWithValue = Template.bind({});
LightThemeTextWithValue.args = createDefaultArgs();
LightThemeTextWithValue.play = async ({ canvasElement }) => {
const asyncCallback = createAsyncCallback();
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", { name: "Category" });
await userEvent.click(filter);
const popover = getLastPopover();
await userEvent.type(
popover.getByPlaceholderText("Enter some text"),
"filter value",
);
await userEvent.click(getLastPopoverElement());
asyncCallback();
};
export const LightThemeParameterSearch = Template.bind({});
LightThemeParameterSearch.args = createDefaultArgs({
parameterType: "search",
});
LightThemeParameterSearch.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", { name: "Category" });
await userEvent.click(filter);
};
export const LightThemeParameterSearchWithValue = Template.bind({});
LightThemeParameterSearchWithValue.args = createDefaultArgs({
parameterType: "search",
});
LightThemeParameterSearchWithValue.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", { name: "Category" });
await userEvent.click(filter);
const documentElement = within(document.documentElement);
const searchInput = documentElement.getByPlaceholderText("Search the list");
await userEvent.click(documentElement.getByText("Widget"));
await userEvent.type(searchInput, "g");
const dropdown = getLastPopover();
(dropdown.getByText("Gadget").parentNode as HTMLElement).setAttribute(
"data-hovered",
"true",
);
};
// Dark theme
export const DarkThemeText = Template.bind({});
DarkThemeText.args = createDefaultArgs({ theme: "night" });
DarkThemeText.play = LightThemeText.play;
export const DarkThemeTextWithValue = Template.bind({});
DarkThemeTextWithValue.args = createDefaultArgs({ theme: "night" });
DarkThemeTextWithValue.play = LightThemeTextWithValue.play;
export const DarkThemeParameterSearch = Template.bind({});
DarkThemeParameterSearch.args = createDefaultArgs({
theme: "night",
parameterType: "search",
});
DarkThemeParameterSearch.play = LightThemeParameterSearch.play;
export const DarkThemeParameterSearchWithValue = Template.bind({});
DarkThemeParameterSearchWithValue.args = createDefaultArgs({
theme: "night",
parameterType: "search",
});
DarkThemeParameterSearchWithValue.play =
LightThemeParameterSearchWithValue.play;
// Parameter list
// Multiple values
export const LightThemeParameterList = Template.bind({});
LightThemeParameterList.args = createDefaultArgs({
parameterType: "dropdown_multiple",
});
LightThemeParameterList.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", { name: "Category" });
await userEvent.click(filter);
};
export const LightThemeParameterListWithValue = Template.bind({});
LightThemeParameterListWithValue.args = createDefaultArgs({
parameterType: "dropdown_multiple",
});
LightThemeParameterListWithValue.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", { name: "Category" });
await userEvent.click(filter);
const documentElement = within(document.documentElement);
await userEvent.type(
documentElement.getByPlaceholderText("Search the list"),
"g",
);
await userEvent.click(documentElement.getByText("Widget"));
};
export const DarkThemeParameterList = Template.bind({});
DarkThemeParameterList.args = createDefaultArgs({
theme: "night",
parameterType: "dropdown_multiple",
});
DarkThemeParameterList.play = LightThemeParameterList.play;
export const DarkThemeParameterListWithValue = Template.bind({});
DarkThemeParameterListWithValue.args = createDefaultArgs({
theme: "night",
parameterType: "dropdown_multiple",
});
DarkThemeParameterListWithValue.play = LightThemeParameterListWithValue.play;
// Single value
export const LightThemeParameterListSingleWithValue = Template.bind({});
LightThemeParameterListSingleWithValue.args = createDefaultArgs({
parameterType: "dropdown_single",
});
LightThemeParameterListSingleWithValue.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", { name: "Category" });
await userEvent.click(filter);
const documentElement = within(document.documentElement);
await userEvent.type(
documentElement.getByPlaceholderText("Search the list"),
"g",
);
await userEvent.click(documentElement.getByText("Widget"));
const popover = getLastPopover();
(popover.getByText("Gadget").parentNode as HTMLElement).classList.add(
"pseudo-hover",
);
};
export const DarkThemeParameterListSingleWithValue = Template.bind({});
DarkThemeParameterListSingleWithValue.args = createDefaultArgs({
theme: "night",
parameterType: "dropdown_single",
});
DarkThemeParameterListSingleWithValue.play =
LightThemeParameterListSingleWithValue.play;
// Date filters
// All options
export const LightThemeDateFilterAllOptions = Template.bind({});
LightThemeDateFilterAllOptions.args = createDefaultArgs({
parameterType: "date_all_options",
});
LightThemeDateFilterAllOptions.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", {
name: "Date all options",
});
await userEvent.click(filter);
const popover = getLastPopover();
const today = popover.getByRole("button", { name: "Today" });
today.classList.add("pseudo-hover");
};
export const DarkThemeDateFilterAllOptions = Template.bind({});
DarkThemeDateFilterAllOptions.args = createDefaultArgs({
theme: "night",
parameterType: "date_all_options",
});
DarkThemeDateFilterAllOptions.play = LightThemeDateFilterAllOptions.play;
// Month and Year
export const LightThemeDateFilterMonthYear = Template.bind({});
LightThemeDateFilterMonthYear.args = createDefaultArgs({
parameterType: "date_month_year",
parameterValues: {
[DATE_FILTER_ID]: "2024-01",
},
});
LightThemeDateFilterMonthYear.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", {
name: "Date Month and Year",
});
await userEvent.click(filter);
const popover = getLastPopover();
const month = popover.getByText("March");
month.classList.add("pseudo-hover");
await userEvent.click(
popover.getAllByDisplayValue("2024").at(-1) as HTMLElement,
);
const dropdown = getLastPopover();
dropdown
.getByRole("option", { name: "2023" })
.setAttribute("data-hovered", "true");
};
export const DarkThemeDateFilterMonthYear = Template.bind({});
DarkThemeDateFilterMonthYear.args = createDefaultArgs({
theme: "night",
parameterType: "date_month_year",
parameterValues: {
[DATE_FILTER_ID]: "2024-01",
},
});
DarkThemeDateFilterMonthYear.play = LightThemeDateFilterMonthYear.play;
// Quarter and Year
export const LightThemeDateFilterQuarterYear = Template.bind({});
LightThemeDateFilterQuarterYear.args = createDefaultArgs({
parameterType: "date_quarter_year",
parameterValues: {
[DATE_FILTER_ID]: "Q1-2024",
},
});
LightThemeDateFilterQuarterYear.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", {
name: "Date Quarter and Year",
});
await userEvent.click(filter);
const popover = getLastPopover();
const month = popover.getByText("Q2");
month.classList.add("pseudo-hover");
};
export const LightThemeDateFilterQuarterYearDropdown = Template.bind({});
LightThemeDateFilterQuarterYearDropdown.args = createDefaultArgs({
parameterType: "date_quarter_year",
parameterValues: {
[DATE_FILTER_ID]: "Q1-2024",
},
});
LightThemeDateFilterQuarterYearDropdown.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", {
name: "Date Quarter and Year",
});
await userEvent.click(filter);
const popover = getLastPopover();
await userEvent.click(
popover.getAllByDisplayValue("2024").at(-1) as HTMLElement,
);
const dropdown = getLastPopover();
dropdown
.getByRole("option", { name: "2023" })
.setAttribute("data-hovered", "true");
};
export const DarkThemeDateFilterQuarterYear = Template.bind({});
DarkThemeDateFilterQuarterYear.args = createDefaultArgs({
theme: "night",
parameterType: "date_quarter_year",
parameterValues: {
[DATE_FILTER_ID]: "Q1-2024",
},
});
DarkThemeDateFilterQuarterYear.play = LightThemeDateFilterQuarterYear.play;
export const DarkThemeDateFilterQuarterYearDropdown = Template.bind({});
DarkThemeDateFilterQuarterYearDropdown.args = createDefaultArgs({
theme: "night",
parameterType: "date_quarter_year",
parameterValues: {
[DATE_FILTER_ID]: "Q1-2024",
},
});
DarkThemeDateFilterQuarterYearDropdown.play =
LightThemeDateFilterQuarterYearDropdown.play;
// Single date
export const LightThemeDateFilterSingle = Template.bind({});
LightThemeDateFilterSingle.args = createDefaultArgs({
parameterType: "date_single",
parameterValues: {
[DATE_FILTER_ID]: "2024-06-01",
},
});
LightThemeDateFilterSingle.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", {
name: "Date single",
});
await userEvent.click(filter);
const popover = getLastPopover();
popover.getByText("15").classList.add("pseudo-hover");
};
export const DarkThemeDateFilterSingle = Template.bind({});
DarkThemeDateFilterSingle.args = createDefaultArgs({
theme: "night",
parameterType: "date_single",
parameterValues: {
[DATE_FILTER_ID]: "2024-06-01",
},
});
DarkThemeDateFilterSingle.play = LightThemeDateFilterSingle.play;
// Range
export const LightThemeDateFilterRange = Template.bind({});
LightThemeDateFilterRange.args = createDefaultArgs({
parameterType: "date_range",
parameterValues: {
[DATE_FILTER_ID]: "2024-06-01~2024-06-10",
},
});
LightThemeDateFilterRange.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", {
name: "Date range",
});
await userEvent.click(filter);
const popover = getLastPopover();
popover.getByText("15").classList.add("pseudo-hover");
};
export const DarkThemeDateFilterRange = Template.bind({});
DarkThemeDateFilterRange.args = createDefaultArgs({
theme: "night",
parameterType: "date_range",
parameterValues: {
[DATE_FILTER_ID]: "2024-06-01~2024-06-10",
},
});
DarkThemeDateFilterRange.play = LightThemeDateFilterRange.play;
// Relative
export const LightThemeDateFilterRelative = Template.bind({});
LightThemeDateFilterRelative.args = createDefaultArgs({
parameterType: "date_relative",
parameterValues: {
[DATE_FILTER_ID]: "thisday",
},
});
LightThemeDateFilterRelative.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", {
name: "Date relative",
});
await userEvent.click(filter);
const popover = getLastPopover();
popover
.getByRole("button", { name: "Yesterday" })
.classList.add("pseudo-hover");
};
export const DarkThemeDateFilterRelative = Template.bind({});
DarkThemeDateFilterRelative.args = createDefaultArgs({
theme: "night",
parameterType: "date_relative",
parameterValues: {
[DATE_FILTER_ID]: "thisday",
},
});
DarkThemeDateFilterRelative.play = LightThemeDateFilterRelative.play;
// Unit of time
export const LightThemeUnitOfTime = Template.bind({});
LightThemeUnitOfTime.args = createDefaultArgs({
parameterType: "temporal_unit",
parameterValues: {
[UNIT_OF_TIME_FILTER_ID]: "minute",
},
});
LightThemeUnitOfTime.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", {
name: "Unit of Time",
});
await userEvent.click(filter);
const popover = getLastPopover();
(popover.getByText("Hour").parentNode as HTMLElement).classList.add(
"pseudo-hover",
);
};
export const DarkThemeUnitOfTime = Template.bind({});
DarkThemeUnitOfTime.args = createDefaultArgs({
theme: "night",
parameterType: "temporal_unit",
parameterValues: {
[UNIT_OF_TIME_FILTER_ID]: "minute",
},
});
DarkThemeUnitOfTime.play = LightThemeUnitOfTime.play;
// Number widget
export const LightThemeNumber = Template.bind({});
LightThemeNumber.args = createDefaultArgs({
parameterType: "number",
});
LightThemeNumber.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const filter = await canvas.findByRole("button", {
name: "Number Equals",
});
await userEvent.click(filter);
const popover = getLastPopover();
const searchInput = popover.getByPlaceholderText("Enter a number");
await userEvent.type(searchInput, "11");
await userEvent.click(getLastPopoverElement());
await userEvent.type(searchInput, "99");
};
export const DarkThemeNumber = Template.bind({});
DarkThemeNumber.args = createDefaultArgs({
theme: "night",
parameterType: "number",
});
DarkThemeNumber.play = LightThemeNumber.play;

View File

@@ -7,7 +7,7 @@ import { Provider } from "react-redux";
import { getStore } from "__support__/entities-store";
import { getNextId } from "__support__/utils";
import { NumberColumn, StringColumn } from "__support__/visualizations";
import { explicitSizeRefreshModeContext } from "metabase/components/ExplicitSize/context";
import { waitTimeContext } from "metabase/context/wait-time";
import { publicReducers } from "metabase/reducers-public";
import { Box } from "metabase/ui";
import TABLE_RAW_SERIES from "metabase/visualizations/components/TableSimple/stories-data/table-simple-orders-with-people.json";
@@ -52,9 +52,9 @@ function ReduxDecorator(Story: Story) {
function FasterExplicitSizeUpdateDecorator(Story: Story) {
return (
<explicitSizeRefreshModeContext.Provider value="none">
<waitTimeContext.Provider value={0}>
<Story />
</explicitSizeRefreshModeContext.Provider>
</waitTimeContext.Provider>
);
}

View File

@@ -8,7 +8,7 @@ import { getStore } from "__support__/entities-store";
import { createMockMetadata } from "__support__/metadata";
import { getNextId } from "__support__/utils";
import { NumberColumn, StringColumn } from "__support__/visualizations";
import { explicitSizeRefreshModeContext } from "metabase/components/ExplicitSize/context";
import { waitTimeContext } from "metabase/context/wait-time";
import { publicReducers } from "metabase/reducers-public";
import { Box } from "metabase/ui";
import {
@@ -48,9 +48,9 @@ function ReduxDecorator(Story: Story) {
function FasterExplicitSizeUpdateDecorator(Story: Story) {
return (
<explicitSizeRefreshModeContext.Provider value="none">
<waitTimeContext.Provider value={0}>
<Story />
</explicitSizeRefreshModeContext.Provider>
</waitTimeContext.Provider>
);
}
@@ -118,6 +118,12 @@ const defaultArgs: Partial<
export const LightThemeDefault = Template.bind({});
LightThemeDefault.args = defaultArgs;
export const LightThemeDefaultNoResults = Template.bind({});
LightThemeDefaultNoResults.args = {
...defaultArgs,
result: createMockDataset(),
};
// Dark theme
export const DarkThemeDefault = Template.bind({});
DarkThemeDefault.args = {
@@ -125,6 +131,13 @@ DarkThemeDefault.args = {
theme: "night",
};
export const DarkThemeDefaultNoResults = Template.bind({});
DarkThemeDefaultNoResults.args = {
...defaultArgs,
theme: "night",
result: createMockDataset(),
};
// Transparent theme
export const TransparentThemeDefault = Template.bind({});
TransparentThemeDefault.args = {

View File

@@ -2,11 +2,10 @@ import { css, Global } from "@emotion/react";
import { baseStyle, rootStyle } from "metabase/css/core/base.styled";
import { defaultFontFiles } from "metabase/css/core/fonts.styled";
import { alpha, color, lighten } from "metabase/lib/colors";
import { getSitePath } from "metabase/lib/dom";
import { useSelector } from "metabase/lib/redux";
import { aceEditorStyles } from "metabase/query_builder/components/NativeQueryEditor/NativeQueryEditor.styled";
import { getThemeSpecificCssVariables } from "metabase/styled-components/theme/css-variables";
import { getMetabaseCssVariables } from "metabase/styled-components/theme/css-variables";
import { useMantineTheme } from "metabase/ui";
import { saveDomImageStyles } from "metabase/visualizations/lib/save-chart-image";
@@ -20,53 +19,9 @@ export const GlobalStyles = (): JSX.Element => {
const theme = useMantineTheme();
const styles = css`
${getMetabaseCssVariables(theme)}
:root {
--mb-default-font-family: "${font}";
--mb-color-brand: ${color("brand")};
--mb-color-brand-alpha-04: ${alpha("brand", 0.04)};
--mb-color-brand-alpha-88: ${alpha("brand", 0.88)};
--mb-color-brand-light: ${lighten("brand", 0.532)};
--mb-color-brand-lighter: ${lighten("brand", 0.598)};
--mb-color-focus: ${color("focus")};
--mb-color-bg-dark: ${color("bg-dark")};
--mb-color-bg-light: ${color("bg-light")};
--mb-color-bg-medium: ${color("bg-medium")};
--mb-color-bg-night: ${color("bg-night")};
--mb-color-bg-white: ${color("bg-white")};
--mb-color-border: ${color("border")};
--mb-color-danger: ${color("danger")};
--mb-color-error: ${color("error")};
--mb-color-filter: ${color("filter")};
--mb-color-shadow: ${color("shadow")};
--mb-color-success: ${color("success")};
--mb-color-summarize: ${color("summarize")};
--mb-color-text-dark: ${color("text-dark")};
--mb-color-text-light: ${color("text-light")};
--mb-color-text-medium: ${color("text-medium")};
--mb-color-text-white: ${color("text-white")};
--mb-color-warning: ${color("warning")};
--mb-color-border-alpha-30: color-mix(
in srgb,
var(--mb-color-border) 30%,
transparent
);
--mb-color-text-white-alpha-85: color-mix(
in srgb,
var(--mb-color-text-white) 85%,
transparent
);
--mb-color-bg-black-alpha-60: color-mix(
in srgb,
var(--mb-color-bg-black) 60%,
transparent
);
--mb-color-bg-white-alpha-15: color-mix(
in srgb,
var(--mb-color-bg-white) 15%,
transparent
);
${getThemeSpecificCssVariables(theme)}
}
${defaultFontFiles({ baseUrl: sitePath })}

View File

@@ -2,7 +2,7 @@ import styled from "@emotion/styled";
import {
breakpointMinSmall,
breakpointMinMedium,
breakpointMinLarge,
} from "metabase/styled-components/theme";
export const FullWidthContainer = styled.div`
@@ -15,7 +15,7 @@ export const FullWidthContainer = styled.div`
padding-right: 1.5rem;
}
${breakpointMinMedium} {
${breakpointMinLarge} {
padding-left: 2rem;
padding-right: 2rem;
}

View File

@@ -22,22 +22,21 @@ type MetabaseComponentThemeKey = FlattenObjectKeys<MetabaseComponentTheme>;
*/
export function getMetabaseCssVariables(theme: MantineTheme) {
return css`
${getDesignSystemCssVariables(theme)}
${getThemeSpecificCssVariables(theme)}
:root {
${getDesignSystemCssVariables(theme)}
${getThemeSpecificCssVariables(theme)}
}
`;
}
/**
* Design System CSS variables.
* These CSS variables are part of the core design system colors.
* DO NOT ADD COLORS WITHOUT EXTREMELY GOOD REASON AND DESIGN REVIEW.
* KEEP SYNCHRONIZED WITH:
* frontend/src/metabase/ui/utils/colors.ts
* frontend/src/metabase/css/core/colors.module.css
* frontend/src/metabase/styled-components/containers/GlobalStyles/GlobalStyles.tsx
* .storybook/preview-head.html
**/
*
* Only keep colors that depend on the theme and are not specified anywhere else here.
* You don't need to add new colors from `colors.module.css` here since they'll already
* be available globally at :root
**/
function getDesignSystemCssVariables(theme: MantineTheme) {
return css`
--mb-default-font-family: "${theme.fontFamily}";
@@ -82,9 +81,6 @@ function getDesignSystemCssVariables(theme: MantineTheme) {
--mb-color-success: ${theme.fn.themeColor("success")};
--mb-color-summarize: ${theme.fn.themeColor("summarize")};
--mb-color-warning: ${theme.fn.themeColor("warning")};
--mb-color-text-primary: var(--mb-color-text-dark);
--mb-color-text-secondary: var(--mb-color-text-medium);
--mb-color-text-tertiary: var(--mb-color-text-light);
`;
}

View File

@@ -64,9 +64,9 @@ export const getButtonOverrides = (): MantineThemeOverride["components"] => ({
backgroundColor: theme.fn.themeColor("bg-light"),
},
"&:disabled": {
color: theme.fn.themeColor("text-light"),
borderColor: theme.fn.themeColor("border"),
backgroundColor: theme.fn.themeColor("bg-light"),
color: "var(--mb-color-text-tertiary)",
borderColor: "var(--mb-color-border)",
backgroundColor: "var(--mb-color-background-disabled)",
},
"&[data-loading]": {
[`& .${getStylesRef("leftIcon")}`]: {
@@ -83,16 +83,16 @@ export const getButtonOverrides = (): MantineThemeOverride["components"] => ({
return {
root: {
color: theme.white,
borderColor: primaryColor,
backgroundColor: primaryColor,
borderColor: "var(--mb-color-background-brand)",
backgroundColor: "var(--mb-color-background-brand)",
"&:hover": {
borderColor: hoverColor,
backgroundColor: hoverColor,
},
"&:disabled": {
color: theme.fn.themeColor("text-light"),
borderColor: theme.fn.themeColor("border"),
backgroundColor: theme.fn.themeColor("bg-light"),
color: "var(--mb-color-text-tertiary)",
borderColor: "var(--mb-color-border)",
backgroundColor: "var(--mb-color-background-disabled)",
},
"&[data-loading]": {
[`& .${getStylesRef("leftIcon")}`]: {
@@ -117,9 +117,9 @@ export const getButtonOverrides = (): MantineThemeOverride["components"] => ({
backgroundColor,
},
"&:disabled": {
color: theme.fn.themeColor("text-light"),
borderColor: theme.fn.themeColor("border"),
backgroundColor: theme.fn.themeColor("bg-light"),
color: "var(--mb-color-text-tertiary)",
borderColor: "var(--mb-color-border)",
backgroundColor: "var(--mb-color-background-disabled)",
},
},
};

View File

@@ -25,13 +25,13 @@ export const getCheckboxOverrides = (): MantineThemeOverride["components"] => ({
root: {
[`&:has(.${getStylesRef("input")}:disabled)`]: {
[`.${getStylesRef("label")}`]: {
color: theme.fn.themeColor("text-light"),
color: "var(--mb-color-text-tertiary)",
},
[`.${getStylesRef("description")}`]: {
color: theme.fn.themeColor("text-light"),
color: "var(--mb-color-text-tertiary)",
},
[`.${getStylesRef("icon")}`]: {
color: theme.fn.themeColor("text-light"),
color: "var(--mb-color-text-tertiary)",
},
},
},
@@ -46,30 +46,31 @@ export const getCheckboxOverrides = (): MantineThemeOverride["components"] => ({
height: getSize({ size, sizes: SIZES }),
cursor: "pointer",
borderRadius: theme.radius.xs,
border: `1px solid ${theme.fn.themeColor("bg-dark")}`,
border: "1px solid var(--mb-color-text-secondary)",
backgroundColor: "var(--mb-color-background)",
"&:checked": {
borderColor: theme.fn.themeColor("brand"),
backgroundColor: theme.fn.themeColor("brand"),
borderColor: "var(--mb-color-background-brand)",
backgroundColor: "var(--mb-color-background-brand)",
[`.${getStylesRef("icon")}`]: {
color: theme.white,
},
},
"&:disabled": {
borderColor: theme.fn.themeColor("border"),
backgroundColor: theme.fn.themeColor("border"),
borderColor: "var(--mb-color-background-disabled)",
backgroundColor: "var(--mb-color-background-disabled)",
},
},
label: {
ref: getStylesRef("label"),
color: theme.fn.themeColor("text-dark"),
color: "var(--mb-color-text-primary)",
fontSize: theme.fontSizes.md,
lineHeight: theme.lineHeight,
cursor: "pointer",
},
description: {
ref: getStylesRef("description"),
color: theme.fn.themeColor("text-dark"),
color: "var(--mb-color-text-primary)",
fontSize: theme.fontSizes.sm,
lineHeight: theme.lineHeight,
marginTop: theme.spacing.xs,

View File

@@ -70,6 +70,11 @@ export const getMultiSelectOverrides =
"&[data-with-icon]": {
paddingLeft: theme.spacing.lg,
},
background: "var(--mb-color-background)",
color: "var(--mb-color-text-primary)",
"&::placeholder": {
color: "var(--mb-color-text-secondary)",
},
},
icon: {
width: theme.spacing.lg,
@@ -100,8 +105,8 @@ export const getMultiSelectOverrides =
fontWeight: "bold",
fontSize: getSize({ size, sizes: theme.fontSizes }),
borderRadius: theme.radius.xs,
color: theme.fn.themeColor("brand"),
backgroundColor: theme.fn.themeColor("bg-medium"),
color: "var(--mb-color-text-selected)",
backgroundColor: "var(--mb-color-background-selected)",
},
defaultValueRemove: {
color: theme.fn.themeColor("brand"),
@@ -117,6 +122,10 @@ export const getMultiSelectOverrides =
height: "100% !important",
},
},
dropdown: {
backgroundColor: "var(--mb-color-background)",
borderColor: "var(--mb-color-border)",
},
}),
variants: {
default: () => ({

View File

@@ -29,7 +29,8 @@ export const getSelectOverrides = (): MantineThemeOverride["components"] => ({
...getSelectItemsOverrides(theme, size),
// For epic (metabase#38699)
dropdown: {
background: theme.fn.themeColor("bg-white"),
background: "var(--mb-color-background)",
borderColor: "var(--mb-color-border)",
">div": {
maxHeight: "none !important",
},
@@ -87,6 +88,8 @@ export const getSelectInputOverrides = (
},
input: {
ref: getStylesRef("input"),
color: "var(--mb-color-text-primary)",
backgroundColor: "var(--mb-color-background)",
"&[data-disabled]": {
opacity: 1,
@@ -94,7 +97,7 @@ export const getSelectInputOverrides = (
},
rightSection: {
ref: getStylesRef("rightSection"),
color: theme.fn.themeColor("text-dark"),
color: "var(--mb-color-text-primary)",
svg: {
color: "inherit !important",
@@ -126,17 +129,17 @@ export const getSelectItemsOverrides = (
padding: "0.75rem",
},
item: {
color: theme.fn.themeColor("text-dark"),
color: "var(--mb-color-text-primary)",
fontSize: getItemFontSize(size),
lineHeight: getItemLineHeight(size),
padding: theme.spacing.sm,
"&[data-hovered]": {
color: theme.fn.themeColor("brand"),
backgroundColor: theme.fn.themeColor("brand-lighter"),
color: "var(--mb-color-text-selected)",
backgroundColor: "var(--mb-color-background-selected)",
},
"&[data-selected]": {
color: theme.fn.themeColor("text-white"),
backgroundColor: theme.fn.themeColor("brand"),
backgroundColor: "var(--mb-color-background-brand)",
},
"&[data-disabled]": {
color: theme.fn.themeColor("text-light"),

View File

@@ -4,17 +4,17 @@
word-break: break-all;
&:hover {
color: var(--mb-color-brand);
background-color: var(--mb-color-brand-lighter);
color: var(--mb-color-text-selected);
background-color: var(--mb-color-background-selected);
}
&.selected {
color: var(--mb-color-text-white);
background-color: var(--mb-color-brand);
background-color: var(--mb-color-background-brand);
}
&.disabled {
cursor: default;
color: var(--mb-color-text-light);
color: var(--mb-color-text-tertiary);
}
}

View File

@@ -12,11 +12,12 @@ export const getPopoverOverrides = (): MantineThemeOverride["components"] => ({
middlewares: { shift: true, flip: true, size: true },
transitionProps: { duration: 0 },
},
styles: theme => ({
styles: () => ({
dropdown: {
padding: 0,
overflow: "auto",
background: theme.fn.themeColor("bg-white"),
background: "var(--mb-color-background)",
borderColor: "var(--mb-color-border)",
},
}),
},

View File

@@ -138,11 +138,14 @@
"@storybook/addon-a11y": "^6.5.16",
"@storybook/addon-actions": "6.5.16",
"@storybook/addon-essentials": "6.5.16",
"@storybook/addon-interactions": "^6.5.16",
"@storybook/addon-links": "6.5.16",
"@storybook/blocks": "^7.0.18",
"@storybook/builder-webpack5": "6.5.16",
"@storybook/jest": "^0.2.3",
"@storybook/manager-webpack5": "6.5.16",
"@storybook/react": "6.5.16",
"@storybook/testing-library": "^0.2.2",
"@svgr/webpack": "^8.0.1",
"@swc/core": "^1.7.0",
"@swc/plugin-emotion": "^3.0.10",
@@ -280,6 +283,7 @@
"semver": "^7.6.3",
"shadow-cljs": "2.27.4",
"source-map-loader": "^4.0.1",
"storybook-addon-pseudo-states": "^3.1.1",
"stylelint": "^16.2.1",
"stylelint-config-css-modules": "^4.4.0",
"stylelint-config-standard": "^36.0.0",

View File

@@ -0,0 +1,22 @@
diff --git a/node_modules/@loki/browser/src/disable-pointer-events.js b/node_modules/@loki/browser/src/disable-pointer-events.js
index ea8f45c..2663c4b 100644
--- a/node_modules/@loki/browser/src/disable-pointer-events.js
+++ b/node_modules/@loki/browser/src/disable-pointer-events.js
@@ -1,17 +1,4 @@
const disablePointerEvents = (window) => {
- const DISABLE_POINTER_EVENTS_STYLE = `
- * {
- pointer-events: none !important;
- }
- `;
-
- // Disable pointer events to avoid having hover styles
- // for elements at the 0 by 0 position.
- window.document.addEventListener('DOMContentLoaded', () => {
- const styleElement = window.document.createElement('style');
- window.document.documentElement.appendChild(styleElement);
- styleElement.sheet.insertRule(DISABLE_POINTER_EVENTS_STYLE);
- });
};
module.exports = disablePointerEvents;

File diff suppressed because one or more lines are too long

View File

@@ -20,5 +20,6 @@ module.exports = {
},
},
"postcss-discard-comments": {},
"postcss-nesting": {},
},
};

Some files were not shown because too many files have changed in this diff Show More