@glatam/calendar-ui 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # 📖 API Reference Guide: @glatam/calendar-ui
2
+
3
+ This is the official technical documentation for the `@glatam/calendar-ui` package. Designed under modern, minimalist (Apple-style), and high-performance standards.
4
+
5
+ ---
6
+
7
+ ## ⚙️ 1. API Reference: `<glatam-calendar>`
8
+
9
+ The primary calendar component displaying the full grid.
10
+
11
+ ### Properties and Attributes (Props)
12
+
13
+ | JS Property | HTML Attribute | Type | Default | Description |
14
+ | :--- | :--- | :--- | :--- | :--- |
15
+ | `role` | `role` | `'provider' \| 'buyer'` | `'provider'` | `provider` allows editing availability; `buyer` disables blocked slots and enables client booking. |
16
+ | `view` | `view` | `'month' \| 'week' \| 'day'` | `'month'` | Sets the active view. |
17
+ | `locale` | `locale` | `string` | `'es'` | Locale code (IETF tag) for native header and date formatting. |
18
+ | `startOfWeekDay` | `start-of-week-day` | `number` | `0` | First day of the week (`0` = Sunday, `1` = Monday). |
19
+ | `rules` | — | `BlockingRule[]` | `[]` | Array of blocking rules (e.g., weekends, holidays). *JS only.* |
20
+ | `slots` | — | `TimeSlot[]` | *(9am to 6pm)* | Array of daily operating hour slots. *JS only.* |
21
+ | `minDate` | `min-date` | `string` | `''` | Minimum selectable date in `YYYY-MM-DD` format. |
22
+ | `maxDate` | `max-date` | `string` | `''` | Maximum selectable date in `YYYY-MM-DD` format. |
23
+ | `showNeighboringMonth`| `show-neighboring-month`| `boolean` | `true` | If `false`, hides neighboring month dates. |
24
+ | `hostTimezone` | `host-timezone` | `string` | `'America/Bogota'` | Timezone of the host or experience venue. |
25
+ | `activeTimezone` | `active-timezone` | `'host' \| 'local'` | `'local'` | `'local'` automatically shifts grid slots to the buyer's local time. |
26
+ | `tileClassName` | — | `Function` | `null` | Callback `({ date, dateString }) => string` to add classes to monthly cells. |
27
+
28
+ ### Events
29
+
30
+ | Event Name | Detail Payload (`event.detail`) | Description |
31
+ | :--- | :--- | :--- |
32
+ | **`booking-selected`** | `{ dateString, start, end, hostStart, hostEnd }` | Fired when a buyer clicks an available slot. Outputs client local and host reference times. |
33
+ | **`rules-changed`** | `{ rules: BlockingRule[] }` | Fired when the provider saves a block or task. |
34
+ | **`date-selected`** | `{ dateString: string }` | Fired when a month cell is clicked. |
35
+ | **`range-selected`** | `{ dateString: string, start: string, end: string }` | Fired when dragging a slot range in week/day views. |
36
+
37
+ ---
38
+
39
+ ## ⚙️ 2. API Reference: `<glatam-calendar-mini>`
40
+
41
+ The inline checkout popup calendar component.
42
+
43
+ ### Properties and Attributes (Props)
44
+
45
+ | JS Property | HTML Attribute | Type | Default | Description |
46
+ | :--- | :--- | :--- | :--- | :--- |
47
+ | `role` | `role` | `'provider' \| 'buyer'` | `'buyer'` | Defaults to buyer mode for quick selection. |
48
+ | `locale` | `locale` | `string` | `'es'` | Regional language code. |
49
+ | `rules` | — | `BlockingRule[]` | `[]` | Availability rules loaded from your database. |
50
+ | `slots` | — | `TimeSlot[]` | *(9am to 6pm)* | Daily operating hour slots. |
51
+ | `minDate` | `min-date` | `string` | `''` | Minimum selectable date (`YYYY-MM-DD`). |
52
+ | `maxDate` | `max-date` | `string` | `''` | Maximum selectable date (`YYYY-MM-DD`). |
53
+ | `hostTimezone` | `host-timezone` | `string` | `'America/Bogota'` | Host timezone reference. |
54
+ | `activeTimezone` | `active-timezone` | `'host' \| 'local'` | `'local'` | Converts slot times to client local time when set to `'local'`. |
55
+
56
+ ### Events
57
+
58
+ | Event Name | Detail Payload (`event.detail`) | Description |
59
+ | :--- | :--- | :--- |
60
+ | **`booking-selected`** | `{ dateString, start, end, hostStart, hostEnd }` | Fired when a slot is booked. |
61
+
62
+ ---
63
+
64
+ ## 🎨 3. CSS Custom Properties and Shadow Parts
65
+
66
+ ### CSS Variables (Theming)
67
+ Customize the calendar theme by declaring these variables in your global CSS stylesheet:
68
+
69
+ ```css
70
+ glatam-calendar, glatam-calendar-mini {
71
+ --glatam-primary: #0071e3; /* Apple Blue branding color */
72
+ --glatam-primary-hover: #0077ed;
73
+ --glatam-bg: #ffffff; /* Grid cell background */
74
+ --glatam-surface: #f5f5f7; /* Header and button background */
75
+ --glatam-border: #e5e5ea; /* Grid line color */
76
+ --glatam-text: #1d1d1f; /* Primary text color */
77
+ --glatam-text-secondary: #86868b; /* Subtexts and captions */
78
+ --glatam-grid-border-radius: 12px;
79
+ }
80
+ ```
81
+
82
+ ### CSS Shadow Parts
83
+ Target month cells from your global CSS using the `::part()` selector.
84
+
85
+ Example to highlight Fridays with a green border:
86
+ ```css
87
+ glatam-calendar::part(casual-friday), glatam-calendar-mini::part(casual-friday) {
88
+ border: 2px solid #34c759 !important;
89
+ background-color: rgba(52, 199, 89, 0.05);
90
+ }
91
+ ```
92
+
93
+ ---
94
+
95
+ ## 💻 4. Integration Examples
96
+
97
+ ### A) HTML + Vanilla JS
98
+ ```html
99
+ <glatam-calendar id="calendar" role="provider" min-date="2026-07-01"></glatam-calendar>
100
+
101
+ <script type="module">
102
+ import '@glatam/calendar-ui';
103
+ const cal = document.getElementById('calendar');
104
+ cal.rules = [
105
+ { id: '1', type: 'weekly', daysOfWeek: [0, 6], description: 'Closed on Weekends' }
106
+ ];
107
+ cal.addEventListener('rules-changed', (e) => {
108
+ console.log('Saved availability rules:', e.detail.rules);
109
+ });
110
+ </script>
111
+ ```
112
+
113
+ ### B) React 18 / Next.js
114
+ *For Next.js App Router, add `"use client"` at the top of the file.*
115
+
116
+ ```tsx
117
+ import { useEffect, useRef } from 'react';
118
+ import '@glatam/calendar-ui';
119
+
120
+ export default function ReactCalendar() {
121
+ const ref = useRef<any>(null);
122
+
123
+ useEffect(() => {
124
+ if (ref.current) {
125
+ ref.current.rules = [{ id: 't1', type: 'date-range', startDate: '2026-07-25', description: 'Daily Scrum' }];
126
+ ref.current.tileClassName = ({ date }: { date: Date }) => date.getDay() === 5 ? 'casual-friday' : '';
127
+ }
128
+ }, []);
129
+
130
+ return <glatam-calendar ref={ref} role="buyer" locale="en" min-date="2026-07-18" />;
131
+ }
132
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glatam/calendar-ui",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Componente web UI para el calendario @glatam/calendar",
5
5
  "type": "module",
6
6
  "author": "Santiago Hernández Saldarriaga",
@@ -14,12 +14,29 @@
14
14
  "import": "./dist/glatam-calendar.js"
15
15
  }
16
16
  },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/santiagoshs/glatam-calendar.git"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/santiagoshs/glatam-calendar/issues"
23
+ },
24
+ "homepage": "https://github.com/santiagoshs/glatam-calendar#readme",
25
+ "keywords": [
26
+ "calendar",
27
+ "booking-system",
28
+ "lit-element",
29
+ "web-components",
30
+ "timezone-safety",
31
+ "availability-scheduler",
32
+ "ssr-compatible"
33
+ ],
17
34
  "scripts": {
18
35
  "build": "vite build",
19
36
  "dev": "vite"
20
37
  },
21
38
  "dependencies": {
22
- "@glatam/calendar-core": "^1.0.0",
39
+ "@glatam/calendar-core": "^1.0.2",
23
40
  "lit": "^3.1.2"
24
41
  },
25
42
  "devDependencies": {
@@ -1,160 +1,11 @@
1
- import { LitElement, html, css } from 'lit';
1
+ import { LitElement, html } from 'lit';
2
2
  import { customElement, property, state } from 'lit/decorators.js';
3
- import { classMap } from 'lit/directives/class-map.js';
4
3
  import { BlockingRule } from '@glatam/calendar-core';
4
+ import { modalStyles } from '../styles/calendar-modal.css.js';
5
5
 
6
6
  @customElement('glatam-calendar-modal')
7
7
  export class GlatamCalendarModal extends LitElement {
8
- static styles = css`
9
- .modal-overlay {
10
- position: fixed;
11
- top: 0; left: 0; right: 0; bottom: 0;
12
- background: rgba(0, 0, 0, 0.35);
13
- backdrop-filter: blur(10px);
14
- display: flex;
15
- align-items: center;
16
- justify-content: center;
17
- z-index: 1000;
18
- opacity: 0; pointer-events: none;
19
- transition: opacity var(--glatam-transition-normal);
20
- }
21
- .modal-overlay.open {
22
- opacity: 1; pointer-events: auto;
23
- }
24
- .modal-content {
25
- background: var(--glatam-bg);
26
- color: var(--glatam-text);
27
- border-radius: 24px;
28
- padding: 28px;
29
- width: 90%;
30
- max-width: 400px;
31
- box-shadow: 0 20px 50px rgba(0, 0, 0, 0.12);
32
- border: 1px solid var(--glatam-border);
33
- transform: scale(0.92) translateY(10px);
34
- transition: transform var(--glatam-transition-normal), background-color var(--glatam-transition-normal);
35
- display: flex;
36
- flex-direction: column;
37
- gap: 18px;
38
- }
39
- .modal-overlay.open .modal-content {
40
- transform: scale(1) translateY(0);
41
- }
42
- h3 { margin: 0; font-size: 1.3rem; font-weight: 700; letter-spacing: -0.02em; }
43
- .form-group { display: flex; flex-direction: column; gap: 6px; }
44
- label { font-size: 0.8rem; color: var(--glatam-text-secondary); font-weight: 600; }
45
- input[type="text"] {
46
- background: var(--glatam-surface);
47
- border: 1px solid var(--glatam-border);
48
- border-radius: 10px;
49
- padding: 10px 14px;
50
- color: var(--glatam-text);
51
- font-family: inherit;
52
- font-size: 0.9rem;
53
- outline: none;
54
- transition: border-color var(--glatam-transition-fast);
55
- }
56
- input[type="text"]:focus {
57
- border-color: var(--glatam-primary);
58
- }
59
- .switch-row {
60
- display: flex;
61
- justify-content: space-between;
62
- align-items: center;
63
- padding: 4px 0;
64
- }
65
-
66
- /* Apple Switch Style */
67
- .switch {
68
- position: relative;
69
- display: inline-block;
70
- width: 46px;
71
- height: 26px;
72
- }
73
- .switch input { opacity: 0; width: 0; height: 0; }
74
- .slider {
75
- position: absolute;
76
- cursor: pointer;
77
- top: 0; left: 0; right: 0; bottom: 0;
78
- background-color: var(--glatam-border);
79
- transition: .25s cubic-bezier(0.16, 1, 0.3, 1);
80
- border-radius: 26px;
81
- }
82
- .slider:before {
83
- position: absolute;
84
- content: "";
85
- height: 20px;
86
- width: 20px;
87
- left: 3px;
88
- bottom: 3px;
89
- background-color: white;
90
- transition: .25s cubic-bezier(0.16, 1, 0.3, 1);
91
- border-radius: 50%;
92
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
93
- }
94
- input:checked + .slider {
95
- background-color: var(--glatam-primary);
96
- }
97
- input:checked + .slider:before {
98
- transform: translateX(20px);
99
- }
100
-
101
- .days-grid {
102
- display: grid;
103
- grid-template-columns: repeat(7, 1fr);
104
- gap: 6px;
105
- margin-top: 4px;
106
- }
107
- .day-btn {
108
- width: 36px;
109
- height: 36px;
110
- margin: 0 auto;
111
- border-radius: 50%;
112
- border: 1px solid var(--glatam-border);
113
- background: var(--glatam-bg);
114
- color: var(--glatam-text);
115
- font-size: 0.8rem;
116
- font-weight: 600;
117
- cursor: pointer;
118
- display: flex;
119
- align-items: center;
120
- justify-content: center;
121
- transition: background-color var(--glatam-transition-fast), color var(--glatam-transition-fast), border-color var(--glatam-transition-fast);
122
- }
123
- .day-btn:hover {
124
- background-color: var(--glatam-surface);
125
- }
126
- .day-btn.selected {
127
- background: var(--glatam-primary);
128
- color: var(--glatam-text-light);
129
- border-color: var(--glatam-primary);
130
- }
131
- .btn-actions { display: flex; gap: 10px; margin-top: 10px; justify-content: flex-end; }
132
- .btn {
133
- padding: 10px 18px;
134
- border-radius: 20px;
135
- font-size: 0.875rem;
136
- font-weight: 600;
137
- cursor: pointer;
138
- border: none;
139
- transition: opacity var(--glatam-transition-fast), transform var(--glatam-transition-fast);
140
- display: inline-flex;
141
- align-items: center;
142
- justify-content: center;
143
- }
144
- .btn:active { transform: scale(0.97); }
145
- .btn-cancel { background: transparent; color: var(--glatam-text); border: 1px solid var(--glatam-border); }
146
- .btn-cancel:hover { background-color: var(--glatam-surface); }
147
- .btn-save { background: var(--glatam-primary); color: var(--glatam-text-light); }
148
- .btn-save:hover { opacity: 0.95; }
149
- .btn-danger {
150
- background: rgba(255, 69, 58, 0.12);
151
- color: #ff453a;
152
- margin-right: auto;
153
- }
154
- .btn-danger:hover {
155
- background: rgba(255, 69, 58, 0.18);
156
- }
157
- `;
8
+ static styles = modalStyles;
158
9
 
159
10
  @property({ type: Boolean }) open = false;
160
11
  @property({ type: String }) dateString = '';
@@ -184,13 +35,28 @@ export class GlatamCalendarModal extends LitElement {
184
35
  }
185
36
  }
186
37
 
38
+ updated(changedProperties: Map<string, any>) {
39
+ if (changedProperties.has('open')) {
40
+ const dialog = this.shadowRoot?.getElementById('booking-dialog') as HTMLDialogElement;
41
+ if (dialog) {
42
+ if (this.open) {
43
+ if (!dialog.open) dialog.showModal();
44
+ } else {
45
+ if (dialog.open) dialog.close();
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ private handleDialogClose() {
52
+ if (this.open) {
53
+ this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true }));
54
+ }
55
+ }
56
+
187
57
  private toggleDay(dayNum: number) {
188
58
  const idx = this.selectedDays.indexOf(dayNum);
189
- if (idx > -1) {
190
- this.selectedDays = this.selectedDays.filter(d => d !== dayNum);
191
- } else {
192
- this.selectedDays = [...this.selectedDays, dayNum];
193
- }
59
+ this.selectedDays = idx > -1 ? this.selectedDays.filter(d => d !== dayNum) : [...this.selectedDays, dayNum];
194
60
  }
195
61
 
196
62
  private handleSave() {
@@ -223,7 +89,7 @@ export class GlatamCalendarModal extends LitElement {
223
89
  : this.dateString;
224
90
 
225
91
  return html`
