Skip to content

Commit

Permalink
feat(Section): add Section component
Browse files Browse the repository at this point in the history
  • Loading branch information
plagoa committed Oct 20, 2023
1 parent 7507556 commit 1b0ea65
Show file tree
Hide file tree
Showing 7 changed files with 424 additions and 1 deletion.
228 changes: 228 additions & 0 deletions packages/core/src/components/Section/Section.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import { Meta, StoryObj } from "@storybook/react";
import {
HvSwitch,
HvTypography,
theme,
} from "@hitachivantara/uikit-react-core";
import { HvDonutChart } from "@hitachivantara/uikit-react-viz";
import { Duplicate, Ticket } from "@hitachivantara/uikit-react-icons";
import { css } from "@emotion/css";
import { useMemo, useState } from "react";
import { HvActionsGeneric } from "@core/components/ActionsGeneric";
import { HvButton } from "@core/components/Button";
import { HvSection, HvSectionProps } from "./Section";

const meta: Meta<typeof HvSection> = {
title: "Widgets/Section",
component: HvSection,
};
export default meta;

export const Main: StoryObj<HvSectionProps> = {
args: {
title: "Section Title",
expandable: false,
defaultExpanded: true,
},
argTypes: {
classes: { control: { disable: true } },
},
render: ({ title, ...others }) => {
const wrappedTitle = <HvTypography variant="title4">{title}</HvTypography>;
return (
<HvSection title={wrappedTitle} {...others}>
<HvTypography>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed tempor
blandit ipsum quis sollicitudin. Aliquam erat volutpat. Praesent nisi
nisl, sodales vitae blandit tincidunt, malesuada id sapien. Nulla
dapibus accumsan est, a pharetra velit consequat et. Nullam iaculis
justo sed urna condimentum ultricies. Integer nec interdum tortor.
Nulla molestie nibh in elit congue malesuada. Donec fringilla volutpat
sapien id maximus. Vestibulum faucibus pellentesque ex, non gravida
dui pharetra quis. Nulla facilisi. Suspendisse erat nisl, mollis ut
est nec, malesuada feugiat orci. Vivamus dignissim nibh id lacinia
vehicula. Nullam lobortis scelerisque dui, non suscipit sapien
tincidunt at. Vivamus ut orci imperdiet, volutpat mauris in, sagittis
mi. Donec pulvinar nibh sit amet neque tristique, vitae gravida ipsum
dapibus. Donec a eros commodo, tincidunt nunc dictum, ullamcorper
quam.
</HvTypography>
</HvSection>
);
},
};

export const WithActions: StoryObj<HvSectionProps> = {
parameters: {
docs: {
description: {
story:
"You can use whatever you want as actions. This example showcases the use of the `HvActionsGeneric` component.",
},
},
},
render: () => {
const classes = {
container: css({
display: "grid",
gridTemplateColumns: "repeat(2, 1fr)",
gap: theme.space.sm,
}),
root: css({ position: "relative" }),
content: css({
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
}),
};

const data = {
Country: ["Portugal", "Spain", "France", "Germany"],
"Tickets Sold": [61829, 123948, 253792, 524638],
};

const actions = useMemo(
() => (
<HvActionsGeneric
actions={[
{ id: "action1", label: "Action 1" },
{
id: "action2",
label: "Action 2",
},
{
id: "action3",
label: "Action 3",
},
]}
actionsCallback={(_, __, action) => {
console.log(action.label);
}}
maxVisibleActions={1}
/>
),
[]
);

return (
<HvSection
title={
<HvTypography variant="title4">Section with actions</HvTypography>
}
actions={actions}
>
<div className={classes.container}>
<HvTypography>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed tempor
blandit ipsum quis sollicitudin. Aliquam erat volutpat. Praesent
nisi nisl, sodales vitae blandit tincidunt, malesuada id sapien.
Nulla dapibus accumsan est, a pharetra velit consequat et. Nullam
iaculis justo sed urna condimentum ultricies. Integer nec interdum
tortor. Nulla molestie nibh in elit congue malesuada. Donec
fringilla volutpat sapien id maximus. Vestibulum faucibus
pellentesque ex, non gravida dui pharetra quis. Nulla facilisi.
Suspendisse erat nisl, mollis ut est nec, malesuada feugiat orci.
Vivamus dignissim nibh id lacinia vehicula.
</HvTypography>

<div className={classes.root}>
<HvDonutChart
data={data}
groupBy="Country"
measure="Tickets Sold"
/>
<div className={classes.content}>
<Ticket iconSize="M" />
<HvTypography variant="title3">
{data["Tickets Sold"].reduce((acc, value) => acc + value, 0)}
</HvTypography>
</div>
</div>
</div>
</HvSection>
);
},
};

