UBER-667: UI fixes, displaying All day, time editor. (#3619)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2023-08-23 06:11:35 +03:00 committed by GitHub
parent 1988dfca33
commit 998689401a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 138 additions and 104 deletions

View File

@ -145,8 +145,6 @@ a.noUnderline {
}
}
.text-center { text-align: center; }
.firstLetter span{
display: inline-block;
&::first-letter { text-transform: uppercase; }
@ -804,6 +802,8 @@ a.no-line {
.uppercase { text-transform: uppercase; }
.lower { text-transform: lowercase; }
.text-left { text-align: left; }
.text-center { text-align: center; }
.leading-3 { line-height: .75rem; }
.over-underline {
cursor: pointer;

View File

@ -2,7 +2,6 @@
display: flex;
align-items: center;
flex-shrink: 0;
padding: 0 .75rem;
min-width: 1.375rem;
white-space: nowrap;
font-size: .8125rem;

View File

@ -41,6 +41,7 @@
export let iconRight: Asset | AnySvelteComponent | ComponentType | undefined = undefined
export let iconRightProps: IconProps = {}
export let justify: 'left' | 'center' = 'center'
export let padding: string = '0 .75rem'
export let disabled: boolean = false
export let loading: boolean = false
export let width: string | undefined = undefined
@ -128,6 +129,7 @@
style:width
style:height
style:flex-shrink={shrink}
style:padding
{title}
type={kind === 'accented' ? 'submit' : 'button'}
on:click|stopPropagation|preventDefault

View File

@ -18,6 +18,9 @@
import Label from '../Label.svelte'
export let currentDate: Date
export let size: 'small' | 'medium' = 'medium'
export let noBorder: boolean = false
export let disabled: boolean = false
type TEdits = 'hour' | 'min'
interface IEdits {
@ -92,7 +95,7 @@
}
const keyDown = (ev: KeyboardEvent, ed: TEdits): void => {
if (selected === ed) {
if (selected === ed && !disabled) {
const index = getIndex(ed)
if (ev.key >= '0' && ev.key <= '9') {
const shouldNext = !startTyping
@ -114,6 +117,7 @@
dateToEdits(currentDate)
}
edits = edits
dispatch('update', currentDate)
if (selected === 'hour' && (shouldNext || edits[0].value > 2)) selected = 'min'
}
@ -130,6 +134,7 @@
if (currentDate) {
currentDate = setValue(val, currentDate, ed)
dateToEdits(currentDate)
dispatch('update', currentDate)
}
}
}
@ -163,59 +168,71 @@
})
</script>
<div class="datetime-input">
<div class="flex-row-center">
<span
bind:this={edits[0].el}
class="digit"
tabindex="0"
on:keydown={(ev) => keyDown(ev, edits[0].id)}
on:focus={() => focused(edits[0].id)}
on:blur={() => (selected = null)}
>
{#if edits[0].value > -1}
{edits[0].value.toString().padStart(2, '0')}
{:else}<Label label={ui.string.HH} />{/if}
</span>
<span class="separator">:</span>
<span
bind:this={edits[1].el}
class="digit"
tabindex="0"
on:keydown={(ev) => keyDown(ev, edits[1].id)}
on:focus={() => focused(edits[1].id)}
on:blur={() => (selected = null)}
>
{#if edits[1].value > -1}
{edits[1].value.toString().padStart(2, '0')}
{:else}<Label label={ui.string.MM} />{/if}
</span>
</div>
<div class="datetime-input {size}" class:noBorder class:disabled>
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<span
bind:this={edits[0].el}
class="digit"
tabindex="0"
on:keydown={(ev) => keyDown(ev, edits[0].id)}
on:focus={() => focused(edits[0].id)}
on:blur={() => (selected = null)}
>
{#if edits[0].value > -1}
{edits[0].value.toString().padStart(2, '0')}
{:else}<Label label={ui.string.HH} />{/if}
</span>
<span class="separator">:</span>
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<span
bind:this={edits[1].el}
class="digit"
tabindex="0"
on:keydown={(ev) => keyDown(ev, edits[1].id)}
on:focus={() => focused(edits[1].id)}
on:blur={() => (selected = null)}
>
{#if edits[1].value > -1}
{edits[1].value.toString().padStart(2, '0')}
{:else}<Label label={ui.string.MM} />{/if}
</span>
</div>
<style lang="scss">
.datetime-input {
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
margin: 0;
padding: 0.75rem;
height: 3rem;
min-width: 0;
font-family: inherit;
font-size: 1rem;
color: var(--theme-content-color);
background-color: var(--theme-bg-color);
border: 1px solid var(--theme-button-border);
border-radius: 0.25rem;
transition: border-color 0.15s ease;
&:hover {
border-color: var(--theme-button-default);
&.small {
font-size: 0.8125rem;
}
&.medium {
height: 3rem;
font-size: 1rem;
}
&:not(.noBorder) {
padding: 0.75rem;
background-color: var(--theme-bg-color);
border: 1px solid var(--theme-button-border);
&:hover {
border-color: var(--theme-button-default);
}
&:focus-within {
border-color: var(--primary-edit-border-color);
}
}
&.noBorder {
padding: 0.125rem;
}
&:focus-within {
color: var(--theme-caption-color);
border-color: var(--primary-edit-border-color);
}
.close-btn {
@ -246,10 +263,6 @@
outline: none;
border-radius: 0.125rem;
&:focus {
color: var(--accented-button-color);
background-color: var(--accented-button-default);
}
&::after {
position: absolute;
top: 0;
@ -260,6 +273,10 @@
cursor: pointer;
}
}
&:not(.disabled) .digit:focus {
color: var(--accented-button-color);
background-color: var(--accented-button-default);
}
.time-divider {
flex-shrink: 0;
margin: 0 0.25rem;

View File

@ -80,6 +80,7 @@ export { default as DateTimeRangePresenter } from './components/calendar/DateTim
export { default as DatePresenter } from './components/calendar/DatePresenter.svelte'
export { default as DueDatePresenter } from './components/calendar/DueDatePresenter.svelte'
export { default as DateTimePresenter } from './components/calendar/DateTimePresenter.svelte'
export { default as TimeInputBox } from './components/calendar/TimeInputBox.svelte'
export { default as StylishEdit } from './components/StylishEdit.svelte'
export { default as Grid } from './components/Grid.svelte'
export { default as Row } from './components/Row.svelte'

View File

@ -16,11 +16,12 @@
import ui, {
Button,
ButtonKind,
ButtonSize,
DatePopup,
SimpleDatePopup,
SimpleTimePopup,
eventToHTMLElement,
showPopup
showPopup,
TimeInputBox
} from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import DateLocalePresenter from './DateLocalePresenter.svelte'
@ -30,22 +31,18 @@
export let showDate: boolean = true
export let withoutTime: boolean
export let kind: ButtonKind = 'ghost'
export let size: ButtonSize = 'medium'
export let disabled: boolean = false
const dispatch = createEventDispatcher()
$: currentDate = new Date(date)
function timeClick (e: MouseEvent) {
if (showDate) {
showPopup(SimpleTimePopup, { currentDate: new Date(date) }, eventToHTMLElement(e), (res) => {
if (res.value) {
date = res.value.getTime()
dispatch('update', date)
}
})
} else {
if (!showDate) {
showPopup(
DatePopup,
{ currentDate: new Date(date), withTime: !withoutTime, label: ui.string.SelectDate, noShift: true },
{ currentDate, withTime: !withoutTime, label: ui.string.SelectDate, noShift: true },
undefined,
(res) => {
if (res) {
@ -58,43 +55,47 @@
}
function dateClick (e: MouseEvent) {
showPopup(SimpleDatePopup, { currentDate: new Date(date) }, eventToHTMLElement(e), (res) => {
showPopup(SimpleDatePopup, { currentDate }, eventToHTMLElement(e), (res) => {
if (res) {
date = res.getTime()
dispatch('update', date)
}
})
}
const updateTime = (d?: Date) => {
const dat = d ?? currentDate
date = dat.getTime()
dispatch('update', dat)
}
</script>
<div class="container" class:vertical={direction === 'vertical'} class:horizontal={direction === 'horizontal'}>
<div class="dateEditor-container {direction}">
{#if showDate || withoutTime}
<Button {kind} on:click={dateClick} {disabled}>
<Button {kind} {size} padding={'0 .5rem'} on:click={dateClick} {disabled}>
<div slot="content">
<DateLocalePresenter {date} />
<DateLocalePresenter date={currentDate.getTime()} />
</div>
</Button>
{/if}
{#if !withoutTime}
<Button {kind} on:click={timeClick} {disabled}>
<div slot="content">
{new Date(date).toLocaleTimeString('default', { hour: 'numeric', minute: '2-digit' })}
</div>
<Button {kind} {size} padding={'0 .5rem'} on:click={timeClick} {disabled}>
<svelte:fragment slot="content">
<TimeInputBox bind:currentDate noBorder size={'small'} on:update={(date) => updateTime(date.detail)} />
</svelte:fragment>
</Button>
{/if}
</div>
<style lang="scss">
.container {
.dateEditor-container {
display: flex;
align-items: center;
}
flex-wrap: nowrap;
.vertical {
flex-direction: column;
}
.horizontal {
flex-direction: row;
&.horizontal {
align-items: center;
}
&.vertical {
flex-direction: column;
}
}
</style>

View File

@ -242,32 +242,34 @@
adRows = []
for (let i = 0; i < displayedDaysCount; i++) alldaysGrid[i] = { alldays: [null] }
adMaxRow = 1
alldays.forEach((event) => {
const days = calendarEvents
.filter((ev) => ev.allDay && ev.day !== -1 && event._id === ev._id)
.map((ev) => {
return ev.day
})
let emptyRow = 0
for (let checkRow = 0; checkRow < adMaxRow; checkRow++) {
const empty = days.every((day) => alldaysGrid[day].alldays[checkRow] === null)
if (empty) {
emptyRow = checkRow
break
} else if (checkRow === adMaxRow - 1) {
emptyRow = adMaxRow
addNullRow()
break
alldays
.filter((event) => event.day === -1)
.forEach((event) => {
const days = newEvents
.filter((ev) => ev.allDay && ev.day !== -1 && event._id === ev._id)
.map((ev) => {
return ev.day
})
let emptyRow = 0
for (let checkRow = 0; checkRow < adMaxRow; checkRow++) {
const empty = days.every((day) => alldaysGrid[day].alldays[checkRow] === null)
if (empty) {
emptyRow = checkRow
break
} else if (checkRow === adMaxRow - 1) {
emptyRow = adMaxRow
addNullRow()
break
}
}
}
adRows.push({ id: event._id, row: emptyRow, startCol: days[0], endCol: days[days.length - 1] })
days.forEach((day) => (alldaysGrid[day].alldays[emptyRow] = event._id))
})
adRows.push({ id: event._id, row: emptyRow, startCol: days[0], endCol: days[days.length - 1] })
days.forEach((day) => (alldaysGrid[day].alldays[emptyRow] = event._id))
})
const shown = minimizedAD ? minAD : maxAD
let tempEventID: string = ''
for (let r = 0; r < shown; r++) {
const lastRow = r === shown - 1
for (let d = 0; d < displayedDaysCount; d++) {
const lastRow = r === shown - 1
if (r < shown - 1 && tempEventID !== alldaysGrid[d].alldays[r] && alldaysGrid[d].alldays[r] !== null) {
tempEventID = alldaysGrid[d].alldays[r] ?? ''
if (tempEventID !== '') shortAlldays.push({ id: tempEventID, day: d })
@ -445,6 +447,7 @@
: rem((heightAD + 0.125) * adMaxRow + 0.25) > maxHeightAD && minimizedAD
? rem((heightAD + 0.125) * (adMaxRow <= minAD ? adMaxRow : minAD) + 0.25)
: rem((heightAD + 0.125) * (adMaxRow <= maxAD ? adMaxRow : maxAD) + 0.25)
$: showArrowAD = (!minimizedAD && adMaxRow > maxAD) || (minimizedAD && adMaxRow > minAD)
</script>
<Scroller
@ -472,9 +475,11 @@
{/each}
{/if}
<div class="sticky-header allday-header text-sm content-dark-color" class:top={!showHeader}>
<Label label={calendar.string.AllDay} />
{#if (!minimizedAD && adMaxRow > maxAD) || (minimizedAD && adMaxRow > minAD)}
<div class="sticky-header allday-header content-dark-color" class:top={!showHeader} class:opened={showArrowAD}>
<div class="flex-center text-sm leading-3 text-center" style:height={`${heightAD + 0.25}rem`}>
<Label label={calendar.string.AllDay} />
</div>
{#if showArrowAD}
<ActionIcon
icon={shownAD ? IconUpOutline : IconDownOutline}
size={'small'}
@ -568,12 +573,12 @@
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="calendar-element antiButton ghost medium accent cursor-pointer"
class="calendar-element withPointer antiButton ghost medium accent cursor-pointer"
style:top={`${rect.top}px`}
style:height={`${heightAD}rem`}
style:left={`${rect.left}px`}
style:width={`${rect.width}px`}
style:padding-left={'1.25rem'}
style:padding={'0 .5rem 0 1.25rem'}
style:--mask-image={'none'}
tabindex={500 + addon + day}
on:click={() => (shownAD = true)}
@ -690,7 +695,10 @@
mask-image: var(--mask-image, none);
--webkit-mask-image: var(--mask-image, none);
border-radius: 0.25rem;
pointer-events: none;
&:not(.withPointer) {
pointer-events: none;
}
}
.sticky-header {
position: sticky;
@ -749,7 +757,12 @@
&.allday-header {
flex-direction: column;
justify-content: space-between;
padding: 0.625rem 0.125rem;
align-items: center;
padding: 0 0.125rem;
&.opened {
padding: 0 0.125rem 0.625rem;
}
}
&.allday-container {
overflow: hidden;

View File

@ -30,7 +30,6 @@
import EventReminders from './EventReminders.svelte'
export let object: Event
$: readOnly = isReadOnly(object)
let title = object.title
@ -232,6 +231,7 @@
<div>
{#if !allDay && rules.length === 0}
<div class="flex-row-center flex-gap-3 ext">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="cursor-pointer" on:click={() => (allDay = true)}>
<Label label={calendar.string.AllDay} />
</div>
@ -239,6 +239,7 @@
<Label label={calendar.string.TimeZone} />
</div>
{#if rules.length > 0}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="cursor-pointer" on:click={setRecurrance}>
<Label label={calendar.string.Repeat} />
</div>
@ -255,6 +256,7 @@
<Label label={calendar.string.TimeZone} />
</div>
{#if rules.length > 0}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="flex-row-center flex-gap-2 mt-1" on:click={setRecurrance}>
<Icon size="small" icon={calendar.icon.Repeat} />
{#if rules.length > 0}
@ -297,7 +299,7 @@
min-height: 0;
background: var(--theme-popup-color);
box-shadow: var(--theme-popup-shadow);
width: 25rem;
min-width: 25rem;
border-radius: 1rem;
.header {

View File

@ -36,7 +36,6 @@
const newDiff = dueDate - startDate
if (newDiff > 0) {
dueDate = allDay ? new Date(dueDate).setHours(23, 59, 59, 999) : dueDate
diff = dueDate - startDate
} else {
dueDate = startDate + (allDay ? allDayDuration : diff)
}