본문으로 건너뛰기
Amineslab UI

Component

Calendar

단일 날짜와 기간을 선택하는 한국어 달력입니다.

Playground

옵션을 조절하며 화면과 사용 코드를 함께 확인하세요.

PC 1280px
Controls

하루·여러 날짜·기간 선택

컨테이너 너비에 맞게 날짜 셀 확장

동시에 표시할 개월 수

이전·다음 달 날짜 표시

매달 6주로 높이 고정

0: 일요일, 1: 월요일

선택한 날짜를 다시 눌러 해제하지 않음

Matcher 예시: 제한 없음·주말·전체 비활성

이동 가능한 첫 월 (YYYY-MM-DD)

이동 가능한 마지막 월. 시작보다 빠르면 범위를 적용하지 않음

Code
import { useState } from 'react'

import { Calendar } from '@amineslab/ui'

function parseCalendarDate(value: string): Date | undefined {
    if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
        return undefined;
    const [year = 0, month = 0, day = 0] = value.split('-').map(Number);
    const result = new Date(year, month - 1, day);
    return result.getFullYear() === year &&
        result.getMonth() === month - 1 &&
        result.getDate() === day
        ? result
        : undefined;
}

function calendarOptions(state: CalendarState) {
    const startMonth = parseCalendarDate(state.startMonth);
    const endMonth = parseCalendarDate(state.endMonth);
    const validRange = !startMonth || !endMonth || startMonth <= endMonth;
    return {
        fullWidth: state.fullWidth,
        numberOfMonths: state.numberOfMonths,
        showOutsideDays: state.showOutsideDays,
        fixedWeeks: state.fixedWeeks,
        weekStartsOn: Number(state.weekStartsOn) as 0 | 1,
        disabled: state.disabled === 'all'
            ? true
            : state.disabled === 'weekends'
                ? { dayOfWeek: [0, 6] }
                : undefined,
        startMonth: validRange ? startMonth : undefined,
        endMonth: validRange ? endMonth : undefined,
    };
}

const calendarInitial = {
    mode: 'single',
    fullWidth: false,
    numberOfMonths: 1,
    showOutsideDays: true,
    fixedWeeks: false,
    weekStartsOn: '0',
    required: false,
    disabled: 'none',
    startMonth: '',
    endMonth: '',
};

type CalendarState = typeof calendarInitial;

function CalendarExample(options: Partial<CalendarState>) {
    const state = { ...calendarInitial, ...options };
    const [day, setDay] = useState<Date>();
    const [days, setDays] = useState<Date[]>([]);
    const [range, setRange] = useState<{
        from: Date | undefined;
        to?: Date;
    }>();
    const common = calendarOptions(state);
    return (<div className="grid gap-3">
      {state.mode === 'multiple' ? (<Calendar {...common} mode="multiple" required={state.required} selected={days} onSelect={(value: Date[] | undefined) => setDays(value ?? [])}/>) : state.mode === 'range' ? (<Calendar {...common} mode="range" required={state.required} selected={range} onSelect={setRange}/>) : (<Calendar {...common} mode="single" required={state.required} selected={day} onSelect={setDay}/>)}
    </div>);
}

export default function ExamplePreview() {
  return (<CalendarExample key={"single"} {...({"mode":"single","fullWidth":false,"numberOfMonths":1,"showOutsideDays":true,"fixedWeeks":false,"weekStartsOn":"0","required":false,"disabled":"none","startMonth":"","endMonth":""} as const)}/>)
}

Props

@amineslab/ui에서 Calendar를 가져옵니다.

import { Calendar } from '@amineslab/ui'

Calendar

react-day-picker v9의 DayPicker props를 전달합니다. locale은 기본 한국어이며 덮어쓸 수 있습니다.

NameTypeDefaultDescription
mode'single' | 'multiple' | 'range'-선택 방식. selected와 onSelect의 타입도 mode에 따라 달라집니다.
selected / onSelectDayPickerProps-선택값과 변경 콜백입니다. 날짜는 로컬 Date 객체입니다.
requiredbooleanfalse선택한 날짜를 다시 클릭해 해제할 수 없게 합니다.
fixedWeeksbooleanfalse매달 6주를 표시해 달 이동 시 높이를 유지합니다.
weekStartsOn0 | 1 | 2 | 3 | 4 | 5 | 6-주 시작 요일입니다. Playground에서는 일요일(0)과 월요일(1)을 비교합니다.
fullWidthbooleanfalse컨테이너를 7열로 균등하게 나눕니다. 모바일·인라인에 사용합니다.
startMonth / endMonthDate-연·월 이동 범위입니다. 기본은 현재 연도 기준 과거 120년부터 미래 20년까지입니다.
disabledMatcher | Matcher[]-선택 불가 일자 조건입니다.
numberOfMonthsnumber1한 번에 표시할 개월 수입니다.
showOutsideDaysbooleantrue이전·다음 달의 날짜를 함께 표시합니다.
className / classNames / componentsDayPickerProps-달력 외곽·내부 슬롯·렌더 컴포넌트를 조정합니다.

Examples

자주 쓰는 조합을 살펴보고, 필요한 예시의 코드를 펼쳐 확인하세요.

여러 날짜 선택

multiple은 Date[]를 사용합니다. 날짜를 클릭해 추가하거나 해제하며, required는 마지막 선택의 해제를 막습니다. 선택 결과는 달력의 선택 표시로 확인합니다.

import { useState } from 'react'

import { Calendar } from '@amineslab/ui'

