sellmate-design-system-react 9.0.0-beta.48 → 9.0.0-beta.49

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
@@ -4945,11 +4945,19 @@ var SChatFileImpl = /* @__PURE__ */ react.forwardRef(function SChatFile({ name,
4945
4945
  });
4946
4946
  var SChatFile2 = /* @__PURE__ */ withDisplayName(SChatFileImpl, "SChatFile");
4947
4947
  var CHAT_MESSAGE_DIRECTIONS = ["incoming", "outgoing"];
4948
- var CHAT_MESSAGE_VARIANTS = ["default", "template"];
4948
+ var CHAT_MESSAGE_VARIANTS = ["default", "template", "admin"];
4949
4949
  var BUBBLE_SURFACE = {
4950
4950
  incoming: "bg-(--cmp-chatBubble-incoming-bg) text-(--cmp-chatBubble-incoming-content)",
4951
- outgoing: "bg-(--cmp-chatBubble-outgoing-bg) text-(--cmp-chatBubble-outgoing-content)",
4952
- template: "bg-(--cmp-chatBubble-templete-bg) text-(--cmp-chatBubble-templete-content)"
4951
+ outgoing: "bg-(--cmp-chatBubble-outgoing-general-bg) text-(--cmp-chatBubble-outgoing-general-content)",
4952
+ template: "bg-(--cmp-chatBubble-templete-bg) text-(--cmp-chatBubble-templete-content)",
4953
+ // TODO: `chatBubble.outgoing.admin` 토큰이 생기면 둘 다 그쪽으로 교체한다.
4954
+ // 지금은 admin 면을 가리키는 컴포넌트 토큰이 없어 글자색만 시맨틱 토큰을 직접 쓴다.
4955
+ admin: "bg-(--cmp-chatBubble-outgoing-default-bg) text-(--sys-color-fg-deep)"
4956
+ };
4957
+ var OUTGOING_SURFACE = {
4958
+ default: "outgoing",
4959
+ template: "template",
4960
+ admin: "admin"
4953
4961
  };
4954
4962
  var CHAT_MESSAGE_READ_STATUSES = ["read", "unread"];
4955
4963
  var READ_STATUS_LABEL = {
@@ -5004,7 +5012,7 @@ var SChatMessageImpl = /* @__PURE__ */ react.forwardRef(function SChatMessage({
5004
5012
  ...rest
5005
5013
  }, ref) {
5006
5014
  const outgoing = direction === "outgoing";
5007
- const surface = direction === "outgoing" && variant === "template" ? "template" : direction;
5015
+ const surface = outgoing ? OUTGOING_SURFACE[variant] : "incoming";
5008
5016
  const bubbles = messages ?? [];
5009
5017
  const files = attachments ?? [];
5010
5018
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -17877,6 +17885,673 @@ function SKeyValueTable({
17877
17885
  )
17878
17886
  ] });
17879
17887
  }
17888
+
17889
+ // src/lib/chart/scale.ts
17890
+ function niceNum(range, round) {
17891
+ if (range <= 0) return 1;
17892
+ const exponent = Math.floor(Math.log10(range));
17893
+ const fraction = range / 10 ** exponent;
17894
+ let nice;
17895
+ if (round) {
17896
+ nice = fraction < 1.5 ? 1 : fraction < 3 ? 2 : fraction < 7 ? 5 : 10;
17897
+ } else {
17898
+ nice = fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 5 ? 5 : 10;
17899
+ }
17900
+ return nice * 10 ** exponent;
17901
+ }
17902
+ function trim(value, step) {
17903
+ const decimals = Math.max(0, -Math.floor(Math.log10(step)));
17904
+ return Number(value.toFixed(Math.min(decimals, 10)));
17905
+ }
17906
+ function niceScale(values, tickCount, fixedMax) {
17907
+ const finite = values.filter(Number.isFinite);
17908
+ const count = Math.max(2, Math.floor(tickCount));
17909
+ const rawMin = Math.min(0, ...finite);
17910
+ const rawMax = fixedMax ?? Math.max(0, ...finite);
17911
+ if (!Number.isFinite(rawMin) || !Number.isFinite(rawMax) || rawMax === rawMin) {
17912
+ const ticks2 = Array.from({ length: count }, (_, i) => i);
17913
+ return { min: 0, max: count - 1, ticks: ticks2 };
17914
+ }
17915
+ const range = niceNum(rawMax - rawMin, false);
17916
+ const step = niceNum(range / (count - 1), true);
17917
+ const min = trim(Math.floor(rawMin / step) * step, step);
17918
+ const max = trim(Math.ceil(rawMax / step) * step, step);
17919
+ const ticks = [];
17920
+ for (let v = min; v <= max + step / 2; v += step) ticks.push(trim(v, step));
17921
+ return { min, max, ticks };
17922
+ }
17923
+ function ratioOf(value, scale) {
17924
+ const span = scale.max - scale.min;
17925
+ if (span <= 0) return 0;
17926
+ return (value - scale.min) / span;
17927
+ }
17928
+ var AXIS_LABEL_GAP = 8;
17929
+ function ChartGrid({ scale, direction, width, height }) {
17930
+ const vertical = direction === "vertical";
17931
+ return /* @__PURE__ */ jsxRuntime.jsx("g", { "aria-hidden": true, children: scale.ticks.map((tick) => {
17932
+ const ratio = ratioOf(tick, scale);
17933
+ const isBaseline = tick === 0;
17934
+ const color = isBaseline ? "var(--cmp-chart-baseline-color)" : "var(--cmp-chart-grid-line-color)";
17935
+ const position = vertical ? height - ratio * height : ratio * width;
17936
+ const extent = vertical ? height : width;
17937
+ const snapped = Math.min(Math.max(Math.round(position), 0), Math.max(0, extent - 1)) + 0.5;
17938
+ return vertical ? /* @__PURE__ */ jsxRuntime.jsx(
17939
+ "line",
17940
+ {
17941
+ x1: 0,
17942
+ x2: width,
17943
+ y1: snapped,
17944
+ y2: snapped,
17945
+ stroke: color,
17946
+ strokeWidth: 1
17947
+ },
17948
+ tick
17949
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
17950
+ "line",
17951
+ {
17952
+ x1: snapped,
17953
+ x2: snapped,
17954
+ y1: 0,
17955
+ y2: height,
17956
+ stroke: color,
17957
+ strokeWidth: 1
17958
+ },
17959
+ tick
17960
+ );
17961
+ }) });
17962
+ }
17963
+ function ChartAxisLabels({
17964
+ scale,
17965
+ direction,
17966
+ format,
17967
+ className,
17968
+ style,
17969
+ sampleRef
17970
+ }) {
17971
+ const vertical = direction === "vertical";
17972
+ const widest = scale.ticks.reduce((longest, tick) => {
17973
+ const text = format(tick);
17974
+ return text.length > longest.length ? text : longest;
17975
+ }, "");
17976
+ return /* @__PURE__ */ jsxRuntime.jsxs(
17977
+ "div",
17978
+ {
17979
+ "aria-hidden": true,
17980
+ className: cn(
17981
+ "relative typo-body-sm-medium text-(--cmp-chart-axis-color)",
17982
+ vertical ? "h-full" : "w-full",
17983
+ className
17984
+ ),
17985
+ style,
17986
+ children: [
17987
+ scale.ticks.map((tick) => {
17988
+ const ratio = ratioOf(tick, scale);
17989
+ return vertical ? /* @__PURE__ */ jsxRuntime.jsx(
17990
+ "span",
17991
+ {
17992
+ className: "absolute right-0 -translate-y-1/2 whitespace-nowrap",
17993
+ style: { top: `${(1 - ratio) * 100}%` },
17994
+ children: format(tick)
17995
+ },
17996
+ tick
17997
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
17998
+ "span",
17999
+ {
18000
+ className: "absolute top-0 -translate-x-1/2 whitespace-nowrap",
18001
+ style: { left: `${ratio * 100}%` },
18002
+ children: format(tick)
18003
+ },
18004
+ tick
18005
+ );
18006
+ }),
18007
+ /* @__PURE__ */ jsxRuntime.jsx("span", { ref: sampleRef, "aria-hidden": true, className: "invisible inline-block whitespace-nowrap", children: vertical ? widest : "0" })
18008
+ ]
18009
+ }
18010
+ );
18011
+ }
18012
+ var ChartFrame = /* @__PURE__ */ react.forwardRef(function ChartFrame2({ legend, legendPosition = "top", children, className, style, ...props }, ref) {
18013
+ return /* @__PURE__ */ jsxRuntime.jsxs(
18014
+ "div",
18015
+ {
18016
+ ref,
18017
+ className: cn(
18018
+ "flex w-full flex-col gap-(--cmp-chart-gap)",
18019
+ "p-(--cmp-chart-paddingAll) bg-(--cmp-chart-bg)",
18020
+ className
18021
+ ),
18022
+ style,
18023
+ ...props,
18024
+ children: [
18025
+ legend && legendPosition === "top" && legend,
18026
+ children,
18027
+ legend && legendPosition === "bottom" && legend
18028
+ ]
18029
+ }
18030
+ );
18031
+ });
18032
+ var DOT_SIZE = 6;
18033
+ function ChartLegend({ items, align = "center", activeIndex, className }) {
18034
+ if (items.length === 0) return null;
18035
+ return /* @__PURE__ */ jsxRuntime.jsx(
18036
+ "ul",
18037
+ {
18038
+ className: cn(
18039
+ "flex list-none flex-wrap items-center gap-x-(--cmp-chart-legend-group-gap) p-0",
18040
+ align === "start" && "justify-start",
18041
+ align === "center" && "justify-center",
18042
+ align === "end" && "justify-end",
18043
+ className
18044
+ ),
18045
+ children: items.map((item4, index) => /* @__PURE__ */ jsxRuntime.jsxs(
18046
+ "li",
18047
+ {
18048
+ className: cn(
18049
+ "flex items-center gap-(--cmp-chart-legend-legendItem-gap)",
18050
+ "py-(--cmp-chart-legend-legendItem-paddingY)",
18051
+ "typo-body-sm-medium text-(--cmp-chart-legend-legendItem-color)",
18052
+ "transition-opacity",
18053
+ activeIndex != null && activeIndex !== index && "opacity-(--opacity-060)"
18054
+ ),
18055
+ children: [
18056
+ /* @__PURE__ */ jsxRuntime.jsx(
18057
+ "span",
18058
+ {
18059
+ "aria-hidden": true,
18060
+ className: "shrink-0 rounded-full",
18061
+ style: { width: DOT_SIZE, height: DOT_SIZE, background: item4.color }
18062
+ }
18063
+ ),
18064
+ item4.name
18065
+ ]
18066
+ },
18067
+ `${item4.name}-${index}`
18068
+ ))
18069
+ }
18070
+ );
18071
+ }
18072
+
18073
+ // src/lib/chart/palette.ts
18074
+ var V3 = (path) => `var(--cmp-chart-palette-${path})`;
18075
+ var CHART_COLORS = {
18076
+ positivePrimary: V3("variation-positivePrimary"),
18077
+ positiveSecondary: V3("variation-positiveSecondary"),
18078
+ negativePrimary: V3("variation-negativePrimary"),
18079
+ negativeSecondary: V3("variation-negativeSecondary"),
18080
+ neutralPrimary: V3("variation-neutralPrimary"),
18081
+ neutralSecondary: V3("variation-neutralSecondary")
18082
+ };
18083
+ var CHART_PALETTES = {
18084
+ default: [V3("single")],
18085
+ multi: [
18086
+ V3("multi-blue"),
18087
+ V3("multi-skyblue"),
18088
+ V3("multi-yellow"),
18089
+ V3("multi-olive"),
18090
+ V3("multi-lightRed"),
18091
+ V3("multi-warmBlue"),
18092
+ V3("multi-orange"),
18093
+ V3("multi-green"),
18094
+ V3("multi-navy"),
18095
+ V3("multi-grey"),
18096
+ V3("multi-red")
18097
+ ],
18098
+ gradation: [
18099
+ V3("gradation-1"),
18100
+ V3("gradation-2"),
18101
+ V3("gradation-3"),
18102
+ V3("gradation-4"),
18103
+ V3("gradation-5"),
18104
+ V3("gradation-6")
18105
+ ]
18106
+ };
18107
+ function paletteColor(palette, index, colors) {
18108
+ if (palette === "custom") {
18109
+ if (!colors?.length) return CHART_PALETTES.default[0];
18110
+ return CHART_COLORS[colors[index % colors.length]];
18111
+ }
18112
+ const list = CHART_PALETTES[palette] ?? CHART_PALETTES.default;
18113
+ return list[index % list.length];
18114
+ }
18115
+ function useElementSize() {
18116
+ const ref = react.useRef(null);
18117
+ const [size, setSize] = react.useState({ width: 0, height: 0 });
18118
+ react.useEffect(() => {
18119
+ const element = ref.current;
18120
+ if (!element) return;
18121
+ const update = () => {
18122
+ const { width, height } = element.getBoundingClientRect();
18123
+ setSize(
18124
+ (prev) => Math.abs(prev.width - width) < 0.5 && Math.abs(prev.height - height) < 0.5 ? prev : { width, height }
18125
+ );
18126
+ };
18127
+ update();
18128
+ if (typeof ResizeObserver === "undefined") return;
18129
+ const observer = new ResizeObserver(update);
18130
+ observer.observe(element);
18131
+ return () => observer.disconnect();
18132
+ }, []);
18133
+ return { ref, size };
18134
+ }
18135
+ var DOT_SIZE2 = 6;
18136
+ var CURSOR_OFFSET = 12;
18137
+ function place(cursor, size, extent) {
18138
+ if (!extent || !size) return cursor + CURSOR_OFFSET;
18139
+ const after = cursor + CURSOR_OFFSET;
18140
+ if (after + size <= extent) return after;
18141
+ const before = cursor - CURSOR_OFFSET - size;
18142
+ if (before >= 0) return before;
18143
+ return Math.max(0, Math.min(after, extent - size));
18144
+ }
18145
+ function ChartTooltip({ title, items, x, y, bounds, className }) {
18146
+ const { ref, size } = useElementSize();
18147
+ const measured = size.width > 0 && size.height > 0;
18148
+ return /* @__PURE__ */ jsxRuntime.jsxs(
18149
+ "div",
18150
+ {
18151
+ ref,
18152
+ role: "tooltip",
18153
+ className: cn(
18154
+ // left/top 대신 transform 으로 옮긴다. 좌표를 바꾸면 마우스가 움직일 때마다
18155
+ // 리플로우가 나 차트가 미세하게 흔들려 보인다. transform 은 레이아웃을
18156
+ // 건드리지 않으므로, 크기를 재기 전에 잠깐 밖으로 나가도 스크롤이 생기지 않는다.
18157
+ "pointer-events-none absolute top-0 left-0 z-10 w-max",
18158
+ "flex flex-col gap-(--cmp-chart-chartTooltip-gap)",
18159
+ "px-(--cmp-chart-chartTooltip-paddingX) py-(--cmp-chart-chartTooltip-paddingY)",
18160
+ "rounded-(--cmp-chart-chartTooltip-radius) bg-(--cmp-chart-chartTooltip-bg) shadow-floating",
18161
+ className
18162
+ ),
18163
+ style: {
18164
+ // 크기를 재기 전에는 원점에 숨겨 둔다. 커서 옆에 그려 두면 그림 밖으로
18165
+ // 넘쳐 스크롤 영역이 잠깐 늘어나고, 차트 전체가 한 번 흔들린다.
18166
+ // transform 도 스크롤 영역 계산에는 포함되므로 위치만 바꿔서는 못 막는다.
18167
+ transform: measured ? `translate(${place(x, size.width, bounds?.width ?? 0)}px, ${place(
18168
+ y,
18169
+ size.height,
18170
+ bounds?.height ?? 0
18171
+ )}px)` : void 0,
18172
+ visibility: measured ? void 0 : "hidden"
18173
+ },
18174
+ children: [
18175
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-md-bold text-(--cmp-chart-chartTooltip-title-color)", children: title }),
18176
+ items.map((item4, index) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-sd-8", children: [
18177
+ /* @__PURE__ */ jsxRuntime.jsx(
18178
+ "span",
18179
+ {
18180
+ "aria-hidden": true,
18181
+ className: "shrink-0 rounded-full",
18182
+ style: { width: DOT_SIZE2, height: DOT_SIZE2, background: item4.color }
18183
+ }
18184
+ ),
18185
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-sm-default text-(--sys-color-fg-primary)", children: item4.label }),
18186
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-sm-bold ml-auto text-(--sys-color-fg-primary)", children: item4.value })
18187
+ ] }, `${item4.label}-${index}`))
18188
+ ]
18189
+ }
18190
+ );
18191
+ }
18192
+
18193
+ // src/components/SBarChart/barChart.config.ts
18194
+ var BAR_SIZE_MIN = 24;
18195
+ var BAR_SIZE_MAX = 80;
18196
+ var BAR_GAP = 6;
18197
+ var GROUP_PADDING_MIN = 8;
18198
+ var GROUP_PADDING_MAX = 24;
18199
+ var VALUE_LABEL_GAP = 6;
18200
+ var BAR_RADIUS = 8;
18201
+ var BAR_OPACITY_DEFAULT = "var(--opacity-080)";
18202
+ var BAR_OPACITY_DIMMED = "var(--opacity-060)";
18203
+ var BAR_VALUE_COLOR = "var(--sys-color-field-text-readOnly)";
18204
+ function bandLayout(size, categoryCount, seriesCount, barSizeMax = BAR_SIZE_MAX) {
18205
+ const categories = Math.max(1, categoryCount);
18206
+ const series = Math.max(1, seriesCount);
18207
+ const gaps = (series - 1) * BAR_GAP;
18208
+ const barRange = BAR_SIZE_MAX - BAR_SIZE_MIN;
18209
+ const padRange = GROUP_PADDING_MAX - GROUP_PADDING_MIN;
18210
+ const maxBar = Math.min(Math.max(BAR_SIZE_MIN, barSizeMax), BAR_SIZE_MAX);
18211
+ const tMax = (maxBar - BAR_SIZE_MIN) / barRange;
18212
+ const bandMin = BAR_SIZE_MIN * series + gaps + GROUP_PADDING_MIN * 2;
18213
+ const span = barRange * series + padRange * 2;
18214
+ const band = Math.min(size / categories, bandMin + span * tMax);
18215
+ const t = Math.min(Math.max((band - bandMin) / span, 0), tMax);
18216
+ const bar = BAR_SIZE_MIN + barRange * t;
18217
+ const padding = GROUP_PADDING_MIN + padRange * t;
18218
+ const group = bar * series + gaps;
18219
+ return {
18220
+ band,
18221
+ bar,
18222
+ group,
18223
+ padding,
18224
+ offset: Math.max(0, (size - band * categories) / 2)
18225
+ };
18226
+ }
18227
+ var defaultFormat = (value) => value.toLocaleString();
18228
+ function minBand(seriesCount) {
18229
+ const series = Math.max(1, seriesCount);
18230
+ return series * BAR_SIZE_MIN + (series - 1) * BAR_GAP + GROUP_PADDING_MIN * 2;
18231
+ }
18232
+ function barPath(x, y, width, height, direction, negative) {
18233
+ const r = Math.max(0, Math.min(BAR_RADIUS, width / 2, height / 2));
18234
+ if (r === 0) return `M${x} ${y}h${width}v${height}h${-width}Z`;
18235
+ if (direction === "vertical") {
18236
+ return negative ? (
18237
+ // 아래로 자란다 — 아래쪽 두 모서리가 둥글다
18238
+ `M${x} ${y}h${width}v${height - r}a${r} ${r} 0 0 1 ${-r} ${r}h${-(width - 2 * r)}a${r} ${r} 0 0 1 ${-r} ${-r}Z`
18239
+ ) : (
18240
+ // 위로 자란다 — 위쪽 두 모서리가 둥글다
18241
+ `M${x} ${y + height}v${-(height - r)}a${r} ${r} 0 0 1 ${r} ${-r}h${width - 2 * r}a${r} ${r} 0 0 1 ${r} ${r}v${height - r}Z`
18242
+ );
18243
+ }
18244
+ return negative ? (
18245
+ // 왼쪽으로 자란다
18246
+ `M${x + width} ${y}h${-(width - r)}a${r} ${r} 0 0 0 ${-r} ${r}v${height - 2 * r}a${r} ${r} 0 0 0 ${r} ${r}h${width - r}Z`
18247
+ ) : (
18248
+ // 오른쪽으로 자란다
18249
+ `M${x} ${y}h${width - r}a${r} ${r} 0 0 1 ${r} ${r}v${height - 2 * r}a${r} ${r} 0 0 1 ${-r} ${r}h${-(width - r)}Z`
18250
+ );
18251
+ }
18252
+ var SBarChart = /* @__PURE__ */ react.forwardRef(function SBarChart2({
18253
+ categories,
18254
+ series,
18255
+ direction = "vertical",
18256
+ palette = "default",
18257
+ colors: customColors,
18258
+ max,
18259
+ tickCount = 8,
18260
+ barSize = BAR_SIZE_MAX,
18261
+ showValueLabel = true,
18262
+ showLegend = true,
18263
+ legendPosition = "top",
18264
+ showTooltip = true,
18265
+ formatValue = defaultFormat,
18266
+ height,
18267
+ onBarClick,
18268
+ className,
18269
+ style
18270
+ }, ref) {
18271
+ const vertical = direction === "vertical";
18272
+ const { ref: plotRef, size } = useElementSize();
18273
+ const { ref: axisTextRef, size: axisText } = useElementSize();
18274
+ const { ref: axisSampleRef, size: axisSample } = useElementSize();
18275
+ const [hovered, setHovered] = react.useState(null);
18276
+ const hoveredCategory = hovered?.category ?? null;
18277
+ const perCategory = series.length === 1;
18278
+ const colorAt = react.useCallback(
18279
+ (seriesIndex, categoryIndex) => paletteColor(palette, perCategory ? categoryIndex : seriesIndex, customColors),
18280
+ [palette, customColors, perCategory]
18281
+ );
18282
+ const scale = react.useMemo(
18283
+ () => niceScale(
18284
+ series.flatMap((s) => s.data),
18285
+ tickCount,
18286
+ max
18287
+ ),
18288
+ [series, tickCount, max]
18289
+ );
18290
+ const axisLength = vertical ? size.width : size.height;
18291
+ const layout = react.useMemo(
18292
+ () => bandLayout(axisLength, categories.length, series.length, barSize),
18293
+ [axisLength, categories.length, series.length, barSize]
18294
+ );
18295
+ const zeroRatio = ratioOf(0, scale);
18296
+ const minPlotWidth = vertical ? categories.length * minBand(series.length) : 0;
18297
+ const totalHeight = height ?? (vertical ? 320 : void 0);
18298
+ const plotHeight = totalHeight == null ? Math.max(1, categories.length) * minBand(series.length) : typeof totalHeight === "number" ? Math.max(0, totalHeight - axisText.height) : `calc(${totalHeight} - ${axisText.height}px)`;
18299
+ const legend = showLegend ? /* @__PURE__ */ jsxRuntime.jsx(ChartLegend, { items: series.map((s, i) => ({ name: s.name, color: colorAt(i, 0) })) }) : null;
18300
+ const handleMove = (event) => {
18301
+ if (!showTooltip || categories.length === 0 || series.length === 0) return;
18302
+ const rect = event.currentTarget.getBoundingClientRect();
18303
+ const x = event.clientX - rect.left;
18304
+ const y = event.clientY - rect.top;
18305
+ const along = (vertical ? x : y) - layout.offset;
18306
+ const category = Math.floor(along / layout.band);
18307
+ if (category < 0 || category >= categories.length) {
18308
+ setHovered(null);
18309
+ return;
18310
+ }
18311
+ setHovered({ category, x, y });
18312
+ };
18313
+ const tooltipItems = hovered == null ? [] : series.flatMap((s, i) => {
18314
+ const value = s.data[hovered.category];
18315
+ if (value == null || !Number.isFinite(value)) return [];
18316
+ return [
18317
+ { color: colorAt(i, hovered.category), label: s.name, value: formatValue(value) }
18318
+ ];
18319
+ });
18320
+ const categoryLabels = /* @__PURE__ */ jsxRuntime.jsxs(
18321
+ "div",
18322
+ {
18323
+ "aria-hidden": true,
18324
+ className: cn(
18325
+ "relative typo-body-sm-medium text-(--cmp-chart-axis-color)",
18326
+ vertical ? "w-full" : "h-full"
18327
+ ),
18328
+ style: vertical ? { minWidth: minPlotWidth || void 0 } : { height: plotHeight },
18329
+ children: [
18330
+ categories.map((label, index) => {
18331
+ const center = layout.offset + (index + 0.5) * layout.band;
18332
+ return vertical ? /* @__PURE__ */ jsxRuntime.jsx(
18333
+ "span",
18334
+ {
18335
+ className: "absolute top-0 -translate-x-1/2 whitespace-nowrap",
18336
+ style: { left: center },
18337
+ children: label
18338
+ },
18339
+ `${label}-${index}`
18340
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
18341
+ "span",
18342
+ {
18343
+ className: "absolute right-0 -translate-y-1/2 whitespace-nowrap",
18344
+ style: { top: center },
18345
+ children: label
18346
+ },
18347
+ `${label}-${index}`
18348
+ );
18349
+ }),
18350
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "invisible block whitespace-nowrap", children: vertical ? "0" : categories.reduce((longest, c) => c.length > longest.length ? c : longest, "") })
18351
+ ]
18352
+ }
18353
+ );
18354
+ const bars = react.useMemo(
18355
+ () => size.width > 0 && size.height > 0 ? series.flatMap(
18356
+ (s, seriesIndex) => categories.map((_, categoryIndex) => {
18357
+ const value = s.data[categoryIndex];
18358
+ if (value == null || !Number.isFinite(value)) return null;
18359
+ const dimmed = hoveredCategory != null && hoveredCategory !== categoryIndex;
18360
+ const groupStart = layout.offset + categoryIndex * layout.band + (layout.band - layout.group) / 2;
18361
+ const offset = groupStart + seriesIndex * (layout.bar + BAR_GAP);
18362
+ const valueRatio = ratioOf(value, scale);
18363
+ const negative = value < 0;
18364
+ let x;
18365
+ let y;
18366
+ let width;
18367
+ let barHeight;
18368
+ let labelX;
18369
+ let labelY;
18370
+ if (vertical) {
18371
+ const zeroY = size.height - zeroRatio * size.height;
18372
+ const valueY = size.height - valueRatio * size.height;
18373
+ x = offset;
18374
+ width = layout.bar;
18375
+ y = Math.min(zeroY, valueY);
18376
+ barHeight = Math.abs(zeroY - valueY);
18377
+ labelX = offset + layout.bar / 2;
18378
+ labelY = negative ? y + barHeight + VALUE_LABEL_GAP + 10 : y - VALUE_LABEL_GAP;
18379
+ } else {
18380
+ const zeroX = zeroRatio * size.width;
18381
+ const valueX = valueRatio * size.width;
18382
+ x = Math.min(zeroX, valueX);
18383
+ width = Math.abs(zeroX - valueX);
18384
+ y = offset;
18385
+ barHeight = layout.bar;
18386
+ labelX = negative ? x - VALUE_LABEL_GAP : x + width + VALUE_LABEL_GAP;
18387
+ labelY = offset + layout.bar / 2;
18388
+ }
18389
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
18390
+ /* @__PURE__ */ jsxRuntime.jsx(
18391
+ "path",
18392
+ {
18393
+ d: barPath(x, y, width, barHeight, direction, negative),
18394
+ style: {
18395
+ fill: colorAt(seriesIndex, categoryIndex),
18396
+ opacity: dimmed ? BAR_OPACITY_DIMMED : BAR_OPACITY_DEFAULT,
18397
+ cursor: onBarClick ? "pointer" : void 0
18398
+ },
18399
+ onClick: onBarClick ? () => onBarClick({ seriesIndex, categoryIndex, value }) : void 0,
18400
+ children: /* @__PURE__ */ jsxRuntime.jsx("title", { children: `${categories[categoryIndex]} ${s.name} ${formatValue(value)}` })
18401
+ }
18402
+ ),
18403
+ showValueLabel && /* @__PURE__ */ jsxRuntime.jsx(
18404
+ "text",
18405
+ {
18406
+ x: labelX,
18407
+ y: labelY,
18408
+ className: "typo-body-sm-medium",
18409
+ style: { fill: BAR_VALUE_COLOR },
18410
+ textAnchor: vertical ? "middle" : negative ? "end" : "start",
18411
+ dominantBaseline: vertical ? "auto" : "central",
18412
+ children: formatValue(value)
18413
+ }
18414
+ )
18415
+ ] }, `${seriesIndex}-${categoryIndex}`);
18416
+ })
18417
+ ) : [],
18418
+ [
18419
+ size.width,
18420
+ size.height,
18421
+ series,
18422
+ categories,
18423
+ layout,
18424
+ scale,
18425
+ zeroRatio,
18426
+ vertical,
18427
+ direction,
18428
+ showValueLabel,
18429
+ formatValue,
18430
+ onBarClick,
18431
+ colorAt,
18432
+ hoveredCategory
18433
+ ]
18434
+ );
18435
+ const axisLabels = /* @__PURE__ */ jsxRuntime.jsx(
18436
+ ChartAxisLabels,
18437
+ {
18438
+ scale,
18439
+ direction,
18440
+ format: formatValue,
18441
+ sampleRef: axisSampleRef
18442
+ }
18443
+ );
18444
+ const inset = vertical ? { paddingTop: axisSample.height / 2, paddingBottom: axisSample.height / 2 } : { paddingLeft: axisSample.width / 2, paddingRight: axisSample.width / 2 };
18445
+ const plot = (
18446
+ // 바깥은 자리(높이·물러남)를, 안쪽은 그림을 맡는다. minWidth 는 바깥에 둬야
18447
+ // 스크롤 컨테이너가 넘치는 폭을 알아본다 — 안쪽에 두면 바깥이 부모 폭에 맞춰져
18448
+ // 스크롤이 생기지 않는다.
18449
+ /* @__PURE__ */ jsxRuntime.jsx(
18450
+ "div",
18451
+ {
18452
+ className: "relative min-w-0",
18453
+ style: { height: plotHeight, minWidth: minPlotWidth || void 0, ...inset },
18454
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
18455
+ "div",
18456
+ {
18457
+ ref: plotRef,
18458
+ className: "relative h-full w-full",
18459
+ onMouseMove: handleMove,
18460
+ onMouseLeave: () => setHovered(null),
18461
+ children: [
18462
+ size.width > 0 && size.height > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
18463
+ "svg",
18464
+ {
18465
+ width: size.width,
18466
+ height: size.height,
18467
+ role: "img",
18468
+ "aria-label": `${categories.length}\uAC1C \uD56D\uBAA9, ${series.length}\uAC1C \uACC4\uC5F4 \uB9C9\uB300 \uADF8\uB798\uD504`,
18469
+ children: [
18470
+ /* @__PURE__ */ jsxRuntime.jsx(
18471
+ ChartGrid,
18472
+ {
18473
+ scale,
18474
+ direction,
18475
+ width: size.width,
18476
+ height: size.height
18477
+ }
18478
+ ),
18479
+ hovered != null && /* @__PURE__ */ jsxRuntime.jsx(
18480
+ "rect",
18481
+ {
18482
+ "aria-hidden": true,
18483
+ x: vertical ? layout.offset + hovered.category * layout.band : 0,
18484
+ y: vertical ? 0 : layout.offset + hovered.category * layout.band,
18485
+ width: vertical ? layout.band : size.width,
18486
+ height: vertical ? size.height : layout.band,
18487
+ style: {
18488
+ fill: "var(--cmp-chart-barContainer-hover-bg)",
18489
+ fillOpacity: "var(--cmp-chart-barContainer-hover-opacity)"
18490
+ }
18491
+ }
18492
+ ),
18493
+ bars
18494
+ ]
18495
+ }
18496
+ ),
18497
+ showTooltip && hovered != null && series.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
18498
+ ChartTooltip,
18499
+ {
18500
+ title: categories[hovered.category] ?? "",
18501
+ items: tooltipItems,
18502
+ x: hovered.x,
18503
+ y: hovered.y,
18504
+ bounds: size
18505
+ }
18506
+ )
18507
+ ]
18508
+ }
18509
+ )
18510
+ }
18511
+ )
18512
+ );
18513
+ return /* @__PURE__ */ jsxRuntime.jsx(
18514
+ ChartFrame,
18515
+ {
18516
+ ref,
18517
+ legend,
18518
+ legendPosition,
18519
+ className,
18520
+ style,
18521
+ children: /* @__PURE__ */ jsxRuntime.jsx(
18522
+ "div",
18523
+ {
18524
+ className: "grid",
18525
+ style: {
18526
+ columnGap: AXIS_LABEL_GAP,
18527
+ // 눈금은 왼쪽 한 줄뿐이다 — 오른쪽에도 적으면 서로 다른 단위처럼 읽힌다.
18528
+ gridTemplateColumns: "max-content 1fr"
18529
+ },
18530
+ children: vertical ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
18531
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: plotHeight, ...inset }, children: /* @__PURE__ */ jsxRuntime.jsx(
18532
+ ChartAxisLabels,
18533
+ {
18534
+ scale,
18535
+ direction,
18536
+ format: formatValue,
18537
+ sampleRef: axisSampleRef
18538
+ }
18539
+ ) }),
18540
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 overflow-x-auto", children: [
18541
+ plot,
18542
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: axisTextRef, children: categoryLabels })
18543
+ ] })
18544
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
18545
+ categoryLabels,
18546
+ plot,
18547
+ /* @__PURE__ */ jsxRuntime.jsx("div", {}),
18548
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: axisTextRef, style: inset, children: axisLabels })
18549
+ ] })
18550
+ }
18551
+ )
18552
+ }
18553
+ );
18554
+ });
17880
18555
  var BUTTON_STYLE = {
17881
18556
  default: { color: "secondary" },
17882
18557
  danger: { color: "danger", outline: true },
@@ -22775,15 +23450,15 @@ var SBarcodeInput = /* @__PURE__ */ react.forwardRef(
22775
23450
  );
22776
23451
 
22777
23452
  // src/components/STimePicker/timepicker.config.ts
22778
- var V3 = (path) => `var(--cmp-timepicker-${path})`;
23453
+ var V4 = (path) => `var(--cmp-timepicker-${path})`;
22779
23454
  var TIMEPICKER_SIZES = ["sm", "md"];
22780
23455
  var TIMEPICKER_SIZE_CONFIG = {
22781
23456
  sm: {
22782
- height: V3("sm-height"),
22783
- paddingX: V3("sm-paddingX"),
22784
- gap: V3("sm-gap"),
22785
- icon: V3("sm-icon"),
22786
- radius: V3("sm-radius"),
23457
+ height: V4("sm-height"),
23458
+ paddingX: V4("sm-paddingX"),
23459
+ gap: V4("sm-gap"),
23460
+ icon: V4("sm-icon"),
23461
+ radius: V4("sm-radius"),
22787
23462
  // TODO: component.timepicker 토큰에 typography가 추가되면 CSS 변수로 교체한다.
22788
23463
  fontSize: 12,
22789
23464
  lineHeight: 20,
@@ -22792,11 +23467,11 @@ var TIMEPICKER_SIZE_CONFIG = {
22792
23467
  fieldMaxWidth: "md"
22793
23468
  },
22794
23469
  md: {
22795
- height: V3("md-height"),
22796
- paddingX: V3("md-paddingX"),
22797
- gap: V3("md-gap"),
22798
- icon: V3("md-icon"),
22799
- radius: V3("md-radius"),
23470
+ height: V4("md-height"),
23471
+ paddingX: V4("md-paddingX"),
23472
+ gap: V4("md-gap"),
23473
+ icon: V4("md-icon"),
23474
+ radius: V4("md-radius"),
22800
23475
  // TODO: component.timepicker 토큰에 typography가 추가되면 CSS 변수로 교체한다.
22801
23476
  fontSize: 14,
22802
23477
  lineHeight: 24,
@@ -23692,6 +24367,9 @@ exports.ACCOUNT_LIST_BOX_LABELS = ACCOUNT_LIST_BOX_LABELS;
23692
24367
  exports.ACCOUNT_LIST_BOX_LAYOUT = ACCOUNT_LIST_BOX_LAYOUT;
23693
24368
  exports.ACCOUNT_LIST_BOX_TONE_CONFIG = ACCOUNT_LIST_BOX_TONE_CONFIG;
23694
24369
  exports.BADGE_COLORS = BADGE_COLORS;
24370
+ exports.BAR_GAP = BAR_GAP;
24371
+ exports.BAR_SIZE_MAX = BAR_SIZE_MAX;
24372
+ exports.BAR_SIZE_MIN = BAR_SIZE_MIN;
23695
24373
  exports.BUTTON_COLORS = BUTTON_COLORS;
23696
24374
  exports.BUTTON_SIZES = BUTTON_SIZES;
23697
24375
  exports.CALENDAR_BOARD_DAY_BG = CALENDAR_BOARD_DAY_BG;
@@ -23730,6 +24408,7 @@ exports.RangeCalendar = RangeCalendar;
23730
24408
  exports.SAccountListBox = SAccountListBox;
23731
24409
  exports.SActionModal = SActionModal;
23732
24410
  exports.SBadge = SBadge;
24411
+ exports.SBarChart = SBarChart;
23733
24412
  exports.SBarcodeInput = SBarcodeInput;
23734
24413
  exports.SButton = SButton;
23735
24414
  exports.SCROLL_AREA_AXES = SCROLL_AREA_AXES;