mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-08 09:02:06 +00:00
299 lines
9.2 KiB
Svelte
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>
|