mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-11 10:47:24 +00:00
207 lines
5.9 KiB
Svelte
207 lines
5.9 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, IconNavNext, Icon } from '../..'
|
|
import { TCellStyle, ICell } from './internal/DateUtils'
|
|
import { firstDay, day, getWeekDayName, areDatesEqual, getMonthName, daysInMonth } from './internal/DateUtils'
|
|
|
|
export let currentDate: Date | null
|
|
export let mondayStart: boolean = true
|
|
export let hideNavigator: boolean = false
|
|
|
|
const dispatch = createEventDispatcher()
|
|
|
|
let monthYear: string
|
|
const today: Date = new Date(Date.now())
|
|
let viewDate: Date = new Date(currentDate ?? today)
|
|
$: firstDayOfCurrentMonth = firstDay(viewDate, mondayStart)
|
|
const isToday = (n: number): boolean => {
|
|
if (areDatesEqual(today, new Date(viewDate.getFullYear(), viewDate.getMonth(), n))) return true
|
|
return false
|
|
}
|
|
const capitalizeFirstLetter = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1)
|
|
|
|
let days: Array<ICell> = []
|
|
const getDateStyle = (date: Date): TCellStyle => {
|
|
if (currentDate != undefined && areDatesEqual(currentDate, date)) return 'selected'
|
|
return 'not-selected'
|
|
}
|
|
const renderCellStyles = (): void => {
|
|
days = []
|
|
for (let i = 1; i <= daysInMonth(viewDate); i++) {
|
|
const tempDate = new Date(viewDate.getFullYear(), viewDate.getMonth(), i)
|
|
days.push({
|
|
dayOfWeek: (tempDate.getDay() === 0) ? 7 : tempDate.getDay(),
|
|
style: getDateStyle(tempDate)
|
|
})
|
|
}
|
|
days = days
|
|
monthYear = capitalizeFirstLetter(getMonthName(viewDate)) + ' ' + viewDate.getFullYear()
|
|
}
|
|
|
|
afterUpdate(() => {
|
|
if (viewDate) renderCellStyles()
|
|
})
|
|
</script>
|
|
|
|
<div class="month-container">
|
|
<div class="header">
|
|
{#if viewDate}
|
|
<div class="monthYear">{monthYear}</div>
|
|
<div class="group" class:hideNavigator>
|
|
<div class="btn" on:click={() => {
|
|
viewDate.setMonth(viewDate.getMonth() - 1)
|
|
renderCellStyles()
|
|
}}>
|
|
<div class="icon-btn"><Icon icon={IconNavPrev} size={'full'} /></div>
|
|
</div>
|
|
<div class="btn" on:click={() => {
|
|
viewDate.setMonth(viewDate.getMonth() + 1)
|
|
renderCellStyles()
|
|
}}>
|
|
<div class="icon-btn"><Icon icon={IconNavNext} size={'full'} /></div>
|
|
</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 days as day, i}
|
|
<div
|
|
class="day {day.style}"
|
|
class:today={isToday(i)}
|
|
class:day-off={day.dayOfWeek > 5}
|
|
style="grid-column: {day.dayOfWeek}/{day.dayOfWeek + 1};"
|
|
on:click|stopPropagation={() => {
|
|
viewDate.setDate(i + 1)
|
|
dispatch('update', viewDate)
|
|
}}
|
|
>
|
|
{i + 1}
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<style lang="scss">
|
|
.month-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
color: var(--theme-caption-color);
|
|
|
|
.header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 1rem 1rem .75rem;
|
|
color: var(--caption-color);
|
|
|
|
.monthYear {
|
|
font-weight: 500;
|
|
font-size: 1rem;
|
|
&::first-letter { text-transform: capitalize; }
|
|
}
|
|
.group {
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
&.hideNavigator { visibility: hidden; }
|
|
.btn {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
width: 1.25rem;
|
|
height: 1.75rem;
|
|
color: var(--dark-color);
|
|
cursor: pointer;
|
|
|
|
.icon-btn { height: .75rem; }
|
|
&:hover { color: var(--accent-color); }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.calendar {
|
|
position: relative;
|
|
display: grid;
|
|
grid-template-columns: repeat(7, 1fr);
|
|
gap: .5rem;
|
|
padding: 0 1rem 1rem;
|
|
|
|
.caption, .day {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
width: 1.625rem;
|
|
height: 1.625rem;
|
|
font-size: 1rem;
|
|
color: var(--content-color);
|
|
}
|
|
.caption {
|
|
align-items: start;
|
|
height: 2rem;
|
|
color: var(--dark-color);
|
|
&::first-letter { text-transform: capitalize; }
|
|
}
|
|
.day {
|
|
position: relative;
|
|
color: var(--accent-color);
|
|
background-color: rgba(var(--accent-color), .05);
|
|
border: 1px solid transparent;
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
|
|
&.day-off { color: var(--content-color); }
|
|
&.today {
|
|
font-weight: 500;
|
|
color: var(--caption-color);
|
|
background-color: var(--button-bg-color);
|
|
border-color: var(--dark-color);
|
|
}
|
|
&.focused { box-shadow: 0 0 0 3px var(--primary-button-outline); }
|
|
&.selected, &:hover {
|
|
color: var(--caption-color);
|
|
background-color: var(--primary-bg-color);
|
|
}
|
|
|
|
&:before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: -.625rem;
|
|
}
|
|
}
|
|
|
|
&::before {
|
|
position: absolute;
|
|
content: '';
|
|
top: 2rem;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 1px;
|
|
background-color: var(--button-bg-color);
|
|
}
|
|
}
|
|
</style>
|