@uniai-fe/uds-primitives 0.6.4 → 0.6.5
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/dist/styles.css +179 -4
- package/package.json +1 -1
- package/src/components/calendar/index.tsx +1 -0
- package/src/components/calendar/markup/Core.tsx +13 -2
- package/src/components/calendar/markup/index.tsx +5 -0
- package/src/components/calendar/markup/range/Core.tsx +92 -0
- package/src/components/calendar/markup/range/Root.tsx +133 -0
- package/src/components/calendar/markup/range/index.tsx +15 -0
- package/src/components/calendar/styles/mantine-calendar.scss +162 -4
- package/src/components/calendar/types/calendar.ts +150 -0
- package/src/components/calendar/utils/value-mapper.ts +37 -4
- package/src/components/input/markup/date/Template.tsx +1 -4
- package/src/components/input/markup/date/index.tsx +11 -0
- package/src/components/input/markup/date/range/Template.tsx +261 -0
- package/src/components/input/styles/date.scss +44 -0
- package/src/components/input/types/date.ts +114 -3
- package/src/components/input/utils/date.ts +41 -1
package/dist/styles.css
CHANGED
|
@@ -1636,6 +1636,17 @@
|
|
|
1636
1636
|
z-index: 30;
|
|
1637
1637
|
}
|
|
1638
1638
|
|
|
1639
|
+
.calendar-range-root,
|
|
1640
|
+
.calendar-range-grid {
|
|
1641
|
+
--calendar-range-column-width: 322px;
|
|
1642
|
+
--calendar-range-column-gap: var(--spacing-gap-6);
|
|
1643
|
+
--calendar-width: calc(
|
|
1644
|
+
(var(--calendar-range-column-width) * 2) +
|
|
1645
|
+
var(--calendar-range-column-gap) + (var(--calendar-inline-padding) * 2)
|
|
1646
|
+
);
|
|
1647
|
+
max-width: min(100vw - var(--spacing-padding-5) * 2, 720px);
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1639
1650
|
.calendar-header {
|
|
1640
1651
|
margin-bottom: var(--spacing-gap-2);
|
|
1641
1652
|
}
|
|
@@ -1649,6 +1660,10 @@
|
|
|
1649
1660
|
width: var(--calendar-body-width);
|
|
1650
1661
|
}
|
|
1651
1662
|
|
|
1663
|
+
.calendar-range-grid {
|
|
1664
|
+
width: var(--calendar-body-width);
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1652
1667
|
.calendar-grid table {
|
|
1653
1668
|
width: auto;
|
|
1654
1669
|
}
|
|
@@ -1663,6 +1678,17 @@
|
|
|
1663
1678
|
width: 100%;
|
|
1664
1679
|
}
|
|
1665
1680
|
|
|
1681
|
+
.calendar-range-grid .calendar-month-level {
|
|
1682
|
+
display: grid;
|
|
1683
|
+
grid-template-columns: repeat(2, var(--calendar-range-column-width));
|
|
1684
|
+
column-gap: var(--calendar-range-column-gap);
|
|
1685
|
+
align-items: start;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
.calendar-range-grid .calendar-month-level > [data-month-level=true] {
|
|
1689
|
+
width: var(--calendar-range-column-width);
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1666
1692
|
.calendar-header-row {
|
|
1667
1693
|
display: grid;
|
|
1668
1694
|
grid-template-columns: 44px 1fr 44px;
|
|
@@ -1670,7 +1696,7 @@
|
|
|
1670
1696
|
width: 100%;
|
|
1671
1697
|
max-width: none;
|
|
1672
1698
|
column-gap: var(--spacing-gap-5);
|
|
1673
|
-
padding: 0
|
|
1699
|
+
padding: 0;
|
|
1674
1700
|
margin-bottom: var(--spacing-gap-5);
|
|
1675
1701
|
--dch-fz: var(--font-heading-small-size) !important;
|
|
1676
1702
|
}
|
|
@@ -1679,7 +1705,7 @@
|
|
|
1679
1705
|
width: 44px;
|
|
1680
1706
|
height: 44px;
|
|
1681
1707
|
border-radius: 999px;
|
|
1682
|
-
display:
|
|
1708
|
+
display: flex;
|
|
1683
1709
|
align-items: center;
|
|
1684
1710
|
justify-content: center;
|
|
1685
1711
|
color: var(--color-label-alternative);
|
|
@@ -1687,15 +1713,25 @@
|
|
|
1687
1713
|
}
|
|
1688
1714
|
|
|
1689
1715
|
.calendar-header-control[data-direction=previous] {
|
|
1716
|
+
grid-column: 1;
|
|
1717
|
+
grid-row: 1;
|
|
1690
1718
|
justify-self: start;
|
|
1691
1719
|
}
|
|
1692
1720
|
|
|
1693
1721
|
.calendar-header-control[data-direction=next] {
|
|
1722
|
+
grid-column: 3;
|
|
1723
|
+
grid-row: 1;
|
|
1694
1724
|
justify-self: end;
|
|
1695
1725
|
}
|
|
1696
1726
|
|
|
1697
1727
|
.calendar-header-level {
|
|
1728
|
+
grid-column: 2;
|
|
1729
|
+
grid-row: 1;
|
|
1698
1730
|
justify-self: center;
|
|
1731
|
+
display: flex;
|
|
1732
|
+
align-items: center;
|
|
1733
|
+
justify-content: center;
|
|
1734
|
+
gap: var(--spacing-gap-4);
|
|
1699
1735
|
font-size: var(--font-heading-small-size);
|
|
1700
1736
|
font-weight: var(--font-heading-small-weight);
|
|
1701
1737
|
text-align: center;
|
|
@@ -1706,6 +1742,17 @@
|
|
|
1706
1742
|
color: var(--color-label-strong);
|
|
1707
1743
|
}
|
|
1708
1744
|
|
|
1745
|
+
.calendar-header-level::after {
|
|
1746
|
+
content: "";
|
|
1747
|
+
flex: 0 0 auto;
|
|
1748
|
+
width: 20px;
|
|
1749
|
+
height: 20px;
|
|
1750
|
+
background-image: url("data:image/svg+xml,%3Csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.66675 8.33333L10.0001 5L13.3334 8.33333' stroke='%2394989E' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M6.66675 11.6667L10.0001 15L13.3334 11.6667' stroke='%2394989E' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
|
1751
|
+
background-repeat: no-repeat;
|
|
1752
|
+
background-position: center;
|
|
1753
|
+
background-size: 20px 20px;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1709
1756
|
.calendar-header-control:where(:not([data-disabled=true])):hover {
|
|
1710
1757
|
background-color: var(--color-tertiary-default);
|
|
1711
1758
|
color: var(--color-label-standard);
|
|
@@ -1824,28 +1871,117 @@
|
|
|
1824
1871
|
padding: 0;
|
|
1825
1872
|
border: none;
|
|
1826
1873
|
border-radius: var(--theme-radius-large-1);
|
|
1874
|
+
box-sizing: border-box;
|
|
1875
|
+
display: flex;
|
|
1876
|
+
align-items: center;
|
|
1877
|
+
justify-content: center;
|
|
1827
1878
|
font-size: var(--font-body-medium-size);
|
|
1828
1879
|
color: var(--color-label-standard);
|
|
1829
1880
|
}
|
|
1830
1881
|
|
|
1882
|
+
.calendar-day-label {
|
|
1883
|
+
width: 44px;
|
|
1884
|
+
height: 44px;
|
|
1885
|
+
box-sizing: border-box;
|
|
1886
|
+
display: flex;
|
|
1887
|
+
align-items: center;
|
|
1888
|
+
justify-content: center;
|
|
1889
|
+
position: relative;
|
|
1890
|
+
color: inherit;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1831
1893
|
.calendar-day[data-outside=true] {
|
|
1832
1894
|
color: var(--color-label-alternative);
|
|
1833
1895
|
}
|
|
1834
1896
|
|
|
1835
1897
|
.calendar-day[data-selected=true],
|
|
1836
1898
|
.calendar-day[data-focused=true] {
|
|
1899
|
+
color: var(--color-common-100);
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
.calendar-day:where([data-in-range]) {
|
|
1903
|
+
background-color: var(--color-surface-static-blue);
|
|
1904
|
+
color: var(--color-label-standard);
|
|
1905
|
+
border-radius: 0;
|
|
1906
|
+
box-shadow: -1px 0 0 var(--color-surface-static-blue), 1px 0 0 var(--color-surface-static-blue);
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
.calendar-day:where([data-first-in-range]) {
|
|
1910
|
+
border-start-start-radius: var(--theme-radius-medium-3);
|
|
1911
|
+
border-end-start-radius: var(--theme-radius-medium-3);
|
|
1912
|
+
box-shadow: 1px 0 0 var(--color-surface-static-blue);
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
.calendar-day:where([data-last-in-range]) {
|
|
1916
|
+
border-start-end-radius: var(--theme-radius-medium-3);
|
|
1917
|
+
border-end-end-radius: var(--theme-radius-medium-3);
|
|
1918
|
+
box-shadow: -1px 0 0 var(--color-surface-static-blue);
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
.calendar-day:where([data-first-in-range][data-last-in-range]) {
|
|
1922
|
+
box-shadow: none;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
.calendar-day:where([data-selected=true], [data-focused=true]) {
|
|
1926
|
+
background-color: transparent;
|
|
1927
|
+
color: var(--color-common-100);
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
.calendar-day:where([data-in-range][data-selected=true]) {
|
|
1931
|
+
background-color: var(--color-surface-static-blue);
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
.calendar-day:where([data-selected=true], [data-focused=true]) .calendar-day-label {
|
|
1837
1935
|
background-color: var(--color-primary-default);
|
|
1838
1936
|
color: var(--color-common-100);
|
|
1937
|
+
border-radius: var(--theme-radius-medium-3);
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
.calendar-day:where([data-today][data-highlight-today]:not([data-selected=true],
|
|
1941
|
+
[data-in-range],
|
|
1942
|
+
[data-disabled=true])) {
|
|
1943
|
+
border: 1px solid var(--color-primary-default);
|
|
1944
|
+
color: var(--color-label-standard);
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
.calendar-day:where([data-today][data-highlight-today]:not([data-selected=true],
|
|
1948
|
+
[data-in-range],
|
|
1949
|
+
[data-disabled=true])) .calendar-day-label::after {
|
|
1950
|
+
content: "";
|
|
1951
|
+
width: 4px;
|
|
1952
|
+
height: 4px;
|
|
1953
|
+
border-radius: 999px;
|
|
1954
|
+
background-color: var(--color-primary-default);
|
|
1955
|
+
position: absolute;
|
|
1956
|
+
bottom: 7px;
|
|
1957
|
+
left: 50%;
|
|
1958
|
+
transform: translateX(-50%);
|
|
1839
1959
|
}
|
|
1840
1960
|
|
|
1841
1961
|
.calendar-day:where(:disabled, [data-disabled=true]) {
|
|
1842
1962
|
color: var(--color-label-disabled);
|
|
1843
1963
|
}
|
|
1844
1964
|
|
|
1845
|
-
.calendar-day:where(:not([data-disabled=true]
|
|
1965
|
+
.calendar-day:where(:not([data-disabled=true],
|
|
1966
|
+
[data-selected=true],
|
|
1967
|
+
[data-focused=true],
|
|
1968
|
+
[data-in-range])):hover {
|
|
1846
1969
|
background-color: var(--color-secondary-default);
|
|
1847
1970
|
color: var(--color-label-standard);
|
|
1848
|
-
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
.calendar-day:where([data-selected=true]):hover {
|
|
1974
|
+
background-color: transparent;
|
|
1975
|
+
color: var(--color-common-100);
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
.calendar-day:where([data-in-range][data-selected=true]):hover {
|
|
1979
|
+
background-color: var(--color-surface-static-blue);
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
.calendar-day:where([data-selected=true]):hover .calendar-day-label {
|
|
1983
|
+
background-color: var(--color-primary-default);
|
|
1984
|
+
color: var(--color-common-100);
|
|
1849
1985
|
}
|
|
1850
1986
|
|
|
1851
1987
|
.calendar-footer {
|
|
@@ -3441,6 +3577,45 @@ figure.chip {
|
|
|
3441
3577
|
width: 100%;
|
|
3442
3578
|
}
|
|
3443
3579
|
|
|
3580
|
+
.input-date-range-footer-template {
|
|
3581
|
+
gap: var(--spacing-gap-6);
|
|
3582
|
+
}
|
|
3583
|
+
|
|
3584
|
+
.input-date-range-today-row {
|
|
3585
|
+
justify-content: flex-end;
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
.input-date-range-footer-row {
|
|
3589
|
+
justify-content: space-between;
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
.input-date-range-clear-button {
|
|
3593
|
+
min-height: 20px;
|
|
3594
|
+
padding-inline: var(--spacing-padding-5);
|
|
3595
|
+
border-color: transparent;
|
|
3596
|
+
background-color: transparent;
|
|
3597
|
+
color: var(--color-feedback-error);
|
|
3598
|
+
}
|
|
3599
|
+
.input-date-range-clear-button:hover:not(:disabled):not([aria-disabled=true]), .input-date-range-clear-button:active:not(:disabled):not([aria-disabled=true]) {
|
|
3600
|
+
border-color: transparent;
|
|
3601
|
+
background-color: transparent;
|
|
3602
|
+
color: var(--color-feedback-error);
|
|
3603
|
+
box-shadow: none;
|
|
3604
|
+
}
|
|
3605
|
+
.input-date-range-clear-button .button-label {
|
|
3606
|
+
font-size: var(--font-body-xxsmall-size);
|
|
3607
|
+
line-height: 1.4;
|
|
3608
|
+
font-weight: var(--font-body-medium-weight);
|
|
3609
|
+
letter-spacing: 0;
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
.input-date-range-apply-button {
|
|
3613
|
+
width: fit-content;
|
|
3614
|
+
flex: 0 0 auto;
|
|
3615
|
+
--button-width: fit-content;
|
|
3616
|
+
--button-flex: 0 0 auto;
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3444
3619
|
.input-address-container {
|
|
3445
3620
|
width: 100%;
|
|
3446
3621
|
}
|
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* - `Calendar.Root`: trigger + popover + core 조합 템플릿이다.
|
|
5
5
|
* - `Calendar.Container`: Header/Body/Footer 레이아웃 컨테이너다.
|
|
6
6
|
* - `Calendar.Core`: Mantine DatePicker SOT 렌더러다.
|
|
7
|
+
* - `Calendar.Range`: Mantine DatePicker range SOT namespace다.
|
|
7
8
|
* - `CalendarHooks`, `CalendarUtils`: 관련 hook/util namespace다.
|
|
8
9
|
*/
|
|
9
10
|
import "./index.scss";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { DatePicker } from "@mantine/dates";
|
|
3
|
+
import { DatePicker, type DateStringValue } from "@mantine/dates";
|
|
4
|
+
import { dayjs } from "../../../init/dayjs";
|
|
4
5
|
import { CalendarIcon } from "./Icon";
|
|
5
6
|
import type { CalendarDatePickerProps, CalendarGridProps } from "../types";
|
|
6
7
|
import { mapValueToPicker, parseValueFromPicker } from "../utils";
|
|
@@ -20,7 +21,7 @@ export default function CalendarCore({
|
|
|
20
21
|
onChange,
|
|
21
22
|
datePickerProps,
|
|
22
23
|
}: CalendarGridProps) {
|
|
23
|
-
const { valueFormat, ...safeDatePickerProps } = (datePickerProps ??
|
|
24
|
+
const { valueFormat, renderDay, ...safeDatePickerProps } = (datePickerProps ??
|
|
24
25
|
{}) as CalendarDatePickerProps & {
|
|
25
26
|
/**
|
|
26
27
|
* deprecated DatePicker 표시 포맷 옵션
|
|
@@ -37,8 +38,18 @@ export default function CalendarCore({
|
|
|
37
38
|
weekendDays: [] as Array<0 | 1 | 2 | 3 | 4 | 5 | 6>,
|
|
38
39
|
nextIcon: <CalendarIcon.Chevron.right />,
|
|
39
40
|
previousIcon: <CalendarIcon.Chevron.left />,
|
|
41
|
+
// 변경: Figma today node 기준의 border/dot 상태를 기본 활성화한다.
|
|
42
|
+
highlightToday: true,
|
|
43
|
+
// 변경: Calendar header는 Figma 기준의 연도-월 순서 표기를 기본값으로 둔다.
|
|
44
|
+
monthLabelFormat: "YYYY년 M월",
|
|
40
45
|
// 변경: 지원하지 않는 valueFormat은 제거한 뒤 DatePicker 옵션을 병합한다.
|
|
41
46
|
...safeDatePickerProps,
|
|
47
|
+
// 변경: selected/range/today 시각 레이어를 분리하기 위해 날짜 값을 내부 span에 고정한다.
|
|
48
|
+
renderDay: (date: DateStringValue) => (
|
|
49
|
+
<span className="calendar-day-label">
|
|
50
|
+
{renderDay ? renderDay(date) : dayjs(date).date()}
|
|
51
|
+
</span>
|
|
52
|
+
),
|
|
42
53
|
classNames: {
|
|
43
54
|
levelsGroup: "calendar-month-level",
|
|
44
55
|
month: "calendar-month-table",
|
|
@@ -5,6 +5,7 @@ import CalendarFooter from "./layout/Footer";
|
|
|
5
5
|
import CalendarCore from "./Core";
|
|
6
6
|
import CalendarRoot from "./Root";
|
|
7
7
|
import { CalendarIcon } from "./Icon";
|
|
8
|
+
import { CalendarRange } from "./range";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Calendar; date picker namespace
|
|
@@ -13,6 +14,7 @@ import { CalendarIcon } from "./Icon";
|
|
|
13
14
|
* - `Calendar.Container`: Header/Body/Footer 레이아웃 컨테이너다.
|
|
14
15
|
* - `Calendar.Header`, `Calendar.Body`, `Calendar.Footer`: depth 고정 레이아웃 섹션이다.
|
|
15
16
|
* - `Calendar.Core`: Mantine DatePicker SOT 렌더러다.
|
|
17
|
+
* - `Calendar.Range`: Mantine DatePicker range SOT namespace다.
|
|
16
18
|
* - `Calendar.Icon`: 입력/네비게이션 아이콘 namespace다.
|
|
17
19
|
*/
|
|
18
20
|
export const Calendar = {
|
|
@@ -22,5 +24,8 @@ export const Calendar = {
|
|
|
22
24
|
Body: CalendarBody,
|
|
23
25
|
Footer: CalendarFooter,
|
|
24
26
|
Core: CalendarCore,
|
|
27
|
+
Range: CalendarRange,
|
|
25
28
|
Icon: CalendarIcon,
|
|
26
29
|
};
|
|
30
|
+
|
|
31
|
+
export * from "./range";
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { DatePicker, type DateStringValue } from "@mantine/dates";
|
|
4
|
+
import { dayjs } from "../../../../init/dayjs";
|
|
5
|
+
import { CalendarIcon } from "../Icon";
|
|
6
|
+
import type {
|
|
7
|
+
CalendarRangeDatePickerProps,
|
|
8
|
+
CalendarRangeGridProps,
|
|
9
|
+
CalendarRangePickerValue,
|
|
10
|
+
} from "../../types";
|
|
11
|
+
import { mapRangeValueToPicker, parseRangeValueFromPicker } from "../../utils";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Calendar Range Grid; Mantine DatePicker range 래퍼.
|
|
15
|
+
* @component
|
|
16
|
+
* @param {CalendarRangeGridProps} props range grid props
|
|
17
|
+
* @param {CalendarRangeValue} props.value 현재 range 선택 값
|
|
18
|
+
* @param {CalendarRangeOnChange} props.onChange range 값 변경 핸들러
|
|
19
|
+
* @param {CalendarRangeDatePickerProps} [props.datePickerProps] Mantine range DatePicker 옵션
|
|
20
|
+
* @example
|
|
21
|
+
* <Calendar.Range.Core value={value} onChange={setValue} />
|
|
22
|
+
*/
|
|
23
|
+
export default function CalendarRangeCore({
|
|
24
|
+
value,
|
|
25
|
+
onChange,
|
|
26
|
+
datePickerProps,
|
|
27
|
+
}: CalendarRangeGridProps) {
|
|
28
|
+
const { valueFormat, renderDay, ...safeDatePickerProps } = (datePickerProps ??
|
|
29
|
+
{}) as CalendarRangeDatePickerProps & {
|
|
30
|
+
/**
|
|
31
|
+
* deprecated DatePicker 표시 포맷 옵션
|
|
32
|
+
*/
|
|
33
|
+
valueFormat?: unknown;
|
|
34
|
+
};
|
|
35
|
+
// 변경: range에서도 valueFormat은 지원하지 않는 옵션이므로 Calendar 단에서 제거한다.
|
|
36
|
+
void valueFormat;
|
|
37
|
+
|
|
38
|
+
// Range Core는 Figma의 2 calendar 구조에 맞춰 두 달을 한 패널에서 렌더링한다.
|
|
39
|
+
const resolvedDatePickerProps = {
|
|
40
|
+
size: "sm" as const,
|
|
41
|
+
firstDayOfWeek: 0 as const,
|
|
42
|
+
weekendDays: [] as Array<0 | 1 | 2 | 3 | 4 | 5 | 6>,
|
|
43
|
+
nextIcon: <CalendarIcon.Chevron.right />,
|
|
44
|
+
previousIcon: <CalendarIcon.Chevron.left />,
|
|
45
|
+
// 변경: range는 2개월을 보여주되 page navigation은 1개월씩 이동한다.
|
|
46
|
+
columnsToScroll: 1,
|
|
47
|
+
// 변경: Figma today node 기준의 border/dot 상태를 range에서도 기본 활성화한다.
|
|
48
|
+
highlightToday: true,
|
|
49
|
+
// 변경: Range header도 단일 Core와 같은 연도-월 순서 표기를 기본값으로 둔다.
|
|
50
|
+
monthLabelFormat: "YYYY년 M월",
|
|
51
|
+
...safeDatePickerProps,
|
|
52
|
+
// 변경: button은 period surface, span은 selected/today 날짜 레이어로 분리한다.
|
|
53
|
+
renderDay: (date: DateStringValue) => (
|
|
54
|
+
<span className="calendar-day-label">
|
|
55
|
+
{renderDay ? renderDay(date) : dayjs(date).date()}
|
|
56
|
+
</span>
|
|
57
|
+
),
|
|
58
|
+
classNames: {
|
|
59
|
+
levelsGroup: "calendar-month-level",
|
|
60
|
+
month: "calendar-month-table",
|
|
61
|
+
monthCell: "calendar-month-cell",
|
|
62
|
+
calendarHeader: "calendar-header-row",
|
|
63
|
+
calendarHeaderControl: "calendar-header-control",
|
|
64
|
+
calendarHeaderLevel: "calendar-header-level",
|
|
65
|
+
day: "calendar-day",
|
|
66
|
+
weekday: "calendar-weekday",
|
|
67
|
+
weekdaysRow: "calendar-weekdays",
|
|
68
|
+
monthsList: "calendar-months-list",
|
|
69
|
+
monthsListCell: "calendar-months-list-cell",
|
|
70
|
+
monthsListControl: "calendar-months-list-control",
|
|
71
|
+
yearsList: "calendar-years-list",
|
|
72
|
+
yearsListCell: "calendar-years-list-cell",
|
|
73
|
+
yearsListControl: "calendar-years-list-control",
|
|
74
|
+
...(safeDatePickerProps.classNames ?? {}),
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const handleChange = (nextValue: CalendarRangePickerValue) => {
|
|
79
|
+
onChange(parseRangeValueFromPicker(nextValue));
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<DatePicker
|
|
84
|
+
className="calendar-grid calendar-range-grid"
|
|
85
|
+
numberOfColumns={2}
|
|
86
|
+
type="range"
|
|
87
|
+
value={mapRangeValueToPicker(value)}
|
|
88
|
+
onChange={handleChange}
|
|
89
|
+
{...resolvedDatePickerProps}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
import { PopOver } from "../../../pop-over";
|
|
6
|
+
import type { CalendarRangeRootProps } from "../../types";
|
|
7
|
+
import CalendarContainer from "../layout/Container";
|
|
8
|
+
import CalendarRangeCore from "./Core";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Calendar Range Root; Trigger/Popover + Range Core 조합 템플릿.
|
|
12
|
+
* @component
|
|
13
|
+
* @param {CalendarRangeRootProps} props range root props
|
|
14
|
+
* @param {string} [props.className] Trigger element className override
|
|
15
|
+
* @param {CalendarRangeValue} props.value 현재 range 선택 값
|
|
16
|
+
* @param {CalendarRangeOnChange} props.onChange range 값 변경 핸들러
|
|
17
|
+
* @param {CalendarRangeDatePickerProps} [props.datePickerProps] Mantine range DatePicker 옵션
|
|
18
|
+
* @param {React.ReactNode} [props.header] Calendar Header 슬롯
|
|
19
|
+
* @param {React.ReactNode} [props.footer] Calendar Footer 슬롯
|
|
20
|
+
* @param {CalendarMode} [props.mode="date"] calendar 모드
|
|
21
|
+
* @param {boolean} [props.disabled] 비활성화 상태
|
|
22
|
+
* @param {boolean} [props.readOnly] 읽기 전용 상태
|
|
23
|
+
* @param {React.ReactNode} props.children Trigger 슬롯(children)
|
|
24
|
+
* @param {boolean} [props.open] 제어형 open 상태
|
|
25
|
+
* @param {boolean} [props.defaultOpen] 비제어 초기 open 상태
|
|
26
|
+
* @param {(open: boolean) => void} [props.onOpenChange] open 상태 변경 핸들러
|
|
27
|
+
* @param {"top" | "right" | "bottom" | "left"} [props.side="bottom"] Content 배치 방향
|
|
28
|
+
* @param {"start" | "center" | "end"} [props.align="start"] Content 정렬 기준
|
|
29
|
+
* @param {number} [props.sideOffset=4] Trigger와 Content 간격
|
|
30
|
+
* @param {number} [props.alignOffset] Content 정렬 보정값
|
|
31
|
+
* @param {boolean} [props.withPortal=true] Portal 사용 여부
|
|
32
|
+
* @param {HTMLElement | null} [props.portalContainer] Portal 컨테이너
|
|
33
|
+
* @example
|
|
34
|
+
* <Calendar.Range.Root value={value} onChange={setValue} />
|
|
35
|
+
*/
|
|
36
|
+
const CalendarRangeRoot = forwardRef<HTMLElement, CalendarRangeRootProps>(
|
|
37
|
+
(
|
|
38
|
+
{
|
|
39
|
+
className,
|
|
40
|
+
value,
|
|
41
|
+
onChange,
|
|
42
|
+
datePickerProps,
|
|
43
|
+
header,
|
|
44
|
+
footer,
|
|
45
|
+
mode = "date",
|
|
46
|
+
disabled,
|
|
47
|
+
readOnly,
|
|
48
|
+
children,
|
|
49
|
+
open,
|
|
50
|
+
defaultOpen,
|
|
51
|
+
onOpenChange,
|
|
52
|
+
side = "bottom",
|
|
53
|
+
align = "start",
|
|
54
|
+
sideOffset = 4,
|
|
55
|
+
alignOffset,
|
|
56
|
+
withPortal = true,
|
|
57
|
+
portalContainer,
|
|
58
|
+
},
|
|
59
|
+
ref,
|
|
60
|
+
) => {
|
|
61
|
+
// disabled/readOnly 상태에서는 range PopOver 토글을 막는다.
|
|
62
|
+
const isInteractive = !disabled && !readOnly;
|
|
63
|
+
|
|
64
|
+
const calendarNode = (
|
|
65
|
+
<CalendarContainer
|
|
66
|
+
ref={ref}
|
|
67
|
+
header={header}
|
|
68
|
+
footer={footer}
|
|
69
|
+
mode={mode}
|
|
70
|
+
columns={2}
|
|
71
|
+
disabled={disabled}
|
|
72
|
+
readOnly={readOnly}
|
|
73
|
+
body={
|
|
74
|
+
<CalendarRangeCore
|
|
75
|
+
value={value}
|
|
76
|
+
onChange={onChange}
|
|
77
|
+
datePickerProps={datePickerProps}
|
|
78
|
+
/>
|
|
79
|
+
}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<PopOver.Root
|
|
85
|
+
open={isInteractive ? open : false}
|
|
86
|
+
defaultOpen={isInteractive ? defaultOpen : false}
|
|
87
|
+
onOpenChange={nextOpen => {
|
|
88
|
+
if (!isInteractive) {
|
|
89
|
+
onOpenChange?.(false);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
onOpenChange?.(nextOpen);
|
|
93
|
+
}}
|
|
94
|
+
modal={false}
|
|
95
|
+
>
|
|
96
|
+
{/* Trigger 래퍼 div 없이 children 노드에 PopOver trigger props를 직접 주입한다. */}
|
|
97
|
+
<PopOver.Trigger
|
|
98
|
+
asChild
|
|
99
|
+
disabled={!isInteractive}
|
|
100
|
+
className={clsx("calendar-trigger", className)}
|
|
101
|
+
>
|
|
102
|
+
{/* children은 asChild 계약상 단일 element여야 하며, 해당 요소가 props를 DOM으로 전달해야 한다. */}
|
|
103
|
+
{children}
|
|
104
|
+
</PopOver.Trigger>
|
|
105
|
+
<PopOver.Content
|
|
106
|
+
className="calendar-pop-over-content"
|
|
107
|
+
side={side}
|
|
108
|
+
align={align}
|
|
109
|
+
sideOffset={sideOffset}
|
|
110
|
+
alignOffset={alignOffset}
|
|
111
|
+
withPortal={withPortal}
|
|
112
|
+
portalContainer={portalContainer}
|
|
113
|
+
onInteractOutside={event => {
|
|
114
|
+
// 변경 설명: trigger 클릭은 outside dismiss에서 제외해 close 후 즉시 reopen 경합을 방지한다.
|
|
115
|
+
const nextTarget = event.target as HTMLElement | null;
|
|
116
|
+
if (nextTarget?.closest(".calendar-trigger")) {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
}
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
{/* range calendar 스킨 class는 content 내부에 고정한다. */}
|
|
122
|
+
<div className="calendar-root calendar-range-root">
|
|
123
|
+
{calendarNode}
|
|
124
|
+
</div>
|
|
125
|
+
</PopOver.Content>
|
|
126
|
+
</PopOver.Root>
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
CalendarRangeRoot.displayName = "CalendarRangeRoot";
|
|
132
|
+
|
|
133
|
+
export default CalendarRangeRoot;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import CalendarRangeCore from "./Core";
|
|
2
|
+
import CalendarRangeRoot from "./Root";
|
|
3
|
+
|
|
4
|
+
export { CalendarRangeCore, CalendarRangeRoot };
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Calendar Range; date range picker namespace
|
|
8
|
+
* @desc
|
|
9
|
+
* - `Calendar.Range.Root`: trigger + popover + range core 조합 템플릿이다.
|
|
10
|
+
* - `Calendar.Range.Core`: Mantine DatePicker range SOT 렌더러다.
|
|
11
|
+
*/
|
|
12
|
+
export const CalendarRange = {
|
|
13
|
+
Root: CalendarRangeRoot,
|
|
14
|
+
Core: CalendarRangeCore,
|
|
15
|
+
};
|