export const Multiple: StoryObj<HvSectionProps> = {
parameters: {
docs: {
description: {
story:
"This sample showcases as example where multiple sections are used together.",
},
},
},
render: () => {
const [openIds, setOpenIds] = useState<string[]>([]);
const [multiple, setMultiple] = useState(false);
const sections = useMemo(
() => [
{ id: "1", title: "Section 1", content: "Section 1 content." },
{ id: "2", title: "Section 2", content: "Section 2 content." },
{ id: "3", title: "Section 3", content: "Section 3 content." },
{ id: "4", title: "Section 4", content: "Section 4 content." },
{ id: "5", title: "Section 5", content: "Section 5 content." },
{ id: "6", title: "Section 6", content: "Section 6 content." },
],
[]
);

const classes = {
section: css({
marginBottom: theme.space.sm,
}),
};

return (
<>
<HvSwitch
label="Allow multiple sections open"
checked={multiple}
defaultChecked={false}
onChange={(_evt, newChecked) => setMultiple(newChecked)}
/>
{sections.map((s) => (
<div key={s.id} className={classes.section}>
<HvSection
id={s.id}
title={<HvTypography variant="title4">{s.title}</HvTypography>}
expandable
expanded={openIds.includes(s.id)}
actions={
<HvButton
variant="primaryGhost"
startIcon={<Duplicate />}
onClick={() => console.log(`Link to ${s.title} copied`)}
>
Copy Link
</HvButton>
}
onToggle={(event, open) =>
setOpenIds((ids) => {
if (!multiple) {
if (open) {
return [s.id];
}
return [];
}
if (open) {
return [...ids, s.id];
}
return [...ids.filter((i) => i !== s.id)];
})
}
>
<HvTypography>{s.content}</HvTypography>
</HvSection>
</div>
))}
</>
);
},
};
31 changes: 31 additions & 0 deletions packages/core/src/components/Section/Section.styles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { theme } from "@hitachivantara/uikit-styles";

import { createClasses } from "@core/utils/classes";

export const { staticClasses, useClasses } = createClasses("HvSection", {
root: {
width: "100%",
display: "flex",
flexDirection: "column",
padding: theme.space.sm,
backgroundColor: theme.colors.atmo1,
borderRadius: theme.radii.round,
border: `1px solid ${theme.colors.atmo4}`,
},
hidden: { height: 0, display: "none" },
header: {
display: "flex",
alignItems: "center",
position: "relative",
minHeight: theme.sizes.sm,
},
content: {
marginTop: theme.space.sm,
},
actions: {
display: "flex",
gap: theme.space.xs,
position: "absolute",
right: 0,
},
});
64 changes: 64 additions & 0 deletions packages/core/src/components/Section/Section.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { HvButton } from "@core/components";
import { HvSection } from "./Section";

describe("Section", () => {
it("should contain all the steps", () => {
render(<HvSection title="Section Title" />);
expect(screen.getByText("Section Title")).toBeInTheDocument();
});

it("should render the content when it's not expandable", () => {
render(
<HvSection title="Section Title" expanded>
<div>Child Content</div>
</HvSection>
);
expect(screen.getByText("Child Content")).toBeInTheDocument();
});

it("should render the content when it's expandable and is expanded by default", () => {
render(
<HvSection title="Section Title" expandable defaultExpanded>
<div>Child Content</div>
</HvSection>
);
expect(screen.queryByText("Child Content")).toBeInTheDocument();
});

it("should not render the content when it's expandable and is not expanded by default", () => {
render(
<HvSection title="Section Title" expandable defaultExpanded={false}>
<div>Child Content</div>
</HvSection>
);
expect(screen.queryByText("Child Content")).not.toBeVisible();
});

it("should toggle the expanded state when the expand button is clicked", async () => {
render(
<HvSection title="Section Title" expandable defaultExpanded={false}>
<div>Child Content</div>
</HvSection>
);

expect(screen.queryByText("Child Content")).not.toBeVisible();

const expandButton = screen.getByRole("button");
fireEvent.click(expandButton);

expect(screen.queryByText("Child Content")).toBeVisible();

fireEvent.click(expandButton);

expect(screen.queryByText("Child Content")).not.toBeVisible();
});

it("should render the actions prop", () => {
const actions = <HvButton>Action 1</HvButton>;
render(<HvSection title="Section Title" actions={actions} />);

expect(screen.getByText("Action 1")).toBeInTheDocument();
});
});

0 comments on commit 1b0ea65

Please sign in to comment.