SecretoCho
menu

component 27 / 28WORKING

SC Calendar Widget

type
Code Component
platform
Framer
license
SecretoCho Design
needs JavaScript

demo · runs with JavaScript

fig 00 · live demo, ported from SC_Calendar.tsx

Form-ready calendar picker: month/year dropdowns, Sunday or Monday start, RTL, disable past dates, themed grid, hidden ISO-date input.

SC Calendar Widget is a self-contained date picker for Framer forms: a header with the selected date, month and year dropdowns, a tinted weekday row and a seven-column grid that fills the leading and trailing days from the neighbouring months. Week start, text direction, year range, past-date locking and every colour and size are controls, and a hidden input carries the ISO date to the form.

Built for the old site’s booking flow together with SC Time Slot Selector.

  • Booking
  • UI
  • Data

Main

Overview

Built With
React (hooks), inline SVG icons, CSS grid, Framer Code Component API
Layout Support
Fills the frame (width / height any)
Orientation
Vertical: header, month | year row, weekday row, day grid; LTR or RTL
Ideal Use Case
Booking and contact forms, date fields inside Framer forms

Tags

  • calendar
  • date picker
  • form
  • booking
  • rtl
  • week start
  • hidden input

Code comment

SC_Calendar.tsx opens with a signed note to curious developers (lines 4–9), left out of this draft until the owner confirms it.

Dependencies

Technologies
None (native Date; no date library)
Notes
Month names and weekday labels are English strings in the file.

Complexity

Logic Level
Medium (grid construction, two dropdowns with click-outside, past-date rules, form value)
Runtime Cost
Very Low (static DOM; one document mousedown listener)
Component Size
~24 KB TSX (23,925 bytes, 671 lines)
Notes
Prints a styled console message on load (lines 664–671).

Guide

Instructions

  1. Drop SC Calendar Widget inside a Framer form.
  2. Set Data Type (date | text), Data Name (the form field name) and Required.
  3. Choose Week Start (Sunday | Monday), Text Direction (LTR | RTL), Min / Max Year and Disable Past Dates.
  4. Set the font and the three sizes (head, body, days), then the colours: fill, text, active text, accent, hover, icon, weekend, border.
  5. Adjust Padding, Gap Between Days (the weekday row’s column gap only; the day grid’s gap is fixed at 8px 0) and Radius.

Examples / Presets

  • Monday start

    weekStart
    monday
  • Disable past dates

    disablePast
    true (past days greyed and unclickable; past months and years hidden)
  • Right to left

    rtl
    rtl

The demo also carries an action “As in the booking flow” built from the owner’s instance on the old site’s earlier home page.

Mechanics

