platform/packages/ui/src/components/calendar/MonthSquare.svelte
Alexander Platov e7b6522e80
UBER-142: update buttons. Cleaning CSS. (#3482)
Signed-off-by: Alexander Platov <sas_lord@mail.ru>
2023-07-06 14:01:27 +07:00

299 lines
9.2 KiB
Svelte

<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { afterUpdate, createEventDispatcher } from 'svelte'
import IconNavPrev from '../icons/NavPrev.svelte'
import IconNavNext from '../icons/NavNext.svelte'
import Icon from '../Icon.svelte'
import { firstDay, day, getWeekDayName, areDatesEqual, getMonthName, weekday, isWeekend } from './internal/DateUtils'
import { capitalizeFirstLetter } from '../../utils'
export let currentDate: Date | null
export let viewDate: Date
export let mondayStart: boolean = true
export let hideNavigator: 'all' | 'left' | 'right' | 'none' = 'none'
export let viewUpdate: boolean = true
export let displayedWeeksCount = 6
export let selectedTo: Date | null | undefined = undefined
const dispatch = createEventDispatcher()
$: firstDayOfCurrentMonth = firstDay(viewDate, mondayStart)
let monthYear: string
const today: Date = new Date(Date.now())
afterUpdate(() => {
monthYear = capitalizeFirstLetter(getMonthName(viewDate)) + ' ' + viewDate.getFullYear()
firstDayOfCurrentMonth = firstDay(viewDate, mondayStart)
})
function inRange (currentDate: Date | null, selectedTo: Date | null | undefined, target: Date): boolean {
if (currentDate == null || selectedTo == null) return false
if (areDatesEqual(currentDate, selectedTo)) return false
const startDate = currentDate < selectedTo ? currentDate : selectedTo
const endDate = currentDate > selectedTo ? currentDate : selectedTo
return target > startDate && target < endDate
}
function isSelected (currentDate: Date | null, selectedTo: Date | null | undefined, target: Date): boolean {
if (currentDate != null && areDatesEqual(currentDate, target)) return true
if (selectedTo != null && areDatesEqual(selectedTo, target)) return true
return false
}
function getNextDate (date: Date, shift: 1 | -1): Date {
return new Date(new Date(date).setDate(date.getDate() + shift))
}
function isPreviosDateWrong (date: Date): boolean {
return getNextDate(date, -1).getMonth() !== viewDate.getMonth()
}
function isNextDateWrong (date: Date): boolean {
return getNextDate(date, 1).getMonth() !== viewDate.getMonth()
}
function isStart (currentDate: Date | null, selectedTo: Date | null | undefined, target: Date): boolean {
if (currentDate == null || selectedTo == null) return false
const startDate = currentDate < selectedTo ? currentDate : selectedTo
return areDatesEqual(startDate, target)
}
function isEnd (currentDate: Date | null, selectedTo: Date | null | undefined, target: Date): boolean {
if (currentDate == null || selectedTo == null) return false
const endDate = currentDate > selectedTo ? currentDate : selectedTo
return areDatesEqual(endDate, target)
}
</script>
<div class="month-container">
<div class="header">
{#if viewDate}
<div
class="btn"
class:hideNavigator={hideNavigator === 'left' || hideNavigator === 'all'}
on:click={() => {
if (viewUpdate) viewDate.setMonth(viewDate.getMonth() - 1)
dispatch('navigation', -1)
}}
>
<div class="icon-btn"><Icon icon={IconNavPrev} size={'full'} /></div>
</div>
<div class="monthYear">{monthYear}</div>
<div
class="btn"
class:hideNavigator={hideNavigator === 'right' || hideNavigator === 'all'}
on:click={() => {
if (viewUpdate) viewDate.setMonth(viewDate.getMonth() + 1)
dispatch('navigation', 1)
}}
>
<div class="icon-btn"><Icon icon={IconNavNext} size={'full'} /></div>
</div>
{/if}
</div>
{#if viewDate}
<div class="calendar">
{#each [...Array(7).keys()] as dayOfWeek}
<span class="caption"
>{capitalizeFirstLetter(getWeekDayName(day(firstDayOfCurrentMonth, dayOfWeek), 'short'))}</span
>
{/each}
{#each [...Array(displayedWeeksCount).keys()] as weekIndex}
{#each [...Array(7).keys()] as dayOfWeek}
{@const date = weekday(firstDayOfCurrentMonth, weekIndex, dayOfWeek)}
{@const wrongM = date.getMonth() !== viewDate.getMonth()}
<div
class="container"
class:range={inRange(currentDate, selectedTo, date)}
class:selected={isSelected(currentDate, selectedTo, date)}
class:startRow={dayOfWeek === 0 || isPreviosDateWrong(date) || isStart(currentDate, selectedTo, date)}
class:endRow={dayOfWeek === 6 || isNextDateWrong(date) || isEnd(currentDate, selectedTo, date)}
class:wrongMonth={wrongM}
>
<div
class="day"
class:weekend={isWeekend(date)}
class:today={areDatesEqual(today, date)}
class:range={inRange(currentDate, selectedTo, date)}
class:selected={isSelected(currentDate, selectedTo, date)}
class:wrongMonth={wrongM}
style={`grid-column-start: ${dayOfWeek + 1}; grid-row-start: ${weekIndex + 2};`}
on:click|stopPropagation={(ev) => {
if (wrongM) {
ev.preventDefault()
return
}
viewDate = new Date(date)
if (currentDate) {
viewDate.setHours(currentDate.getHours())
viewDate.setMinutes(currentDate.getMinutes())
}
dispatch('update', viewDate)
}}
>
{date.getDate()}
</div>
</div>
{/each}
{/each}
</div>
{/if}
</div>
<style lang="scss">
.month-container {
display: flex;
flex-direction: column;
min-height: 0;
height: 100%;
color: var(--caption-color);
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 0.5rem 0.75rem;
color: var(--caption-color);
.monthYear {
font-weight: 500;
font-size: 1rem;
&::first-letter {
text-transform: capitalize;
}
}
.hideNavigator {
visibility: hidden;
}
.btn {
display: flex;
justify-content: center;
align-items: center;
width: 2rem;
height: 2rem;
color: var(--dark-color);
border-radius: 0.25rem;
background-color: var(--theme-refinput-color);
border: 1px solid var(--theme-refinput-color);
cursor: pointer;
.icon-btn {
height: 0.75rem;
}
&:hover {
color: var(--accent-color);
}
}
}
}
.calendar {
position: relative;
display: grid;
grid-template-columns: repeat(7, 1fr);
padding: 0 1rem 1rem;
.caption,
.day {
display: flex;
justify-content: center;
align-items: center;
width: 2rem;
height: 2rem;
font-size: 1rem;
color: var(--content-color);
}
.caption {
align-items: start;
height: 2rem;
padding: 0.25rem;
color: var(--dark-color);
&::first-letter {
text-transform: capitalize;
}
}
.container {
border: 0px solid transparent;
margin: 0.125rem 0;
padding: 0 0.125rem;
&.range:not(.wrongMonth),
&.selected:not(.wrongMonth) {
color: var(--caption-color);
background-color: var(--accented-button-transparent);
}
&.startRow:not(.wrongMonth) {
border-top-left-radius: 0.25rem;
border-bottom-left-radius: 0.25rem;
padding-left: 0;
margin-left: 0.125rem;
}
&.endRow:not(.wrongMonth) {
border-top-right-radius: 0.25rem;
border-bottom-right-radius: 0.25rem;
padding-right: 0;
margin-right: 0.125rem;
}
}
.day {
position: relative;
color: var(--accent-color);
background-color: rgba(var(--accent-color), 0.05);
border: 1px solid transparent;
border-radius: 0.25rem;
cursor: pointer;
&.weekend {
color: var(--content-color);
}
&.wrongMonth {
color: var(--dark-color);
cursor: default;
}
&.today:not(.worngMonth, .selected, .range) {
font-weight: 500;
background-color: rgba(76, 56, 188, 0.2);
border-radius: 50%;
}
&.selected:not(.wrongMonth) {
color: var(--accented-button-color);
background-color: var(--accented-button-default);
}
&:not(.wrongMonth):hover {
color: var(--caption-color);
background-color: var(--accented-button-transparent);
}
}
&::before {
position: absolute;
content: '';
top: 2rem;
left: 0;
width: 100%;
height: 1px;
background-color: var(--button-bg-color);
}
}
</style>