diff --git a/ui/webapp/src/App.css b/ui/webapp/src/App.css index ba102aa5..ba8a5f13 100644 --- a/ui/webapp/src/App.css +++ b/ui/webapp/src/App.css @@ -1,17 +1,11 @@ -html, -body { - height: 100%; - height: 100vh; - margin: 0; - padding: 0; -} - :root { + --app-height: 100%; --card-size-width: 50px; --card-size-height: 45px; --card-gap: 5px; --mobile-card-size-width: 50px; --mobile-card-size-height: 50px; + --color1: rgba(0, 107, 204, 1); --color2: rgba(255, 0, 170, 1); --color3: rgba(96, 149, 214, 1); @@ -20,6 +14,15 @@ body { --color6: rgba(80, 142, 191, 0.7); } +html, +body { + height: 100vh; + height: 100%; + height: var(--app-height); + margin: 0; + padding: 0; +} + button:focus, a:focus, input:not([type='color']):not(.withShadow):focus, @@ -86,8 +89,9 @@ a, } body { - min-height: 100%; min-height: 100vh; + min-height: 100%; + min-height: var(--app-height); overflow: auto; } @@ -107,8 +111,9 @@ a, @media only screen and (min-width: 992px) { #landscape { - height: 100%; height: 100vh; + height: 100%; + height: var(--app-height); } /* Lazy loading for images are not working properly on body - desktop */ diff --git a/ui/webapp/src/App.tsx b/ui/webapp/src/App.tsx index 5d8fc7b7..442c2657 100644 --- a/ui/webapp/src/App.tsx +++ b/ui/webapp/src/App.tsx @@ -1,3 +1,4 @@ +import { createWindowSize } from '@solid-primitives/resize-observer'; import { Route, Router } from '@solidjs/router'; import isUndefined from 'lodash/isUndefined'; import range from 'lodash/range'; @@ -41,6 +42,8 @@ const App = () => { const [data, setData] = createSignal(); const [loadingOverlay, setLoadingOverlay] = createSignal(false); const [error, setError] = createSignal(); + const size = createWindowSize(); + const height = () => size.height; async function fetchOverlayData() { try { @@ -57,6 +60,10 @@ const App = () => { setLoadingOverlay(false); } + const updateAppHeight = () => { + document.documentElement.style.setProperty('--app-height', `${window.innerHeight}px`); + }; + const loadColors = () => { if (!isUndefined(window.baseDS) && !isUndefined(window.baseDS.colors)) { if (!isUndefined(window.baseDS.colors?.color1)) { @@ -100,6 +107,8 @@ const App = () => { }) ); + createEffect(on(height, updateAppHeight)); + onMount(() => { const isOverlayActive = overlayData.checkIfOverlayInQuery(); if (!isOverlayActive) { @@ -110,6 +119,7 @@ const App = () => { } batch(() => { + updateAppHeight(); setIsOverlay(isOverlayActive); loadColors(); }); diff --git a/ui/webapp/src/layout/Layout.module.css b/ui/webapp/src/layout/Layout.module.css index bc0d07cd..020fb747 100644 --- a/ui/webapp/src/layout/Layout.module.css +++ b/ui/webapp/src/layout/Layout.module.css @@ -1,5 +1,7 @@ .container { min-height: 100vh; + min-height: 100%; + min-height: var(--app-height); } :global(.overlay-active) .container { diff --git a/ui/webapp/src/layout/common/itemModal/MobileContent.tsx b/ui/webapp/src/layout/common/itemModal/MobileContent.tsx index 354228a7..1339b9b1 100644 --- a/ui/webapp/src/layout/common/itemModal/MobileContent.tsx +++ b/ui/webapp/src/layout/common/itemModal/MobileContent.tsx @@ -129,11 +129,13 @@ const MobileContent = (props: Props) => { {/* Additional categories */}
Additional categories
-
+
{(additional: AdditionalCategory) => { return ( -
+
{additional.category} / {additional.subcategory}
); diff --git a/ui/webapp/src/layout/common/itemModal/ParticipationStats.tsx b/ui/webapp/src/layout/common/itemModal/ParticipationStats.tsx index 04f4fc6f..7bfbc359 100644 --- a/ui/webapp/src/layout/common/itemModal/ParticipationStats.tsx +++ b/ui/webapp/src/layout/common/itemModal/ParticipationStats.tsx @@ -36,9 +36,7 @@ const ParticipationStats = (props: Props) => { +
No activity in the last year
} diff --git a/ui/webapp/src/layout/explore/Explore.module.css b/ui/webapp/src/layout/explore/Explore.module.css index 3ec03a01..5a9bd928 100644 --- a/ui/webapp/src/layout/explore/Explore.module.css +++ b/ui/webapp/src/layout/explore/Explore.module.css @@ -42,7 +42,7 @@ } .loadingContent { - min-height: calc(100vh - 92px - 36px); + min-height: calc(var(--app-height) - 92px - 36px); } .mobileMenuBtn { diff --git a/ui/webapp/src/layout/explore/card/Content.module.css b/ui/webapp/src/layout/explore/card/Content.module.css index 34aeabbe..8f4bf6cb 100644 --- a/ui/webapp/src/layout/explore/card/Content.module.css +++ b/ui/webapp/src/layout/explore/card/Content.module.css @@ -3,6 +3,10 @@ min-width: 0; } +.section { + scroll-margin: 5.5rem; +} + .categoryTitle { max-width: 50%; } @@ -31,7 +35,10 @@ } .card :global(.badge:not(.tagBadge)) { - font-size: 65%; + font-size: 0.6rem; + border: 1px solid transparent; + line-height: 1; + height: 18px; } .card :global(.accepted-date) { diff --git a/ui/webapp/src/layout/explore/card/Content.tsx b/ui/webapp/src/layout/explore/card/Content.tsx index 66eed80e..c4e2126e 100644 --- a/ui/webapp/src/layout/explore/card/Content.tsx +++ b/ui/webapp/src/layout/explore/card/Content.tsx @@ -128,7 +128,7 @@ const Content = (props: Props) => { const id = getNormalizedName({ title: title, subtitle: subtitle, grouped: true }); return ( -
+
{ -
+
diff --git a/ui/webapp/src/layout/explore/card/Menu.module.css b/ui/webapp/src/layout/explore/card/Menu.module.css index 13eaaa3d..0f18516c 100644 --- a/ui/webapp/src/layout/explore/card/Menu.module.css +++ b/ui/webapp/src/layout/explore/card/Menu.module.css @@ -8,10 +8,12 @@ min-width: 270px; top: 5.5rem; bottom: 1rem; - max-height: calc(100vh - 6.5rem); + max-height: calc(var(--app-height) - 6.5rem); } .content { + overflow-y: auto; + max-height: 100%; overscroll-behavior: contain; } diff --git a/ui/webapp/src/layout/explore/card/Menu.tsx b/ui/webapp/src/layout/explore/card/Menu.tsx index e4f912a7..2737d6bc 100644 --- a/ui/webapp/src/layout/explore/card/Menu.tsx +++ b/ui/webapp/src/layout/explore/card/Menu.tsx @@ -79,8 +79,7 @@ const Menu = (props: Props) => { }} disabled={`#${hash}` === location.hash} onClick={() => { - // When menu sticky, we need to check #landscape - goToElement(!props.sticky, `card_${hash}`, 16); + goToElement(`card_${hash}`); if (!isUndefined(props.onClickOption)) { props.onClickOption(); } diff --git a/ui/webapp/src/layout/explore/card/index.tsx b/ui/webapp/src/layout/explore/card/index.tsx index 81e73118..73a27941 100644 --- a/ui/webapp/src/layout/explore/card/index.tsx +++ b/ui/webapp/src/layout/explore/card/index.tsx @@ -25,8 +25,6 @@ interface Props { menu?: CardMenu; } -const TITLE_OFFSET = 16; - const CardCategory = (props: Props) => { const location = useLocation(); const [firstLoad, setFirstLoad] = createSignal(false); @@ -81,7 +79,7 @@ const CardCategory = (props: Props) => { } } setTimeout(() => { - goToElement(false, `card_${cleanHash}`, TITLE_OFFSET); + goToElement(`card_${cleanHash}`); }, 100); } } diff --git a/ui/webapp/src/layout/explore/grid/Grid.tsx b/ui/webapp/src/layout/explore/grid/Grid.tsx index 30722c34..86e96af3 100644 --- a/ui/webapp/src/layout/explore/grid/Grid.tsx +++ b/ui/webapp/src/layout/explore/grid/Grid.tsx @@ -49,7 +49,7 @@ export const ItemsList = (props: ItemsListProps) => { const [items, setItems] = createSignal<(BaseItem | Item)[]>(); const [itemsPerRow, setItemsPerRow] = createSignal(0); - createEffect(() => { + const updateItemsPerRow = () => { setItemsPerRow( calculateGridItemsPerRow( percentage(), @@ -58,21 +58,43 @@ export const ItemsList = (props: ItemsListProps) => { !isUndefined(props.itemWidth) ) ); - }); + }; - createEffect( - on(itemsPerRow, () => { - setItems((prev) => { - const tmpItems: (BaseItem | Item)[] = []; + const prepareItems = () => { + setItems((prev) => { + const tmpItems: (BaseItem | Item)[] = []; - for (const item of new ItemIterator(initialItems(), itemsPerRow() <= 0 ? MIN_COLUMN_ITEMS : itemsPerRow())) { - if (item) { - tmpItems.push(item); - } + for (const item of new ItemIterator(initialItems(), itemsPerRow() <= 0 ? MIN_COLUMN_ITEMS : itemsPerRow())) { + if (item) { + tmpItems.push(item); } + } - return !isEqual(tmpItems, prev) ? tmpItems : prev; - }); + return !isEqual(tmpItems, prev) ? tmpItems : prev; + }); + }; + + createEffect(() => { + const newItemsPerRow = calculateGridItemsPerRow( + percentage(), + gridWidth(), + props.itemWidth || ZOOM_LEVELS[zoom()][0], + !isUndefined(props.itemWidth) + ); + if (newItemsPerRow !== itemsPerRow()) { + setItemsPerRow(newItemsPerRow); + } else { + if (!isUndefined(items()) && initialItems().length !== items()!.length) { + prepareItems(); + } + } + }); + + createEffect(on(initialItems, () => updateItemsPerRow())); + + createEffect( + on(itemsPerRow, () => { + prepareItems(); }) ); diff --git a/ui/webapp/src/layout/explore/grid/GridCategory.module.css b/ui/webapp/src/layout/explore/grid/GridCategory.module.css index d11bafbc..7c2cc2fb 100644 --- a/ui/webapp/src/layout/explore/grid/GridCategory.module.css +++ b/ui/webapp/src/layout/explore/grid/GridCategory.module.css @@ -39,5 +39,5 @@ } .loading { - min-height: calc(100vh - 92px - 36px); + min-height: calc(var(--app-height) - 92px - 36px); } diff --git a/ui/webapp/src/layout/explore/grid/GridItem.tsx b/ui/webapp/src/layout/explore/grid/GridItem.tsx index 26df3856..8a0963c5 100644 --- a/ui/webapp/src/layout/explore/grid/GridItem.tsx +++ b/ui/webapp/src/layout/explore/grid/GridItem.tsx @@ -3,6 +3,7 @@ import { createEffect, createSignal, on, onCleanup, Show } from 'solid-js'; import { BaseItem, Item } from '../../../types'; import getItemDescription from '../../../utils/getItemDescription'; +import isTouchDevice from '../../../utils/isTouchDevice'; import itemsDataGetter from '../../../utils/itemsDataGetter'; import Image from '../../common/Image'; import Loading from '../../common/Loading'; @@ -41,6 +42,9 @@ const GridItem = (props: Props) => { const description = () => getItemDescription(props.item); const containerWidth = () => (!isUndefined(props.container) ? props.container.clientWidth : window.innerWidth); const containerHeight = () => (!isUndefined(props.container) ? props.container.clientHeight : window.innerHeight); + const touchDevice = () => isTouchDevice(); + // Only show dropdown on hover if it's not a touch device and activeDropdown prop is true + const activeDropdown = () => (touchDevice() ? false : props.activeDropdown); createEffect( on(fullDataReady, () => { @@ -113,7 +117,7 @@ const GridItem = (props: Props) => { return ( { }); } } + // Check if select for groups has to be visible + checkContainerWidth(); }) ); @@ -504,7 +506,7 @@ const Explore = (props: Props) => { }, DELAY_ACTIONS); }; - const handler = () => { + const checkContainerWidth = () => { if (!isUndefined(containerRef())) { const gap = !isUndefined(point()) && SMALL_DEVICES_BREAKPOINTS.includes(point()!) ? 0 : TITLE_GAP; const width = containerRef()!.offsetWidth - gap; @@ -555,10 +557,10 @@ const Explore = (props: Props) => { window.addEventListener( 'resize', // eslint-disable-next-line solid/reactivity - throttle(() => handler(), 400), + throttle(() => checkContainerWidth(), 400), { passive: true } ); - handler(); + checkContainerWidth(); if (fullDataReady()) { fetchItems(); @@ -566,7 +568,7 @@ const Explore = (props: Props) => { }); onCleanup(() => { - window.removeEventListener('resize', handler); + window.removeEventListener('resize', checkContainerWidth); }); return ( @@ -579,7 +581,7 @@ const Explore = (props: Props) => { title="Index" class={`position-relative btn btn-sm btn-secondary text-white btn-sm rounded-0 py-0 me-1 me-lg-4 btnIconMobile ${styles.mobileToCBtn}`} onClick={() => setOpenMenuStatus(true)} - disabled={numVisibleItems() === 0} + disabled={numVisibleItems() === 0 || classify() === ClassifyOption.None} > diff --git a/ui/webapp/src/layout/explore/mobile/ExploreMobileIndex.module.css b/ui/webapp/src/layout/explore/mobile/ExploreMobileIndex.module.css index 34433217..2b256ea9 100644 --- a/ui/webapp/src/layout/explore/mobile/ExploreMobileIndex.module.css +++ b/ui/webapp/src/layout/explore/mobile/ExploreMobileIndex.module.css @@ -3,6 +3,10 @@ min-width: 0; } +.section { + scroll-margin: 4rem; +} + .catTitle { background-color: var(--bs-gray-100); } diff --git a/ui/webapp/src/layout/explore/mobile/ExploreMobileIndex.tsx b/ui/webapp/src/layout/explore/mobile/ExploreMobileIndex.tsx index dce4287c..08a2ba22 100644 --- a/ui/webapp/src/layout/explore/mobile/ExploreMobileIndex.tsx +++ b/ui/webapp/src/layout/explore/mobile/ExploreMobileIndex.tsx @@ -112,7 +112,7 @@ const ExploreMobileIndex = (props: Props) => { const id = getNormalizedName({ title: title, subtitle: subtitle, grouped: true }); return ( -
+
{ const id = getNormalizedName({ title: title, subtitle: subtitle, grouped: true }); return ( -
+
{ return ( <> -
+
{ } } else { setTimeout(() => { - goToElement( - !isUndefined(point()) && SMALL_DEVICES_BREAKPOINTS.includes(point()!), - `section_${cleanHash}`, - 16 - ); + goToElement(`section_${cleanHash}`); scrollInToC(); }, 50); } @@ -161,10 +157,10 @@ const GuideIndex = () => { } else { if (!isUndefined(onLoad) && onLoad) { setTimeout(() => { - goToElement(onSmallDevice, `section_${title}`, 16); + goToElement(`section_${title}`); }, 50); } else { - goToElement(onSmallDevice, `section_${title}`, 16); + goToElement(`section_${title}`); } } }; @@ -213,7 +209,11 @@ const GuideIndex = () => { return ( <> -
+

{ return (

diff --git a/ui/webapp/src/layout/navigation/EmbedModal.module.css b/ui/webapp/src/layout/navigation/EmbedModal.module.css index 7ddd636e..98dd5e00 100644 --- a/ui/webapp/src/layout/navigation/EmbedModal.module.css +++ b/ui/webapp/src/layout/navigation/EmbedModal.module.css @@ -67,6 +67,7 @@ border: 3px solid rgb(248, 249, 250) !important; inline-size: 35px; block-size: 20px; + border-radius: 0; } .inputColor::-moz-color-swatch, diff --git a/ui/webapp/src/layout/navigation/MobileHeader.tsx b/ui/webapp/src/layout/navigation/MobileHeader.tsx index 51fa38f0..6ce34494 100644 --- a/ui/webapp/src/layout/navigation/MobileHeader.tsx +++ b/ui/webapp/src/layout/navigation/MobileHeader.tsx @@ -42,7 +42,7 @@ const MobileHeader = (props: Props) => { return ( <>
diff --git a/ui/webapp/src/layout/stats/Stats.module.css b/ui/webapp/src/layout/stats/Stats.module.css index 69bedfe2..3443ffd5 100644 --- a/ui/webapp/src/layout/stats/Stats.module.css +++ b/ui/webapp/src/layout/stats/Stats.module.css @@ -18,7 +18,7 @@ } .loadingContent { - min-height: calc(100vh - 36px); + min-height: calc(var(--app-height) - 36px); } .projectsCol { diff --git a/ui/webapp/src/layout/upcomingEvents/UpcomingEvents.module.css b/ui/webapp/src/layout/upcomingEvents/UpcomingEvents.module.css index 02534aa3..065bda5f 100644 --- a/ui/webapp/src/layout/upcomingEvents/UpcomingEvents.module.css +++ b/ui/webapp/src/layout/upcomingEvents/UpcomingEvents.module.css @@ -7,6 +7,7 @@ .loaded { transform: translate(-50%, 0%); + z-index: 1025; } .imageWrapper { diff --git a/ui/webapp/src/utils/goToElement.ts b/ui/webapp/src/utils/goToElement.ts index c223b626..637d6659 100644 --- a/ui/webapp/src/utils/goToElement.ts +++ b/ui/webapp/src/utils/goToElement.ts @@ -1,30 +1,9 @@ -import { DEFAULT_STICKY_MOBILE_NAVBAR_HEIGHT, DEFAULT_STICKY_NAVBAR_HEIGHT } from '../data'; +const goToElement = (id: string) => { + const target = document.getElementById(id); -const goToElement = (onWindow: boolean, id: string, offset?: number) => { - const target = window.document.getElementById(id); if (target) { - const elementPosition = target.getBoundingClientRect().top; - // Sticky navbar for small devices - const extraOffset = - window.innerWidth < 992 - ? window.scrollY < 130 - ? DEFAULT_STICKY_MOBILE_NAVBAR_HEIGHT * 2 + 20 - : DEFAULT_STICKY_MOBILE_NAVBAR_HEIGHT - : DEFAULT_STICKY_NAVBAR_HEIGHT; - const offsetPosition = elementPosition - (offset || 0) - extraOffset; - setTimeout(() => { - if (onWindow) { - window.scrollBy({ - top: offsetPosition, - behavior: 'instant', - }); - } else { - document.getElementById('landscape')!.scrollBy({ - top: offsetPosition, - behavior: 'instant', - }); - } + target!.scrollIntoView({ block: 'start', behavior: 'instant' }); }); } }; diff --git a/ui/webapp/src/utils/isTouchDevice.ts b/ui/webapp/src/utils/isTouchDevice.ts new file mode 100644 index 00000000..8c7250a5 --- /dev/null +++ b/ui/webapp/src/utils/isTouchDevice.ts @@ -0,0 +1,5 @@ +const isTouchDevice = (): boolean => { + return 'ontouchstart' in window || navigator.maxTouchPoints > 0; +}; + +export default isTouchDevice;