[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>
4
.github/workflows/loki.yml
vendored
@@ -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 &
|
||||
|
||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 18 KiB |
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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 })}
|
||||
|
||||
@@ -286,7 +286,6 @@ export function FilterPopover({
|
||||
className={className}
|
||||
filter={filter}
|
||||
dateShortcutOptions={dateShortcutOptions}
|
||||
primaryColor={primaryColor}
|
||||
minWidth={MIN_WIDTH}
|
||||
maxWidth={MAX_WIDTH}
|
||||
onBack={onBack}
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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));
|
||||
}}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
@@ -1,3 +1,2 @@
|
||||
// eslint-disable-next-line import/no-default-export -- deprecated usage
|
||||
export { default } from "./ExplicitSize";
|
||||
export { explicitSizeRefreshModeContext } from "./context";
|
||||
|
||||
@@ -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 || "")}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)"};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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])}
|
||||
|
||||
6
frontend/src/metabase/context/wait-time.ts
Normal 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);
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 })}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: () => ({
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)",
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
22
patches/@loki+browser+0.35.0.patch
Normal 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;
|
||||
15
patches/storybook-addon-pseudo-states+3.1.1.patch
Normal file
@@ -20,5 +20,6 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
"postcss-discard-comments": {},
|
||||
"postcss-nesting": {},
|
||||
},
|
||||
};
|
||||
|
||||