From 6ee96b75ebc1ab813177a685b739b07908ee8e31 Mon Sep 17 00:00:00 2001 From: LasseStaus Date: Tue, 7 May 2024 14:00:03 +0200 Subject: [PATCH] wipw --- admin-base.scss | 7 + base.scss | 1 + package.json | 2 +- .../MaterialSearch.stories.tsx | 15 ++ .../material-search/MaterialSearch.tsx | 41 +++ .../MaterialSearchExampleData.ts | 69 +++++ .../MaterialSearchHiddenInputs.tsx | 42 ++++ .../material-search/MaterialSearchInputs.tsx | 55 ++++ .../MaterialSearchListResults.tsx | 62 +++++ .../material-search/MaterialSearchLoading.tsx | 22 ++ .../material-search/MaterialSearchPreview.tsx | 75 ++++++ .../material-search/material-search.scss | 235 ++++++++++++++++++ 12 files changed, 625 insertions(+), 1 deletion(-) create mode 100644 admin-base.scss create mode 100644 src/stories/Library/material-search/MaterialSearch.stories.tsx create mode 100644 src/stories/Library/material-search/MaterialSearch.tsx create mode 100644 src/stories/Library/material-search/MaterialSearchExampleData.ts create mode 100644 src/stories/Library/material-search/MaterialSearchHiddenInputs.tsx create mode 100644 src/stories/Library/material-search/MaterialSearchInputs.tsx create mode 100644 src/stories/Library/material-search/MaterialSearchListResults.tsx create mode 100644 src/stories/Library/material-search/MaterialSearchLoading.tsx create mode 100644 src/stories/Library/material-search/MaterialSearchPreview.tsx create mode 100644 src/stories/Library/material-search/material-search.scss diff --git a/admin-base.scss b/admin-base.scss new file mode 100644 index 000000000..fc0ba6d04 --- /dev/null +++ b/admin-base.scss @@ -0,0 +1,7 @@ +@import "./src/styles/scss/tools"; + +// CSS sheets that are used to style the admin interface. +@import "./src/stories/Library/opening-hours-editor/opening-hours-editor"; +@import "./src/stories/Library/material-search/material-search"; +@import "./src/stories/Library/cover/cover"; +// CSS sheets that are used to style the admin interface.ssds diff --git a/base.scss b/base.scss index 61051826d..d3d35342b 100644 --- a/base.scss +++ b/base.scss @@ -139,6 +139,7 @@ @import "./src/stories/Library/opening-hours/opening-hours-skeleton"; @import "./src/stories/Library/filtered-event-list/filtered-event-list"; @import "./src/stories/Library/event-list-stacked/event-list-stacked"; +@import "./src/stories/Library/material-search/material-search"; // Autosuggest block styling needs to be loaded before the rest of the scss for autosuggest @import "./src/stories/Blocks/autosuggest/autosuggest"; diff --git a/package.json b/package.json index a077cceac..deafea539 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "css:lint": "concurrently 'yarn:css:stylelint' 'yarn:css:prettier -- --check' --raw", "css:lint:watch": "chokidar 'src/**/*.scss' -c 'yarn css:lint'", "css:format": "concurrently 'yarn:css:stylelint -- --fix' 'yarn:css:prettier -- --write' --max-processes 1 --raw", - "css:build": "sass base.scss:src/styles/css/base.css wysiwyg.scss:src/styles/css/wysiwyg.css src/stories/Library/opening-hours-editor/opening-hours-editor.scss:src/styles/css/opening-hours-editor.css --style compressed", + "css:build": "sass base.scss:src/styles/css/base.css wysiwyg.scss:src/styles/css/wysiwyg.css admin-base.scss:src/styles/css/admin-base.css --style compressed", "css:watch": "yarn css:build -- --watch", "build": "concurrently 'yarn:css:build' --raw", "markdown:lint": "markdownlint-cli2", diff --git a/src/stories/Library/material-search/MaterialSearch.stories.tsx b/src/stories/Library/material-search/MaterialSearch.stories.tsx new file mode 100644 index 000000000..e0f95f241 --- /dev/null +++ b/src/stories/Library/material-search/MaterialSearch.stories.tsx @@ -0,0 +1,15 @@ +import { ComponentMeta, ComponentStory } from "@storybook/react"; + +import MaterialSearch from "./MaterialSearch"; + +export default { + title: "Library / Material Search", + component: MaterialSearch, + argTypes: {}, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + +); + +export const Default = Template.bind({}); diff --git a/src/stories/Library/material-search/MaterialSearch.tsx b/src/stories/Library/material-search/MaterialSearch.tsx new file mode 100644 index 000000000..72b0af1dd --- /dev/null +++ b/src/stories/Library/material-search/MaterialSearch.tsx @@ -0,0 +1,41 @@ +import { FC, useState } from "react"; +import { PreviewData } from "./MaterialSearchExampleData"; +import MaterialSearchHiddenInputs from "./MaterialSearchHiddenInputs"; +import MaterialSearchInputs from "./MaterialSearchInputs"; +import MaterialSearchListResults from "./MaterialSearchListResults"; +import MaterialSearchPreview from "./MaterialSearchPreview"; + +const MaterialSearch: FC = () => { + const [searchInput, setSearchInput] = useState(""); + const [selectedWorkId, setSelectedWorkId] = useState(""); + const [selectedMaterialType, setSelectedMaterialType] = useState(""); + + const handleSetSearchInput = (value: string) => { + setSearchInput(value); + setSelectedWorkId(PreviewData.workId); + }; + + return ( +
+ 1 ? selectedWorkId : ""} + selectedMaterialType={selectedMaterialType} + /> + +
+ 1} /> + setSelectedWorkId(workId)} + showListResults={searchInput.length > 1} + /> +
+
+ ); +}; + +export default MaterialSearch; diff --git a/src/stories/Library/material-search/MaterialSearchExampleData.ts b/src/stories/Library/material-search/MaterialSearchExampleData.ts new file mode 100644 index 000000000..db1281874 --- /dev/null +++ b/src/stories/Library/material-search/MaterialSearchExampleData.ts @@ -0,0 +1,69 @@ +export const MaterialTypes = [ + "bog", + "artikel", + "tidsskrift", + "lydbog", + "film", + "musik", + "spil", +]; + +export interface PreviewDataProps { + coverUrl: string; + title: string; + author: string; + publicationYear: number; + source: string; + workId: string; +} +export const PreviewData: PreviewDataProps = { + coverUrl: "images/book_cover_6.jpg", + title: "Rødhals", + author: "Jo Nesbø", + publicationYear: 2022, + source: "InterLibraryLoan", + workId: "work-of:800010-katalog:99122475830405763", +}; + +export const ListResultData: PreviewDataProps[] = [ + { + coverUrl: "images/book_cover_1.jpg", + title: "Book Title 1", + author: "Author 1", + publicationYear: 2021, + source: "Library", + workId: "work-of:800010-katalog:99122475830405761", + }, + { + coverUrl: "images/book_cover_2.jpg", + title: "Book Title 2", + author: "Author 2", + publicationYear: 2020, + source: "InterLibraryLoan", + workId: "work-of:800010-katalog:2389129", + }, + { + coverUrl: "images/book_cover_3.jpg", + title: "Book Title 3", + author: "Author 3", + publicationYear: 2019, + source: "Library", + workId: "work-of:800010-katalog:98432897", + }, + { + coverUrl: "images/book_cover_4.jpg", + title: "Book Title 4", + author: "Author 4", + publicationYear: 2018, + source: "InterLibraryLoan", + workId: "work-of:800010-katalog:9778817", + }, + { + coverUrl: "images/book_cover_5.jpg", + title: "Book Title 5", + author: "Author 5", + publicationYear: 2017, + source: "Library", + workId: "work-of:800010-katalog:2093", + }, +]; diff --git a/src/stories/Library/material-search/MaterialSearchHiddenInputs.tsx b/src/stories/Library/material-search/MaterialSearchHiddenInputs.tsx new file mode 100644 index 000000000..75eeadae8 --- /dev/null +++ b/src/stories/Library/material-search/MaterialSearchHiddenInputs.tsx @@ -0,0 +1,42 @@ +interface MaterialSearchHiddenInputsProps { + selectedWorkId: string; + selectedMaterialType: string; +} + +const MaterialSearchHiddenInputs = ({ + selectedWorkId, + selectedMaterialType, +}: MaterialSearchHiddenInputsProps) => { + // These hidden inputs are used to store the selected work ID and material type in the drupal form + // In react, they are located in the storybook component, but in the drupal form they are created in the field widget and hidden with css. + return ( +
+ + +
+ ); +}; + +export default MaterialSearchHiddenInputs; diff --git a/src/stories/Library/material-search/MaterialSearchInputs.tsx b/src/stories/Library/material-search/MaterialSearchInputs.tsx new file mode 100644 index 000000000..972c8925a --- /dev/null +++ b/src/stories/Library/material-search/MaterialSearchInputs.tsx @@ -0,0 +1,55 @@ +import { MaterialTypes } from "./MaterialSearchExampleData"; + +interface MaterialSearchInputsProps { + searchInput: string; + selectedMaterialType: string; + setSearchInput: (value: string) => void; + onMaterialTypeChange: (value: string) => void; +} + +const MaterialSearchInputs = ({ + searchInput, + selectedMaterialType, + setSearchInput, + onMaterialTypeChange, +}: MaterialSearchInputsProps) => { + return ( +
+ + +
+ ); +}; + +export default MaterialSearchInputs; diff --git a/src/stories/Library/material-search/MaterialSearchListResults.tsx b/src/stories/Library/material-search/MaterialSearchListResults.tsx new file mode 100644 index 000000000..39d06820a --- /dev/null +++ b/src/stories/Library/material-search/MaterialSearchListResults.tsx @@ -0,0 +1,62 @@ +import Cover from "../cover/Cover"; +import { ListResultData } from "./MaterialSearchExampleData"; +import MaterialSearchLoading from "./MaterialSearchLoading"; + +interface MaterialSearchListResultsProps { + onWorkIdSelect: (workId: string) => void; + showListResults: boolean; +} + +const MaterialSearchListResults = ({ + onWorkIdSelect, + showListResults, +}: MaterialSearchListResultsProps) => { + const handleClickOnListItem = (workId: string) => { + onWorkIdSelect(workId); + }; + + return !showListResults ? ( +
No results found.
+ ) : ( +
+
Total results: 5
+
    + {ListResultData.map((work) => { + return ( +
  • + +
  • + ); + })} +
  • + +
  • +
+
+ ); +}; + +export default MaterialSearchListResults; diff --git a/src/stories/Library/material-search/MaterialSearchLoading.tsx b/src/stories/Library/material-search/MaterialSearchLoading.tsx new file mode 100644 index 000000000..e27cb4cac --- /dev/null +++ b/src/stories/Library/material-search/MaterialSearchLoading.tsx @@ -0,0 +1,22 @@ +import LoadingLogo from "../../../public/icons/logo/reload_logo_black.svg"; + +interface ReloadLoadingIconProps { + loadingText?: string; +} + +const MaterialSearchLoading = ({ loadingText }: ReloadLoadingIconProps) => { + return ( +
+ Loading... + {loadingText && ( + {loadingText} + )} +
+ ); +}; + +export default MaterialSearchLoading; diff --git a/src/stories/Library/material-search/MaterialSearchPreview.tsx b/src/stories/Library/material-search/MaterialSearchPreview.tsx new file mode 100644 index 000000000..1f57691f3 --- /dev/null +++ b/src/stories/Library/material-search/MaterialSearchPreview.tsx @@ -0,0 +1,75 @@ +import { useState, useEffect } from "react"; +import Cover from "../cover/Cover"; +import { PreviewData } from "./MaterialSearchExampleData"; +import MaterialSearchLoading from "./MaterialSearchLoading"; // Assuming this is the correct import + +interface MaterialSearchPreviewProps { + displayMaterial: boolean; +} + +const MaterialSearchPreview = ({ + displayMaterial, +}: MaterialSearchPreviewProps) => { + const [fictiveLoading, setFictiveLoading] = useState(false); + + useEffect(() => { + if (displayMaterial) { + setFictiveLoading(true); + const timer = setTimeout(() => { + setFictiveLoading(false); + }, 1500); + + return () => clearTimeout(timer); + } + + return () => {}; + }, [displayMaterial]); + + if (fictiveLoading) { + return ( +
+
+ +
+
+ ); + } + + return ( +
+ {displayMaterial ? ( +
+ +
+ + Title: + + {PreviewData.title} + + Author: + + {PreviewData.author} + + Publication Year: + + {PreviewData.publicationYear} + + Source: + + {PreviewData.source} + + Work Id: + + {PreviewData.workId} +
+
+ ) : ( +
+ No material selected +
+ )} +
+ ); +}; + +export default MaterialSearchPreview; diff --git a/src/stories/Library/material-search/material-search.scss b/src/stories/Library/material-search/material-search.scss new file mode 100644 index 000000000..c1f5882c1 --- /dev/null +++ b/src/stories/Library/material-search/material-search.scss @@ -0,0 +1,235 @@ +// Variables +$_gin_xs_spacing: 0.5rem; +$_gin_m_spacing: 1rem; +$_gin_border_color: #8e929c; +$_gin_font_weight_semibold: 525; + +$_color__background-light: #eeeeed; +$_color__hover-background: #8e929c; + +$_box-shadow1: 1px 1px 5px 1px rgba(0, 0, 0, 0.1); +$_box-shadow: 0px 0px 0px 2px rgba(0, 0, 0, 0.25); +$_loading-spinner-size: 40px; + +// These heights are explicitly set to covers used in the material search. +// The reason for this is, that the covers must all use "large" as their size +// prop, because this prop is used to determine which size of image is +// displayed in the cover as provided by cover-service. +// Example: A small "size" prop is used on the listResults and preview in +// material search, but the component being used for displaying the materials +// (I.e RecommendedMaterial) has its' prop set to "large", then image will +// potentially not be displayed at all, since a "large" version is not served +// by cover-service, but a small version is. + +// Arguably, this is a flaw in cover service, and should be fixed there, but +// since there is no solution to this, and it potentially could happen for the +// vast majority of materials I have decided to manually overwrite this here, +// to ensure that the covers are displayed correctly and avoid frustration. +$MATERIAL_SMALL_MOBILE: 104px; +$MATERIAL_SMALL_DESKTOP: 137px; + +$list_header_height: 50px; + +// Material Search +.material-search { + max-width: 800px; + margin-bottom: $_gin_m_spacing; +} + +.material-search__label { + display: flex; + flex-direction: column; + gap: $_gin_xs_spacing; + margin-bottom: $_gin_xs_spacing; + font-weight: $_gin_font_weight_semibold; +} + +.material-search__inputs-container { + display: grid; + grid-template-columns: 2fr minmax(auto, 220px); + gap: $_gin_m_spacing; + margin-bottom: $_gin_xs_spacing; +} + +.material-search__inputs-container--hidden { + display: none; // This class is only applied in Drupal. +} + +// Preview +.material-search__preview { + color: $color__global-grey; + cursor: not-allowed; + display: grid; + background-color: $color__global-white; + min-height: 170px; + align-items: center; + border: 1px solid $_gin_border_color; + border-radius: $_gin_xs_spacing; + overflow: hidden; +} + +.material-search__preview-material { + display: grid; + padding: 0 $_gin_m_spacing; + grid-template-columns: min-content auto; + column-gap: $_gin_m_spacing; + align-items: center; + height: 100%; + + .cover { + height: $MATERIAL_SMALL_MOBILE; + @media screen and (min-width: 768px) { + height: $MATERIAL_SMALL_DESKTOP; + } + } +} + +.material-search__preview-empty { + height: 100%; + display: flex; + justify-content: center; + background: $color__global-tertiary-1; + align-items: center; + color: $color__global-grey; +} + +.material-search__preview-loading { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + flex-direction: column; +} + +.material-search__input, +.material-search__selector { + border: 1px solid $_gin_border_color; + padding: $_gin_xs_spacing; + border-radius: $_gin_xs_spacing; + height: 100%; + font-family: inherit; + + &:disabled { + background-color: $color__global-tertiary-1; + } +} + +.material-search__preview-content { + display: grid; + grid-template-columns: max-content auto; + column-gap: $_gin_m_spacing; +} + +.material-search__preview-content__label { + font-weight: 700; +} + +// Loading +.material-search__loading { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.material-search__loading-spinner { + width: $_loading-spinner-size; + height: $_loading-spinner-size; + animation: spin 0.9s linear infinite; + animation-timing-function: ease-in-out; +} + +.material-search__loading-text { + font-size: 1rem; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +// Search List +.material-search__list { + border: 1px solid $_gin_border_color; + margin-top: $_gin_m_spacing; + overflow: hidden; + border-radius: $_gin_xs_spacing; + background-color: $color__global-white; + position: relative; + overflow-y: auto; + height: 100%; + max-height: 500px; +} + +.material-search__list-results { + display: flex; + flex-direction: column; + justify-content: left; + row-gap: $_gin_xs_spacing; + padding: $_gin_xs_spacing; + padding-bottom: 20px; + background-color: $_color__background-light; + margin: 0; +} + +.material-search__list-result-button { + width: 100%; + background-color: $color__global-white; + border: 1px solid $_gin_border_color; + border-radius: $_gin_xs_spacing; + padding: $_gin_xs_spacing; + display: grid; + grid-template-columns: max-content auto; + column-gap: $_gin_m_spacing; + justify-items: baseline; + align-items: center; + transition: scale 0.1s ease-in-out, box-shadow 0.1s ease-in-out; + + &:hover, + &:focus { + transform: scale(1.005); + box-shadow: $_box-shadow1; + cursor: pointer; + } +} + +.material-search__list-header { + display: flex; + width: 100%; + border-bottom: 1px solid $_gin_border_color; + height: $list_header_height; + align-items: center; + padding: 0 $_gin_m_spacing; + position: sticky; + top: 0; + left: 0; + z-index: 10; + background: $color__global-white; + gap: $_gin_xs_spacing; +} + +.material-search__list-result-content { + display: grid; + grid-template-columns: max-content auto; + column-gap: $_gin_m_spacing; + align-items: center; + justify-items: baseline; + text-align: left; + font-size: 14px; +} + +.material-search__list-result-image-content img, +.material-search__list-result-image-content .cover { + height: calc($MATERIAL_SMALL_MOBILE / 2); + @media screen and (min-width: 768px) { + height: calc($MATERIAL_SMALL_DESKTOP / 2); + } +} + +.material-search__list-result--loading { + margin-top: $_gin_m_spacing; +}