Skip to content

Commit

Permalink
feat: add details summary component
Browse files Browse the repository at this point in the history
this is a collapsible html tag
  • Loading branch information
pgurusinga committed May 10, 2024
1 parent 7aec747 commit facaaaa
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 0 deletions.
23 changes: 23 additions & 0 deletions app/components/DetailsSummary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import RichText from "./RichText";
import { z } from "zod";

export const DetailsSummarySchema = z.object({
identifier: z.string().optional(),
title: z.string().optional(),
content: z.string().optional(),
});

type DetailsSummaryProps = z.infer<typeof DetailsSummarySchema>;

export const DetailsSummary = ({ title, content }: DetailsSummaryProps) => {
return (
<details className="details focus-within:outline focus-within:outline-4 focus-within:outline-offset-4 focus-within:outline-blue-800 text-blue-800 ds-label-01-bold">
<summary className="summary-content focus:outline-none cursor-pointer">
{title}
</summary>
<p className="block ds-label-01-reg ml-[0.325rem] border-l-2 border-solid border-grey-300 pl-12 pt-8 text-black">
{content && <RichText markdown={content} />}
</p>
</details>
);
};
4 changes: 4 additions & 0 deletions app/components/InfoBoxItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import Heading, { HeadingPropsSchema } from "./Heading";
import Image, { ImagePropsSchema } from "./Image";
import RichText from "./RichText";
import ButtonContainer from "./ButtonContainer";
import { DetailsSummary, DetailsSummarySchema } from "./DetailsSummary";

export const InfoBoxItemPropsSchema = z.object({
identifier: z.string().optional(),
label: HeadingPropsSchema.optional(),
headline: HeadingPropsSchema.optional(),
image: ImagePropsSchema.optional(),
content: z.string().optional(),
detailsSummary: DetailsSummarySchema.optional(),
buttons: z.array(ButtonPropsSchema).optional(),
});

