sellmate-design-system-react 0.1.11 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/SActionModal/SActionModal.cjs +7 -3
- package/dist/components/SActionModal/SActionModal.cjs.map +1 -1
- package/dist/components/SActionModal/SActionModal.js +7 -3
- package/dist/components/SActionModal/SActionModal.js.map +1 -1
- package/dist/components/SBarcodeInput/SBarcodeInput.cjs +7 -3
- package/dist/components/SBarcodeInput/SBarcodeInput.cjs.map +1 -1
- package/dist/components/SBarcodeInput/SBarcodeInput.js +7 -3
- package/dist/components/SBarcodeInput/SBarcodeInput.js.map +1 -1
- package/dist/components/SCalendar/SCalendar.cjs +7 -3
- package/dist/components/SCalendar/SCalendar.cjs.map +1 -1
- package/dist/components/SCalendar/SCalendar.js +7 -3
- package/dist/components/SCalendar/SCalendar.js.map +1 -1
- package/dist/components/SChip/SChip.cjs +7 -3
- package/dist/components/SChip/SChip.cjs.map +1 -1
- package/dist/components/SChip/SChip.js +7 -3
- package/dist/components/SChip/SChip.js.map +1 -1
- package/dist/components/SChipInput/SChipInput.cjs +23 -10
- package/dist/components/SChipInput/SChipInput.cjs.map +1 -1
- package/dist/components/SChipInput/SChipInput.js +23 -10
- package/dist/components/SChipInput/SChipInput.js.map +1 -1
- package/dist/components/SCircleProgress/SCircleProgress.cjs +6 -2
- package/dist/components/SCircleProgress/SCircleProgress.cjs.map +1 -1
- package/dist/components/SCircleProgress/SCircleProgress.js +6 -2
- package/dist/components/SCircleProgress/SCircleProgress.js.map +1 -1
- package/dist/components/SConfirmModal/SConfirmModal.cjs +7 -3
- package/dist/components/SConfirmModal/SConfirmModal.cjs.map +1 -1
- package/dist/components/SConfirmModal/SConfirmModal.js +7 -3
- package/dist/components/SConfirmModal/SConfirmModal.js.map +1 -1
- package/dist/components/SDatePicker/SDatePicker.cjs +7 -3
- package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
- package/dist/components/SDatePicker/SDatePicker.js +7 -3
- package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
- package/dist/components/SDateRangePicker/SDateRangePicker.cjs +7 -3
- package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
- package/dist/components/SDateRangePicker/SDateRangePicker.js +7 -3
- package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
- package/dist/components/SDropdownButton/SDropdownButton.cjs +1 -1
- package/dist/components/SDropdownButton/SDropdownButton.cjs.map +1 -1
- package/dist/components/SDropdownButton/SDropdownButton.js +1 -1
- package/dist/components/SDropdownButton/SDropdownButton.js.map +1 -1
- package/dist/components/SField/SField.cjs +7 -3
- package/dist/components/SField/SField.cjs.map +1 -1
- package/dist/components/SField/SField.js +7 -3
- package/dist/components/SField/SField.js.map +1 -1
- package/dist/components/SFilePicker/SFilePicker.cjs +7 -3
- package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
- package/dist/components/SFilePicker/SFilePicker.js +7 -3
- package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
- package/dist/components/SGhostButton/SGhostButton.cjs +7 -3
- package/dist/components/SGhostButton/SGhostButton.cjs.map +1 -1
- package/dist/components/SGhostButton/SGhostButton.d.ts +5 -1
- package/dist/components/SGhostButton/SGhostButton.js +7 -3
- package/dist/components/SGhostButton/SGhostButton.js.map +1 -1
- package/dist/components/SGnb/SGnb.cjs +3297 -0
- package/dist/components/SGnb/SGnb.cjs.map +1 -0
- package/dist/components/SGnb/SGnb.d.ts +35 -0
- package/dist/components/SGnb/SGnb.js +3295 -0
- package/dist/components/SGnb/SGnb.js.map +1 -0
- package/dist/components/SGnb/gnb.config.d.ts +63 -0
- package/dist/components/SGnb/index.d.ts +2 -0
- package/dist/components/SGuide/SGuide.cjs +7 -3
- package/dist/components/SGuide/SGuide.cjs.map +1 -1
- package/dist/components/SGuide/SGuide.js +7 -3
- package/dist/components/SGuide/SGuide.js.map +1 -1
- package/dist/components/SInput/SInput.cjs +7 -3
- package/dist/components/SInput/SInput.cjs.map +1 -1
- package/dist/components/SInput/SInput.js +7 -3
- package/dist/components/SInput/SInput.js.map +1 -1
- package/dist/components/SKeyValueTable/SKeyValueTable.cjs +18 -6
- package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
- package/dist/components/SKeyValueTable/SKeyValueTable.js +18 -6
- package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
- package/dist/components/SLayout/SLayout.cjs +79 -0
- package/dist/components/SLayout/SLayout.cjs.map +1 -0
- package/dist/components/SLayout/SLayout.d.ts +41 -0
- package/dist/components/SLayout/SLayout.js +75 -0
- package/dist/components/SLayout/SLayout.js.map +1 -0
- package/dist/components/SLayout/index.d.ts +1 -0
- package/dist/components/SLoadingContainer/SLoadingContainer.cjs +6 -2
- package/dist/components/SLoadingContainer/SLoadingContainer.cjs.map +1 -1
- package/dist/components/SLoadingContainer/SLoadingContainer.js +6 -2
- package/dist/components/SLoadingContainer/SLoadingContainer.js.map +1 -1
- package/dist/components/SLoadingContainer/loading.cjs +6 -2
- package/dist/components/SLoadingContainer/loading.cjs.map +1 -1
- package/dist/components/SLoadingContainer/loading.js +6 -2
- package/dist/components/SLoadingContainer/loading.js.map +1 -1
- package/dist/components/SLoadingModal/SLoadingModal.cjs +13 -5
- package/dist/components/SLoadingModal/SLoadingModal.cjs.map +1 -1
- package/dist/components/SLoadingModal/SLoadingModal.js +13 -5
- package/dist/components/SLoadingModal/SLoadingModal.js.map +1 -1
- package/dist/components/SModalContainer/SModalContainer.cjs +7 -3
- package/dist/components/SModalContainer/SModalContainer.cjs.map +1 -1
- package/dist/components/SModalContainer/SModalContainer.d.ts +10 -0
- package/dist/components/SModalContainer/SModalContainer.js +7 -3
- package/dist/components/SModalContainer/SModalContainer.js.map +1 -1
- package/dist/components/SNumberInput/SNumberInput.cjs +7 -3
- package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
- package/dist/components/SNumberInput/SNumberInput.js +7 -3
- package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
- package/dist/components/SPage/SPage.cjs +86 -0
- package/dist/components/SPage/SPage.cjs.map +1 -0
- package/dist/components/SPage/SPage.d.ts +13 -0
- package/dist/components/SPage/SPage.js +84 -0
- package/dist/components/SPage/SPage.js.map +1 -0
- package/dist/components/SPage/index.d.ts +1 -0
- package/dist/components/SPopover/SPopover.cjs +7 -3
- package/dist/components/SPopover/SPopover.cjs.map +1 -1
- package/dist/components/SPopover/SPopover.js +7 -3
- package/dist/components/SPopover/SPopover.js.map +1 -1
- package/dist/components/SPortal/SPortal.cjs +1 -1
- package/dist/components/SPortal/SPortal.cjs.map +1 -1
- package/dist/components/SPortal/SPortal.js +1 -1
- package/dist/components/SPortal/SPortal.js.map +1 -1
- package/dist/components/SSelect/SSelect.cjs +18 -6
- package/dist/components/SSelect/SSelect.cjs.map +1 -1
- package/dist/components/SSelect/SSelect.js +18 -6
- package/dist/components/SSelect/SSelect.js.map +1 -1
- package/dist/components/STable/STable.cjs +26 -10
- package/dist/components/STable/STable.cjs.map +1 -1
- package/dist/components/STable/STable.js +26 -10
- package/dist/components/STable/STable.js.map +1 -1
- package/dist/components/STextarea/STextarea.cjs +7 -3
- package/dist/components/STextarea/STextarea.cjs.map +1 -1
- package/dist/components/STextarea/STextarea.js +7 -3
- package/dist/components/STextarea/STextarea.js.map +1 -1
- package/dist/components/SToast/SToast.cjs +7 -3
- package/dist/components/SToast/SToast.cjs.map +1 -1
- package/dist/components/SToast/SToast.js +7 -3
- package/dist/components/SToast/SToast.js.map +1 -1
- package/dist/components/SToast/SToastContainer.cjs +7 -3
- package/dist/components/SToast/SToastContainer.cjs.map +1 -1
- package/dist/components/SToast/SToastContainer.js +7 -3
- package/dist/components/SToast/SToastContainer.js.map +1 -1
- package/dist/components/STooltip/STooltip.cjs +7 -3
- package/dist/components/STooltip/STooltip.cjs.map +1 -1
- package/dist/components/STooltip/STooltip.js +7 -3
- package/dist/components/STooltip/STooltip.js.map +1 -1
- package/dist/index.cjs +430 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +422 -19
- package/dist/index.js.map +1 -1
- package/dist/styles.css +192 -26
- package/dist/theme.css +66 -13
- package/package.json +1 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var clsx = require('clsx');
|
|
5
|
+
var tailwindMerge = require('tailwind-merge');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
// src/components/SPage/SPage.tsx
|
|
9
|
+
var twMerge = tailwindMerge.extendTailwindMerge({
|
|
10
|
+
extend: {
|
|
11
|
+
classGroups: {
|
|
12
|
+
"font-size": [{ text: ["10", "11", "12", "13", "14", "16", "18", "20", "24", "28", "32"] }]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
function cn(...inputs) {
|
|
17
|
+
return twMerge(clsx.clsx(inputs));
|
|
18
|
+
}
|
|
19
|
+
var box = (p) => `var(--cmp-gnbBox-${p})`;
|
|
20
|
+
var GNB_COMMON = {
|
|
21
|
+
topHeight: box("top-height")};
|
|
22
|
+
var GNB_MENU_WIDTH = 240;
|
|
23
|
+
var SLayoutContext = react.createContext(null);
|
|
24
|
+
react.forwardRef(function SLayout2({
|
|
25
|
+
type = "box",
|
|
26
|
+
folded,
|
|
27
|
+
defaultFolded = false,
|
|
28
|
+
onFoldedChange,
|
|
29
|
+
className,
|
|
30
|
+
style,
|
|
31
|
+
children,
|
|
32
|
+
...rest
|
|
33
|
+
}, ref) {
|
|
34
|
+
const [uncontrolledFolded, setUncontrolledFolded] = react.useState(defaultFolded);
|
|
35
|
+
const isControlled = folded !== void 0;
|
|
36
|
+
const currentFolded = isControlled ? folded : uncontrolledFolded;
|
|
37
|
+
const setFolded = react.useCallback(
|
|
38
|
+
(next) => {
|
|
39
|
+
if (!isControlled) setUncontrolledFolded(next);
|
|
40
|
+
onFoldedChange?.(next);
|
|
41
|
+
},
|
|
42
|
+
[isControlled, onFoldedChange]
|
|
43
|
+
);
|
|
44
|
+
const ctx = react.useMemo(
|
|
45
|
+
() => ({ type, folded: currentFolded, setFolded }),
|
|
46
|
+
[type, currentFolded, setFolded]
|
|
47
|
+
);
|
|
48
|
+
const isBox = type === "box";
|
|
49
|
+
const frameStyle = isBox ? {
|
|
50
|
+
gridTemplateColumns: `${currentFolded ? 0 : GNB_MENU_WIDTH}px 1fr`,
|
|
51
|
+
gridTemplateRows: `${GNB_COMMON.topHeight} 1fr`
|
|
52
|
+
} : void 0;
|
|
53
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SLayoutContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
54
|
+
"div",
|
|
55
|
+
{
|
|
56
|
+
ref,
|
|
57
|
+
"data-type": type,
|
|
58
|
+
className: cn(
|
|
59
|
+
"box-border h-full overflow-hidden",
|
|
60
|
+
isBox ? "grid" : "flex flex-row",
|
|
61
|
+
className
|
|
62
|
+
),
|
|
63
|
+
style: { ...frameStyle, ...style },
|
|
64
|
+
...rest,
|
|
65
|
+
children
|
|
66
|
+
}
|
|
67
|
+
) });
|
|
68
|
+
});
|
|
69
|
+
var SPage = react.forwardRef(function SPage2({ className, style, children, ...rest }, ref) {
|
|
70
|
+
const layout = react.useContext(SLayoutContext);
|
|
71
|
+
const gridStyle = layout?.type === "box" ? { gridColumn: 2, gridRow: 2 } : void 0;
|
|
72
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
73
|
+
"main",
|
|
74
|
+
{
|
|
75
|
+
ref,
|
|
76
|
+
className: cn("box-border min-h-0 min-w-0 flex-1 overflow-auto p-24", className),
|
|
77
|
+
style: { ...gridStyle, ...style },
|
|
78
|
+
...rest,
|
|
79
|
+
children
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
exports.SPage = SPage;
|
|
85
|
+
//# sourceMappingURL=SPage.cjs.map
|
|
86
|
+
//# sourceMappingURL=SPage.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/cn.ts","../../../src/components/SGnb/gnb.config.ts","../../../src/components/SLayout/SLayout.tsx","../../../src/components/SPage/SPage.tsx"],"names":["extendTailwindMerge","clsx","createContext","forwardRef","SLayout","useState","useCallback","useMemo","jsx","SPage","useContext"],"mappings":"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","file":"SPage.cjs","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { extendTailwindMerge } from 'tailwind-merge';\n\n/**\n * theme.css 의 --text-* 는 기본 Tailwind 폰트사이즈 스케일(xs/sm/base...)이 아닌\n * 숫자 스케일(10~32)이라, 확장 없이는 tailwind-merge 가 이를 텍스트 컬러 그룹으로\n * 오인해 text-12 같은 클래스를 text-(--color-var) 뒤에서 충돌 제거해버린다.\n */\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [{ text: ['10', '11', '12', '13', '14', '16', '18', '20', '24', '28', '32'] }],\n },\n },\n});\n\n/**\n * className 병합 유틸.\n * clsx로 조건부 클래스를 합치고, tailwind-merge로 Tailwind 클래스 충돌을 해소한다.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","import type { SIconName } from '../SIcon';\n\n/** 구조(레이아웃): box = 라운드/좁은 들여쓰기, belt = 풀폭/넓은 들여쓰기 */\nexport type SGnbType = 'box' | 'belt';\n/** 색상(테마): light / dark */\nexport type SGnbColor = 'light' | 'dark';\n\n/** GNB 메뉴 아이템 (재귀 트리). depth1 → depth2 → depth3. */\nexport interface SGnbMenuItem {\n /** 표시 텍스트 */\n label: string;\n /** 고유 식별값 (선택 상태 비교 기준) */\n value: string;\n /** depth1 전용 아이콘명 */\n icon?: SIconName;\n /** 우측 Tag 뱃지 텍스트 */\n tag?: string;\n /** 비활성 여부 */\n disabled?: boolean;\n /** 하위 아이템 (있으면 펼침/접힘 대상) */\n children?: SGnbMenuItem[];\n}\n\n// theme.css 의 컴포넌트 토큰 참조 (디자인팀 소유, 직접 수정 금지)\n// 치수(type 축)는 gnbBox/gnbBelt 토큰, 색(color 축)은 gnb light/dark 토큰(--cmp-light-*/--cmp-dark-*).\nconst belt = (p: string) => `var(--cmp-gnbBelt-${p})`;\nconst box = (p: string) => `var(--cmp-gnbBox-${p})`;\nconst light = (p: string) => `var(--cmp-light-${p})`;\nconst dark = (p: string) => `var(--cmp-dark-${p})`;\n\n/** 레이아웃(type 축). box → --cmp-gnbBox-*, belt → --cmp-gnbBelt-* */\nexport const GNB_LAYOUT: Record<\n SGnbType,\n {\n topPaddingX: string;\n topGap: string;\n bodyPadding: string;\n itemPaddingRight: string;\n itemGap: string;\n itemRadius: string;\n itemArrow: string;\n depth1PaddingLeft: string;\n depth2PaddingLeft: string;\n depth3PaddingLeft: string;\n depth1Icon: string;\n // 타이포 (theme.css 에 gnb 타이포 변수 없음 → 토큰 값 직접 명시)\n depth1FontSize: number;\n depth1LineHeight: number;\n depth1FontWeight: number;\n itemFontSize: number;\n itemLineHeight: number;\n }\n> = {\n // 사용자 명명: type=\"box\" → gnbBox 토큰(둥금), type=\"belt\" → gnbBelt 토큰(각짐)\n box: {\n topPaddingX: box('paddingX'),\n topGap: box('gap'), // 상단바 gap (버튼 간 세부 gap 은 --cmp-gnbBox-button-gap)\n bodyPadding: box('menu-body-padding'),\n itemPaddingRight: box('item-paddingRight'),\n itemGap: box('item-gap'),\n itemRadius: box('item-radius'),\n itemArrow: box('item-arrow'),\n depth1PaddingLeft: box('item-depth1-paddingLeft'),\n depth2PaddingLeft: box('item-depth2-paddingLeft'),\n depth3PaddingLeft: box('item-depth3-paddingLeft'),\n depth1Icon: box('item-depth1-icon'),\n depth1FontSize: 14, // token: gnbBox.item.typography.bold.fontSize\n depth1LineHeight: 24, // token: gnbBox.item.typography.bold.lineHeight\n depth1FontWeight: 700,\n itemFontSize: 14, // token: gnbBox.item.typography.default.fontSize\n itemLineHeight: 24, // token: gnbBox.item.typography.default.lineHeight\n },\n belt: {\n topPaddingX: belt('top-paddingX'),\n topGap: belt('top-gap'),\n bodyPadding: belt('body-paddingY'),\n itemPaddingRight: belt('item-paddingRight'),\n itemGap: belt('item-gap'),\n itemRadius: '0px', // belt 토큰에 radius 없음 → 풀폭 행\n itemArrow: belt('item-icon'),\n depth1PaddingLeft: belt('item-depth1-paddingLeft'),\n depth2PaddingLeft: belt('item-depth2-paddingLeft'),\n depth3PaddingLeft: belt('item-depth3-paddingLeft'),\n depth1Icon: box('item-depth1-icon'), // belt 토큰에 depth1-icon 없음 → box 값(20) 공유\n depth1FontSize: 13, // token: gnbBelt.item.depth1.typography.fontSize\n depth1LineHeight: 22, // token: gnbBelt.item.depth1.typography.lineHeight\n depth1FontWeight: 700,\n itemFontSize: 12, // token: gnbBelt.item.typography.default.fontSize\n itemLineHeight: 20, // token: gnbBelt.item.typography.default.lineHeight\n },\n};\n\n/** 색상(color 축). light → --cmp-light-* 색, dark → --cmp-dark-* 색 (gnb colortype 토큰) */\nexport const GNB_COLORS: Record<\n SGnbColor,\n {\n panelBg: string;\n topBorder: string;\n itemBgDefault: string;\n itemBgHover: string;\n itemBgSelected: string;\n itemColorDefault: string;\n itemColorActive: string;\n }\n> = {\n light: {\n panelBg: light('bg'),\n topBorder: light('divider'),\n itemBgDefault: light('item-bg-default'),\n itemBgHover: light('item-bg-hover'),\n itemBgSelected: light('item-bg-selected'),\n itemColorDefault: light('item-fg-default'),\n itemColorActive: light('item-fg-selected'),\n },\n dark: {\n panelBg: dark('bg'),\n topBorder: dark('divider'),\n itemBgDefault: dark('item-bg-default'),\n itemBgHover: dark('item-bg-hover'),\n itemBgSelected: dark('item-bg-selected'),\n itemColorDefault: dark('item-fg-default'),\n itemColorActive: dark('item-fg-selected'),\n },\n};\n\n/** 구조와 무관한 공통 레이아웃 */\nexport const GNB_COMMON = {\n topHeight: box('top-height'), // 56 (box=belt 동일)\n logoHeight: box('logo-height'), // 32\n itemPaddingY: box('item-paddingY'), // 6\n foldWidth: belt('fold-width'), // 48 (belt 토큰 공유)\n};\n\n/**\n * 메뉴 고정 폭. belt/box 모두 메뉴는 이 폭으로 좌측에 붙는다(토큰 없음 → 상수).\n * box 에서 SLayout 이 \"메뉴 오른쪽 = 페이지\" 영역을 잡을 때도 이 값을 쓴다.\n */\nexport const GNB_MENU_WIDTH = 240;\n\n/** value 로 선택된 아이템의 조상 value 목록(자기 자신 제외)을 반환 → 자동 펼침용 */\nexport function findGnbAncestors(items: SGnbMenuItem[], value: string): string[] {\n const walk = (list: SGnbMenuItem[], trail: string[]): string[] | null => {\n for (const item of list) {\n if (item.value === value) return trail;\n if (item.children) {\n const found = walk(item.children, [...trail, item.value]);\n if (found) return found;\n }\n }\n return null;\n };\n return walk(items, []) ?? [];\n}\n","'use client';\n\nimport {\n createContext,\n forwardRef,\n useCallback,\n useContext,\n useMemo,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n} from 'react';\nimport { cn } from '../../lib/cn';\nimport { GNB_COMMON, GNB_MENU_WIDTH, type SGnbType } from '../SGnb/gnb.config';\n\n/** 레이아웃 구조: box(풀폭 상단바 + 아래에 메뉴|페이지) / belt(좌측 레일 + 페이지 가로 분할) */\nexport type SLayoutType = SGnbType;\n\nexport interface SLayoutContextValue {\n /** 레이아웃 구조 — 자식 SGnb 가 배치·스타일을 맞추는 기준 */\n type: SLayoutType;\n /** GNB 접힘 상태 */\n folded: boolean;\n /** 접힘 상태 변경 */\n setFolded: (folded: boolean) => void;\n}\n\nexport const SLayoutContext = createContext<SLayoutContextValue | null>(null);\n\n/**\n * SLayout 의 상태를 읽고 제어한다. `<SLayout>` 밖에서 호출하면 throw.\n * 외부 버튼으로 GNB 를 접거나, 현재 구조(type)에 따라 분기할 때 사용한다.\n */\nexport function useSLayout(): SLayoutContextValue {\n const ctx = useContext(SLayoutContext);\n if (!ctx) throw new Error('useSLayout 은 <SLayout> 안에서만 사용할 수 있습니다.');\n return ctx;\n}\n\nexport interface SLayoutProps extends HTMLAttributes<HTMLDivElement> {\n /** 레이아웃 구조: box(세로) / belt(가로). 자식 SGnb 가 이 값을 따른다. */\n type?: SLayoutType;\n /** GNB 접힘 상태 (controlled) */\n folded?: boolean;\n /** GNB 초기 접힘 상태 (uncontrolled) */\n defaultFolded?: boolean;\n /** 접힘 상태 변경 */\n onFoldedChange?: (folded: boolean) => void;\n}\n\n/**\n * SLayout — 앱 셸 프레임 겸 context provider.\n * type 하나로 배치를 결정하고 overflow:hidden 으로 스크롤 경계를 만든다.\n * 자식 SGnb·SPage 는 context 에서 type·folded 를 읽으므로 앱은 type 을 여기 한 곳에만 쓴다.\n * 높이는 기본 100%(부모를 채움) — 앱 루트로 쓸 땐 부모를 뷰포트 높이로 잡는다.\n *\n * - belt: [GNB(자기 폭 소유) | 페이지] 가로 flex.\n * - box: 상단바가 풀폭이고 그 아래에 [메뉴 | 페이지] 가 나란히 서야 하는데, SGnb 가 상단바와\n * 메뉴를 한 엘리먼트로 갖고 있어 사이에 끼워넣을 수 없다. 그래서 grid 로 깔고\n * SGnb 를 전 셀에 겹친 뒤(root 는 투명) SPage 가 (2행,2열) = \"메뉴 오른쪽 투명 영역\" 을 차지한다.\n * 접으면 메뉴가 사라지므로 1열을 0 으로 접어 페이지가 전폭을 쓴다.\n */\nexport const SLayout = forwardRef<HTMLDivElement, SLayoutProps>(function SLayout(\n {\n type = 'box',\n folded,\n defaultFolded = false,\n onFoldedChange,\n className,\n style,\n children,\n ...rest\n },\n ref,\n) {\n const [uncontrolledFolded, setUncontrolledFolded] = useState(defaultFolded);\n const isControlled = folded !== undefined;\n const currentFolded = isControlled ? folded : uncontrolledFolded;\n\n const setFolded = useCallback(\n (next: boolean) => {\n if (!isControlled) setUncontrolledFolded(next);\n onFoldedChange?.(next);\n },\n [isControlled, onFoldedChange],\n );\n\n const ctx = useMemo<SLayoutContextValue>(\n () => ({ type, folded: currentFolded, setFolded }),\n [type, currentFolded, setFolded],\n );\n\n const isBox = type === 'box';\n const frameStyle: CSSProperties | undefined = isBox\n ? {\n gridTemplateColumns: `${currentFolded ? 0 : GNB_MENU_WIDTH}px 1fr`,\n gridTemplateRows: `${GNB_COMMON.topHeight} 1fr`,\n }\n : undefined;\n\n return (\n <SLayoutContext.Provider value={ctx}>\n <div\n ref={ref}\n data-type={type}\n className={cn(\n 'box-border h-full overflow-hidden',\n isBox ? 'grid' : 'flex flex-row',\n className,\n )}\n style={{ ...frameStyle, ...style }}\n {...rest}\n >\n {children}\n </div>\n </SLayoutContext.Provider>\n );\n});\n","import { forwardRef, useContext, type CSSProperties, type HTMLAttributes } from 'react';\nimport { cn } from '../../lib/cn';\nimport { SLayoutContext } from '../SLayout/SLayout';\n\n// 자체 prop 없이 표준 HTML 속성만 받는다.\n// docs:gen 은 export interface 만 수집하므로(gen-docs.mjs) 타입 별칭 대신 interface 로 둔다.\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface SPageProps extends HTMLAttributes<HTMLElement> {}\n\n/**\n * SPage — SLayout 안에서 남은 공간을 채우는 스크롤 콘텐츠 영역.\n * 스크롤은 SLayout(overflow:hidden)이 잡아둔 경계 안에서 이 영역에만 생긴다.\n * 패딩·배경은 className 으로 덮어쓸 수 있다.\n *\n * - belt: flex 자식으로 남은 가로 공간을 채운다.\n * - box: SLayout 이 깐 grid 의 (2행,2열) = 상단바 아래·메뉴 오른쪽 자리를 차지한다.\n * (SGnb 가 전 셀에 겹쳐 있지만 그 영역은 투명해서 SPage 가 보인다)\n */\nexport const SPage = forwardRef<HTMLElement, SPageProps>(function SPage(\n { className, style, children, ...rest },\n ref,\n) {\n const layout = useContext(SLayoutContext);\n const gridStyle: CSSProperties | undefined =\n layout?.type === 'box' ? { gridColumn: 2, gridRow: 2 } : undefined;\n\n return (\n <main\n ref={ref}\n className={cn('box-border min-h-0 min-w-0 flex-1 overflow-auto p-24', className)}\n style={{ ...gridStyle, ...style }}\n {...rest}\n >\n {children}\n </main>\n );\n});\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type HTMLAttributes } from 'react';
|
|
2
|
+
export interface SPageProps extends HTMLAttributes<HTMLElement> {
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* SPage — SLayout 안에서 남은 공간을 채우는 스크롤 콘텐츠 영역.
|
|
6
|
+
* 스크롤은 SLayout(overflow:hidden)이 잡아둔 경계 안에서 이 영역에만 생긴다.
|
|
7
|
+
* 패딩·배경은 className 으로 덮어쓸 수 있다.
|
|
8
|
+
*
|
|
9
|
+
* - belt: flex 자식으로 남은 가로 공간을 채운다.
|
|
10
|
+
* - box: SLayout 이 깐 grid 의 (2행,2열) = 상단바 아래·메뉴 오른쪽 자리를 차지한다.
|
|
11
|
+
* (SGnb 가 전 셀에 겹쳐 있지만 그 영역은 투명해서 SPage 가 보인다)
|
|
12
|
+
*/
|
|
13
|
+
export declare const SPage: import("react").ForwardRefExoticComponent<SPageProps & import("react").RefAttributes<HTMLElement>>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createContext, forwardRef, useState, useCallback, useMemo, useContext } from 'react';
|
|
2
|
+
import { clsx } from 'clsx';
|
|
3
|
+
import { extendTailwindMerge } from 'tailwind-merge';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// src/components/SPage/SPage.tsx
|
|
7
|
+
var twMerge = extendTailwindMerge({
|
|
8
|
+
extend: {
|
|
9
|
+
classGroups: {
|
|
10
|
+
"font-size": [{ text: ["10", "11", "12", "13", "14", "16", "18", "20", "24", "28", "32"] }]
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
function cn(...inputs) {
|
|
15
|
+
return twMerge(clsx(inputs));
|
|
16
|
+
}
|
|
17
|
+
var box = (p) => `var(--cmp-gnbBox-${p})`;
|
|
18
|
+
var GNB_COMMON = {
|
|
19
|
+
topHeight: box("top-height")};
|
|
20
|
+
var GNB_MENU_WIDTH = 240;
|
|
21
|
+
var SLayoutContext = createContext(null);
|
|
22
|
+
forwardRef(function SLayout2({
|
|
23
|
+
type = "box",
|
|
24
|
+
folded,
|
|
25
|
+
defaultFolded = false,
|
|
26
|
+
onFoldedChange,
|
|
27
|
+
className,
|
|
28
|
+
style,
|
|
29
|
+
children,
|
|
30
|
+
...rest
|
|
31
|
+
}, ref) {
|
|
32
|
+
const [uncontrolledFolded, setUncontrolledFolded] = useState(defaultFolded);
|
|
33
|
+
const isControlled = folded !== void 0;
|
|
34
|
+
const currentFolded = isControlled ? folded : uncontrolledFolded;
|
|
35
|
+
const setFolded = useCallback(
|
|
36
|
+
(next) => {
|
|
37
|
+
if (!isControlled) setUncontrolledFolded(next);
|
|
38
|
+
onFoldedChange?.(next);
|
|
39
|
+
},
|
|
40
|
+
[isControlled, onFoldedChange]
|
|
41
|
+
);
|
|
42
|
+
const ctx = useMemo(
|
|
43
|
+
() => ({ type, folded: currentFolded, setFolded }),
|
|
44
|
+
[type, currentFolded, setFolded]
|
|
45
|
+
);
|
|
46
|
+
const isBox = type === "box";
|
|
47
|
+
const frameStyle = isBox ? {
|
|
48
|
+
gridTemplateColumns: `${currentFolded ? 0 : GNB_MENU_WIDTH}px 1fr`,
|
|
49
|
+
gridTemplateRows: `${GNB_COMMON.topHeight} 1fr`
|
|
50
|
+
} : void 0;
|
|
51
|
+
return /* @__PURE__ */ jsx(SLayoutContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
|
|
52
|
+
"div",
|
|
53
|
+
{
|
|
54
|
+
ref,
|
|
55
|
+
"data-type": type,
|
|
56
|
+
className: cn(
|
|
57
|
+
"box-border h-full overflow-hidden",
|
|
58
|
+
isBox ? "grid" : "flex flex-row",
|
|
59
|
+
className
|
|
60
|
+
),
|
|
61
|
+
style: { ...frameStyle, ...style },
|
|
62
|
+
...rest,
|
|
63
|
+
children
|
|
64
|
+
}
|
|
65
|
+
) });
|
|
66
|
+
});
|
|
67
|
+
var SPage = forwardRef(function SPage2({ className, style, children, ...rest }, ref) {
|
|
68
|
+
const layout = useContext(SLayoutContext);
|
|
69
|
+
const gridStyle = layout?.type === "box" ? { gridColumn: 2, gridRow: 2 } : void 0;
|
|
70
|
+
return /* @__PURE__ */ jsx(
|
|
71
|
+
"main",
|
|
72
|
+
{
|
|
73
|
+
ref,
|
|
74
|
+
className: cn("box-border min-h-0 min-w-0 flex-1 overflow-auto p-24", className),
|
|
75
|
+
style: { ...gridStyle, ...style },
|
|
76
|
+
...rest,
|
|
77
|
+
children
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export { SPage };
|
|
83
|
+
//# sourceMappingURL=SPage.js.map
|
|
84
|
+
//# sourceMappingURL=SPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/cn.ts","../../../src/components/SGnb/gnb.config.ts","../../../src/components/SLayout/SLayout.tsx","../../../src/components/SPage/SPage.tsx"],"names":["SLayout","forwardRef","SPage","useContext","jsx"],"mappings":"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","file":"SPage.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { extendTailwindMerge } from 'tailwind-merge';\n\n/**\n * theme.css 의 --text-* 는 기본 Tailwind 폰트사이즈 스케일(xs/sm/base...)이 아닌\n * 숫자 스케일(10~32)이라, 확장 없이는 tailwind-merge 가 이를 텍스트 컬러 그룹으로\n * 오인해 text-12 같은 클래스를 text-(--color-var) 뒤에서 충돌 제거해버린다.\n */\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [{ text: ['10', '11', '12', '13', '14', '16', '18', '20', '24', '28', '32'] }],\n },\n },\n});\n\n/**\n * className 병합 유틸.\n * clsx로 조건부 클래스를 합치고, tailwind-merge로 Tailwind 클래스 충돌을 해소한다.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","import type { SIconName } from '../SIcon';\n\n/** 구조(레이아웃): box = 라운드/좁은 들여쓰기, belt = 풀폭/넓은 들여쓰기 */\nexport type SGnbType = 'box' | 'belt';\n/** 색상(테마): light / dark */\nexport type SGnbColor = 'light' | 'dark';\n\n/** GNB 메뉴 아이템 (재귀 트리). depth1 → depth2 → depth3. */\nexport interface SGnbMenuItem {\n /** 표시 텍스트 */\n label: string;\n /** 고유 식별값 (선택 상태 비교 기준) */\n value: string;\n /** depth1 전용 아이콘명 */\n icon?: SIconName;\n /** 우측 Tag 뱃지 텍스트 */\n tag?: string;\n /** 비활성 여부 */\n disabled?: boolean;\n /** 하위 아이템 (있으면 펼침/접힘 대상) */\n children?: SGnbMenuItem[];\n}\n\n// theme.css 의 컴포넌트 토큰 참조 (디자인팀 소유, 직접 수정 금지)\n// 치수(type 축)는 gnbBox/gnbBelt 토큰, 색(color 축)은 gnb light/dark 토큰(--cmp-light-*/--cmp-dark-*).\nconst belt = (p: string) => `var(--cmp-gnbBelt-${p})`;\nconst box = (p: string) => `var(--cmp-gnbBox-${p})`;\nconst light = (p: string) => `var(--cmp-light-${p})`;\nconst dark = (p: string) => `var(--cmp-dark-${p})`;\n\n/** 레이아웃(type 축). box → --cmp-gnbBox-*, belt → --cmp-gnbBelt-* */\nexport const GNB_LAYOUT: Record<\n SGnbType,\n {\n topPaddingX: string;\n topGap: string;\n bodyPadding: string;\n itemPaddingRight: string;\n itemGap: string;\n itemRadius: string;\n itemArrow: string;\n depth1PaddingLeft: string;\n depth2PaddingLeft: string;\n depth3PaddingLeft: string;\n depth1Icon: string;\n // 타이포 (theme.css 에 gnb 타이포 변수 없음 → 토큰 값 직접 명시)\n depth1FontSize: number;\n depth1LineHeight: number;\n depth1FontWeight: number;\n itemFontSize: number;\n itemLineHeight: number;\n }\n> = {\n // 사용자 명명: type=\"box\" → gnbBox 토큰(둥금), type=\"belt\" → gnbBelt 토큰(각짐)\n box: {\n topPaddingX: box('paddingX'),\n topGap: box('gap'), // 상단바 gap (버튼 간 세부 gap 은 --cmp-gnbBox-button-gap)\n bodyPadding: box('menu-body-padding'),\n itemPaddingRight: box('item-paddingRight'),\n itemGap: box('item-gap'),\n itemRadius: box('item-radius'),\n itemArrow: box('item-arrow'),\n depth1PaddingLeft: box('item-depth1-paddingLeft'),\n depth2PaddingLeft: box('item-depth2-paddingLeft'),\n depth3PaddingLeft: box('item-depth3-paddingLeft'),\n depth1Icon: box('item-depth1-icon'),\n depth1FontSize: 14, // token: gnbBox.item.typography.bold.fontSize\n depth1LineHeight: 24, // token: gnbBox.item.typography.bold.lineHeight\n depth1FontWeight: 700,\n itemFontSize: 14, // token: gnbBox.item.typography.default.fontSize\n itemLineHeight: 24, // token: gnbBox.item.typography.default.lineHeight\n },\n belt: {\n topPaddingX: belt('top-paddingX'),\n topGap: belt('top-gap'),\n bodyPadding: belt('body-paddingY'),\n itemPaddingRight: belt('item-paddingRight'),\n itemGap: belt('item-gap'),\n itemRadius: '0px', // belt 토큰에 radius 없음 → 풀폭 행\n itemArrow: belt('item-icon'),\n depth1PaddingLeft: belt('item-depth1-paddingLeft'),\n depth2PaddingLeft: belt('item-depth2-paddingLeft'),\n depth3PaddingLeft: belt('item-depth3-paddingLeft'),\n depth1Icon: box('item-depth1-icon'), // belt 토큰에 depth1-icon 없음 → box 값(20) 공유\n depth1FontSize: 13, // token: gnbBelt.item.depth1.typography.fontSize\n depth1LineHeight: 22, // token: gnbBelt.item.depth1.typography.lineHeight\n depth1FontWeight: 700,\n itemFontSize: 12, // token: gnbBelt.item.typography.default.fontSize\n itemLineHeight: 20, // token: gnbBelt.item.typography.default.lineHeight\n },\n};\n\n/** 색상(color 축). light → --cmp-light-* 색, dark → --cmp-dark-* 색 (gnb colortype 토큰) */\nexport const GNB_COLORS: Record<\n SGnbColor,\n {\n panelBg: string;\n topBorder: string;\n itemBgDefault: string;\n itemBgHover: string;\n itemBgSelected: string;\n itemColorDefault: string;\n itemColorActive: string;\n }\n> = {\n light: {\n panelBg: light('bg'),\n topBorder: light('divider'),\n itemBgDefault: light('item-bg-default'),\n itemBgHover: light('item-bg-hover'),\n itemBgSelected: light('item-bg-selected'),\n itemColorDefault: light('item-fg-default'),\n itemColorActive: light('item-fg-selected'),\n },\n dark: {\n panelBg: dark('bg'),\n topBorder: dark('divider'),\n itemBgDefault: dark('item-bg-default'),\n itemBgHover: dark('item-bg-hover'),\n itemBgSelected: dark('item-bg-selected'),\n itemColorDefault: dark('item-fg-default'),\n itemColorActive: dark('item-fg-selected'),\n },\n};\n\n/** 구조와 무관한 공통 레이아웃 */\nexport const GNB_COMMON = {\n topHeight: box('top-height'), // 56 (box=belt 동일)\n logoHeight: box('logo-height'), // 32\n itemPaddingY: box('item-paddingY'), // 6\n foldWidth: belt('fold-width'), // 48 (belt 토큰 공유)\n};\n\n/**\n * 메뉴 고정 폭. belt/box 모두 메뉴는 이 폭으로 좌측에 붙는다(토큰 없음 → 상수).\n * box 에서 SLayout 이 \"메뉴 오른쪽 = 페이지\" 영역을 잡을 때도 이 값을 쓴다.\n */\nexport const GNB_MENU_WIDTH = 240;\n\n/** value 로 선택된 아이템의 조상 value 목록(자기 자신 제외)을 반환 → 자동 펼침용 */\nexport function findGnbAncestors(items: SGnbMenuItem[], value: string): string[] {\n const walk = (list: SGnbMenuItem[], trail: string[]): string[] | null => {\n for (const item of list) {\n if (item.value === value) return trail;\n if (item.children) {\n const found = walk(item.children, [...trail, item.value]);\n if (found) return found;\n }\n }\n return null;\n };\n return walk(items, []) ?? [];\n}\n","'use client';\n\nimport {\n createContext,\n forwardRef,\n useCallback,\n useContext,\n useMemo,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n} from 'react';\nimport { cn } from '../../lib/cn';\nimport { GNB_COMMON, GNB_MENU_WIDTH, type SGnbType } from '../SGnb/gnb.config';\n\n/** 레이아웃 구조: box(풀폭 상단바 + 아래에 메뉴|페이지) / belt(좌측 레일 + 페이지 가로 분할) */\nexport type SLayoutType = SGnbType;\n\nexport interface SLayoutContextValue {\n /** 레이아웃 구조 — 자식 SGnb 가 배치·스타일을 맞추는 기준 */\n type: SLayoutType;\n /** GNB 접힘 상태 */\n folded: boolean;\n /** 접힘 상태 변경 */\n setFolded: (folded: boolean) => void;\n}\n\nexport const SLayoutContext = createContext<SLayoutContextValue | null>(null);\n\n/**\n * SLayout 의 상태를 읽고 제어한다. `<SLayout>` 밖에서 호출하면 throw.\n * 외부 버튼으로 GNB 를 접거나, 현재 구조(type)에 따라 분기할 때 사용한다.\n */\nexport function useSLayout(): SLayoutContextValue {\n const ctx = useContext(SLayoutContext);\n if (!ctx) throw new Error('useSLayout 은 <SLayout> 안에서만 사용할 수 있습니다.');\n return ctx;\n}\n\nexport interface SLayoutProps extends HTMLAttributes<HTMLDivElement> {\n /** 레이아웃 구조: box(세로) / belt(가로). 자식 SGnb 가 이 값을 따른다. */\n type?: SLayoutType;\n /** GNB 접힘 상태 (controlled) */\n folded?: boolean;\n /** GNB 초기 접힘 상태 (uncontrolled) */\n defaultFolded?: boolean;\n /** 접힘 상태 변경 */\n onFoldedChange?: (folded: boolean) => void;\n}\n\n/**\n * SLayout — 앱 셸 프레임 겸 context provider.\n * type 하나로 배치를 결정하고 overflow:hidden 으로 스크롤 경계를 만든다.\n * 자식 SGnb·SPage 는 context 에서 type·folded 를 읽으므로 앱은 type 을 여기 한 곳에만 쓴다.\n * 높이는 기본 100%(부모를 채움) — 앱 루트로 쓸 땐 부모를 뷰포트 높이로 잡는다.\n *\n * - belt: [GNB(자기 폭 소유) | 페이지] 가로 flex.\n * - box: 상단바가 풀폭이고 그 아래에 [메뉴 | 페이지] 가 나란히 서야 하는데, SGnb 가 상단바와\n * 메뉴를 한 엘리먼트로 갖고 있어 사이에 끼워넣을 수 없다. 그래서 grid 로 깔고\n * SGnb 를 전 셀에 겹친 뒤(root 는 투명) SPage 가 (2행,2열) = \"메뉴 오른쪽 투명 영역\" 을 차지한다.\n * 접으면 메뉴가 사라지므로 1열을 0 으로 접어 페이지가 전폭을 쓴다.\n */\nexport const SLayout = forwardRef<HTMLDivElement, SLayoutProps>(function SLayout(\n {\n type = 'box',\n folded,\n defaultFolded = false,\n onFoldedChange,\n className,\n style,\n children,\n ...rest\n },\n ref,\n) {\n const [uncontrolledFolded, setUncontrolledFolded] = useState(defaultFolded);\n const isControlled = folded !== undefined;\n const currentFolded = isControlled ? folded : uncontrolledFolded;\n\n const setFolded = useCallback(\n (next: boolean) => {\n if (!isControlled) setUncontrolledFolded(next);\n onFoldedChange?.(next);\n },\n [isControlled, onFoldedChange],\n );\n\n const ctx = useMemo<SLayoutContextValue>(\n () => ({ type, folded: currentFolded, setFolded }),\n [type, currentFolded, setFolded],\n );\n\n const isBox = type === 'box';\n const frameStyle: CSSProperties | undefined = isBox\n ? {\n gridTemplateColumns: `${currentFolded ? 0 : GNB_MENU_WIDTH}px 1fr`,\n gridTemplateRows: `${GNB_COMMON.topHeight} 1fr`,\n }\n : undefined;\n\n return (\n <SLayoutContext.Provider value={ctx}>\n <div\n ref={ref}\n data-type={type}\n className={cn(\n 'box-border h-full overflow-hidden',\n isBox ? 'grid' : 'flex flex-row',\n className,\n )}\n style={{ ...frameStyle, ...style }}\n {...rest}\n >\n {children}\n </div>\n </SLayoutContext.Provider>\n );\n});\n","import { forwardRef, useContext, type CSSProperties, type HTMLAttributes } from 'react';\nimport { cn } from '../../lib/cn';\nimport { SLayoutContext } from '../SLayout/SLayout';\n\n// 자체 prop 없이 표준 HTML 속성만 받는다.\n// docs:gen 은 export interface 만 수집하므로(gen-docs.mjs) 타입 별칭 대신 interface 로 둔다.\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface SPageProps extends HTMLAttributes<HTMLElement> {}\n\n/**\n * SPage — SLayout 안에서 남은 공간을 채우는 스크롤 콘텐츠 영역.\n * 스크롤은 SLayout(overflow:hidden)이 잡아둔 경계 안에서 이 영역에만 생긴다.\n * 패딩·배경은 className 으로 덮어쓸 수 있다.\n *\n * - belt: flex 자식으로 남은 가로 공간을 채운다.\n * - box: SLayout 이 깐 grid 의 (2행,2열) = 상단바 아래·메뉴 오른쪽 자리를 차지한다.\n * (SGnb 가 전 셀에 겹쳐 있지만 그 영역은 투명해서 SPage 가 보인다)\n */\nexport const SPage = forwardRef<HTMLElement, SPageProps>(function SPage(\n { className, style, children, ...rest },\n ref,\n) {\n const layout = useContext(SLayoutContext);\n const gridStyle: CSSProperties | undefined =\n layout?.type === 'box' ? { gridColumn: 2, gridRow: 2 } : undefined;\n\n return (\n <main\n ref={ref}\n className={cn('box-border min-h-0 min-w-0 flex-1 overflow-auto p-24', className)}\n style={{ ...gridStyle, ...style }}\n {...rest}\n >\n {children}\n </main>\n );\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SPage, type SPageProps } from './SPage';
|
|
@@ -2998,6 +2998,8 @@ function SGhostButton({
|
|
|
2998
2998
|
tooltipText,
|
|
2999
2999
|
disabled = false,
|
|
3000
3000
|
onClick,
|
|
3001
|
+
anchorClassName,
|
|
3002
|
+
anchorStyle,
|
|
3001
3003
|
className,
|
|
3002
3004
|
style
|
|
3003
3005
|
}) {
|
|
@@ -3020,6 +3022,8 @@ function SGhostButton({
|
|
|
3020
3022
|
};
|
|
3021
3023
|
const s = SIZE[size];
|
|
3022
3024
|
const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
|
|
3025
|
+
const text = (tooltipText ?? "").trim();
|
|
3026
|
+
const hasTooltip = text !== "";
|
|
3023
3027
|
const btn = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3024
3028
|
"button",
|
|
3025
3029
|
{
|
|
@@ -3060,13 +3064,13 @@ function SGhostButton({
|
|
|
3060
3064
|
]
|
|
3061
3065
|
}
|
|
3062
3066
|
);
|
|
3063
|
-
|
|
3064
|
-
if (text === "") return btn;
|
|
3067
|
+
if (!hasTooltip) return btn;
|
|
3065
3068
|
return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: setOpen, children: [
|
|
3066
3069
|
/* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3067
3070
|
"span",
|
|
3068
3071
|
{
|
|
3069
|
-
className: "inline-flex",
|
|
3072
|
+
className: cn("inline-flex", anchorClassName),
|
|
3073
|
+
style: anchorStyle,
|
|
3070
3074
|
onMouseEnter: openNow,
|
|
3071
3075
|
onMouseLeave: startHide,
|
|
3072
3076
|
onFocus: openNow,
|