Arrows have been added to the Scroller to display/offset non-contained content. (#9188)

Signed-off-by: Alexander Platov <alexander.platov@hardcoreeng.com>
This commit is contained in:
Alexander Platov
2025-06-09 15:06:11 +07:00
committed by GitHub
parent 2d38b8636f
commit 7c032ab596
6 changed files with 108 additions and 13 deletions
@@ -53,10 +53,7 @@
<Label label={selected !== undefined ? selected.label : label} />
</span>
<svelte:fragment slot="iconRight">
<DropdownIcon
size={'small'}
fill={!disabled ? 'var(--primary-button-content-color)' : 'var(--theme-dark-color)'}
/>
<DropdownIcon size={'small'} fill={!disabled ? 'var(--theme-content-color)' : 'var(--theme-dark-color)'} />
</svelte:fragment>
</Button>
</div>
+1 -1
View File
@@ -55,7 +55,7 @@
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
<div class="menu-space" />
<Scroller>
<Scroller noFade={false} showOverflowArrows>
{#if nestedFrom}
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<button
+101 -2
View File
@@ -17,12 +17,13 @@
import { afterUpdate, beforeUpdate, createEventDispatcher, onDestroy, onMount } from 'svelte'
import { resizeObserver } from '../resize'
import { closeTooltip, tooltipstore } from '../tooltips'
import type { FadeOptions, ScrollParams } from '../types'
import type { FadeOptions, ScrollParams, MouseTargetEvent } from '../types'
import { defaultSP } from '../types'
import { DelayedCaller } from '../utils'
import IconDownOutline from './icons/DownOutline.svelte'
import HalfUpDown from './icons/HalfUpDown.svelte'
import IconUpOutline from './icons/UpOutline.svelte'
import IconNavPrev from './icons/NavPrev.svelte'
export let padding: string | undefined = undefined
export let bottomPadding: string | undefined = undefined
@@ -45,6 +46,7 @@
export let checkForHeaders: boolean = false
export let stickedScrollBars: boolean = false
export let thinScrollBars: boolean = false
export let showOverflowArrows: boolean = false
export let disableOverscroll = false
export let disablePointerEventsOnScroll = false
export let onScroll: ((params: ScrollParams) => void) | undefined = undefined
@@ -74,6 +76,7 @@
let topCrop: 'top' | 'bottom' | 'full' | 'none' = 'none'
let topCropValue: number = 0
let maskH: 'left' | 'right' | 'both' | 'none' = 'none'
let scrollArrows: boolean[] = [false, false, false, false] // [up, right, down, left]
let divHScroll: HTMLElement
let divBar: HTMLElement
@@ -243,7 +246,15 @@
isScrollingByBar = direction
}
const renderFade = () => {
const renderFade = (): void => {
if (showOverflowArrows) {
scrollArrows = [
mask === 'top' || mask === 'both',
maskH === 'left' || maskH === 'both',
mask === 'bottom' || mask === 'both',
maskH === 'right' || maskH === 'both'
]
}
if (divScroll && !noFade) {
const th = shiftTop + (topCrop === 'top' ? 2 * fz - topCropValue : 0)
const tf =
@@ -537,6 +548,24 @@
}
}
const tapToScroll = (event: MouseTargetEvent): void => {
const target = event.currentTarget as HTMLButtonElement
if (!target.hasAttribute('data-direct') || divScroll == null) return
const direct = target.getAttribute('data-direct')
const dir =
direct === 'up'
? { top: -stepScroll }
: direct === 'down'
? { top: stepScroll }
: direct === 'left'
? { left: -stepScroll }
: direct === 'right'
? { left: stepScroll }
: { top: 0, left: 0 }
if (!(dir?.top === 0 && dir?.left === 0)) divScroll.scrollBy({ ...dir, behavior: 'smooth' })
}
$: topButton =
(orientir === 'vertical' && (mask === 'top' || mask === 'both')) ||
(orientir === 'horizontal' && (maskH === 'right' || maskH === 'both'))
@@ -715,9 +744,79 @@
on:pointerleave={checkFade}
/>
{/if}
{#if showOverflowArrows}
{#each ['up', 'right', 'down', 'left'] as dir, i}
<button class="scrollArrow" data-direct={dir} class:shown={scrollArrows[i]} on:click={tapToScroll}>
<IconNavPrev size={'full'} />
</button>
{/each}
{/if}
</div>
<style lang="scss">
.scrollArrow {
position: absolute;
display: none;
justify-content: center;
align-items: center;
margin: 0;
padding: 0;
width: 1rem;
height: 2rem;
color: var(--theme-halfcontent-color);
background-color: var(--theme-popup-color);
border: 1px solid var(--theme-button-border);
border-radius: 0.25rem;
outline: none;
box-shadow: 0 0 0.375rem rgba($color: #000000, $alpha: 0.1);
user-select: none;
-webkit-tap-highlight-color: transparent;
tap-highlight-color: transparent;
&.shown {
display: flex;
}
&:hover,
&:focus {
background-color: var(--theme-popup-hover);
color: var(--theme-content-color);
}
:global(> svg) {
width: 0.5rem;
height: 0.75rem;
pointer-events: none;
}
}
.scrollArrow[data-direct='up'] {
top: var(--scroller-header-height, 0);
left: calc(
(100% - var(--scroller-right-offset, 0) - var(--scroller-left-offset, 0)) / 2 + var(--scroller-left-offset, 0)
);
transform: translateX(-50%) rotate(90deg);
}
.scrollArrow[data-direct='right'] {
top: calc(
(100% - var(--scroller-header-height, 0) - var(--scroller-footer-height, 0)) / 2 +
var(--scroller-header-height, 0)
);
right: calc(var(--scroller-right-offset, 0) + 0.25rem);
transform: translateY(-50%) rotate(180deg);
}
.scrollArrow[data-direct='down'] {
bottom: var(--scroller-footer-height, 0);
left: calc(
(100% - var(--scroller-right-offset, 0) - var(--scroller-left-offset, 0)) / 2 + var(--scroller-left-offset, 0)
);
transform: translateX(-50%) rotate(-90deg);
}
.scrollArrow[data-direct='left'] {
top: calc(
(100% - var(--scroller-header-height, 0) - var(--scroller-footer-height, 0)) / 2 +
var(--scroller-header-height, 0)
);
left: calc(var(--scroller-left-offset, 0) + 0.25rem);
transform: translateY(-50%);
}
.updown-container {
position: absolute;
display: flex;
@@ -1,5 +1,5 @@
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
export let size: 'small' | 'medium' | 'large' | 'full'
const fill: string = 'currentColor'
</script>
@@ -258,7 +258,7 @@
{#if rows.length}
{@const dep = departmentById.get(department)}
<Scroller horizontal fade={{ multipler: { top: headerHeightRem, left: headerWidthRem } }} noFade>
<Scroller horizontal fade={{ multipler: { top: headerHeightRem, left: headerWidthRem } }} shrink showOverflowArrows>
<div
use:resizeObserver={(evt) => {
containerWidth = evt.clientWidth
@@ -409,7 +409,8 @@
$timeline-weekend-stroke-color: var(--theme-calendar-weekend-stroke-color);
.timeline {
width: 100%;
width: max-content;
min-width: 100%;
display: grid;
grid-auto-flow: column;
grid-template-columns: auto 1fr;
+1 -3
View File
@@ -28,9 +28,7 @@
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.73444 2.19183C8.27831 1.93589 7.72169 1.93589 7.26556 2.19183L1.25519 5.56425C1.09757 5.65269 1 5.81925 1 5.99988C1 6.1805 1.09757 6.34706 1.25519 6.4355L4.04323 7.99988L1.25519 9.56425C1.09757 9.65269 1 9.81925 1 9.99988C1 10.1805 1.09757 10.3471 1.25519 10.4355L7.26556 13.8079C7.72169 14.0639 8.27831 14.0639 8.73444 13.8079L14.7448 10.4355C14.9024 10.3471 15 10.1805 15 9.99988C15 9.81925 14.9024 9.65269 14.7448 9.56425L11.9568 7.99988L14.7448 6.4355C14.9024 6.34706 15 6.1805 15 5.99988C15 5.81925 14.9024 5.65269 14.7448 5.56425L8.73444 2.19183ZM5.06442 8.57287L7.26556 9.80793C7.72169 10.0639 8.27831 10.0639 8.73444 9.80793L10.9356 8.57287L13.4788 9.99988L8.24481 12.9367C8.09277 13.022 7.90723 13.022 7.75519 12.9367L2.52119 9.99988L5.06442 8.57287Z" />
</symbol>
<symbol id="unset-parent-issue" viewBox="0 0 16 16">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.73444 2.19183C8.27831 1.93589 7.72169 1.93589 7.26556 2.19183L1.25519 5.56425C1.09757 5.65269 1 5.81925 1 5.99988C1 6.1805 1.09757 6.34706 1.25519 6.4355L4.04323 7.99988L1.25519 9.56425C1.09757 9.65269 1 9.81925 1 9.99988C1 10.1805 1.09757 10.3471 1.25519 10.4355L7.26556 13.8079C7.72169 14.0639 8.27831 14.0639 8.73444 13.8079L14.7448 10.4355C14.9024 10.3471 15 10.1805 15 9.99988C15 9.81925 14.9024 9.65269 14.7448 9.56425L11.9568 7.99988L14.7448 6.4355C14.9024 6.34706 15 6.1805 15 5.99988C15 5.81925 14.9024 5.65269 14.7448 5.56425L8.73444 2.19183ZM5.06442 8.57287L7.26556 9.80793C7.72169 10.0639 8.27831 10.0639 8.73444 9.80793L10.9356 8.57287L13.4788 9.99988L8.24481 12.9367C8.09277 13.022 7.90723 13.022 7.75519 12.9367L2.52119 9.99988L5.06442 8.57287Z"/>
<path d="M1 1L15 15" stroke="#000" stroke-width="0.5" />
<path d="M1 15L15 1" stroke="#000" stroke-width="0.5" />
<path d="M12.4,11.7l2.3-1.3c0.2-0.1,0.3-0.3,0.3-0.4s-0.1-0.3-0.3-0.4L12,8l2.8-1.6C14.9,6.3,15,6.2,15,6s-0.1-0.3-0.3-0.4l-2.3-1.3l2.9-2.9c0.2-0.2,0.2-0.5,0-0.7s-0.5-0.2-0.7,0l-3.1,3.1L8.7,2.2c-0.5-0.3-1-0.3-1.5,0L4.5,3.8L1.4,0.6c-0.2-0.2-0.5-0.2-0.7,0s-0.2,0.5,0,0.7l2.9,2.9L1.3,5.6C1.1,5.7,1,5.8,1,6s0.1,0.3,0.3,0.4L4,8L1.3,9.6C1.1,9.7,1,9.8,1,10s0.1,0.3,0.3,0.4l2.3,1.3l-2.9,2.9c-0.2,0.2-0.2,0.5,0,0.7c0.1,0.1,0.2,0.1,0.4,0.1s0.3,0,0.4-0.1l3.1-3.1l2.8,1.6c0.5,0.3,1,0.3,1.5,0l2.8-1.6l3.1,3.1c0.1,0.1,0.2,0.1,0.4,0.1s0.3,0,0.4-0.1c0.2-0.2,0.2-0.5,0-0.7L12.4,11.7z M13.5,10l-1.8,1L9.9,9.2l1.1-0.6L13.5,10z M2.5,10l2.5-1.4l1.1,0.6L4.3,11L2.5,10z M8.2,12.9c-0.2,0.1-0.3,0.1-0.5,0l-2.6-1.4L7,9.7l0.2,0.1c0.5,0.3,1,0.3,1.5,0L9,9.7l1.8,1.8L8.2,12.9z" />
</symbol>
<symbol id="new-issue" viewBox="0 0 16 16">
<path d="M8 14.5C8 14.2239 8.22386 14 8.5 14H14.5C14.7761 14 15 14.2239 15 14.5C15 14.7761 14.7761 15 14.5 15H8.5C8.22386 15 8 14.7761 8 14.5Z" />

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 35 KiB