diff --git a/base.scss b/base.scss index ba740b98f..8eb1ce1f1 100644 --- a/base.scss +++ b/base.scss @@ -137,6 +137,7 @@ @import "./src/stories/Library/opening-hours-editor/opening-hours-editor"; @import "./src/stories/Library/opening-hours/opening-hours"; @import "./src/stories/Library/opening-hours/opening-hours-skeleton"; +@import "./src/stories/Library/filtered-event-list/filtered-event-list"; // Autosuggest block styling needs to be loaded before the rest of the scss for autosuggest @import "./src/stories/Blocks/autosuggest/autosuggest"; diff --git a/src/stories/Library/filtered-event-list/FilteredEventList.stories.tsx b/src/stories/Library/filtered-event-list/FilteredEventList.stories.tsx new file mode 100644 index 000000000..0e3a74fbc --- /dev/null +++ b/src/stories/Library/filtered-event-list/FilteredEventList.stories.tsx @@ -0,0 +1,43 @@ +import { ComponentMeta, ComponentStory } from "@storybook/react"; +import FilteredEventList from "./FilteredEventlist"; +import FilteredListData from "./FilteredEventListData"; + +export default { + title: "Library/ Filtered Event List", + + component: FilteredEventList, + argTypes: { + title: { + defaultValue: "Aktiviteter på biblioteket", + control: "text", + description: "Title of the recommendation", + }, + events: { + defaultValue: FilteredListData, + control: "object", + description: "List of events to be displayed", + }, + buttonText: { + defaultValue: "Se alle", + control: "text", + description: "Text for the button", + }, + buttonShowLessText: { + defaultValue: "Se færre", + control: "text", + description: "Text for the button", + }, + }, + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/file/Zx9GrkFA3l4ISvyZD2q0Qi/Designsystem?type=design&node-id=7567-80202&mode=design&t=eZs7Tgx4a1ebZQiO-4", + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + +); + +export const FilteredList = Template.bind({}); diff --git a/src/stories/Library/filtered-event-list/FilteredEventListData.tsx b/src/stories/Library/filtered-event-list/FilteredEventListData.tsx new file mode 100644 index 000000000..2f42f62da --- /dev/null +++ b/src/stories/Library/filtered-event-list/FilteredEventListData.tsx @@ -0,0 +1,59 @@ +import { ContentListItemProps } from "../content-list-item/ContentListItem"; +import ImageCredited from "../image-credited/ImageCredited"; + +const FilteredListData: ContentListItemProps[] = [ + { + image: ( + + ), + tagText: "Foredrag", + title: "Kunst og kultur i middelalderen", + description: "En dybdegåendenalysef kunst og kultur i middelalderen.", + location: "Kulturhuset", + price: "50 - 100 KR", + href: "/", + date: "2023-01-10", + time: "15:00 - 17:00", + }, + { + image: ( + + ), + tagText: "arrangement", + title: "Fars Legestue", + description: "Kom forbi til hygge i Fars Legestue", + location: "Hovedbiblioteket", + price: "60 KR", + href: "/", + date: "2023-01-12", + time: "18:00 - 20:00", + }, + { + image: ( + + ), + tagText: "arrangement", + title: "Fars Legestue", + description: "Kom forbi til hygge i Fars Legestue", + location: "Hovedeblibloteket", + price: "60 KR", + href: "/", + date: "2023-01-13", + time: "18:00 - 20:00", + }, + { + image: ( + + ), + tagText: "arrangement", + title: "Fars Legestue", + description: "Kom forbi til hygge i Fars Legestue", + location: "Hovedeblibloteket", + price: "60 KR", + href: "/", + date: "2023-01-14", + time: "18:00 - 20:00", + }, +]; + +export default FilteredListData; diff --git a/src/stories/Library/filtered-event-list/FilteredEventlist.tsx b/src/stories/Library/filtered-event-list/FilteredEventlist.tsx new file mode 100644 index 000000000..b16e75792 --- /dev/null +++ b/src/stories/Library/filtered-event-list/FilteredEventlist.tsx @@ -0,0 +1,52 @@ +import React, { useEffect } from "react"; +import { + ContentListItem, + ContentListItemProps, +} from "../content-list-item/ContentListItem"; + +interface PromoteEventsListProps { + title: string; + events: ContentListItemProps[]; + buttonText: string; + buttonShowLessText?: string; +} + +const PromoteEventsList: React.FC = ({ + title, + events, + buttonText, + buttonShowLessText, +}) => { + useEffect(() => { + require("../../utils/show-more"); + }, []); + + return ( +
+