226
- <div class=${classMap({ 'modal-overlay': true, 'open': this.open })}>
92
+ <dialog id="booking-dialog" @close=${this.handleDialogClose}>
227
93
  <div class="modal-content">
228
94
  <h3>${this.existingRule ? 'Gestionar Bloqueo' : 'Crear Bloqueo'}</h3>
229
95
 
@@ -291,7 +157,7 @@ export class GlatamCalendarModal extends LitElement {
291
157
  <button class="btn btn-save" @click=${this.handleSave}>Guardar</button>
292
158
  </div>
293
159
  </div>
294
- </div>
160
+ </dialog>
295
161
  `;
296
162
  }
297
163
  }
@@ -22,29 +22,18 @@ const DEFAULT_SLOTS: TimeSlot[] = [
22
22
  export class GlatamCalendar extends LitElement {
23
23
  static styles = [variablesStyles, calendarStyles];
24
24
 
25
- @property({ type: String }) role = 'provider';
26
- @property({ type: String, reflect: true }) size = 'medium';
27
- @property({ type: String }) view: 'month' | 'week' | 'day' = 'month';
28
- @property({ type: String }) locale = 'es';
29
- @property({ type: Number }) startOfWeekDay = 0;
30
- @property({ type: Array }) rules: BlockingRule[] = [];
31
- @property({ type: Array }) selectedDates: string[] = [];
32
- @property({ type: Object }) selectedRange: { dateString: string; start: string; end: string } | null = null;
25
+ @property({ type: String }) role = 'provider'; @property({ type: String, reflect: true }) size = 'medium';
26
+ @property({ type: String }) view: 'month' | 'week' | 'day' = 'month'; @property({ type: String }) locale = 'es';
27
+ @property({ type: Number }) startOfWeekDay = 0; @property({ type: Array }) rules: BlockingRule[] = [];
28
+ @property({ type: Array }) selectedDates: string[] = []; @property({ type: Object }) selectedRange: { dateString: string; start: string; end: string } | null = null;
33
29
  @property({ type: String }) hostTimezone = 'America/Bogota'; @property({ type: String }) activeTimezone = 'local';
34
- @property({ type: Array }) slots: TimeSlot[] = DEFAULT_SLOTS;
35
- @property({ type: String }) minDate = ''; @property({ type: String }) maxDate = '';
36
- @property({ type: Boolean }) showNeighboringMonth = true;
37
- @property({ attribute: false }) tileClassName: ((data: { date: Date; dateString: string }) => string) | null = null;
38
-
39
- @state() private activeDate = new Date();
40
- @state() private localRules: BlockingRule[] = [];
41
- @state() private darkMode = false;
42
- @state() private modalOpen = false;
43
- @state() private modalDateString = '';
44
- @state() private modalStartTime = '';
45
- @state() private modalEndTime = '';
46
- @state() private modalIsRange = false;
47
- @state() private modalExistingRule: BlockingRule | null = null;
30
+ @property({ type: Array }) slots: TimeSlot[] = DEFAULT_SLOTS; @property({ type: String }) minDate = ''; @property({ type: String }) maxDate = '';
31
+ @property({ type: Boolean }) showNeighboringMonth = true; @property({ attribute: false }) tileClassName: ((data: { date: Date; dateString: string }) => string) | null = null;
32
+
33
+ @state() private activeDate = new Date(); @state() private localRules: BlockingRule[] = [];
34
+ @state() private darkMode = false; @state() private modalOpen = false;
35
+ @state() private modalDateString = ''; @state() private modalStartTime = ''; @state() private modalEndTime = '';
36
+ @state() private modalIsRange = false; @state() private modalExistingRule: BlockingRule | null = null;
48
37
 
49
38
  firstUpdated() { this.darkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; this.localRules = [...this.rules]; }
50
39
  willUpdate(changedProps: Map<string, any>) { if (changedProps.has('rules') && this.rules.length > 0 && this.localRules.length === 0) this.localRules = [...this.rules]; }
@@ -77,6 +66,10 @@ export class GlatamCalendar extends LitElement {
77
66
 
78
67
  private handleRangeSelect(e: CustomEvent<{ dateString: string; start: string; end: string }>) {
79
68
  const { dateString, start, end } = e.detail, offset = this.activeTimezone === 'local' ? getTimezoneOffsetDiff(this.activeDate, this.hostTimezone, 'local') : 0;
69
+ const todayStr = new Date().toISOString().split('T')[0];
70
+ const limitMinDate = this.minDate || (this.role === 'buyer' ? todayStr : '');
71
+ if (this.role === 'buyer' && limitMinDate && dateString < limitMinDate) return;
72
+
80
73
  const displaySlots = this.getDisplaySlots(offset), startIdx = displaySlots.findIndex(s => s.start === start), endIdx = displaySlots.findIndex(s => s.end === end);
81
74
  const hostStart = this.slots[startIdx]?.start || start, hostEnd = this.slots[endIdx]?.end || end;
82
75
  if (this.role === 'buyer') {
@@ -87,8 +80,12 @@ export class GlatamCalendar extends LitElement {
87
80
  this.openModal(dateString, hostStart, hostEnd, true, null);
88
81
  }
89
82
 
90
- private handleSlotClick(e: CustomEvent<{ dateString: string; slot: TimeSlot }>) {
83
+ private handleSlotClick(e: CustomEvent<{ dateString: string; slot: TimeSlot & { isBlocked?: boolean } }>) {
91
84
  const { dateString, slot } = e.detail, date = new Date(dateString + 'T00:00:00'), offset = this.activeTimezone === 'local' ? getTimezoneOffsetDiff(this.activeDate, this.hostTimezone, 'local') : 0;
85
+ const todayStr = new Date().toISOString().split('T')[0];
86
+ const limitMinDate = this.minDate || (this.role === 'buyer' ? todayStr : '');
87
+ if (this.role === 'buyer' && (slot.isBlocked || (limitMinDate && dateString < limitMinDate))) return;
88
+
92
89
  const displaySlots = this.getDisplaySlots(offset), idx = displaySlots.findIndex(s => s.start === slot.start), hostSlot = this.slots[idx] || slot;
93
90
  if (this.role === 'buyer') {
94
91
  this.selectedRange = { dateString, start: slot.start, end: slot.end };
@@ -177,7 +174,7 @@ export class GlatamCalendar extends LitElement {
177
174
 
178
175
  <div class="calendar-body">
179
176
  ${this.view === 'month'
180
- ? html`<glatam-calendar-month-view .days=${generateMonthDays(y, m, this.localRules, this.slots, this.startOfWeekDay)} .locale=${this.locale} .startOfWeekDay=${this.startOfWeekDay} .role=${this.role} .size=${this.size} .minDate=${this.minDate} .maxDate=${this.maxDate} .showNeighboringMonth=${this.showNeighboringMonth} .tileClassName=${this.tileClassName} @day-select=${this.handleDaySelect}></glatam-calendar-month-view>`
177
+ ? html`<glatam-calendar-month-view .days=${generateMonthDays(y, m, this.localRules, this.slots, this.startOfWeekDay)} .locale=${this.locale} .startOfWeekDay=${this.startOfWeekDay} .role=${this.role} .size=${this.size} .minDate=${this.minDate || (this.role === 'buyer' ? new Date().toISOString().split('T')[0] : '')} .maxDate=${this.maxDate} .showNeighboringMonth=${this.showNeighboringMonth} .tileClassName=${this.tileClassName} @day-select=${this.handleDaySelect}></glatam-calendar-month-view>`
181
178
  : this.view === 'week'
182
179
  ? html`<glatam-calendar-week-view .days=${mapDays(generateWeekDays(this.activeDate, this.localRules, this.slots, this.startOfWeekDay))} .slots=${displaySlots} .locale=${this.locale} .selectedRange=${this.selectedRange} .role=${this.role} @range-select=${this.handleRangeSelect} @slot-click=${this.handleSlotClick}></glatam-calendar-week-view>`
183
180
  : html`<glatam-calendar-day-view .day=${mapDays(generateWeekDays(this.activeDate, this.localRules, this.slots, this.startOfWeekDay)).find(d => d.dateString === formatISODate(this.activeDate)) || null} .slots=${displaySlots} .locale=${this.locale} .selectedRange=${this.selectedRange} .role=${this.role} @range-select=${this.handleRangeSelect} @slot-click=${this.handleSlotClick}></glatam-calendar-day-view>`}
@@ -0,0 +1,158 @@
1
+ import { css } from 'lit';
2
+
3
+ export const modalStyles = css`
4
+ dialog {
5
+ background: transparent;
6
+ border: none;
7
+ padding: 0;
8
+ overflow: visible;
9
+ outline: none;
10
+ max-width: 100vw;
11
+ max-height: 100vh;
12
+ }
13
+ dialog::backdrop {
14
+ background: rgba(0, 0, 0, 0.35);
15
+ backdrop-filter: blur(10px);
16
+ -webkit-backdrop-filter: blur(10px);
17
+ }
18
+ .modal-content {
19
+ background: var(--glatam-bg);
20
+ color: var(--glatam-text);
21
+ border-radius: 24px;
22
+ padding: 28px;
23
+ width: 90vw;
24
+ max-width: 400px;
25
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.12);
26
+ border: 1px solid var(--glatam-border);
27
+ display: flex;
28
+ flex-direction: column;
29
+ gap: 18px;
30
+ box-sizing: border-box;
31
+ animation: zoomIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
32
+ }
33
+ @keyframes zoomIn {
34
+ from {
35
+ transform: scale(0.92) translateY(10px);
36
+ opacity: 0;
37
+ }
38
+ to {
39
+ transform: scale(1) translateY(0);
40
+ opacity: 1;
41
+ }
42
+ }
43
+ h3 { margin: 0; font-size: 1.3rem; font-weight: 700; letter-spacing: -0.02em; }
44
+ .form-group { display: flex; flex-direction: column; gap: 6px; }
45
+ label { font-size: 0.8rem; color: var(--glatam-text-secondary); font-weight: 600; }
46
+ input[type="text"] {
47
+ background: var(--glatam-surface);
48
+ border: 1px solid var(--glatam-border);
49
+ border-radius: 10px;
50
+ padding: 10px 14px;
51
+ color: var(--glatam-text);
52
+ font-family: inherit;
53
+ font-size: 0.9rem;
54
+ outline: none;
55
+ transition: border-color var(--glatam-transition-fast);
56
+ }
57
+ input[type="text"]:focus {
58
+ border-color: var(--glatam-primary);
59
+ }
60
+ .switch-row {
61
+ display: flex;
62
+ justify-content: space-between;
63
+ align-items: center;
64
+ padding: 4px 0;
65
+ }
66
+
67
+ /* Apple Switch Style */
68
+ .switch {
69
+ position: relative;
70
+ display: inline-block;
71
+ width: 46px;
72
+ height: 26px;
73
+ }
74
+ .switch input { opacity: 0; width: 0; height: 0; }
75
+ .slider {
76
+ position: absolute;
77
+ cursor: pointer;
78
+ top: 0; left: 0; right: 0; bottom: 0;
79
+ background-color: var(--glatam-border);
80
+ transition: .25s cubic-bezier(0.16, 1, 0.3, 1);
81
+ border-radius: 26px;
82
+ }
83
+ .slider:before {
84
+ position: absolute;
85
+ content: "";
86
+ height: 20px;
87
+ width: 20px;
88
+ left: 3px;
89
+ bottom: 3px;
90
+ background-color: white;
91
+ transition: .25s cubic-bezier(0.16, 1, 0.3, 1);
92
+ border-radius: 50%;
93
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
94
+ }
95
+ input:checked + .slider {
96
+ background-color: var(--glatam-primary);
97
+ }
98
+ input:checked + .slider:before {
99
+ transform: translateX(20px);
100
+ }
101
+
102
+ .days-grid {
103
+ display: grid;
104
+ grid-template-columns: repeat(7, 1fr);
105
+ gap: 6px;
106
+ margin-top: 4px;
107
+ }
108
+ .day-btn {
109
+ width: 36px;
110
+ height: 36px;
111
+ margin: 0 auto;
112
+ border-radius: 50%;
113
+ border: 1px solid var(--glatam-border);
114
+ background: var(--glatam-bg);
115
+ color: var(--glatam-text);
116
+ font-size: 0.8rem;
117
+ font-weight: 600;
118
+ cursor: pointer;
119
+ display: flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ transition: background-color var(--glatam-transition-fast), color var(--glatam-transition-fast), border-color var(--glatam-transition-fast);
123
+ }
124
+ .day-btn:hover {
125
+ background-color: var(--glatam-surface);
126
+ }
127
+ .day-btn.selected {
128
+ background: var(--glatam-primary);
129
+ color: var(--glatam-text-light);
130
+ border-color: var(--glatam-primary);
131
+ }
132
+ .btn-actions { display: flex; gap: 10px; margin-top: 10px; justify-content: flex-end; }
133
+ .btn {
134
+ padding: 10px 18px;
135
+ border-radius: 20px;
136
+ font-size: 0.875rem;
137
+ font-weight: 600;
138
+ cursor: pointer;
139
+ border: none;
140
+ transition: opacity var(--glatam-transition-fast), transform var(--glatam-transition-fast);
141
+ display: inline-flex;
142
+ align-items: center;
143
+ justify-content: center;
144
+ }
145
+ .btn:active { transform: scale(0.97); }
146
+ .btn-cancel { background: transparent; color: var(--glatam-text); border: 1px solid var(--glatam-border); }
147
+ .btn-cancel:hover { background-color: var(--glatam-surface); }
148
+ .btn-save { background: var(--glatam-primary); color: var(--glatam-text-light); }
149
+ .btn-save:hover { opacity: 0.95; }
150
+ .btn-danger {
151
+ background: rgba(255, 69, 58, 0.12);
152
+ color: #ff453a;
153
+ margin-right: auto;
154
+ }
155
+ .btn-danger:hover {
156
+ background: rgba(255, 69, 58, 0.18);
157
+ }
158
+ `;
@@ -62,7 +62,8 @@ export class GlatamCalendarMonthView extends LitElement {
62
62
  }
63
63
 
64
64
  private handleDayClick(day: CalendarDay) {
65
- if (this.role === 'buyer' && day.isBlocked) return;
65
+ const isDisabled = (this.minDate && day.dateString < this.minDate) || (this.maxDate && day.dateString > this.maxDate);
66
+ if (this.role === 'buyer' && (day.isBlocked || isDisabled)) return;
66
67
  this.dispatchEvent(new CustomEvent('day-select', {
67
68
  detail: { dateString: day.dateString, isBlocked: day.isBlocked },
68
69
  bubbles: true, composed: true
@@ -1,22 +0,0 @@
1
- import { LitElement } from 'lit';
2
- import { BlockingRule } from '@glatam/calendar-core';
3
-
4
- export declare class GlatamCalendarModal extends LitElement {
5
- static styles: import('lit').CSSResult;
6
- open: boolean;
7
- dateString: string;
8
- startTime: string;
9
- endTime: string;
10
- isRange: boolean;
11
- existingRule: BlockingRule | null;
12
- private description;
13
- private blockAllDay;
14
- private isRecurring;
15
- private selectedDays;
16
- willUpdate(changedProperties: Map<string, any>): void;
17
- private toggleDay;
18
- private handleSave;
19
- private handleDelete;
20
- render(): import('lit-html').TemplateResult<1>;
21
- }
22
- //# sourceMappingURL=calendar-modal.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"calendar-modal.d.ts","sourceRoot":"","sources":["../../src/components/calendar-modal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,qBACa,mBAAoB,SAAQ,UAAU;IACjD,MAAM,CAAC,MAAM,0BAqJX;IAE2B,IAAI,UAAS;IACd,UAAU,SAAM;IAChB,SAAS,SAAM;IACf,OAAO,SAAM;IACZ,OAAO,UAAS;IACjB,YAAY,EAAE,YAAY,GAAG,IAAI,CAAQ;IAE5D,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAgB;IAE7C,UAAU,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC;IAgB9C,OAAO,CAAC,SAAS;IASjB,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,YAAY;IAUpB,MAAM;CA8EP"}