@seed-design/react 2.1.0 → 2.2.0
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/lib/components/DatePicker/DatePicker.cjs +351 -0
- package/lib/components/DatePicker/DatePicker.d.ts +39 -0
- package/lib/components/DatePicker/DatePicker.d.ts.map +1 -0
- package/lib/components/DatePicker/DatePicker.js +345 -0
- package/lib/components/DatePicker/DatePicker.test.d.ts +2 -0
- package/lib/components/DatePicker/DatePicker.test.d.ts.map +1 -0
- package/lib/components/DatePicker/index.cjs +62 -0
- package/lib/components/DatePicker/index.d.ts +3 -0
- package/lib/components/DatePicker/index.d.ts.map +1 -0
- package/lib/components/DatePicker/index.js +3 -0
- package/lib/components/QuantityPicker/QuantityPicker.d.ts +13 -1
- package/lib/components/QuantityPicker/QuantityPicker.d.ts.map +1 -1
- package/lib/components/TimePicker/TimePicker.cjs +97 -0
- package/lib/components/TimePicker/TimePicker.d.ts +28 -0
- package/lib/components/TimePicker/TimePicker.d.ts.map +1 -0
- package/lib/components/TimePicker/TimePicker.js +94 -0
- package/lib/components/TimePicker/TimePicker.test.d.ts +2 -0
- package/lib/components/TimePicker/TimePicker.test.d.ts.map +1 -0
- package/lib/components/TimePicker/index.cjs +3 -0
- package/lib/components/TimePicker/index.d.ts +2 -0
- package/lib/components/TimePicker/index.d.ts.map +1 -0
- package/lib/components/TimePicker/index.js +2 -0
- package/lib/components/index.cjs +63 -1
- package/lib/components/index.d.ts +2 -0
- package/lib/components/index.d.ts.map +1 -1
- package/lib/components/index.js +5 -2
- package/lib/components/private/WheelPicker.cjs +64 -0
- package/lib/components/private/WheelPicker.d.ts +16 -0
- package/lib/components/private/WheelPicker.d.ts.map +1 -0
- package/lib/components/private/WheelPicker.js +59 -0
- package/lib/components/private/WheelPicker.test.d.ts +2 -0
- package/lib/components/private/WheelPicker.test.d.ts.map +1 -0
- package/lib/index.cjs +63 -1
- package/lib/index.js +5 -2
- package/package.json +8 -5
- package/src/components/DatePicker/DatePicker.test.tsx +234 -0
- package/src/components/DatePicker/DatePicker.tsx +574 -0
- package/src/components/DatePicker/index.ts +34 -0
- package/src/components/QuantityPicker/QuantityPicker.test.tsx +10 -0
- package/src/components/QuantityPicker/QuantityPicker.tsx +14 -2
- package/src/components/TimePicker/TimePicker.test.tsx +166 -0
- package/src/components/TimePicker/TimePicker.tsx +163 -0
- package/src/components/TimePicker/index.ts +6 -0
- package/src/components/index.ts +2 -0
- package/src/components/private/WheelPicker.test.tsx +54 -0
- package/src/components/private/WheelPicker.tsx +110 -0
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { composeRefs } from "@radix-ui/react-compose-refs";
|
|
4
|
+
import { datePicker } from "@seed-design/css/recipes/date-picker";
|
|
5
|
+
import { mergeProps } from "@seed-design/dom-utils";
|
|
6
|
+
import {
|
|
7
|
+
useDatePicker,
|
|
8
|
+
type DatePickerActions,
|
|
9
|
+
type DatePickerCell,
|
|
10
|
+
type DatePickerCellState,
|
|
11
|
+
type DatePickerMonth,
|
|
12
|
+
type DatePickerVisibleRange,
|
|
13
|
+
type UseDatePickerProps,
|
|
14
|
+
} from "@seed-design/react-date-picker";
|
|
15
|
+
import { Primitive, type PrimitiveProps } from "@seed-design/react-primitive";
|
|
16
|
+
import clsx from "clsx";
|
|
17
|
+
import * as React from "react";
|
|
18
|
+
import { InternalWheelPickerColumn, InternalWheelPickerRoot } from "../private/WheelPicker";
|
|
19
|
+
import { useStyleProps, type StyleProps } from "../../utils/styled";
|
|
20
|
+
import { ActionButton } from "../ActionButton";
|
|
21
|
+
import { Icon } from "../Icon";
|
|
22
|
+
|
|
23
|
+
// 44px × 7개 항목으로 308px viewport를 만들고, 336px 컨테이너 안에 중앙 정렬합니다.
|
|
24
|
+
const WHEEL_ITEM_SIZE = 44;
|
|
25
|
+
const WHEEL_VISIBLE_ITEM_COUNT = 7;
|
|
26
|
+
|
|
27
|
+
type DatePickerCssProperties = React.CSSProperties & {
|
|
28
|
+
"--seed-date-picker-continuous-spacer-height"?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function getContinuousSpacerStyle(height: number): DatePickerCssProperties {
|
|
32
|
+
return { "--seed-date-picker-continuous-spacer-height": `${height}px` };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type DatePickerRootProps = PrimitiveProps &
|
|
36
|
+
Pick<StyleProps, "height" | "minHeight" | "maxHeight"> &
|
|
37
|
+
Omit<
|
|
38
|
+
React.HTMLAttributes<HTMLDivElement>,
|
|
39
|
+
"children" | "defaultValue" | "onChange" | "onValueChange"
|
|
40
|
+
>;
|
|
41
|
+
|
|
42
|
+
export interface DatePickerCellContentRenderProps extends DatePickerCellState {}
|
|
43
|
+
|
|
44
|
+
type DatePickerBehaviorProps = UseDatePickerProps extends infer Props
|
|
45
|
+
? Props extends UseDatePickerProps
|
|
46
|
+
? Omit<Props, "visibleRange">
|
|
47
|
+
: never
|
|
48
|
+
: never;
|
|
49
|
+
|
|
50
|
+
type DatePickerCellCustomization =
|
|
51
|
+
| {
|
|
52
|
+
/**
|
|
53
|
+
* 기본 날짜 숫자 아래에 부가 콘텐츠를 추가합니다.
|
|
54
|
+
* 날짜 숫자를 유지하는 대부분의 사례에서는 이 prop을 우선 사용하세요.
|
|
55
|
+
*/
|
|
56
|
+
renderDateCellSupplement?: (props: DatePickerCellContentRenderProps) => React.ReactNode;
|
|
57
|
+
renderDateCellContent?: never;
|
|
58
|
+
}
|
|
59
|
+
| {
|
|
60
|
+
/**
|
|
61
|
+
* `renderDateCellSupplement`로 표현할 수 없을 때 내부 콘텐츠 전체를 교체하는
|
|
62
|
+
* 저수준 이스케이프 해치입니다. 날짜 셀의 접근성·인터랙션 구조는 유지됩니다.
|
|
63
|
+
*/
|
|
64
|
+
renderDateCellContent?: (props: DatePickerCellContentRenderProps) => React.ReactNode;
|
|
65
|
+
renderDateCellSupplement?: never;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
type DatePickerSharedProps = DatePickerBehaviorProps &
|
|
69
|
+
DatePickerRootProps & {
|
|
70
|
+
/** 날짜 이동과 포커스 action을 받는 ref입니다. */
|
|
71
|
+
actionsRef?: React.Ref<DatePickerActions>;
|
|
72
|
+
} & DatePickerCellCustomization;
|
|
73
|
+
|
|
74
|
+
type ContinuousSizeConstraint =
|
|
75
|
+
| (Required<Pick<StyleProps, "height">> & Pick<StyleProps, "minHeight" | "maxHeight">)
|
|
76
|
+
| (Required<Pick<StyleProps, "minHeight">> & Pick<StyleProps, "height" | "maxHeight">)
|
|
77
|
+
| (Required<Pick<StyleProps, "maxHeight">> & Pick<StyleProps, "height" | "minHeight">);
|
|
78
|
+
|
|
79
|
+
export type DatePickerProps = DatePickerSharedProps;
|
|
80
|
+
export type TwoMonthDatePickerProps = DatePickerSharedProps;
|
|
81
|
+
export type WeekDatePickerProps = DatePickerSharedProps;
|
|
82
|
+
type DatePickerPropsWithoutSize<Props = DatePickerSharedProps> = Props extends DatePickerSharedProps
|
|
83
|
+
? Omit<Props, "height" | "minHeight" | "maxHeight">
|
|
84
|
+
: never;
|
|
85
|
+
export type ContinuousDatePickerProps = DatePickerPropsWithoutSize & ContinuousSizeConstraint;
|
|
86
|
+
|
|
87
|
+
type DatePickerImplementationProps = DatePickerSharedProps & {
|
|
88
|
+
visibleRange: DatePickerVisibleRange;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
type NavigationChevronIconProps = React.SVGProps<SVGSVGElement> & {
|
|
92
|
+
direction: "left" | "right";
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const NavigationChevronIcon = React.forwardRef<SVGSVGElement, NavigationChevronIconProps>(
|
|
96
|
+
({ direction, ...props }, ref) => {
|
|
97
|
+
const path =
|
|
98
|
+
direction === "left"
|
|
99
|
+
? "M22.7931 13.0704C23.0788 13.3703 23.0672 13.8451 22.7673 14.1307L16.5875 20.0166L22.7672 25.9017C23.0672 26.1873 23.0788 26.6621 22.7931 26.962C22.5075 27.262 22.0327 27.2736 21.7328 26.9879L14.9828 20.5598C14.8341 20.4182 14.75 20.2219 14.75 20.0167C14.75 19.8114 14.8341 19.6151 14.9827 19.4736L21.7327 13.0446C22.0327 12.7589 22.5074 12.7704 22.7931 13.0704Z"
|
|
100
|
+
: "M17.2069 13.0704C16.9212 13.3703 16.9328 13.8451 17.2327 14.1307L23.4125 20.0166L17.2328 25.9017C16.9328 26.1873 16.9212 26.6621 17.2069 26.962C17.4925 27.262 17.9673 27.2736 18.2672 26.9879L25.0172 20.5598C25.1659 20.4182 25.25 20.2219 25.25 20.0167C25.25 19.8114 25.1659 19.6151 25.0173 19.4736L18.2673 13.0446C17.9673 12.7589 17.4926 12.7704 17.2069 13.0704Z";
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<svg
|
|
104
|
+
ref={ref}
|
|
105
|
+
{...props}
|
|
106
|
+
viewBox="8 8 24 24"
|
|
107
|
+
fill="none"
|
|
108
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
109
|
+
>
|
|
110
|
+
<path fillRule="evenodd" clipRule="evenodd" d={path} fill="currentColor" />
|
|
111
|
+
</svg>
|
|
112
|
+
);
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
NavigationChevronIcon.displayName = "NavigationChevronIcon";
|
|
116
|
+
|
|
117
|
+
type HeaderChevronIconProps = React.SVGProps<SVGSVGElement> & {
|
|
118
|
+
expanded: boolean;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const HeaderChevronIcon = React.forwardRef<SVGSVGElement, HeaderChevronIconProps>(
|
|
122
|
+
({ expanded, ...props }, ref) => (
|
|
123
|
+
<svg
|
|
124
|
+
ref={ref}
|
|
125
|
+
{...props}
|
|
126
|
+
viewBox="0 0 20 20"
|
|
127
|
+
fill="none"
|
|
128
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
129
|
+
>
|
|
130
|
+
<path
|
|
131
|
+
fillRule="evenodd"
|
|
132
|
+
clipRule="evenodd"
|
|
133
|
+
d="M5.2453 7.74286C5.57141 7.4181 6.09905 7.41919 6.42381 7.7453L9.98736 11.3237L13.5808 7.77134C13.9081 7.44778 14.4357 7.45081 14.7593 7.77812C15.0829 8.10542 15.0798 8.63305 14.7525 8.95661L10.5686 13.0926C10.2422 13.4154 9.7162 13.4133 9.39228 13.088L5.24286 8.92137C4.9181 8.59526 4.91919 8.06762 5.2453 7.74286Z"
|
|
134
|
+
fill="currentColor"
|
|
135
|
+
transform={expanded ? "rotate(180 10 10)" : undefined}
|
|
136
|
+
/>
|
|
137
|
+
</svg>
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
HeaderChevronIcon.displayName = "HeaderChevronIcon";
|
|
141
|
+
|
|
142
|
+
function WeekdayRow({
|
|
143
|
+
api,
|
|
144
|
+
classNames,
|
|
145
|
+
semantic = true,
|
|
146
|
+
}: {
|
|
147
|
+
api: ReturnType<typeof useDatePicker>;
|
|
148
|
+
classNames: ReturnType<typeof datePicker>;
|
|
149
|
+
semantic?: boolean;
|
|
150
|
+
}) {
|
|
151
|
+
return (
|
|
152
|
+
<Primitive.div
|
|
153
|
+
role={semantic ? "row" : undefined}
|
|
154
|
+
aria-hidden={semantic ? undefined : true}
|
|
155
|
+
className={classNames.weekdayRow}
|
|
156
|
+
>
|
|
157
|
+
{api.weekdayLabels.map((weekday) => (
|
|
158
|
+
<Primitive.div
|
|
159
|
+
key={weekday.key}
|
|
160
|
+
role={semantic ? "columnheader" : undefined}
|
|
161
|
+
aria-label={semantic ? weekday.long : undefined}
|
|
162
|
+
className={classNames.weekday}
|
|
163
|
+
>
|
|
164
|
+
<span aria-hidden="true">{weekday.short}</span>
|
|
165
|
+
</Primitive.div>
|
|
166
|
+
))}
|
|
167
|
+
</Primitive.div>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function DateCellView({
|
|
172
|
+
cell,
|
|
173
|
+
classNames,
|
|
174
|
+
renderDateCellContent,
|
|
175
|
+
renderDateCellSupplement,
|
|
176
|
+
}: {
|
|
177
|
+
cell: DatePickerCell | null;
|
|
178
|
+
classNames: ReturnType<typeof datePicker>;
|
|
179
|
+
renderDateCellContent: DatePickerImplementationProps["renderDateCellContent"];
|
|
180
|
+
renderDateCellSupplement: DatePickerImplementationProps["renderDateCellSupplement"];
|
|
181
|
+
}) {
|
|
182
|
+
if (cell === null) {
|
|
183
|
+
return <Primitive.div role="gridcell" className={classNames.emptyCell} />;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const { cellProps, buttonProps, key: _key, ...renderProps } = cell;
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<Primitive.div {...cellProps} className={classNames.dateCell}>
|
|
190
|
+
<Primitive.button {...buttonProps} className={classNames.dateButton}>
|
|
191
|
+
<Primitive.span className={classNames.dateContent}>
|
|
192
|
+
{renderDateCellContent ? (
|
|
193
|
+
renderDateCellContent(renderProps)
|
|
194
|
+
) : (
|
|
195
|
+
<>
|
|
196
|
+
<Primitive.span data-date-picker-day="">{cell.formattedDay}</Primitive.span>
|
|
197
|
+
{renderDateCellSupplement?.(renderProps)}
|
|
198
|
+
</>
|
|
199
|
+
)}
|
|
200
|
+
</Primitive.span>
|
|
201
|
+
</Primitive.button>
|
|
202
|
+
</Primitive.div>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function MonthView({
|
|
207
|
+
api,
|
|
208
|
+
classNames,
|
|
209
|
+
month,
|
|
210
|
+
renderDateCellContent,
|
|
211
|
+
renderDateCellSupplement,
|
|
212
|
+
showWeekdays,
|
|
213
|
+
showMonthLabel,
|
|
214
|
+
renderHeader,
|
|
215
|
+
monthRef,
|
|
216
|
+
}: {
|
|
217
|
+
api: ReturnType<typeof useDatePicker>;
|
|
218
|
+
classNames: ReturnType<typeof datePicker>;
|
|
219
|
+
month: DatePickerMonth;
|
|
220
|
+
renderDateCellContent: DatePickerImplementationProps["renderDateCellContent"];
|
|
221
|
+
renderDateCellSupplement: DatePickerImplementationProps["renderDateCellSupplement"];
|
|
222
|
+
showWeekdays: boolean;
|
|
223
|
+
showMonthLabel: boolean;
|
|
224
|
+
renderHeader?: (labelId: string) => React.ReactNode;
|
|
225
|
+
monthRef?: React.Ref<HTMLDivElement>;
|
|
226
|
+
}) {
|
|
227
|
+
const labelId = `${api.rootProps.id}-${month.key}-label`;
|
|
228
|
+
const headerLabelId = `${api.rootProps.id}-header-label`;
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<Primitive.div ref={monthRef} className={classNames.month}>
|
|
232
|
+
{renderHeader?.(labelId)}
|
|
233
|
+
{showMonthLabel ? (
|
|
234
|
+
<Primitive.div id={labelId} className={classNames.monthLabel}>
|
|
235
|
+
{month.label}
|
|
236
|
+
</Primitive.div>
|
|
237
|
+
) : (
|
|
238
|
+
<Primitive.span id={labelId} className={classNames.liveRegion}>
|
|
239
|
+
{month.label}
|
|
240
|
+
</Primitive.span>
|
|
241
|
+
)}
|
|
242
|
+
<Primitive.div
|
|
243
|
+
{...api.gridProps}
|
|
244
|
+
aria-labelledby={
|
|
245
|
+
showMonthLabel || renderHeader
|
|
246
|
+
? labelId
|
|
247
|
+
: api.visibleRange === "month"
|
|
248
|
+
? headerLabelId
|
|
249
|
+
: labelId
|
|
250
|
+
}
|
|
251
|
+
className={classNames.grid}
|
|
252
|
+
>
|
|
253
|
+
{showWeekdays && <WeekdayRow api={api} classNames={classNames} />}
|
|
254
|
+
{month.weeks.map((week) => (
|
|
255
|
+
<Primitive.div key={week.key} role="row" className={classNames.weekRow}>
|
|
256
|
+
{week.cells.map((cell, index) => (
|
|
257
|
+
<DateCellView
|
|
258
|
+
key={cell?.key ?? `${week.key}-empty-${index}`}
|
|
259
|
+
cell={cell}
|
|
260
|
+
classNames={classNames}
|
|
261
|
+
renderDateCellContent={renderDateCellContent}
|
|
262
|
+
renderDateCellSupplement={renderDateCellSupplement}
|
|
263
|
+
/>
|
|
264
|
+
))}
|
|
265
|
+
</Primitive.div>
|
|
266
|
+
))}
|
|
267
|
+
</Primitive.div>
|
|
268
|
+
</Primitive.div>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function NavigationButton({
|
|
273
|
+
direction,
|
|
274
|
+
className,
|
|
275
|
+
...props
|
|
276
|
+
}: Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "color"> & {
|
|
277
|
+
direction: "left" | "right";
|
|
278
|
+
}) {
|
|
279
|
+
return (
|
|
280
|
+
<ActionButton {...props} variant="ghost" size="medium" layout="iconOnly" className={className}>
|
|
281
|
+
<Icon svg={<NavigationChevronIcon direction={direction} />} />
|
|
282
|
+
</ActionButton>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function Header({
|
|
287
|
+
api,
|
|
288
|
+
classNames,
|
|
289
|
+
}: {
|
|
290
|
+
api: ReturnType<typeof useDatePicker>;
|
|
291
|
+
classNames: ReturnType<typeof datePicker>;
|
|
292
|
+
}) {
|
|
293
|
+
const previousDirection = api.isRtl ? "right" : "left";
|
|
294
|
+
const nextDirection = api.isRtl ? "left" : "right";
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<Primitive.div className={classNames.header}>
|
|
298
|
+
<Primitive.button
|
|
299
|
+
{...api.monthYearButtonProps}
|
|
300
|
+
id={`${api.rootProps.id}-header-label`}
|
|
301
|
+
className={classNames.headerLabel}
|
|
302
|
+
>
|
|
303
|
+
<Primitive.span data-date-picker-header-label="">{api.headerLabel}</Primitive.span>
|
|
304
|
+
<Primitive.span className={classNames.headerChevron}>
|
|
305
|
+
<Icon svg={<HeaderChevronIcon expanded={api.isWheelOpen} />} />
|
|
306
|
+
</Primitive.span>
|
|
307
|
+
</Primitive.button>
|
|
308
|
+
<Primitive.div className={classNames.navigation}>
|
|
309
|
+
<NavigationButton
|
|
310
|
+
{...api.previousButtonProps}
|
|
311
|
+
direction={previousDirection}
|
|
312
|
+
className={classNames.navigationButton}
|
|
313
|
+
/>
|
|
314
|
+
<NavigationButton
|
|
315
|
+
{...api.nextButtonProps}
|
|
316
|
+
direction={nextDirection}
|
|
317
|
+
className={classNames.navigationButton}
|
|
318
|
+
/>
|
|
319
|
+
</Primitive.div>
|
|
320
|
+
</Primitive.div>
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function TwoMonthHeader({
|
|
325
|
+
api,
|
|
326
|
+
classNames,
|
|
327
|
+
label,
|
|
328
|
+
labelId,
|
|
329
|
+
position,
|
|
330
|
+
}: {
|
|
331
|
+
api: ReturnType<typeof useDatePicker>;
|
|
332
|
+
classNames: ReturnType<typeof datePicker>;
|
|
333
|
+
label: string;
|
|
334
|
+
labelId: string;
|
|
335
|
+
position: "first" | "last";
|
|
336
|
+
}) {
|
|
337
|
+
const isFirst = position === "first";
|
|
338
|
+
const direction = api.isRtl ? (isFirst ? "right" : "left") : isFirst ? "left" : "right";
|
|
339
|
+
const buttonProps = isFirst ? api.previousButtonProps : api.nextButtonProps;
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<Primitive.div className={classNames.twoMonthHeader}>
|
|
343
|
+
<Primitive.div id={labelId} className={classNames.twoMonthLabel}>
|
|
344
|
+
{label}
|
|
345
|
+
</Primitive.div>
|
|
346
|
+
<NavigationButton
|
|
347
|
+
{...buttonProps}
|
|
348
|
+
direction={direction}
|
|
349
|
+
data-placement={isFirst ? "start" : "end"}
|
|
350
|
+
className={classNames.twoMonthNavigationButton}
|
|
351
|
+
/>
|
|
352
|
+
</Primitive.div>
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function WheelView({
|
|
357
|
+
api,
|
|
358
|
+
classNames,
|
|
359
|
+
}: {
|
|
360
|
+
api: ReturnType<typeof useDatePicker>;
|
|
361
|
+
classNames: ReturnType<typeof datePicker>;
|
|
362
|
+
}) {
|
|
363
|
+
return (
|
|
364
|
+
<Primitive.div className={classNames.wheelContainer}>
|
|
365
|
+
<InternalWheelPickerRoot
|
|
366
|
+
{...api.wheelProps}
|
|
367
|
+
itemSize={WHEEL_ITEM_SIZE}
|
|
368
|
+
visibleItemCount={WHEEL_VISIBLE_ITEM_COUNT}
|
|
369
|
+
disabled={api.disabled}
|
|
370
|
+
readOnly={api.readOnly}
|
|
371
|
+
aria-label={api.headerLabel}
|
|
372
|
+
className={classNames.wheelView}
|
|
373
|
+
columnsClassName={classNames.wheelColumns}
|
|
374
|
+
scrollFogClassName={classNames.wheelScrollFog}
|
|
375
|
+
selectionIndicatorClassName={classNames.wheelSelectionIndicator}
|
|
376
|
+
fogSize={102}
|
|
377
|
+
>
|
|
378
|
+
<InternalWheelPickerColumn
|
|
379
|
+
aria-label={api.ariaLabels.yearWheel}
|
|
380
|
+
className={classNames.yearColumn}
|
|
381
|
+
itemClassName={classNames.wheelItem}
|
|
382
|
+
options={api.wheel.yearOptions}
|
|
383
|
+
value={api.wheel.yearValue}
|
|
384
|
+
onValueChange={api.wheel.onYearValueChange}
|
|
385
|
+
loop={false}
|
|
386
|
+
/>
|
|
387
|
+
<InternalWheelPickerColumn
|
|
388
|
+
aria-label={api.ariaLabels.monthWheel}
|
|
389
|
+
className={classNames.monthColumn}
|
|
390
|
+
itemClassName={classNames.wheelItem}
|
|
391
|
+
options={api.wheel.monthOptions}
|
|
392
|
+
value={api.wheel.monthValue}
|
|
393
|
+
onValueChange={api.wheel.onMonthValueChange}
|
|
394
|
+
loop
|
|
395
|
+
/>
|
|
396
|
+
</InternalWheelPickerRoot>
|
|
397
|
+
</Primitive.div>
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const DatePickerImplementation = React.forwardRef<HTMLDivElement, DatePickerImplementationProps>(
|
|
402
|
+
(props, forwardedRef) => {
|
|
403
|
+
const api = useDatePicker(props);
|
|
404
|
+
const [variantProps, otherProps] = datePicker.splitVariantProps(props);
|
|
405
|
+
const classNames = datePicker({
|
|
406
|
+
...variantProps,
|
|
407
|
+
visibleRange: api.visibleRange,
|
|
408
|
+
});
|
|
409
|
+
const {
|
|
410
|
+
renderDateCellContent,
|
|
411
|
+
renderDateCellSupplement,
|
|
412
|
+
actionsRef,
|
|
413
|
+
selectionMode: _selectionMode,
|
|
414
|
+
value: _value,
|
|
415
|
+
defaultValue: _defaultValue,
|
|
416
|
+
onValueChange: _onValueChange,
|
|
417
|
+
viewDate: _viewDate,
|
|
418
|
+
defaultViewDate: _defaultViewDate,
|
|
419
|
+
onViewDateChange: _onViewDateChange,
|
|
420
|
+
today: _today,
|
|
421
|
+
locale: _locale,
|
|
422
|
+
weekStartsOn: _weekStartsOn,
|
|
423
|
+
yearRange: _yearRange,
|
|
424
|
+
constraints: _constraints,
|
|
425
|
+
disabled: _disabled,
|
|
426
|
+
readOnly: _readOnly,
|
|
427
|
+
ariaLabels: _ariaLabels,
|
|
428
|
+
className,
|
|
429
|
+
"aria-label": ariaLabel,
|
|
430
|
+
"aria-labelledby": ariaLabelledby,
|
|
431
|
+
...styleAndRootProps
|
|
432
|
+
} = otherProps;
|
|
433
|
+
const { style: styleProps, restProps: rootProps } = useStyleProps(styleAndRootProps);
|
|
434
|
+
const rootRef = React.useRef<HTMLDivElement | null>(null);
|
|
435
|
+
const hasContinuousSizeConstraint =
|
|
436
|
+
props.height !== undefined || props.minHeight !== undefined || props.maxHeight !== undefined;
|
|
437
|
+
|
|
438
|
+
React.useEffect(() => {
|
|
439
|
+
if (process.env.NODE_ENV === "production" || api.visibleRange !== "continuous") return;
|
|
440
|
+
if (!hasContinuousSizeConstraint) {
|
|
441
|
+
console.warn("ContinuousDatePicker: height, minHeight 또는 maxHeight가 필요합니다.");
|
|
442
|
+
}
|
|
443
|
+
}, [api.visibleRange, hasContinuousSizeConstraint]);
|
|
444
|
+
|
|
445
|
+
React.useImperativeHandle(actionsRef, () => api.actions, [api.actions]);
|
|
446
|
+
|
|
447
|
+
const rootAriaLabel = ariaLabelledby ? ariaLabel : (ariaLabel ?? api.ariaLabels.root);
|
|
448
|
+
|
|
449
|
+
return (
|
|
450
|
+
<Primitive.div
|
|
451
|
+
ref={composeRefs(forwardedRef, rootRef, api.refs.root)}
|
|
452
|
+
{...mergeProps(api.rootProps, rootProps)}
|
|
453
|
+
aria-label={rootAriaLabel}
|
|
454
|
+
aria-labelledby={ariaLabelledby}
|
|
455
|
+
className={clsx(classNames.root, className)}
|
|
456
|
+
style={styleProps}
|
|
457
|
+
>
|
|
458
|
+
{api.visibleRange === "continuous" ? (
|
|
459
|
+
<Primitive.div
|
|
460
|
+
ref={api.refs.continuousScroll}
|
|
461
|
+
{...api.continuousScrollProps}
|
|
462
|
+
data-date-picker-continuous-scroll=""
|
|
463
|
+
className={classNames.continuousScroll}
|
|
464
|
+
>
|
|
465
|
+
<WeekdayRow api={api} classNames={classNames} semantic={false} />
|
|
466
|
+
<Primitive.div className={classNames.continuousContent}>
|
|
467
|
+
<Primitive.div
|
|
468
|
+
aria-hidden="true"
|
|
469
|
+
data-date-picker-continuous-spacer=""
|
|
470
|
+
className={classNames.continuousSpacer}
|
|
471
|
+
style={getContinuousSpacerStyle(api.virtual.topHeight)}
|
|
472
|
+
/>
|
|
473
|
+
{api.months.map((month) => (
|
|
474
|
+
<MonthView
|
|
475
|
+
key={month.key}
|
|
476
|
+
api={api}
|
|
477
|
+
classNames={classNames}
|
|
478
|
+
month={month}
|
|
479
|
+
renderDateCellContent={renderDateCellContent}
|
|
480
|
+
renderDateCellSupplement={renderDateCellSupplement}
|
|
481
|
+
showWeekdays={false}
|
|
482
|
+
showMonthLabel
|
|
483
|
+
monthRef={api.refs.continuousMonth(month.key)}
|
|
484
|
+
/>
|
|
485
|
+
))}
|
|
486
|
+
<Primitive.div
|
|
487
|
+
aria-hidden="true"
|
|
488
|
+
data-date-picker-continuous-spacer=""
|
|
489
|
+
className={classNames.continuousSpacer}
|
|
490
|
+
style={getContinuousSpacerStyle(api.virtual.bottomHeight)}
|
|
491
|
+
/>
|
|
492
|
+
</Primitive.div>
|
|
493
|
+
</Primitive.div>
|
|
494
|
+
) : (
|
|
495
|
+
<>
|
|
496
|
+
{api.visibleRange !== "twoMonths" && <Header api={api} classNames={classNames} />}
|
|
497
|
+
<Primitive.div
|
|
498
|
+
data-wheel-open={api.isWheelOpen && api.visibleRange !== "twoMonths" ? "" : undefined}
|
|
499
|
+
aria-hidden={api.isWheelOpen && api.visibleRange !== "twoMonths" ? true : undefined}
|
|
500
|
+
className={classNames.months}
|
|
501
|
+
>
|
|
502
|
+
{api.months.map((month, index) => (
|
|
503
|
+
<MonthView
|
|
504
|
+
key={month.key}
|
|
505
|
+
api={api}
|
|
506
|
+
classNames={classNames}
|
|
507
|
+
month={month}
|
|
508
|
+
renderDateCellContent={renderDateCellContent}
|
|
509
|
+
renderDateCellSupplement={renderDateCellSupplement}
|
|
510
|
+
showWeekdays
|
|
511
|
+
showMonthLabel={false}
|
|
512
|
+
renderHeader={
|
|
513
|
+
api.visibleRange === "twoMonths"
|
|
514
|
+
? (labelId) => (
|
|
515
|
+
<TwoMonthHeader
|
|
516
|
+
api={api}
|
|
517
|
+
classNames={classNames}
|
|
518
|
+
label={month.label}
|
|
519
|
+
labelId={labelId}
|
|
520
|
+
position={index === 0 ? "first" : "last"}
|
|
521
|
+
/>
|
|
522
|
+
)
|
|
523
|
+
: undefined
|
|
524
|
+
}
|
|
525
|
+
/>
|
|
526
|
+
))}
|
|
527
|
+
</Primitive.div>
|
|
528
|
+
{api.isWheelOpen && api.visibleRange !== "twoMonths" && (
|
|
529
|
+
<WheelView api={api} classNames={classNames} />
|
|
530
|
+
)}
|
|
531
|
+
</>
|
|
532
|
+
)}
|
|
533
|
+
<Primitive.span {...api.liveRegionProps} className={classNames.liveRegion}>
|
|
534
|
+
{api.headerLabel}
|
|
535
|
+
</Primitive.span>
|
|
536
|
+
</Primitive.div>
|
|
537
|
+
);
|
|
538
|
+
},
|
|
539
|
+
);
|
|
540
|
+
DatePickerImplementation.displayName = "DatePickerImplementation";
|
|
541
|
+
|
|
542
|
+
export const DatePicker = React.forwardRef<HTMLDivElement, DatePickerProps>((props, ref) => (
|
|
543
|
+
<DatePickerImplementation ref={ref} {...props} visibleRange="month" />
|
|
544
|
+
));
|
|
545
|
+
DatePicker.displayName = "DatePicker";
|
|
546
|
+
|
|
547
|
+
export const TwoMonthDatePicker = React.forwardRef<HTMLDivElement, TwoMonthDatePickerProps>(
|
|
548
|
+
(props, ref) => <DatePickerImplementation ref={ref} {...props} visibleRange="twoMonths" />,
|
|
549
|
+
);
|
|
550
|
+
TwoMonthDatePicker.displayName = "TwoMonthDatePicker";
|
|
551
|
+
|
|
552
|
+
export const WeekDatePicker = React.forwardRef<HTMLDivElement, WeekDatePickerProps>(
|
|
553
|
+
(props, ref) => <DatePickerImplementation ref={ref} {...props} visibleRange="week" />,
|
|
554
|
+
);
|
|
555
|
+
WeekDatePicker.displayName = "WeekDatePicker";
|
|
556
|
+
|
|
557
|
+
export const ContinuousDatePicker = React.forwardRef<HTMLDivElement, ContinuousDatePickerProps>(
|
|
558
|
+
(props, ref) => <DatePickerImplementation ref={ref} {...props} visibleRange="continuous" />,
|
|
559
|
+
);
|
|
560
|
+
ContinuousDatePicker.displayName = "ContinuousDatePicker";
|
|
561
|
+
|
|
562
|
+
export type {
|
|
563
|
+
DatePickerActions,
|
|
564
|
+
DatePickerAriaLabels,
|
|
565
|
+
DatePickerConstraint,
|
|
566
|
+
DatePickerConstraintContext,
|
|
567
|
+
DatePickerDate,
|
|
568
|
+
DatePickerMultipleProps,
|
|
569
|
+
DatePickerRangeProps,
|
|
570
|
+
DatePickerRangeValue,
|
|
571
|
+
DatePickerSelectionMode,
|
|
572
|
+
DatePickerSingleProps,
|
|
573
|
+
DatePickerValue,
|
|
574
|
+
} from "@seed-design/react-date-picker";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ContinuousDatePicker,
|
|
3
|
+
DatePicker,
|
|
4
|
+
TwoMonthDatePicker,
|
|
5
|
+
WeekDatePicker,
|
|
6
|
+
type ContinuousDatePickerProps,
|
|
7
|
+
type DatePickerActions,
|
|
8
|
+
type DatePickerAriaLabels,
|
|
9
|
+
type DatePickerCellContentRenderProps,
|
|
10
|
+
type DatePickerConstraint,
|
|
11
|
+
type DatePickerConstraintContext,
|
|
12
|
+
type DatePickerDate,
|
|
13
|
+
type DatePickerMultipleProps,
|
|
14
|
+
type DatePickerProps,
|
|
15
|
+
type DatePickerRangeProps,
|
|
16
|
+
type DatePickerRangeValue,
|
|
17
|
+
type DatePickerSelectionMode,
|
|
18
|
+
type DatePickerSingleProps,
|
|
19
|
+
type DatePickerValue,
|
|
20
|
+
type TwoMonthDatePickerProps,
|
|
21
|
+
type WeekDatePickerProps,
|
|
22
|
+
} from "./DatePicker";
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
compareDates,
|
|
26
|
+
dateKey,
|
|
27
|
+
dateOnOrAfter,
|
|
28
|
+
dateOnOrBefore,
|
|
29
|
+
excludeDates,
|
|
30
|
+
isSameDate,
|
|
31
|
+
maxSelectionCount,
|
|
32
|
+
rangeDayCountAtLeast,
|
|
33
|
+
rangeDayCountAtMost,
|
|
34
|
+
} from "@seed-design/react-date-picker";
|
|
@@ -62,6 +62,16 @@ describe("QuantityPicker", () => {
|
|
|
62
62
|
expect(container.querySelectorAll(".seed-quantity-picker__divider")).toHaveLength(1);
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
+
it("fill layout이면 부모 Flex 레이아웃의 여유 공간을 채우는 class를 적용한다", () => {
|
|
66
|
+
const { getByLabelText } = render(
|
|
67
|
+
<QuantityPicker.Root layout="fill" min={0} max={5} aria-label="수량">
|
|
68
|
+
<QuantityPicker.ValueDisplay />
|
|
69
|
+
</QuantityPicker.Root>,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(getByLabelText("수량")).toHaveClass("seed-quantity-picker__root--layout_fill");
|
|
73
|
+
});
|
|
74
|
+
|
|
65
75
|
it("최대값의 자릿수만큼 0으로 채운 숨김 요소로 값 표시 너비를 확보한다", () => {
|
|
66
76
|
const { container } = render(
|
|
67
77
|
<QuantityPicker.Root min={0} max={123} aria-label="수량">
|
|
@@ -77,8 +77,20 @@ function renderIcon(
|
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
export type QuantityPickerRootProps = QuantityPickerVariantProps &
|
|
81
|
-
QuantityPickerPrimitive.RootProps
|
|
80
|
+
export type QuantityPickerRootProps = Omit<QuantityPickerVariantProps, "size" | "layout"> &
|
|
81
|
+
QuantityPickerPrimitive.RootProps & {
|
|
82
|
+
/**
|
|
83
|
+
* 컴포넌트의 크기입니다.
|
|
84
|
+
* @default "medium"
|
|
85
|
+
*/
|
|
86
|
+
size?: QuantityPickerVariantProps["size"];
|
|
87
|
+
/**
|
|
88
|
+
* 컴포넌트가 부모 Flex 레이아웃의 여유 공간을 채울지 지정합니다.
|
|
89
|
+
* @since 2.2.0
|
|
90
|
+
* @default "hug"
|
|
91
|
+
*/
|
|
92
|
+
layout?: QuantityPickerVariantProps["layout"];
|
|
93
|
+
};
|
|
82
94
|
|
|
83
95
|
export const QuantityPickerRoot = React.forwardRef<HTMLDivElement, QuantityPickerRootProps>(
|
|
84
96
|
(props, ref) => {
|