Skip to content

Commit

Permalink
Show Errors filter in sidebar when there's component errors
Browse files Browse the repository at this point in the history
  • Loading branch information
ghengeveld committed May 15, 2024
1 parent d37a74d commit addd9fe
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 51 deletions.
65 changes: 51 additions & 14 deletions src/SidebarBottom.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,71 @@
import { type API, useStorybookState } from "@storybook/manager-api";
import { styled } from "@storybook/theming";
import type { API_FilterFunction } from "@storybook/types";
import React, { useCallback } from "react";
import React, { useCallback, useEffect } from "react";

import { SidebarToggleButton } from "./components/SidebarToggleButton";
import { ADDON_ID } from "./constants";
import { ADDON_ID, ENABLE_FILTER } from "./constants";

const filterNone: API_FilterFunction = () => true;
const filterWarn: API_FilterFunction = ({ status }) => status?.[ADDON_ID]?.status === "warn";
const filterError: API_FilterFunction = ({ status }) => status?.[ADDON_ID]?.status === "error";
const filterBoth: API_FilterFunction = ({ status }) =>
status?.[ADDON_ID]?.status === "warn" || status?.[ADDON_ID]?.status === "error";

const Wrapper = styled.div({
display: "flex",
gap: 5,
});

interface SidebarBottomProps {
api: API;
}

export const ENABLE_FILTER = "enableFilter";

export const SidebarBottom = ({ api }: SidebarBottomProps) => {
const onEnable = useCallback(() => {
api.experimental_setFilter(ADDON_ID, filterWarn);
// Used internally to trigger next step in guided tour
api.emit(ENABLE_FILTER, ADDON_ID, filterWarn);
}, [api]);
const onDisable = useCallback(() => api.experimental_setFilter(ADDON_ID, filterNone), [api]);
const [showWarnings, setShowWarnings] = React.useState(false);
const [showErrors, setShowErrors] = React.useState(false);

const { status } = useStorybookState();
const warnings = Object.values(status).filter((value) => value[ADDON_ID]?.status === "warn");
if (!warnings.length) return null;
const errors = Object.values(status).filter((value) => value[ADDON_ID]?.status === "error");
const hasWarnings = warnings.length > 0;
const hasErrors = errors.length > 0;

const toggleWarnings = useCallback(() => setShowWarnings((shown) => !shown), []);
const toggleErrors = useCallback(() => setShowErrors((shown) => !shown), []);

useEffect(() => {
let filter = filterNone;
if (hasWarnings && showWarnings) filter = filterWarn;
if (hasErrors && showErrors) filter = filter === filterWarn ? filterBoth : filterError;
api.experimental_setFilter(ADDON_ID, filter);
api.emit(ENABLE_FILTER, filter);
}, [api, hasWarnings, hasErrors, showWarnings, showErrors]);

if (!hasWarnings && !hasErrors) return null;

return (
<span id="sidebar-bottom-wrapper">
<SidebarToggleButton count={warnings.length} onEnable={onEnable} onDisable={onDisable} />
</span>
<Wrapper id="sidebar-bottom-wrapper">
{hasWarnings && (
<SidebarToggleButton
id="changes-found-filter"
active={showWarnings}
count={warnings.length}
label="Change"
status="warning"
onClick={toggleWarnings}
/>
)}
{hasErrors && (
<SidebarToggleButton
id="errors-found-filter"
active={showErrors}
count={errors.length}
label="Error"
status="critical"
onClick={toggleErrors}
/>
)}
</Wrapper>
);
};
39 changes: 28 additions & 11 deletions src/components/SidebarToggleButton.stories.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,41 @@
import { action } from "@storybook/addon-actions";
import { type StoryObj } from "@storybook/react";
import { findByRole, userEvent } from "@storybook/testing-library";

import { playAll } from "../utils/playAll";
import { SidebarToggleButton } from "./SidebarToggleButton";

export default {
component: SidebarToggleButton,
args: {
active: false,
onClick: action("onClick"),
},
};

export const Changes = {
args: {
count: 12,
onEnable: action("onEnable"),
onDisable: action("onDisable"),
label: "Change",
status: "warning",
},
};