How it Works

  • Grid: getCalendarGrid builds the month by hand — leading days from the previous month for the week-start offset, the month’s days, then trailing days to complete the last week; neighbouring-month days render transparent and unclickable.
  • Header: shows MM/DD/YYYY of the selection with a calendar icon; the month and year cells open dropdown lists (max-height 200, scrollable) and a document mousedown listener closes them on an outside click.
  • Disable past: days before today are greyed (#ccc) with a not-allowed cursor; past months of the current year and past years are hidden from the dropdowns.
  • Styling: the weekday row sits on the accent at 10 % opacity; the selected day fills with the accent; weekends use weekendColor; fonts come from the Font control plus three sizes.
  • Form: a hidden read-only <input type=date|text name=dataName required> holds selectedDate.toISOString().slice(0, 10) and updates on every change.

Performance & Tuning

  • Static DOM of 28–42 day cells (the grid completes only the last week); nothing animates except CSS hover transitions.
  • The year dropdown lists maxYear − minYear + 1 rows; keep the range tight.

Compatibility & Limits

  • Evergreen browsers; native Date only, in the visitor’s local time.
  • English month and weekday labels; the header date is always MM/DD/YYYY.
  • The hidden value is a UTC date (toISOString): east of UTC, local midnight converts to the previous day, so the submitted date can be one day early.
  • Required has no effect: a read-only input is exempt from form validation, and its value is never empty.
  • Disable Past Dates can be bypassed: pick a later year, pick January, then switch back to this year; nothing rechecks the month.
  • A shadows value is read but has no control; canSelectDate is unused.
  • Changing the month does not clamp the selected day: a 31 in a 30-day month rolls the form value into the next month while no cell shows as selected.

Quality

Accessibility

  • Days, month and year cells are clickable <div>s without button roles or keyboard handling; the hidden input is the only form control.
  • Disabled days are shown by colour and cursor only.
  • RTL flips the layout direction; the labels stay English.

Security & Privacy

  • No network calls, no storage, no permissions, no third-party libraries.
  • One document mousedown listener while mounted (removed on unmount).
  • Logs a styled console message once when the module loads in a browser.

Reference

Props

nametypedefaultdescription
dataTypeenumdateData Type. date | text for the hidden input.
dataNamestringDateData Name. The form field name.
requiredbooleanfalseRequired (no effect: the input is read-only and never empty).
weekStartenumsundayWeek Start. Sunday | Monday.
rtlenumltrText Direction. LTR | RTL.
minYearnumberthe current yearMin Year 2000–2100.
maxYearnumberthe current year + 10Max Year 2000–2100.
disablePastbooleanfalseDisable Past Dates.
fontFontStyleFramer font controlFont. Family, weight, letter spacing and line height.
fontSizeHeadnumber15Font Size Head 8–36 px (header date, month, year).
fontSizeBodynumber13Font Size Body 8–36 px (dropdowns, weekday row).
fontSizeDaysnumber13Font Size Days 8–36 px.
fillColorcolor#ffffffFill Color (background).
ActiveTextColorcolor#ffffffActive Text Color (on the accent).
textColorcolor#191919Text Color.
accentColorcolor#3B82F6Accent Color. Weekname background row use %10 of this color.
hoverColorcolor#f0f0f0Hover Color.
iconColorcolor#000Icon Color.
weekendColorcolor#999999Weekend Color.
paddingstring10px 8pxPadding (CSS string) for header, cells and grid.
dayGapnumber4Gap Between Days 2–12 px: sets only the weekday row’s column gap; the day grid’s gap is fixed at 8px 0.
borderobjectshow=true, width=1, color=#d2d2d2Border. Show, Width 0–4, Color.
radiusnumber8Radius 0–32 px (frame and day cells).

ActiveTextColor is the only prop with a capital initial; keep the exact name when setting it from code.

Notes & Tips

  • Pair it with SC Time Slot Selector in the same form: the two hidden inputs submit together.
  • Set Min Year to the current year and Disable Past Dates on for booking forms.
  • Keep Radius low when Border is on: day cells share the frame’s radius.

Troubleshooting / FAQ

The submitted date is one day early

The hidden value is toISOString() of local midnight, a UTC date; in zones ahead of UTC it is the previous day. Fix by formatting the local year, month and day instead.

No day looks selected after changing month

The selected day number does not exist in that month (for example 31); pick a day again.

A dropdown stays open

The outside-click listener is on document mousedown; a layer that stops propagation keeps it open.

Source

Header comment

SC_Calendar.tsx opens with a signed note to curious developers (lines 4–9), left out of this draft until the owner confirms it.

props [REF]

Key excerpt

The grid is built by hand: leading days from the previous month for the week-start offset, the month itself, then trailing days to complete the last week — no date library.

SC_Calendar.tsx · lines 55–91 · tsx
const getCalendarGrid = (year, month, weekStartsSunday) => {    const days = []    const first = new Date(year, month, 1)    const last = new Date(year, month + 1, 0)    const firstIndex = first.getDay()    const totalDays = last.getDate()    const startOffset = weekStartsSunday        ? firstIndex        : firstIndex === 0          ? 6          : firstIndex - 1    for (let i = startOffset - 1; i >= 0; i--) {        const date = new Date(            year,            month - 1,            new Date(year, month, 0).getDate() - i        )        days.push({ day: date.getDate(), current: false, date })    }    for (let i = 1; i <= totalDays; i++) {        const date = new Date(year, month, i)        days.push({ day: i, current: true, date })    }    const totalCells = days.length    const remainder = totalCells % 7    if (remainder > 0) {        for (let i = 1; i <= 7 - remainder; i++) {            const date = new Date(year, month + 1, i)            days.push({ day: i, current: false, date })        }    }    return days}

Credits

Credits

© SecretoCho Design 2025 — stated in the file (line 96); the header note (lines 4–9) is signed “– SecretoCho Design”.

license
SecretoCho Design

SecretoCho Design 2025

Disclaimer

Proves

Hiring a design engineer?

I’m open to senior design engineer roles: remote from Belgrade, or relocation with visa sponsorship. Write to me, or read the CV first. Client work stays selective, one project at a time.

sc@secretocho.design

read the CV · LinkedIn