sellmate-design-system-react 2.2.0 → 3.1.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/AGENTS.md +661 -0
- package/README.md +85 -0
- package/dist/components/SActionModal/README.md +41 -0
- package/dist/components/SBadge/README.md +25 -0
- package/dist/components/SBarcodeInput/README.md +64 -0
- package/dist/components/SButton/README.md +49 -0
- package/dist/components/SCalendar/README.md +42 -0
- package/dist/components/SCallout/README.md +29 -0
- package/dist/components/SCallout/SCallout.cjs +76 -1
- package/dist/components/SCallout/SCallout.cjs.map +1 -1
- package/dist/components/SCallout/SCallout.d.ts +6 -2
- package/dist/components/SCallout/SCallout.js +76 -1
- package/dist/components/SCallout/SCallout.js.map +1 -1
- package/dist/components/SCard/README.md +12 -0
- package/dist/components/SCheckbox/README.md +44 -0
- package/dist/components/SChip/README.md +57 -0
- package/dist/components/SChipInput/README.md +77 -0
- package/dist/components/SCircleProgress/README.md +35 -0
- package/dist/components/SConfirmModal/README.md +61 -0
- package/dist/components/SDatePicker/README.md +62 -0
- package/dist/components/SDateRangePicker/README.md +64 -0
- package/dist/components/SDivider/README.md +25 -0
- package/dist/components/SDraggableItem/README.md +40 -0
- package/dist/components/SDraggableItem/SDraggableItem.cjs +3079 -0
- package/dist/components/SDraggableItem/SDraggableItem.cjs.map +1 -0
- package/dist/components/SDraggableItem/SDraggableItem.d.ts +33 -0
- package/dist/components/SDraggableItem/SDraggableItem.js +3077 -0
- package/dist/components/SDraggableItem/SDraggableItem.js.map +1 -0
- package/dist/components/SDraggableItem/index.d.ts +1 -0
- package/dist/components/SDropdownButton/README.md +50 -0
- package/dist/components/SExpansionItem/README.md +40 -0
- package/dist/components/SExpansionItem/SExpansionItem.cjs +6 -15
- package/dist/components/SExpansionItem/SExpansionItem.cjs.map +1 -1
- package/dist/components/SExpansionItem/SExpansionItem.js +6 -15
- package/dist/components/SExpansionItem/SExpansionItem.js.map +1 -1
- package/dist/components/SField/README.md +91 -0
- package/dist/components/SFilePicker/README.md +72 -0
- package/dist/components/SForm/README.md +31 -0
- package/dist/components/SGhostButton/README.md +76 -0
- package/dist/components/SGnb/README.md +56 -0
- package/dist/components/SGuide/README.md +34 -0
- package/dist/components/SIcon/README.md +91 -0
- package/dist/components/SInput/README.md +66 -0
- package/dist/components/SKeyValueTable/README.md +59 -0
- package/dist/components/SLayout/README.md +42 -0
- package/dist/components/SLinearProgress/README.md +17 -0
- package/dist/components/SList/README.md +14 -0
- package/dist/components/SList/SList.cjs +38 -0
- package/dist/components/SList/SList.cjs.map +1 -0
- package/dist/components/SList/SList.d.ts +10 -0
- package/dist/components/SList/SList.js +36 -0
- package/dist/components/SList/SList.js.map +1 -0
- package/dist/components/SList/index.d.ts +1 -0
- package/dist/components/SListItem/README.md +19 -0
- package/dist/components/SListItem/SListItem.cjs +83 -0
- package/dist/components/SListItem/SListItem.cjs.map +1 -0
- package/dist/components/SListItem/SListItem.d.ts +26 -0
- package/dist/components/SListItem/SListItem.js +81 -0
- package/dist/components/SListItem/SListItem.js.map +1 -0
- package/dist/components/SListItem/index.d.ts +1 -0
- package/dist/components/SLoadingContainer/README.md +29 -0
- package/dist/components/SLoadingModal/README.md +52 -0
- package/dist/components/SModal/README.md +192 -0
- package/dist/components/SModalContainer/README.md +49 -0
- package/dist/components/SNumberInput/README.md +74 -0
- package/dist/components/SPage/README.md +25 -0
- package/dist/components/SPagination/README.md +40 -0
- package/dist/components/SPopover/README.md +54 -0
- package/dist/components/SPopup/README.md +38 -0
- package/dist/components/SPortal/README.md +51 -0
- package/dist/components/SRadio/README.md +57 -0
- package/dist/components/SRadioButton/README.md +24 -0
- package/dist/components/SScrollArea/README.md +16 -0
- package/dist/components/SSectionHeaderCard/README.md +51 -0
- package/dist/components/SSectionHeaderCard/SSectionHeaderCard.cjs +3414 -0
- package/dist/components/SSectionHeaderCard/SSectionHeaderCard.cjs.map +1 -0
- package/dist/components/SSectionHeaderCard/SSectionHeaderCard.d.ts +32 -0
- package/dist/components/SSectionHeaderCard/SSectionHeaderCard.js +3392 -0
- package/dist/components/SSectionHeaderCard/SSectionHeaderCard.js.map +1 -0
- package/dist/components/SSectionHeaderCard/index.d.ts +1 -0
- package/dist/components/SSelect/README.md +79 -0
- package/dist/components/SStepper/README.md +30 -0
- package/dist/components/SSwitch/README.md +24 -0
- package/dist/components/STable/README.md +82 -0
- package/dist/components/STableBar/README.md +28 -0
- package/dist/components/STabs/README.md +37 -0
- package/dist/components/STag/README.md +44 -0
- package/dist/components/STextLink/README.md +48 -0
- package/dist/components/STextarea/README.md +60 -0
- package/dist/components/STimePicker/README.md +65 -0
- package/dist/components/STimeRangePicker/README.md +63 -0
- package/dist/components/SToast/README.md +70 -0
- package/dist/components/SToggle/README.md +22 -0
- package/dist/components/STooltip/README.md +57 -0
- package/dist/index.cjs +815 -419
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +815 -423
- package/dist/index.js.map +1 -1
- package/dist/llms.txt +3496 -0
- package/dist/styles.css +545 -19
- package/dist/theme.css +22 -0
- package/eslint/index.mjs +66 -0
- package/eslint/lib/class-names.mjs +119 -0
- package/eslint/lib/table-column.mjs +53 -0
- package/eslint/rules/no-arbitrary-class.mjs +91 -0
- package/eslint/rules/no-off-scale-spacing.mjs +67 -0
- package/eslint/rules/no-raw-html-control.mjs +117 -0
- package/eslint/rules/prefer-typo-preset.mjs +73 -0
- package/eslint/rules/require-locale-number.mjs +78 -0
- package/eslint/rules/table-numeric-align.mjs +93 -0
- package/eslint/scale.gen.mjs +16 -0
- package/package.json +9 -4
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var clsx = require('clsx');
|
|
6
|
+
var tailwindMerge = require('tailwind-merge');
|
|
7
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
8
|
+
|
|
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 SList = react.forwardRef(function SList2({ className, children, useGap = false, usePadding = false, ...rest }, ref) {
|
|
20
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
21
|
+
"div",
|
|
22
|
+
{
|
|
23
|
+
ref,
|
|
24
|
+
className: cn(
|
|
25
|
+
useGap && "flex flex-col gap-(--cmp-list-group-gap)",
|
|
26
|
+
usePadding && "p-(--cmp-list-group-padding)",
|
|
27
|
+
className
|
|
28
|
+
),
|
|
29
|
+
...rest,
|
|
30
|
+
children
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
SList.displayName = "SList";
|
|
35
|
+
|
|
36
|
+
exports.SList = SList;
|
|
37
|
+
//# sourceMappingURL=SList.cjs.map
|
|
38
|
+
//# sourceMappingURL=SList.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/cn.ts","../../../src/components/SList/SList.tsx"],"names":["extendTailwindMerge","clsx","forwardRef","SList","jsx"],"mappings":";;;;;;;AAQA,IAAM,UAAUA,iCAAA,CAAoB;AAAA,EAClC,MAAA,EAAQ;AAAA,IACN,WAAA,EAAa;AAAA,MACX,aAAa,CAAC,EAAE,IAAA,EAAM,CAAC,MAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,MAAM,IAAA,EAAM,IAAA,EAAM,MAAM,IAAA,EAAM,IAAI,GAAG;AAAA;AAC5F;AAEJ,CAAC,CAAA;AAMM,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQC,SAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACRO,IAAM,KAAA,GAAQC,gBAAA,CAAuC,SAASC,MAAAA,CACnE,EAAE,SAAA,EAAW,QAAA,EAAU,MAAA,GAAS,KAAA,EAAO,UAAA,GAAa,KAAA,EAAO,GAAG,IAAA,IAC9D,GAAA,EACA;AACA,EAAA,uBACEC,cAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,SAAA,EAAW,EAAA;AAAA,QACT,MAAA,IAAU,0CAAA;AAAA,QACV,UAAA,IAAc,8BAAA;AAAA,QACd;AAAA,OACF;AAAA,MACC,GAAG,IAAA;AAAA,MAEH;AAAA;AAAA,GACH;AAEJ,CAAC;AAED,KAAA,CAAM,WAAA,GAAc,OAAA","file":"SList.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","'use client';\n\nimport { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cn } from '../../lib/cn';\n\nexport interface SListProps extends HTMLAttributes<HTMLDivElement> {\n /** 리스트 컨테이너 내부에 렌더링할 내용 */\n children?: ReactNode;\n /** 리스트 아이템 사이 gap 토큰 적용 여부 */\n useGap?: boolean;\n /** 리스트 컨테이너 padding 토큰 적용 여부 */\n usePadding?: boolean;\n}\n\nexport const SList = forwardRef<HTMLDivElement, SListProps>(function SList(\n { className, children, useGap = false, usePadding = false, ...rest },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn(\n useGap && 'flex flex-col gap-(--cmp-list-group-gap)',\n usePadding && 'p-(--cmp-list-group-padding)',\n className,\n )}\n {...rest}\n >\n {children}\n </div>\n );\n});\n\nSList.displayName = 'SList';\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
export interface SListProps extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
/** 리스트 컨테이너 내부에 렌더링할 내용 */
|
|
4
|
+
children?: ReactNode;
|
|
5
|
+
/** 리스트 아이템 사이 gap 토큰 적용 여부 */
|
|
6
|
+
useGap?: boolean;
|
|
7
|
+
/** 리스트 컨테이너 padding 토큰 적용 여부 */
|
|
8
|
+
usePadding?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare const SList: import("react").ForwardRefExoticComponent<SListProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
|
+
import { extendTailwindMerge } from 'tailwind-merge';
|
|
5
|
+
import { jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
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 SList = forwardRef(function SList2({ className, children, useGap = false, usePadding = false, ...rest }, ref) {
|
|
18
|
+
return /* @__PURE__ */ jsx(
|
|
19
|
+
"div",
|
|
20
|
+
{
|
|
21
|
+
ref,
|
|
22
|
+
className: cn(
|
|
23
|
+
useGap && "flex flex-col gap-(--cmp-list-group-gap)",
|
|
24
|
+
usePadding && "p-(--cmp-list-group-padding)",
|
|
25
|
+
className
|
|
26
|
+
),
|
|
27
|
+
...rest,
|
|
28
|
+
children
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
SList.displayName = "SList";
|
|
33
|
+
|
|
34
|
+
export { SList };
|
|
35
|
+
//# sourceMappingURL=SList.js.map
|
|
36
|
+
//# sourceMappingURL=SList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/cn.ts","../../../src/components/SList/SList.tsx"],"names":["SList"],"mappings":";;;;;AAQA,IAAM,UAAU,mBAAA,CAAoB;AAAA,EAClC,MAAA,EAAQ;AAAA,IACN,WAAA,EAAa;AAAA,MACX,aAAa,CAAC,EAAE,IAAA,EAAM,CAAC,MAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,MAAM,IAAA,EAAM,IAAA,EAAM,MAAM,IAAA,EAAM,IAAI,GAAG;AAAA;AAC5F;AAEJ,CAAC,CAAA;AAMM,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACRO,IAAM,KAAA,GAAQ,UAAA,CAAuC,SAASA,MAAAA,CACnE,EAAE,SAAA,EAAW,QAAA,EAAU,MAAA,GAAS,KAAA,EAAO,UAAA,GAAa,KAAA,EAAO,GAAG,IAAA,IAC9D,GAAA,EACA;AACA,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,SAAA,EAAW,EAAA;AAAA,QACT,MAAA,IAAU,0CAAA;AAAA,QACV,UAAA,IAAc,8BAAA;AAAA,QACd;AAAA,OACF;AAAA,MACC,GAAG,IAAA;AAAA,MAEH;AAAA;AAAA,GACH;AAEJ,CAAC;AAED,KAAA,CAAM,WAAA,GAAc,OAAA","file":"SList.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","'use client';\n\nimport { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cn } from '../../lib/cn';\n\nexport interface SListProps extends HTMLAttributes<HTMLDivElement> {\n /** 리스트 컨테이너 내부에 렌더링할 내용 */\n children?: ReactNode;\n /** 리스트 아이템 사이 gap 토큰 적용 여부 */\n useGap?: boolean;\n /** 리스트 컨테이너 padding 토큰 적용 여부 */\n usePadding?: boolean;\n}\n\nexport const SList = forwardRef<HTMLDivElement, SListProps>(function SList(\n { className, children, useGap = false, usePadding = false, ...rest },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn(\n useGap && 'flex flex-col gap-(--cmp-list-group-gap)',\n usePadding && 'p-(--cmp-list-group-padding)',\n className,\n )}\n {...rest}\n >\n {children}\n </div>\n );\n});\n\nSList.displayName = 'SList';\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SList, type SListProps } from './SList';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# SListItem
|
|
2
|
+
|
|
3
|
+
> 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
|
|
4
|
+
|
|
5
|
+
### SListItem
|
|
6
|
+
|
|
7
|
+
#### Props
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Default | Description |
|
|
10
|
+
|------|------|---------|-------------|
|
|
11
|
+
| `title` | `SListItemSlot` | — | 리스트 아이템 제목 |
|
|
12
|
+
| `depth?` | `1 \| 2 \| 3` | `1` | 중첩 단계. Figma 기준 1~3단계 |
|
|
13
|
+
| `leading?` | `SListItemSlot` | — | 타이틀 앞에 표시할 아이콘/콘텐츠 |
|
|
14
|
+
| `trailing?` | `SListItemSlot` | — | 타이틀 뒤에 표시할 태그/콘텐츠 |
|
|
15
|
+
| `bordered?` | `boolean` | `false` | 외곽 테두리 사용 여부 |
|
|
16
|
+
| `accentStripe?` | `boolean` | `false` | 아이템 왼쪽 accent stripe 표시 여부 |
|
|
17
|
+
| `dense?` | `boolean` | `false` | 조밀한 높이 사용 여부 |
|
|
18
|
+
| `disabled?` | `boolean` | `false` | 비활성 상태 여부 |
|
|
19
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var clsx = require('clsx');
|
|
6
|
+
var tailwindMerge = require('tailwind-merge');
|
|
7
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
8
|
+
|
|
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 SListItem = react.forwardRef(function SListItem2({
|
|
20
|
+
title,
|
|
21
|
+
depth = 1,
|
|
22
|
+
leading,
|
|
23
|
+
trailing,
|
|
24
|
+
bordered = false,
|
|
25
|
+
accentStripe = false,
|
|
26
|
+
dense = false,
|
|
27
|
+
disabled = false,
|
|
28
|
+
className,
|
|
29
|
+
onMouseEnter,
|
|
30
|
+
onMouseLeave,
|
|
31
|
+
...rest
|
|
32
|
+
}, ref) {
|
|
33
|
+
const [hovered, setHovered] = react.useState(false);
|
|
34
|
+
const renderState = { hovered, disabled };
|
|
35
|
+
const titleNode = typeof title === "function" ? title(renderState) : title;
|
|
36
|
+
const leadingNode = typeof leading === "function" ? leading(renderState) : leading;
|
|
37
|
+
const trailingNode = typeof trailing === "function" ? trailing(renderState) : trailing;
|
|
38
|
+
const handleMouseEnter = (event) => {
|
|
39
|
+
if (!disabled) setHovered(true);
|
|
40
|
+
onMouseEnter?.(event);
|
|
41
|
+
};
|
|
42
|
+
const handleMouseLeave = (event) => {
|
|
43
|
+
setHovered(false);
|
|
44
|
+
onMouseLeave?.(event);
|
|
45
|
+
};
|
|
46
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
47
|
+
"div",
|
|
48
|
+
{
|
|
49
|
+
ref,
|
|
50
|
+
"aria-disabled": disabled || void 0,
|
|
51
|
+
onMouseEnter: handleMouseEnter,
|
|
52
|
+
onMouseLeave: handleMouseLeave,
|
|
53
|
+
className: cn(
|
|
54
|
+
"group/list-item relative flex w-full items-center justify-between bg-(--cmp-listItem-bg-default) text-left text-[14px] font-bold leading-[24px] text-(--cmp-listItem-text-default)",
|
|
55
|
+
disabled ? "pointer-events-none cursor-not-allowed text-(--sys-color-disabled-text)" : "hover:bg-(--cmp-listItem-bg-hover)",
|
|
56
|
+
dense ? "h-[32px] py-(--cmp-listItem-paddingY-dense)" : "h-[40px] py-(--cmp-listItem-paddingY-normal)",
|
|
57
|
+
"gap-(--cmp-listItem-gap)",
|
|
58
|
+
"pr-(--cmp-listItem-paddingX)",
|
|
59
|
+
depth === 2 ? "pl-[calc(var(--cmp-listItem-paddingX)+32px)]" : depth === 3 ? "pl-[calc(var(--cmp-listItem-paddingX)+64px)]" : "pl-(--cmp-listItem-paddingX)",
|
|
60
|
+
bordered ? "overflow-hidden rounded-(--cmp-listItem-radius-bordered) border border-(--cmp-listItem-border-default) hover:border-(--cmp-listItem-bordered-border-hover)" : "border-b border-(--cmp-listItem-border-default)",
|
|
61
|
+
className
|
|
62
|
+
),
|
|
63
|
+
...rest,
|
|
64
|
+
children: [
|
|
65
|
+
accentStripe && !bordered && /* @__PURE__ */ jsxRuntime.jsx(
|
|
66
|
+
"span",
|
|
67
|
+
{
|
|
68
|
+
"aria-hidden": "true",
|
|
69
|
+
className: "pointer-events-none absolute bottom-[-1px] left-0 top-0 w-(--cmp-listItem-accentStripe-width) bg-(--cmp-listItem-accentStripe-color)"
|
|
70
|
+
}
|
|
71
|
+
),
|
|
72
|
+
leadingNode,
|
|
73
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex min-w-0 flex-1 flex-col", children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: titleNode }) }),
|
|
74
|
+
trailingNode
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
SListItem.displayName = "SListItem";
|
|
80
|
+
|
|
81
|
+
exports.SListItem = SListItem;
|
|
82
|
+
//# sourceMappingURL=SListItem.cjs.map
|
|
83
|
+
//# sourceMappingURL=SListItem.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/cn.ts","../../../src/components/SListItem/SListItem.tsx"],"names":["extendTailwindMerge","clsx","forwardRef","SListItem","useState","jsxs","jsx"],"mappings":";;;;;;;AAQA,IAAM,UAAUA,iCAAA,CAAoB;AAAA,EAClC,MAAA,EAAQ;AAAA,IACN,WAAA,EAAa;AAAA,MACX,aAAa,CAAC,EAAE,IAAA,EAAM,CAAC,MAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,MAAM,IAAA,EAAM,IAAA,EAAM,MAAM,IAAA,EAAM,IAAI,GAAG;AAAA;AAC5F;AAEJ,CAAC,CAAA;AAMM,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQC,SAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACUO,IAAM,SAAA,GAAYC,gBAAA,CAA2C,SAASC,UAAAA,CAC3E;AAAA,EACE,KAAA;AAAA,EACA,KAAA,GAAQ,CAAA;AAAA,EACR,OAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX,YAAA,GAAe,KAAA;AAAA,EACf,KAAA,GAAQ,KAAA;AAAA,EACR,QAAA,GAAW,KAAA;AAAA,EACX,SAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA,GAAG;AACL,CAAA,EACA,GAAA,EACA;AACA,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIC,eAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,WAAA,GAAc,EAAE,OAAA,EAAS,QAAA,EAAS;AACxC,EAAA,MAAM,YAAY,OAAO,KAAA,KAAU,UAAA,GAAa,KAAA,CAAM,WAAW,CAAA,GAAI,KAAA;AACrE,EAAA,MAAM,cAAc,OAAO,OAAA,KAAY,UAAA,GAAa,OAAA,CAAQ,WAAW,CAAA,GAAI,OAAA;AAC3E,EAAA,MAAM,eAAe,OAAO,QAAA,KAAa,UAAA,GAAa,QAAA,CAAS,WAAW,CAAA,GAAI,QAAA;AAE9E,EAAA,MAAM,gBAAA,GAAmB,CAAC,KAAA,KAAsC;AAC9D,IAAA,IAAI,CAAC,QAAA,EAAU,UAAA,CAAW,IAAI,CAAA;AAC9B,IAAA,YAAA,GAAe,KAAK,CAAA;AAAA,EACtB,CAAA;AAEA,EAAA,MAAM,gBAAA,GAAmB,CAAC,KAAA,KAAsC;AAC9D,IAAA,UAAA,CAAW,KAAK,CAAA;AAChB,IAAA,YAAA,GAAe,KAAK,CAAA;AAAA,EACtB,CAAA;AAEA,EAAA,uBACEC,eAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,iBAAe,QAAA,IAAY,MAAA;AAAA,MAC3B,YAAA,EAAc,gBAAA;AAAA,MACd,YAAA,EAAc,gBAAA;AAAA,MACd,SAAA,EAAW,EAAA;AAAA,QACT,oLAAA;AAAA,QACA,WACI,yEAAA,GACA,oCAAA;AAAA,QACJ,QACI,6CAAA,GACA,8CAAA;AAAA,QACJ,0BAAA;AAAA,QACA,8BAAA;AAAA,QACA,KAAA,KAAU,CAAA,GACN,8CAAA,GACA,KAAA,KAAU,IACR,8CAAA,GACA,8BAAA;AAAA,QACN,WACI,4JAAA,GACA,iDAAA;AAAA,QACJ;AAAA,OACF;AAAA,MACC,GAAG,IAAA;AAAA,MAEH,QAAA,EAAA;AAAA,QAAA,YAAA,IAAgB,CAAC,QAAA,oBAChBC,cAAA;AAAA,UAAC,MAAA;AAAA,UAAA;AAAA,YACC,aAAA,EAAY,MAAA;AAAA,YACZ,SAAA,EAAU;AAAA;AAAA,SACZ;AAAA,QAED,WAAA;AAAA,uCACA,MAAA,EAAA,EAAK,SAAA,EAAU,gCACd,QAAA,kBAAAA,cAAA,CAAC,MAAA,EAAA,EAAM,qBAAU,CAAA,EACnB,CAAA;AAAA,QACC;AAAA;AAAA;AAAA,GACH;AAEJ,CAAC;AAED,SAAA,CAAU,WAAA,GAAc,WAAA","file":"SListItem.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","'use client';\n\nimport { forwardRef, useState, type HTMLAttributes, type MouseEvent, type ReactNode } from 'react';\nimport { cn } from '../../lib/cn';\n\nexport interface SListItemRenderState {\n hovered: boolean;\n disabled: boolean;\n}\n\nexport type SListItemRenderProp = (state: SListItemRenderState) => ReactNode;\nexport type SListItemSlot = ReactNode | SListItemRenderProp;\n\nexport interface SListItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> {\n /** 리스트 아이템 제목 */\n title: SListItemSlot;\n /** 중첩 단계. Figma 기준 1~3단계 */\n depth?: 1 | 2 | 3;\n /** 타이틀 앞에 표시할 아이콘/콘텐츠 */\n leading?: SListItemSlot;\n /** 타이틀 뒤에 표시할 태그/콘텐츠 */\n trailing?: SListItemSlot;\n /** 외곽 테두리 사용 여부 */\n bordered?: boolean;\n /** 아이템 왼쪽 accent stripe 표시 여부 */\n accentStripe?: boolean;\n /** 조밀한 높이 사용 여부 */\n dense?: boolean;\n /** 비활성 상태 여부 */\n disabled?: boolean;\n}\n\nexport const SListItem = forwardRef<HTMLDivElement, SListItemProps>(function SListItem(\n {\n title,\n depth = 1,\n leading,\n trailing,\n bordered = false,\n accentStripe = false,\n dense = false,\n disabled = false,\n className,\n onMouseEnter,\n onMouseLeave,\n ...rest\n },\n ref,\n) {\n const [hovered, setHovered] = useState(false);\n const renderState = { hovered, disabled };\n const titleNode = typeof title === 'function' ? title(renderState) : title;\n const leadingNode = typeof leading === 'function' ? leading(renderState) : leading;\n const trailingNode = typeof trailing === 'function' ? trailing(renderState) : trailing;\n\n const handleMouseEnter = (event: MouseEvent<HTMLDivElement>) => {\n if (!disabled) setHovered(true);\n onMouseEnter?.(event);\n };\n\n const handleMouseLeave = (event: MouseEvent<HTMLDivElement>) => {\n setHovered(false);\n onMouseLeave?.(event);\n };\n\n return (\n <div\n ref={ref}\n aria-disabled={disabled || undefined}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n className={cn(\n 'group/list-item relative flex w-full items-center justify-between bg-(--cmp-listItem-bg-default) text-left text-[14px] font-bold leading-[24px] text-(--cmp-listItem-text-default)',\n disabled\n ? 'pointer-events-none cursor-not-allowed text-(--sys-color-disabled-text)'\n : 'hover:bg-(--cmp-listItem-bg-hover)',\n dense\n ? 'h-[32px] py-(--cmp-listItem-paddingY-dense)'\n : 'h-[40px] py-(--cmp-listItem-paddingY-normal)',\n 'gap-(--cmp-listItem-gap)',\n 'pr-(--cmp-listItem-paddingX)',\n depth === 2\n ? 'pl-[calc(var(--cmp-listItem-paddingX)+32px)]'\n : depth === 3\n ? 'pl-[calc(var(--cmp-listItem-paddingX)+64px)]'\n : 'pl-(--cmp-listItem-paddingX)',\n bordered\n ? 'overflow-hidden rounded-(--cmp-listItem-radius-bordered) border border-(--cmp-listItem-border-default) hover:border-(--cmp-listItem-bordered-border-hover)'\n : 'border-b border-(--cmp-listItem-border-default)',\n className,\n )}\n {...rest}\n >\n {accentStripe && !bordered && (\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute bottom-[-1px] left-0 top-0 w-(--cmp-listItem-accentStripe-width) bg-(--cmp-listItem-accentStripe-color)\"\n />\n )}\n {leadingNode}\n <span className=\"flex min-w-0 flex-1 flex-col\">\n <span>{titleNode}</span>\n </span>\n {trailingNode}\n </div>\n );\n});\n\nSListItem.displayName = 'SListItem';\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
export interface SListItemRenderState {
|
|
3
|
+
hovered: boolean;
|
|
4
|
+
disabled: boolean;
|
|
5
|
+
}
|
|
6
|
+
export type SListItemRenderProp = (state: SListItemRenderState) => ReactNode;
|
|
7
|
+
export type SListItemSlot = ReactNode | SListItemRenderProp;
|
|
8
|
+
export interface SListItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> {
|
|
9
|
+
/** 리스트 아이템 제목 */
|
|
10
|
+
title: SListItemSlot;
|
|
11
|
+
/** 중첩 단계. Figma 기준 1~3단계 */
|
|
12
|
+
depth?: 1 | 2 | 3;
|
|
13
|
+
/** 타이틀 앞에 표시할 아이콘/콘텐츠 */
|
|
14
|
+
leading?: SListItemSlot;
|
|
15
|
+
/** 타이틀 뒤에 표시할 태그/콘텐츠 */
|
|
16
|
+
trailing?: SListItemSlot;
|
|
17
|
+
/** 외곽 테두리 사용 여부 */
|
|
18
|
+
bordered?: boolean;
|
|
19
|
+
/** 아이템 왼쪽 accent stripe 표시 여부 */
|
|
20
|
+
accentStripe?: boolean;
|
|
21
|
+
/** 조밀한 높이 사용 여부 */
|
|
22
|
+
dense?: boolean;
|
|
23
|
+
/** 비활성 상태 여부 */
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare const SListItem: import("react").ForwardRefExoticComponent<SListItemProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { forwardRef, useState } from 'react';
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
|
+
import { extendTailwindMerge } from 'tailwind-merge';
|
|
5
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
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 SListItem = forwardRef(function SListItem2({
|
|
18
|
+
title,
|
|
19
|
+
depth = 1,
|
|
20
|
+
leading,
|
|
21
|
+
trailing,
|
|
22
|
+
bordered = false,
|
|
23
|
+
accentStripe = false,
|
|
24
|
+
dense = false,
|
|
25
|
+
disabled = false,
|
|
26
|
+
className,
|
|
27
|
+
onMouseEnter,
|
|
28
|
+
onMouseLeave,
|
|
29
|
+
...rest
|
|
30
|
+
}, ref) {
|
|
31
|
+
const [hovered, setHovered] = useState(false);
|
|
32
|
+
const renderState = { hovered, disabled };
|
|
33
|
+
const titleNode = typeof title === "function" ? title(renderState) : title;
|
|
34
|
+
const leadingNode = typeof leading === "function" ? leading(renderState) : leading;
|
|
35
|
+
const trailingNode = typeof trailing === "function" ? trailing(renderState) : trailing;
|
|
36
|
+
const handleMouseEnter = (event) => {
|
|
37
|
+
if (!disabled) setHovered(true);
|
|
38
|
+
onMouseEnter?.(event);
|
|
39
|
+
};
|
|
40
|
+
const handleMouseLeave = (event) => {
|
|
41
|
+
setHovered(false);
|
|
42
|
+
onMouseLeave?.(event);
|
|
43
|
+
};
|
|
44
|
+
return /* @__PURE__ */ jsxs(
|
|
45
|
+
"div",
|
|
46
|
+
{
|
|
47
|
+
ref,
|
|
48
|
+
"aria-disabled": disabled || void 0,
|
|
49
|
+
onMouseEnter: handleMouseEnter,
|
|
50
|
+
onMouseLeave: handleMouseLeave,
|
|
51
|
+
className: cn(
|
|
52
|
+
"group/list-item relative flex w-full items-center justify-between bg-(--cmp-listItem-bg-default) text-left text-[14px] font-bold leading-[24px] text-(--cmp-listItem-text-default)",
|
|
53
|
+
disabled ? "pointer-events-none cursor-not-allowed text-(--sys-color-disabled-text)" : "hover:bg-(--cmp-listItem-bg-hover)",
|
|
54
|
+
dense ? "h-[32px] py-(--cmp-listItem-paddingY-dense)" : "h-[40px] py-(--cmp-listItem-paddingY-normal)",
|
|
55
|
+
"gap-(--cmp-listItem-gap)",
|
|
56
|
+
"pr-(--cmp-listItem-paddingX)",
|
|
57
|
+
depth === 2 ? "pl-[calc(var(--cmp-listItem-paddingX)+32px)]" : depth === 3 ? "pl-[calc(var(--cmp-listItem-paddingX)+64px)]" : "pl-(--cmp-listItem-paddingX)",
|
|
58
|
+
bordered ? "overflow-hidden rounded-(--cmp-listItem-radius-bordered) border border-(--cmp-listItem-border-default) hover:border-(--cmp-listItem-bordered-border-hover)" : "border-b border-(--cmp-listItem-border-default)",
|
|
59
|
+
className
|
|
60
|
+
),
|
|
61
|
+
...rest,
|
|
62
|
+
children: [
|
|
63
|
+
accentStripe && !bordered && /* @__PURE__ */ jsx(
|
|
64
|
+
"span",
|
|
65
|
+
{
|
|
66
|
+
"aria-hidden": "true",
|
|
67
|
+
className: "pointer-events-none absolute bottom-[-1px] left-0 top-0 w-(--cmp-listItem-accentStripe-width) bg-(--cmp-listItem-accentStripe-color)"
|
|
68
|
+
}
|
|
69
|
+
),
|
|
70
|
+
leadingNode,
|
|
71
|
+
/* @__PURE__ */ jsx("span", { className: "flex min-w-0 flex-1 flex-col", children: /* @__PURE__ */ jsx("span", { children: titleNode }) }),
|
|
72
|
+
trailingNode
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
SListItem.displayName = "SListItem";
|
|
78
|
+
|
|
79
|
+
export { SListItem };
|
|
80
|
+
//# sourceMappingURL=SListItem.js.map
|
|
81
|
+
//# sourceMappingURL=SListItem.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/cn.ts","../../../src/components/SListItem/SListItem.tsx"],"names":["SListItem"],"mappings":";;;;;AAQA,IAAM,UAAU,mBAAA,CAAoB;AAAA,EAClC,MAAA,EAAQ;AAAA,IACN,WAAA,EAAa;AAAA,MACX,aAAa,CAAC,EAAE,IAAA,EAAM,CAAC,MAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,MAAM,IAAA,EAAM,IAAA,EAAM,MAAM,IAAA,EAAM,IAAI,GAAG;AAAA;AAC5F;AAEJ,CAAC,CAAA;AAMM,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACUO,IAAM,SAAA,GAAY,UAAA,CAA2C,SAASA,UAAAA,CAC3E;AAAA,EACE,KAAA;AAAA,EACA,KAAA,GAAQ,CAAA;AAAA,EACR,OAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX,YAAA,GAAe,KAAA;AAAA,EACf,KAAA,GAAQ,KAAA;AAAA,EACR,QAAA,GAAW,KAAA;AAAA,EACX,SAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA,GAAG;AACL,CAAA,EACA,GAAA,EACA;AACA,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,WAAA,GAAc,EAAE,OAAA,EAAS,QAAA,EAAS;AACxC,EAAA,MAAM,YAAY,OAAO,KAAA,KAAU,UAAA,GAAa,KAAA,CAAM,WAAW,CAAA,GAAI,KAAA;AACrE,EAAA,MAAM,cAAc,OAAO,OAAA,KAAY,UAAA,GAAa,OAAA,CAAQ,WAAW,CAAA,GAAI,OAAA;AAC3E,EAAA,MAAM,eAAe,OAAO,QAAA,KAAa,UAAA,GAAa,QAAA,CAAS,WAAW,CAAA,GAAI,QAAA;AAE9E,EAAA,MAAM,gBAAA,GAAmB,CAAC,KAAA,KAAsC;AAC9D,IAAA,IAAI,CAAC,QAAA,EAAU,UAAA,CAAW,IAAI,CAAA;AAC9B,IAAA,YAAA,GAAe,KAAK,CAAA;AAAA,EACtB,CAAA;AAEA,EAAA,MAAM,gBAAA,GAAmB,CAAC,KAAA,KAAsC;AAC9D,IAAA,UAAA,CAAW,KAAK,CAAA;AAChB,IAAA,YAAA,GAAe,KAAK,CAAA;AAAA,EACtB,CAAA;AAEA,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,iBAAe,QAAA,IAAY,MAAA;AAAA,MAC3B,YAAA,EAAc,gBAAA;AAAA,MACd,YAAA,EAAc,gBAAA;AAAA,MACd,SAAA,EAAW,EAAA;AAAA,QACT,oLAAA;AAAA,QACA,WACI,yEAAA,GACA,oCAAA;AAAA,QACJ,QACI,6CAAA,GACA,8CAAA;AAAA,QACJ,0BAAA;AAAA,QACA,8BAAA;AAAA,QACA,KAAA,KAAU,CAAA,GACN,8CAAA,GACA,KAAA,KAAU,IACR,8CAAA,GACA,8BAAA;AAAA,QACN,WACI,4JAAA,GACA,iDAAA;AAAA,QACJ;AAAA,OACF;AAAA,MACC,GAAG,IAAA;AAAA,MAEH,QAAA,EAAA;AAAA,QAAA,YAAA,IAAgB,CAAC,QAAA,oBAChB,GAAA;AAAA,UAAC,MAAA;AAAA,UAAA;AAAA,YACC,aAAA,EAAY,MAAA;AAAA,YACZ,SAAA,EAAU;AAAA;AAAA,SACZ;AAAA,QAED,WAAA;AAAA,4BACA,MAAA,EAAA,EAAK,SAAA,EAAU,gCACd,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAM,qBAAU,CAAA,EACnB,CAAA;AAAA,QACC;AAAA;AAAA;AAAA,GACH;AAEJ,CAAC;AAED,SAAA,CAAU,WAAA,GAAc,WAAA","file":"SListItem.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","'use client';\n\nimport { forwardRef, useState, type HTMLAttributes, type MouseEvent, type ReactNode } from 'react';\nimport { cn } from '../../lib/cn';\n\nexport interface SListItemRenderState {\n hovered: boolean;\n disabled: boolean;\n}\n\nexport type SListItemRenderProp = (state: SListItemRenderState) => ReactNode;\nexport type SListItemSlot = ReactNode | SListItemRenderProp;\n\nexport interface SListItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> {\n /** 리스트 아이템 제목 */\n title: SListItemSlot;\n /** 중첩 단계. Figma 기준 1~3단계 */\n depth?: 1 | 2 | 3;\n /** 타이틀 앞에 표시할 아이콘/콘텐츠 */\n leading?: SListItemSlot;\n /** 타이틀 뒤에 표시할 태그/콘텐츠 */\n trailing?: SListItemSlot;\n /** 외곽 테두리 사용 여부 */\n bordered?: boolean;\n /** 아이템 왼쪽 accent stripe 표시 여부 */\n accentStripe?: boolean;\n /** 조밀한 높이 사용 여부 */\n dense?: boolean;\n /** 비활성 상태 여부 */\n disabled?: boolean;\n}\n\nexport const SListItem = forwardRef<HTMLDivElement, SListItemProps>(function SListItem(\n {\n title,\n depth = 1,\n leading,\n trailing,\n bordered = false,\n accentStripe = false,\n dense = false,\n disabled = false,\n className,\n onMouseEnter,\n onMouseLeave,\n ...rest\n },\n ref,\n) {\n const [hovered, setHovered] = useState(false);\n const renderState = { hovered, disabled };\n const titleNode = typeof title === 'function' ? title(renderState) : title;\n const leadingNode = typeof leading === 'function' ? leading(renderState) : leading;\n const trailingNode = typeof trailing === 'function' ? trailing(renderState) : trailing;\n\n const handleMouseEnter = (event: MouseEvent<HTMLDivElement>) => {\n if (!disabled) setHovered(true);\n onMouseEnter?.(event);\n };\n\n const handleMouseLeave = (event: MouseEvent<HTMLDivElement>) => {\n setHovered(false);\n onMouseLeave?.(event);\n };\n\n return (\n <div\n ref={ref}\n aria-disabled={disabled || undefined}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n className={cn(\n 'group/list-item relative flex w-full items-center justify-between bg-(--cmp-listItem-bg-default) text-left text-[14px] font-bold leading-[24px] text-(--cmp-listItem-text-default)',\n disabled\n ? 'pointer-events-none cursor-not-allowed text-(--sys-color-disabled-text)'\n : 'hover:bg-(--cmp-listItem-bg-hover)',\n dense\n ? 'h-[32px] py-(--cmp-listItem-paddingY-dense)'\n : 'h-[40px] py-(--cmp-listItem-paddingY-normal)',\n 'gap-(--cmp-listItem-gap)',\n 'pr-(--cmp-listItem-paddingX)',\n depth === 2\n ? 'pl-[calc(var(--cmp-listItem-paddingX)+32px)]'\n : depth === 3\n ? 'pl-[calc(var(--cmp-listItem-paddingX)+64px)]'\n : 'pl-(--cmp-listItem-paddingX)',\n bordered\n ? 'overflow-hidden rounded-(--cmp-listItem-radius-bordered) border border-(--cmp-listItem-border-default) hover:border-(--cmp-listItem-bordered-border-hover)'\n : 'border-b border-(--cmp-listItem-border-default)',\n className,\n )}\n {...rest}\n >\n {accentStripe && !bordered && (\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute bottom-[-1px] left-0 top-0 w-(--cmp-listItem-accentStripe-width) bg-(--cmp-listItem-accentStripe-color)\"\n />\n )}\n {leadingNode}\n <span className=\"flex min-w-0 flex-1 flex-col\">\n <span>{titleNode}</span>\n </span>\n {trailingNode}\n </div>\n );\n});\n\nSListItem.displayName = 'SListItem';\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SListItem, type SListItemProps } from './SListItem';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# SLoadingContainer
|
|
2
|
+
|
|
3
|
+
> 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
|
|
4
|
+
|
|
5
|
+
### SLoadingContainer
|
|
6
|
+
|
|
7
|
+
#### Props
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Default | Description |
|
|
10
|
+
|------|------|---------|-------------|
|
|
11
|
+
| `open?` | `boolean` | `false` | 오버레이 표시 여부 (Stencil의 show()/hide() 명령형 API → 제어 prop으로 대체) |
|
|
12
|
+
| `message?` | `string` | — | 스피너 하단 메시지 |
|
|
13
|
+
| `zIndex?` | `number` | `1200` | z-index. 기본값(1200)은 토스트(1100)·모달(1000/1001) 위 — 전체 화면을 막는 최상단 블로킹 오버레이 |
|
|
14
|
+
| `className?` | `string` | — | |
|
|
15
|
+
| `style?` | `CSSProperties` | — | |
|
|
16
|
+
|
|
17
|
+
## Dependencies
|
|
18
|
+
|
|
19
|
+
### Depends on
|
|
20
|
+
|
|
21
|
+
- [SCircleProgress](../SCircleProgress)
|
|
22
|
+
|
|
23
|
+
### Graph
|
|
24
|
+
|
|
25
|
+
```mermaid
|
|
26
|
+
graph TD;
|
|
27
|
+
SLoadingContainer --> SCircleProgress
|
|
28
|
+
style SLoadingContainer fill:#f9f,stroke:#333,stroke-width:4px
|
|
29
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# SLoadingModal
|
|
2
|
+
|
|
3
|
+
> 자동 생성 문서 — `npm run docs:gen`. 소스: 각 컴포넌트의 Props/Handle 인터페이스 + import 의존성.
|
|
4
|
+
|
|
5
|
+
### SLoadingModal
|
|
6
|
+
|
|
7
|
+
#### Props
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Default | Description |
|
|
10
|
+
|------|------|---------|-------------|
|
|
11
|
+
| `open?` | `boolean` | — | |
|
|
12
|
+
| `persistent?` | `boolean` | — | true면 백드롭·ESC로 안 닫히고 흔들림 (sd-modal-container persistent). 로딩 중 닫힘 방지에 유용 |
|
|
13
|
+
| `state?` | `LoadingModalState` | `'loading'` | 상태 (loading: 스피너, error: 경고 아이콘) |
|
|
14
|
+
| `progress?` | `number` | — | 진행률 (0–100) — 지정 시 determinate 스피너 |
|
|
15
|
+
| `message?` | `string \| string[]` | — | 메시지 (없으면 state 기본값) |
|
|
16
|
+
| `useButton?` | `boolean` | `false` | 하단 버튼 표시 |
|
|
17
|
+
| `buttonLabel?` | `string` | — | 버튼 레이블 (없으면 state 기본값) |
|
|
18
|
+
| `width?` | `number \| string` | `520` | |
|
|
19
|
+
| `height?` | `number \| string` | `320` | |
|
|
20
|
+
|
|
21
|
+
#### Events
|
|
22
|
+
|
|
23
|
+
| Event | Type | Description |
|
|
24
|
+
|-------|------|-------------|
|
|
25
|
+
| `onOpenChange` | `(open: boolean) => void` | |
|
|
26
|
+
| `onClose` | `() => void` | 닫기(X) 버튼 클릭 (sdClose) — error 상태에서만 노출 |
|
|
27
|
+
| `onButtonClick` | `() => void` | 버튼 클릭 (sdClick) |
|
|
28
|
+
|
|
29
|
+
## Dependencies
|
|
30
|
+
|
|
31
|
+
### Used by
|
|
32
|
+
|
|
33
|
+
- [SModal](../SModal)
|
|
34
|
+
|
|
35
|
+
### Depends on
|
|
36
|
+
|
|
37
|
+
- [SButton](../SButton)
|
|
38
|
+
- [SCircleProgress](../SCircleProgress)
|
|
39
|
+
- [SIcon](../SIcon)
|
|
40
|
+
- [SModalContainer](../SModalContainer)
|
|
41
|
+
|
|
42
|
+
### Graph
|
|
43
|
+
|
|
44
|
+
```mermaid
|
|
45
|
+
graph TD;
|
|
46
|
+
SLoadingModal --> SButton
|
|
47
|
+
SLoadingModal --> SCircleProgress
|
|
48
|
+
SLoadingModal --> SIcon
|
|
49
|
+
SLoadingModal --> SModalContainer
|
|
50
|
+
SModal --> SLoadingModal
|
|
51
|
+
style SLoadingModal fill:#f9f,stroke:#333,stroke-width:4px
|
|
52
|
+
```
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# SModal
|
|
2
|
+
|
|
3
|
+
> 수작성 문서 — SModal 은 컴포넌트가 아니라 **명령형 모달 서비스**라 `docs:gen`(Props/Events 표) 대상이 아니다.
|
|
4
|
+
|
|
5
|
+
버튼 핸들러에서 바로 호출해 모달을 띄우는 명령형 API. 선언형 `<SConfirmModal open>` / `<SLoadingModal open>` 을 **대체하지 않고 추가로** 제공한다. (원본 디자인 시스템 `sdModal` 파리티)
|
|
6
|
+
|
|
7
|
+
호출 시마다 `document.body` 에 컨테이너를 만들어 모달을 렌더하고, 닫힘 애니메이션이 끝나면 자동으로 언마운트한다. 모든 메서드는 체이닝 핸들 [`SModalRef`](#smodalref) 를 반환한다.
|
|
8
|
+
|
|
9
|
+
| 메서드 | 띄우는 모달 | 용도 | 주요 콜백/제어 |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| [`SModal.confirm(options)`](#smodalconfirm) | `SConfirmModal` | 확인/취소 | `onOk` / `onCancel` / `onClose` |
|
|
12
|
+
| [`SModal.loading(options?)`](#smodalloading) | `SLoadingModal` | 로딩·에러 (persistent 기본 true) | `onClick` / `update` |
|
|
13
|
+
| [`SModal.create({ component })`](#smodalcreate) | `SActionModal` | 액션 모달 | `modalRef` 주입 → `ok/cancel/close/submit` |
|
|
14
|
+
|
|
15
|
+
> **띄울 수 있는 모달은 `SActionModal` / `SConfirmModal` / `SLoadingModal` 세 가지뿐이고, 위 메서드가 1:1 로 대응한다.** 그 밖의 스타일로 모달을 띄우는 경로는 제공하지 않는다. `create` 는 컨테이너를 덧씌우지 않고 `component` 를 **그대로** 렌더하므로, `component` 는 루트에 `SActionModal` 을 렌더해야 한다 — 그러지 않으면 딤·카드 없이 콘텐츠만 뜨며, 개발 모드에서 `console.warn` 으로 경고한다.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { SModal } from 'sellmate-design-system-react';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## SModal.confirm
|
|
24
|
+
|
|
25
|
+
아이콘 + 제목 + 메시지 + 확인/취소 버튼. `type` 에 따라 아이콘·메인 버튼 색이 결정된다.
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
SModal.confirm({
|
|
29
|
+
type: 'negative', // 'positive' | 'negative' | 'default'
|
|
30
|
+
modalTitle: '삭제하시겠습니까?',
|
|
31
|
+
topMessage: ['이 작업은 되돌릴 수 없습니다.'],
|
|
32
|
+
mainButtonLabel: '삭제',
|
|
33
|
+
subButtonLabel: '취소',
|
|
34
|
+
})
|
|
35
|
+
.onOk(() => deleteItem())
|
|
36
|
+
.onCancel(() => {});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**옵션 (`SConfirmOptions`)** — 선언형 `SConfirmModalProps` 에서 제어 흐름 props(`open`/`onOpenChange`/`onOk`/`onCancel`/`onClose`)를 제외한 전부. 주요 키: `type`, `modalTitle`, `topMessage`/`bottomMessage`, `mainButtonLabel`/`mainButtonName`, `subButtonLabel`, `tagSlot`/`optionSlot`, `persistent`.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## SModal.loading
|
|
44
|
+
|
|
45
|
+
스피너/에러 모달. 로딩 중 임의 닫힘을 막기 위해 **`persistent` 기본값이 `true`** 다(백드롭·ESC로 안 닫힘). "띄우고 → 작업 → 결과 반영" 흐름을 `update()` / `close()` 로 제어한다.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
const ref = SModal.loading({ message: '업로드 중...' });
|
|
49
|
+
try {
|
|
50
|
+
await upload();
|
|
51
|
+
ref.close();
|
|
52
|
+
} catch {
|
|
53
|
+
ref.update({ state: 'error', message: '업로드 실패' }) // 표시 중 상태 갱신
|
|
54
|
+
.onClick(() => retry()); // 「다시 시도」 버튼
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
진행률:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
const ref = SModal.loading({ progress: 0, message: '파일 업로드 중...' });
|
|
62
|
+
ref.update({ progress: 60 }); // 0–100
|
|
63
|
+
ref.update({ progress: 100 });
|
|
64
|
+
ref.close();
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> **버튼 클릭은 자동 닫힘이 아니다.** error 상태의 버튼(기본 "다시 시도")은 `onClick` 만 발화하고 모달은 유지된다. consumer 가 `update()`(다시 로딩) / `close()` 로 후속 동작을 결정한다.
|
|
68
|
+
|
|
69
|
+
**옵션 (`SLoadingOptions`)** — `state`('loading' | 'error'), `progress`(0–100), `message`(string | string[]), `useButton`, `buttonLabel`, `width`/`height`, `persistent`.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## SModal.create
|
|
74
|
+
|
|
75
|
+
**`SActionModal` 을 루트로 렌더하는 컴포넌트**를 띄운다. `create` 는 컨테이너를 덧씌우지 않고 `component` 를 **그대로** 렌더하며, 표시 제어(`open` / `onOpenChange` / `onClose`)와 [`modalRef`](#smodalref) 를 주입하고 닫힘 애니메이션 종료 후 언마운트를 담당한다.
|
|
76
|
+
|
|
77
|
+
컴포넌트는 주입받은 `open` / `onOpenChange` / `onClose` 를 **SActionModal 에 그대로 전달**해야 한다. 전달하지 않으면 모달이 열리지도, 닫히지도 않는다.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { SModal, SActionModal, type SModalCreateComponentProps } from 'sellmate-design-system-react';
|
|
81
|
+
|
|
82
|
+
// componentProps 로 넘긴 값 + 주입 prop 을 함께 받는다
|
|
83
|
+
interface OrderModalProps extends SModalCreateComponentProps {
|
|
84
|
+
orderId: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function OrderModal({ open, onOpenChange, onClose, modalRef, orderId }: OrderModalProps) {
|
|
88
|
+
return (
|
|
89
|
+
<SActionModal
|
|
90
|
+
open={open}
|
|
91
|
+
onOpenChange={onOpenChange}
|
|
92
|
+
onClose={onClose}
|
|
93
|
+
modalTitle="주문 처리"
|
|
94
|
+
button={{ label: '처리', onClick: () => modalRef.ok() }}
|
|
95
|
+
>
|
|
96
|
+
<p>주문번호 {orderId} 를 접수합니다.</p>
|
|
97
|
+
</SActionModal>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
SModal.create({ component: OrderModal, componentProps: { orderId: 'ORD-001' } })
|
|
102
|
+
.onOk(() => toast('저장 완료'))
|
|
103
|
+
.onDismissed(() => cleanup());
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**옵션 (`SCreateOptions<P>`)**
|
|
107
|
+
|
|
108
|
+
| 키 | 타입 | 설명 |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `component` | `ComponentType<P & SModalCreateComponentProps>` | 루트에 `SActionModal` 을 렌더하는 컴포넌트 |
|
|
111
|
+
| `componentProps?` | `P` | 컴포넌트에 전달할 추가 props |
|
|
112
|
+
|
|
113
|
+
**주입되는 prop (`SModalCreateComponentProps`)**
|
|
114
|
+
|
|
115
|
+
| 키 | 타입 | 설명 |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `open` | `boolean` | SActionModal 의 `open` 에 그대로 전달 |
|
|
118
|
+
| `onOpenChange` | `(open: boolean) => void` | SActionModal 의 `onOpenChange` 에 그대로 전달 |
|
|
119
|
+
| `onClose` | `() => void` | SActionModal 의 `onClose` 에 그대로 전달 |
|
|
120
|
+
| `modalRef` | `SModalRef` | `ok()`/`cancel()`/`close()`/`submit()` 으로 자기 모달 제어 |
|
|
121
|
+
|
|
122
|
+
### 비동기 제출 — 응답 보고 닫기
|
|
123
|
+
|
|
124
|
+
SActionModal `button` 의 푸터 버튼은 클릭 시 **즉시 닫힌다**. 저장 API 응답에 따라 닫힘 여부를 정해야 하면 푸터 버튼 대신 **본문에 버튼을 두고** `modalRef` 로 닫힘 시점을 직접 제어한다.
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
function OrderModal({ open, onOpenChange, onClose, modalRef, orderId }: OrderModalProps) {
|
|
128
|
+
const [error, setError] = useState('');
|
|
129
|
+
const handleSubmit = async () => {
|
|
130
|
+
try {
|
|
131
|
+
await save(orderId);
|
|
132
|
+
modalRef.ok(); // 성공 → onOk + 닫기
|
|
133
|
+
} catch {
|
|
134
|
+
setError('저장 실패'); // 실패 → 모달 유지
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
return (
|
|
138
|
+
// button 을 주지 않으면 푸터가 렌더되지 않는다
|
|
139
|
+
<SActionModal open={open} onOpenChange={onOpenChange} onClose={onClose} persistent modalTitle="주문 처리">
|
|
140
|
+
{error && <p>{error}</p>}
|
|
141
|
+
<SButton label="저장" onClick={handleSubmit} />
|
|
142
|
+
<SButton label="취소" onClick={() => modalRef.cancel()} />
|
|
143
|
+
</SActionModal>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## SModalRef
|
|
151
|
+
|
|
152
|
+
모든 `SModal.*` 호출이 반환하고, `create` 에서는 컴포넌트 prop 으로도 주입되는 제어 핸들. 콜백 등록(체이닝)과 트리거/갱신 메서드를 함께 제공한다. 모달 종류에 따라 관련 있는 메서드만 실제로 발화한다.
|
|
153
|
+
|
|
154
|
+
### 콜백 등록 (체이닝)
|
|
155
|
+
|
|
156
|
+
| 메서드 | 발화 시점 |
|
|
157
|
+
|---|---|
|
|
158
|
+
| `onOk(fn)` | 확인 버튼(confirm) 또는 `ok()` |
|
|
159
|
+
| `onCancel(fn)` | 취소 버튼(confirm) 또는 `cancel()` |
|
|
160
|
+
| `onClose(fn)` | 닫기(X) 버튼 또는 `close()` |
|
|
161
|
+
| `onClick(fn)` | 단일 버튼 모달(loading error)의 버튼 클릭 — **닫힘 없음** |
|
|
162
|
+
| `onSubmit(fn)` | create 커스텀 모달의 `submit()` — **닫힘 없음** |
|
|
163
|
+
| `onDismissed(fn)` | 사유 무관 완전히 닫혀 언마운트된 뒤(백드롭·ESC 포함) |
|
|
164
|
+
|
|
165
|
+
### 트리거 / 제어
|
|
166
|
+
|
|
167
|
+
| 메서드 | 동작 |
|
|
168
|
+
|---|---|
|
|
169
|
+
| `ok()` | `onOk` 발화 + 닫기 (저장/처리 성공) |
|
|
170
|
+
| `cancel()` | `onCancel` 발화 + 닫기 (작업 취소) |
|
|
171
|
+
| `close()` | `onClose` 발화 + 닫기 (중립적 닫기) |
|
|
172
|
+
| `submit()` | `onSubmit` 발화 (닫힘 없음) |
|
|
173
|
+
| `update(patch)` | 표시 중 옵션 갱신 (loading→error, progress 등). create 는 미지원. |
|
|
174
|
+
|
|
175
|
+
모든 닫힘은 애니메이션 종료 후 `onDismissed` 로 수렴한다. 이미 닫힘이 시작된 뒤의 중복 트리거는 무시된다.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 주의사항
|
|
180
|
+
|
|
181
|
+
- **선언형과 공존**: 서비스는 추가 API다. open 상태가 앱 상태/라우트에 묶인 경우엔 선언형 `<SConfirmModal open>` / `<SLoadingModal open>` 이 더 적합하다.
|
|
182
|
+
- **백드롭·ESC = 중립적 닫힘**: 특정 콜백(onClose 등) 없이 `onDismissed` 만 발화한다. 명시적 버튼·메서드만 onOk/onCancel/onClose 를 발화한다.
|
|
183
|
+
- **Context 미상속**: `create` 의 커스텀 컴포넌트는 새 React 트리(createRoot)에서 렌더되어 부모의 Context Provider(Theme·Store 등)를 상속하지 않는다. 필요하면 컴포넌트 내부에서 직접 Provider 로 감싸라. (confirm/loading 은 토큰이 `:root` CSS 변수라 무관)
|
|
184
|
+
- **`create` 의 `component` 는 SActionModal 을 루트로**: `create` 는 컨테이너를 덧씌우지 않으므로, 본문만 렌더하는 컴포넌트를 넘기면 딤·카드 없이 콘텐츠가 그대로 화면에 붙는다. TypeScript 는 이를 막지 못한다(`component` 타입이 아무 컴포넌트나 허용). 개발 모드에서는 마운트 직후 렌더 결과로 이를 감지해 `console.warn` 으로 경고한다 — 세 모달은 모두 Portal 로 `body` 에 렌더되므로 `create` 가 만든 host 는 비어 있어야 하는데, host 에 엘리먼트가 남아 있으면 모달이 아닌 것으로 판정한다.
|
|
185
|
+
|
|
186
|
+
## Dependencies
|
|
187
|
+
|
|
188
|
+
### Depends on
|
|
189
|
+
|
|
190
|
+
- [SActionModal](../SActionModal)
|
|
191
|
+
- [SConfirmModal](../SConfirmModal)
|
|
192
|
+
- [SLoadingModal](../SLoadingModal)
|