diff --git a/packages/calendar/.npmignore b/packages/calendar/.npmignore new file mode 100644 index 00000000000..c50cbe188c0 --- /dev/null +++ b/packages/calendar/.npmignore @@ -0,0 +1,2 @@ +stories +test \ No newline at end of file diff --git a/packages/calendar/README.md b/packages/calendar/README.md new file mode 100644 index 00000000000..36045970735 --- /dev/null +++ b/packages/calendar/README.md @@ -0,0 +1,40 @@ +## Description + +### Usage + +[![See it on NPM!](https://img.shields.io/npm/v/@spectrum-web-components/calendar?style=for-the-badge)](https://www.npmjs.com/package/@spectrum-web-components/calendar) +[![How big is this package in your project?](https://img.shields.io/bundlephobia/minzip/@spectrum-web-components/calendar?style=for-the-badge)](https://bundlephobia.com/result?p=@spectrum-web-components/calendar) + +``` +yarn add @spectrum-web-components/calendar +``` + +Import the side effectful registration of `` via: + +``` +import '@spectrum-web-components/calendar/sp-calendar.js'; +``` + +When looking to leverage the `Calendar` base class as a type and/or for extension purposes, do so via: + +``` +import { Calendar } from '@spectrum-web-components/calendar'; +``` + +## Example + +```html + +``` + +## To-do list + +- Implement a cache mechanism to store the value of `today` and use this value to initialise `currentDate` +- Include a condition to run the `setWeekdays()` method only when really needed +- Attribute 'role="heading"' removed, due to error 'The "heading" role requires the attribute "aria-level"' +- Translate the "Previous" text used in the "title" and "aria-label" of the button displayed in the header of the calendar +- Translate the "Next" text used in the "title" and "aria-label" of the button displayed in the header of the calendar +- The title must include "Today," and " selected" translated to the current language +- Implement keyboard navigation +- Add support for other types of calendars - [React Calendar: International Calendars](https://react-spectrum.adobe.com/react-spectrum/Calendar.html#international-calendars) +- Include `aria-label` in calendar for accessibility - [React Calendar: Labeling](https://react-spectrum.adobe.com/react-spectrum/Calendar.html#labeling) diff --git a/packages/calendar/exports.json b/packages/calendar/exports.json new file mode 100644 index 00000000000..5aa8b7b31c6 --- /dev/null +++ b/packages/calendar/exports.json @@ -0,0 +1,4 @@ +{ + "./src/*": "./src/*.js", + "./sp-calendar.js": "./sp-calendar.js" +} diff --git a/packages/calendar/package.json b/packages/calendar/package.json new file mode 100644 index 00000000000..0a8ff0a1500 --- /dev/null +++ b/packages/calendar/package.json @@ -0,0 +1,80 @@ +{ + "name": "@spectrum-web-components/calendar", + "version": "0.0.1", + "publishConfig": { + "access": "public" + }, + "description": "Web component implementation of a Spectrum Calendar", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/adobe/spectrum-web-components.git", + "directory": "packages/calendar" + }, + "author": "", + "homepage": "https://adobe.github.io/spectrum-web-components/components/calendar", + "bugs": { + "url": "https://github.com/adobe/spectrum-web-components/issues" + }, + "main": "./src/index.js", + "module": "./src/index.js", + "type": "module", + "exports": { + ".": { + "development": "./src/index.dev.js", + "default": "./src/index.js" + }, + "./package.json": "./package.json", + "./src/Calendar.js": { + "development": "./src/Calendar.dev.js", + "default": "./src/Calendar.js" + }, + "./src/calendar.css.js": "./src/calendar.css.js", + "./src/index.js": { + "development": "./src/index.dev.js", + "default": "./src/index.js" + }, + "./src/types.js": { + "development": "./src/types.dev.js", + "default": "./src/types.js" + }, + "./sp-calendar.js": { + "development": "./sp-calendar.dev.js", + "default": "./sp-calendar.js" + } + }, + "scripts": { + "test": "echo \"Error: run tests from mono-repo root.\" && exit 1" + }, + "files": [ + "**/*.d.ts", + "**/*.js", + "**/*.js.map", + "custom-elements.json", + "!stories/", + "!test/" + ], + "keywords": [ + "spectrum css", + "web components", + "lit-element", + "lit-html" + ], + "dependencies": { + "@internationalized/date": "^3.2.0", + "@internationalized/number": "^3.1.0", + "@spectrum-web-components/action-button": "^0.34.0", + "@spectrum-web-components/base": "^0.34.0", + "@spectrum-web-components/icons-workflow": "^0.34.0", + "@spectrum-web-components/reactive-controllers": "^0.34.0" + }, + "devDependencies": { + "@spectrum-css/calendar": "^3.2.6" + }, + "types": "./src/index.d.ts", + "customElements": "custom-elements.json", + "sideEffects": [ + "./sp-*.js", + "./**/*.dev.js" + ] +} diff --git a/packages/calendar/sp-calendar.ts b/packages/calendar/sp-calendar.ts new file mode 100644 index 00000000000..79df5db0aa4 --- /dev/null +++ b/packages/calendar/sp-calendar.ts @@ -0,0 +1,20 @@ +/* +Copyright 2023 Adobe. All rights reserved. +This file is licensed to you under the Apache 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 http://www.apache.org/licenses/LICENSE-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 REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ +import { Calendar } from './src/Calendar.js'; + +customElements.define('sp-calendar', Calendar); + +declare global { + interface HTMLElementTagNameMap { + 'sp-calendar': Calendar; + } +} diff --git a/packages/calendar/src/Calendar.ts b/packages/calendar/src/Calendar.ts new file mode 100644 index 00000000000..53da531a31a --- /dev/null +++ b/packages/calendar/src/Calendar.ts @@ -0,0 +1,516 @@ +/* +Copyright 2023 Adobe. All rights reserved. +This file is licensed to you under the Apache 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 http://www.apache.org/licenses/LICENSE-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 REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ +import { + CalendarDate, + DateFormatter, + getLocalTimeZone, + getWeeksInMonth, + isSameDay, + startOfMonth, + startOfWeek, + today, +} from '@internationalized/date'; +import { NumberFormatter } from '@internationalized/number'; +import { + CSSResultArray, + html, + PropertyValueMap, + SpectrumElement, + TemplateResult, +} from '@spectrum-web-components/base'; +import { + property, + state, +} from '@spectrum-web-components/base/src/decorators.js'; +import { + ClassInfo, + classMap, + ifDefined, +} from '@spectrum-web-components/base/src/directives.js'; +import { LanguageResolutionController } from '@spectrum-web-components/reactive-controllers/src/LanguageResolution.js'; + +import { CalendarWeekday, daysInWeek } from './types.js'; + +import styles from './calendar.css.js'; + +import '@spectrum-web-components/action-button/sp-action-button.js'; +import '@spectrum-web-components/icons-workflow/icons/sp-icon-chevron-left.js'; +import '@spectrum-web-components/icons-workflow/icons/sp-icon-chevron-right.js'; + +/** + * @element sp-calendar + * + * @slot prev-icon - The icon used in the "Previous Month" button + * @slot next-icon - The icon used in the "Next Month" button + * + * @event change - Announces when a day is selected by emitting a `Date` object + */ +export class Calendar extends SpectrumElement { + public static override get styles(): CSSResultArray { + return [styles]; + } + + /** + * Date used to display the calendar. If no date is given, the current month will be used + */ + @property({ attribute: false }) + selectedDate?: Date; + + /** + * The minimum allowed date a user can select + */ + @property({ attribute: false }) + min?: Date; + + /** + * The maximum allowed date a user can select + */ + @property({ attribute: false }) + max?: Date; + + /** + * Indicates when the calendar should be disabled entirely + */ + @property({ type: Boolean, reflect: true }) + disabled = false; + + /** + * Adds a padding around the calendar + */ + @property({ type: Boolean, reflect: true }) + padded = false; + + @state() + private currentDate!: CalendarDate; + + @state() + private minDate!: CalendarDate; + + @state() + private maxDate!: CalendarDate; + + @state() + private weeksInCurrentMonth: number[] = []; + + @state() + private weekdays: CalendarWeekday[] = []; + + private languageResolver = new LanguageResolutionController(this); + private timeZone: string = getLocalTimeZone(); + + private get locale(): string { + return this.languageResolver.language; + } + + // TODO: Implement a cache mechanism to store the value of `today` and use this value to initialise `currentDate` + public get today(): CalendarDate { + return today(this.timeZone); + } + + constructor() { + super(); + this.setInitialCalendarDate(); + } + + protected override willUpdate( + changedProperties: PropertyValueMap + ): void { + if (changedProperties.has('selectedDate')) { + this.setCurrentCalendarDate(); + } + + if (changedProperties.has('min')) { + this.setMinCalendarDate(); + } + + if (changedProperties.has('max')) { + this.setMaxCalendarDate(); + } + + this.setWeeksInCurrentMonth(); + + // TODO: Include a condition to run the `setWeekdays()` method only when really needed + this.setWeekdays(); + } + + protected override render(): TemplateResult { + return html` + ${this.renderCalendarHeader()}${this.renderCalendarGrid()} + `; + } + + public renderCalendarHeader(): TemplateResult { + const monthAndYear = this.formatDate(this.currentDate, { + month: 'long', + year: 'numeric', + }); + + return html` +
+ +
+ ${monthAndYear} +
+ + + +
+ + + +
+
+ + + +
+ + + +
+
+
+ `; + } + + public renderCalendarGrid(): TemplateResult { + return html` + +
+ + ${this.renderCalendarTableHead()} + ${this.renderCalendarTableBody()} + +
+ `; + } + + public renderCalendarTableHead(): TemplateResult { + return html` + + + ${this.weekdays.map((weekday) => + this.renderWeekdayColumn(weekday) + )} + + + `; + } + + public renderWeekdayColumn(weekday: CalendarWeekday): TemplateResult { + return html` + + + ${weekday.narrow} + + + `; + } + + public renderCalendarTableBody(): TemplateResult { + return html` + + ${this.weeksInCurrentMonth.map((weekIndex) => + this.renderCalendarTableRow(weekIndex) + )} + + `; + } + + public renderCalendarTableRow(weekIndex: number): TemplateResult { + return html` + + ${this.getDatesInWeek(weekIndex).map((calendarDate) => + this.renderCalendarTableCell(calendarDate) + )} + + `; + } + + public renderCalendarTableCell(calendarDate: CalendarDate): TemplateResult { + const isOutsideMonth = calendarDate.month !== this.currentDate.month; + + const isSelected = Boolean( + this.selectedDate && + isSameDay(this.toCalendarDate(this.selectedDate), calendarDate) + ); + + const isToday = isSameDay(calendarDate, this.today); + + const isDisabled = Boolean( + this.disabled || + (this.minDate && calendarDate.compare(this.minDate) < 0) || + (this.maxDate && calendarDate.compare(this.maxDate) > 0) + ); + + const dayClasses: ClassInfo = { + 'spectrum-Calendar-date': true, + 'is-outsideMonth': isOutsideMonth, + 'is-selected': isSelected, + 'is-today': isToday, + 'is-disabled': isDisabled, + }; + + // TODO: The title must include "Today," and " selected" translated to the current language + const currentDayTitle = this.formatDate(calendarDate, { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + }); + + return html` + + this.handleDayClick(calendarDate)} + > + ${this.formatNumber(calendarDate.day)} + + + `; + } + + public handlePreviousMonth(): void { + this.currentDate = startOfMonth(this.currentDate).subtract({ + months: 1, + }); + } + + public handleNextMonth(): void { + this.currentDate = startOfMonth(this.currentDate).add({ months: 1 }); + } + + public handleDayClick(calendarDate: CalendarDate): void { + this.selectedDate = calendarDate.toDate(this.timeZone); + this.setCurrentCalendarDate(); + + this.dispatchEvent( + new CustomEvent('change', { + bubbles: true, + composed: true, + detail: this.selectedDate, + }) + ); + } + + /** + * Defines the array with the indexes (starting at zero) of the weeks of the current month + */ + private setWeeksInCurrentMonth(): void { + const numberOfWeeks = getWeeksInMonth(this.currentDate, this.locale); + + this.weeksInCurrentMonth = [...new Array(numberOfWeeks).keys()]; + } + + /** + * Defines the array with data for the days of the week, starting on the first day of the week according to the + * defined location (Sunday, Monday, etc.) + */ + private setWeekdays(): void { + this.weekdays = [...new Array(daysInWeek).keys()].map((dayIndex) => { + const weekStart = startOfWeek(this.currentDate, this.locale); + const date = weekStart.add({ days: dayIndex }); + + return { + narrow: this.formatDate(date, { weekday: 'narrow' }), + long: this.formatDate(date, { weekday: 'long' }), + }; + }); + } + + /** + * Defines the initial date that will be used to render the calendar, if no specific date is provided + */ + private setInitialCalendarDate(): void { + this.currentDate = this.today; + } + + /** + * If a date is received by the component via property, it uses that date as the current date to render the calendar + */ + private setCurrentCalendarDate(): void { + if (!this.selectedDate) { + return; + } + + this.selectedDate = new Date(this.selectedDate); + + if (!this.isValidDate(this.selectedDate)) { + this.selectedDate = undefined; + return; + } + + this.currentDate = this.toCalendarDate(this.selectedDate); + } + + /** + * Sets the minimum allowed date a user can select by converting a `Date` object to `CalendarDate`, which is the + * type of object used internally by the class + */ + private setMinCalendarDate(): void { + if (!this.min) { + return; + } + + this.min = new Date(this.min); + + if (!this.isValidDate(this.min)) { + this.min = undefined; + return; + } + + this.minDate = this.toCalendarDate(this.min); + } + + /** + * Sets the maximum allowed date a user can select by converting a `Date` object to `CalendarDate`, which is the + * type of object used internally by the class + */ + private setMaxCalendarDate(): void { + if (!this.max) { + return; + } + + this.max = new Date(this.max); + + if (!this.isValidDate(this.max)) { + this.max = undefined; + return; + } + + this.maxDate = this.toCalendarDate(this.max); + } + + /** + * Returns an array with all days of the week corresponding to the given index, starting with the first day of the + * week according to the locale + * + * @param weekIndex - The index of the week + */ + private getDatesInWeek(weekIndex: number): CalendarDate[] { + const dates: CalendarDate[] = []; + + let date = startOfWeek( + startOfMonth(this.currentDate).add({ weeks: weekIndex }), + this.locale + ); + + while (dates.length < daysInWeek) { + dates.push(date); + + const nextDate = date.add({ days: 1 }); + + // If the next day is the same, we have hit the end of the calendar system + if (isSameDay(date, nextDate)) { + break; + } + + date = nextDate; + } + + return dates; + } + + /** + * Converts a `Date` object to a `CalendarDate` + * + * @param date - `Date` object to be converted + */ + private toCalendarDate(date: Date): CalendarDate { + return new CalendarDate( + date.getFullYear(), + date.getMonth() + 1, // The month to create a new `CalendarDate` cannot be a zero-based index, unlike `Date` + date.getDate() + ); + } + + /** + * Checks if the date is valid by parsing the time. Invalid dates return `NaN` for times of invalid dates + * + * @param date - `Date` object to validate + */ + private isValidDate(date: Date): boolean { + return !isNaN(date.getTime()); + } + + /** + * Formats a `CalendarDate` object using the current locale and the provided date format options + * + * @param calendarDate - The `CalendarDate` object that will be used by the formatter + * @param options - All date format options that will be used by the formatter + */ + private formatDate( + calendarDate: CalendarDate, + options: Intl.DateTimeFormatOptions + ): string { + return new DateFormatter(this.locale, options).format( + calendarDate.toDate(this.timeZone) + ); + } + + /** + * Formats a number using the defined locale + * + * @param number - The number to format + */ + private formatNumber(number: number): string { + return new NumberFormatter(this.locale).format(number); + } +} diff --git a/packages/calendar/src/calendar.css b/packages/calendar/src/calendar.css new file mode 100644 index 00000000000..8bfbb5a10a5 --- /dev/null +++ b/packages/calendar/src/calendar.css @@ -0,0 +1,13 @@ +/* +Copyright 2023 Adobe. All rights reserved. +This file is licensed to you under the Apache 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 http://www.apache.org/licenses/LICENSE-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 REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +@import './spectrum-calendar.css'; diff --git a/packages/calendar/src/index.ts b/packages/calendar/src/index.ts new file mode 100644 index 00000000000..c1532216a3a --- /dev/null +++ b/packages/calendar/src/index.ts @@ -0,0 +1,12 @@ +/* +Copyright 2023 Adobe. All rights reserved. +This file is licensed to you under the Apache 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 http://www.apache.org/licenses/LICENSE-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 REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ +export * from './Calendar.js'; diff --git a/packages/calendar/src/spectrum-calendar.css b/packages/calendar/src/spectrum-calendar.css new file mode 100644 index 00000000000..eb03f0451f4 --- /dev/null +++ b/packages/calendar/src/spectrum-calendar.css @@ -0,0 +1,750 @@ +/* +Copyright 2023 Adobe. All rights reserved. +This file is licensed to you under the Apache 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 http://www.apache.org/licenses/LICENSE-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 REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +/* THIS FILE IS MACHINE GENERATED. DO NOT EDIT */ +:host { + --spectrum-calendar-border-radius-reset: 0; + --spectrum-calendar-border-width-reset: 0; + --spectrum-calendar-margin-y: 24px; + --spectrum-calendar-margin-x: 32px; + --spectrum-calendar-width: calc( + ( + var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ) + + var( + --spectrum-calendar-day-padding, + var(--spectrum-global-dimension-static-size-50) + ) * 2 + ) * 7 + ); + --spectrum-calendar-button-gap: var(--spectrum-global-dimension-size-40); + --spectrum-calendar-title-text-letter-spacing: var( + --spectrum-detail-m-text-letter-spacing, + var(--spectrum-global-font-letter-spacing-medium) + ); +} +:host { + display: inline-block; + width: var(--spectrum-calendar-width, 250px); +} +:host([padded]) { + margin: var(--spectrum-calendar-margin-x) var(--spectrum-calendar-margin-y); +} +.spectrum-Calendar-header { + align-items: center; + display: flex; + width: 100%; +} +.spectrum-Calendar-title { + flex-grow: 1; + font-size: var( + --spectrum-calendar-title-text-size, + var(--spectrum-global-dimension-font-size-300) + ); + font-weight: 700; + line-height: var( + --spectrum-calendar-title-height, + var(--spectrum-global-dimension-static-size-400) + ); + margin: 0; + order: 1; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; +} +:host([dir='ltr']) .spectrum-Calendar-nextMonth, +:host([dir='ltr']) .spectrum-Calendar-prevMonth, +:host([dir='rtl']) .spectrum-Calendar-nextMonth, +:host([dir='rtl']) .spectrum-Calendar-prevMonth { + margin: 0 var(--spectrum-calendar-button-gap); +} +:host([dir='rtl']) .spectrum-Calendar-nextMonth, +:host([dir='rtl']) .spectrum-Calendar-prevMonth { + transform: matrix(-1, 0, 0, 1, 0, 0); +} +.spectrum-Calendar-prevMonth { + order: 0; +} +.spectrum-Calendar-nextMonth { + order: 2; +} +.spectrum-Calendar-dayOfWeek { + border-bottom: none !important; + cursor: default; + display: flex; + flex-direction: column; + font-size: var( + --spectrum-calendar-day-title-text-size, + var(--spectrum-global-dimension-font-size-50) + ); + font-weight: var( + --spectrum-calendar-day-title-text-font-weight, + var(--spectrum-alias-detail-text-font-weight-regular) + ); + height: 100%; + justify-content: flex-end; + -webkit-text-decoration: none !important; + text-decoration: none !important; + text-transform: uppercase; + width: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); +} +:host([title]) .spectrum-Calendar-dayOfWeek { + border-bottom: none; + letter-spacing: var(--spectrum-calendar-title-text-letter-spacing); + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} +.spectrum-Calendar-body { + outline: none; +} +.spectrum-Calendar-table { + border-collapse: collapse; + border-spacing: 0; + table-layout: fixed; + -webkit-user-select: none; + user-select: none; +} +.spectrum-Calendar-tableCell { + box-sizing: content-box; + height: var( + --spectrum-calendar-day-height, + var(--spectrum-global-dimension-size-400) + ); + padding: 0; + padding: var( + --spectrum-calendar-day-padding, + var(--spectrum-global-dimension-static-size-50) + ); + position: relative; + text-align: center; + width: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); +} +.spectrum-Calendar-tableCell:focus { + outline: 0; +} +:host([dir='ltr']) .spectrum-Calendar-date { + left: 0; +} +:host([dir='rtl']) .spectrum-Calendar-date { + right: 0; +} +.spectrum-Calendar-date { + border: var( + --spectrum-calendar-day-border-size, + var(--spectrum-alias-border-size-thick) + ) + solid transparent; + border-radius: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); + box-sizing: border-box; + cursor: pointer; + display: block; + font-size: var( + --spectrum-calendar-day-text-size, + var(--spectrum-alias-font-size-default) + ); + height: var( + --spectrum-calendar-day-height, + var(--spectrum-global-dimension-size-400) + ); + line-height: calc( + var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ) - + var( + --spectrum-calendar-day-border-size, + var(--spectrum-alias-border-size-thick) + ) * 2 + ); + margin: var( + --spectrum-calendar-day-padding, + var(--spectrum-global-dimension-static-size-50) + ); + position: absolute; + top: 0; + white-space: nowrap; + width: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); +} +.spectrum-Calendar-date:lang(ja), +.spectrum-Calendar-date:lang(ko), +.spectrum-Calendar-date:lang(zh) { + font-size: var( + --spectrum-calendar-day-text-size-han, + var(--spectrum-global-dimension-font-size-50) + ); +} +.spectrum-Calendar-date.is-disabled { + cursor: default; + pointer-events: none; +} +.spectrum-Calendar-date.is-outsideMonth { + visibility: hidden; +} +:host([dir='ltr']) .spectrum-Calendar-date:before { + left: calc( + 50% - + var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ) / 2 + ); +} +:host([dir='rtl']) .spectrum-Calendar-date:before { + right: calc( + 50% - + var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ) / 2 + ); +} +.spectrum-Calendar-date:before { + border: var( + --spectrum-calendar-day-border-size, + var(--spectrum-alias-border-size-thick) + ) + solid transparent; + border-radius: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); + box-sizing: border-box; + content: ''; + height: var( + --spectrum-calendar-day-height, + var(--spectrum-global-dimension-size-400) + ); + position: absolute; + top: calc( + 50% - + var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ) / 2 + ); + width: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); +} +.spectrum-Calendar-date.is-selected:not(.is-range-selection) { + font-weight: var( + --spectrum-calendar-day-text-font-weight-selected, + var(--spectrum-global-font-weight-bold) + ); +} +.spectrum-Calendar-date.is-selected:not(.is-range-selection):before { + display: none; +} +.spectrum-Calendar-date.is-today { + font-weight: var( + --spectrum-calendar-day-today-text-font-weight, + var(--spectrum-global-font-weight-bold) + ); +} +.spectrum-Calendar-date.is-range-selection { + border-radius: var(--spectrum-calendar-border-radius-reset); + border-width: var(--spectrum-calendar-border-width-reset); + line-height: var( + --spectrum-calendar-day-height, + var(--spectrum-global-dimension-size-400) + ); + margin: var( + --spectrum-calendar-day-padding, + var(--spectrum-global-dimension-static-size-50) + ) + 0; + width: calc( + var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ) + + var( + --spectrum-calendar-day-padding, + var(--spectrum-global-dimension-static-size-50) + ) * 2 + ); +} +.spectrum-Calendar-date.is-range-selection.is-range-end, +.spectrum-Calendar-date.is-range-selection.is-range-start, +.spectrum-Calendar-date.is-range-selection.is-selection-end, +.spectrum-Calendar-date.is-range-selection.is-selection-start { + width: calc( + var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ) + + var( + --spectrum-calendar-day-padding, + var(--spectrum-global-dimension-static-size-50) + ) + ); +} +.spectrum-Calendar-date.is-range-selection.is-selection-end, +.spectrum-Calendar-date.is-range-selection.is-selection-start { + font-weight: var( + --spectrum-calendar-day-text-font-weight-cap-selected, + var(--spectrum-global-font-weight-bold) + ); +} +.spectrum-Calendar-date.is-range-selection.is-selection-end:after, +.spectrum-Calendar-date.is-range-selection.is-selection-start:after { + border-radius: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); + content: ''; + display: block; + height: var( + --spectrum-calendar-day-height, + var(--spectrum-global-dimension-size-400) + ); + position: absolute; + top: 0; + width: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); +} +:host([dir='ltr']) .spectrum-Calendar-date.is-range-selection.is-range-start, +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-selection-start { + padding-right: var( + --spectrum-calendar-day-padding, + var(--spectrum-global-dimension-static-size-50) + ); +} +:host([dir='rtl']) .spectrum-Calendar-date.is-range-selection.is-range-start, +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-selection-start { + padding-left: var( + --spectrum-calendar-day-padding, + var(--spectrum-global-dimension-static-size-50) + ); +} +:host([dir='ltr']) .spectrum-Calendar-date.is-range-selection.is-range-start, +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-selection-start { + margin-left: var( + --spectrum-calendar-day-padding, + var(--spectrum-global-dimension-static-size-50) + ); +} +:host([dir='rtl']) .spectrum-Calendar-date.is-range-selection.is-range-start, +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-selection-start { + margin-right: var( + --spectrum-calendar-day-padding, + var(--spectrum-global-dimension-static-size-50) + ); +} +:host([dir='ltr']) .spectrum-Calendar-date.is-range-selection.is-range-start, +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-selection-start { + border-top-left-radius: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); +} +:host([dir='rtl']) .spectrum-Calendar-date.is-range-selection.is-range-start, +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-selection-start { + border-top-right-radius: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); +} +:host([dir='ltr']) .spectrum-Calendar-date.is-range-selection.is-range-start, +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-selection-start { + border-bottom-left-radius: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); +} +:host([dir='rtl']) .spectrum-Calendar-date.is-range-selection.is-range-start, +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-selection-start { + border-bottom-right-radius: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); +} +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-range-start:after, +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-range-start:before, +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-selection-start:after, +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-selection-start:before { + left: 0; +} +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-range-start:after, +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-range-start:before, +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-selection-start:after, +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-selection-start:before { + right: 0; +} +:host([dir='ltr']) .spectrum-Calendar-date.is-range-selection.is-range-end, +:host([dir='ltr']) .spectrum-Calendar-date.is-range-selection.is-selection-end { + padding-left: var( + --spectrum-calendar-day-padding, + var(--spectrum-global-dimension-static-size-50) + ); +} +:host([dir='rtl']) .spectrum-Calendar-date.is-range-selection.is-range-end, +:host([dir='rtl']) .spectrum-Calendar-date.is-range-selection.is-selection-end { + padding-right: var( + --spectrum-calendar-day-padding, + var(--spectrum-global-dimension-static-size-50) + ); +} +:host([dir='ltr']) .spectrum-Calendar-date.is-range-selection.is-range-end, +:host([dir='ltr']) .spectrum-Calendar-date.is-range-selection.is-selection-end { + margin-right: var( + --spectrum-calendar-day-padding, + var(--spectrum-global-dimension-static-size-50) + ); +} +:host([dir='rtl']) .spectrum-Calendar-date.is-range-selection.is-range-end, +:host([dir='rtl']) .spectrum-Calendar-date.is-range-selection.is-selection-end { + margin-left: var( + --spectrum-calendar-day-padding, + var(--spectrum-global-dimension-static-size-50) + ); +} +:host([dir='ltr']) .spectrum-Calendar-date.is-range-selection.is-range-end, +:host([dir='ltr']) .spectrum-Calendar-date.is-range-selection.is-selection-end { + border-top-right-radius: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); +} +:host([dir='rtl']) .spectrum-Calendar-date.is-range-selection.is-range-end, +:host([dir='rtl']) .spectrum-Calendar-date.is-range-selection.is-selection-end { + border-top-left-radius: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); +} +:host([dir='ltr']) .spectrum-Calendar-date.is-range-selection.is-range-end, +:host([dir='ltr']) .spectrum-Calendar-date.is-range-selection.is-selection-end { + border-bottom-right-radius: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); +} +:host([dir='rtl']) .spectrum-Calendar-date.is-range-selection.is-range-end, +:host([dir='rtl']) .spectrum-Calendar-date.is-range-selection.is-selection-end { + border-bottom-left-radius: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); +} +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-range-end:after, +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-range-end:before, +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-selection-end:after, +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-selection-end:before { + left: auto; +} +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-range-end:after, +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-range-end:before, +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-selection-end:after, +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-selection-end:before { + right: auto; +} +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-range-end:after, +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-range-end:before, +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-selection-end:after, +:host([dir='ltr']) + .spectrum-Calendar-date.is-range-selection.is-selection-end:before { + right: 0; +} +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-range-end:after, +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-range-end:before, +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-selection-end:after, +:host([dir='rtl']) + .spectrum-Calendar-date.is-range-selection.is-selection-end:before { + left: 0; +} +.spectrum-Calendar-date.is-range-selection.is-range-start.is-range-end, +.spectrum-Calendar-date.is-range-selection.is-selection-end.is-range-start, +.spectrum-Calendar-date.is-range-selection.is-selection-start.is-range-end, +.spectrum-Calendar-date.is-range-selection.is-selection-start.is-selection-end { + border-radius: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); + width: var( + --spectrum-calendar-day-width, + var(--spectrum-global-dimension-size-400) + ); +} +.spectrum-Calendar-nextMonth, +.spectrum-Calendar-prevMonth { + color: var( + --spectrum-calendar-button-icon-color, + var(--spectrum-global-color-gray-700) + ); +} +.spectrum-Calendar-dayOfWeek { + color: var( + --spectrum-calendar-day-title-text-color, + var(--spectrum-global-color-gray-700) + ); +} +.spectrum-Calendar-date:hover { + color: var( + --spectrum-calendar-day-text-color-hover, + var(--spectrum-alias-text-color-hover) + ); +} +.spectrum-Calendar-date:hover:not(.is-selection-end):not(.is-selection-start):before { + background: var( + --spectrum-calendar-day-background-color-hover, + var(--spectrum-alias-highlight-hover) + ); +} +.spectrum-Calendar-date:hover.is-selected { + color: var( + --spectrum-calendar-day-text-color-selected-hover, + var(--spectrum-alias-text-color-hover) + ); +} +.spectrum-Calendar-date:hover.is-selected:not(.is-selection-end):not(.is-selection-start):before { + background: var( + --spectrum-calendar-day-background-color-selected-hover, + var(--spectrum-alias-highlight-selected-hover) + ); +} +.spectrum-Calendar-date:hover.is-range-selection:before { + background: var( + --spectrum-calendar-day-background-color-selected-hover, + var(--spectrum-alias-highlight-selected-hover) + ); +} +.spectrum-Calendar-date:active { + background-color: var( + --spectrum-calendar-day-background-color-down, + var(--spectrum-alias-highlight-down) + ); +} +.spectrum-Calendar-date.is-selected { + background: var( + --spectrum-calendar-day-background-color-selected, + var(--spectrum-alias-highlight-selected) + ); + color: var( + --spectrum-calendar-day-text-color-selected, + var(--spectrum-alias-text-color-hover) + ); +} +.spectrum-Calendar-date.is-selected:not(.is-range-selection) { + background: var( + --spectrum-calendar-day-background-color-cap-selected, + var(--spectrum-alias-highlight-selected-hover) + ); +} +.spectrum-Calendar-date.is-today { + border-color: var( + --spectrum-calendar-day-today-border-color, + var(--spectrum-global-color-gray-800) + ); + color: var( + --spectrum-calendar-day-today-text-color, + var(--spectrum-alias-text-color) + ); +} +.spectrum-Calendar-date.is-today:before { + border-color: var( + --spectrum-calendar-day-today-border-color, + var(--spectrum-global-color-gray-800) + ); +} +.spectrum-Calendar-date.is-today:hover.is-selected:not(.is-range-selection):before { + background: var( + --spectrum-calendar-day-today-background-color-selected-hover, + var(--spectrum-alias-highlight-selected-hover) + ); +} +.spectrum-Calendar-date.is-today.is-disabled { + border-color: var( + --spectrum-calendar-day-today-border-color-disabled, + var(--spectrum-global-color-gray-400) + ); + color: var( + --spectrum-calendar-day-today-text-color-disabled, + var(--spectrum-alias-text-color-disabled) + ); +} +.spectrum-Calendar-date.is-today.is-disabled:before { + border-color: var( + --spectrum-calendar-day-today-border-color-disabled, + var(--spectrum-global-color-gray-400) + ); +} +.spectrum-Calendar-date.is-focused:not(.is-range-selection) { + background: var( + --spectrum-calendar-day-background-color-key-focus, + var(--spectrum-alias-highlight-hover) + ); + border-color: var( + --spectrum-calendar-day-border-color-key-focus, + var(--spectrum-alias-focus-color) + ); + color: var( + --spectrum-calendar-day-text-color-key-focus, + var(--spectrum-alias-text-color-hover) + ); +} +.spectrum-Calendar-date.is-focused:not(.is-range-selection).is-today { + border-color: var( + --spectrum-calendar-day-border-color-key-focus, + var(--spectrum-alias-focus-color) + ); +} +.spectrum-Calendar-date.is-focused:not(.is-range-selection).is-selected, +.spectrum-Calendar-date.is-focused:not(.is-range-selection):active { + background: var( + --spectrum-calendar-day-background-color-cap-selected, + var(--spectrum-alias-highlight-selected-hover) + ); + border-color: var( + --spectrum-calendar-day-border-color-key-focus, + var(--spectrum-alias-focus-color) + ); + color: var( + --spectrum-calendar-day-text-color-selected, + var(--spectrum-alias-text-color-hover) + ); +} +.spectrum-Calendar-date.is-focused.is-selected:before { + background: var( + --spectrum-calendar-day-background-color-selected-hover, + var(--spectrum-alias-highlight-selected-hover) + ); +} +.spectrum-Calendar-date.is-focused.is-range-selection:before { + background: var( + --spectrum-calendar-day-background-color-selected-hover, + var(--spectrum-alias-highlight-selected-hover) + ); +} +.spectrum-Calendar-date.is-focused:before { + border-color: var( + --spectrum-calendar-day-border-color-key-focus, + var(--spectrum-alias-focus-color) + ); +} +.spectrum-Calendar-date.is-disabled { + color: var( + --spectrum-calendar-day-text-color-disabled, + var(--spectrum-alias-text-color-disabled) + ); +} +.spectrum-Calendar-date.is-selection-end, +.spectrum-Calendar-date.is-selection-start { + color: var( + --spectrum-calendar-day-text-color-cap-selected, + var(--spectrum-alias-text-color-hover) + ); +} +.spectrum-Calendar-date.is-selection-end:after, +.spectrum-Calendar-date.is-selection-start:after { + background-color: var( + --spectrum-calendar-day-background-color-selected, + var(--spectrum-alias-highlight-selected) + ); +} +.spectrum-Calendar-date.is-selection-end.is-disabled, +.spectrum-Calendar-date.is-selection-start.is-disabled { + color: var( + --spectrum-calendar-day-text-color-disabled, + var(--spectrum-alias-text-color-disabled) + ); +} +@media (forced-colors: active) { + .spectrum-Calendar-date { + --spectrum-calendar-button-icon-color: ButtonText; + --spectrum-calendar-day-background-color-cap-selected: Highlight; + --spectrum-calendar-day-background-color-down: ButtonFace; + --spectrum-calendar-day-background-color-hover: transparent; + --spectrum-calendar-day-background-color-key-focus: ButtonFace; + --spectrum-calendar-day-background-color-selected-hover: transparent; + --spectrum-calendar-day-background-color-selected: Highlight; + --spectrum-calendar-day-border-color-key-focus: ButtonText; + --spectrum-calendar-day-text-color-cap-selected: HighlightText; + --spectrum-calendar-day-text-color-disabled: GrayText; + --spectrum-calendar-day-text-color-hover: ButtonText; + --spectrum-calendar-day-text-color-key-focus: ButtonText; + --spectrum-calendar-day-text-color-selected-hover: HighlightText; + --spectrum-calendar-day-text-color-selected: HighlightText; + --spectrum-calendar-day-title-text-color: CanvasText; + --spectrum-calendar-day-today-background-color-selected-hover: Highlight; + --spectrum-calendar-day-today-border-color-disabled: GrayText; + --spectrum-calendar-day-today-border-color: ButtonText; + --spectrum-calendar-day-today-text-color-disabled: GrayText; + --spectrum-calendar-day-today-text-color: ButtonText; + color: CanvasText; + forced-color-adjust: none; + } + .spectrum-Calendar-date.is-range-selection { + color: HighlightText; + } + .spectrum-Calendar-date.is-range-selection.is-selection-end:after, + .spectrum-Calendar-date.is-range-selection.is-selection-start:after { + content: none; + } + .spectrum-Calendar-date.is-disabled.is-range-selection, + .spectrum-Calendar-date.is-disabled.is-selected { + background: Highlight; + color: HighlightText; + } + .spectrum-Calendar-date:hover.is-today { + color: ButtonText; + } +} diff --git a/packages/calendar/src/spectrum-config.js b/packages/calendar/src/spectrum-config.js new file mode 100644 index 00000000000..c83b888d563 --- /dev/null +++ b/packages/calendar/src/spectrum-config.js @@ -0,0 +1,35 @@ +/* +Copyright 2023 Adobe. All rights reserved. +This file is licensed to you under the Apache 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 http://www.apache.org/licenses/LICENSE-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 REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ +// @ts-check + +import { converterFor } from '../../../tasks/process-spectrum-utils.js'; + +const converter = converterFor('spectrum-Calendar'); + +/** + * @type { import('../../../tasks/spectrum-css-converter').SpectrumCSSConverter } + */ +const config = { + conversions: [ + { + inPackage: '@spectrum-css/calendar', + outPackage: 'calendar', + fileName: 'calendar', + components: [ + converter.classToHost(), + converter.classToAttribute('spectrum-Calendar--padded'), + ], + }, + ], +}; + +export default config; diff --git a/packages/calendar/src/types.ts b/packages/calendar/src/types.ts new file mode 100644 index 00000000000..659c5e03606 --- /dev/null +++ b/packages/calendar/src/types.ts @@ -0,0 +1,17 @@ +/* +Copyright 2023 Adobe. All rights reserved. +This file is licensed to you under the Apache 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 http://www.apache.org/licenses/LICENSE-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 REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ +export const daysInWeek = 7; + +export interface CalendarWeekday { + narrow: string; + long: string; +} diff --git a/packages/calendar/stories/calendar.stories.ts b/packages/calendar/stories/calendar.stories.ts new file mode 100644 index 00000000000..73c2512e93e --- /dev/null +++ b/packages/calendar/stories/calendar.stories.ts @@ -0,0 +1,243 @@ +/* +Copyright 2023 Adobe. All rights reserved. +This file is licensed to you under the Apache 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 http://www.apache.org/licenses/LICENSE-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 REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ +import { html, TemplateResult } from '@spectrum-web-components/base'; +import { ifDefined } from '@spectrum-web-components/base/src/directives.js'; + +import { spreadProps } from '../../../test/lit-helpers.js'; + +import '@spectrum-web-components/calendar/sp-calendar.js'; +import '@spectrum-web-components/theme/sp-theme.js'; + +const locales = [ + 'cs-CZ', + 'cy-GB', + 'da-DK', + 'de-DE', + 'en-GB', + 'en-US', + 'es-ES', + 'fi-FI', + 'fr-FR', + 'hu-HU', + 'it-IT', + 'ja-JP', + 'ko-KR', + 'nb-NO', + 'nl-NL', + 'pl-PL', + 'pt-BR', + 'ru-RU', + 'sv-SE', + 'tr-TR', + 'uk-UA', + 'zh-Hans-CN', + 'zh-Hans-CN-u-nu-hanidec', + 'zh-Hant-TW', + 'zz-ZY', + 'zz-ZZ', +] as const; + +const defaultLocale = 'en-US'; + +const hiddenProperty = { + table: { + disable: true, + }, +}; + +export default { + title: 'Calendar', + component: 'sp-calendar', + + argTypes: { + lang: { + options: locales, + control: { + type: 'select', + }, + table: { + defaultValue: { + summary: defaultLocale, + }, + }, + }, + + // Don't render private properties and getters in the Storybook UI + currentDate: { ...hiddenProperty }, + minDate: { ...hiddenProperty }, + maxDate: { ...hiddenProperty }, + weeksInCurrentMonth: { ...hiddenProperty }, + weekdays: { ...hiddenProperty }, + languageResolver: { ...hiddenProperty }, + timeZone: { ...hiddenProperty }, + locale: { ...hiddenProperty }, + today: { ...hiddenProperty }, + + // Inherited + _dirParent: { ...hiddenProperty }, + shadowRoot: { ...hiddenProperty }, + dir: { ...hiddenProperty }, + isLTR: { ...hiddenProperty }, + }, + + args: { + lang: defaultLocale, + }, + + parameters: { + controls: { + // Hide "This story is not configured to handle controls" warning + hideNoControlsWarning: true, + }, + actions: { + handles: ['onChange'], + }, + }, +}; + +interface StoryArgs { + lang?: string; + + padded?: boolean; + disabled?: boolean; + selectedDate?: Date; + min?: Date; + max?: Date; + + onChange?: (date: Date) => void; + + [prop: string]: unknown; +} + +const renderCalendar = ( + title: string, + args: StoryArgs = {} +): TemplateResult => { + return html` + +

