@wishket/design-system 2.2.1 → 3.0.1
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/README.md +58 -0
- package/dist/Components/Base/Layouts/Box/Box.d.ts +5 -5
- package/dist/Components/Base/Layouts/Box/Box.js +3 -2
- package/dist/Components/Base/Typography/Typography.d.ts +6 -1
- package/dist/Components/Base/Typography/Typography.js +4 -3
- package/dist/Components/Base/Typography/Typography.types.d.ts +4 -5
- package/dist/Components/DataDisplays/ImageLabel/ImageLabel.js +1 -1
- package/dist/Components/Feedbacks/Tooltip/Tooltip.js +3 -3
- package/dist/Components/Inputs/CheckboxCard/CheckboxCard.parts.js +1 -1
- package/dist/Components/Inputs/IconButtonDropdown/IconButtonDropdown.js +1 -1
- package/dist/Components/Inputs/RadioCard/RadioCard.parts.js +1 -1
- package/dist/Components/Inputs/TextFieldDropdown/TextFieldDropdown.js +2 -2
- package/dist/Components/Navigations/GNBList/GNBList.d.ts +1 -1
- package/dist/Components/Navigations/GNBList/GNBList.parts.d.ts +3 -2
- package/dist/Components/Navigations/GNBList/GNBList.parts.js +10 -10
- package/dist/Components/Navigations/GNBList/GNBList.types.d.ts +8 -3
- package/dist/Components/Navigations/Menu/Menu.types.d.ts +53 -0
- package/dist/Components/Navigations/Menu/MenuBase.d.ts +21 -0
- package/dist/Components/Navigations/Menu/MenuBase.js +3 -0
- package/dist/Components/Navigations/Menu/MenuButton.d.ts +40 -0
- package/dist/Components/Navigations/Menu/MenuButton.js +39 -0
- package/dist/Components/Navigations/Menu/MenuLink.d.ts +43 -0
- package/dist/Components/Navigations/Menu/MenuLink.js +42 -0
- package/dist/Components/Navigations/Menu/index.d.ts +3 -1
- package/dist/Components/Navigations/TextLink/TextLink.d.ts +13 -35
- package/dist/Components/Navigations/TextLink/TextLink.js +11 -34
- package/dist/Components/Utils/BackDrop/BackDrop.js +1 -1
- package/dist/Components/Utils/BottomModalContainer/BottomModalContainer.js +2 -2
- package/dist/Components/Utils/FullModalContainer/FullModalContainer.js +1 -1
- package/dist/Components/Utils/Modal/Modal.js +2 -2
- package/dist/Components/Utils/ModalContainer/ModalContainer.js +2 -2
- package/dist/Components/Wrappers/WithBadge/WithBadge.js +1 -1
- package/dist/Components/Wrappers/WithSnackBar/WithSnackBar.js +1 -1
- package/dist/cjs/Components/Base/Layouts/Box/Box.js +3 -2
- package/dist/cjs/Components/Base/Typography/Typography.js +4 -3
- package/dist/cjs/Components/DataDisplays/ImageLabel/ImageLabel.js +1 -1
- package/dist/cjs/Components/Feedbacks/Tooltip/Tooltip.js +3 -3
- package/dist/cjs/Components/Inputs/CheckboxCard/CheckboxCard.parts.js +1 -1
- package/dist/cjs/Components/Inputs/IconButtonDropdown/IconButtonDropdown.js +1 -1
- package/dist/cjs/Components/Inputs/RadioCard/RadioCard.parts.js +1 -1
- package/dist/cjs/Components/Inputs/TextFieldDropdown/TextFieldDropdown.js +2 -2
- package/dist/cjs/Components/Navigations/GNBList/GNBList.parts.js +11 -11
- package/dist/cjs/Components/Navigations/Menu/MenuBase.js +3 -0
- package/dist/cjs/Components/Navigations/Menu/MenuButton.js +39 -0
- package/dist/cjs/Components/Navigations/Menu/MenuLink.js +42 -0
- package/dist/cjs/Components/Navigations/TextLink/TextLink.js +11 -34
- package/dist/cjs/Components/Utils/BackDrop/BackDrop.js +1 -1
- package/dist/cjs/Components/Utils/BottomModalContainer/BottomModalContainer.js +3 -3
- package/dist/cjs/Components/Utils/FullModalContainer/FullModalContainer.js +1 -1
- package/dist/cjs/Components/Utils/Modal/Modal.js +2 -2
- package/dist/cjs/Components/Utils/ModalContainer/ModalContainer.js +2 -2
- package/dist/cjs/Components/Wrappers/WithBadge/WithBadge.js +1 -1
- package/dist/cjs/Components/Wrappers/WithSnackBar/WithSnackBar.js +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +7 -5
- package/scripts/codemods/README.md +178 -0
- package/scripts/codemods/__tests__/__fixtures__/add-as-link/already-has-as.input.tsx +6 -0
- package/scripts/codemods/__tests__/__fixtures__/add-as-link/already-has-as.output.tsx +6 -0
- package/scripts/codemods/__tests__/__fixtures__/add-as-link/basic.input.tsx +9 -0
- package/scripts/codemods/__tests__/__fixtures__/add-as-link/basic.output.tsx +10 -0
- package/scripts/codemods/__tests__/__fixtures__/add-as-link/existing-next-link-import.input.tsx +9 -0
- package/scripts/codemods/__tests__/__fixtures__/add-as-link/existing-next-link-import.output.tsx +9 -0
- package/scripts/codemods/__tests__/__fixtures__/add-as-link/menu-button-fallback.input.tsx +5 -0
- package/scripts/codemods/__tests__/__fixtures__/add-as-link/menu-button-fallback.output.tsx +5 -0
- package/scripts/codemods/__tests__/__fixtures__/add-as-link/unrelated-import.input.tsx +3 -0
- package/scripts/codemods/__tests__/__fixtures__/add-as-link/unrelated-import.output.tsx +3 -0
- package/scripts/codemods/__tests__/__fixtures__/menu-split/basic.input.tsx +8 -0
- package/scripts/codemods/__tests__/__fixtures__/menu-split/basic.output.tsx +8 -0
- package/scripts/codemods/__tests__/__fixtures__/menu-split/conflict.input.tsx +5 -0
- package/scripts/codemods/__tests__/__fixtures__/menu-split/conflict.output.tsx +6 -0
- package/scripts/codemods/__tests__/__fixtures__/menu-split/href-only.input.tsx +3 -0
- package/scripts/codemods/__tests__/__fixtures__/menu-split/href-only.output.tsx +3 -0
- package/scripts/codemods/__tests__/__fixtures__/menu-split/onclick-only.input.tsx +3 -0
- package/scripts/codemods/__tests__/__fixtures__/menu-split/onclick-only.output.tsx +3 -0
- package/scripts/codemods/__tests__/__fixtures__/menu-split/spread-props.input.tsx +3 -0
- package/scripts/codemods/__tests__/__fixtures__/menu-split/spread-props.output.tsx +4 -0
- package/scripts/codemods/__tests__/run-fixtures.cjs +100 -0
- package/scripts/codemods/add-as-link.ts +110 -0
- package/scripts/codemods/menu-split.ts +252 -0
- package/dist/Components/Navigations/Menu/Menu.d.ts +0 -81
- package/dist/Components/Navigations/Menu/Menu.js +0 -62
- package/dist/cjs/Components/Navigations/Menu/Menu.js +0 -2
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Codemod fixture 검증 스크립트.
|
|
4
|
+
*
|
|
5
|
+
* 각 codemod에 대해 `__fixtures__/<codemod-name>/<case>.input.tsx`를
|
|
6
|
+
* jscodeshift로 변환한 결과를 `<case>.output.tsx`와 비교한다.
|
|
7
|
+
*
|
|
8
|
+
* 사용:
|
|
9
|
+
* node scripts/codemods/__tests__/run-fixtures.js
|
|
10
|
+
*
|
|
11
|
+
* 회귀 방어 목적. CI에서 매 PR 마다 실행하면 codemod 변경 시 회귀를 잡을 수 있다.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { execSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
const CODEMODS = ['add-as-link', 'menu-split'];
|
|
19
|
+
const ROOT = path.join(__dirname, '..');
|
|
20
|
+
const FIXTURES_ROOT = path.join(__dirname, '__fixtures__');
|
|
21
|
+
|
|
22
|
+
function normalize(s) {
|
|
23
|
+
// Whitespace + 사소한 포맷 차이 흡수
|
|
24
|
+
return s.replace(/\s+/g, ' ').replace(/\s*([{}<>=,;])\s*/g, '$1').trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function runCodemod(codemodPath, inputPath) {
|
|
28
|
+
// jscodeshift를 stdout으로 출력
|
|
29
|
+
const out = execSync(
|
|
30
|
+
`npx jscodeshift -t "${codemodPath}" --parser=tsx --dry --print --silent --stdin --no-babel < "${inputPath}"`,
|
|
31
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] },
|
|
32
|
+
).trim();
|
|
33
|
+
|
|
34
|
+
// --print는 변환된 코드를 출력하지만 헤더(파일 경로 등)도 같이 나옴.
|
|
35
|
+
// 가장 단순한 방법: tmp 파일에 input을 복사 후 변환 결과를 stdout으로 읽기
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let passed = 0;
|
|
40
|
+
let failed = 0;
|
|
41
|
+
const failures = [];
|
|
42
|
+
|
|
43
|
+
for (const codemod of CODEMODS) {
|
|
44
|
+
const codemodPath = path.join(ROOT, `${codemod}.ts`);
|
|
45
|
+
const fixturesDir = path.join(FIXTURES_ROOT, codemod);
|
|
46
|
+
|
|
47
|
+
if (!fs.existsSync(fixturesDir)) {
|
|
48
|
+
console.log(`(skip) no fixtures for ${codemod}`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const cases = fs
|
|
53
|
+
.readdirSync(fixturesDir)
|
|
54
|
+
.filter(f => f.endsWith('.input.tsx'))
|
|
55
|
+
.map(f => f.replace('.input.tsx', ''));
|
|
56
|
+
|
|
57
|
+
for (const c of cases) {
|
|
58
|
+
const inputPath = path.join(fixturesDir, `${c}.input.tsx`);
|
|
59
|
+
const outputPath = path.join(fixturesDir, `${c}.output.tsx`);
|
|
60
|
+
|
|
61
|
+
if (!fs.existsSync(outputPath)) {
|
|
62
|
+
failed++;
|
|
63
|
+
failures.push(`${codemod}/${c}: missing ${c}.output.tsx`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Input을 임시 파일로 복사한 뒤 jscodeshift로 in-place 변환, 결과 읽기
|
|
68
|
+
const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'codemod-test-'));
|
|
69
|
+
const tmpFile = path.join(tmpDir, `${c}.tsx`);
|
|
70
|
+
fs.copyFileSync(inputPath, tmpFile);
|
|
71
|
+
try {
|
|
72
|
+
execSync(
|
|
73
|
+
`npx jscodeshift -t "${codemodPath}" --parser=tsx --extensions=tsx --no-babel "${tmpFile}"`,
|
|
74
|
+
{ stdio: 'pipe' },
|
|
75
|
+
);
|
|
76
|
+
const actual = fs.readFileSync(tmpFile, 'utf8');
|
|
77
|
+
const expected = fs.readFileSync(outputPath, 'utf8');
|
|
78
|
+
|
|
79
|
+
if (normalize(actual) === normalize(expected)) {
|
|
80
|
+
passed++;
|
|
81
|
+
console.log(` ✓ ${codemod}/${c}`);
|
|
82
|
+
} else {
|
|
83
|
+
failed++;
|
|
84
|
+
failures.push(
|
|
85
|
+
`${codemod}/${c}:\n--- expected ---\n${expected}\n--- actual ---\n${actual}`,
|
|
86
|
+
);
|
|
87
|
+
console.log(` ✗ ${codemod}/${c}`);
|
|
88
|
+
}
|
|
89
|
+
} finally {
|
|
90
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
96
|
+
if (failed > 0) {
|
|
97
|
+
console.log('\n--- failures ---');
|
|
98
|
+
failures.forEach(f => console.log(f + '\n'));
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
API,
|
|
3
|
+
ASTPath,
|
|
4
|
+
Collection,
|
|
5
|
+
FileInfo,
|
|
6
|
+
ImportDeclaration,
|
|
7
|
+
JSCodeshift,
|
|
8
|
+
JSXAttribute,
|
|
9
|
+
JSXMemberExpression,
|
|
10
|
+
JSXOpeningElement,
|
|
11
|
+
} from 'jscodeshift';
|
|
12
|
+
|
|
13
|
+
const TARGET_COMPONENTS = new Set([
|
|
14
|
+
'TextLink',
|
|
15
|
+
'Menu',
|
|
16
|
+
'GNBList.Item',
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const DESIGN_SYSTEM_PACKAGE = '@wishket/design-system';
|
|
20
|
+
const NEXT_LINK_PACKAGE = 'next/link';
|
|
21
|
+
const NEXT_LINK_LOCAL_NAME = 'Link';
|
|
22
|
+
|
|
23
|
+
export default function transformer(file: FileInfo, api: API): string {
|
|
24
|
+
const j: JSCodeshift = api.jscodeshift;
|
|
25
|
+
const root = j(file.source);
|
|
26
|
+
|
|
27
|
+
const dsImport: Collection<ImportDeclaration> = root.find(
|
|
28
|
+
j.ImportDeclaration,
|
|
29
|
+
{
|
|
30
|
+
source: { value: DESIGN_SYSTEM_PACKAGE },
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
if (dsImport.size() === 0) return file.source;
|
|
34
|
+
|
|
35
|
+
let didChange = false;
|
|
36
|
+
|
|
37
|
+
root
|
|
38
|
+
.find(j.JSXOpeningElement)
|
|
39
|
+
.forEach((path: ASTPath<JSXOpeningElement>) => {
|
|
40
|
+
const name = getJSXName(path.node);
|
|
41
|
+
if (!name || !TARGET_COMPONENTS.has(name)) return;
|
|
42
|
+
|
|
43
|
+
const attrs = path.node.attributes ?? [];
|
|
44
|
+
|
|
45
|
+
const hasAs = attrs.some(
|
|
46
|
+
(attr): attr is JSXAttribute =>
|
|
47
|
+
attr.type === 'JSXAttribute' &&
|
|
48
|
+
attr.name.type === 'JSXIdentifier' &&
|
|
49
|
+
attr.name.name === 'as',
|
|
50
|
+
);
|
|
51
|
+
if (hasAs) return;
|
|
52
|
+
|
|
53
|
+
const hasHref = attrs.some(
|
|
54
|
+
(attr): attr is JSXAttribute =>
|
|
55
|
+
attr.type === 'JSXAttribute' &&
|
|
56
|
+
attr.name.type === 'JSXIdentifier' &&
|
|
57
|
+
attr.name.name === 'href',
|
|
58
|
+
);
|
|
59
|
+
if (!hasHref) return;
|
|
60
|
+
|
|
61
|
+
const asAttr = j.jsxAttribute(
|
|
62
|
+
j.jsxIdentifier('as'),
|
|
63
|
+
j.jsxExpressionContainer(j.identifier(NEXT_LINK_LOCAL_NAME)),
|
|
64
|
+
);
|
|
65
|
+
path.node.attributes = [asAttr, ...attrs];
|
|
66
|
+
didChange = true;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (!didChange) return file.source;
|
|
70
|
+
|
|
71
|
+
const hasNextLinkImport =
|
|
72
|
+
root
|
|
73
|
+
.find(j.ImportDeclaration, {
|
|
74
|
+
source: { value: NEXT_LINK_PACKAGE },
|
|
75
|
+
})
|
|
76
|
+
.size() > 0;
|
|
77
|
+
|
|
78
|
+
if (!hasNextLinkImport) {
|
|
79
|
+
const newImport = j.importDeclaration(
|
|
80
|
+
[j.importDefaultSpecifier(j.identifier(NEXT_LINK_LOCAL_NAME))],
|
|
81
|
+
j.literal(NEXT_LINK_PACKAGE),
|
|
82
|
+
);
|
|
83
|
+
root.get().node.program.body.unshift(newImport);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return root.toSource({ quote: 'single' });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getJSXName(node: JSXOpeningElement): string | null {
|
|
90
|
+
const { name } = node;
|
|
91
|
+
if (name.type === 'JSXIdentifier') return name.name;
|
|
92
|
+
if (name.type === 'JSXMemberExpression') {
|
|
93
|
+
return memberExpressionToString(name);
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function memberExpressionToString(
|
|
99
|
+
expr: JSXMemberExpression,
|
|
100
|
+
): string | null {
|
|
101
|
+
const right = expr.property.name;
|
|
102
|
+
if (expr.object.type === 'JSXIdentifier') {
|
|
103
|
+
return `${expr.object.name}.${right}`;
|
|
104
|
+
}
|
|
105
|
+
if (expr.object.type === 'JSXMemberExpression') {
|
|
106
|
+
const left = memberExpressionToString(expr.object);
|
|
107
|
+
return left ? `${left}.${right}` : null;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
API,
|
|
3
|
+
ASTPath,
|
|
4
|
+
Collection,
|
|
5
|
+
FileInfo,
|
|
6
|
+
ImportDeclaration,
|
|
7
|
+
ImportSpecifier,
|
|
8
|
+
JSCodeshift,
|
|
9
|
+
JSXAttribute,
|
|
10
|
+
JSXIdentifier,
|
|
11
|
+
JSXOpeningElement,
|
|
12
|
+
} from 'jscodeshift';
|
|
13
|
+
|
|
14
|
+
const DESIGN_SYSTEM_PACKAGE = '@wishket/design-system';
|
|
15
|
+
const SOURCE_NAME = 'Menu';
|
|
16
|
+
const LINK_NAME = 'MenuLink';
|
|
17
|
+
const BUTTON_NAME = 'MenuButton';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* v3 — `Menu` 컴포넌트를 `MenuLink` / `MenuButton`으로 분리하는 codemod.
|
|
21
|
+
*
|
|
22
|
+
* 변환 규칙:
|
|
23
|
+
*
|
|
24
|
+
* 1. `@wishket/design-system`에서 import된 `Menu`만 처리 (alias 미지원).
|
|
25
|
+
* 2. JSX 호출 분석:
|
|
26
|
+
* - `href` prop 있음 → `<MenuLink ...>`로 rename + import 정리
|
|
27
|
+
* - `href` 없고 `onClick` 있음 → `<MenuButton ...>`로 rename + import 정리
|
|
28
|
+
* - 둘 다 있음 (모순) → 변환 안 함 + TODO 코멘트
|
|
29
|
+
* - 둘 다 없음 → 변환 안 함 + TODO 코멘트
|
|
30
|
+
* - spread props (`<Menu {...props} />`) → 변환 안 함 + TODO 코멘트
|
|
31
|
+
* 3. import 정리:
|
|
32
|
+
* - `Menu`를 사용된 변형(`MenuLink` / `MenuButton` / 둘 다)으로 교체
|
|
33
|
+
* - 모든 호출을 변환하지 못해 `Menu`가 여전히 필요한 경우 `Menu`도 유지
|
|
34
|
+
*/
|
|
35
|
+
export default function transformer(file: FileInfo, api: API): string {
|
|
36
|
+
const j: JSCodeshift = api.jscodeshift;
|
|
37
|
+
const root = j(file.source);
|
|
38
|
+
|
|
39
|
+
const dsImports: Collection<ImportDeclaration> = root.find(
|
|
40
|
+
j.ImportDeclaration,
|
|
41
|
+
{
|
|
42
|
+
source: { value: DESIGN_SYSTEM_PACKAGE },
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
if (dsImports.size() === 0) return file.source;
|
|
46
|
+
|
|
47
|
+
// `Menu`가 alias 없이 default-named로 import 되었는지 확인.
|
|
48
|
+
let hasMenuImport = false;
|
|
49
|
+
let hasAliasedMenuImport = false;
|
|
50
|
+
|
|
51
|
+
dsImports.forEach(path => {
|
|
52
|
+
const specifiers = path.node.specifiers ?? [];
|
|
53
|
+
specifiers.forEach(spec => {
|
|
54
|
+
if (spec.type !== 'ImportSpecifier') return;
|
|
55
|
+
const importedName = spec.imported.name;
|
|
56
|
+
if (importedName !== SOURCE_NAME) return;
|
|
57
|
+
const localName = spec.local?.name ?? importedName;
|
|
58
|
+
if (localName !== SOURCE_NAME) {
|
|
59
|
+
hasAliasedMenuImport = true;
|
|
60
|
+
} else {
|
|
61
|
+
hasMenuImport = true;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!hasMenuImport && !hasAliasedMenuImport) {
|
|
67
|
+
return file.source;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let didChange = false;
|
|
71
|
+
let usedMenuLink = false;
|
|
72
|
+
let usedMenuButton = false;
|
|
73
|
+
let hasUnconvertedMenu = false;
|
|
74
|
+
|
|
75
|
+
if (hasAliasedMenuImport) {
|
|
76
|
+
// alias가 있는 경우 안전하게 변환하기 어려움 — 파일 상단에 안내 코멘트 추가.
|
|
77
|
+
addLeadingComment(
|
|
78
|
+
j,
|
|
79
|
+
root,
|
|
80
|
+
` TODO: codemod could not safely resolve aliased Menu import — manually split into MenuLink / MenuButton.`,
|
|
81
|
+
);
|
|
82
|
+
return root.toSource({ quote: 'single' });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
root
|
|
86
|
+
.find(j.JSXOpeningElement)
|
|
87
|
+
.forEach((path: ASTPath<JSXOpeningElement>) => {
|
|
88
|
+
const name = getJSXName(path.node);
|
|
89
|
+
if (name !== SOURCE_NAME) return;
|
|
90
|
+
|
|
91
|
+
const attrs = path.node.attributes ?? [];
|
|
92
|
+
|
|
93
|
+
const hasSpread = attrs.some(attr => attr.type === 'JSXSpreadAttribute');
|
|
94
|
+
if (hasSpread) {
|
|
95
|
+
addCommentBefore(
|
|
96
|
+
j,
|
|
97
|
+
path,
|
|
98
|
+
` TODO: codemod could not safely resolve spread props — manually choose MenuLink or MenuButton.`,
|
|
99
|
+
);
|
|
100
|
+
hasUnconvertedMenu = true;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const hasHref = attrs.some(
|
|
105
|
+
(attr): attr is JSXAttribute =>
|
|
106
|
+
attr.type === 'JSXAttribute' &&
|
|
107
|
+
attr.name.type === 'JSXIdentifier' &&
|
|
108
|
+
attr.name.name === 'href',
|
|
109
|
+
);
|
|
110
|
+
const hasOnClick = attrs.some(
|
|
111
|
+
(attr): attr is JSXAttribute =>
|
|
112
|
+
attr.type === 'JSXAttribute' &&
|
|
113
|
+
attr.name.type === 'JSXIdentifier' &&
|
|
114
|
+
attr.name.name === 'onClick',
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (hasHref && hasOnClick) {
|
|
118
|
+
addCommentBefore(
|
|
119
|
+
j,
|
|
120
|
+
path,
|
|
121
|
+
` TODO: codemod could not safely resolve — manually choose MenuLink or MenuButton.`,
|
|
122
|
+
);
|
|
123
|
+
hasUnconvertedMenu = true;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!hasHref && !hasOnClick) {
|
|
128
|
+
addCommentBefore(
|
|
129
|
+
j,
|
|
130
|
+
path,
|
|
131
|
+
` TODO: codemod could not safely resolve — Menu without href or onClick. Choose MenuLink (add href) or MenuButton (add onClick).`,
|
|
132
|
+
);
|
|
133
|
+
hasUnconvertedMenu = true;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const target = hasHref ? LINK_NAME : BUTTON_NAME;
|
|
138
|
+
renameJSXElement(j, path, target);
|
|
139
|
+
|
|
140
|
+
if (target === LINK_NAME) usedMenuLink = true;
|
|
141
|
+
else usedMenuButton = true;
|
|
142
|
+
|
|
143
|
+
didChange = true;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Import 정리.
|
|
147
|
+
dsImports.forEach(path => {
|
|
148
|
+
const specifiers = path.node.specifiers ?? [];
|
|
149
|
+
const next: ImportSpecifier[] = [];
|
|
150
|
+
let touched = false;
|
|
151
|
+
|
|
152
|
+
specifiers.forEach(spec => {
|
|
153
|
+
if (spec.type !== 'ImportSpecifier') {
|
|
154
|
+
next.push(spec as ImportSpecifier);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (spec.imported.name !== SOURCE_NAME) {
|
|
158
|
+
next.push(spec);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
// `Menu`를 제거하고 사용된 변형으로 교체.
|
|
162
|
+
touched = true;
|
|
163
|
+
if (hasUnconvertedMenu) {
|
|
164
|
+
// 일부 변환 못한 호출이 남아있어 Menu도 유지가 필요할 수 있음.
|
|
165
|
+
next.push(spec);
|
|
166
|
+
}
|
|
167
|
+
if (usedMenuLink) {
|
|
168
|
+
next.push(
|
|
169
|
+
j.importSpecifier(j.identifier(LINK_NAME)) as ImportSpecifier,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
if (usedMenuButton) {
|
|
173
|
+
next.push(
|
|
174
|
+
j.importSpecifier(j.identifier(BUTTON_NAME)) as ImportSpecifier,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (touched) {
|
|
180
|
+
// 중복 제거 (같은 이름이 이미 import된 경우 대비).
|
|
181
|
+
const seen = new Set<string>();
|
|
182
|
+
path.node.specifiers = next.filter(spec => {
|
|
183
|
+
if (spec.type !== 'ImportSpecifier') return true;
|
|
184
|
+
const importedName = spec.imported.name;
|
|
185
|
+
const localName = spec.local?.name ?? importedName;
|
|
186
|
+
const key = `${importedName}::${localName}`;
|
|
187
|
+
if (seen.has(key)) return false;
|
|
188
|
+
seen.add(key);
|
|
189
|
+
return true;
|
|
190
|
+
});
|
|
191
|
+
didChange = true;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (!didChange) return file.source;
|
|
196
|
+
|
|
197
|
+
return root.toSource({ quote: 'single' });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function getJSXName(node: JSXOpeningElement): string | null {
|
|
201
|
+
const { name } = node;
|
|
202
|
+
if (name.type === 'JSXIdentifier') return name.name;
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function renameJSXElement(
|
|
207
|
+
j: JSCodeshift,
|
|
208
|
+
path: ASTPath<JSXOpeningElement>,
|
|
209
|
+
newName: string,
|
|
210
|
+
) {
|
|
211
|
+
const opening = path.node;
|
|
212
|
+
(opening.name as JSXIdentifier).name = newName;
|
|
213
|
+
|
|
214
|
+
// self-closing이 아니면 닫는 태그도 함께 갱신.
|
|
215
|
+
const parent = path.parent.node;
|
|
216
|
+
if (parent && parent.type === 'JSXElement' && parent.closingElement) {
|
|
217
|
+
const closingName = parent.closingElement.name;
|
|
218
|
+
if (closingName.type === 'JSXIdentifier') {
|
|
219
|
+
(closingName as JSXIdentifier).name = newName;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function addCommentBefore(
|
|
225
|
+
j: JSCodeshift,
|
|
226
|
+
path: ASTPath<JSXOpeningElement>,
|
|
227
|
+
message: string,
|
|
228
|
+
) {
|
|
229
|
+
// JSX element 자체의 부모(보통 JSXElement)를 찾아 그 앞에 코멘트를 삽입.
|
|
230
|
+
let current: ASTPath | null = path;
|
|
231
|
+
while (current && current.node && current.node.type !== 'JSXElement') {
|
|
232
|
+
current = current.parent;
|
|
233
|
+
}
|
|
234
|
+
if (!current) return;
|
|
235
|
+
|
|
236
|
+
const node = current.node;
|
|
237
|
+
node.comments = node.comments ?? [];
|
|
238
|
+
node.comments.push(j.commentLine(message, true, false));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function addLeadingComment(
|
|
242
|
+
j: JSCodeshift,
|
|
243
|
+
root: Collection,
|
|
244
|
+
message: string,
|
|
245
|
+
) {
|
|
246
|
+
const program = root.get().node.program;
|
|
247
|
+
const body = program.body;
|
|
248
|
+
if (!body || body.length === 0) return;
|
|
249
|
+
const first = body[0];
|
|
250
|
+
first.comments = first.comments ?? [];
|
|
251
|
+
first.comments.push(j.commentLine(message, true, false));
|
|
252
|
+
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
2
|
-
import { MediumSystemIconName, SmallSystemIconName } from '../../DataDisplays/SystemIcon';
|
|
3
|
-
import { IconButtonDropdownItem } from '../../Inputs';
|
|
4
|
-
export interface MenuProps {
|
|
5
|
-
type?: 'main' | 'sub';
|
|
6
|
-
variant?: 'white' | 'gray';
|
|
7
|
-
name: string;
|
|
8
|
-
badgeCount?: string;
|
|
9
|
-
isSelected?: boolean;
|
|
10
|
-
leadingIcon?: MediumSystemIconName;
|
|
11
|
-
iconButtonName?: SmallSystemIconName;
|
|
12
|
-
onClick?: () => void;
|
|
13
|
-
onOptionClick?: () => void;
|
|
14
|
-
href?: string;
|
|
15
|
-
children?: ReactNode;
|
|
16
|
-
items?: IconButtonDropdownItem[];
|
|
17
|
-
selectedItem?: IconButtonDropdownItem;
|
|
18
|
-
onItemClick?: (item: IconButtonDropdownItem) => void;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* 메뉴 컴포넌트는 네비게이션에서 사용되는 클릭 가능한 항목을 표시합니다.
|
|
22
|
-
*
|
|
23
|
-
* @param {Object} props
|
|
24
|
-
* @param {'main' | 'sub'} [props.type='main'] - 메뉴 타입 ('main', 'sub')
|
|
25
|
-
* @param {'white' | 'gray'} [props.variant='white'] - 메뉴 스타일 변형 ('white', 'gray')
|
|
26
|
-
* @param {string} props.name - 메뉴에 표시될 텍스트
|
|
27
|
-
* @param {string} [props.badgeCount] - 메뉴 항목에 표시될 뱃지 숫자
|
|
28
|
-
* @param {boolean} [props.isSelected=false] - 메뉴 선택 상태
|
|
29
|
-
* @param {SystemIconName} [props.leadingIcon] - 메뉴 앞쪽에 표시될 아이콘(medium 사이즈)
|
|
30
|
-
* @param {SystemIconName} [props.iconButtonName] - IconButtonDropdown 아이콘(small 사이즈)
|
|
31
|
-
* @param {() => void} props.onClick - 메뉴 클릭 시 실행될 함수
|
|
32
|
-
* @param {() => void} [props.onOptionClick] - 후행 아이콘 클릭 시 실행될 함수 (IconButtonDropdown이 아닌 IconButton으로만 사용 시 필요)
|
|
33
|
-
* @param {string} [props.href] - 메뉴 링크 주소
|
|
34
|
-
* @param {ReactNode} [props.children] - 메뉴 내부에 표시될 자식 요소 (IconButtonDropdown 컴포넌트 사용 시 필요, trailingIcon과 함께 사용하지 않도록 주의 필요)
|
|
35
|
-
* @param {Item[]} [props.items] - IconButtonDropdown 컴포넌트 사용 시 필요 {key: number, value: string}[]
|
|
36
|
-
* @param {Item} [props.selectedItem] - IconButtonDropdown 컴포넌트 사용 시 필요 {key: number, value: string}
|
|
37
|
-
* @param {(item: Item) => void} [props.onItemClick] - IconButtonDropdown 컴포넌트 사용 시 필요 (item: Item) => {}
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* // 기본 메뉴
|
|
41
|
-
* <Menu
|
|
42
|
-
* name="메뉴 항목"
|
|
43
|
-
* onClick={() => console.log('메뉴 클릭')}
|
|
44
|
-
* />
|
|
45
|
-
*
|
|
46
|
-
* // 아이콘과 뱃지가 있는 서브 메뉴
|
|
47
|
-
* <Menu
|
|
48
|
-
* type="sub"
|
|
49
|
-
* name="서브 메뉴"
|
|
50
|
-
* leadingIcon="home"
|
|
51
|
-
* badgeCount="5"
|
|
52
|
-
* onClick={() => console.log('서브 메뉴 클릭')}
|
|
53
|
-
* />
|
|
54
|
-
*
|
|
55
|
-
* // IconButtonDropdown과 함께 사용시
|
|
56
|
-
* <Menu
|
|
57
|
-
* name="메뉴"
|
|
58
|
-
* onClick={() => console.log('메뉴 클릭')}
|
|
59
|
-
* iconButtonName="small_more_options"
|
|
60
|
-
* items={items}
|
|
61
|
-
* selectedItem={selectedItem}
|
|
62
|
-
* onItemClick={onItemClick}
|
|
63
|
-
* />
|
|
64
|
-
*
|
|
65
|
-
* // 드롭다운이 필요하지 않은 옵션 메뉴(ex. 버튼 클릭 시 모달 오픈))
|
|
66
|
-
* <Menu
|
|
67
|
-
* variant="gray"
|
|
68
|
-
* name="옵션 메뉴"
|
|
69
|
-
* isSelected={true}
|
|
70
|
-
* iconButtonName="small_more_options"
|
|
71
|
-
* onClick={() => console.log('메뉴 클릭')}
|
|
72
|
-
* onOptionClick={() => console.log('옵션 클릭', '모달 오픈')}
|
|
73
|
-
* />
|
|
74
|
-
* // 링크로 사용해야하는 메뉴
|
|
75
|
-
* <Menu
|
|
76
|
-
* href="/test"
|
|
77
|
-
* name="테스트 메뉴"
|
|
78
|
-
* />
|
|
79
|
-
*/
|
|
80
|
-
declare const Menu: ({ type, variant, name, isSelected, badgeCount, leadingIcon, iconButtonName, onClick, onOptionClick, href, items, selectedItem, onItemClick, }: MenuProps) => import("react/jsx-runtime").JSX.Element;
|
|
81
|
-
export { Menu };
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import{jsxs as t,Fragment as e,jsx as o}from"react/jsx-runtime";import{twJoin as s,twMerge as i}from"tailwind-merge";import r from"next/link";import"../../DataDisplays/Avatar/Avatar.js";import{CountBadge as n}from"../../DataDisplays/CountBadge/CountBadge.js";import{Typography as a}from"../../Base/Typography/Typography.js";import"../../Base/Layouts/Box/Box.js";import"../../Base/Layouts/FullBleed/FullBleed.js";import"react";import{SystemIcon as p}from"../../DataDisplays/SystemIcon/SystemIcon.js";import"../../DataDisplays/SystemIcon/SystemIcon.constants.js";import"../../DataDisplays/Accordion/Accordion.js";import"@wishket/yogokit";import"../../Inputs/AutoCompleteList/AutoCompleteList.parts.js";import"../../Inputs/Input/Input.js";import"../../Inputs/Input/PasswordInput.js";import"../../Inputs/Input/LabelInput.js";import"../../Inputs/Input/InputTypeSelector.js";import"../../Inputs/Button/Button.js";import"../../Inputs/Calendar/Calendar.utils.js";import"../../Inputs/Checkbox/Checkbox.js";import"../../Inputs/ChoiceChip/ChoiceChip.js";import{IconButton as m}from"../../Inputs/IconButton/IconButton.js";import"../../Inputs/Radio/Radio.js";import"../../Inputs/TextField/TextField.js";import"../../Inputs/CommentArea/CommentArea.js";import{IconButtonDropdown as l}from"../../Inputs/IconButtonDropdown/IconButtonDropdown.js";import"../../Inputs/FilterChip/FilterChip.js";
|
|
2
|
-
/**
|
|
3
|
-
* 메뉴 컴포넌트는 네비게이션에서 사용되는 클릭 가능한 항목을 표시합니다.
|
|
4
|
-
*
|
|
5
|
-
* @param {Object} props
|
|
6
|
-
* @param {'main' | 'sub'} [props.type='main'] - 메뉴 타입 ('main', 'sub')
|
|
7
|
-
* @param {'white' | 'gray'} [props.variant='white'] - 메뉴 스타일 변형 ('white', 'gray')
|
|
8
|
-
* @param {string} props.name - 메뉴에 표시될 텍스트
|
|
9
|
-
* @param {string} [props.badgeCount] - 메뉴 항목에 표시될 뱃지 숫자
|
|
10
|
-
* @param {boolean} [props.isSelected=false] - 메뉴 선택 상태
|
|
11
|
-
* @param {SystemIconName} [props.leadingIcon] - 메뉴 앞쪽에 표시될 아이콘(medium 사이즈)
|
|
12
|
-
* @param {SystemIconName} [props.iconButtonName] - IconButtonDropdown 아이콘(small 사이즈)
|
|
13
|
-
* @param {() => void} props.onClick - 메뉴 클릭 시 실행될 함수
|
|
14
|
-
* @param {() => void} [props.onOptionClick] - 후행 아이콘 클릭 시 실행될 함수 (IconButtonDropdown이 아닌 IconButton으로만 사용 시 필요)
|
|
15
|
-
* @param {string} [props.href] - 메뉴 링크 주소
|
|
16
|
-
* @param {ReactNode} [props.children] - 메뉴 내부에 표시될 자식 요소 (IconButtonDropdown 컴포넌트 사용 시 필요, trailingIcon과 함께 사용하지 않도록 주의 필요)
|
|
17
|
-
* @param {Item[]} [props.items] - IconButtonDropdown 컴포넌트 사용 시 필요 {key: number, value: string}[]
|
|
18
|
-
* @param {Item} [props.selectedItem] - IconButtonDropdown 컴포넌트 사용 시 필요 {key: number, value: string}
|
|
19
|
-
* @param {(item: Item) => void} [props.onItemClick] - IconButtonDropdown 컴포넌트 사용 시 필요 (item: Item) => {}
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* // 기본 메뉴
|
|
23
|
-
* <Menu
|
|
24
|
-
* name="메뉴 항목"
|
|
25
|
-
* onClick={() => console.log('메뉴 클릭')}
|
|
26
|
-
* />
|
|
27
|
-
*
|
|
28
|
-
* // 아이콘과 뱃지가 있는 서브 메뉴
|
|
29
|
-
* <Menu
|
|
30
|
-
* type="sub"
|
|
31
|
-
* name="서브 메뉴"
|
|
32
|
-
* leadingIcon="home"
|
|
33
|
-
* badgeCount="5"
|
|
34
|
-
* onClick={() => console.log('서브 메뉴 클릭')}
|
|
35
|
-
* />
|
|
36
|
-
*
|
|
37
|
-
* // IconButtonDropdown과 함께 사용시
|
|
38
|
-
* <Menu
|
|
39
|
-
* name="메뉴"
|
|
40
|
-
* onClick={() => console.log('메뉴 클릭')}
|
|
41
|
-
* iconButtonName="small_more_options"
|
|
42
|
-
* items={items}
|
|
43
|
-
* selectedItem={selectedItem}
|
|
44
|
-
* onItemClick={onItemClick}
|
|
45
|
-
* />
|
|
46
|
-
*
|
|
47
|
-
* // 드롭다운이 필요하지 않은 옵션 메뉴(ex. 버튼 클릭 시 모달 오픈))
|
|
48
|
-
* <Menu
|
|
49
|
-
* variant="gray"
|
|
50
|
-
* name="옵션 메뉴"
|
|
51
|
-
* isSelected={true}
|
|
52
|
-
* iconButtonName="small_more_options"
|
|
53
|
-
* onClick={() => console.log('메뉴 클릭')}
|
|
54
|
-
* onOptionClick={() => console.log('옵션 클릭', '모달 오픈')}
|
|
55
|
-
* />
|
|
56
|
-
* // 링크로 사용해야하는 메뉴
|
|
57
|
-
* <Menu
|
|
58
|
-
* href="/test"
|
|
59
|
-
* name="테스트 메뉴"
|
|
60
|
-
* />
|
|
61
|
-
*/const u=({type:u="main",variant:c="white",name:d,isSelected:y=!1,badgeCount:I,leadingIcon:g,iconButtonName:h,onClick:j,onOptionClick:C,href:w,items:x,selectedItem:B,onItemClick:f})=>{const b=!!(h&&x&&f&&B),k=!!h&&!!C,D=(t,e,o)=>{const r="sub"===t?"pl-12":"",n={white:s("hover:bg-w-gray-50",o&&"bg-primary-10 text-primary hover:bg-primary-10"),gray:s("hover:bg-w-gray-100",o&&"bg-w-gray-100 hover:bg-w-gray-100")};return i("flex w-full cursor-pointer items-center gap-2 rounded-xl px-4 py-3 text-w-gray-900 text-left",r,n[e])},v=/*#__PURE__*/t(e,{children:[g&&/*#__PURE__*/o(p,{testId:"design-system-menu-leading-icon",name:g,className:s("white"===c&&y&&"text-primary")}),
|
|
62
|
-
/*#__PURE__*/o(a,{"data-testid":"design-system-menu-name",variant:"body16",className:"w-full select-none",children:d}),I&&/*#__PURE__*/o(n,{variant:((t,e)=>"white"!==t?"white_gray":e?"primary":"gray")(c,y),text:I,className:"relative",showZero:!0}),b&&/*#__PURE__*/o(l,{size:"sm",icon:h,items:x,selectedItem:B,onItemClick:f}),k&&/*#__PURE__*/o(m,{size:"sm",className:"shrink-0",onClick:t=>{t.stopPropagation(),C()},children:/*#__PURE__*/o(p,{name:h})})]});return w?/*#__PURE__*/o(r,{href:w,"data-testid":"design-system-menu",className:D(u,c,y),children:v}):/*#__PURE__*/o("button",{"data-testid":"design-system-menu",className:D(u,c,y),onClick:j,children:v})};export{u as Menu};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
"use strict";var e=require("react/jsx-runtime"),t=require("tailwind-merge"),r=require("next/link");require("../../DataDisplays/Avatar/Avatar.js");var s=require("../../DataDisplays/CountBadge/CountBadge.js"),n=require("../../Base/Typography/Typography.js");require("../../Base/Layouts/Box/Box.js"),require("../../Base/Layouts/FullBleed/FullBleed.js"),require("react");var i=require("../../DataDisplays/SystemIcon/SystemIcon.js");require("../../DataDisplays/SystemIcon/SystemIcon.constants.js"),require("../../DataDisplays/Accordion/Accordion.js"),require("@wishket/yogokit"),require("../../Inputs/AutoCompleteList/AutoCompleteList.parts.js"),require("../../Inputs/Input/Input.js"),require("../../Inputs/Input/PasswordInput.js"),require("../../Inputs/Input/LabelInput.js"),require("../../Inputs/Input/InputTypeSelector.js"),require("../../Inputs/Button/Button.js"),require("../../Inputs/Calendar/Calendar.utils.js"),require("../../Inputs/Checkbox/Checkbox.js"),require("../../Inputs/ChoiceChip/ChoiceChip.js");var a=require("../../Inputs/IconButton/IconButton.js");require("../../Inputs/Radio/Radio.js"),require("../../Inputs/TextField/TextField.js"),require("../../Inputs/CommentArea/CommentArea.js");var o=require("../../Inputs/IconButtonDropdown/IconButtonDropdown.js");require("../../Inputs/FilterChip/FilterChip.js");exports.Menu=({type:u="main",variant:p="white",name:l,isSelected:c=!1,badgeCount:m,leadingIcon:d,iconButtonName:y,onClick:I,onOptionClick:g,href:j,items:h,selectedItem:q,onItemClick:x})=>{const C=!!(y&&h&&x&&q),w=!!y&&!!g,B=(e,r,s)=>{const n="sub"===e?"pl-12":"",i={white:t.twJoin("hover:bg-w-gray-50",s&&"bg-primary-10 text-primary hover:bg-primary-10"),gray:t.twJoin("hover:bg-w-gray-100",s&&"bg-w-gray-100 hover:bg-w-gray-100")};return t.twMerge("flex w-full cursor-pointer items-center gap-2 rounded-xl px-4 py-3 text-w-gray-900 text-left",n,i[r])},v=/*#__PURE__*/e.jsxs(e.Fragment,{children:[d&&/*#__PURE__*/e.jsx(i.SystemIcon,{testId:"design-system-menu-leading-icon",name:d,className:t.twJoin("white"===p&&c&&"text-primary")}),
|
|
2
|
-
/*#__PURE__*/e.jsx(n.Typography,{"data-testid":"design-system-menu-name",variant:"body16",className:"w-full select-none",children:l}),m&&/*#__PURE__*/e.jsx(s.CountBadge,{variant:((e,t)=>"white"!==e?"white_gray":t?"primary":"gray")(p,c),text:m,className:"relative",showZero:!0}),C&&/*#__PURE__*/e.jsx(o.IconButtonDropdown,{size:"sm",icon:y,items:h,selectedItem:q,onItemClick:x}),w&&/*#__PURE__*/e.jsx(a.IconButton,{size:"sm",className:"shrink-0",onClick:e=>{e.stopPropagation(),g()},children:/*#__PURE__*/e.jsx(i.SystemIcon,{name:y})})]});return j?/*#__PURE__*/e.jsx(r,{href:j,"data-testid":"design-system-menu",className:B(u,p,c),children:v}):/*#__PURE__*/e.jsx("button",{"data-testid":"design-system-menu",className:B(u,p,c),onClick:I,children:v})};
|