{title}

+
    + {events.map((event) => ( +
  • + +
  • + ))} +
+ +
+ ); +}; +export default PromoteEventsList; diff --git a/src/stories/Library/filtered-event-list/filtered-event-list.scss b/src/stories/Library/filtered-event-list/filtered-event-list.scss new file mode 100644 index 000000000..cd660a548 --- /dev/null +++ b/src/stories/Library/filtered-event-list/filtered-event-list.scss @@ -0,0 +1,28 @@ +.filtered-event-list { + @include layout-container($layout__max-width--medium); +} +.filtered-event-list__heading { + @include typography($typo__h2); + margin-bottom: $s-xl; +} +.filtered-event-list__list-item { + border-bottom: 1px solid $color__global-tertiary-1; + + &--hidden { + display: none; + } + + &:first-child { + border-top: 1px solid $color__global-tertiary-1; + } +} + +.filtered-event-list__button { + width: min-content; + margin: 0 auto; + margin-top: $s-xl; + + &--hidden { + display: none; + } +} diff --git a/src/stories/Library/tag/tag-list/TagList.tsx b/src/stories/Library/tag/tag-list/TagList.tsx index d87243714..da8696978 100644 --- a/src/stories/Library/tag/tag-list/TagList.tsx +++ b/src/stories/Library/tag/tag-list/TagList.tsx @@ -17,8 +17,13 @@ const TagList: FC = ({ tags }) => { return ( <> {tags.length > 1 && ( -
-
    +
    +
      {tags.map((tag, index) => (
    • @@ -31,6 +36,8 @@ const TagList: FC = ({ tags }) => { className="tag tag--fill cursor-pointer" aria-expanded="false" data-show-more-button + data-show-more-text="..." + data-show-less-text="-" > ... diff --git a/src/stories/utils/show-more.js b/src/stories/utils/show-more.js index b5285ca87..68efd6aa6 100644 --- a/src/stories/utils/show-more.js +++ b/src/stories/utils/show-more.js @@ -1,41 +1,73 @@ document.addEventListener("DOMContentLoaded", () => { - const lists = document.querySelectorAll("[data-show-more-list]"); + const listWrappers = document.querySelectorAll( + "[data-show-more-list-wrapper]" + ); - lists.forEach((list) => { - const uid = Math.floor(Math.random() * 1000000); - list.setAttribute("id", `category-list-${uid}`); + listWrappers.forEach((listWrapper) => { + const list = listWrapper.querySelector("[data-show-more-list]"); + const listId = list.getAttribute("data-show-more-list-id"); + const listElements = list.querySelectorAll("[data-show-more-item]"); + const amountOfListElements = parseInt(listElements.length, 10); + const listShowMoreButton = listWrapper.querySelector( + "[data-show-more-button]" + ); - const categories = list.querySelectorAll("[data-show-more-item]"); - const toggleButton = list.querySelector("[data-show-more-button]"); - toggleButton.setAttribute("id", `category-toggle-${uid}`); + if (!list || !listShowMoreButton || !listElements || !listId) { + const missingElements = []; + if (!list) missingElements.push("list"); + if (!listShowMoreButton) missingElements.push("listShowMoreButton"); + if (!listElements.length) missingElements.push("listElements"); + if (!listId) missingElements.push("listId"); + + // eslint-disable-next-line no-console + console.debug( + `show-more.js: Missing required elements: ${missingElements.join(", ")}` + ); + return; + } + + // Add listId & aria attributes to list and buttons + list.setAttribute("id", `list-id-${listId}`); + listShowMoreButton.setAttribute("aria-controls", `list-id-${listId}`); const initialVisibleItems = - list.getAttribute("data-show-more-initial-visible-items") || 2; // default to 2 if not set + list.getAttribute("data-initial-visible-items") || amountOfListElements; - // if there are no categories or only one category, hide the toggle button - // or if there are less categories than the initial visible items, hide the toggle button - if (!categories.length || categories.length <= initialVisibleItems) { - toggleButton.classList.add("show-more__hidden"); + // Hide button if there are less items than the initial visible items + if (initialVisibleItems >= amountOfListElements) { + listShowMoreButton.classList.add("show-more__hidden"); return; } - const showMoreText = - toggleButton.getAttribute("data-show-more-text") || "+"; - const hideMoreText = - toggleButton.getAttribute("data-show-more-hide-text") || "-"; - const visibleItems = initialVisibleItems - 1; - - categories.forEach((item, index) => { - if (index > visibleItems) item.classList.add("show-more__hidden"); + // Hide items beyond the initial visible items + listElements.forEach((listItem, index) => { + if (index > initialVisibleItems - 1) + listItem.classList.add("show-more__hidden"); }); - toggleButton.addEventListener("click", () => { - const isExpanded = toggleButton.getAttribute("aria-expanded") === "true"; - categories.forEach((item, index) => { - if (index > visibleItems) item.classList.toggle("show-more__hidden"); + const showMoreText = listShowMoreButton.getAttribute("data-show-more-text"); + const showLessText = listShowMoreButton.getAttribute("data-show-less-text"); + const hideListButtonAfterExpand = list.getAttribute( + "data-hide-list-button-after-expand" + ); + + listShowMoreButton.addEventListener("click", () => { + listElements.forEach((listItem, index) => { + if (index > initialVisibleItems - 1) + listItem.classList.toggle("show-more__hidden"); }); - toggleButton.innerText = isExpanded ? showMoreText : hideMoreText; - toggleButton.setAttribute("aria-expanded", !isExpanded); + + const isAriaExpanded = + listShowMoreButton.getAttribute("aria-expanded") === "true"; + listShowMoreButton.setAttribute("aria-expanded", !isAriaExpanded); + + listShowMoreButton.innerText = isAriaExpanded + ? showMoreText + : showLessText; + + if (hideListButtonAfterExpand === "true" && !isAriaExpanded) { + listShowMoreButton.classList.add("show-more__hidden"); + } }); }); }); diff --git a/src/stories/utils/show-more.md b/src/stories/utils/show-more.md index 4d0ae3d4c..4753808af 100644 --- a/src/stories/utils/show-more.md +++ b/src/stories/utils/show-more.md @@ -9,33 +9,45 @@ Follow these steps to implement this feature effectively: Structure your HTML elements as follows for implementing the "Show More/Less" feature: -- **List Element**: -Use an wrapper element with the `data-show-more-list` attribute -This element will contain both the list items and the toggle button. +- **List wrapper**: +Assign this value to the element that will wrap both the list and the button +associated with the list. Use a wrapper element with the +`data-show-more-list-wrapper` attribute. +- **List**: +Assign this to the list you want to control. Include the following data +attributes for the element: + - `data-show-more-list`: Indicates that this list is controlled by the show + more/less functionality. + - `[data-show-more-list-id`: Indicates a an ID for the list. The ID is used + to set aria-controls on the button to the list, and sets a list-id-X + attribute on the list. + - `data-initial-visible-items`: Specifies the initial number of items visible. + This is optional and defaults to the total number of items if not set. + - `data-hide-list-button-after-expand="true"`: Specifies whether to hide the + button after expanding the list. Add this with value of `true` if you do + not want the button to be displayed after the list is +expanded, otherwise do not add it. - **List Items**: Assign the `data-show-more-item` attribute to each list item that you want to show or hide. - **Toggle Button**: -Place a button within the list element with the `data-show-more-button` +Place a button within the list element with the data-show-more-button attribute. This button is used by users to toggle the visibility of list items. -- **ARIA Expanded**: -Add the `aria-expanded="false"` attribute to the button to indicate the current -state (expanded or collapsed) of the additional content. -- **Show More Text (Optional)**: -Use the `data-show-more-text` attribute on the button to specify the text -displayed when there's more content to show. -- **Show Less Text (Optional)**: -Use the `data-show-more-hide-text` attribute on the button to specify the text -displayed when the list is in its collapsed state. -- **Initial Visible Items (Optional)**: -Set the initial number of visible items with the -`data-show-more-initial-visible-items` attribute. It defaults to 2 if not set. + - `data-show-more-text`: Specifies the text displayed on the button when + there are more items to show. + - `data-show-less-text`: Specifies the text displayed on the button when + the list is fully expanded. +- **AARIA Attributes**: + - `aria-expanded`: Add the aria-expanded="false" attribute to the button to + indicate the initial state (expanded or collapsed) of the additional content. + - `aria-controls`: This will be added to the button with a reference to + the list it controls. ### Example HTML Markup ```html -
      -
        +
        +
        • Item 1
        • Item 2
        • @@ -44,12 +56,12 @@ Set the initial number of visible items with the
        + ``````