sellmate-design-system-react 9.0.0-beta.47 → 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/AGENTS.md +21 -5
- package/dist/components/SBarChart/README.md +104 -0
- package/dist/components/SBarChart/SBarChart.d.ts +75 -0
- package/dist/components/SBarChart/barChart.config.d.ts +65 -0
- package/dist/components/SBarChart/index.d.ts +2 -0
- package/dist/components/SChatAttachedFile/README.md +3 -4
- package/dist/components/SChatAttachedFile/SChatAttachedFile.d.ts +3 -44
- package/dist/components/SChatAttachedFile/chatAttachedFile.config.d.ts +19 -39
- package/dist/components/SChatAttachedFile/index.d.ts +1 -1
- package/dist/components/SChatFile/README.md +33 -0
- package/dist/components/SChatFile/SChatFile.d.ts +37 -0
- package/dist/components/SChatFile/chatFile.config.d.ts +56 -0
- package/dist/components/SChatFile/index.d.ts +2 -0
- package/dist/components/SChatMessage/README.md +9 -5
- package/dist/components/SChatMessage/SChatMessage.d.ts +22 -7
- package/dist/components/SGhostButton/README.md +2 -0
- package/dist/components/SIcon/README.md +2 -0
- package/dist/index.cjs +944 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +939 -132
- package/dist/index.js.map +1 -1
- package/dist/lib/chart/cartesian.d.ts +46 -0
- package/dist/lib/chart/frame.d.ts +17 -0
- package/dist/lib/chart/legend.d.ts +15 -0
- package/dist/lib/chart/palette.d.ts +26 -0
- package/dist/lib/chart/scale.d.ts +26 -0
- package/dist/lib/chart/tooltip.d.ts +23 -0
- package/dist/lib/chart/types.d.ts +29 -0
- package/dist/lib/chart/use-element-size.d.ts +17 -0
- package/dist/llms-full.txt +171 -16
- package/dist/llms.txt +25 -10
- package/dist/styles.css +347 -40
- package/dist/theme.css +118 -7
- package/eslint/scale.gen.mjs +2 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4681,10 +4681,9 @@ var STextarea = /* @__PURE__ */ react.forwardRef(function STextarea2({
|
|
|
4681
4681
|
);
|
|
4682
4682
|
});
|
|
4683
4683
|
var DEFAULT_PLACEHOLDER = "\uBA54\uC2DC\uC9C0\uB97C \uC785\uB825\uD574 \uC8FC\uC138\uC694...";
|
|
4684
|
-
var FIELD_MIN_HEIGHT = 48;
|
|
4685
4684
|
var FIELD_BORDER = 1;
|
|
4686
|
-
var TEXTAREA_MIN_HEIGHT =
|
|
4687
|
-
var BUTTON_BOTTOM_INSET = `calc((
|
|
4685
|
+
var TEXTAREA_MIN_HEIGHT = `calc(var(--cmp-chatInput-buttonFrame-height) - ${FIELD_BORDER * 2}px)`;
|
|
4686
|
+
var BUTTON_BOTTOM_INSET = `calc((var(--cmp-chatInput-buttonFrame-height) - var(--cmp-ghostButton-md-size)) / 2)`;
|
|
4688
4687
|
var DEFAULT_MAX_ROWS = 5;
|
|
4689
4688
|
var SChatInputImpl = /* @__PURE__ */ react.forwardRef(function SChatInput({
|
|
4690
4689
|
value = "",
|
|
@@ -4713,8 +4712,8 @@ var SChatInputImpl = /* @__PURE__ */ react.forwardRef(function SChatInput({
|
|
|
4713
4712
|
e.preventDefault();
|
|
4714
4713
|
send();
|
|
4715
4714
|
};
|
|
4716
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col bg-(--
|
|
4717
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-
|
|
4715
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col bg-(--cmp-chatInput-bg)", className), style, children: [
|
|
4716
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-(--cmp-chatInput-gap) p-(--cmp-chatInput-padding)", children: [
|
|
4718
4717
|
onAttach != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4719
4718
|
SGhostButton,
|
|
4720
4719
|
{
|
|
@@ -4776,81 +4775,67 @@ var SChatInputImpl = /* @__PURE__ */ react.forwardRef(function SChatInput({
|
|
|
4776
4775
|
var SChatInput2 = /* @__PURE__ */ withDisplayName(SChatInputImpl, "SChatInput");
|
|
4777
4776
|
|
|
4778
4777
|
// src/components/SChatAttachedFile/chatAttachedFile.config.ts
|
|
4779
|
-
var CHAT_ATTACHED_FILE_VARIANTS = ["attached", "sent"];
|
|
4780
4778
|
var CHAT_ATTACHED_FILE_CONFIG = {
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4779
|
+
/** 바깥 상자 — 좌우·상하 여백·면·선·라운드 */
|
|
4780
|
+
box: "px-(--cmp-chatAttachedFile-paddingX) py-(--cmp-chatAttachedFile-paddingY) rounded-(--cmp-chatAttachedFile-radius) border-(length:--cmp-chatAttachedFile-border-width) border-(--cmp-chatAttachedFile-border-color) bg-(--cmp-chatAttachedFile-bg)",
|
|
4781
|
+
/** 콘텐츠 묶음 ↔ 제거 버튼 간격 */
|
|
4782
|
+
outerGap: "gap-(--cmp-chatAttachedFile-gap)",
|
|
4783
|
+
/** 아이콘 칩 ↔ 파일명 간격 */
|
|
4784
|
+
contentGap: "gap-(--cmp-chatAttachedFile-content-gap)",
|
|
4785
|
+
/** 클립 아이콘이 앉는 칩 — 내부 패딩·면·라운드 (높이는 아이콘+패딩이 정한다) */
|
|
4786
|
+
tile: "p-(--cmp-chatAttachedFile-icon-wrapper-padding) rounded-(--cmp-chatAttachedFile-icon-wrapper-radius) bg-(--cmp-chatAttachedFile-icon-wrapper-bg)",
|
|
4787
|
+
/** 칩 안 클립 아이콘 크기(px). SVG width/height 는 속성값이라 CSS 변수를 못 받아 값으로 둔다 (토큰 값과 동일) */
|
|
4788
|
+
iconSize: 16,
|
|
4789
|
+
/** 파일명 타이포 프리셋 */
|
|
4790
|
+
nameTypo: "typo-body-sm-default",
|
|
4791
|
+
/** 파일명 텍스트색 */
|
|
4792
|
+
textColor: "text-(--cmp-chatAttachedFile-filename-color)"
|
|
4794
4793
|
};
|
|
4795
|
-
var SIZE_UNITS = ["B", "KB", "MB", "GB", "TB"];
|
|
4796
|
-
function formatFileSize(bytes) {
|
|
4797
|
-
if (!Number.isFinite(bytes) || bytes < 0) return "";
|
|
4798
|
-
let value = bytes;
|
|
4799
|
-
let unit = 0;
|
|
4800
|
-
while (value >= 1024 && unit < SIZE_UNITS.length - 1) {
|
|
4801
|
-
value /= 1024;
|
|
4802
|
-
unit += 1;
|
|
4803
|
-
}
|
|
4804
|
-
return unit === 0 ? `${Math.round(value)}${SIZE_UNITS[0]}` : `${value.toFixed(1)}${SIZE_UNITS[unit]}`;
|
|
4805
|
-
}
|
|
4806
|
-
function normalizeExtension(extension) {
|
|
4807
|
-
return extension.trim().replace(/^\.+/, "").toLowerCase();
|
|
4808
|
-
}
|
|
4809
|
-
function fileExtension(name) {
|
|
4810
|
-
const dot = name.lastIndexOf(".");
|
|
4811
|
-
return dot > 0 && dot < name.length - 1 ? name.slice(dot + 1).toLowerCase() : "";
|
|
4812
|
-
}
|
|
4813
4794
|
var SChatAttachedFileImpl = /* @__PURE__ */ react.forwardRef(
|
|
4814
|
-
function SChatAttachedFile({
|
|
4815
|
-
const preset = CHAT_ATTACHED_FILE_CONFIG[variant];
|
|
4816
|
-
const ext = extension != null ? normalizeExtension(extension) : fileExtension(name);
|
|
4817
|
-
const meta = [ext, size != null ? formatFileSize(size) : ""].filter(Boolean).join(" / ");
|
|
4818
|
-
const showMeta = variant === "sent" && meta !== "";
|
|
4819
|
-
const showRemove = variant === "attached" && onRemove != null;
|
|
4795
|
+
function SChatAttachedFile({ name, onRemove, className, ...rest }, ref) {
|
|
4820
4796
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4821
4797
|
"div",
|
|
4822
4798
|
{
|
|
4823
4799
|
ref,
|
|
4824
4800
|
className: cn(
|
|
4825
|
-
"inline-flex max-w-full items-center
|
|
4826
|
-
|
|
4801
|
+
"inline-flex max-w-full items-center border-solid",
|
|
4802
|
+
CHAT_ATTACHED_FILE_CONFIG.box,
|
|
4803
|
+
CHAT_ATTACHED_FILE_CONFIG.outerGap,
|
|
4827
4804
|
className
|
|
4828
4805
|
),
|
|
4829
4806
|
...rest,
|
|
4830
4807
|
children: [
|
|
4831
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4808
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("flex min-w-0 items-center", CHAT_ATTACHED_FILE_CONFIG.contentGap), children: [
|
|
4809
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4810
|
+
"span",
|
|
4811
|
+
{
|
|
4812
|
+
className: cn(
|
|
4813
|
+
"inline-grid shrink-0 place-items-center text-(--sys-color-fg-accentLight)",
|
|
4814
|
+
CHAT_ATTACHED_FILE_CONFIG.tile
|
|
4815
|
+
),
|
|
4816
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "attachFile", color: "grey_65", size: CHAT_ATTACHED_FILE_CONFIG.iconSize })
|
|
4817
|
+
}
|
|
4818
|
+
),
|
|
4819
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4820
|
+
"span",
|
|
4821
|
+
{
|
|
4822
|
+
className: cn(
|
|
4823
|
+
"truncate",
|
|
4824
|
+
CHAT_ATTACHED_FILE_CONFIG.nameTypo,
|
|
4825
|
+
CHAT_ATTACHED_FILE_CONFIG.textColor
|
|
4826
|
+
),
|
|
4827
|
+
children: name
|
|
4828
|
+
}
|
|
4829
|
+
)
|
|
4845
4830
|
] }),
|
|
4846
|
-
|
|
4847
|
-
|
|
4831
|
+
onRemove != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4832
|
+
SGhostButton,
|
|
4848
4833
|
{
|
|
4849
|
-
|
|
4850
|
-
"
|
|
4834
|
+
icon: "close",
|
|
4835
|
+
size: "xs",
|
|
4836
|
+
ariaLabel: `${name} \uC81C\uAC70`,
|
|
4851
4837
|
onClick: onRemove,
|
|
4852
|
-
className: "
|
|
4853
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "close", size: 16 })
|
|
4838
|
+
className: "shrink-0"
|
|
4854
4839
|
}
|
|
4855
4840
|
)
|
|
4856
4841
|
]
|
|
@@ -4859,12 +4844,120 @@ var SChatAttachedFileImpl = /* @__PURE__ */ react.forwardRef(
|
|
|
4859
4844
|
}
|
|
4860
4845
|
);
|
|
4861
4846
|
var SChatAttachedFile2 = /* @__PURE__ */ withDisplayName(SChatAttachedFileImpl, "SChatAttachedFile");
|
|
4847
|
+
|
|
4848
|
+
// src/components/SChatFile/chatFile.config.ts
|
|
4849
|
+
var CHAT_FILE_CONFIG = {
|
|
4850
|
+
/** 바깥 상자 — 좌우 비대칭 여백·상하 여백·면·선·라운드 */
|
|
4851
|
+
box: "pl-(--cmp-chatFile-paddingLeft) pr-(--cmp-chatFile-paddingRight) py-(--cmp-chatFile-paddingY) rounded-(--cmp-chatFile-radius) border-(length:--cmp-chatFile-border-width) border-(--cmp-chatFile-border-color) bg-(--cmp-chatFile-bg-default)",
|
|
4852
|
+
/** 아이콘 칩 ↔ 파일명 간격 */
|
|
4853
|
+
contentGap: "gap-(--cmp-chatFile-gap)",
|
|
4854
|
+
/**
|
|
4855
|
+
* 클립 아이콘이 앉는 칩 — 내부 패딩·면·라운드 (높이는 아이콘+패딩이 정한다).
|
|
4856
|
+
*
|
|
4857
|
+
* hover 면은 상자가 아니라 여기에도 걸린다 — 카드가 어두워질 때 칩은 반대로 밝아져
|
|
4858
|
+
* 칩이 배경에 묻히지 않는다. 상자의 hover 를 따라가야 하므로 group-hover 로 받는다.
|
|
4859
|
+
*
|
|
4860
|
+
* **`--sys-color-bg-frame` 은 임시다.** 시안의 hover 칩 색(`#FFFFFF`)에 대응하는
|
|
4861
|
+
* 컴포넌트 토큰이 없다 — `component.chatMessage.chatFile.icon.wrapper.bg` 는 값이 하나뿐이라
|
|
4862
|
+
* hover 를 담지 못한다. 상자의 `bg.default` 가 참조하는 것과 같은 시스템 토큰으로 대신 걸어 둔다.
|
|
4863
|
+
* 디자인팀에 `icon.wrapper.bg.hover` 를 요청해 나오는 대로 갈아끼운다.
|
|
4864
|
+
*/
|
|
4865
|
+
tile: "p-(--cmp-chatFile-icon-wrapper-padding) rounded-(--cmp-chatFile-icon-wrapper-radius) bg-(--cmp-chatFile-icon-wrapper-bg) group-hover/chat-file:bg-(--sys-color-bg-frame)",
|
|
4866
|
+
/** 칩 안 클립 아이콘 크기(px). SVG width/height 는 속성값이라 CSS 변수를 못 받아 값으로 둔다 (토큰 값과 동일) */
|
|
4867
|
+
iconSize: 20,
|
|
4868
|
+
/** 파일명 타이포 프리셋 */
|
|
4869
|
+
nameTypo: "typo-body-md-bold",
|
|
4870
|
+
/** 파일명·부가정보 텍스트색 */
|
|
4871
|
+
textColor: "text-(--cmp-chatFile-text-color)",
|
|
4872
|
+
/**
|
|
4873
|
+
* 눌리는 모양 — 커서·hover 면·포커스 링. 보낸 파일은 언제나 열 수 있으므로 늘 붙는다
|
|
4874
|
+
* (`onFileClick` 이 필수다).
|
|
4875
|
+
*
|
|
4876
|
+
* 상자의 여백·면·선과 따로 두는 이유: 이건 상호작용을 드러내는 값이라 성격이 다르다.
|
|
4877
|
+
* 링은 `SExpansionItem`·`SStepper` 와 같은 accent outline.
|
|
4878
|
+
*/
|
|
4879
|
+
interactive: "cursor-pointer hover:bg-(--cmp-chatFile-bg-hover) focus-visible:outline focus-visible:outline-2 focus-visible:outline-(--sys-color-border-accent)"
|
|
4880
|
+
};
|
|
4881
|
+
var SIZE_UNITS = ["B", "KB", "MB", "GB", "TB"];
|
|
4882
|
+
function formatFileSize(bytes) {
|
|
4883
|
+
if (!Number.isFinite(bytes) || bytes < 0) return "";
|
|
4884
|
+
let value = bytes;
|
|
4885
|
+
let unit = 0;
|
|
4886
|
+
while (value >= 1024 && unit < SIZE_UNITS.length - 1) {
|
|
4887
|
+
value /= 1024;
|
|
4888
|
+
unit += 1;
|
|
4889
|
+
}
|
|
4890
|
+
return unit === 0 ? `${Math.round(value)}${SIZE_UNITS[0]}` : `${value.toFixed(1)}${SIZE_UNITS[unit]}`;
|
|
4891
|
+
}
|
|
4892
|
+
function normalizeExtension(extension) {
|
|
4893
|
+
return extension.trim().replace(/^\.+/, "").toLowerCase();
|
|
4894
|
+
}
|
|
4895
|
+
function fileExtension(name) {
|
|
4896
|
+
const dot = name.lastIndexOf(".");
|
|
4897
|
+
return dot > 0 && dot < name.length - 1 ? name.slice(dot + 1).toLowerCase() : "";
|
|
4898
|
+
}
|
|
4899
|
+
var SChatFileImpl = /* @__PURE__ */ react.forwardRef(function SChatFile({ name, extension, size, onFileClick, className, ...rest }, ref) {
|
|
4900
|
+
const ext = extension != null ? normalizeExtension(extension) : fileExtension(name);
|
|
4901
|
+
const meta = [ext, size != null ? formatFileSize(size) : ""].filter(Boolean).join(" / ");
|
|
4902
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4903
|
+
"div",
|
|
4904
|
+
{
|
|
4905
|
+
ref,
|
|
4906
|
+
className: cn(
|
|
4907
|
+
// group/chat-file: 아이콘 칩이 상자의 hover 를 따라가야 해서 이름을 붙인다 (config.tile 참고)
|
|
4908
|
+
"group/chat-file inline-flex max-w-full items-center border-solid",
|
|
4909
|
+
CHAT_FILE_CONFIG.box,
|
|
4910
|
+
CHAT_FILE_CONFIG.contentGap,
|
|
4911
|
+
CHAT_FILE_CONFIG.interactive,
|
|
4912
|
+
className
|
|
4913
|
+
),
|
|
4914
|
+
...rest,
|
|
4915
|
+
role: "button",
|
|
4916
|
+
tabIndex: 0,
|
|
4917
|
+
onClick: (event) => {
|
|
4918
|
+
rest.onClick?.(event);
|
|
4919
|
+
onFileClick?.();
|
|
4920
|
+
},
|
|
4921
|
+
onKeyDown: (event) => {
|
|
4922
|
+
rest.onKeyDown?.(event);
|
|
4923
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
4924
|
+
event.preventDefault();
|
|
4925
|
+
onFileClick?.();
|
|
4926
|
+
},
|
|
4927
|
+
children: [
|
|
4928
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4929
|
+
"span",
|
|
4930
|
+
{
|
|
4931
|
+
className: cn(
|
|
4932
|
+
"inline-grid shrink-0 place-items-center text-(--sys-color-fg-accentLight)",
|
|
4933
|
+
CHAT_FILE_CONFIG.tile
|
|
4934
|
+
),
|
|
4935
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "attachFile", size: CHAT_FILE_CONFIG.iconSize })
|
|
4936
|
+
}
|
|
4937
|
+
),
|
|
4938
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col", children: [
|
|
4939
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("truncate", CHAT_FILE_CONFIG.nameTypo, CHAT_FILE_CONFIG.textColor), children: name }),
|
|
4940
|
+
meta !== "" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("typo-body-xs-default truncate", CHAT_FILE_CONFIG.textColor), children: meta })
|
|
4941
|
+
] })
|
|
4942
|
+
]
|
|
4943
|
+
}
|
|
4944
|
+
);
|
|
4945
|
+
});
|
|
4946
|
+
var SChatFile2 = /* @__PURE__ */ withDisplayName(SChatFileImpl, "SChatFile");
|
|
4862
4947
|
var CHAT_MESSAGE_DIRECTIONS = ["incoming", "outgoing"];
|
|
4863
|
-
var CHAT_MESSAGE_VARIANTS = ["default", "template"];
|
|
4948
|
+
var CHAT_MESSAGE_VARIANTS = ["default", "template", "admin"];
|
|
4864
4949
|
var BUBBLE_SURFACE = {
|
|
4865
|
-
incoming: "bg-(--
|
|
4866
|
-
outgoing: "bg-(--
|
|
4867
|
-
template: "bg-(--
|
|
4950
|
+
incoming: "bg-(--cmp-chatBubble-incoming-bg) text-(--cmp-chatBubble-incoming-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"
|
|
4868
4961
|
};
|
|
4869
4962
|
var CHAT_MESSAGE_READ_STATUSES = ["read", "unread"];
|
|
4870
4963
|
var READ_STATUS_LABEL = {
|
|
@@ -4872,8 +4965,8 @@ var READ_STATUS_LABEL = {
|
|
|
4872
4965
|
unread: "\uC548 \uC77D\uC74C"
|
|
4873
4966
|
};
|
|
4874
4967
|
var READ_STATUS_COLOR = {
|
|
4875
|
-
read: "text-(--
|
|
4876
|
-
unread: "text-(--
|
|
4968
|
+
read: "text-(--cmp-chatTimeStamp-color)",
|
|
4969
|
+
unread: "text-(--cmp-chatReadMark-color)"
|
|
4877
4970
|
};
|
|
4878
4971
|
var AVATAR_SIZE = 32;
|
|
4879
4972
|
var AVATAR_ROW_HEIGHT = 40;
|
|
@@ -4885,7 +4978,7 @@ function Avatar({ src, alt, placeholder }) {
|
|
|
4885
4978
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 items-center", style: { height: AVATAR_ROW_HEIGHT }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4886
4979
|
"div",
|
|
4887
4980
|
{
|
|
4888
|
-
className: "relative overflow-hidden rounded-
|
|
4981
|
+
className: "relative overflow-hidden rounded-(--cmp-chatAvatar-radius) bg-(--cmp-chatAvatar-bg)",
|
|
4889
4982
|
style: { width: AVATAR_SIZE, height: AVATAR_SIZE },
|
|
4890
4983
|
children: src != null ? /* @__PURE__ */ jsxRuntime.jsx(SImage, { src, alt: alt ?? "", fit: "cover", className: "h-full w-full" }) : (
|
|
4891
4984
|
// 원 밖으로 나가는 만큼을 좌우로 나눠 가운데를 맞추고, 아래는 원이 자른다.
|
|
@@ -4894,7 +4987,7 @@ function Avatar({ src, alt, placeholder }) {
|
|
|
4894
4987
|
{
|
|
4895
4988
|
name: "user",
|
|
4896
4989
|
size: AVATAR_ICON_SIZE,
|
|
4897
|
-
color: "var(--
|
|
4990
|
+
color: "var(--cmp-chatAvatar-icon)",
|
|
4898
4991
|
className: "absolute top-0",
|
|
4899
4992
|
style: { left: (AVATAR_SIZE - AVATAR_ICON_SIZE) / 2 }
|
|
4900
4993
|
}
|
|
@@ -4914,12 +5007,14 @@ var SChatMessageImpl = /* @__PURE__ */ react.forwardRef(function SChatMessage({
|
|
|
4914
5007
|
variant = "default",
|
|
4915
5008
|
actions,
|
|
4916
5009
|
messages,
|
|
5010
|
+
attachments,
|
|
4917
5011
|
className,
|
|
4918
5012
|
...rest
|
|
4919
5013
|
}, ref) {
|
|
4920
5014
|
const outgoing = direction === "outgoing";
|
|
4921
|
-
const surface =
|
|
5015
|
+
const surface = outgoing ? OUTGOING_SURFACE[variant] : "incoming";
|
|
4922
5016
|
const bubbles = messages ?? [];
|
|
5017
|
+
const files = attachments ?? [];
|
|
4923
5018
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4924
5019
|
"div",
|
|
4925
5020
|
{
|
|
@@ -4927,45 +5022,77 @@ var SChatMessageImpl = /* @__PURE__ */ react.forwardRef(function SChatMessage({
|
|
|
4927
5022
|
className: cn(
|
|
4928
5023
|
// 아바타와 본문 열이 같은 줄에 서고, 보낸 메시지는 좌우를 뒤집는다.
|
|
4929
5024
|
// items-start 라 이름이 있든 없든 아바타는 언제나 첫 줄에 붙는다.
|
|
4930
|
-
"flex items-start gap-
|
|
5025
|
+
"flex items-start gap-(--cmp-chatMessage-avatarGap)",
|
|
4931
5026
|
outgoing && "flex-row-reverse",
|
|
4932
5027
|
className
|
|
4933
5028
|
),
|
|
4934
5029
|
...rest,
|
|
4935
5030
|
children: [
|
|
4936
5031
|
!outgoing && /* @__PURE__ */ jsxRuntime.jsx(Avatar, { src: avatarSrc, alt: avatarAlt, placeholder: continued }),
|
|
4937
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
5032
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5033
|
+
"div",
|
|
5034
|
+
{
|
|
5035
|
+
className: cn(
|
|
5036
|
+
"flex min-w-0 flex-col gap-(--cmp-chatMessage-contents-gap)",
|
|
5037
|
+
outgoing ? "items-end" : "items-start"
|
|
5038
|
+
),
|
|
5039
|
+
children: [
|
|
5040
|
+
!outgoing && !continued && sender != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-sm-bold px-(--cmp-chatMessage-name-paddingX) text-(--cmp-chatMessage-name-color)", children: sender }),
|
|
5041
|
+
bubbles.length > 0 && // 말풍선끼리는 이름·시각과의 간격(4px)보다 벌려 한 덩어리 안의 여러 발화로 읽히게 한다.
|
|
5042
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4945
5043
|
"div",
|
|
4946
5044
|
{
|
|
4947
5045
|
className: cn(
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
// 한글은 기본 규칙으로 두면 어절 한가운데서도 줄이 바뀐다 — 띄어쓰기에서만 끊고,
|
|
4951
|
-
// 끊을 자리가 없는 긴 토큰(URL·주문번호)만 흘려보낸다.
|
|
4952
|
-
"break-keep wrap-break-word",
|
|
4953
|
-
BUBBLE_SURFACE[surface]
|
|
5046
|
+
"flex min-w-0 flex-col gap-(--cmp-chatMessage-group-bubble-gap)",
|
|
5047
|
+
outgoing ? "items-end" : "items-start"
|
|
4954
5048
|
),
|
|
4955
|
-
children: message
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
5049
|
+
children: bubbles.map((message, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
5050
|
+
"div",
|
|
5051
|
+
{
|
|
5052
|
+
className: cn(
|
|
5053
|
+
"typo-body-md-medium max-w-full rounded-(--cmp-chatBubble-radius) px-(--cmp-chatBubble-paddingX) py-(--cmp-chatBubble-paddingY)",
|
|
5054
|
+
// 한글은 기본 규칙으로 두면 어절 한가운데서도 줄이 바뀐다 — 띄어쓰기에서만 끊고,
|
|
5055
|
+
// 끊을 자리가 없는 긴 토큰(URL·주문번호)만 흘려보낸다.
|
|
5056
|
+
"break-keep wrap-break-word",
|
|
5057
|
+
BUBBLE_SURFACE[surface]
|
|
5058
|
+
),
|
|
5059
|
+
children: message
|
|
5060
|
+
},
|
|
5061
|
+
index
|
|
5062
|
+
))
|
|
5063
|
+
}
|
|
5064
|
+
),
|
|
5065
|
+
files.length > 0 && // 파일 칸끼리는 말풍선과 같은 간격으로 벌린다 — 한 덩어리 안의 여러 발화로 읽힌다.
|
|
5066
|
+
// 말풍선과 달리 면으로 감싸지 않는다 (`attachments` 주석 참고).
|
|
5067
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5068
|
+
"div",
|
|
5069
|
+
{
|
|
5070
|
+
className: cn(
|
|
5071
|
+
"flex min-w-0 flex-col gap-(--cmp-chatMessage-group-bubble-gap)",
|
|
5072
|
+
outgoing ? "items-end" : "items-start"
|
|
5073
|
+
),
|
|
5074
|
+
children: files.map((file, index) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-full", children: file }, index))
|
|
5075
|
+
}
|
|
5076
|
+
),
|
|
5077
|
+
(readStatus != null || time != null) && // 읽음 표시와 시각은 한 줄에 선다 — 읽음이 먼저, 시각이 뒤다.
|
|
5078
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-(--cmp-chatMessage-subData-gap) px-(--cmp-chatMessage-subData-paddingX)", children: [
|
|
5079
|
+
readStatus != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("typo-body-sm-bold", READ_STATUS_COLOR[readStatus]), children: READ_STATUS_LABEL[readStatus] }),
|
|
5080
|
+
time != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-sm-medium text-(--cmp-chatTimeStamp-color)", children: time })
|
|
5081
|
+
] }),
|
|
5082
|
+
actions != null && // 버튼이 많아 한 줄에 안 들어가면 감싼다 — 넘쳐서 잘리는 것보다 낫다.
|
|
5083
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5084
|
+
"div",
|
|
5085
|
+
{
|
|
5086
|
+
className: cn(
|
|
5087
|
+
"flex flex-wrap gap-sd-8 px-(--cmp-chatMessage-bottomButtons-paddingX)",
|
|
5088
|
+
outgoing && "justify-end"
|
|
5089
|
+
),
|
|
5090
|
+
children: actions
|
|
5091
|
+
}
|
|
5092
|
+
)
|
|
5093
|
+
]
|
|
5094
|
+
}
|
|
5095
|
+
)
|
|
4969
5096
|
]
|
|
4970
5097
|
}
|
|
4971
5098
|
);
|
|
@@ -4981,14 +5108,13 @@ var SChatSystemMessageImpl = /* @__PURE__ */ react.forwardRef(
|
|
|
4981
5108
|
"span",
|
|
4982
5109
|
{
|
|
4983
5110
|
className: cn(
|
|
4984
|
-
//
|
|
4985
|
-
//
|
|
4986
|
-
//
|
|
4987
|
-
|
|
4988
|
-
"
|
|
4989
|
-
"
|
|
4990
|
-
"
|
|
4991
|
-
"px-sd-16 py-sd-4 text-center",
|
|
5111
|
+
// 라운드는 말풍선과 같은 토큰을 쓴다 — 여러 줄이 돼도 모서리가 상자 높이를 따라
|
|
5112
|
+
// 부풀지 않는다. 제 몫인 chatSystemMessage.radius 는 pill(9999px)이라 쓰지 않는다.
|
|
5113
|
+
// 두 줄부터 좌우가 커다란 반원이 되어 첫 글자가 안으로 밀리기 때문이다.
|
|
5114
|
+
"inline-flex max-w-full rounded-(--cmp-chatBubble-radius) border",
|
|
5115
|
+
"border-(--cmp-chatSystemMessage-border-color) bg-(--cmp-chatSystemMessage-bg)",
|
|
5116
|
+
"typo-body-sm-medium text-(--cmp-chatSystemMessage-color)",
|
|
5117
|
+
"px-(--cmp-chatSystemMessage-paddingX) py-(--cmp-chatSystemMessage-paddingY) text-center",
|
|
4992
5118
|
// 한글은 기본 규칙으로 두면 어절 한가운데서도 줄이 바뀐다 — 띄어쓰기에서만 끊고,
|
|
4993
5119
|
// 끊을 자리가 없는 긴 토큰(URL·주문번호)만 흘려보낸다 (SChatMessage 말풍선과 같은 규칙).
|
|
4994
5120
|
// 가운데 정렬이라 마지막 줄에 한 단어만 남으면 특히 눈에 띄므로 text-pretty 로 줄을 고른다.
|
|
@@ -6313,10 +6439,21 @@ function SCheckbox({
|
|
|
6313
6439
|
) })
|
|
6314
6440
|
}
|
|
6315
6441
|
),
|
|
6316
|
-
label && // 고스트(500) 텍스트로 너비를 고정 → 두께 400↔500 전환 시 레이아웃이 밀리지
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6442
|
+
label && // 고스트(500) 텍스트로 너비를 고정 → 두께 400↔500 전환 시 레이아웃이 밀리지 않음.
|
|
6443
|
+
// 한글은 기본 규칙으로 두면 어절 한가운데서도 줄이 바뀐다. 그러면 min-content 가 한 글자가
|
|
6444
|
+
// 되어, flex 아이템을 지켜 주는 min-width:auto 가 무력해지고 폭이 남는데도 라벨부터
|
|
6445
|
+
// 찌그러진다 — 띄어쓰기에서만 끊고, 끊을 자리가 없는 긴 토큰만 흘려보낸다
|
|
6446
|
+
// (SRadio·SSwitch 라벨과 같은 규칙). 두 사본에 함께 걸려야 하므로 바깥 grid 에 준다.
|
|
6447
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "grid break-keep wrap-break-word", style: { color: labelColor }, children: [
|
|
6448
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "typo-body-sm-medium invisible", style: { gridArea: "1 / 1" }, children: label }),
|
|
6449
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6450
|
+
"span",
|
|
6451
|
+
{
|
|
6452
|
+
className: isChecked ? "typo-body-sm-medium" : "typo-body-sm-default",
|
|
6453
|
+
style: { gridArea: "1 / 1" },
|
|
6454
|
+
children: label
|
|
6455
|
+
}
|
|
6456
|
+
)
|
|
6320
6457
|
] })
|
|
6321
6458
|
]
|
|
6322
6459
|
}
|
|
@@ -7770,7 +7907,7 @@ function SSwitch({
|
|
|
7770
7907
|
style
|
|
7771
7908
|
}) {
|
|
7772
7909
|
const right = rightLabel;
|
|
7773
|
-
const renderLabel = (text) => text ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "
|
|
7910
|
+
const renderLabel = (text) => text ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-sm-medium break-keep wrap-break-word", children: text }) : null;
|
|
7774
7911
|
const trackBg = disabled ? value ? "var(--cmp-switch-bg-disabled-on)" : "var(--cmp-switch-bg-disabled-off)" : value ? "var(--cmp-switch-bg-on)" : "var(--cmp-switch-bg-off)";
|
|
7775
7912
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
7776
7913
|
"label",
|
|
@@ -11777,7 +11914,7 @@ var SPageHeader = /* @__PURE__ */ withDisplayName(
|
|
|
11777
11914
|
// plain 에는 전용 배경 토큰이 없어 시스템 흰 표면을 쓴다(토큰이 생기면 그것으로 갈아끼운다).
|
|
11778
11915
|
bar ? "h-(--cmp-pageHeader-bar-height) border-b border-(--cmp-pageHeader-bar-border) bg-(--cmp-pageHeader-bar-bg) py-(--cmp-pageHeader-bar-paddingY)" : cn(
|
|
11779
11916
|
// 스크롤되면 그림자를 더해 콘텐츠 위에 뜬 것처럼 보이게 한다.
|
|
11780
|
-
"bg-(--sys-color-bg-frame)
|
|
11917
|
+
"bg-(--sys-color-bg-frame) py-(--cmp-pageHeader-plain-paddingY)",
|
|
11781
11918
|
isScrolled && "shadow-floating"
|
|
11782
11919
|
),
|
|
11783
11920
|
className
|
|
@@ -11787,7 +11924,10 @@ var SPageHeader = /* @__PURE__ */ withDisplayName(
|
|
|
11787
11924
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
11788
11925
|
"div",
|
|
11789
11926
|
{
|
|
11790
|
-
className:
|
|
11927
|
+
className: cn(
|
|
11928
|
+
"flex min-w-0 flex-1 gap-(--cmp-pageHeader-gap)",
|
|
11929
|
+
bar ? "items-center" : "items-start"
|
|
11930
|
+
),
|
|
11791
11931
|
style: rowStyle,
|
|
11792
11932
|
children: [
|
|
11793
11933
|
hasBack && /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -11797,7 +11937,7 @@ var SPageHeader = /* @__PURE__ */ withDisplayName(
|
|
|
11797
11937
|
size: "xs",
|
|
11798
11938
|
ariaLabel: "\uB4A4\uB85C\uAC00\uAE30",
|
|
11799
11939
|
onClick: onBack,
|
|
11800
|
-
className: "shrink-0"
|
|
11940
|
+
className: cn("shrink-0", bar ? "" : "mt-[3px]")
|
|
11801
11941
|
}
|
|
11802
11942
|
),
|
|
11803
11943
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -15930,7 +16070,7 @@ function SRadio({
|
|
|
15930
16070
|
label && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
15931
16071
|
"span",
|
|
15932
16072
|
{
|
|
15933
|
-
className: "grid",
|
|
16073
|
+
className: "grid break-keep wrap-break-word",
|
|
15934
16074
|
style: {
|
|
15935
16075
|
color: disabled ? "var(--cmp-radio-label-disabled)" : "var(--cmp-radio-label-default)"
|
|
15936
16076
|
},
|
|
@@ -17745,6 +17885,673 @@ function SKeyValueTable({
|
|
|
17745
17885
|
)
|
|
17746
17886
|
] });
|
|
17747
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
|
+
});
|
|
17748
18555
|
var BUTTON_STYLE = {
|
|
17749
18556
|
default: { color: "secondary" },
|
|
17750
18557
|
danger: { color: "danger", outline: true },
|
|
@@ -22643,15 +23450,15 @@ var SBarcodeInput = /* @__PURE__ */ react.forwardRef(
|
|
|
22643
23450
|
);
|
|
22644
23451
|
|
|
22645
23452
|
// src/components/STimePicker/timepicker.config.ts
|
|
22646
|
-
var
|
|
23453
|
+
var V4 = (path) => `var(--cmp-timepicker-${path})`;
|
|
22647
23454
|
var TIMEPICKER_SIZES = ["sm", "md"];
|
|
22648
23455
|
var TIMEPICKER_SIZE_CONFIG = {
|
|
22649
23456
|
sm: {
|
|
22650
|
-
height:
|
|
22651
|
-
paddingX:
|
|
22652
|
-
gap:
|
|
22653
|
-
icon:
|
|
22654
|
-
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"),
|
|
22655
23462
|
// TODO: component.timepicker 토큰에 typography가 추가되면 CSS 변수로 교체한다.
|
|
22656
23463
|
fontSize: 12,
|
|
22657
23464
|
lineHeight: 20,
|
|
@@ -22660,11 +23467,11 @@ var TIMEPICKER_SIZE_CONFIG = {
|
|
|
22660
23467
|
fieldMaxWidth: "md"
|
|
22661
23468
|
},
|
|
22662
23469
|
md: {
|
|
22663
|
-
height:
|
|
22664
|
-
paddingX:
|
|
22665
|
-
gap:
|
|
22666
|
-
icon:
|
|
22667
|
-
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"),
|
|
22668
23475
|
// TODO: component.timepicker 토큰에 typography가 추가되면 CSS 변수로 교체한다.
|
|
22669
23476
|
fontSize: 14,
|
|
22670
23477
|
lineHeight: 24,
|
|
@@ -23560,6 +24367,9 @@ exports.ACCOUNT_LIST_BOX_LABELS = ACCOUNT_LIST_BOX_LABELS;
|
|
|
23560
24367
|
exports.ACCOUNT_LIST_BOX_LAYOUT = ACCOUNT_LIST_BOX_LAYOUT;
|
|
23561
24368
|
exports.ACCOUNT_LIST_BOX_TONE_CONFIG = ACCOUNT_LIST_BOX_TONE_CONFIG;
|
|
23562
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;
|
|
23563
24373
|
exports.BUTTON_COLORS = BUTTON_COLORS;
|
|
23564
24374
|
exports.BUTTON_SIZES = BUTTON_SIZES;
|
|
23565
24375
|
exports.CALENDAR_BOARD_DAY_BG = CALENDAR_BOARD_DAY_BG;
|
|
@@ -23568,7 +24378,7 @@ exports.CALENDAR_BOARD_EVENT_COLORS = CALENDAR_BOARD_EVENT_COLORS;
|
|
|
23568
24378
|
exports.CALENDAR_BOARD_EVENT_PRESETS = CALENDAR_BOARD_EVENT_PRESETS;
|
|
23569
24379
|
exports.CALENDAR_BOARD_MAX_VISIBLE_EVENTS = CALENDAR_BOARD_MAX_VISIBLE_EVENTS;
|
|
23570
24380
|
exports.CHAT_ATTACHED_FILE_CONFIG = CHAT_ATTACHED_FILE_CONFIG;
|
|
23571
|
-
exports.
|
|
24381
|
+
exports.CHAT_FILE_CONFIG = CHAT_FILE_CONFIG;
|
|
23572
24382
|
exports.CHAT_MESSAGE_DIRECTIONS = CHAT_MESSAGE_DIRECTIONS;
|
|
23573
24383
|
exports.CHAT_MESSAGE_READ_STATUSES = CHAT_MESSAGE_READ_STATUSES;
|
|
23574
24384
|
exports.CHAT_MESSAGE_VARIANTS = CHAT_MESSAGE_VARIANTS;
|
|
@@ -23598,6 +24408,7 @@ exports.RangeCalendar = RangeCalendar;
|
|
|
23598
24408
|
exports.SAccountListBox = SAccountListBox;
|
|
23599
24409
|
exports.SActionModal = SActionModal;
|
|
23600
24410
|
exports.SBadge = SBadge;
|
|
24411
|
+
exports.SBarChart = SBarChart;
|
|
23601
24412
|
exports.SBarcodeInput = SBarcodeInput;
|
|
23602
24413
|
exports.SButton = SButton;
|
|
23603
24414
|
exports.SCROLL_AREA_AXES = SCROLL_AREA_AXES;
|
|
@@ -23606,6 +24417,7 @@ exports.SCalendarBoard = SCalendarBoard;
|
|
|
23606
24417
|
exports.SCallout = SCallout;
|
|
23607
24418
|
exports.SCard = SCard;
|
|
23608
24419
|
exports.SChatAttachedFile = SChatAttachedFile2;
|
|
24420
|
+
exports.SChatFile = SChatFile2;
|
|
23609
24421
|
exports.SChatInput = SChatInput2;
|
|
23610
24422
|
exports.SChatMessage = SChatMessage2;
|
|
23611
24423
|
exports.SChatSystemMessage = SChatSystemMessage2;
|