Expand All @@ -22,6 +24,7 @@ const InfoBoxItem = ({
headline,
image,
content,
detailsSummary,
buttons,
}: InfoBoxItemProps) => {
return (
Expand Down Expand Up @@ -49,6 +52,7 @@ const InfoBoxItem = ({
{label && <Heading {...label} />}
{headline && <Heading {...headline} />}
{content && <RichText markdown={content} />}
{detailsSummary && <DetailsSummary {...detailsSummary} />}
{buttons && buttons.length > 0 && (
<ButtonContainer>
{buttons.map((button) => (
Expand Down
3 changes: 3 additions & 0 deletions app/components/PageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { StrapiTimeInput } from "~/services/cms/components/StrapiTimeInput";
import { StrapiFileInput } from "~/services/cms/components/StrapiFileInput";
import { StrapiInlineNotice } from "~/services/cms/components/StrapiInlineNotice";
import StrapiSuggestionInput from "~/services/cms/components/StrapiSuggestionInput";
import { StrapiDetailsSummary } from "~/services/cms/components/StrapiDetailsSummary";

export type StrapiContent = StrapiContentComponent | StrapiFormComponent;

Expand Down Expand Up @@ -113,6 +114,8 @@ function cmsToReact(strapiContent: StrapiContent) {
return <List {...getListProps(strapiContent)} />;
case "page.inline-notice":
return <StrapiInlineNotice {...strapiContent} />;
case "page.details-summary":
return <StrapiDetailsSummary {...strapiContent} />;
default:
return <></>;
}
Expand Down
27 changes: 27 additions & 0 deletions app/services/cms/components/StrapiDetailsSummary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { z } from "zod";
import { HasOptionalStrapiIdSchema } from "../models/HasStrapiId";
import { OptionalStrapiLinkIdentifierSchema } from "../models/HasStrapiLinkIdentifier";
import { DetailsSummary } from "~/components/DetailsSummary";
import { omitNull } from "~/util/omitNull";

export const StrapiDetailsSummarySchema = z
.object({
title: z.string().nullable(),
content: z.string().nullable(),
})
.merge(HasOptionalStrapiIdSchema)
.merge(OptionalStrapiLinkIdentifierSchema);

type StrapiDetailsSummary = z.infer<typeof StrapiDetailsSummarySchema>;

export const StrapiDetailsSummaryComponentSchema =
StrapiDetailsSummarySchema.extend({
__component: z.literal("page.details-summary"),
});

export const StrapiDetailsSummary = (

Check warning on line 22 in app/services/cms/components/StrapiDetailsSummary.tsx

View workflow job for this annotation

GitHub Actions / code-quality / npm run lint

'StrapiDetailsSummary' is assigned a value but never used
strapiDetailsSummary: StrapiDetailsSummary,
) => {
const props = omitNull(strapiDetailsSummary);
return <DetailsSummary {...props} />;
};
2 changes: 2 additions & 0 deletions app/services/cms/models/StrapiContentComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { StrapiLinkListBoxComponentSchema } from "./StrapiLinkListBox";
import { StrapiListComponentSchema } from "./StrapiList";
import { StrapiArraySummaryComponentSchema } from "./StrapiArraySummary";
import { StrapiInlineNoticeComponentSchema } from "../components/StrapiInlineNotice";
import { StrapiDetailsSummaryComponentSchema } from "../components/StrapiDetailsSummary";

export const StrapiContentComponentSchema = z.discriminatedUnion(
"__component",
Expand All @@ -25,6 +26,7 @@ export const StrapiContentComponentSchema = z.discriminatedUnion(
StrapiListComponentSchema,
StrapiArraySummaryComponentSchema,
StrapiInlineNoticeComponentSchema,
StrapiDetailsSummaryComponentSchema,
],
);

Expand Down
2 changes: 2 additions & 0 deletions app/services/cms/models/StrapiInfoBoxItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { OptionalStrapiLinkIdentifierSchema } from "./HasStrapiLinkIdentifier";
import { omitNull } from "~/util/omitNull";
import { InfoBoxItemPropsSchema } from "~/components/InfoBoxItem";
import { type StrapiElementWithId } from "./StrapiElementWithId";
import { StrapiDetailsSummarySchema } from "../components/StrapiDetailsSummary";

export const StrapiInfoBoxItemSchema = z
.object({
label: StrapiHeadingSchema.nullable(),
headline: StrapiHeadingSchema.nullable(),
image: StrapiImageSchema.nullable(),
content: z.string().nullable(),
detailsSummary: StrapiDetailsSummarySchema.nullable(),
buttons: z.array(StrapiButtonSchema).nullable(),
})
.merge(HasOptionalStrapiIdSchema)
Expand Down
1 change: 1 addition & 0 deletions app/services/errorPages/fallbackInfobox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const fallbackStrapiInfoBox = {
items: [
{
label: { text: "500", look: "ds-label-01-reg", tagName: "div" },
detailsSummary: null,
headline: {
text: "Unerwarteter Fehler",
look: "ds-heading-02-reg",
Expand Down
14 changes: 14 additions & 0 deletions app/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,20 @@ svg {
@apply text-black;
}

/* when focus without the keyboard, remove the outline of the children*/
.details:not(:has(.summary-content:focus-visible)) {
@apply outline-none;
}

/* Only apply hover effect on devices that support it, see https://stackoverflow.com/a/28058919 */
@media (hover: hover) {
.summary-content:hover {
@apply underline;
@apply underline-offset-2;
@apply decoration-2;
}
}

.increase-tap-area {
@apply relative;
@apply inline-block;
Expand Down
32 changes: 32 additions & 0 deletions stories/DetailsSummary.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { Meta, StoryObj } from "@storybook/react";
import { DetailsSummary } from "../app/components/DetailsSummary";
import { remixContext } from "../.storybook/remixContext";
import Container from "~/components/Container";

const meta = {
title: "Component/DetailsSummary",
component: DetailsSummary,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof DetailsSummary>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
title: "Lorem ipsum dolor sit amet?",
content:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
},
decorators: [
(Story) => (
<Container paddingTop="24" paddingBottom="64">
{remixContext(Story)}
</Container>
),
],
};
24 changes: 24 additions & 0 deletions tests/unit/components/DetailsSummary.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { render, screen, fireEvent } from "@testing-library/react";
import { DetailsSummary } from "~/components/DetailsSummary";

describe("DetailsSummary", () => {
it("renders title and content correctly", () => {
render(<DetailsSummary title="Test Title" content="Test Content" />);
expect(screen.getByText("Test Title")).toBeInTheDocument();
expect(screen.getByText("Test Content")).toBeInTheDocument();
});

it("renders RichText with correct markdown", () => {
render(<DetailsSummary title="Test Title" content="**Test Markdown**" />);
expect(screen.getByText("Test Markdown")).toBeInTheDocument();
});

it("toggles visibility of content on summary click", () => {
render(<DetailsSummary title="Test Title" content="Test Content" />);
const summaryElement = screen.getByText("Test Title");
fireEvent.click(summaryElement);
expect(screen.getByText("Test Content")).toBeVisible();
fireEvent.click(summaryElement);
expect(screen.getByText("Test Content")).not.toBeVisible();
});
});

0 comments on commit facaaaa

Please sign in to comment.