sellmate-design-system-react 9.0.0-beta.0 → 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.
package/dist/index.cjs CHANGED
@@ -3141,6 +3141,209 @@ var SIcon = /* @__PURE__ */ react.forwardRef(function SIcon2({ name, size = 24,
3141
3141
  }
3142
3142
  );
3143
3143
  });
3144
+ var VIEWBOX = 80;
3145
+ var RADIUS = 34;
3146
+ var STROKE = 12;
3147
+ var CIRCUMFERENCE = 2 * Math.PI * RADIUS;
3148
+ var COLOR_MAP = {
3149
+ primary: { arc: "var(--cmp-progress-color-active)", track: "var(--cmp-progress-color-track)" },
3150
+ // inverse: 어두운 배경용 — arc 흰색, track 은 흰색 40%(회색 링)
3151
+ inverse: { arc: "var(--color-white)", track: "rgba(255,255,255,0.4)" },
3152
+ error: { arc: "var(--color-red-75)", track: "var(--cmp-progress-color-track)" },
3153
+ complete: { arc: "#01BB4B", track: "var(--cmp-progress-color-track)" },
3154
+ // neutral: 콘텐츠가 아직 없는 자리를 채우는 용도 — arc 도 회색이라 진행률보다 "대기"로 읽힌다
3155
+ neutral: { arc: "var(--color-grey-35)", track: "var(--cmp-progress-color-track)" }
3156
+ };
3157
+ var DEFAULT_SIZE = 80;
3158
+ var clamp = (v) => Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : 0;
3159
+ var SCircleProgress = /* @__PURE__ */ react.forwardRef(
3160
+ function SCircleProgress2({
3161
+ value = 0,
3162
+ type = "primary",
3163
+ indeterminate = false,
3164
+ size = DEFAULT_SIZE,
3165
+ label,
3166
+ innerValue = false,
3167
+ className,
3168
+ style
3169
+ }, ref) {
3170
+ const v = clamp(value);
3171
+ const dimension = typeof size === "number" ? `${size}px` : size;
3172
+ const colors = COLOR_MAP[type];
3173
+ const c = VIEWBOX / 2;
3174
+ const percentColor = v === 0 ? "var(--sys-color-fg-tertiary, #888888)" : colors.arc;
3175
+ const filled = v / 100 * CIRCUMFERENCE;
3176
+ const showPercent = !indeterminate;
3177
+ const percentNode = /* @__PURE__ */ jsxRuntime.jsxs(
3178
+ "span",
3179
+ {
3180
+ className: cn(
3181
+ "text-center text-[16px] font-bold",
3182
+ innerValue ? (
3183
+ // leading-[normal]: 26px leading 은 폰트 content(19px) 위아래로 홀수(7px)만큼 남아
3184
+ // 3px/4px 로 쪼개지고 글자가 링 중심보다 위로 뜬다. (leading-normal 은 1.5 라 동일 문제)
3185
+ "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 leading-[normal]"
3186
+ ) : "leading-[26px]"
3187
+ ),
3188
+ style: { color: percentColor },
3189
+ children: [
3190
+ Math.round(v),
3191
+ "%"
3192
+ ]
3193
+ }
3194
+ );
3195
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3196
+ "div",
3197
+ {
3198
+ ref,
3199
+ className: cn(
3200
+ "inline-flex flex-col items-center gap-[var(--cmp-progress-circular-gap)]",
3201
+ className
3202
+ ),
3203
+ style,
3204
+ children: [
3205
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(innerValue && "relative"), children: [
3206
+ /* @__PURE__ */ jsxRuntime.jsxs(
3207
+ "svg",
3208
+ {
3209
+ viewBox: `0 0 ${VIEWBOX} ${VIEWBOX}`,
3210
+ xmlns: "http://www.w3.org/2000/svg",
3211
+ className: cn(indeterminate && "origin-center animate-circle-spin"),
3212
+ style: { width: dimension, height: dimension },
3213
+ children: [
3214
+ /* @__PURE__ */ jsxRuntime.jsx(
3215
+ "circle",
3216
+ {
3217
+ cx: c,
3218
+ cy: c,
3219
+ r: RADIUS,
3220
+ fill: "none",
3221
+ strokeWidth: STROKE,
3222
+ style: { stroke: colors.track }
3223
+ }
3224
+ ),
3225
+ /* @__PURE__ */ jsxRuntime.jsx(
3226
+ "circle",
3227
+ {
3228
+ cx: c,
3229
+ cy: c,
3230
+ r: RADIUS,
3231
+ fill: "none",
3232
+ strokeWidth: STROKE,
3233
+ strokeLinecap: "round",
3234
+ className: cn(
3235
+ "origin-center",
3236
+ indeterminate ? "animate-circle-arc" : "-rotate-90 transition-[stroke-dasharray] duration-[400ms] ease-linear"
3237
+ ),
3238
+ style: {
3239
+ stroke: colors.arc,
3240
+ ...indeterminate ? {} : { strokeDasharray: `${filled} ${CIRCUMFERENCE}`, strokeDashoffset: 0 }
3241
+ }
3242
+ }
3243
+ )
3244
+ ]
3245
+ }
3246
+ ),
3247
+ showPercent && innerValue && percentNode
3248
+ ] }),
3249
+ showPercent && !innerValue && percentNode,
3250
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-center text-[12px] font-normal leading-[20px] text-[color:var(--cmp-progress-color-label)]", children: label })
3251
+ ]
3252
+ }
3253
+ );
3254
+ }
3255
+ );
3256
+ var IMAGE_FITS = ["cover", "contain", "fill", "none", "scale-down"];
3257
+ var EMPTY_ICON_SIZE = "60cqh";
3258
+ var SPINNER_SIZE = "45cqh";
3259
+ var SImage = /* @__PURE__ */ react.forwardRef(function SImage2({
3260
+ src,
3261
+ alt = "",
3262
+ ratio = 1,
3263
+ fit = "cover",
3264
+ position = "50% 50%",
3265
+ loading: loading2 = "lazy",
3266
+ loadingShowDelay = 0,
3267
+ noSpinner = false,
3268
+ loadingSlot,
3269
+ errorSlot,
3270
+ imgProps,
3271
+ onLoad,
3272
+ onError,
3273
+ className,
3274
+ style,
3275
+ ...rest
3276
+ }, ref) {
3277
+ const [status, setStatus] = react.useState("loading");
3278
+ const [spinnerVisible, setSpinnerVisible] = react.useState(loadingShowDelay <= 0);
3279
+ const [trackedSrc, setTrackedSrc] = react.useState(src);
3280
+ const imgRef = react.useRef(null);
3281
+ if (src !== trackedSrc) {
3282
+ setTrackedSrc(src);
3283
+ setStatus("loading");
3284
+ }
3285
+ react.useEffect(() => {
3286
+ if (!src) return;
3287
+ const node = imgRef.current;
3288
+ if (node?.complete && node.naturalWidth > 0) {
3289
+ setStatus((current) => current === "loading" ? "loaded" : current);
3290
+ }
3291
+ }, [src]);
3292
+ react.useEffect(() => {
3293
+ if (status !== "loading" || loadingShowDelay <= 0) {
3294
+ setSpinnerVisible(status === "loading");
3295
+ return;
3296
+ }
3297
+ setSpinnerVisible(false);
3298
+ const timer = setTimeout(() => setSpinnerVisible(true), loadingShowDelay);
3299
+ return () => clearTimeout(timer);
3300
+ }, [status, loadingShowDelay, src]);
3301
+ const isEmpty = !src;
3302
+ const showFallback = isEmpty || status === "error";
3303
+ const showSpinner = !isEmpty && status === "loading" && !noSpinner && spinnerVisible;
3304
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3305
+ "div",
3306
+ {
3307
+ ref,
3308
+ className: cn("relative overflow-hidden bg-(--cmp-image-bg)", className),
3309
+ style: { containerType: "size", aspectRatio: ratio, ...style },
3310
+ ...rest,
3311
+ children: [
3312
+ !isEmpty && /* @__PURE__ */ jsxRuntime.jsx(
3313
+ "img",
3314
+ {
3315
+ ...imgProps,
3316
+ ref: imgRef,
3317
+ src,
3318
+ alt,
3319
+ loading: loading2,
3320
+ onLoad: () => {
3321
+ setStatus("loaded");
3322
+ onLoad?.(src);
3323
+ },
3324
+ onError: (event) => {
3325
+ setStatus("error");
3326
+ onError?.(event);
3327
+ },
3328
+ className: cn(
3329
+ "h-full w-full",
3330
+ // 로딩 중에도 자리를 지켜야 브라우저가 계속 받아온다 — 숨기되 없애지 않는다
3331
+ status === "loaded" ? "visible" : "invisible",
3332
+ imgProps?.className
3333
+ ),
3334
+ style: {
3335
+ objectFit: fit,
3336
+ objectPosition: position,
3337
+ ...imgProps?.style
3338
+ }
3339
+ }
3340
+ ),
3341
+ showFallback && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-0 flex items-center justify-center", children: errorSlot ?? /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "image", size: EMPTY_ICON_SIZE, color: "var(--cmp-image-icon)" }) }),
3342
+ showSpinner && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-0 flex items-center justify-center", children: loadingSlot ?? /* @__PURE__ */ jsxRuntime.jsx(SCircleProgress, { indeterminate: true, type: "neutral", size: SPINNER_SIZE }) })
3343
+ ]
3344
+ }
3345
+ );
3346
+ });
3144
3347
  var BADGE_COLORS = [
3145
3348
  "red",
3146
3349
  "orange",
@@ -5582,14 +5785,14 @@ var SCallout = /* @__PURE__ */ react.forwardRef(function SCallout2({ type = "def
5582
5785
  );
5583
5786
  });
5584
5787
  var FILL_COLOR = {
5585
- primary: "var(--cmp-progress-active-color)",
5586
- error: "var(--cmp-progress-error-color)",
5587
- complete: "var(--cmp-progress-success-color)"
5788
+ primary: "var(--cmp-progress-color-active)",
5789
+ error: "var(--cmp-progress-color-error)",
5790
+ complete: "var(--cmp-progress-color-success)"
5588
5791
  };
5589
- var clamp = (v) => Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : 0;
5792
+ var clamp2 = (v) => Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : 0;
5590
5793
  var SLinearProgress = /* @__PURE__ */ react.forwardRef(
5591
5794
  function SLinearProgress2({ value = 0, type = "primary", indeterminate = false, label, className, style }, ref) {
5592
- const v = clamp(value);
5795
+ const v = clamp2(value);
5593
5796
  const fill = FILL_COLOR[type];
5594
5797
  const valueColor = v === 0 ? "var(--sys-color-fg-tertiary, #888888)" : fill;
5595
5798
  const valueText = `${Math.round(v)}%`;
@@ -5636,115 +5839,6 @@ var SLinearProgress = /* @__PURE__ */ react.forwardRef(
5636
5839
  );
5637
5840
  }
5638
5841
  );
5639
- var VIEWBOX = 80;
5640
- var RADIUS = 34;
5641
- var STROKE = 12;
5642
- var CIRCUMFERENCE = 2 * Math.PI * RADIUS;
5643
- var COLOR_MAP = {
5644
- primary: { arc: "var(--cmp-progress-active-color)", track: "var(--cmp-progress-color-track)" },
5645
- // inverse: 어두운 배경용 — arc 흰색, track 은 흰색 40%(회색 링)
5646
- inverse: { arc: "var(--color-white)", track: "rgba(255,255,255,0.4)" },
5647
- error: { arc: "var(--color-red-75)", track: "var(--cmp-progress-color-track)" },
5648
- complete: { arc: "#01BB4B", track: "var(--cmp-progress-color-track)" }
5649
- };
5650
- var clamp2 = (v) => Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : 0;
5651
- var SCircleProgress = /* @__PURE__ */ react.forwardRef(
5652
- function SCircleProgress2({
5653
- value = 0,
5654
- type = "primary",
5655
- indeterminate = false,
5656
- label,
5657
- innerValue = false,
5658
- className,
5659
- style
5660
- }, ref) {
5661
- const v = clamp2(value);
5662
- const colors = COLOR_MAP[type];
5663
- const c = VIEWBOX / 2;
5664
- const percentColor = v === 0 ? "var(--sys-color-fg-tertiary, #888888)" : colors.arc;
5665
- const filled = v / 100 * CIRCUMFERENCE;
5666
- const showPercent = !indeterminate;
5667
- const percentNode = /* @__PURE__ */ jsxRuntime.jsxs(
5668
- "span",
5669
- {
5670
- className: cn(
5671
- "text-center text-[16px] font-bold",
5672
- innerValue ? (
5673
- // leading-[normal]: 26px leading 은 폰트 content(19px) 위아래로 홀수(7px)만큼 남아
5674
- // 3px/4px 로 쪼개지고 글자가 링 중심보다 위로 뜬다. (leading-normal 은 1.5 라 동일 문제)
5675
- "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 leading-[normal]"
5676
- ) : "leading-[26px]"
5677
- ),
5678
- style: { color: percentColor },
5679
- children: [
5680
- Math.round(v),
5681
- "%"
5682
- ]
5683
- }
5684
- );
5685
- return /* @__PURE__ */ jsxRuntime.jsxs(
5686
- "div",
5687
- {
5688
- ref,
5689
- className: cn(
5690
- "inline-flex flex-col items-center gap-[var(--cmp-progress-circular-gap)]",
5691
- className
5692
- ),
5693
- style,
5694
- children: [
5695
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(innerValue && "relative"), children: [
5696
- /* @__PURE__ */ jsxRuntime.jsxs(
5697
- "svg",
5698
- {
5699
- viewBox: `0 0 ${VIEWBOX} ${VIEWBOX}`,
5700
- xmlns: "http://www.w3.org/2000/svg",
5701
- className: cn(
5702
- "h-[80px] w-[80px]",
5703
- indeterminate && "origin-center animate-circle-spin"
5704
- ),
5705
- children: [
5706
- /* @__PURE__ */ jsxRuntime.jsx(
5707
- "circle",
5708
- {
5709
- cx: c,
5710
- cy: c,
5711
- r: RADIUS,
5712
- fill: "none",
5713
- strokeWidth: STROKE,
5714
- style: { stroke: colors.track }
5715
- }
5716
- ),
5717
- /* @__PURE__ */ jsxRuntime.jsx(
5718
- "circle",
5719
- {
5720
- cx: c,
5721
- cy: c,
5722
- r: RADIUS,
5723
- fill: "none",
5724
- strokeWidth: STROKE,
5725
- strokeLinecap: "round",
5726
- className: cn(
5727
- "origin-center",
5728
- indeterminate ? "animate-circle-arc" : "-rotate-90 transition-[stroke-dasharray] duration-[400ms] ease-linear"
5729
- ),
5730
- style: {
5731
- stroke: colors.arc,
5732
- ...indeterminate ? {} : { strokeDasharray: `${filled} ${CIRCUMFERENCE}`, strokeDashoffset: 0 }
5733
- }
5734
- }
5735
- )
5736
- ]
5737
- }
5738
- ),
5739
- showPercent && innerValue && percentNode
5740
- ] }),
5741
- showPercent && !innerValue && percentNode,
5742
- label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-center text-[12px] font-normal leading-[20px] text-[color:var(--cmp-progress-color-label)]", children: label })
5743
- ]
5744
- }
5745
- );
5746
- }
5747
- );
5748
5842
 
