@mindlogic-ai/logician-ui 5.0.0-alpha.11 → 5.0.0-alpha.13
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 +1 -0
- package/dist/components/Input/Input.d.ts.map +1 -1
- package/dist/components/Input/Input.js +4 -0
- package/dist/components/Input/Input.js.map +1 -1
- package/dist/components/Input/Input.mjs +4 -0
- package/dist/components/Input/Input.mjs.map +1 -1
- package/dist/components/NavList/NavList.d.ts +34 -0
- package/dist/components/NavList/NavList.d.ts.map +1 -0
- package/dist/components/NavList/NavList.js +113 -0
- package/dist/components/NavList/NavList.js.map +1 -0
- package/dist/components/NavList/NavList.mjs +111 -0
- package/dist/components/NavList/NavList.mjs.map +1 -0
- package/dist/components/NavList/NavList.styles.d.ts +90 -0
- package/dist/components/NavList/NavList.styles.d.ts.map +1 -0
- package/dist/components/NavList/NavList.styles.js +104 -0
- package/dist/components/NavList/NavList.styles.js.map +1 -0
- package/dist/components/NavList/NavList.styles.mjs +97 -0
- package/dist/components/NavList/NavList.styles.mjs.map +1 -0
- package/dist/components/NavList/NavList.translations.json.js +19 -0
- package/dist/components/NavList/NavList.translations.json.js.map +1 -0
- package/dist/components/NavList/NavList.translations.json.mjs +14 -0
- package/dist/components/NavList/NavList.translations.json.mjs.map +1 -0
- package/dist/components/NavList/NavList.types.d.ts +131 -0
- package/dist/components/NavList/NavList.types.d.ts.map +1 -0
- package/dist/components/NavList/NavListContext.d.ts +30 -0
- package/dist/components/NavList/NavListContext.d.ts.map +1 -0
- package/dist/components/NavList/NavListContext.js +16 -0
- package/dist/components/NavList/NavListContext.js.map +1 -0
- package/dist/components/NavList/NavListContext.mjs +13 -0
- package/dist/components/NavList/NavListContext.mjs.map +1 -0
- package/dist/components/NavList/NavListGroup.d.ts +31 -0
- package/dist/components/NavList/NavListGroup.d.ts.map +1 -0
- package/dist/components/NavList/NavListGroup.js +115 -0
- package/dist/components/NavList/NavListGroup.js.map +1 -0
- package/dist/components/NavList/NavListGroup.mjs +113 -0
- package/dist/components/NavList/NavListGroup.mjs.map +1 -0
- package/dist/components/NavList/NavListItem.d.ts +15 -0
- package/dist/components/NavList/NavListItem.d.ts.map +1 -0
- package/dist/components/NavList/NavListItem.js +269 -0
- package/dist/components/NavList/NavListItem.js.map +1 -0
- package/dist/components/NavList/NavListItem.mjs +267 -0
- package/dist/components/NavList/NavListItem.mjs.map +1 -0
- package/dist/components/NavList/NavListSkeleton.d.ts +17 -0
- package/dist/components/NavList/NavListSkeleton.d.ts.map +1 -0
- package/dist/components/NavList/NavListSkeleton.js +33 -0
- package/dist/components/NavList/NavListSkeleton.js.map +1 -0
- package/dist/components/NavList/NavListSkeleton.mjs +31 -0
- package/dist/components/NavList/NavListSkeleton.mjs.map +1 -0
- package/dist/components/NavList/index.d.ts +7 -0
- package/dist/components/NavList/index.d.ts.map +1 -0
- package/dist/components/Textarea/Textarea.d.ts.map +1 -1
- package/dist/components/Textarea/Textarea.js +4 -0
- package/dist/components/Textarea/Textarea.js.map +1 -1
- package/dist/components/Textarea/Textarea.mjs +4 -0
- package/dist/components/Textarea/Textarea.mjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6 -1
- package/dist/index.mjs.map +1 -1
- package/dist/utils/focusRing.d.ts +22 -0
- package/dist/utils/focusRing.d.ts.map +1 -1
- package/dist/utils/focusRing.js +22 -0
- package/dist/utils/focusRing.js.map +1 -1
- package/dist/utils/focusRing.mjs +21 -1
- package/dist/utils/focusRing.mjs.map +1 -1
- package/package.json +2 -1
- package/src/components/Input/Input.tsx +4 -0
- package/src/components/Menu/Menu.usage.mdx +8 -0
- package/src/components/NavList/NavList.stories.tsx +895 -0
- package/src/components/NavList/NavList.styles.ts +114 -0
- package/src/components/NavList/NavList.test.tsx +1254 -0
- package/src/components/NavList/NavList.translations.json +9 -0
- package/src/components/NavList/NavList.tsx +160 -0
- package/src/components/NavList/NavList.types.ts +145 -0
- package/src/components/NavList/NavList.usage.mdx +540 -0
- package/src/components/NavList/NavListContext.ts +25 -0
- package/src/components/NavList/NavListGroup.tsx +243 -0
- package/src/components/NavList/NavListItem.tsx +525 -0
- package/src/components/NavList/NavListSkeleton.tsx +57 -0
- package/src/components/NavList/index.ts +14 -0
- package/src/components/Tabs/Tabs.usage.mdx +58 -14
- package/src/components/Textarea/Textarea.tsx +4 -0
- package/src/components/Tooltip/Tooltip.usage.mdx +8 -0
- package/src/components/a11y.kwcag.test.tsx +66 -0
- package/src/components/claude.md +1 -1
- package/src/index.ts +7 -1
- package/src/utils/focusRing.ts +23 -0
|
@@ -0,0 +1,895 @@
|
|
|
1
|
+
import { useId, useState } from 'react';
|
|
2
|
+
import { Box, Flex, Text, VisuallyHidden } from '@chakra-ui/react';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
|
|
5
|
+
import { T } from '../../guidelines/_components/Lang';
|
|
6
|
+
import { TokenTable } from '../../guidelines/_components/TokenTable';
|
|
7
|
+
import { Badge } from '../Badge';
|
|
8
|
+
import { Button } from '../Button';
|
|
9
|
+
import {
|
|
10
|
+
AlertTriangleIcon,
|
|
11
|
+
BookIcon,
|
|
12
|
+
ChatIcon,
|
|
13
|
+
ClockIcon,
|
|
14
|
+
DotsHorizontalIcon,
|
|
15
|
+
FilledChatIcon,
|
|
16
|
+
FileMusicIcon,
|
|
17
|
+
FileVideoIcon,
|
|
18
|
+
HomeIcon,
|
|
19
|
+
LayoutDashboardIcon,
|
|
20
|
+
PhotoIcon,
|
|
21
|
+
PresentationIcon,
|
|
22
|
+
SettingsIcon,
|
|
23
|
+
StarIcon,
|
|
24
|
+
UsersIcon,
|
|
25
|
+
} from '../Icon';
|
|
26
|
+
import { IconButton } from '../IconButton';
|
|
27
|
+
import { Menu } from '../Menu';
|
|
28
|
+
import { Tooltip } from '../Tooltip';
|
|
29
|
+
import { NavList } from './NavList';
|
|
30
|
+
import { NAV_LIST_RAMP } from './NavList.styles';
|
|
31
|
+
|
|
32
|
+
const meta = {
|
|
33
|
+
title: 'Components/NavList',
|
|
34
|
+
component: NavList,
|
|
35
|
+
parameters: { layout: 'padded' },
|
|
36
|
+
argTypes: {
|
|
37
|
+
orientation: {
|
|
38
|
+
control: { type: 'radio' },
|
|
39
|
+
options: ['vertical', 'horizontal'],
|
|
40
|
+
description:
|
|
41
|
+
"`vertical` is a rail; `horizontal` is one row that scrolls sideways. Accepts a per-breakpoint object, e.g. `{ base: 'horizontal', md: 'vertical' }`.",
|
|
42
|
+
table: { defaultValue: { summary: 'vertical' } },
|
|
43
|
+
},
|
|
44
|
+
isCollapsed: {
|
|
45
|
+
control: { type: 'boolean' },
|
|
46
|
+
description:
|
|
47
|
+
'The icon-only rail: 40px rows, label as accessible name and tooltip. Every row needs a `leadingVisual`.',
|
|
48
|
+
table: { defaultValue: { summary: 'false' } },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
} satisfies Meta<typeof NavList>;
|
|
52
|
+
|
|
53
|
+
export default meta;
|
|
54
|
+
|
|
55
|
+
type Story = StoryObj<typeof meta>;
|
|
56
|
+
|
|
57
|
+
/** A rail panel to sit the list on, like the app sidebar. */
|
|
58
|
+
const Rail = ({
|
|
59
|
+
children,
|
|
60
|
+
w = '240px',
|
|
61
|
+
isAnimated = false,
|
|
62
|
+
}: {
|
|
63
|
+
children: React.ReactNode;
|
|
64
|
+
w?: string;
|
|
65
|
+
/** Animate the width, the way the app's NavigationBarWrapper does. */
|
|
66
|
+
isAnimated?: boolean;
|
|
67
|
+
}) => (
|
|
68
|
+
<Box
|
|
69
|
+
w={w}
|
|
70
|
+
p={2}
|
|
71
|
+
bg="bg.subtle"
|
|
72
|
+
borderRadius="lg"
|
|
73
|
+
{...(isAnimated && {
|
|
74
|
+
animationStyle: 'feedback',
|
|
75
|
+
transitionProperty: 'width',
|
|
76
|
+
overflowX: 'hidden',
|
|
77
|
+
})}
|
|
78
|
+
>
|
|
79
|
+
{children}
|
|
80
|
+
</Box>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A row's more-menu with its own tooltip. Menu and Tooltip both make the
|
|
85
|
+
* button their trigger, and each would give it a different id — the menu
|
|
86
|
+
* then cannot find its anchor and opens at the top-left of the page. One
|
|
87
|
+
* shared `ids.trigger` makes them agree (factchat-nextjs does the same in
|
|
88
|
+
* ResearchReportActions).
|
|
89
|
+
*/
|
|
90
|
+
const RowMenu = ({ label }: { label: string }) => {
|
|
91
|
+
const triggerId = useId();
|
|
92
|
+
return (
|
|
93
|
+
<Menu ids={{ trigger: triggerId }}>
|
|
94
|
+
<Tooltip ids={{ trigger: triggerId }} content="더 보기">
|
|
95
|
+
<Menu.Trigger asChild>
|
|
96
|
+
<IconButton aria-label={`${label} 더 보기`} size="xs">
|
|
97
|
+
<DotsHorizontalIcon />
|
|
98
|
+
</IconButton>
|
|
99
|
+
</Menu.Trigger>
|
|
100
|
+
</Tooltip>
|
|
101
|
+
<Menu.List>
|
|
102
|
+
<Menu.Item value="rename">이름 바꾸기</Menu.Item>
|
|
103
|
+
<Menu.Item value="delete">삭제</Menu.Item>
|
|
104
|
+
</Menu.List>
|
|
105
|
+
</Menu>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/** Links with icons. The current page has `aria-current="page"`. */
|
|
110
|
+
export const Basic: Story = {
|
|
111
|
+
render: (args) => (
|
|
112
|
+
<Rail>
|
|
113
|
+
<NavList {...args} aria-label="관리자 메뉴">
|
|
114
|
+
<NavList.Item href="#" leadingVisual={<HomeIcon />} isCurrent>
|
|
115
|
+
홈
|
|
116
|
+
</NavList.Item>
|
|
117
|
+
<NavList.Item href="#" leadingVisual={<UsersIcon />}>
|
|
118
|
+
멤버 관리
|
|
119
|
+
</NavList.Item>
|
|
120
|
+
<NavList.Item href="#" leadingVisual={<LayoutDashboardIcon />}>
|
|
121
|
+
사용량 대시보드
|
|
122
|
+
</NavList.Item>
|
|
123
|
+
<NavList.Item href="#" leadingVisual={<SettingsIcon />}>
|
|
124
|
+
설정
|
|
125
|
+
</NavList.Item>
|
|
126
|
+
</NavList>
|
|
127
|
+
</Rail>
|
|
128
|
+
),
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const FILTERS = [
|
|
132
|
+
{ key: 'all', label: '전체', count: 128 },
|
|
133
|
+
{ key: 'image', label: '이미지', count: 64, icon: <PhotoIcon /> },
|
|
134
|
+
{ key: 'video', label: '영상', count: 21, icon: <FileVideoIcon /> },
|
|
135
|
+
{ key: 'music', label: '음악', count: 9, icon: <FileMusicIcon /> },
|
|
136
|
+
{ key: 'ppt', label: '프레젠테이션', count: 34, icon: <PresentationIcon /> },
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* An in-page filter rail: buttons, not links, so the current filter has
|
|
141
|
+
* `aria-current="true"`. Counts go in `trailingVisual`.
|
|
142
|
+
*/
|
|
143
|
+
export const FilterRailWithCounts: Story = {
|
|
144
|
+
render: (args) => {
|
|
145
|
+
const [current, setCurrent] = useState('all');
|
|
146
|
+
return (
|
|
147
|
+
<Rail>
|
|
148
|
+
<NavList {...args} aria-label="템플릿 종류">
|
|
149
|
+
{FILTERS.map((f) => (
|
|
150
|
+
<NavList.Item
|
|
151
|
+
key={f.key}
|
|
152
|
+
leadingVisual={f.icon}
|
|
153
|
+
trailingVisual={f.count}
|
|
154
|
+
isCurrent={current === f.key}
|
|
155
|
+
onClick={() => setCurrent(f.key)}
|
|
156
|
+
>
|
|
157
|
+
{f.label}
|
|
158
|
+
</NavList.Item>
|
|
159
|
+
))}
|
|
160
|
+
</NavList>
|
|
161
|
+
</Rail>
|
|
162
|
+
);
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/** Groups with overline titles. Each title names its group's list. */
|
|
167
|
+
export const GroupsWithHeadings: Story = {
|
|
168
|
+
render: (args) => {
|
|
169
|
+
const [current, setCurrent] = useState('recent');
|
|
170
|
+
const item = (key: string, label: string, icon?: React.ReactNode) => (
|
|
171
|
+
<NavList.Item
|
|
172
|
+
leadingVisual={icon}
|
|
173
|
+
isCurrent={current === key}
|
|
174
|
+
onClick={() => setCurrent(key)}
|
|
175
|
+
>
|
|
176
|
+
{label}
|
|
177
|
+
</NavList.Item>
|
|
178
|
+
);
|
|
179
|
+
return (
|
|
180
|
+
<Rail w="220px">
|
|
181
|
+
<NavList {...args} aria-label="드라이브 위치">
|
|
182
|
+
{item('recent', '최근 문서함', <ClockIcon />)}
|
|
183
|
+
{item('shared', '공유 문서함', <UsersIcon />)}
|
|
184
|
+
{item('starred', '중요 문서함', <StarIcon />)}
|
|
185
|
+
<NavList.Group title="공유 드라이브">
|
|
186
|
+
{item('marketing', '마케팅팀')}
|
|
187
|
+
{item('sales', '영업팀 — 2026년 하반기 제안서와 계약서 보관함')}
|
|
188
|
+
{item('dev', '개발팀')}
|
|
189
|
+
</NavList.Group>
|
|
190
|
+
</NavList>
|
|
191
|
+
</Rail>
|
|
192
|
+
);
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* `trailingAction`: a separate button beside the row, with its own name.
|
|
198
|
+
* It is a sibling of the link, so Tab reaches the row and then its action.
|
|
199
|
+
*/
|
|
200
|
+
export const TrailingAction: Story = {
|
|
201
|
+
render: (args) => (
|
|
202
|
+
<Rail>
|
|
203
|
+
<NavList {...args} aria-label="최근 대화">
|
|
204
|
+
{[
|
|
205
|
+
'보고서 요약 요청',
|
|
206
|
+
'회의록 정리',
|
|
207
|
+
'2026 하반기 마케팅 캠페인 아이디어',
|
|
208
|
+
].map((title, i) => (
|
|
209
|
+
<NavList.Item
|
|
210
|
+
key={title}
|
|
211
|
+
href="#"
|
|
212
|
+
isCurrent={i === 0}
|
|
213
|
+
trailingAction={
|
|
214
|
+
<IconButton aria-label={`${title} 더 보기`} size="xs">
|
|
215
|
+
<DotsHorizontalIcon />
|
|
216
|
+
</IconButton>
|
|
217
|
+
}
|
|
218
|
+
>
|
|
219
|
+
{title}
|
|
220
|
+
</NavList.Item>
|
|
221
|
+
))}
|
|
222
|
+
</NavList>
|
|
223
|
+
</Rail>
|
|
224
|
+
),
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/** Disabled rows use the fixed `disabled.fg` ink — never opacity — and are skipped by Tab. */
|
|
228
|
+
export const Disabled: Story = {
|
|
229
|
+
render: (args) => (
|
|
230
|
+
<Rail>
|
|
231
|
+
<NavList {...args} aria-label="설정">
|
|
232
|
+
<NavList.Item href="#" isCurrent leadingVisual={<SettingsIcon />}>
|
|
233
|
+
일반
|
|
234
|
+
</NavList.Item>
|
|
235
|
+
<NavList.Item href="#" isDisabled leadingVisual={<UsersIcon />}>
|
|
236
|
+
SSO (엔터프라이즈 전용)
|
|
237
|
+
</NavList.Item>
|
|
238
|
+
<NavList.Item onClick={() => {}} isDisabled trailingVisual={0}>
|
|
239
|
+
보관된 항목
|
|
240
|
+
</NavList.Item>
|
|
241
|
+
</NavList>
|
|
242
|
+
</Rail>
|
|
243
|
+
),
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/** One row that scrolls sideways instead of wrapping. */
|
|
247
|
+
export const Horizontal: Story = {
|
|
248
|
+
args: { orientation: 'horizontal' },
|
|
249
|
+
render: (args) => {
|
|
250
|
+
const [current, setCurrent] = useState('all');
|
|
251
|
+
return (
|
|
252
|
+
<Box maxW="360px" p={2} bg="bg.subtle" borderRadius="lg">
|
|
253
|
+
<NavList {...args} aria-label="템플릿 종류">
|
|
254
|
+
{FILTERS.map((f) => (
|
|
255
|
+
<NavList.Item
|
|
256
|
+
key={f.key}
|
|
257
|
+
leadingVisual={f.icon}
|
|
258
|
+
trailingVisual={f.count}
|
|
259
|
+
isCurrent={current === f.key}
|
|
260
|
+
onClick={() => setCurrent(f.key)}
|
|
261
|
+
>
|
|
262
|
+
{f.label}
|
|
263
|
+
</NavList.Item>
|
|
264
|
+
))}
|
|
265
|
+
</NavList>
|
|
266
|
+
</Box>
|
|
267
|
+
);
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* `orientation={{ base: 'horizontal', md: 'vertical' }}`: a rail beside the
|
|
273
|
+
* content from `md` up, a scrolling strip above it below. Resize the canvas.
|
|
274
|
+
*/
|
|
275
|
+
export const Responsive: Story = {
|
|
276
|
+
render: () => {
|
|
277
|
+
const [current, setCurrent] = useState('all');
|
|
278
|
+
return (
|
|
279
|
+
<Flex
|
|
280
|
+
direction={{ base: 'column', md: 'row' }}
|
|
281
|
+
borderWidth="1px"
|
|
282
|
+
borderColor="border.subtle"
|
|
283
|
+
borderRadius="lg"
|
|
284
|
+
overflow="hidden"
|
|
285
|
+
minH="240px"
|
|
286
|
+
>
|
|
287
|
+
<Box
|
|
288
|
+
w={{ base: 'full', md: '200px' }}
|
|
289
|
+
flexShrink={0}
|
|
290
|
+
p={2}
|
|
291
|
+
bg="bg.subtle"
|
|
292
|
+
borderRightWidth={{ base: 0, md: '1px' }}
|
|
293
|
+
borderBottomWidth={{ base: '1px', md: 0 }}
|
|
294
|
+
borderColor="border.subtle"
|
|
295
|
+
>
|
|
296
|
+
<NavList
|
|
297
|
+
aria-label="템플릿 종류"
|
|
298
|
+
orientation={{ base: 'horizontal', md: 'vertical' }}
|
|
299
|
+
>
|
|
300
|
+
{FILTERS.map((f) => (
|
|
301
|
+
<NavList.Item
|
|
302
|
+
key={f.key}
|
|
303
|
+
leadingVisual={f.icon}
|
|
304
|
+
trailingVisual={f.count}
|
|
305
|
+
isCurrent={current === f.key}
|
|
306
|
+
onClick={() => setCurrent(f.key)}
|
|
307
|
+
>
|
|
308
|
+
{f.label}
|
|
309
|
+
</NavList.Item>
|
|
310
|
+
))}
|
|
311
|
+
</NavList>
|
|
312
|
+
</Box>
|
|
313
|
+
<Box p={4} flex={1}>
|
|
314
|
+
<Text color="fg.muted">
|
|
315
|
+
{FILTERS.find((f) => f.key === current)?.label} 템플릿
|
|
316
|
+
</Text>
|
|
317
|
+
</Box>
|
|
318
|
+
</Flex>
|
|
319
|
+
);
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Every state in one rail, for side-by-side review in light and dark: rest,
|
|
325
|
+
* current, a count, a disabled link, a disabled button, and disabled + current.
|
|
326
|
+
* Hover, press and focus are live — point at, press and Tab through it. A
|
|
327
|
+
* press is one step past hover (`border.default`), on the current row too.
|
|
328
|
+
*/
|
|
329
|
+
export const States: Story = {
|
|
330
|
+
render: (args) => (
|
|
331
|
+
<Rail>
|
|
332
|
+
<NavList {...args} aria-label="상태">
|
|
333
|
+
<NavList.Item href="#rest" leadingVisual={<HomeIcon boxSize="sm" />}>
|
|
334
|
+
기본
|
|
335
|
+
</NavList.Item>
|
|
336
|
+
<NavList.Item
|
|
337
|
+
href="#current"
|
|
338
|
+
isCurrent
|
|
339
|
+
leadingVisual={<UsersIcon boxSize="sm" />}
|
|
340
|
+
>
|
|
341
|
+
현재
|
|
342
|
+
</NavList.Item>
|
|
343
|
+
<NavList.Item
|
|
344
|
+
href="#count"
|
|
345
|
+
leadingVisual={<PhotoIcon boxSize="sm" />}
|
|
346
|
+
trailingVisual={64}
|
|
347
|
+
>
|
|
348
|
+
개수
|
|
349
|
+
</NavList.Item>
|
|
350
|
+
<NavList.Item
|
|
351
|
+
href="#disabled"
|
|
352
|
+
isDisabled
|
|
353
|
+
leadingVisual={<SettingsIcon boxSize="sm" />}
|
|
354
|
+
>
|
|
355
|
+
사용 불가 링크
|
|
356
|
+
</NavList.Item>
|
|
357
|
+
<NavList.Item onClick={() => {}} isDisabled trailingVisual={0}>
|
|
358
|
+
사용 불가 버튼
|
|
359
|
+
</NavList.Item>
|
|
360
|
+
<NavList.Item
|
|
361
|
+
href="#disabled-current"
|
|
362
|
+
isDisabled
|
|
363
|
+
isCurrent
|
|
364
|
+
leadingVisual={<StarIcon boxSize="sm" />}
|
|
365
|
+
>
|
|
366
|
+
사용 불가 + 현재
|
|
367
|
+
</NavList.Item>
|
|
368
|
+
</NavList>
|
|
369
|
+
</Rail>
|
|
370
|
+
),
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const RAIL_ITEMS = [
|
|
374
|
+
{
|
|
375
|
+
key: 'chat',
|
|
376
|
+
label: '새 채팅',
|
|
377
|
+
icon: <ChatIcon boxSize="sm" />,
|
|
378
|
+
activeIcon: <FilledChatIcon boxSize="sm" />,
|
|
379
|
+
},
|
|
380
|
+
{ key: 'members', label: '멤버 관리', icon: <UsersIcon boxSize="sm" /> },
|
|
381
|
+
{
|
|
382
|
+
key: 'usage',
|
|
383
|
+
label: '사용량 대시보드',
|
|
384
|
+
icon: <LayoutDashboardIcon boxSize="sm" />,
|
|
385
|
+
},
|
|
386
|
+
{ key: 'settings', label: '설정', icon: <SettingsIcon boxSize="sm" /> },
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* `isCollapsed`: the icon-only rail. Each row is a 40px square; its label is
|
|
391
|
+
* still the accessible name and shows as a tooltip on hover and keyboard
|
|
392
|
+
* focus. The active-icon swap is the caller's — pick `leadingVisual` by
|
|
393
|
+
* `isCurrent`.
|
|
394
|
+
*/
|
|
395
|
+
export const CollapsedRail: Story = {
|
|
396
|
+
render: () => {
|
|
397
|
+
const [isCollapsed, setIsCollapsed] = useState(true);
|
|
398
|
+
const [current, setCurrent] = useState('chat');
|
|
399
|
+
return (
|
|
400
|
+
<Flex direction="column" gap={3} align="start">
|
|
401
|
+
<Button
|
|
402
|
+
size="sm"
|
|
403
|
+
variant="secondary"
|
|
404
|
+
onClick={() => setIsCollapsed((c) => !c)}
|
|
405
|
+
>
|
|
406
|
+
{isCollapsed ? '펼치기' : '접기'}
|
|
407
|
+
</Button>
|
|
408
|
+
{/* The parent animates the width; NavList fades its labels, counts
|
|
409
|
+
and group titles on the same clock and keeps every icon where it
|
|
410
|
+
is. 3.5em = the 2.5em row square + the rail's 0.5em padding. */}
|
|
411
|
+
<Rail w={isCollapsed ? '3.5em' : '240px'} isAnimated>
|
|
412
|
+
<NavList aria-label="관리자 메뉴" isCollapsed={isCollapsed}>
|
|
413
|
+
{RAIL_ITEMS.map((item) => (
|
|
414
|
+
<NavList.Item
|
|
415
|
+
key={item.key}
|
|
416
|
+
href={`#${item.key}`}
|
|
417
|
+
isCurrent={current === item.key}
|
|
418
|
+
onClick={(e) => {
|
|
419
|
+
e.preventDefault();
|
|
420
|
+
setCurrent(item.key);
|
|
421
|
+
}}
|
|
422
|
+
leadingVisual={
|
|
423
|
+
current === item.key && item.activeIcon
|
|
424
|
+
? item.activeIcon
|
|
425
|
+
: item.icon
|
|
426
|
+
}
|
|
427
|
+
trailingVisual={item.key === 'members' ? 12 : undefined}
|
|
428
|
+
>
|
|
429
|
+
{item.label}
|
|
430
|
+
</NavList.Item>
|
|
431
|
+
))}
|
|
432
|
+
<NavList.Group title="도움말">
|
|
433
|
+
<NavList.Item
|
|
434
|
+
href="https://docs.example.com"
|
|
435
|
+
target="_blank"
|
|
436
|
+
rel="noreferrer"
|
|
437
|
+
leadingVisual={<BookIcon boxSize="sm" />}
|
|
438
|
+
>
|
|
439
|
+
사용 가이드
|
|
440
|
+
</NavList.Item>
|
|
441
|
+
</NavList.Group>
|
|
442
|
+
</NavList>
|
|
443
|
+
</Rail>
|
|
444
|
+
</Flex>
|
|
445
|
+
);
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* `tooltip` adds a description (`aria-describedby`); `target="_blank"` adds
|
|
451
|
+
* "(새 창 열림)" to the link's accessible name. A label the ellipsis cuts off
|
|
452
|
+
* shows itself in full on hover and focus — no prop needed.
|
|
453
|
+
*/
|
|
454
|
+
export const TooltipsAndNewWindow: Story = {
|
|
455
|
+
render: (args) => (
|
|
456
|
+
<Rail w="200px">
|
|
457
|
+
<NavList {...args} aria-label="워크스페이스">
|
|
458
|
+
<NavList.Item
|
|
459
|
+
href="#drive"
|
|
460
|
+
leadingVisual={<UsersIcon boxSize="sm" />}
|
|
461
|
+
tooltip="팀원 모두가 보고 편집할 수 있는 문서"
|
|
462
|
+
>
|
|
463
|
+
공유 드라이브
|
|
464
|
+
</NavList.Item>
|
|
465
|
+
<NavList.Item href="#long" leadingVisual={<ClockIcon boxSize="sm" />}>
|
|
466
|
+
2026년 하반기 마케팅 캠페인 아이디어 회의록
|
|
467
|
+
</NavList.Item>
|
|
468
|
+
<NavList.Item href="#long-en" leadingVisual={<StarIcon boxSize="sm" />}>
|
|
469
|
+
Quarterly business review — enterprise customers
|
|
470
|
+
</NavList.Item>
|
|
471
|
+
<NavList.Item
|
|
472
|
+
href="https://docs.example.com"
|
|
473
|
+
target="_blank"
|
|
474
|
+
rel="noreferrer"
|
|
475
|
+
leadingVisual={<BookIcon boxSize="sm" />}
|
|
476
|
+
>
|
|
477
|
+
사용 가이드
|
|
478
|
+
</NavList.Item>
|
|
479
|
+
<NavList.Item
|
|
480
|
+
href="#sso"
|
|
481
|
+
isDisabled
|
|
482
|
+
tooltip="엔터프라이즈 요금제에서 쓸 수 있습니다"
|
|
483
|
+
leadingVisual={<SettingsIcon boxSize="sm" />}
|
|
484
|
+
>
|
|
485
|
+
SSO
|
|
486
|
+
</NavList.Item>
|
|
487
|
+
</NavList>
|
|
488
|
+
</Rail>
|
|
489
|
+
),
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Session rows: `trailingActionVisibility="hover"` keeps the more-menu out of
|
|
494
|
+
* the way until the row is hovered or focused, or its menu is open. It stays
|
|
495
|
+
* in the tab order — focusing it reveals it — and is always shown on touch
|
|
496
|
+
* screens. Hovering the action keeps the row lit, because the fill is the
|
|
497
|
+
* row's `<li>`.
|
|
498
|
+
*/
|
|
499
|
+
export const HoverRevealedAction: Story = {
|
|
500
|
+
render: (args) => (
|
|
501
|
+
<Rail>
|
|
502
|
+
<NavList {...args} aria-label="최근 대화">
|
|
503
|
+
{[
|
|
504
|
+
'보고서 요약 요청',
|
|
505
|
+
'회의록 정리',
|
|
506
|
+
'2026 하반기 마케팅 캠페인 아이디어와 예산 배분 초안',
|
|
507
|
+
].map((title, i) => (
|
|
508
|
+
<NavList.Item
|
|
509
|
+
key={title}
|
|
510
|
+
href={`#s${i}`}
|
|
511
|
+
isCurrent={i === 0}
|
|
512
|
+
trailingActionVisibility="hover"
|
|
513
|
+
trailingAction={<RowMenu label={title} />}
|
|
514
|
+
>
|
|
515
|
+
{title}
|
|
516
|
+
</NavList.Item>
|
|
517
|
+
))}
|
|
518
|
+
</NavList>
|
|
519
|
+
</Rail>
|
|
520
|
+
),
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* The two visibilities side by side, on the same long label. `hover` (top)
|
|
525
|
+
* gives the label the whole row at rest and lays the action over its end;
|
|
526
|
+
* the label makes room only while the action shows. `always` (bottom) keeps
|
|
527
|
+
* the action's space. On a touch screen both keep it, with the action shown.
|
|
528
|
+
*/
|
|
529
|
+
export const TrailingActionVisibility: Story = {
|
|
530
|
+
render: (args) => (
|
|
531
|
+
<Rail>
|
|
532
|
+
<NavList {...args} aria-label="최근 대화">
|
|
533
|
+
{(['hover', 'always'] as const).map((visibility) => (
|
|
534
|
+
<NavList.Item
|
|
535
|
+
key={visibility}
|
|
536
|
+
href={`#${visibility}`}
|
|
537
|
+
trailingActionVisibility={visibility}
|
|
538
|
+
trailingAction={
|
|
539
|
+
<RowMenu label={`${visibility} 하반기 캠페인 아이디어`} />
|
|
540
|
+
}
|
|
541
|
+
>
|
|
542
|
+
{`${visibility} · 2026 하반기 마케팅 캠페인 아이디어`}
|
|
543
|
+
</NavList.Item>
|
|
544
|
+
))}
|
|
545
|
+
</NavList>
|
|
546
|
+
</Rail>
|
|
547
|
+
),
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* `isCollapsible` turns a group title into a disclosure button. Persisting the
|
|
552
|
+
* state is the caller's: here `isOpen` / `onOpenChange` write local storage.
|
|
553
|
+
*/
|
|
554
|
+
export const CollapsibleGroups: Story = {
|
|
555
|
+
render: (args) => {
|
|
556
|
+
const KEY = 'navlist-story-open';
|
|
557
|
+
const read = (): Record<string, boolean> => {
|
|
558
|
+
try {
|
|
559
|
+
return JSON.parse(localStorage.getItem(KEY) ?? '{}');
|
|
560
|
+
} catch {
|
|
561
|
+
return {};
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
const [open, setOpen] = useState<Record<string, boolean>>(read);
|
|
565
|
+
const groupProps = (key: string) => ({
|
|
566
|
+
isCollapsible: true,
|
|
567
|
+
isOpen: open[key] ?? true,
|
|
568
|
+
onOpenChange: (next: boolean) => {
|
|
569
|
+
const value = { ...open, [key]: next };
|
|
570
|
+
setOpen(value);
|
|
571
|
+
localStorage.setItem(KEY, JSON.stringify(value));
|
|
572
|
+
},
|
|
573
|
+
});
|
|
574
|
+
return (
|
|
575
|
+
<Rail>
|
|
576
|
+
<NavList {...args} aria-label="관리자 메뉴">
|
|
577
|
+
<NavList.Item
|
|
578
|
+
href="#home"
|
|
579
|
+
isCurrent
|
|
580
|
+
leadingVisual={<HomeIcon boxSize="sm" />}
|
|
581
|
+
>
|
|
582
|
+
홈
|
|
583
|
+
</NavList.Item>
|
|
584
|
+
<NavList.Group title="멤버" {...groupProps('members')}>
|
|
585
|
+
<NavList.Item href="#m1">멤버 목록</NavList.Item>
|
|
586
|
+
<NavList.Item href="#m2">그룹 관리</NavList.Item>
|
|
587
|
+
</NavList.Group>
|
|
588
|
+
<NavList.Group title="결제" {...groupProps('billing')}>
|
|
589
|
+
<NavList.Item href="#b1">요금제</NavList.Item>
|
|
590
|
+
<NavList.Item href="#b2">결제 내역</NavList.Item>
|
|
591
|
+
</NavList.Group>
|
|
592
|
+
</NavList>
|
|
593
|
+
</Rail>
|
|
594
|
+
);
|
|
595
|
+
},
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* `NavList.Skeleton` stands in for a rail whose entries are loading, at the
|
|
600
|
+
* same row metrics. Counts that load later need no skeleton: leave
|
|
601
|
+
* `trailingVisual` unset until they arrive.
|
|
602
|
+
*/
|
|
603
|
+
export const Loading: Story = {
|
|
604
|
+
render: () => {
|
|
605
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
606
|
+
return (
|
|
607
|
+
<Flex direction="column" gap={3} align="start">
|
|
608
|
+
<Button
|
|
609
|
+
size="sm"
|
|
610
|
+
variant="secondary"
|
|
611
|
+
onClick={() => setIsLoading((l) => !l)}
|
|
612
|
+
>
|
|
613
|
+
{isLoading ? '불러오기 완료' : '다시 불러오기'}
|
|
614
|
+
</Button>
|
|
615
|
+
<Rail>
|
|
616
|
+
{isLoading ? (
|
|
617
|
+
<NavList.Skeleton count={5} />
|
|
618
|
+
) : (
|
|
619
|
+
<NavList aria-label="템플릿 종류">
|
|
620
|
+
{FILTERS.map((f) => (
|
|
621
|
+
<NavList.Item
|
|
622
|
+
key={f.key}
|
|
623
|
+
onClick={() => {}}
|
|
624
|
+
leadingVisual={f.icon}
|
|
625
|
+
trailingVisual={f.count}
|
|
626
|
+
isCurrent={f.key === 'all'}
|
|
627
|
+
>
|
|
628
|
+
{f.label}
|
|
629
|
+
</NavList.Item>
|
|
630
|
+
))}
|
|
631
|
+
</NavList>
|
|
632
|
+
)}
|
|
633
|
+
</Rail>
|
|
634
|
+
</Flex>
|
|
635
|
+
);
|
|
636
|
+
},
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* A badge with its own spoken text: the number is `aria-hidden` and a visually
|
|
641
|
+
* hidden phrase says what it counts, so the row reads "커넥터 확인 필요 2개".
|
|
642
|
+
*/
|
|
643
|
+
export const BadgesWithSpokenText: Story = {
|
|
644
|
+
render: (args) => (
|
|
645
|
+
<Rail>
|
|
646
|
+
<NavList {...args} aria-label="스튜디오">
|
|
647
|
+
<NavList.Item
|
|
648
|
+
href="#a"
|
|
649
|
+
isCurrent
|
|
650
|
+
trailingVisual={<Badge size="sm">꺼짐</Badge>}
|
|
651
|
+
>
|
|
652
|
+
메모리
|
|
653
|
+
</NavList.Item>
|
|
654
|
+
<NavList.Item
|
|
655
|
+
href="#b"
|
|
656
|
+
trailingVisual={
|
|
657
|
+
<Badge size="sm" variant="warning">
|
|
658
|
+
<AlertTriangleIcon boxSize="2xs" aria-hidden />
|
|
659
|
+
<span aria-hidden>2</span>
|
|
660
|
+
<VisuallyHidden>확인 필요 2개</VisuallyHidden>
|
|
661
|
+
</Badge>
|
|
662
|
+
}
|
|
663
|
+
>
|
|
664
|
+
커넥터
|
|
665
|
+
</NavList.Item>
|
|
666
|
+
</NavList>
|
|
667
|
+
</Rail>
|
|
668
|
+
),
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
const MANY = Array.from({ length: 60 }, (_, i) => i + 1);
|
|
672
|
+
|
|
673
|
+
/** 60 rows in three groups, inside a scrolling sidebar — Tab to the bottom. */
|
|
674
|
+
export const LongList: Story = {
|
|
675
|
+
render: (args) => (
|
|
676
|
+
<Box
|
|
677
|
+
h="360px"
|
|
678
|
+
overflowY="auto"
|
|
679
|
+
w="260px"
|
|
680
|
+
p={2}
|
|
681
|
+
bg="bg.subtle"
|
|
682
|
+
borderRadius="lg"
|
|
683
|
+
>
|
|
684
|
+
<NavList {...args} aria-label="문서함">
|
|
685
|
+
{[0, 1, 2].map((g) => (
|
|
686
|
+
<NavList.Group
|
|
687
|
+
key={g}
|
|
688
|
+
title={['최근', '공유 드라이브', 'Archive'][g]}
|
|
689
|
+
>
|
|
690
|
+
{MANY.slice(g * 20, g * 20 + 20).map((n) => (
|
|
691
|
+
<NavList.Item
|
|
692
|
+
key={n}
|
|
693
|
+
href={`#doc-${n}`}
|
|
694
|
+
isCurrent={n === 42}
|
|
695
|
+
trailingVisual={n % 7 === 0 ? n : undefined}
|
|
696
|
+
>
|
|
697
|
+
{n % 5 === 0
|
|
698
|
+
? `문서 ${n} — 아주 긴 제목이 말줄임표로 잘리는지 확인하는 항목`
|
|
699
|
+
: n % 3 === 0
|
|
700
|
+
? `Document ${n} with a fairly long English title`
|
|
701
|
+
: `문서 ${n}`}
|
|
702
|
+
</NavList.Item>
|
|
703
|
+
))}
|
|
704
|
+
</NavList.Group>
|
|
705
|
+
))}
|
|
706
|
+
</NavList>
|
|
707
|
+
</Box>
|
|
708
|
+
),
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
const STRIP = [
|
|
712
|
+
'전체',
|
|
713
|
+
'이미지',
|
|
714
|
+
'영상',
|
|
715
|
+
'음악',
|
|
716
|
+
'프레젠테이션',
|
|
717
|
+
'문서',
|
|
718
|
+
'스프레드시트',
|
|
719
|
+
'코드',
|
|
720
|
+
'기타',
|
|
721
|
+
];
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* A strip whose current item starts off-screen: the list scrolls ITSELF
|
|
725
|
+
* sideways to show it (never the page), on mount and whenever it changes.
|
|
726
|
+
*/
|
|
727
|
+
export const HorizontalCurrentOffscreen: Story = {
|
|
728
|
+
render: () => {
|
|
729
|
+
const [current, setCurrent] = useState('기타');
|
|
730
|
+
return (
|
|
731
|
+
<Box maxW="320px" p={2} bg="bg.subtle" borderRadius="lg">
|
|
732
|
+
<NavList aria-label="파일 종류" orientation="horizontal">
|
|
733
|
+
{STRIP.map((label) => (
|
|
734
|
+
<NavList.Item
|
|
735
|
+
key={label}
|
|
736
|
+
onClick={() => setCurrent(label)}
|
|
737
|
+
isCurrent={current === label}
|
|
738
|
+
>
|
|
739
|
+
{label}
|
|
740
|
+
</NavList.Item>
|
|
741
|
+
))}
|
|
742
|
+
</NavList>
|
|
743
|
+
</Box>
|
|
744
|
+
);
|
|
745
|
+
},
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Right-to-left: the count and the trailing action follow the inline end, and
|
|
750
|
+
* a group title its start. Layout reads the inherited direction in CSS, so a
|
|
751
|
+
* server render is right to left on its first paint.
|
|
752
|
+
*/
|
|
753
|
+
export const RightToLeft: Story = {
|
|
754
|
+
render: () => (
|
|
755
|
+
<Box dir="rtl">
|
|
756
|
+
<Rail>
|
|
757
|
+
<NavList aria-label="القائمة">
|
|
758
|
+
<NavList.Item
|
|
759
|
+
href="#1"
|
|
760
|
+
isCurrent
|
|
761
|
+
leadingVisual={<HomeIcon boxSize="sm" />}
|
|
762
|
+
trailingVisual={12}
|
|
763
|
+
>
|
|
764
|
+
الرئيسية
|
|
765
|
+
</NavList.Item>
|
|
766
|
+
<NavList.Item
|
|
767
|
+
href="#2"
|
|
768
|
+
leadingVisual={<UsersIcon boxSize="sm" />}
|
|
769
|
+
trailingAction={
|
|
770
|
+
<IconButton aria-label="المزيد" size="xs">
|
|
771
|
+
<DotsHorizontalIcon />
|
|
772
|
+
</IconButton>
|
|
773
|
+
}
|
|
774
|
+
>
|
|
775
|
+
الأعضاء
|
|
776
|
+
</NavList.Item>
|
|
777
|
+
<NavList.Item
|
|
778
|
+
href="#3"
|
|
779
|
+
leadingVisual={<ChatIcon boxSize="sm" />}
|
|
780
|
+
trailingActionVisibility="hover"
|
|
781
|
+
trailingAction={
|
|
782
|
+
<IconButton aria-label="خيارات المحادثات" size="xs">
|
|
783
|
+
<DotsHorizontalIcon />
|
|
784
|
+
</IconButton>
|
|
785
|
+
}
|
|
786
|
+
>
|
|
787
|
+
المحادثات الأخيرة ومقترحات الحملة التسويقية
|
|
788
|
+
</NavList.Item>
|
|
789
|
+
<NavList.Group title="الإدارة" isCollapsible>
|
|
790
|
+
<NavList.Item
|
|
791
|
+
href="#4"
|
|
792
|
+
leadingVisual={<SettingsIcon boxSize="sm" />}
|
|
793
|
+
>
|
|
794
|
+
الإعدادات
|
|
795
|
+
</NavList.Item>
|
|
796
|
+
</NavList.Group>
|
|
797
|
+
</NavList>
|
|
798
|
+
</Rail>
|
|
799
|
+
</Box>
|
|
800
|
+
),
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
// ───────────────────────── Usage-page specimens ─────────────────────────
|
|
804
|
+
// Pulled into `NavList.usage.mdx` with <Canvas of={…} />. Hidden from the
|
|
805
|
+
// sidebar (`!dev`).
|
|
806
|
+
|
|
807
|
+
export const DoNamedRailOfPlaces: Story = {
|
|
808
|
+
tags: ['!dev'],
|
|
809
|
+
name: 'Usage / Do: a named rail, one current row',
|
|
810
|
+
render: () => (
|
|
811
|
+
<Rail>
|
|
812
|
+
<NavList aria-label="관리자 메뉴">
|
|
813
|
+
<NavList.Item href="#" leadingVisual={<UsersIcon />} isCurrent>
|
|
814
|
+
멤버 관리
|
|
815
|
+
</NavList.Item>
|
|
816
|
+
<NavList.Item href="#" leadingVisual={<LayoutDashboardIcon />}>
|
|
817
|
+
사용량
|
|
818
|
+
</NavList.Item>
|
|
819
|
+
<NavList.Item href="#" leadingVisual={<SettingsIcon />}>
|
|
820
|
+
설정
|
|
821
|
+
</NavList.Item>
|
|
822
|
+
</NavList>
|
|
823
|
+
</Rail>
|
|
824
|
+
),
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
export const DontActionsInARail: Story = {
|
|
828
|
+
tags: ['!dev'],
|
|
829
|
+
name: "Usage / Don't: commands mixed into the rail",
|
|
830
|
+
render: () => (
|
|
831
|
+
<Rail>
|
|
832
|
+
<NavList aria-label="관리자 메뉴">
|
|
833
|
+
<NavList.Item href="#" isCurrent>
|
|
834
|
+
멤버 관리
|
|
835
|
+
</NavList.Item>
|
|
836
|
+
<NavList.Item onClick={() => {}}>멤버 초대하기</NavList.Item>
|
|
837
|
+
<NavList.Item onClick={() => {}}>CSV 내보내기</NavList.Item>
|
|
838
|
+
</NavList>
|
|
839
|
+
</Rail>
|
|
840
|
+
),
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
/** Tokens a row reads, from `NAV_LIST_RAMP` in `NavList.styles.ts`. */
|
|
844
|
+
export const UsageTokens: Story = {
|
|
845
|
+
tags: ['!dev'],
|
|
846
|
+
name: 'Usage / Tokens',
|
|
847
|
+
render: () => (
|
|
848
|
+
<TokenTable
|
|
849
|
+
rows={[
|
|
850
|
+
{
|
|
851
|
+
token: NAV_LIST_RAMP.label,
|
|
852
|
+
use: <T en="Row · label" ko="항목 · 글자" />,
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
token: NAV_LIST_RAMP.currentLabel,
|
|
856
|
+
use: (
|
|
857
|
+
<T en="Current row · label (bold)" ko="현재 항목 · 글자(굵게)" />
|
|
858
|
+
),
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
token: NAV_LIST_RAMP.currentFill,
|
|
862
|
+
use: (
|
|
863
|
+
<T
|
|
864
|
+
en="Current row and hover · fill"
|
|
865
|
+
ko="현재 항목·마우스 올림 · 배경"
|
|
866
|
+
/>
|
|
867
|
+
),
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
token: NAV_LIST_RAMP.pressedFill,
|
|
871
|
+
use: (
|
|
872
|
+
<T
|
|
873
|
+
en="Pressed row · fill (border.strong under high contrast)"
|
|
874
|
+
ko="누르는 동안 · 배경(고대비에서는 border.strong)"
|
|
875
|
+
/>
|
|
876
|
+
),
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
token: NAV_LIST_RAMP.trailing,
|
|
880
|
+
use: (
|
|
881
|
+
<T en="Trailing count · group title" ko="뒤쪽 개수 · 묶음 제목" />
|
|
882
|
+
),
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
token: 'disabled.fg',
|
|
886
|
+
use: <T en="Disabled row" ko="사용할 수 없는 항목" />,
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
token: 'focus.ring',
|
|
890
|
+
use: <T en="Focus ring (inset)" ko="초점 테두리(안쪽)" />,
|
|
891
|
+
},
|
|
892
|
+
]}
|
|
893
|
+
/>
|
|
894
|
+
),
|
|
895
|
+
};
|