From 912e0aebbc12d66372975f0505a46d1fee9953a4 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 17 Mar 2026 22:27:40 +0100 Subject: [PATCH 1/2] Add image tile compnenet --- .../ImageTile/ImageTile.module.scss | 53 ++++++++++++++++++ .../src/Components/ImageTile/ImageTile.tsx | 55 +++++++++++++++++++ frontend/src/Components/ImageTile/index.ts | 1 + 3 files changed, 109 insertions(+) create mode 100644 frontend/src/Components/ImageTile/ImageTile.module.scss create mode 100644 frontend/src/Components/ImageTile/ImageTile.tsx create mode 100644 frontend/src/Components/ImageTile/index.ts diff --git a/frontend/src/Components/ImageTile/ImageTile.module.scss b/frontend/src/Components/ImageTile/ImageTile.module.scss new file mode 100644 index 000000000..d4a68f602 --- /dev/null +++ b/frontend/src/Components/ImageTile/ImageTile.module.scss @@ -0,0 +1,53 @@ +@use 'src/constants' as *; + +@use 'src/mixins' as *; + +.imageContainer { + @include rounded; + @include shadow-light; + display: flex; + flex-direction: column; + justify-content: flex-end; + overflow: hidden; + background-repeat: no-repeat; + background-position: center; + background-size: cover; + transition: 0.2s; + border: none; + + &:hover { + transform: scale(1.02); + } +} + +.clickable { + cursor: pointer; + padding: 0; + text-align: inherit; +} + +.selected { + box-shadow: 0 0 25px 5px rgba(143, 181, 238, 0.625); + border: 3px solid $blue; + transform: scale(1); +} + +.imageTitle { + color: $white; + text-align: center; + min-height: 2em; +} + +.text { + font-size: 1.2em; + padding: 0.2em; + background-color: $black; + text-decoration: none; +} + +.tags { + font-size: 0.8em; + padding: 0.4em; + background-color: $black-t75; + text-decoration: none; +} diff --git a/frontend/src/Components/ImageTile/ImageTile.tsx b/frontend/src/Components/ImageTile/ImageTile.tsx new file mode 100644 index 000000000..4541dc832 --- /dev/null +++ b/frontend/src/Components/ImageTile/ImageTile.tsx @@ -0,0 +1,55 @@ +import classNames from 'classnames'; +import { BACKEND_DOMAIN } from '~/constants'; +import type { ImageDto } from '~/dto'; +import { backgroundImageFromUrl } from '~/utils'; +import styles from './ImageTile.module.scss'; + +type ImageTileProps = { + image: ImageDto; + className?: string; + selected?: boolean; + selectedClassName?: string; + onClick?(): void; +}; + +function TileContent({ image }: Pick) { + const imageTags = image.tags.map((tag) => ` ${tag.name}`).toString(); + + return ( + <> +
+

{image.title}

+

{imageTags}

+
+ + ); +} + +export function ImageTile({ image, className, selected = false, selectedClassName, onClick }: ImageTileProps) { + const tileClassName = classNames( + styles.imageContainer, + className, + selected && styles.selected, + selected && selectedClassName, + onClick && styles.clickable, + ); + + if (onClick !== undefined) { + return ( + + ); + } + + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/frontend/src/Components/ImageTile/index.ts b/frontend/src/Components/ImageTile/index.ts new file mode 100644 index 000000000..b3a7e34cc --- /dev/null +++ b/frontend/src/Components/ImageTile/index.ts @@ -0,0 +1 @@ +export { ImageTile } from './ImageTile'; \ No newline at end of file From c4b92c5192e88216465bfe1b84b371ac4cb89f0e Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 17 Mar 2026 22:28:51 +0100 Subject: [PATCH 2/2] Incorporate new image tile element in admin pages to unify layout --- .../ImagePicker/ImagePicker.module.scss | 42 +++++++++---------- .../Components/ImagePicker/ImagePicker.tsx | 11 ++--- .../src/Components/ImageTile/ImageTile.tsx | 2 +- frontend/src/Components/ImageTile/index.ts | 2 +- frontend/src/Components/index.ts | 1 + .../AdminImage/AdminImage.module.scss | 34 --------------- .../components/AdminImage/AdminImage.tsx | 22 +--------- 7 files changed, 30 insertions(+), 84 deletions(-) delete mode 100644 frontend/src/PagesAdmin/ImageAdminPage/components/AdminImage/AdminImage.module.scss diff --git a/frontend/src/Components/ImagePicker/ImagePicker.module.scss b/frontend/src/Components/ImagePicker/ImagePicker.module.scss index b0c1ba438..b44d30fc3 100644 --- a/frontend/src/Components/ImagePicker/ImagePicker.module.scss +++ b/frontend/src/Components/ImagePicker/ImagePicker.module.scss @@ -37,35 +37,31 @@ max-height: 25em; overflow-y: scroll; display: flex; - flex-direction: row; - justify-content: center; + flex-direction: column; + justify-content: flex-start; flex-wrap: wrap; - gap: 0.5em; - margin: 0 auto; + gap: 1em; + margin-top: 1em; + + @include for-tablet-up { + flex-direction: row; + } } -.image { - @include rounded; - @include shadow-light; - width: 10em; - height: 8em; - background-color: $grey-1; - background-size: cover; - background-position: center; - border: 3px solid transparent; - transition: 0.2s; - transform: scale(0.9); - cursor: pointer; +.image_box { + overflow: hidden; + flex-basis: calc(25% - 1em); + min-height: 12em; + height: 12em; + box-sizing: inherit; - &:hover { - filter: brightness(110%); + @include for-tablet-down { + flex-basis: calc(33.33% - 1em); } +} - &.selected_image { - box-shadow: 0 0 25px 5px rgba(143, 181, 238, 0.625); - border: 3px solid $blue; - transform: scale(1); - } +.image_box_selected { + border: none; } .search_wrapper { diff --git a/frontend/src/Components/ImagePicker/ImagePicker.tsx b/frontend/src/Components/ImagePicker/ImagePicker.tsx index 16f58b078..139a67f88 100644 --- a/frontend/src/Components/ImagePicker/ImagePicker.tsx +++ b/frontend/src/Components/ImagePicker/ImagePicker.tsx @@ -1,8 +1,8 @@ import { Icon } from '@iconify/react'; import { keepPreviousData, useQuery } from '@tanstack/react-query'; -import classNames from 'classnames'; import { type ReactNode, useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { ImageTile } from '~/Components/ImageTile'; import { getImagesPaginated } from '~/api'; import { BACKEND_DOMAIN } from '~/constants'; import type { ImageDto } from '~/dto'; @@ -62,12 +62,13 @@ export function ImagePicker({ onSelected, selectedImage }: ImagePickerProps) { function renderImage(image: ImageDto): ReactNode { const isSelected = selected?.id === image.id; return ( -