export const Default = {};
export const ChangesActive = {
args: {
...Changes.args,
active: true,
},
};

export const Enabled: StoryObj = {
play: playAll(async ({ canvasElement }) => {
const button = await findByRole(canvasElement, "button");
await userEvent.click(button);
}),
export const Errors = {
args: {
count: 2,
label: "Error",
status: "critical",
},
};

export const ErrorsActive = {
args: {
...Errors.args,
active: true,
},
};
43 changes: 20 additions & 23 deletions src/components/SidebarToggleButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Badge as BaseBadge } from "@storybook/components";
import { css, styled } from "@storybook/theming";
import pluralize from "pluralize";
import React, { useEffect, useState } from "react";
import React, { ComponentProps } from "react";

import { IconButton } from "./IconButton";

Expand All @@ -13,11 +13,16 @@ const Badge = styled(BaseBadge)(({ theme }) => ({
const Button = styled(IconButton)(
({ theme }) => ({
fontSize: theme.typography.size.s2,
"&:hover [data-badge], [data-badge=true]": {
"&:hover [data-badge][data-status=warning], [data-badge=true][data-status=warning]": {
background: "#E3F3FF",
borderColor: "rgba(2, 113, 182, 0.1)",
color: "#0271B6",
},
"&:hover [data-badge][data-status=critical], [data-badge=true][data-status=critical]": {
background: theme.background.negative,
boxShadow: `inset 0 0 0 1px rgba(182, 2, 2, 0.1)`,
color: theme.color.negativeText,
},
}),
({ active, theme }) =>
!active &&
Expand All @@ -33,33 +38,25 @@ const Label = styled.span(({ theme }) => ({
}));

interface SidebarToggleButtonProps {
active: boolean;
count: number;
onEnable: () => void;
onDisable: () => void;
label: string;
status: ComponentProps<typeof Badge>["status"];
}

export const SidebarToggleButton = React.memo(function SidebarToggleButton({
export const SidebarToggleButton = ({
active,
count,
onEnable,
onDisable,
}: SidebarToggleButtonProps) {
const [filter, setFilter] = useState(false);

const toggleFilter = () => {
setFilter(!filter);
if (filter) onDisable();
else onEnable();
};

// Ensure the filter is disabled if the button is not visible
useEffect(() => () => onDisable(), [onDisable]);

label,
status,
...props
}: SidebarToggleButtonProps & Omit<ComponentProps<typeof Button>, "status">) => {
return (
<Button id="changes-found-filter" active={filter} onClick={toggleFilter}>
<Badge status="warning" data-badge={filter}>
<Button active={active} {...props}>
<Badge status={status} data-badge={active} data-status={status}>
{count}
</Badge>
<Label>{pluralize("Change", count)}</Label>
<Label>{pluralize(label, count)}</Label>
</Button>
);
});
};
2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const LOCAL_BUILD_PROGRESS = `${ADDON_ID}/localBuildProgress`;
export const SELECTED_MODE_NAME = `${ADDON_ID}/selectedModeName`;
export const SELECTED_BROWSER_ID = `${ADDON_ID}/selectedBrowserId`;
export const TELEMETRY = `${ADDON_ID}/telemetry`;

export const ENABLE_FILTER = `${ADDON_ID}/enableFilter`;
export const REMOVE_ADDON = `${ADDON_ID}/removeAddon`;
export const RETRY_CONNECTION = `${ADDON_ID}/retryConnection`;

Expand Down
3 changes: 1 addition & 2 deletions src/screens/GuidedTour/GuidedTour.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { useTheme } from "@storybook/theming";
import React, { useEffect, useRef } from "react";
import Joyride from "react-joyride";

import { PANEL_ID } from "../../constants";
import { ENABLE_FILTER } from "../../SidebarBottom";
import { ENABLE_FILTER, PANEL_ID } from "../../constants";
import { useSessionState } from "../../utils/useSessionState";
import { useSelectedStoryState } from "../VisualTests/BuildContext";
import { Confetti } from "./Confetti";
Expand Down

0 comments on commit addd9fe

Please sign in to comment.