5749
5843
  // src/lib/global-overlay.ts
5750
5844
  var GLOBAL_OVERLAY_ATTR = "data-s-global-overlay";
@@ -6230,10 +6324,15 @@ function SSwitch({
6230
6324
  }
6231
6325
  );
6232
6326
  }
6327
+ var SIZE_CLASSES = {
6328
+ xs: "h-[24px] px-[8px] text-[12px] font-normal leading-[20px]",
6329
+ sm: "h-[28px] px-[12px] text-[14px] font-medium leading-[24px]"
6330
+ };
6233
6331
  function SToggle({
6234
6332
  value = false,
6235
6333
  label = "",
6236
6334
  disabled = false,
6335
+ size = "sm",
6237
6336
  onValueChange,
6238
6337
  className,
6239
6338
  style
@@ -6249,7 +6348,8 @@ function SToggle({
6249
6348
  "aria-pressed": value,
6250
6349
  onClick: () => !disabled && onValueChange?.(!value),
6251
6350
  className: cn(
6252
- "box-border inline-flex h-[28px] items-center justify-center whitespace-nowrap rounded-full border border-solid px-[12px] text-[14px] font-medium leading-[24px] transition-colors",
6351
+ "box-border inline-flex items-center justify-center whitespace-nowrap rounded-full border border-solid transition-colors",
6352
+ SIZE_CLASSES[size],
6253
6353
  disabled ? "cursor-not-allowed" : "cursor-pointer",
6254
6354
  // 미선택(off) hover 시 파란 채움 + 흰 글자
6255
6355
  !disabled && !value && "hover:!bg-[var(--cmp-toggle-bg-hover)] hover:!text-[color:var(--cmp-toggle-content-hover)] hover:!border-[color:var(--cmp-toggle-bg-hover)]",
@@ -12056,12 +12156,244 @@ function SCalendar({
12056
12156
  }
12057
12157
  );
12058
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
+ }
12059
12390
  function SDatePicker({
12060
12391
  value,
12061
12392
  onValueChange,
12062
12393
  onViewChange,
12394
+ mode = "date",
12063
12395
  size = "sm",
12064
- placeholder = "YYYY-MM-DD",
12396
+ placeholder,
12065
12397
  selectable,
12066
12398
  disabled = false,
12067
12399
  width,
@@ -12087,6 +12419,7 @@ function SDatePicker({
12087
12419
  const [focused, setFocused] = react.useState(false);
12088
12420
  const [ruleError, setRuleError] = react.useState("");
12089
12421
  const hasValue = value != null && value !== "";
12422
+ const resolvedPlaceholder = placeholder ?? (mode === "year" ? "YYYY" : mode === "month" ? "YYYY-MM" : "YYYY-MM-DD");
12090
12423
  useFormField({
12091
12424
  name,
12092
12425
  validate: () => {
@@ -12098,12 +12431,34 @@ function SDatePicker({
12098
12431
  });
12099
12432
  const resolvedError = error || ruleError !== "";
12100
12433
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
12101
- 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
+ };
12102
12453
  const select = (date) => {
12103
12454
  onValueChange?.(date);
12104
12455
  if (rules && rules.length > 0) setRuleError(runRules(date, rules));
12105
12456
  setOpen(false);
12106
12457
  };
12458
+ const selectListboxValue = (nextValue) => {
12459
+ onValueChange?.(nextValue);
12460
+ if (rules && rules.length > 0) setRuleError(runRules(nextValue, rules));
12461
+ };
12107
12462
  return /* @__PURE__ */ jsxRuntime.jsx(
12108
12463
  SField,
12109
12464
  {
@@ -12122,6 +12477,7 @@ function SDatePicker({
12122
12477
  focused: focused || open,
12123
12478
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
12124
12479
  width,
12480
+ minWidth: dims.fieldMinW,
12125
12481
  disabled,
12126
12482
  className,
12127
12483
  style,
@@ -12156,7 +12512,7 @@ function SDatePicker({
12156
12512
  lineHeight: `${dims.lineHeight}px`,
12157
12513
  color: disabled ? "var(--cmp-datepicker-text-disabled)" : hasValue ? "var(--cmp-datepicker-text-default)" : "var(--cmp-field-hint-color)"
12158
12514
  },
12159
- children: hasValue ? value : placeholder
12515
+ children: hasValue ? value : resolvedPlaceholder
12160
12516
  }
12161
12517
  )
12162
12518
  ]
@@ -12172,7 +12528,7 @@ function SDatePicker({
12172
12528
  "z-50 max-w-[var(--radix-popover-content-available-width)]",
12173
12529
  FLOATING_SLIDE_ANIM
12174
12530
  ),
12175
- children: /* @__PURE__ */ jsxRuntime.jsx(
12531
+ children: mode === "date" ? /* @__PURE__ */ jsxRuntime.jsx(
12176
12532
  SCalendar,
12177
12533
  {
12178
12534
  value: value ?? void 0,
@@ -12181,6 +12537,15 @@ function SDatePicker({
12181
12537
  onValueChange: select,
12182
12538
  onViewChange
12183
12539
  }
12540
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
12541
+ DatePickerListbox,
12542
+ {
12543
+ mode,
12544
+ value,
12545
+ selectable,
12546
+ onValueChange: selectListboxValue,
12547
+ onClose: () => setOpen(false)
12548
+ }
12184
12549
  )
12185
12550
  }
12186
12551
  ) })
@@ -16402,6 +16767,7 @@ exports.GNB_MENU_WIDTH = GNB_MENU_WIDTH;
16402
16767
  exports.GNB_RAIL = GNB_RAIL;
16403
16768
  exports.GNB_RAIL_WIDTH = GNB_RAIL_WIDTH;
16404
16769
  exports.ICONS = ICONS;
16770
+ exports.IMAGE_FITS = IMAGE_FITS;
16405
16771
  exports.PAGE_BACKGROUNDS = PAGE_BACKGROUNDS;
16406
16772
  exports.RangeCalendar = RangeCalendar;
16407
16773
  exports.SActionModal = SActionModal;
@@ -16436,6 +16802,7 @@ exports.SGhostButton = SGhostButton;
16436
16802
  exports.SGnb = SGnb;
16437
16803
  exports.SGuide = SGuide;
16438
16804
  exports.SIcon = SIcon;
16805
+ exports.SImage = SImage;
16439
16806
  exports.SInput = SInput;
16440
16807
  exports.SKeyValueTable = SKeyValueTable;
16441
16808
  exports.SLAYOUT_MIN_WIDTH = SLAYOUT_MIN_WIDTH;