From 8017f32b054e759bb39c923aabc95540ca271f9a Mon Sep 17 00:00:00 2001 From: Kasper Birch Date: Sat, 1 Jun 2024 14:34:51 +0200 Subject: [PATCH] Add new `banner` component This component contains a required link and title. It can be used with a description and a background image to enhance visual appeal. The title field is a `ReactNode` where the `` (underlined) tag has a custom underline SVG placed beneath it. --- base.scss | 1 + public/icons/basic/icon-underlined-large.svg | 3 + public/icons/basic/icon-underlined.svg | 3 + src/stories/Library/banner/Banner.stories.tsx | 53 +++++++++++++++ src/stories/Library/banner/Banner.tsx | 40 +++++++++++ src/stories/Library/banner/banner.scss | 66 +++++++++++++++++++ src/styles/scss/tools/mixins.tools.scss | 26 ++++++++ 7 files changed, 192 insertions(+) create mode 100644 public/icons/basic/icon-underlined-large.svg create mode 100644 public/icons/basic/icon-underlined.svg create mode 100644 src/stories/Library/banner/Banner.stories.tsx create mode 100644 src/stories/Library/banner/Banner.tsx create mode 100644 src/stories/Library/banner/banner.scss diff --git a/base.scss b/base.scss index 359618de7..85bda840f 100644 --- a/base.scss +++ b/base.scss @@ -142,6 +142,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/banner/banner"; // Autosuggest block styling needs to be loaded before the rest of the scss for autosuggest @import "./src/stories/Blocks/autosuggest/autosuggest"; diff --git a/public/icons/basic/icon-underlined-large.svg b/public/icons/basic/icon-underlined-large.svg new file mode 100644 index 000000000..8352ab299 --- /dev/null +++ b/public/icons/basic/icon-underlined-large.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/basic/icon-underlined.svg b/public/icons/basic/icon-underlined.svg new file mode 100644 index 000000000..d76d2cbf3 --- /dev/null +++ b/public/icons/basic/icon-underlined.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/stories/Library/banner/Banner.stories.tsx b/src/stories/Library/banner/Banner.stories.tsx new file mode 100644 index 000000000..f8862fcd0 --- /dev/null +++ b/src/stories/Library/banner/Banner.stories.tsx @@ -0,0 +1,53 @@ +import { ComponentStory, ComponentMeta } from "@storybook/react"; +import { withDesign } from "storybook-addon-designs"; +import Banner from "./Banner"; +import ImageCredited from "../image-credited/ImageCredited"; + +export default { + title: "Library / Banner", + component: Banner, + decorators: [withDesign], + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/design/Zx9GrkFA3l4ISvyZD2q0Qi/Designsystem?node-id=446-6957&t=IWAOniAbcjV2y2Hf-4", + }, + }, + argTypes: { + image: { + defaultValue: ( + + ), + }, + title: { + name: "Title", + defaultValue: "Hvad skal jeg høre?", + control: { type: "text" }, + }, + description: { + name: "Description", + defaultValue: + "Om du er dedikeret musiknørd eller moderat musikinteresseret, så er dette siden til dig. Her kan du finde anbefalinger, digitale musikmagasiner, nyheder, musiklitteratur og meget mere.", + control: { type: "text" }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Default = Template.bind({}); + +export const NoImage = Template.bind({}); +NoImage.args = { + title: "Title uden billede", + image: undefined, +}; + +export const NoDescription = Template.bind({}); +NoDescription.args = { + title: "Banner uden beskrivelse", + description: undefined, +}; diff --git a/src/stories/Library/banner/Banner.tsx b/src/stories/Library/banner/Banner.tsx new file mode 100644 index 000000000..e9bf05354 --- /dev/null +++ b/src/stories/Library/banner/Banner.tsx @@ -0,0 +1,40 @@ +import { FC, ReactNode } from "react"; +import clsx from "clsx"; +import MediaContainer from "../media-container/MediaContainer"; +import { ReactComponent as ArrowLargeRight } from "../Arrows/icon-arrow-ui/icon-arrow-ui-large-right.svg"; + +type BannerType = { + title: string; + image?: ReactNode; + description?: string; +}; + +const Banner: FC = ({ image, title, description }) => { + return ( + + {image && ( +
+ +
+ )} +
+

) words. + // eslint-disable-next-line react/no-danger + dangerouslySetInnerHTML={{ __html: title }} + /> + {description && ( +

{description}

+ )} + +

+
+ ); +}; + +export default Banner; diff --git a/src/stories/Library/banner/banner.scss b/src/stories/Library/banner/banner.scss new file mode 100644 index 000000000..4656a454f --- /dev/null +++ b/src/stories/Library/banner/banner.scss @@ -0,0 +1,66 @@ +@mixin banner-height { + height: 500px; + @include media-query__medium { + height: 810px; + } +} + +@mixin banner-text-margin { + margin-bottom: $s-md; + @include media-query__medium { + margin-bottom: $s-xl; + } +} + +.banner { + @include layout-container($layout__max-width--large, 0); + @include banner-height; + display: grid; + grid-template-rows: 1fr auto 1fr; + text-decoration: none; +} + +.banner-content { + padding: $s-xl; + max-width: 375px; + background-color: white; + grid-row: 2; + grid-column: 1; + + @include media-query__medium { + padding: $s-2xl; + max-width: 575px; + margin-left: 250px; + } + + &--no-image { + max-width: unset; + background-color: unset; + margin-left: unset; + text-align: center; + } +} + +.banner-content__title { + @include typography($typo__h1); + @include underlined-title; + @include banner-text-margin; +} + +.banner-content__description { + @include typography($typo__body-medium); + @include banner-text-margin; + color: $color__global-grey; +} + +.banner-visual { + grid-row: 1 / -1; + grid-column: 1; + + // Todo: can this be done in drupal? + img { + @include banner-height; + object-fit: cover; + object-position: center; + } +} diff --git a/src/styles/scss/tools/mixins.tools.scss b/src/styles/scss/tools/mixins.tools.scss index a78af14f2..c3f188cec 100644 --- a/src/styles/scss/tools/mixins.tools.scss +++ b/src/styles/scss/tools/mixins.tools.scss @@ -70,3 +70,29 @@ text-overflow: ellipsis; text-align: left; } + +/** + * Mixin `underlined-title` applies a custom underline effect to `` elements. + * - Uses a pseudo-element (::after) with a positioned SVG image as the underline. + * - Removes default text underline. + */ +@mixin underlined-title { + u { + position: relative; + text-decoration: none; + + &::after { + content: ""; + position: absolute; + bottom: -5px; + left: 0; + width: 100%; + height: 10px; + background-size: 100% 100%; + // Underlined icon from public/icons/basic/icon-underlined.svg + // background-image: url("data:image/svg+xml,%3Csvg width='101' height='7' viewBox='0 0 101 7' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1.88867 6C36.7022 -0.286878 63.0591 0.988901 99.1109 1.35819' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A"); + // Underlined icon from public/icons/basic/icon-underlined-large.svg + background-image: url("data:image/svg+xml,%3Csvg width='205' height='8' viewBox='0 0 205 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1.11133 6.87425C73.7223 0.129386 128.695 1.4981 203.889 1.8943' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A"); + } + } +}