function parseCalendarDate(value: string): Date | undefined {
    if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
        return undefined;
    const [year = 0, month = 0, day = 0] = value.split('-').map(Number);
    const result = new Date(year, month - 1, day);
    return result.getFullYear() === year &&
        result.getMonth() === month - 1 &&
        result.getDate() === day
        ? result
        : undefined;
}

function calendarOptions(state: CalendarState) {
    const startMonth = parseCalendarDate(state.startMonth);
    const endMonth = parseCalendarDate(state.endMonth);
    const validRange = !startMonth || !endMonth || startMonth <= endMonth;
    return {
        fullWidth: state.fullWidth,
        numberOfMonths: state.numberOfMonths,
        showOutsideDays: state.showOutsideDays,
        fixedWeeks: state.fixedWeeks,
        weekStartsOn: Number(state.weekStartsOn) as 0 | 1,
        disabled: state.disabled === 'all'
            ? true
            : state.disabled === 'weekends'
                ? { dayOfWeek: [0, 6] }
                : undefined,
        startMonth: validRange ? startMonth : undefined,
        endMonth: validRange ? endMonth : undefined,
    };
}

const calendarInitial = {
    mode: 'single',
    fullWidth: false,
    numberOfMonths: 1,
    showOutsideDays: true,
    fixedWeeks: false,
    weekStartsOn: '0',
    required: false,
    disabled: 'none',
    startMonth: '',
    endMonth: '',
};

type CalendarState = typeof calendarInitial;

function CalendarExample(options: Partial<CalendarState>) {
    const state = { ...calendarInitial, ...options };
    const [day, setDay] = useState<Date>();
    const [days, setDays] = useState<Date[]>([]);
    const [range, setRange] = useState<{
        from: Date | undefined;
        to?: Date;
    }>();
    const common = calendarOptions(state);
    return (<div className="grid gap-3">
      {state.mode === 'multiple' ? (<Calendar {...common} mode="multiple" required={state.required} selected={days} onSelect={(value: Date[] | undefined) => setDays(value ?? [])}/>) : state.mode === 'range' ? (<Calendar {...common} mode="range" required={state.required} selected={range} onSelect={setRange}/>) : (<Calendar {...common} mode="single" required={state.required} selected={day} onSelect={setDay}/>)}
    </div>);
}

export default function ExamplePreview() {
  return (<CalendarExample mode="multiple"/>)
}

주 단위로 이어지는 기간 표시

기간 배경은 같은 주 안에서 이어지고, 시작·종료일은 원형으로 표시합니다.

import { useState } from 'react'

import { Calendar } from '@amineslab/ui'

function parseCalendarDate(value: string): Date | undefined {
    if (!/^\d{4}-\d{2}-\d{2}$/.test(value))
        return undefined;
    const [year = 0, month = 0, day = 0] = value.split('-').map(Number);
    const result = new Date(year, month - 1, day);
    return result.getFullYear() === year &&
        result.getMonth() === month - 1 &&
        result.getDate() === day
        ? result
        : undefined;
}

function calendarOptions(state: CalendarState) {
    const startMonth = parseCalendarDate(state.startMonth);
    const endMonth = parseCalendarDate(state.endMonth);
    const validRange = !startMonth || !endMonth || startMonth <= endMonth;
    return {
        fullWidth: state.fullWidth,
        numberOfMonths: state.numberOfMonths,
        showOutsideDays: state.showOutsideDays,
        fixedWeeks: state.fixedWeeks,
        weekStartsOn: Number(state.weekStartsOn) as 0 | 1,
        disabled: state.disabled === 'all'
            ? true
            : state.disabled === 'weekends'
                ? { dayOfWeek: [0, 6] }
                : undefined,
        startMonth: validRange ? startMonth : undefined,
        endMonth: validRange ? endMonth : undefined,
    };
}

const calendarInitial = {
    mode: 'single',
    fullWidth: false,
    numberOfMonths: 1,
    showOutsideDays: true,
    fixedWeeks: false,
    weekStartsOn: '0',
    required: false,
    disabled: 'none',
    startMonth: '',
    endMonth: '',
};

type CalendarState = typeof calendarInitial;

function CalendarExample(options: Partial<CalendarState>) {
    const state = { ...calendarInitial, ...options };
    const [day, setDay] = useState<Date>();
    const [days, setDays] = useState<Date[]>([]);
    const [range, setRange] = useState<{
        from: Date | undefined;
        to?: Date;
    }>();
    const common = calendarOptions(state);
    return (<div className="grid gap-3">
      {state.mode === 'multiple' ? (<Calendar {...common} mode="multiple" required={state.required} selected={days} onSelect={(value: Date[] | undefined) => setDays(value ?? [])}/>) : state.mode === 'range' ? (<Calendar {...common} mode="range" required={state.required} selected={range} onSelect={setRange}/>) : (<Calendar {...common} mode="single" required={state.required} selected={day} onSelect={setDay}/>)}
    </div>);
}

export default function ExamplePreview() {
  return (<CalendarExample mode="range" fullWidth/>)
}

사용 기준

권장하는 사용
  • 트리거와 반응형 표면까지 필요하면 DatePicker·DateRangePicker를 사용합니다.
  • Calendar는 인라인 달력과 사용자 정의 조합에 사용합니다.
피해야 할 사용
  • 겉모양만으로 사용을 결정하지 마세요.
  • 의미와 키보드 동작, 모바일에서의 흐름을 함께 확인하세요.

접근성

화면에 맞는 이름을 제공하고, 키보드만으로 작업을 마칠 수 있는지 확인하세요.

  • 방향키: 날짜 사이를 이동합니다.
  • Enter / Space: 포커스한 날짜를 선택합니다.