sellmate-design-system-react 9.0.0-beta.2 → 9.0.0-beta.3

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.
@@ -8,9 +8,10 @@
8
8
 
9
9
  | Prop | Type | Default | Description |
10
10
  |------|------|---------|-------------|
11
- | `value?` | `string \| null` | — | 선택 날짜 (YYYY-MM-DD) |
11
+ | `value?` | `string \| null` | — | 선택 (date: YYYY-MM-DD, month: YYYY-MM, year: YYYY) |
12
+ | `mode?` | `SDatePickerMode` | `'date'` | 선택 모드 |
12
13
  | `size?` | `SDatePickerSize` | `'sm'` | |
13
- | `placeholder?` | `string` | `'YYYY-MM-DD'` | |
14
+ | `placeholder?` | `string` | | |
14
15
  | `selectable?` | `[string, string]` | — | 선택 가능 범위 [시작, 종료] |
15
16
  | `disabled?` | `boolean` | `false` | |
16
17
  | `width?` | `number \| string` | — | |
@@ -35,7 +36,7 @@
35
36
 
36
37
  | Event | Type | Description |
37
38
  |-------|------|-------------|
38
- | `onValueChange` | `(date: string) => void` | 선택 변경 (sdUpdate) |
39
+ | `onValueChange` | `(value: string) => void` | 선택 변경 (sdUpdate) |
39
40
  | `onViewChange` | `(view: { year: number; month: number }) => void` | 보이는 연·월 변경 (sdViewChange) |
40
41
 
41
42
  ## Dependencies
@@ -5,16 +5,19 @@ import { type SColor } from '../../lib/color';
5
5
  import { type Rule } from '../../lib/form';
6
6
  import { type STooltipProps } from '../STooltip';
7
7
  export type SDatePickerSize = SFieldSize;
8
+ export type SDatePickerMode = 'date' | 'month' | 'year';
8
9
  export interface SDatePickerProps {
9
- /** 선택 날짜 (YYYY-MM-DD) */
10
+ /** 선택 (date: YYYY-MM-DD, month: YYYY-MM, year: YYYY) */
10
11
  value?: string | null;
11
12
  /** 선택 변경 (sdUpdate) */
12
- onValueChange?: (date: string) => void;
13
+ onValueChange?: (value: string) => void;
13
14
  /** 보이는 연·월 변경 (sdViewChange) */
14
15
  onViewChange?: (view: {
15
16
  year: number;
16
17
  month: number;
17
18
  }) => void;
19
+ /** 선택 모드 */
20
+ mode?: SDatePickerMode;
18
21
  size?: SDatePickerSize;
19
22
  placeholder?: string;
20
23
  /** 선택 가능 범위 [시작, 종료] */
@@ -39,4 +42,4 @@ export interface SDatePickerProps {
39
42
  style?: CSSProperties;
40
43
  }
41
44
  /** SDatePicker — sd-date-picker 포팅. SField + SPopover(SCalendar). */
42
- export declare function SDatePicker({ value, onValueChange, onViewChange, size, placeholder, selectable, disabled, width, name, rules, status, label, labelWidth, icon, iconColor, labelTooltip, labelTooltipProps, addonLabel, addonAlign, hint, error, errorMessage, className, style, }: SDatePickerProps): import("react").JSX.Element;
45
+ export declare function SDatePicker({ value, onValueChange, onViewChange, mode, size, placeholder, selectable, disabled, width, name, rules, status, label, labelWidth, icon, iconColor, labelTooltip, labelTooltipProps, addonLabel, addonAlign, hint, error, errorMessage, className, style, }: SDatePickerProps): import("react").JSX.Element;
@@ -1 +1 @@
1
- export { SDatePicker, type SDatePickerProps, type SDatePickerSize } from './SDatePicker';
1
+ export { SDatePicker, type SDatePickerMode, type SDatePickerProps, type SDatePickerSize, } from './SDatePicker';
package/dist/index.cjs CHANGED
@@ -12156,12 +12156,244 @@ function SCalendar({
12156
12156
  }
12157
12157
  );
12158
12158
  }