${title}

+

+ Locale: + ${args.lang} +

+ +
+ + +
+ `; +}; + +export const Default = (args: StoryArgs = {}): TemplateResult => { + return renderCalendar('Default', args); +}; + +export const padded = (args: StoryArgs = {}): TemplateResult => { + return renderCalendar(`Padded? ${args.padded}`, args); +}; + +padded.argTypes = { + padded: { + control: 'boolean', + table: { + defaultValue: { + summary: true, + }, + }, + }, +}; + +padded.args = { + padded: true, +}; + +export const disabled = (args: StoryArgs = {}): TemplateResult => { + return renderCalendar(`Disabled? ${args.disabled}`, args); +}; + +disabled.argTypes = { + disabled: { + control: 'boolean', + table: { + defaultValue: { + summary: true, + }, + }, + }, +}; + +disabled.args = { + disabled: true, +}; + +export const selectedDate = (args: StoryArgs = {}): TemplateResult => { + const date = new Date(2019, 0, 30); + const formatted = Intl.DateTimeFormat(defaultLocale, { + day: 'numeric', + month: 'long', + year: 'numeric', + }).format(date); + + args = { + ...args, + selectedDate: date, + }; + + return renderCalendar(`Selected Date: ${formatted}`, args); +}; + +export const minimumDate = (args: StoryArgs = {}): TemplateResult => { + const today = new Date(); + const lastMonth = new Date( + today.getFullYear(), + today.getMonth() - 1, + today.getDate() + ); + + const formatted = Intl.DateTimeFormat(defaultLocale, { + day: 'numeric', + month: 'long', + year: 'numeric', + }).format(lastMonth); + + args = { + ...args, + min: lastMonth, + }; + + return renderCalendar(`Minimum Date: ${formatted}`, args); +}; + +export const maximumDate = (args: StoryArgs = {}): TemplateResult => { + const today = new Date(); + const nextMonth = new Date( + today.getFullYear(), + today.getMonth() + 1, + today.getDate() + ); + + const formatted = Intl.DateTimeFormat(defaultLocale, { + day: 'numeric', + month: 'long', + year: 'numeric', + }).format(nextMonth); + + args = { + ...args, + max: nextMonth, + }; + + return renderCalendar(`Maximum Date: ${formatted}`, args); +}; diff --git a/packages/calendar/test/benchmark/basic-test.ts b/packages/calendar/test/benchmark/basic-test.ts new file mode 100644 index 00000000000..5ae4a925f61 --- /dev/null +++ b/packages/calendar/test/benchmark/basic-test.ts @@ -0,0 +1,18 @@ +/* +Copyright 2023 Adobe. All rights reserved. +This file is licensed to you under the Apache 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 http://www.apache.org/licenses/LICENSE-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 REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ +import '@spectrum-web-components/calendar/sp-calendar.js'; +import { html } from '@spectrum-web-components/base'; +import { measureFixtureCreation } from '../../../../test/benchmark/helpers.js'; + +measureFixtureCreation(html` + +`); diff --git a/packages/calendar/test/calendar.test.ts b/packages/calendar/test/calendar.test.ts new file mode 100644 index 00000000000..9bc72ba3af8 --- /dev/null +++ b/packages/calendar/test/calendar.test.ts @@ -0,0 +1,38 @@ +/* +Copyright 2023 Adobe. All rights reserved. +This file is licensed to you under the Apache 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 http://www.apache.org/licenses/LICENSE-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 REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ +import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; + +import '../sp-calendar.js'; +import { Calendar } from '..'; +import { testForLitDevWarnings } from '../../../test/testing-helpers.js'; + +describe('Calendar', () => { + testForLitDevWarnings( + async () => + await fixture( + html` + + ` + ) + ); + it('loads default calendar accessibly', async () => { + const el = await fixture( + html` + + ` + ); + + await elementUpdated(el); + + await expect(el).to.be.accessible(); + }); +}); diff --git a/packages/calendar/tsconfig.json b/packages/calendar/tsconfig.json new file mode 100644 index 00000000000..c90873db4cf --- /dev/null +++ b/packages/calendar/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": "./" + }, + "include": ["*.ts", "src/*.ts"], + "exclude": ["test/*.ts", "stories/*.ts"], + "references": [{ "path": "../../tools/base" }] +} diff --git a/tools/base/src/directives.ts b/tools/base/src/directives.ts index 12b05449945..16780ee6e9b 100644 --- a/tools/base/src/directives.ts +++ b/tools/base/src/directives.ts @@ -13,6 +13,7 @@ governing permissions and limitations under the License. export { ifDefined } from 'lit/directives/if-defined.js'; export { repeat } from 'lit/directives/repeat.js'; export { classMap } from 'lit/directives/class-map.js'; +export type { ClassInfo } from 'lit/directives/class-map.js'; export { styleMap } from 'lit/directives/style-map.js'; export type { StyleInfo } from 'lit/directives/style-map.js'; export { until } from 'lit/directives/until.js'; diff --git a/tools/bundle/elements.ts b/tools/bundle/elements.ts index 12e537eec70..af94b1b3ff1 100644 --- a/tools/bundle/elements.ts +++ b/tools/bundle/elements.ts @@ -22,6 +22,7 @@ import '@spectrum-web-components/banner/sp-banner.js'; import '@spectrum-web-components/button/sp-button.js'; import '@spectrum-web-components/button/sp-clear-button.js'; import '@spectrum-web-components/button-group/sp-button-group.js'; +import '@spectrum-web-components/calendar/sp-calendar.js'; import '@spectrum-web-components/card/sp-card.js'; import '@spectrum-web-components/checkbox/sp-checkbox.js'; import '@spectrum-web-components/coachmark/sp-coachmark.js'; diff --git a/tools/bundle/package.json b/tools/bundle/package.json index e32e397f9b6..cbf5716100d 100644 --- a/tools/bundle/package.json +++ b/tools/bundle/package.json @@ -72,6 +72,7 @@ "@spectrum-web-components/base": "^0.34.0", "@spectrum-web-components/button": "^0.34.0", "@spectrum-web-components/button-group": "^0.34.0", + "@spectrum-web-components/calendar": "^0.0.1", "@spectrum-web-components/card": "^0.34.0", "@spectrum-web-components/checkbox": "^0.34.0", "@spectrum-web-components/clear-button": "^0.34.0", diff --git a/tools/bundle/src/index.ts b/tools/bundle/src/index.ts index bc86cabd7e4..3d7a937e503 100644 --- a/tools/bundle/src/index.ts +++ b/tools/bundle/src/index.ts @@ -20,6 +20,7 @@ export * from '@spectrum-web-components/badge'; export * from '@spectrum-web-components/banner'; export * from '@spectrum-web-components/button'; export * from '@spectrum-web-components/button-group'; +export * from '@spectrum-web-components/calendar'; export * from '@spectrum-web-components/card'; export * from '@spectrum-web-components/checkbox'; export * from '@spectrum-web-components/coachmark'; diff --git a/tools/bundle/tsconfig.json b/tools/bundle/tsconfig.json index 2314367fdc8..bc2b6774fe0 100644 --- a/tools/bundle/tsconfig.json +++ b/tools/bundle/tsconfig.json @@ -18,6 +18,7 @@ { "path": "../../packages/banner" }, { "path": "../../packages/button" }, { "path": "../../packages/button-group" }, + { "path": "../../packages/calendar" }, { "path": "../../packages/card" }, { "path": "../../packages/checkbox" }, { "path": "../../packages/coachmark" }, diff --git a/tsconfig-all.json b/tsconfig-all.json index 7ef6eb33807..a3ad7b0c079 100644 --- a/tsconfig-all.json +++ b/tsconfig-all.json @@ -27,6 +27,7 @@ { "path": "packages/badge" }, { "path": "packages/button" }, { "path": "packages/button-group" }, + { "path": "packages/calendar" }, { "path": "packages/card" }, { "path": "packages/checkbox" }, { "path": "packages/clear-button" }, diff --git a/yarn.lock b/yarn.lock index 5b8fc212b22..538faa80e9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2499,6 +2499,13 @@ resolved "https://registry.yarnpkg.com/@import-maps/resolve/-/resolve-1.0.1.tgz#1e9fcadcf23aa0822256a329aabca241879d37c9" integrity sha512-tWZNBIS1CoekcwlMuyG2mr0a1Wo5lb5lEHwwWvZo+5GLgr3e9LLDTtmgtCWEwBpXMkxn9D+2W9j2FY6eZQq0tA== +"@internationalized/date@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.2.0.tgz#1d266e5e5543a059cf8cca9b954fa033c3e58a75" + integrity sha512-VDMHN1m33L4eqPs5BaihzgQJXyaORbMoHOtrapFxx179J8ucY5CRIHYsq5RRLKPHZWgjNfa5v6amWWDkkMFywA== + dependencies: + "@swc/helpers" "^0.4.14" + "@internationalized/number@^3.1.0": version "3.1.2" resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.1.2.tgz#4482a6ac573acfb18efd354a42008af20da6c89c" @@ -5339,6 +5346,11 @@ resolved "https://registry.yarnpkg.com/@spectrum-css/buttongroup/-/buttongroup-6.0.57.tgz#e393ecef4c352e56c289fa660005db294be9d506" integrity sha512-pVdfGIcZdPeWv0Ha0BKICrQx7YiENwKlnT9mZXy3Dw8EAxWbR45zsb1xpsMOLFQ/UZtNn5njKD5FcC3gD2ZVRw== +"@spectrum-css/calendar@^3.2.6": + version "3.2.6" + resolved "https://registry.yarnpkg.com/@spectrum-css/calendar/-/calendar-3.2.6.tgz#edd700b5b6188a67711087c9604c7dda9ac8920a" + integrity sha512-bB5CXl6B4zpJizEXmRGvv+WJHImXi2D4VaRL0Yi7EBmswYfi0eEjN6CiKljoqe+MBc5nHPp4pimI08Al08q2MA== + "@spectrum-css/card@^6.0.4": version "6.0.4" resolved "https://registry.yarnpkg.com/@spectrum-css/card/-/card-6.0.4.tgz#0dc81c5753c3c1540939061778576336852af135"