sellmate-design-system-react 9.0.0-beta.63 → 9.0.0-beta.64
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/AGENTS.md +3 -2
- package/dist/components/SCalendarBoard/CalendarDayCard.d.ts +11 -3
- package/dist/components/SCalendarBoard/CalendarEventButton.d.ts +7 -2
- package/dist/components/SCalendarBoard/CalendarMoreButton.d.ts +2 -1
- package/dist/components/SCalendarBoard/README.md +11 -2
- package/dist/components/SCalendarBoard/SCalendarBoard.d.ts +4 -1
- package/dist/components/SCalendarBoard/calendar-board-layout.d.ts +49 -0
- package/dist/components/SCalendarBoard/types.d.ts +10 -1
- package/dist/index.cjs +159 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +159 -55
- package/dist/index.js.map +1 -1
- package/dist/llms-full.txt +14 -4
- package/dist/llms.txt +3 -2
- package/dist/styles.css +11 -3
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -1701,17 +1701,18 @@ useEffect(() => {
|
|
|
1701
1701
|
| 연도+월 선택 리스트만 필요하다 (트리거·팝오버는 직접 조합) | `SDatePickerMonthListbox` |
|
|
1702
1702
|
| **시작~종료** 를 값으로 받는다 | `SDateRangePicker` |
|
|
1703
1703
|
| 달력 격자 **자체가 화면 콘텐츠** 다 (날짜를 고르게 한다) | `SCalendar` |
|
|
1704
|
-
| 한 달치 **일정을 읽게** 펼친다 (칸마다 여러
|
|
1704
|
+
| 한 달치 **일정을 읽게** 펼친다 (칸마다 여러 건, 여러 날에 걸친 일정 포함) | `SCalendarBoard` |
|
|
1705
1705
|
|
|
1706
1706
|
- **기간을 `SDatePicker` 두 개로 만들지 않는다.** 시작이 종료보다 뒤인 입력을 막는 검증과 한쪽만 고른 중간 상태 처리가 `SDateRangePicker` 안에 이미 있다. 두 개로 쪼개면 그게 전부 앱 몫이 된다.
|
|
1707
1707
|
- `SDatePicker`·`SDateRangePicker` 는 내부적으로 `SCalendar` 를 팝오버로 띄운다. 값을 받는 자리에 `SCalendar` 를 직접 쓰지 않는다.
|
|
1708
1708
|
- **`SCalendar` 와 `SCalendarBoard` 는 방향이 반대다.** `SCalendar` 는 날짜를 **고르는** 달력(칸에는 점만 찍힌다)이고, `SCalendarBoard` 는 일정을 **읽는** 달력이다 — 칸마다 일정이 줄로 쌓이고, 칸 높이에 담기는 줄 수를 넘으면 "N개 더보기" 로 접히고, **"더보기" 또는 일정이 있는 칸 아무 데나** 누르면 **그 칸을 덮으며 뜨는 판**에 그 날짜의 일정이 전부 펼쳐진다. 폭도 다르다: `SCalendar` 는 제 폭을 갖는 요소, `SCalendarBoard` 는 페이지 폭을 채우는 블록이다.
|
|
1709
1709
|
- **`SCalendarBoard` 는 기본이 읽기 전용 판이다.** 날짜 칸을 눌러 **고르게** 하려면 `selectable` 을 켠다 — 켜야 `onValueChange(date, events)` 로 고른 날짜와 **그 날짜의 일정**이 함께 온다. 다만 **일정이 있는 칸은 `selectable` 과 무관하게 눌린다** — 그때는 목록 판이 열린다. 둘 다 켜져 있으면 고르기와 열기가 함께 일어난다. 일정 버튼과 "N개 더보기" 도 항상 눌린다.
|
|
1710
|
+
- **여러 날에 걸친 일정은 `events[].endDate` 로 준다.** 그날까지(그날 포함) **칸 경계를 넘어 하나의 막대로** 이어져 그려진다. 같은 일정을 날짜 수만큼 쪼개 넣지 않는다 — 쪼개면 칸마다 막대가 끊겨 보이고, 목록 판과 `onValueChange` 에도 건수가 부풀려 나간다. 줄은 **주 단위**로 정해져서, 막대가 지나가는 칸은 그 줄을 비워 두므로 다른 일정과 겹치지 않는다. 주 경계에서는 줄이 바뀌므로 조각이 나뉘고 **잘린 쪽 모서리가 평평해진다** — 앞/뒤로 이어진다는 표시다. **막대가 걸친 날은 모두 그 일정을 가진 날이다** — 어느 칸을 눌러도 목록 판에 함께 나오고 `onValueChange` 에도 실려 온다. `endDate` 가 `date` 보다 앞서면 하루짜리로 본다.
|
|
1710
1711
|
- **칸 배경과 일정 색은 다른 층이다.** 그날의 성격(마감·완료·휴무 등)은 `dayColors` 로 **칸 전체**에 칠하고(`default` `danger` `accent` `complete` `dim`), 일정 하나의 색은 `events[].color` 로 준다. 둘을 섞어 쓰면 서로를 가리지 않고 함께 읽힌다.
|
|
1711
1712
|
- 일정 왼쪽의 색 점은 `SBadge` 다 — `events[].color` 프리셋이 점 색까지 정하고, 따로 주려면 `dotColor` 에 `SBadge` 색(`SBadgeColor`) 중 하나를 넣는다 (§3-1).
|
|
1712
1713
|
- **칸 본문을 통째로 바꾸려면 `dayContent` 다.** 일정으로 표현되지 않는 그날치 요약(재고·매출 같은)을 칸에 얹을 때 쓴다. **슬롯을 준 날짜는 그 날짜의 `events` 를 그리지 않고 목록 판도 열지 않는다** — 한 칸에 둘을 겹쳐 담지 않는다. 일정과 요약을 함께 보여야 하면 요약을 일정 한 건(`events[]`)으로 넣는다.
|
|
1713
1714
|
- **`SCalendarBoard` 는 높이를 부모에게서 받는다.** 행이 그 높이를 균등하게 나눠 갖고, 칸 높이가 다시 "한 칸에 몇 줄이 보일지" 를 정한다. 그래서 **놓는 자리에 높이를 준다** — 페이지 본문의 남은 높이를 채우려면 `SPage` 안에서 스택을 `flex-1 min-h-0` 으로 두고 판에 `h-full` 을 준다. 높이를 주지 않으면 칸이 최소 높이로 주저앉아 한두 줄만 보인다.
|
|
1714
|
-
- **한 칸에 몇 줄을 펼칠지는 컴포넌트가 잰다.** 조절하는 prop 이 없다 — 칸 높이에서 나오는 값이라 따로 정할 것이 아니다. 줄이 넘치면 마지막 한 줄을 "N개 더보기" 에 내주므로 **접히든 아니든 칸 높이가 같다.** **소비 앱이 `events` 를 미리 잘라 넘기지 않는다.** 잘라 넘기면 "N개 더보기" 의 수가 실제 건수와 어긋나고, 눌러도 나머지가 나오지 않는다.
|
|
1715
|
+
- **한 칸에 몇 줄을 펼칠지는 컴포넌트가 잰다.** 조절하는 prop 이 없다 — 칸 높이에서 나오는 값이라 따로 정할 것이 아니다. 줄이 넘치면 마지막 한 줄을 "N개 더보기" 에 내주므로 **접히든 아니든 칸 높이가 같다.** 기간 막대가 접히면 **걸친 날마다 한 건으로** 세므로 날짜별 "N개" 가 서로 다를 수 있다. **소비 앱이 `events` 를 미리 잘라 넘기지 않는다.** 잘라 넘기면 "N개 더보기" 의 수가 실제 건수와 어긋나고, 눌러도 나머지가 나오지 않는다.
|
|
1715
1716
|
- **`events[].label` 은 필수다** — 일정이 무엇인지 알리는 부분이라 비워 두지 않는다. 인라인 HTML 을 받으므로 부분 강조가 필요할 때만 태그를 쓰고, 나머지는 `description`·`value` 로 나눠 담는다. 칸이 좁아지면 `label`+`description` 이 한 덩어리로 말줄임되고 `value` 만 끝에 온전히 남는다 — **금액·수량은 반드시 `value` 에 담는다.** `description` 에 넣으면 잘린다.
|
|
1716
1717
|
- `SDatePickerYearListbox`·`SDatePickerMonthListbox` 는 `SDatePicker` 의 mode listbox 조각만 떼어낸 컴포넌트다. 일반 폼 입력에는 `SDatePicker mode="year" | "month"` 를 우선 쓰고, 다른 트리거·팝오버 안에 리스트만 끼워 넣을 때만 직접 쓴다.
|
|
1717
1718
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type HTMLAttributes, type MouseEvent, type ReactNode } from 'react';
|
|
2
|
+
import type { CalendarBoardSegment } from './calendar-board-layout';
|
|
2
3
|
import { type SCalendarBoardDayColor } from './calendarBoard.config';
|
|
3
4
|
import type { SCalendarBoardEvent } from './types';
|
|
4
5
|
/**
|
|
5
6
|
* 달력의 날짜 한 칸 (SCalendarBoard 내부 조립 요소).
|
|
6
|
-
* 위에 날짜
|
|
7
|
+
* 위에 날짜 숫자가 서고, 그 아래로 일정 막대가 줄 단위로 얹힌다.
|
|
7
8
|
* `body` 를 받은 칸은 아래를 통째로 그것에 내준다 — 일정은 그리지 않는다.
|
|
8
9
|
*/
|
|
9
10
|
export declare const CalendarDayCard: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & {
|
|
@@ -19,8 +20,13 @@ export declare const CalendarDayCard: import("react").ForwardRefExoticComponent<
|
|
|
19
20
|
selectable: boolean;
|
|
20
21
|
/** 칸 배경 */
|
|
21
22
|
color: SCalendarBoardDayColor;
|
|
22
|
-
/**
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* **이 칸에서 시작하는** 일정 막대. 여러 날에 걸친 일정은 시작 칸이 그리고 오른쪽 칸 위로 뻗는다 —
|
|
25
|
+
* 지나가는 칸은 자기 줄을 비워 두므로(배치 계산이 이미 잡아 둔다) 겹치지 않는다.
|
|
26
|
+
*/
|
|
27
|
+
segments: CalendarBoardSegment[];
|
|
28
|
+
/** 이 날짜에 걸친 일정 수 — 하나라도 있으면 칸을 눌러 목록 판을 열 수 있다 */
|
|
29
|
+
dayEventCount: number;
|
|
24
30
|
/**
|
|
25
31
|
* 일정 자리를 대신 차지할 칸 본문 (SCalendarBoard 의 `dayContent`).
|
|
26
32
|
* 주면 일정·"더보기" 를 그리지 않는다. `content` 는 HTMLAttributes 가 이미 쓰는 이름이라 피했다.
|
|
@@ -28,6 +34,8 @@ export declare const CalendarDayCard: import("react").ForwardRefExoticComponent<
|
|
|
28
34
|
body?: ReactNode;
|
|
29
35
|
/** 접혀 있는 일정 수. 0 이면 "더보기" 를 그리지 않는다 */
|
|
30
36
|
hiddenCount: number;
|
|
37
|
+
/** "더보기" 가 앉는 줄. 접힌 것이 없는 주는 -1 */
|
|
38
|
+
moreLane: number;
|
|
31
39
|
/** 이 칸의 일정 목록 판이 열려 있는지 */
|
|
32
40
|
isOpen: boolean;
|
|
33
41
|
moreLabel?: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type MouseEvent } from 'react';
|
|
1
|
+
import { type CSSProperties, type MouseEvent } from 'react';
|
|
2
2
|
import type { SCalendarBoardEvent } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* 달력 칸/패널 안의 일정 한 줄 (SCalendarBoard 내부 조립 요소).
|
|
@@ -6,8 +6,13 @@ import type { SCalendarBoardEvent } from './types';
|
|
|
6
6
|
* 점 → 레이블(강조) → 설명 → `/` → 값 순서로 한 줄에 눕는다.
|
|
7
7
|
* 가운데 레이블 묶음만 줄어들고 값은 끝에 고정되므로, 좁은 칸에서도 금액이 잘리지 않는다.
|
|
8
8
|
*/
|
|
9
|
-
export declare function CalendarEventButton({ event, onSelect, className, }: {
|
|
9
|
+
export declare function CalendarEventButton({ event, onSelect, flatStart, flatEnd, className, style, }: {
|
|
10
10
|
event: SCalendarBoardEvent;
|
|
11
11
|
onSelect?: (event: SCalendarBoardEvent, e: MouseEvent<HTMLButtonElement>) => void;
|
|
12
|
+
/** 왼쪽 끝이 잘린 조각 — 앞 주에서 이어지므로 모서리를 평평하게 한다 */
|
|
13
|
+
flatStart?: boolean;
|
|
14
|
+
/** 오른쪽 끝이 잘린 조각 — 다음 주로 이어진다 */
|
|
15
|
+
flatEnd?: boolean;
|
|
12
16
|
className?: string;
|
|
17
|
+
style?: CSSProperties;
|
|
13
18
|
}): import("react").JSX.Element;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type MouseEvent } from 'react';
|
|
1
|
+
import { type CSSProperties, type MouseEvent } from 'react';
|
|
2
2
|
/**
|
|
3
3
|
* 칸에 다 담기지 않은 일정 수를 알리는 "N개 더보기" 줄 (SCalendarBoard 내부 조립 요소).
|
|
4
4
|
* 개수는 강조색, 뒷말은 본문색으로 갈라 개수가 먼저 읽히게 한다.
|
|
@@ -9,4 +9,5 @@ export declare const CalendarMoreButton: import("react").ForwardRefExoticCompone
|
|
|
9
9
|
label?: string;
|
|
10
10
|
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
|
11
11
|
className?: string;
|
|
12
|
+
style?: CSSProperties;
|
|
12
13
|
} & import("react").RefAttributes<HTMLButtonElement>>;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
| `defaultMonth?` | `number` | — | 처음 보여줄 월 1-12 (비제어). 없으면 오늘 |
|
|
15
15
|
| `selectable?` | `boolean` | `false` | 날짜 칸을 눌러 고를 수 있게 한다. 기본은 읽기 전용 판이다 — 켜야 칸에 커서·포커스·키보드(Enter·Space)가 붙고 `onValueChange` 가 온다. 일정 버튼과 "N개 더보기" 는 이 값과 무관하게 항상 눌린다. |
|
|
16
16
|
| `value?` | `string \| null` | — | 선택 날짜 (YYYY-MM-DD). `selectable` 과 별개로 강조만 주고 싶을 때도 쓴다 |
|
|
17
|
-
| `events?` | `SCalendarBoardEvent[]` | — |
|
|
17
|
+
| `events?` | `SCalendarBoardEvent[]` | — | 일정 목록. `date`(시작)로 칸에 배분되고, `endDate` 를 주면 그날까지 **하나의 막대로 이어져** 칸 경계를 넘어 그려진다. |
|
|
18
18
|
| `dayColors?` | `Record<string, SCalendarBoardDayColor>` | — | 날짜별 칸 배경. 키는 `YYYY-MM-DD`, 없는 날짜는 `'default'` 다. 그날의 성격(마감·완료·휴무 등)을 칸 전체로 알릴 때 쓴다 — 일정 하나의 색은 `events[].color` 다. |
|
|
19
19
|
| `dayContent?` | `Record<string, ReactNode>` | — | 날짜별 칸 본문. 키는 `YYYY-MM-DD`, 준 날짜는 **일정 자리를 통째로 이 내용이 차지한다** — 그날의 `events` 는 그리지 않고 목록 판도 열리지 않는다. 일정으로 표현되지 않는 것(재고 현황· 매출 요약 같은 그날치 요약)을 칸에 그대로 얹을 때 쓴다. |
|
|
20
20
|
| `todayLabel?` | `string` | `'오늘'` | 헤더 오른쪽 "오늘" 버튼 레이블. 빈 문자열이면 버튼을 그리지 않는다 |
|
|
@@ -52,8 +52,17 @@ export interface SCalendarBoardView {
|
|
|
52
52
|
export interface SCalendarBoardEvent {
|
|
53
53
|
/** 고유 식별자. 없으면 목록 순서로 key 를 만든다 */
|
|
54
54
|
id?: string;
|
|
55
|
-
/** 날짜 (YYYY-MM-DD) */
|
|
55
|
+
/** 시작 날짜 (YYYY-MM-DD). 하루짜리 일정은 이것만 준다 */
|
|
56
56
|
date: string;
|
|
57
|
+
/**
|
|
58
|
+
* 마지막 날 (YYYY-MM-DD, **그날 포함**). 주지 않거나 `date` 보다 앞서면 하루짜리로 본다.
|
|
59
|
+
*
|
|
60
|
+
* 여러 날에 걸친 일정은 칸 경계를 넘어 **하나의 막대로 이어져** 그려진다. 주 경계에서는 줄이
|
|
61
|
+
* 바뀌므로 조각이 나뉘고, 그렇게 잘린 쪽 모서리는 평평해져 "앞/뒤로 이어진다" 가 읽힌다.
|
|
62
|
+
*
|
|
63
|
+
* 막대가 걸친 날은 **모두** 그 일정을 가진 날로 친다 — 목록 판과 `onValueChange` 에 함께 온다.
|
|
64
|
+
*/
|
|
65
|
+
endDate?: string;
|
|
57
66
|
/**
|
|
58
67
|
* 강조 레이블 (필수) — 예: `"[입고 예정]"`.
|
|
59
68
|
* 일정이 무엇인지 한눈에 알리는 부분이라 비워 두지 않는다.
|
|
@@ -22,7 +22,10 @@ export interface SCalendarBoardProps {
|
|
|
22
22
|
value?: string | null;
|
|
23
23
|
/** 날짜 칸 선택 — 고른 날짜와 그 날짜의 일정이 함께 온다 (`selectable` 일 때만) */
|
|
24
24
|
onValueChange?: (date: string, events: SCalendarBoardEvent[]) => void;
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* 일정 목록. `date`(시작)로 칸에 배분되고, `endDate` 를 주면 그날까지 **하나의 막대로 이어져**
|
|
27
|
+
* 칸 경계를 넘어 그려진다.
|
|
28
|
+
*/
|
|
26
29
|
events?: SCalendarBoardEvent[];
|
|
27
30
|
/**
|
|
28
31
|
* 날짜별 칸 배경. 키는 `YYYY-MM-DD`, 없는 날짜는 `'default'` 다.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 달력 판의 **일정 배치**를 계산한다 (순수 함수 — DOM 을 보지 않는다).
|
|
3
|
+
*
|
|
4
|
+
* 왜 필요한가: 일정이 하루짜리뿐이면 칸마다 자기 목록을 세로로 쌓으면 그만이다. 그런데 여러 날에
|
|
5
|
+
* 걸친 일정은 **칸 경계를 넘어 하나의 막대로 이어져야** 하므로, 칸 혼자서는 자기 줄을 정할 수 없다 —
|
|
6
|
+
* 같은 주의 다른 칸에 걸친 막대가 이미 그 줄을 쓰고 있을 수 있기 때문이다. 그래서 줄(lane) 배정은
|
|
7
|
+
* **주 단위**로 한 번에 푼다.
|
|
8
|
+
*
|
|
9
|
+
* 하루짜리 일정만 있는 판은 이 계산을 거쳐도 예전과 같은 화면이 된다 — 서로 다른 날의 일정은
|
|
10
|
+
* 겹치지 않아 전부 0번 줄부터 차곡차곡 앉기 때문이다.
|
|
11
|
+
*/
|
|
12
|
+
import type { DayCell } from '../../lib/date';
|
|
13
|
+
import type { SCalendarBoardEvent } from './types';
|
|
14
|
+
/** 한 주 안에서 이어지는 막대 하나 — 일정 하나가 주 경계에서 잘리면 조각이 여럿 된다 */
|
|
15
|
+
export interface CalendarBoardSegment {
|
|
16
|
+
event: SCalendarBoardEvent;
|
|
17
|
+
/** React key */
|
|
18
|
+
key: string;
|
|
19
|
+
/** 격자 칸 배열에서의 시작 인덱스 */
|
|
20
|
+
startIndex: number;
|
|
21
|
+
/** 이어지는 칸 수 (1 이상) */
|
|
22
|
+
span: number;
|
|
23
|
+
/** 그 주 안에서 몇 번째 줄인지 (0부터) */
|
|
24
|
+
lane: number;
|
|
25
|
+
/** 왼쪽 끝이 일정의 **실제 시작**인지. 아니면 앞에서 이어진 것이라 그 모서리를 평평하게 한다 */
|
|
26
|
+
startsHere: boolean;
|
|
27
|
+
/** 오른쪽 끝이 일정의 **실제 끝**인지 */
|
|
28
|
+
endsHere: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface CalendarBoardLayout {
|
|
31
|
+
/** 칸별로, **그 칸에서 시작하는** 막대. 막대는 시작 칸이 그린다 */
|
|
32
|
+
segmentsByCell: CalendarBoardSegment[][];
|
|
33
|
+
/** 칸별로, 그 날짜에 걸친 일정 전부 — 목록 판과 콜백에 넘기는 값 */
|
|
34
|
+
eventsByCell: SCalendarBoardEvent[][];
|
|
35
|
+
/** 칸별로 줄 수를 넘겨 접힌 일정 수 */
|
|
36
|
+
hiddenCountByCell: number[];
|
|
37
|
+
/** 주별로 "N개 더보기" 가 앉는 줄. 접힌 것이 없는 주는 -1 */
|
|
38
|
+
moreLaneByWeek: number[];
|
|
39
|
+
}
|
|
40
|
+
/** 일정의 마지막 날. `endDate` 가 없거나 시작보다 앞서면 하루짜리로 본다 */
|
|
41
|
+
export declare function eventEndDate(event: SCalendarBoardEvent): string;
|
|
42
|
+
export declare function layoutCalendarBoard({ cells, events, rowCapacity, blockedDates, }: {
|
|
43
|
+
cells: readonly DayCell[];
|
|
44
|
+
events: readonly SCalendarBoardEvent[];
|
|
45
|
+
/** 칸 하나에 담기는 줄 수 */
|
|
46
|
+
rowCapacity: number;
|
|
47
|
+
/** 일정을 그리지 않는 날짜 (`dayContent` 를 받은 칸) */
|
|
48
|
+
blockedDates: ReadonlySet<string>;
|
|
49
|
+
}): CalendarBoardLayout;
|
|
@@ -4,8 +4,17 @@ import type { SCalendarBoardEventColor } from './calendarBoard.config';
|
|
|
4
4
|
export interface SCalendarBoardEvent {
|
|
5
5
|
/** 고유 식별자. 없으면 목록 순서로 key 를 만든다 */
|
|
6
6
|
id?: string;
|
|
7
|
-
/** 날짜 (YYYY-MM-DD) */
|
|
7
|
+
/** 시작 날짜 (YYYY-MM-DD). 하루짜리 일정은 이것만 준다 */
|
|
8
8
|
date: string;
|
|
9
|
+
/**
|
|
10
|
+
* 마지막 날 (YYYY-MM-DD, **그날 포함**). 주지 않거나 `date` 보다 앞서면 하루짜리로 본다.
|
|
11
|
+
*
|
|
12
|
+
* 여러 날에 걸친 일정은 칸 경계를 넘어 **하나의 막대로 이어져** 그려진다. 주 경계에서는 줄이
|
|
13
|
+
* 바뀌므로 조각이 나뉘고, 그렇게 잘린 쪽 모서리는 평평해져 "앞/뒤로 이어진다" 가 읽힌다.
|
|
14
|
+
*
|
|
15
|
+
* 막대가 걸친 날은 **모두** 그 일정을 가진 날로 친다 — 목록 판과 `onValueChange` 에 함께 온다.
|
|
16
|
+
*/
|
|
17
|
+
endDate?: string;
|
|
9
18
|
/**
|
|
10
19
|
* 강조 레이블 (필수) — 예: `"[입고 예정]"`.
|
|
11
20
|
* 일정이 무엇인지 한눈에 알리는 부분이라 비워 두지 않는다.
|
package/dist/index.cjs
CHANGED
|
@@ -13717,6 +13717,98 @@ function monthCells(year, month) {
|
|
|
13717
13717
|
}
|
|
13718
13718
|
var WEEK_LABELS = ["\uC77C", "\uC6D4", "\uD654", "\uC218", "\uBAA9", "\uAE08", "\uD1A0"];
|
|
13719
13719
|
|
|
13720
|
+
// src/components/SCalendarBoard/calendar-board-layout.ts
|
|
13721
|
+
function eventEndDate(event) {
|
|
13722
|
+
const end = event.endDate;
|
|
13723
|
+
return end != null && end > event.date ? end : event.date;
|
|
13724
|
+
}
|
|
13725
|
+
function layoutCalendarBoard({
|
|
13726
|
+
cells,
|
|
13727
|
+
events,
|
|
13728
|
+
rowCapacity,
|
|
13729
|
+
blockedDates
|
|
13730
|
+
}) {
|
|
13731
|
+
const count = cells.length;
|
|
13732
|
+
const weekCount = Math.ceil(count / 7);
|
|
13733
|
+
const segmentsByCell = Array.from({ length: count }, () => []);
|
|
13734
|
+
const eventsByCell = Array.from({ length: count }, () => []);
|
|
13735
|
+
const hiddenCountByCell = new Array(count).fill(0);
|
|
13736
|
+
const moreLaneByWeek = new Array(weekCount).fill(-1);
|
|
13737
|
+
const runs = [];
|
|
13738
|
+
events.forEach((event, order) => {
|
|
13739
|
+
const start = event.date;
|
|
13740
|
+
const end = eventEndDate(event);
|
|
13741
|
+
let from = -1;
|
|
13742
|
+
let to = -1;
|
|
13743
|
+
for (let i = 0; i < count; i++) {
|
|
13744
|
+
const { date } = cells[i];
|
|
13745
|
+
if (date < start || date > end) continue;
|
|
13746
|
+
if (from < 0) from = i;
|
|
13747
|
+
to = i;
|
|
13748
|
+
if (!blockedDates.has(date)) eventsByCell[i].push(event);
|
|
13749
|
+
}
|
|
13750
|
+
if (from < 0) return;
|
|
13751
|
+
let runStart = -1;
|
|
13752
|
+
const flush = (endIndex) => {
|
|
13753
|
+
if (runStart < 0) return;
|
|
13754
|
+
runs.push({ event, order, startIndex: runStart, endIndex, lane: -1 });
|
|
13755
|
+
runStart = -1;
|
|
13756
|
+
};
|
|
13757
|
+
for (let i = from; i <= to; i++) {
|
|
13758
|
+
if (blockedDates.has(cells[i].date)) {
|
|
13759
|
+
flush(i - 1);
|
|
13760
|
+
continue;
|
|
13761
|
+
}
|
|
13762
|
+
if (runStart >= 0 && i % 7 === 0) flush(i - 1);
|
|
13763
|
+
if (runStart < 0) runStart = i;
|
|
13764
|
+
}
|
|
13765
|
+
flush(to);
|
|
13766
|
+
});
|
|
13767
|
+
runs.sort(
|
|
13768
|
+
(a, b) => a.startIndex - b.startIndex || b.endIndex - b.startIndex - (a.endIndex - a.startIndex) || a.order - b.order
|
|
13769
|
+
);
|
|
13770
|
+
const weekLanes = Array.from({ length: weekCount }, () => []);
|
|
13771
|
+
for (const run of runs) {
|
|
13772
|
+
const week = Math.floor(run.startIndex / 7);
|
|
13773
|
+
const fromDay = run.startIndex % 7;
|
|
13774
|
+
const toDay = run.endIndex % 7;
|
|
13775
|
+
const lanes = weekLanes[week];
|
|
13776
|
+
let lane = 0;
|
|
13777
|
+
for (; ; ) {
|
|
13778
|
+
if (lanes[lane] == null) lanes[lane] = new Array(7).fill(false);
|
|
13779
|
+
let free = true;
|
|
13780
|
+
for (let d = fromDay; d <= toDay && free; d++) if (lanes[lane][d]) free = false;
|
|
13781
|
+
if (free) break;
|
|
13782
|
+
lane += 1;
|
|
13783
|
+
}
|
|
13784
|
+
for (let d = fromDay; d <= toDay; d++) lanes[lane][d] = true;
|
|
13785
|
+
run.lane = lane;
|
|
13786
|
+
}
|
|
13787
|
+
for (let week = 0; week < weekCount; week++) {
|
|
13788
|
+
if (weekLanes[week].length > rowCapacity) moreLaneByWeek[week] = rowCapacity - 1;
|
|
13789
|
+
}
|
|
13790
|
+
for (const run of runs) {
|
|
13791
|
+
const week = Math.floor(run.startIndex / 7);
|
|
13792
|
+
const moreLane = moreLaneByWeek[week];
|
|
13793
|
+
const limit = moreLane < 0 ? rowCapacity : moreLane;
|
|
13794
|
+
if (run.lane >= limit) {
|
|
13795
|
+
for (let i = run.startIndex; i <= run.endIndex; i++) hiddenCountByCell[i] += 1;
|
|
13796
|
+
continue;
|
|
13797
|
+
}
|
|
13798
|
+
const { event } = run;
|
|
13799
|
+
segmentsByCell[run.startIndex].push({
|
|
13800
|
+
event,
|
|
13801
|
+
key: `${event.id ?? `${event.date}-${run.order}`}-${run.startIndex}`,
|
|
13802
|
+
startIndex: run.startIndex,
|
|
13803
|
+
span: run.endIndex - run.startIndex + 1,
|
|
13804
|
+
lane: run.lane,
|
|
13805
|
+
startsHere: cells[run.startIndex].date === event.date,
|
|
13806
|
+
endsHere: cells[run.endIndex].date === eventEndDate(event)
|
|
13807
|
+
});
|
|
13808
|
+
}
|
|
13809
|
+
return { segmentsByCell, eventsByCell, hiddenCountByCell, moreLaneByWeek };
|
|
13810
|
+
}
|
|
13811
|
+
|
|
13720
13812
|
// src/components/SCalendarBoard/calendarBoard.config.ts
|
|
13721
13813
|
var CALENDAR_BOARD_EVENT_COLORS = [
|
|
13722
13814
|
"default",
|
|
@@ -13761,7 +13853,10 @@ var CALENDAR_BOARD_DAY_BG = {
|
|
|
13761
13853
|
function CalendarEventButton({
|
|
13762
13854
|
event,
|
|
13763
13855
|
onSelect,
|
|
13764
|
-
|
|
13856
|
+
flatStart = false,
|
|
13857
|
+
flatEnd = false,
|
|
13858
|
+
className,
|
|
13859
|
+
style
|
|
13765
13860
|
}) {
|
|
13766
13861
|
const preset = CALENDAR_BOARD_EVENT_PRESETS[event.color ?? "default"];
|
|
13767
13862
|
const dot = event.dotColor ?? preset.dot;
|
|
@@ -13781,10 +13876,13 @@ function CalendarEventButton({
|
|
|
13781
13876
|
"h-(--cmp-calendarBoard-item-height) shrink-0 rounded-(--cmp-calendarBoard-item-radius)",
|
|
13782
13877
|
"gap-(--cmp-calendarBoard-eventButton-inline-gap) px-(--cmp-calendarBoard-item-paddingX)",
|
|
13783
13878
|
"bg-(--ceb-bg) text-(--cmp-calendarBoard-eventButton-text-color-default)",
|
|
13879
|
+
// 잘린 쪽은 평평하게 — 다음 조각과 이어 보면 한 막대가 주 경계에서 나뉜 모양이 된다.
|
|
13880
|
+
flatStart && "rounded-s-none",
|
|
13881
|
+
flatEnd && "rounded-e-none",
|
|
13784
13882
|
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
|
|
13785
13883
|
className
|
|
13786
13884
|
),
|
|
13787
|
-
style: { "--ceb-bg": preset.bg },
|
|
13885
|
+
style: { ...style, "--ceb-bg": preset.bg },
|
|
13788
13886
|
onClick: (e) => {
|
|
13789
13887
|
if (disabled) return;
|
|
13790
13888
|
onSelect?.(event, e);
|
|
@@ -13794,7 +13892,7 @@ function CalendarEventButton({
|
|
|
13794
13892
|
"span",
|
|
13795
13893
|
{
|
|
13796
13894
|
"aria-hidden": true,
|
|
13797
|
-
className: "pointer-events-none absolute inset-0 rounded-
|
|
13895
|
+
className: "pointer-events-none absolute inset-0 rounded-[inherit] bg-(--cmp-calendarBoard-eventButton-hover-overlay-color) opacity-0 transition-opacity group-hover:opacity-(--cmp-calendarBoard-eventButton-hover-overlay-opacity)"
|
|
13798
13896
|
}
|
|
13799
13897
|
),
|
|
13800
13898
|
/* @__PURE__ */ jsxRuntime.jsx(SBadge, { "aria-hidden": true, color: dot, className: "shrink-0" }),
|
|
@@ -13828,7 +13926,7 @@ function CalendarEventButton({
|
|
|
13828
13926
|
}
|
|
13829
13927
|
);
|
|
13830
13928
|
}
|
|
13831
|
-
var CalendarMoreButton = /* @__PURE__ */ react.forwardRef(function CalendarMoreButton2({ count, label = "\uB354\uBCF4\uAE30", onClick, className }, ref) {
|
|
13929
|
+
var CalendarMoreButton = /* @__PURE__ */ react.forwardRef(function CalendarMoreButton2({ count, label = "\uB354\uBCF4\uAE30", onClick, className, style }, ref) {
|
|
13832
13930
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
13833
13931
|
"button",
|
|
13834
13932
|
{
|
|
@@ -13841,6 +13939,7 @@ var CalendarMoreButton = /* @__PURE__ */ react.forwardRef(function CalendarMoreB
|
|
|
13841
13939
|
"bg-(--cmp-calendarBoard-moreButton-bg)",
|
|
13842
13940
|
className
|
|
13843
13941
|
),
|
|
13942
|
+
style,
|
|
13844
13943
|
onClick,
|
|
13845
13944
|
children: [
|
|
13846
13945
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -13859,6 +13958,9 @@ var CalendarMoreButton = /* @__PURE__ */ react.forwardRef(function CalendarMoreB
|
|
|
13859
13958
|
}
|
|
13860
13959
|
);
|
|
13861
13960
|
});
|
|
13961
|
+
var HEADER_HEIGHT = "var(--cmp-calendarBoard-dayCard-header-paddingTop) + var(--cmp-calendarBoard-daySequence-size) + var(--cmp-calendarBoard-dayCard-header-paddingBottom)";
|
|
13962
|
+
var laneTop = (lane) => `calc(${HEADER_HEIGHT} + ${lane} * (var(--cmp-calendarBoard-item-height) + var(--cmp-calendarBoard-dayCard-grid-gap)))`;
|
|
13963
|
+
var spanWidth = (span) => `calc(${span} * 100% + ${span - 1}px - 2 * var(--cmp-calendarBoard-dayCard-paddingX))`;
|
|
13862
13964
|
var CalendarDayCard = /* @__PURE__ */ react.forwardRef(function CalendarDayCard2({
|
|
13863
13965
|
date,
|
|
13864
13966
|
day,
|
|
@@ -13867,9 +13969,11 @@ var CalendarDayCard = /* @__PURE__ */ react.forwardRef(function CalendarDayCard2
|
|
|
13867
13969
|
isSelected,
|
|
13868
13970
|
selectable,
|
|
13869
13971
|
color,
|
|
13870
|
-
|
|
13972
|
+
segments,
|
|
13973
|
+
dayEventCount,
|
|
13871
13974
|
body,
|
|
13872
13975
|
hiddenCount,
|
|
13976
|
+
moreLane,
|
|
13873
13977
|
isOpen,
|
|
13874
13978
|
moreLabel,
|
|
13875
13979
|
onDaySelect,
|
|
@@ -13877,7 +13981,7 @@ var CalendarDayCard = /* @__PURE__ */ react.forwardRef(function CalendarDayCard2
|
|
|
13877
13981
|
onOpenEvents,
|
|
13878
13982
|
...rest
|
|
13879
13983
|
}, ref) {
|
|
13880
|
-
const hasEvents =
|
|
13984
|
+
const hasEvents = dayEventCount > 0;
|
|
13881
13985
|
const interactive = selectable || hasEvents;
|
|
13882
13986
|
const activate = () => {
|
|
13883
13987
|
if (hasEvents) onOpenEvents?.();
|
|
@@ -13912,7 +14016,7 @@ var CalendarDayCard = /* @__PURE__ */ react.forwardRef(function CalendarDayCard2
|
|
|
13912
14016
|
"span",
|
|
13913
14017
|
{
|
|
13914
14018
|
"aria-hidden": true,
|
|
13915
|
-
className: "pointer-events-none absolute inset-[1px] rounded-(--cmp-calendarBoard-dayCard-focus-border-radius) border-(length:--cmp-calendarBoard-dayCard-focus-border-width) border-solid border-(--cmp-calendarBoard-dayCard-focus-border-color)"
|
|
14019
|
+
className: "pointer-events-none absolute inset-[1px] z-20 rounded-(--cmp-calendarBoard-dayCard-focus-border-radius) border-(length:--cmp-calendarBoard-dayCard-focus-border-width) border-solid border-(--cmp-calendarBoard-dayCard-focus-border-color)"
|
|
13916
14020
|
}
|
|
13917
14021
|
),
|
|
13918
14022
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 pt-(--cmp-calendarBoard-dayCard-header-paddingTop) pb-(--cmp-calendarBoard-dayCard-header-paddingBottom)", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -13931,39 +14035,41 @@ var CalendarDayCard = /* @__PURE__ */ react.forwardRef(function CalendarDayCard2
|
|
|
13931
14035
|
children: day
|
|
13932
14036
|
}
|
|
13933
14037
|
) }),
|
|
13934
|
-
/* @__PURE__ */ jsxRuntime.
|
|
13935
|
-
|
|
14038
|
+
body != null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-0 flex-1 flex-col pb-(--cmp-calendarBoard-dayCard-grid-paddingBottom)", children: body }),
|
|
14039
|
+
body == null && segments.map((segment) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
14040
|
+
CalendarEventButton,
|
|
13936
14041
|
{
|
|
13937
|
-
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
|
|
13941
|
-
|
|
13942
|
-
|
|
13943
|
-
|
|
13944
|
-
|
|
13945
|
-
|
|
13946
|
-
|
|
13947
|
-
|
|
13948
|
-
|
|
13949
|
-
|
|
13950
|
-
|
|
13951
|
-
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
|
|
13955
|
-
|
|
13956
|
-
|
|
13957
|
-
|
|
13958
|
-
|
|
13959
|
-
|
|
13960
|
-
|
|
13961
|
-
|
|
13962
|
-
|
|
13963
|
-
|
|
13964
|
-
|
|
13965
|
-
)
|
|
13966
|
-
|
|
14042
|
+
event: segment.event,
|
|
14043
|
+
flatStart: !segment.startsHere,
|
|
14044
|
+
flatEnd: !segment.endsHere,
|
|
14045
|
+
className: "absolute z-10",
|
|
14046
|
+
style: {
|
|
14047
|
+
top: laneTop(segment.lane),
|
|
14048
|
+
left: "var(--cmp-calendarBoard-dayCard-paddingX)",
|
|
14049
|
+
width: spanWidth(segment.span)
|
|
14050
|
+
},
|
|
14051
|
+
onSelect: (ev, e) => {
|
|
14052
|
+
e.stopPropagation();
|
|
14053
|
+
onEventClick?.(ev, e);
|
|
14054
|
+
}
|
|
14055
|
+
},
|
|
14056
|
+
segment.key
|
|
14057
|
+
)),
|
|
14058
|
+
body == null && hiddenCount > 0 && moreLane >= 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
14059
|
+
CalendarMoreButton,
|
|
14060
|
+
{
|
|
14061
|
+
count: hiddenCount,
|
|
14062
|
+
label: moreLabel,
|
|
14063
|
+
className: "absolute z-10",
|
|
14064
|
+
style: {
|
|
14065
|
+
top: laneTop(moreLane),
|
|
14066
|
+
left: "var(--cmp-calendarBoard-dayCard-paddingX)",
|
|
14067
|
+
width: spanWidth(1)
|
|
14068
|
+
},
|
|
14069
|
+
onClick: (e) => {
|
|
14070
|
+
e.stopPropagation();
|
|
14071
|
+
onOpenEvents?.();
|
|
14072
|
+
}
|
|
13967
14073
|
}
|
|
13968
14074
|
)
|
|
13969
14075
|
]
|
|
@@ -14097,16 +14203,14 @@ function SCalendarBoard({
|
|
|
14097
14203
|
observer.observe(cell);
|
|
14098
14204
|
return () => observer.disconnect();
|
|
14099
14205
|
}, [rowCount]);
|
|
14100
|
-
const
|
|
14101
|
-
const
|
|
14102
|
-
|
|
14103
|
-
|
|
14104
|
-
|
|
14105
|
-
|
|
14106
|
-
|
|
14107
|
-
|
|
14108
|
-
}, [events]);
|
|
14109
|
-
const openEvents = openDate ? eventMap.get(openDate) ?? [] : [];
|
|
14206
|
+
const layout = react.useMemo(() => {
|
|
14207
|
+
const blockedDates = new Set(
|
|
14208
|
+
cells.filter((cell) => dayContent?.[cell.date] != null).map((cell) => cell.date)
|
|
14209
|
+
);
|
|
14210
|
+
return layoutCalendarBoard({ cells, events: events ?? [], rowCapacity, blockedDates });
|
|
14211
|
+
}, [cells, events, rowCapacity, dayContent]);
|
|
14212
|
+
const openIndex = openDate == null ? -1 : cells.findIndex((cell) => cell.date === openDate);
|
|
14213
|
+
const openEvents = openIndex < 0 ? [] : layout.eventsByCell[openIndex];
|
|
14110
14214
|
react.useLayoutEffect(() => {
|
|
14111
14215
|
if (openDate == null || panelEl == null) return;
|
|
14112
14216
|
const cell = gridRef.current?.querySelector(`[data-date="${openDate}"]`);
|
|
@@ -14247,11 +14351,9 @@ function SCalendarBoard({
|
|
|
14247
14351
|
// 행은 판 높이를 균등하게 나눠 갖는다 — 일정이 많은 주만 길어지면 격자가 깨진다.
|
|
14248
14352
|
gridTemplateRows: `repeat(${rowCount}, minmax(var(--cmp-calendarBoard-dayCard-minHeight), 1fr))`
|
|
14249
14353
|
},
|
|
14250
|
-
children: cells.map((cell) => {
|
|
14354
|
+
children: cells.map((cell, index) => {
|
|
14251
14355
|
const content = dayContent?.[cell.date];
|
|
14252
|
-
const list =
|
|
14253
|
-
const folded = list.length > rowCapacity;
|
|
14254
|
-
const visible = folded ? list.slice(0, rowCapacity - 1) : list;
|
|
14356
|
+
const list = layout.eventsByCell[index];
|
|
14255
14357
|
const card = /* @__PURE__ */ jsxRuntime.jsx(
|
|
14256
14358
|
CalendarDayCard,
|
|
14257
14359
|
{
|
|
@@ -14262,9 +14364,11 @@ function SCalendarBoard({
|
|
|
14262
14364
|
isSelected: value != null && value === cell.date,
|
|
14263
14365
|
selectable,
|
|
14264
14366
|
color: dayColors?.[cell.date] ?? "default",
|
|
14265
|
-
|
|
14367
|
+
segments: layout.segmentsByCell[index],
|
|
14368
|
+
dayEventCount: list.length,
|
|
14266
14369
|
body: content,
|
|
14267
|
-
hiddenCount:
|
|
14370
|
+
hiddenCount: layout.hiddenCountByCell[index],
|
|
14371
|
+
moreLane: layout.moreLaneByWeek[Math.floor(index / 7)],
|
|
14268
14372
|
isOpen: openDate === cell.date,
|
|
14269
14373
|
moreLabel,
|
|
14270
14374
|
onDaySelect: (date) => onValueChange?.(date, list),
|