12159
+ var DATEPICKER_MODE_LIST_HEIGHT = 240;
12160
+ var DATEPICKER_MODE_ITEM_HEIGHT = 28;
12161
+ var YEAR_LIST_EDGE_BUFFER = 24;
12162
+ var YEAR_LIST_EXPAND_SIZE = 80;
12163
+ var MONTHS = Array.from({ length: 12 }, (_, i) => i + 1);
12164
+ var getTodayParts = () => {
12165
+ const today = /* @__PURE__ */ new Date();
12166
+ return {
12167
+ year: today.getFullYear(),
12168
+ month: today.getMonth() + 1
12169
+ };
12170
+ };
12171
+ var parseYear = (value) => {
12172
+ const year = Number(value?.slice(0, 4));
12173
+ return Number.isFinite(year) && year > 0 ? year : void 0;
12174
+ };
12175
+ var parseMonth = (value) => {
12176
+ const month = Number(value?.slice(5, 7));
12177
+ return Number.isFinite(month) && month >= 1 && month <= 12 ? month : void 0;
12178
+ };
12179
+ var formatMonthValue = (year, month) => `${year}-${String(month).padStart(2, "0")}`;
12180
+ var isYearDisabled = (year, selectable) => {
12181
+ if (!selectable) return false;
12182
+ const [start, end] = selectable;
12183
+ if (start !== "" && year < Number(start.slice(0, 4))) return true;
12184
+ if (end !== "" && year > Number(end.slice(0, 4))) return true;
12185
+ return false;
12186
+ };
12187
+ var isMonthDisabled = (year, month, selectable) => {
12188
+ if (!selectable) return false;
12189
+ const value = formatMonthValue(year, month);
12190
+ const [start, end] = selectable;
12191
+ if (start !== "" && value < start.slice(0, 7)) return true;
12192
+ if (end !== "" && value > end.slice(0, 7)) return true;
12193
+ return false;
12194
+ };
12195
+ function YearWheel({ value, anchorYear, selectable, onCommit }) {
12196
+ const listRef = react.useRef(null);
12197
+ const syncingScrollRef = react.useRef(false);
12198
+ const anchorYearRef = react.useRef(anchorYear);
12199
+ const [range, setRange] = react.useState(() => ({
12200
+ start: anchorYear - YEAR_LIST_EXPAND_SIZE,
12201
+ end: anchorYear + YEAR_LIST_EXPAND_SIZE
12202
+ }));
12203
+ const rangeStartRef = react.useRef(range.start);
12204
+ rangeStartRef.current = range.start;
12205
+ const years = Array.from({ length: range.end - range.start + 1 }, (_, i) => range.start + i);
12206
+ react.useLayoutEffect(() => {
12207
+ const list = listRef.current;
12208
+ if (!list) return;
12209
+ const index = anchorYearRef.current - rangeStartRef.current;
12210
+ syncingScrollRef.current = true;
12211
+ list.scrollTop = index * DATEPICKER_MODE_ITEM_HEIGHT;
12212
+ requestAnimationFrame(() => {
12213
+ syncingScrollRef.current = false;
12214
+ });
12215
+ }, []);
12216
+ const handleScroll = () => {
12217
+ const list = listRef.current;
12218
+ if (!list) return;
12219
+ if (syncingScrollRef.current) return;
12220
+ if (list.scrollTop < DATEPICKER_MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
12221
+ const previousHeight = list.scrollHeight;
12222
+ setRange((current) => ({
12223
+ start: current.start - YEAR_LIST_EXPAND_SIZE,
12224
+ end: current.end
12225
+ }));
12226
+ requestAnimationFrame(() => {
12227
+ if (!listRef.current) return;
12228
+ syncingScrollRef.current = true;
12229
+ listRef.current.scrollTop += listRef.current.scrollHeight - previousHeight;
12230
+ requestAnimationFrame(() => {
12231
+ syncingScrollRef.current = false;
12232
+ });
12233
+ });
12234
+ return;
12235
+ }
12236
+ if (list.scrollHeight - list.scrollTop - list.clientHeight < DATEPICKER_MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
12237
+ setRange((current) => ({
12238
+ start: current.start,
12239
+ end: current.end + YEAR_LIST_EXPAND_SIZE
12240
+ }));
12241
+ }
12242
+ };
12243
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]", children: /* @__PURE__ */ jsxRuntime.jsx(
12244
+ "div",
12245
+ {
12246
+ ref: listRef,
12247
+ role: "listbox",
12248
+ "aria-label": "year",
12249
+ className: "h-full overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
12250
+ style: {
12251
+ paddingTop: `calc((${DATEPICKER_MODE_LIST_HEIGHT}px - ${DATEPICKER_MODE_ITEM_HEIGHT}px) / 2)`,
12252
+ paddingBottom: `calc((${DATEPICKER_MODE_LIST_HEIGHT}px - ${DATEPICKER_MODE_ITEM_HEIGHT}px) / 2)`
12253
+ },
12254
+ onScroll: handleScroll,
12255
+ children: years.map((year) => {
12256
+ const selected = year === value;
12257
+ const disabled = isYearDisabled(year, selectable);
12258
+ return /* @__PURE__ */ jsxRuntime.jsx(
12259
+ "button",
12260
+ {
12261
+ type: "button",
12262
+ role: "option",
12263
+ "aria-selected": selected,
12264
+ disabled,
12265
+ className: cn(
12266
+ "flex w-full items-center justify-center gap-[var(--cmp-datepicker-mode-item-gap)] border-0 px-[var(--cmp-datepicker-mode-item-paddingX)] py-[var(--cmp-datepicker-mode-item-paddingY)] text-center font-base text-12 leading-[20px]",
12267
+ disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
12268
+ !disabled && !selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-default)] hover:bg-[var(--cmp-datepicker-mode-item-bg-hover)] hover:text-[color:var(--cmp-datepicker-mode-item-content-hover)]",
12269
+ !disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
12270
+ ),
12271
+ onClick: () => !disabled && onCommit(year),
12272
+ children: year
12273
+ },
12274
+ year
12275
+ );
12276
+ })
12277
+ }
12278
+ ) });
12279
+ }
12280
+ function MonthWheel({ year, value, selectable, onCommit }) {
12281
+ const listRef = react.useRef(null);
12282
+ const initialValueRef = react.useRef(value);
12283
+ react.useLayoutEffect(() => {
12284
+ const list = listRef.current;
12285
+ if (!list) return;
12286
+ list.scrollTop = Math.max(
12287
+ 0,
12288
+ (initialValueRef.current - 1) * DATEPICKER_MODE_ITEM_HEIGHT - (list.clientHeight - DATEPICKER_MODE_ITEM_HEIGHT) / 2
12289
+ );
12290
+ }, []);
12291
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]", children: /* @__PURE__ */ jsxRuntime.jsx(
12292
+ "div",
12293
+ {
12294
+ ref: listRef,
12295
+ role: "listbox",
12296
+ "aria-label": "month",
12297
+ className: "h-full overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
12298
+ children: MONTHS.map((month) => {
12299
+ const selected = month === value;
12300
+ const disabled = isMonthDisabled(year, month, selectable);
12301
+ return /* @__PURE__ */ jsxRuntime.jsxs(
12302
+ "button",
12303
+ {
12304
+ type: "button",
12305
+ role: "option",
12306
+ "aria-selected": selected,
12307
+ disabled,
12308
+ className: cn(
12309
+ "flex w-full items-center justify-center gap-[var(--cmp-datepicker-mode-item-gap)] border-0 px-[var(--cmp-datepicker-mode-item-paddingX)] py-[var(--cmp-datepicker-mode-item-paddingY)] text-center font-base text-12 leading-[20px]",
12310
+ disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
12311
+ !disabled && !selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-default)] hover:bg-[var(--cmp-datepicker-mode-item-bg-hover)] hover:text-[color:var(--cmp-datepicker-mode-item-content-hover)]",
12312
+ !disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
12313
+ ),
12314
+ onClick: () => !disabled && onCommit(month),
12315
+ children: [
12316
+ month,
12317
+ "\uC6D4"
12318
+ ]
12319
+ },
12320
+ month
12321
+ );
12322
+ })
12323
+ }
12324
+ ) });
12325
+ }
12326
+ function DatePickerListbox({
12327
+ mode,
12328
+ value,
12329
+ selectable,
12330
+ onValueChange,
12331
+ onClose
12332
+ }) {
12333
+ const today = getTodayParts();
12334
+ const [year, setYear] = react.useState(() => parseYear(value) ?? today.year);
12335
+ const [month, setMonth] = react.useState(() => parseMonth(value) ?? today.month);
12336
+ const commitYear = (nextYear) => {
12337
+ setYear(nextYear);
12338
+ if (mode === "year") {
12339
+ onValueChange?.(String(nextYear));
12340
+ onClose();
12341
+ return;
12342
+ }
12343
+ onValueChange?.(formatMonthValue(nextYear, month));
12344
+ };
12345
+ const commitMonth = (nextMonth) => {
12346
+ setMonth(nextMonth);
12347
+ onValueChange?.(formatMonthValue(year, nextMonth));
12348
+ onClose();
12349
+ };
12350
+ return /* @__PURE__ */ jsxRuntime.jsxs(
12351
+ "div",
12352
+ {
12353
+ className: cn(
12354
+ "inline-flex h-[240px] overflow-hidden rounded-[var(--cmp-datepicker-mode-radius)] bg-[var(--cmp-datepicker-mode-bg)] shadow-normal-sm",
12355
+ mode === "month" ? "gap-[var(--cmp-datepicker-mode-yearMonth-gap)]" : "w-[var(--cmp-datepicker-mode-year-width)]"
12356
+ ),
12357
+ children: [
12358
+ /* @__PURE__ */ jsxRuntime.jsx(
12359
+ "div",
12360
+ {
12361
+ className: cn(
12362
+ "min-w-0",
12363
+ mode === "month" ? "w-[var(--cmp-datepicker-mode-yearMonth-column-width)]" : "w-full"
12364
+ ),
12365
+ children: /* @__PURE__ */ jsxRuntime.jsx(
12366
+ YearWheel,
12367
+ {
12368
+ value: year,
12369
+ anchorYear: today.year,
12370
+ selectable,
12371
+ onCommit: commitYear
12372
+ }
12373
+ )
12374
+ }
12375
+ ),
12376
+ mode === "month" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
12377
+ /* @__PURE__ */ jsxRuntime.jsx(
12378
+ "div",
12379
+ {
12380
+ "aria-hidden": true,
12381
+ className: "w-[var(--border-width-1)] self-stretch bg-[var(--sys-color-divider-default)]"
12382
+ }
12383
+ ),
12384
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 w-[var(--cmp-datepicker-mode-yearMonth-column-width)]", children: /* @__PURE__ */ jsxRuntime.jsx(MonthWheel, { year, value: month, selectable, onCommit: commitMonth }) })
12385
+ ] })
12386
+ ]
12387
+ }
12388
+ );
12389
+ }
12159
12390
  function SDatePicker({
12160
12391
  value,
12161
12392
  onValueChange,
12162
12393
  onViewChange,
12394
+ mode = "date",
12163
12395
  size = "sm",
12164
- placeholder = "YYYY-MM-DD",
12396
+ placeholder,
12165
12397
  selectable,
12166
12398
  disabled = false,
12167
12399
  width,
@@ -12187,6 +12419,7 @@ function SDatePicker({
12187
12419
  const [focused, setFocused] = react.useState(false);
12188
12420
  const [ruleError, setRuleError] = react.useState("");
12189
12421
  const hasValue = value != null && value !== "";
12422
+ const resolvedPlaceholder = placeholder ?? (mode === "year" ? "YYYY" : mode === "month" ? "YYYY-MM" : "YYYY-MM-DD");
12190
12423
  useFormField({
12191
12424
  name,
12192
12425
  validate: () => {
@@ -12198,12 +12431,34 @@ function SDatePicker({
12198
12431
  });
12199
12432
  const resolvedError = error || ruleError !== "";
12200
12433
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
12201
- const dims = size === "md" ? { padX: 16, gap: 12, icon: 20, minW: 104, fontSize: 14, lineHeight: 24, radius: 6 } : { padX: 12, gap: 8, icon: 16, minW: 80, fontSize: 12, lineHeight: 20, radius: 4 };
12434
+ const dims = size === "md" ? {
12435
+ padX: 16,
12436
+ gap: 12,
12437
+ icon: 20,
12438
+ minW: 104,
12439
+ fieldMinW: 168,
12440
+ fontSize: 14,
12441
+ lineHeight: 24,
12442
+ radius: 6
12443
+ } : {
12444
+ padX: 12,
12445
+ gap: 8,
12446
+ icon: 16,
12447
+ minW: 80,
12448
+ fieldMinW: 128,
12449
+ fontSize: 12,
12450
+ lineHeight: 20,
12451
+ radius: 4
12452
+ };
12202
12453
  const select = (date) => {
12203
12454
  onValueChange?.(date);
12204
12455
  if (rules && rules.length > 0) setRuleError(runRules(date, rules));
12205
12456
  setOpen(false);
12206
12457
  };
12458
+ const selectListboxValue = (nextValue) => {
12459
+ onValueChange?.(nextValue);
12460
+ if (rules && rules.length > 0) setRuleError(runRules(nextValue, rules));
12461
+ };
12207
12462
  return /* @__PURE__ */ jsxRuntime.jsx(
12208
12463
  SField,
12209
12464
  {
@@ -12222,6 +12477,7 @@ function SDatePicker({
12222
12477
  focused: focused || open,
12223
12478
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
12224
12479
  width,
12480
+ minWidth: dims.fieldMinW,
12225
12481
  disabled,
12226
12482
  className,
12227
12483
  style,
@@ -12256,7 +12512,7 @@ function SDatePicker({
12256
12512
  lineHeight: `${dims.lineHeight}px`,
12257
12513
  color: disabled ? "var(--cmp-datepicker-text-disabled)" : hasValue ? "var(--cmp-datepicker-text-default)" : "var(--cmp-field-hint-color)"
12258
12514
  },
12259
- children: hasValue ? value : placeholder
12515
+ children: hasValue ? value : resolvedPlaceholder
12260
12516
  }
12261
12517
  )
12262
12518
  ]
@@ -12272,7 +12528,7 @@ function SDatePicker({
12272
12528
  "z-50 max-w-[var(--radix-popover-content-available-width)]",
12273
12529
  FLOATING_SLIDE_ANIM
12274
12530
  ),
12275
- children: /* @__PURE__ */ jsxRuntime.jsx(
12531
+ children: mode === "date" ? /* @__PURE__ */ jsxRuntime.jsx(
12276
12532
  SCalendar,
12277
12533
  {
12278
12534
  value: value ?? void 0,
@@ -12281,6 +12537,15 @@ function SDatePicker({
12281
12537
  onValueChange: select,
12282
12538
  onViewChange
12283
12539
  }
12540
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
12541
+ DatePickerListbox,
12542
+ {
12543
+ mode,
12544
+ value,
12545
+ selectable,
12546
+ onValueChange: selectListboxValue,
12547
+ onClose: () => setOpen(false)
12548
+ }
12284
12549
  )
12285
12550
  }
12286
12551
  ) })