@nexus-cross/design-system 1.0.0 → 1.0.2
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/cursor-rules/nexus-project-setup.mdc +150 -150
- package/cursor-rules/nexus-ui-api.mdc +659 -316
- package/cursor-rules/nexus-ui-components.mdc +162 -96
- package/dist/chunks/chunk-55IEEVNR.js +7 -0
- package/dist/chunks/{chunk-D6FII7HW.js → chunk-BBLBTOP4.js} +8 -5
- package/dist/chunks/{chunk-5JHN4FCY.mjs → chunk-K2TBLM3F.mjs} +1 -4
- package/dist/chunks/{chunk-MTX7GD3H.js → chunk-PEIEVKD5.js} +1 -4
- package/dist/chunks/{chunk-54RBL7J4.mjs → chunk-UKRU46PH.mjs} +8 -5
- package/dist/chunks/chunk-XMG7ZEYY.mjs +5 -0
- package/dist/data-list.js +2 -2
- package/dist/data-list.mjs +1 -1
- package/dist/error-boundary.d.mts +1 -1
- package/dist/error-boundary.d.ts +1 -1
- package/dist/index.js +5 -5
- package/dist/index.mjs +2 -2
- package/dist/schemas/_all.json +870 -373
- package/dist/schemas/accordion.json +12 -12
- package/dist/schemas/avatar.json +9 -9
- package/dist/schemas/button.json +27 -9
- package/dist/schemas/carousel.json +6 -6
- package/dist/schemas/carouselButton.json +3 -3
- package/dist/schemas/carouselDots.json +2 -2
- package/dist/schemas/carouselSlide.json +3 -3
- package/dist/schemas/checkBox.json +28 -10
- package/dist/schemas/chip.json +13 -7
- package/dist/schemas/clientOnly.json +3 -3
- package/dist/schemas/countdown.json +8 -8
- package/dist/schemas/counter.json +13 -10
- package/dist/schemas/dataList.json +10 -10
- package/dist/schemas/divider.json +8 -5
- package/dist/schemas/drawer.json +22 -3
- package/dist/schemas/drawerClose.json +24 -0
- package/dist/schemas/drawerContent.json +7 -7
- package/dist/schemas/drawerDescription.json +20 -0
- package/dist/schemas/drawerTitle.json +20 -0
- package/dist/schemas/drawerTrigger.json +24 -0
- package/dist/schemas/ellipsis.json +9 -9
- package/dist/schemas/errorBoundary.json +4 -4
- package/dist/schemas/infiniteScroll.json +12 -12
- package/dist/schemas/marquee.json +10 -7
- package/dist/schemas/modalCall.json +81 -3
- package/dist/schemas/modalTemplate.json +28 -25
- package/dist/schemas/numberInput.json +32 -14
- package/dist/schemas/pagination.json +8 -8
- package/dist/schemas/popover.json +12 -12
- package/dist/schemas/radioGroup.json +17 -10
- package/dist/schemas/radioItem.json +12 -5
- package/dist/schemas/select.json +11 -11
- package/dist/schemas/selectItem.json +5 -5
- package/dist/schemas/skeleton.json +10 -7
- package/dist/schemas/spinner.json +11 -4
- package/dist/schemas/switch.json +18 -7
- package/dist/schemas/tab.json +15 -15
- package/dist/schemas/table.json +14 -14
- package/dist/schemas/tableRow.json +5 -5
- package/dist/schemas/tdColumn.json +17 -17
- package/dist/schemas/textArea.json +42 -9
- package/dist/schemas/textInput.json +55 -15
- package/dist/schemas/themeProvider.json +10 -10
- package/dist/schemas/toastOptions.json +81 -0
- package/dist/schemas/toaster.json +48 -3
- package/dist/schemas/tooltip.json +10 -10
- package/dist/schemas/virtualGrid.json +19 -16
- package/dist/schemas/virtualList.json +12 -9
- package/dist/schemas.d.mts +420 -56
- package/dist/schemas.d.ts +420 -56
- package/dist/schemas.js +502 -367
- package/dist/schemas.mjs +498 -368
- package/dist/styles/layer.js +2 -2
- package/dist/styles/layer.mjs +1 -1
- package/dist/styles.css +56 -45
- package/dist/styles.js +2 -2
- package/dist/styles.layered.css +56 -45
- package/dist/styles.mjs +1 -1
- package/dist/text-input.d.mts +1 -1
- package/dist/text-input.d.ts +1 -1
- package/dist/text-input.js +3 -3
- package/dist/text-input.mjs +1 -1
- package/package.json +8 -6
- package/scripts/setup-cursor-rules.cjs +6 -6
- package/dist/chunks/chunk-7AISZYWL.js +0 -7
- package/dist/chunks/chunk-V5OTJP6H.mjs +0 -5
|
@@ -1,41 +1,57 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "@nexus-cross/design-system
|
|
2
|
+
description: "@nexus-cross/design-system component API reference — props and usage examples for all shared components"
|
|
3
3
|
globs: "**/*.tsx,**/*.jsx,**/*.ts"
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# @nexus-cross/design-system — Component API Reference
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
All components are imported from `@nexus-cross/design-system`.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
## Button
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Interactive button. semantic(color) x variant(style) 2-axis system. Rendering element changeable via asChild.
|
|
16
16
|
|
|
17
17
|
| Prop | Type | Default | Description |
|
|
18
18
|
|---|---|---|---|
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
19
|
+
| `semantic` | `'primary'` \| `'secondary'` \| `'normal'` \| `'danger'` | `"primary"` | Color theme (primary=main, secondary=sub, normal=neutral, danger=danger) |
|
|
20
|
+
| `variant` | `'contained'` \| `'outlined'` \| `'subtle'` \| `'ghost'` | `"contained"` | Visual style (contained=filled, outlined=border, subtle=light bg, ghost=transparent) |
|
|
21
|
+
| `size` | `'xl'` \| `'lg'` \| `'md'` \| `'sm'` | `"md"` | Size |
|
|
22
|
+
| `radius` | `'default'` \| `'circle'` | `"default"` | Corner radius (default=size-based radius, circle=pill shape) |
|
|
23
|
+
| `asChild` | `boolean` | - | If true, renders as child element (Slot pattern) |
|
|
24
|
+
| `detectDoubleClick` | `boolean` | - | Prevent double click within 500ms |
|
|
25
|
+
| `disabled` | `boolean` | - | Disabled (auto aria-disabled) |
|
|
26
|
+
| `children` | `ReactNode` | - | Button content (ReactNode) |
|
|
27
|
+
| `onClick` | `ReactNode` | - | Click event handler |
|
|
28
|
+
| `type` | `'button'` \| `'submit'` \| `'reset'` | - | HTML button type (default: button) |
|
|
29
|
+
| `style` | `ReactNode` | - | Inline style (CSSProperties) |
|
|
30
|
+
| `className` | `string` | - | Style override |
|
|
31
|
+
|
|
32
|
+
2-axis variant: semantic (color) × variant (style). sizes: xl, lg, md, sm. radius: default, circle.
|
|
25
33
|
|
|
26
34
|
```tsx
|
|
27
|
-
|
|
35
|
+
// Default (primary + contained)
|
|
36
|
+
<Button>Confirm</Button>
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
// semantic × variant combinations
|
|
39
|
+
<Button semantic="primary" variant="contained" size="lg">Primary Contained</Button>
|
|
40
|
+
<Button semantic="secondary" variant="outlined" size="md">Secondary Outlined</Button>
|
|
41
|
+
<Button semantic="normal" variant="subtle" size="sm">Normal Subtle</Button>
|
|
42
|
+
<Button semantic="danger" variant="ghost">Danger Ghost</Button>
|
|
30
43
|
|
|
31
|
-
//
|
|
44
|
+
// Pill shape (radius="circle")
|
|
45
|
+
<Button semantic="primary" variant="contained" radius="circle">Pill</Button>
|
|
46
|
+
|
|
47
|
+
// Loading state
|
|
32
48
|
<Button disabled>
|
|
33
|
-
<Spinner size={16} />
|
|
49
|
+
<Spinner size={16} /> Processing...
|
|
34
50
|
</Button>
|
|
35
51
|
|
|
36
|
-
// <a>
|
|
37
|
-
<Button asChild variant="ghost">
|
|
38
|
-
<a href="/settings"
|
|
52
|
+
// Render as <a> tag
|
|
53
|
+
<Button asChild semantic="normal" variant="ghost">
|
|
54
|
+
<a href="/settings">Settings</a>
|
|
39
55
|
</Button>
|
|
40
56
|
```
|
|
41
57
|
|
|
@@ -43,42 +59,91 @@ alwaysApply: false
|
|
|
43
59
|
|
|
44
60
|
## TextInput
|
|
45
61
|
|
|
46
|
-
|
|
62
|
+
Text input field. Supports label, description, prefix/suffix icons, clearable, character counter.
|
|
47
63
|
|
|
48
64
|
| Prop | Type | Default | Description |
|
|
49
65
|
|---|---|---|---|
|
|
50
|
-
| `size` | `'
|
|
51
|
-
| `error` | `boolean` |
|
|
52
|
-
| `prefixIcon` | `ReactNode` | - |
|
|
53
|
-
| `suffixIcon` | `ReactNode` | - |
|
|
54
|
-
| `
|
|
66
|
+
| `size` | `'md'` \| `'lg'` \| `'xl'` | `"md"` | Size |
|
|
67
|
+
| `error` | `boolean` | - | Error state (auto aria-invalid) |
|
|
68
|
+
| `prefixIcon` | `ReactNode` | - | Prefix icon (ReactNode) |
|
|
69
|
+
| `suffixIcon` | `ReactNode` | - | Suffix icon (ReactNode) |
|
|
70
|
+
| `label` | `ReactNode` | - | Label above input field (ReactNode) |
|
|
71
|
+
| `description` | `ReactNode` | - | Description below input field (ReactNode, red on error) |
|
|
72
|
+
| `showCount` | `boolean` | - | Show character count (requires maxLength) |
|
|
73
|
+
| `maxLength` | `number` | - | Maximum character count |
|
|
74
|
+
| `clearable` | `boolean` | - | Clear input via X button |
|
|
75
|
+
| `placeholder` | `string` | - | Placeholder |
|
|
76
|
+
| `disabled` | `boolean` | - | Disabled |
|
|
77
|
+
| `readOnly` | `boolean` | - | Read-only |
|
|
78
|
+
| `value` | `string` | - | Input value (controlled mode) |
|
|
79
|
+
| `defaultValue` | `string` | - | Initial value (uncontrolled mode) |
|
|
80
|
+
| `type` | `string` | - | Input type (text, password, email, url, etc.) |
|
|
81
|
+
| `name` | `string` | - | Form field name |
|
|
82
|
+
| `id` | `string` | - | Element ID (for label htmlFor binding) |
|
|
83
|
+
| `autoFocus` | `boolean` | - | Auto focus |
|
|
84
|
+
| `autoComplete` | `string` | - | Autocomplete hint (on, off, email, etc.) |
|
|
85
|
+
| `onValueChange` | `ReactNode` | - | Value change callback (value: string) => void |
|
|
86
|
+
| `onChange` | `ReactNode` | - | Native change event handler |
|
|
87
|
+
| `onBlur` | `ReactNode` | - | Blur callback |
|
|
88
|
+
| `onFocus` | `ReactNode` | - | Focus callback |
|
|
89
|
+
| `className` | `string` | - | Style override |
|
|
55
90
|
|
|
56
91
|
```tsx
|
|
57
|
-
<TextInput placeholder="
|
|
92
|
+
<TextInput placeholder="Email" size="md" />
|
|
58
93
|
|
|
59
94
|
<TextInput
|
|
60
|
-
|
|
95
|
+
label="Email"
|
|
96
|
+
description="Enter your email address"
|
|
97
|
+
placeholder="example@email.com"
|
|
98
|
+
clearable
|
|
99
|
+
/>
|
|
100
|
+
|
|
101
|
+
<TextInput
|
|
102
|
+
label="Search"
|
|
61
103
|
prefixIcon={<SearchIcon />}
|
|
104
|
+
clearable
|
|
62
105
|
onValueChange={(v) => setQuery(v)}
|
|
63
106
|
/>
|
|
107
|
+
|
|
108
|
+
<TextInput
|
|
109
|
+
label="Nickname"
|
|
110
|
+
description="2-20 characters"
|
|
111
|
+
showCount
|
|
112
|
+
maxLength={20}
|
|
113
|
+
clearable
|
|
114
|
+
error
|
|
115
|
+
/>
|
|
64
116
|
```
|
|
65
117
|
|
|
66
118
|
---
|
|
67
119
|
|
|
68
120
|
## TextArea
|
|
69
121
|
|
|
70
|
-
|
|
122
|
+
Multi-line text input. Built-in character counter.
|
|
71
123
|
|
|
72
124
|
| Prop | Type | Default | Description |
|
|
73
125
|
|---|---|---|---|
|
|
74
|
-
| `error` | `boolean` |
|
|
75
|
-
| `showCount` | `boolean` |
|
|
76
|
-
| `maxLength` | `number` | - |
|
|
77
|
-
| `
|
|
126
|
+
| `error` | `boolean` | - | Error state (auto aria-invalid) |
|
|
127
|
+
| `showCount` | `boolean` | - | Show character count (requires maxLength) |
|
|
128
|
+
| `maxLength` | `number` | - | Maximum character count |
|
|
129
|
+
| `placeholder` | `string` | - | Placeholder |
|
|
130
|
+
| `rows` | `number` | - | Visible row count |
|
|
131
|
+
| `disabled` | `boolean` | - | Disabled |
|
|
132
|
+
| `readOnly` | `boolean` | - | Read-only |
|
|
133
|
+
| `value` | `string` | - | Input value (controlled mode) |
|
|
134
|
+
| `defaultValue` | `string` | - | Initial value (uncontrolled mode) |
|
|
135
|
+
| `name` | `string` | - | Form field name |
|
|
136
|
+
| `id` | `string` | - | Element ID |
|
|
137
|
+
| `autoFocus` | `boolean` | - | Auto focus |
|
|
138
|
+
| `onValueChange` | `ReactNode` | - | Value change callback (value: string) => void |
|
|
139
|
+
| `onChange` | `ReactNode` | - | Native change event handler |
|
|
140
|
+
| `onBlur` | `ReactNode` | - | Blur callback |
|
|
141
|
+
| `onFocus` | `ReactNode` | - | Focus callback |
|
|
142
|
+
| `className` | `string` | - | Style override |
|
|
78
143
|
|
|
79
144
|
```tsx
|
|
80
145
|
<TextArea
|
|
81
|
-
placeholder="
|
|
146
|
+
placeholder="Enter your content"
|
|
82
147
|
maxLength={500}
|
|
83
148
|
showCount
|
|
84
149
|
rows={4}
|
|
@@ -89,23 +154,37 @@ alwaysApply: false
|
|
|
89
154
|
|
|
90
155
|
## Select
|
|
91
156
|
|
|
92
|
-
|
|
157
|
+
Dropdown select. Based on Radix Select. Used with SelectItem.
|
|
158
|
+
|
|
159
|
+
| Prop | Type | Default | Description |
|
|
160
|
+
|---|---|---|---|
|
|
161
|
+
| `value` | `string` | - | Selected value |
|
|
162
|
+
| `placeholder` | `string` | - | Placeholder |
|
|
163
|
+
| `variant` | `'default'` \| `'outline'` | `"default"` | Trigger style |
|
|
164
|
+
| `size` | `'sm'` \| `'md'` \| `'lg'` \| `'full'` | `"full"` | Width |
|
|
165
|
+
| `disabled` | `boolean` | - | Disabled |
|
|
166
|
+
| `onValueChange` | `ReactNode` | - | Value change callback (value: string) => void |
|
|
167
|
+
| `displayComponent` | `ReactNode` | - | Custom display in trigger (ReactNode) |
|
|
168
|
+
| `children` | `ReactNode` | - | SelectItem list (ReactNode, required) |
|
|
169
|
+
| `className` | `string` | - | Wrapper style |
|
|
170
|
+
| `triggerClassName` | `string` | - | Trigger style override |
|
|
171
|
+
|
|
172
|
+
### SelectItem
|
|
173
|
+
|
|
174
|
+
Individual option within Select.
|
|
93
175
|
|
|
94
176
|
| Prop | Type | Default | Description |
|
|
95
177
|
|---|---|---|---|
|
|
96
|
-
| `value` | `string` | - |
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `size` | `'sm' \| 'md' \| 'lg' \| 'full'` | `'full'` | 너비 |
|
|
101
|
-
| `disabled` | `boolean` | - | 비활성 |
|
|
102
|
-
| `displayComponent` | `ReactNode` | - | 트리거에 커스텀 표시 |
|
|
178
|
+
| `value` | `string` | - | Item value |
|
|
179
|
+
| `children` | `ReactNode` | - | Item content (ReactNode, required) |
|
|
180
|
+
| `disabled` | `boolean` | - | Disabled |
|
|
181
|
+
| `className` | `string` | - | Style override |
|
|
103
182
|
|
|
104
183
|
```tsx
|
|
105
|
-
<Select value={lang} onValueChange={setLang} placeholder="
|
|
106
|
-
<SelectItem value="ko"
|
|
184
|
+
<Select value={lang} onValueChange={setLang} placeholder="Select language">
|
|
185
|
+
<SelectItem value="ko">Korean</SelectItem>
|
|
107
186
|
<SelectItem value="en">English</SelectItem>
|
|
108
|
-
<SelectItem value="ja"
|
|
187
|
+
<SelectItem value="ja">Japanese</SelectItem>
|
|
109
188
|
</Select>
|
|
110
189
|
```
|
|
111
190
|
|
|
@@ -113,22 +192,30 @@ alwaysApply: false
|
|
|
113
192
|
|
|
114
193
|
## CheckBox
|
|
115
194
|
|
|
116
|
-
|
|
195
|
+
Checkbox. Native input-based, supports square/round shapes.
|
|
117
196
|
|
|
118
197
|
| Prop | Type | Default | Description |
|
|
119
198
|
|---|---|---|---|
|
|
120
|
-
| `size` | `'sm' \| 'md'` | `
|
|
121
|
-
| `shape` | `'square' \| 'round'` | `
|
|
122
|
-
| `checked` | `boolean` | - |
|
|
123
|
-
| `indeterminate` | `boolean` |
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
199
|
+
| `size` | `'sm'` \| `'md'` | `"md"` | Size |
|
|
200
|
+
| `shape` | `'square'` \| `'round'` | `"square"` | Shape |
|
|
201
|
+
| `checked` | `boolean` | - | Checked state |
|
|
202
|
+
| `indeterminate` | `boolean` | - | Indeterminate state (aria-checked="mixed") |
|
|
203
|
+
| `disabled` | `boolean` | - | Disabled |
|
|
204
|
+
| `readOnly` | `boolean` | - | Read-only |
|
|
205
|
+
| `label` | `ReactNode` | - | Label text (ReactNode) |
|
|
206
|
+
| `children` | `ReactNode` | - | Label alternative content (ReactNode) |
|
|
207
|
+
| `name` | `string` | - | Form field name |
|
|
208
|
+
| `id` | `string` | - | Element ID |
|
|
209
|
+
| `value` | `string` | - | Value for form submission |
|
|
210
|
+
| `onCheckedChange` | `ReactNode` | - | Checked state change callback (checked: boolean) => void |
|
|
211
|
+
| `onChange` | `ReactNode` | - | Native change event handler |
|
|
212
|
+
| `className` | `string` | - | Style override |
|
|
126
213
|
|
|
127
214
|
```tsx
|
|
128
215
|
<CheckBox
|
|
129
216
|
checked={agreed}
|
|
130
217
|
onCheckedChange={setAgreed}
|
|
131
|
-
label="
|
|
218
|
+
label="I agree to the terms of service"
|
|
132
219
|
/>
|
|
133
220
|
|
|
134
221
|
<CheckBox shape="round" size="sm" checked indeterminate />
|
|
@@ -136,23 +223,36 @@ alwaysApply: false
|
|
|
136
223
|
|
|
137
224
|
---
|
|
138
225
|
|
|
139
|
-
## RadioGroup
|
|
226
|
+
## RadioGroup
|
|
140
227
|
|
|
141
|
-
|
|
228
|
+
Radio group. Used with RadioItem.
|
|
142
229
|
|
|
143
|
-
|
|
|
230
|
+
| Prop | Type | Default | Description |
|
|
144
231
|
|---|---|---|---|
|
|
145
|
-
| `name` | `string`
|
|
146
|
-
| `value` | `string` | - |
|
|
147
|
-
| `
|
|
148
|
-
| `
|
|
149
|
-
| `
|
|
150
|
-
| `
|
|
232
|
+
| `name` | `string` | - | Form name (required) |
|
|
233
|
+
| `value` | `string` | - | Selected value (controlled) |
|
|
234
|
+
| `defaultValue` | `string` | - | Initial value (uncontrolled) |
|
|
235
|
+
| `size` | `'sm'` \| `'md'` | `"md"` | Size |
|
|
236
|
+
| `orientation` | `'horizontal'` \| `'vertical'` | `"vertical"` | Layout direction |
|
|
237
|
+
| `disabled` | `boolean` | - | Disabled |
|
|
238
|
+
| `children` | `ReactNode` | - | RadioItem list (ReactNode, required) |
|
|
239
|
+
| `aria-label` | `string` | - | Accessibility label |
|
|
240
|
+
| `aria-labelledby` | `string` | - | Accessibility label reference ID |
|
|
241
|
+
| `onValueChange` | `ReactNode` | - | Value change callback (value: string) => void |
|
|
242
|
+
| `className` | `string` | - | Style override |
|
|
243
|
+
|
|
244
|
+
### RadioItem
|
|
245
|
+
|
|
246
|
+
Individual option within RadioGroup.
|
|
151
247
|
|
|
152
|
-
|
|
|
153
|
-
|
|
154
|
-
| `value` | `string`
|
|
155
|
-
| `
|
|
248
|
+
| Prop | Type | Default | Description |
|
|
249
|
+
|---|---|---|---|
|
|
250
|
+
| `value` | `string` | - | Item value (required) |
|
|
251
|
+
| `size` | `'sm'` \| `'md'` | - | Size (overrides group) |
|
|
252
|
+
| `label` | `ReactNode` | - | Label text (ReactNode) |
|
|
253
|
+
| `children` | `ReactNode` | - | Label alternative content (ReactNode) |
|
|
254
|
+
| `disabled` | `boolean` | - | Disabled |
|
|
255
|
+
| `className` | `string` | - | Style override |
|
|
156
256
|
|
|
157
257
|
```tsx
|
|
158
258
|
<RadioGroup
|
|
@@ -160,11 +260,11 @@ alwaysApply: false
|
|
|
160
260
|
value={plan}
|
|
161
261
|
onValueChange={setPlan}
|
|
162
262
|
orientation="horizontal"
|
|
163
|
-
aria-label="
|
|
263
|
+
aria-label="Select plan"
|
|
164
264
|
>
|
|
165
|
-
<RadioItem value="free" label="
|
|
166
|
-
<RadioItem value="pro" label="
|
|
167
|
-
<RadioItem value="enterprise" label="
|
|
265
|
+
<RadioItem value="free" label="Free" />
|
|
266
|
+
<RadioItem value="pro" label="Pro" />
|
|
267
|
+
<RadioItem value="enterprise" label="Enterprise" />
|
|
168
268
|
</RadioGroup>
|
|
169
269
|
```
|
|
170
270
|
|
|
@@ -172,14 +272,19 @@ alwaysApply: false
|
|
|
172
272
|
|
|
173
273
|
## Switch
|
|
174
274
|
|
|
175
|
-
|
|
275
|
+
Toggle switch. Native checkbox-based, role="switch".
|
|
176
276
|
|
|
177
277
|
| Prop | Type | Default | Description |
|
|
178
278
|
|---|---|---|---|
|
|
179
|
-
| `size` | `'sm' \| 'md'` | `
|
|
180
|
-
| `checked` | `boolean` | - |
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
279
|
+
| `size` | `'sm'` \| `'md'` | `"md"` | Size |
|
|
280
|
+
| `checked` | `boolean` | - | On/off state |
|
|
281
|
+
| `disabled` | `boolean` | - | Disabled |
|
|
282
|
+
| `readOnly` | `boolean` | - | Read-only |
|
|
283
|
+
| `name` | `string` | - | Form field name |
|
|
284
|
+
| `id` | `string` | - | Element ID |
|
|
285
|
+
| `onCheckedChange` | `ReactNode` | - | Toggle state change callback (checked: boolean) => void |
|
|
286
|
+
| `onChange` | `ReactNode` | - | Native change event handler (ChangeEvent) |
|
|
287
|
+
| `className` | `string` | - | Style override |
|
|
183
288
|
|
|
184
289
|
```tsx
|
|
185
290
|
<Switch checked={darkMode} onCheckedChange={setDarkMode} />
|
|
@@ -189,24 +294,27 @@ alwaysApply: false
|
|
|
189
294
|
|
|
190
295
|
## Chip
|
|
191
296
|
|
|
192
|
-
|
|
297
|
+
Chip/tag/badge. Close button displayed via onClose prop.
|
|
193
298
|
|
|
194
299
|
| Prop | Type | Default | Description |
|
|
195
300
|
|---|---|---|---|
|
|
196
|
-
| `variant` | `'default' \| 'filled' \| 'outline' \| 'accent'` | `
|
|
197
|
-
| `size` | `'sm' \| 'md' \| 'lg'` | `
|
|
198
|
-
| `asChild` | `boolean` |
|
|
199
|
-
| `disabled` | `boolean` | - |
|
|
200
|
-
| `
|
|
301
|
+
| `variant` | `'default'` \| `'filled'` \| `'outline'` \| `'accent'` | `"default"` | Style |
|
|
302
|
+
| `size` | `'sm'` \| `'md'` \| `'lg'` | `"md"` | Size |
|
|
303
|
+
| `asChild` | `boolean` | - | If true, renders as child element (Slot pattern) |
|
|
304
|
+
| `disabled` | `boolean` | - | Disabled (auto aria-disabled) |
|
|
305
|
+
| `children` | `ReactNode` | - | Chip content (ReactNode) |
|
|
306
|
+
| `onClose` | `ReactNode` | - | Close button click callback (e: MouseEvent) => void. Shows X button when provided |
|
|
307
|
+
| `onClick` | `ReactNode` | - | Click event handler |
|
|
308
|
+
| `className` | `string` | - | Style override |
|
|
201
309
|
|
|
202
310
|
```tsx
|
|
203
311
|
<Chip variant="accent" size="sm">New</Chip>
|
|
204
312
|
|
|
205
313
|
<Chip onClose={() => removeTag(id)}>React</Chip>
|
|
206
314
|
|
|
207
|
-
// <li
|
|
315
|
+
// Render as <li>
|
|
208
316
|
<Chip asChild variant="filled">
|
|
209
|
-
<li
|
|
317
|
+
<li>List chip</li>
|
|
210
318
|
</Chip>
|
|
211
319
|
```
|
|
212
320
|
|
|
@@ -214,31 +322,73 @@ alwaysApply: false
|
|
|
214
322
|
|
|
215
323
|
## Spinner
|
|
216
324
|
|
|
217
|
-
|
|
325
|
+
Loading indicator. SVG-based. Built-in role="status".
|
|
218
326
|
|
|
219
327
|
| Prop | Type | Default | Description |
|
|
220
328
|
|---|---|---|---|
|
|
221
|
-
| `size` | `number` | `20` | px
|
|
222
|
-
| `
|
|
223
|
-
| `aria-label` | `string` | `
|
|
329
|
+
| `size` | `number` | `20` | Size in px |
|
|
330
|
+
| `color` | `string` | - | Color (CSS color value, default currentColor) |
|
|
331
|
+
| `aria-label` | `string` | `"Loading"` | Accessibility label |
|
|
332
|
+
| `style` | `ReactNode` | - | Inline style (CSSProperties) |
|
|
333
|
+
| `className` | `string` | - | Color override etc. |
|
|
224
334
|
|
|
225
335
|
```tsx
|
|
226
336
|
<Spinner size={24} />
|
|
227
337
|
|
|
228
|
-
<Spinner size={14} className="text-white" aria-label="
|
|
338
|
+
<Spinner size={14} className="text-white" aria-label="Loading" />
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Skeleton
|
|
344
|
+
|
|
345
|
+
Skeleton loading placeholder. Size/shape via className. With children, wraps transparently to maintain actual size.
|
|
346
|
+
|
|
347
|
+
| Prop | Type | Default | Description |
|
|
348
|
+
|---|---|---|---|
|
|
349
|
+
| `as` | `'div'` \| `'span'` | `"div"` | Rendered tag |
|
|
350
|
+
| `circle` | `boolean` | `false` | Circle skeleton (rounded-full) |
|
|
351
|
+
| `width` | `string` \| `number` | - | Width (e.g. '100px', '50%', 200) |
|
|
352
|
+
| `height` | `string` \| `number` | - | Height (e.g. '16px', 40) |
|
|
353
|
+
| `children` | `ReactNode` | - | Inner content (shown when loaded, maintains actual size) |
|
|
354
|
+
| `style` | `ReactNode` | - | Inline style (CSSProperties) |
|
|
355
|
+
| `className` | `string` | - | Style override |
|
|
356
|
+
|
|
357
|
+
```tsx
|
|
358
|
+
// Basic usage (size via className)
|
|
359
|
+
<Skeleton className="h-4 w-48" />
|
|
360
|
+
<Skeleton className="h-3 w-32" />
|
|
361
|
+
|
|
362
|
+
// Circular avatar skeleton
|
|
363
|
+
<Skeleton circle width={40} height={40} />
|
|
364
|
+
|
|
365
|
+
// width/height props
|
|
366
|
+
<Skeleton width="100%" height={120} className="rounded-lg" />
|
|
367
|
+
|
|
368
|
+
// Match children size
|
|
369
|
+
<Skeleton>
|
|
370
|
+
<p>Skeleton will match this text size</p>
|
|
371
|
+
</Skeleton>
|
|
372
|
+
|
|
373
|
+
// With DataList
|
|
374
|
+
<DataList list={data} skeletonElement={<MySkeleton />} skeletonCount={5}>
|
|
375
|
+
{({ item }) => <Card key={item.id} {...item} />}
|
|
376
|
+
</DataList>
|
|
229
377
|
```
|
|
230
378
|
|
|
231
379
|
---
|
|
232
380
|
|
|
233
381
|
## Divider
|
|
234
382
|
|
|
235
|
-
|
|
383
|
+
Divider. Supports horizontal/vertical, solid/dashed/dotted.
|
|
236
384
|
|
|
237
385
|
| Prop | Type | Default | Description |
|
|
238
386
|
|---|---|---|---|
|
|
239
|
-
| `orientation` | `'horizontal' \| 'vertical'` | `
|
|
240
|
-
| `variant` | `'solid' \| 'dashed' \| 'dotted'` | `
|
|
241
|
-
| `color` | `string` | - |
|
|
387
|
+
| `orientation` | `'horizontal'` \| `'vertical'` | `"horizontal"` | Direction |
|
|
388
|
+
| `variant` | `'solid'` \| `'dashed'` \| `'dotted'` | `"solid"` | Line style |
|
|
389
|
+
| `color` | `string` | - | Custom color (CSS value) |
|
|
390
|
+
| `style` | `ReactNode` | - | Inline style (CSSProperties) |
|
|
391
|
+
| `className` | `string` | - | Style override |
|
|
242
392
|
|
|
243
393
|
```tsx
|
|
244
394
|
<Divider />
|
|
@@ -249,19 +399,22 @@ alwaysApply: false
|
|
|
249
399
|
|
|
250
400
|
## Tooltip
|
|
251
401
|
|
|
252
|
-
|
|
402
|
+
Tooltip. Based on Radix Tooltip. Built-in Provider.
|
|
253
403
|
|
|
254
404
|
| Prop | Type | Default | Description |
|
|
255
405
|
|---|---|---|---|
|
|
256
|
-
| `
|
|
257
|
-
| `
|
|
258
|
-
| `
|
|
259
|
-
| `
|
|
260
|
-
| `
|
|
261
|
-
| `
|
|
406
|
+
| `children` | `ReactNode` | - | Trigger element (ReactNode, required) |
|
|
407
|
+
| `content` | `ReactNode` | - | Tooltip content (ReactNode, required) |
|
|
408
|
+
| `variant` | `'dark'` \| `'light'` | `"dark"` | Style |
|
|
409
|
+
| `side` | `'top'` \| `'right'` \| `'bottom'` \| `'left'` | `"top"` | Position |
|
|
410
|
+
| `align` | `'start'` \| `'center'` \| `'end'` | `"center"` | Alignment |
|
|
411
|
+
| `delayDuration` | `number` | `200` | Show delay (ms) |
|
|
412
|
+
| `disabled` | `boolean` | `false` | Disabled |
|
|
413
|
+
| `className` | `string` | - | Content style |
|
|
414
|
+
| `triggerClassName` | `string` | - | Trigger style |
|
|
262
415
|
|
|
263
416
|
```tsx
|
|
264
|
-
<Tooltip content="
|
|
417
|
+
<Tooltip content="Copied!" side="bottom">
|
|
265
418
|
<button>📋</button>
|
|
266
419
|
</Tooltip>
|
|
267
420
|
```
|
|
@@ -270,21 +423,25 @@ alwaysApply: false
|
|
|
270
423
|
|
|
271
424
|
## Popover
|
|
272
425
|
|
|
273
|
-
|
|
426
|
+
Popover. Based on Radix Popover.
|
|
274
427
|
|
|
275
428
|
| Prop | Type | Default | Description |
|
|
276
429
|
|---|---|---|---|
|
|
277
|
-
| `trigger` | `ReactNode` | - |
|
|
278
|
-
| `
|
|
279
|
-
| `
|
|
280
|
-
| `
|
|
281
|
-
| `
|
|
282
|
-
| `open` | `boolean` | - |
|
|
283
|
-
| `onOpenChange` | `(open: boolean) => void
|
|
430
|
+
| `trigger` | `ReactNode` | - | Trigger element (ReactNode, required) |
|
|
431
|
+
| `side` | `'top'` \| `'right'` \| `'bottom'` \| `'left'` | `"bottom"` | Position |
|
|
432
|
+
| `align` | `'start'` \| `'center'` \| `'end'` | `"center"` | Alignment |
|
|
433
|
+
| `sideOffset` | `number` | `4` | Position offset (px) |
|
|
434
|
+
| `alignOffset` | `number` | - | Alignment offset (px) |
|
|
435
|
+
| `open` | `boolean` | - | Controlled mode |
|
|
436
|
+
| `onOpenChange` | `ReactNode` | - | Open/close state change callback (open: boolean) => void |
|
|
437
|
+
| `onClickTrigger` | `ReactNode` | - | Callback on trigger element click. Passed to PopoverPrimitive.Trigger onClick. |
|
|
438
|
+
| `children` | `ReactNode` | - | Popover body (ReactNode) |
|
|
439
|
+
| `className` | `string` | - | Content style |
|
|
440
|
+
| `arrowClassName` | `string` | - | Arrow style |
|
|
284
441
|
|
|
285
442
|
```tsx
|
|
286
|
-
<Popover trigger={<Button variant="outline"
|
|
287
|
-
<div className="p-4"
|
|
443
|
+
<Popover trigger={<Button variant="outline">Menu</Button>}>
|
|
444
|
+
<div className="p-4">Popover content</div>
|
|
288
445
|
</Popover>
|
|
289
446
|
```
|
|
290
447
|
|
|
@@ -292,27 +449,29 @@ alwaysApply: false
|
|
|
292
449
|
|
|
293
450
|
## Accordion
|
|
294
451
|
|
|
295
|
-
|
|
452
|
+
Accordion. Supports both items array and composable patterns.
|
|
296
453
|
|
|
297
454
|
| Prop | Type | Default | Description |
|
|
298
455
|
|---|---|---|---|
|
|
299
|
-
| `items` | `
|
|
300
|
-
| `type` | `'single' \| 'multiple'` | `
|
|
301
|
-
| `collapsible` | `boolean` | `true` |
|
|
302
|
-
| `value`
|
|
303
|
-
| `
|
|
456
|
+
| `items` | `object`[] | - | Accordion item array (required) |
|
|
457
|
+
| `type` | `'single'` \| `'multiple'` | `"single"` | Single/multiple open mode |
|
|
458
|
+
| `collapsible` | `boolean` | `true` | Allow collapsing all |
|
|
459
|
+
| `value` | `string` \| `string`[] | - | Controlled mode |
|
|
460
|
+
| `defaultValue` | `string` \| `string`[] | - | Uncontrolled initial value |
|
|
461
|
+
| `onValueChange` | `ReactNode` | - | Open item change callback (value: string | string[]) => void |
|
|
462
|
+
| `className` | `string` | - | Root style |
|
|
304
463
|
|
|
305
464
|
```tsx
|
|
306
465
|
<Accordion items={[
|
|
307
|
-
{ id: '1', trigger: 'FAQ 1', content: '
|
|
308
|
-
{ id: '2', trigger: 'FAQ 2', content: '
|
|
466
|
+
{ id: '1', trigger: 'FAQ 1', content: 'Answer 1' },
|
|
467
|
+
{ id: '2', trigger: 'FAQ 2', content: 'Answer 2' },
|
|
309
468
|
]} />
|
|
310
469
|
|
|
311
|
-
//
|
|
470
|
+
// Composable pattern
|
|
312
471
|
<AccordionRoot type="single" collapsible>
|
|
313
472
|
<AccordionItem value="item-1">
|
|
314
|
-
<AccordionTrigger
|
|
315
|
-
<AccordionContent
|
|
473
|
+
<AccordionTrigger>Title</AccordionTrigger>
|
|
474
|
+
<AccordionContent>Content</AccordionContent>
|
|
316
475
|
</AccordionItem>
|
|
317
476
|
</AccordionRoot>
|
|
318
477
|
```
|
|
@@ -321,24 +480,41 @@ alwaysApply: false
|
|
|
321
480
|
|
|
322
481
|
## Drawer
|
|
323
482
|
|
|
324
|
-
|
|
483
|
+
Drawer/bottom sheet. Based on Vaul. Compound component pattern.
|
|
484
|
+
|
|
485
|
+
| Prop | Type | Default | Description |
|
|
486
|
+
|---|---|---|---|
|
|
487
|
+
| `direction` | `'bottom'` \| `'top'` \| `'left'` \| `'right'` | `"bottom"` | Direction |
|
|
488
|
+
| `open` | `boolean` | - | Open state (controlled mode) |
|
|
489
|
+
| `onOpenChange` | `ReactNode` | - | Open state change callback (open: boolean) => void |
|
|
490
|
+
| `dismissible` | `boolean` | - | Allow close via swipe/outside click (default true) |
|
|
491
|
+
| `modal` | `boolean` | - | Modal mode (default true). If false, background is interactive |
|
|
492
|
+
| `shouldScaleBackground` | `boolean` | - | Background scale effect (default false) |
|
|
493
|
+
| `children` | `ReactNode` | - | Drawer sub-components (ReactNode, required) |
|
|
494
|
+
|
|
495
|
+
### DrawerContent
|
|
496
|
+
|
|
497
|
+
Drawer.Content area.
|
|
325
498
|
|
|
326
499
|
| Prop | Type | Default | Description |
|
|
327
500
|
|---|---|---|---|
|
|
328
|
-
| `direction` | `'bottom' \| 'top' \| 'left' \| 'right'` | `
|
|
329
|
-
| `
|
|
330
|
-
| `
|
|
501
|
+
| `direction` | `'bottom'` \| `'top'` \| `'left'` \| `'right'` | `"bottom"` | Direction (Context takes priority) |
|
|
502
|
+
| `blur` | `'none'` \| `'sm'` \| `'md'` | `"none"` | Overlay blur |
|
|
503
|
+
| `showHandle` | `boolean` | `true` | Show handle bar |
|
|
504
|
+
| `children` | `ReactNode` | - | Content area (ReactNode) |
|
|
505
|
+
| `overlayClassName` | `string` | - | Overlay style |
|
|
506
|
+
| `className` | `string` | - | Panel style |
|
|
331
507
|
|
|
332
508
|
```tsx
|
|
333
509
|
<Drawer direction="bottom">
|
|
334
510
|
<Drawer.Trigger asChild>
|
|
335
|
-
<Button
|
|
511
|
+
<Button>Open</Button>
|
|
336
512
|
</Drawer.Trigger>
|
|
337
513
|
<Drawer.Content>
|
|
338
|
-
<Drawer.Title
|
|
339
|
-
<Drawer.Description
|
|
514
|
+
<Drawer.Title>Title</Drawer.Title>
|
|
515
|
+
<Drawer.Description>Description</Drawer.Description>
|
|
340
516
|
<Drawer.Close asChild>
|
|
341
|
-
<Button variant="ghost"
|
|
517
|
+
<Button variant="ghost">Close</Button>
|
|
342
518
|
</Drawer.Close>
|
|
343
519
|
</Drawer.Content>
|
|
344
520
|
</Drawer>
|
|
@@ -346,353 +522,520 @@ alwaysApply: false
|
|
|
346
522
|
|
|
347
523
|
---
|
|
348
524
|
|
|
349
|
-
## Modal
|
|
525
|
+
## Modal
|
|
350
526
|
|
|
351
|
-
|
|
527
|
+
Modal template. All modal components must be wrapped with ModalTemplate.
|
|
352
528
|
|
|
353
|
-
|
|
529
|
+
| Prop | Type | Default | Description |
|
|
530
|
+
|---|---|---|---|
|
|
531
|
+
| `title` | `ReactNode` | - | Header title (ReactNode) |
|
|
532
|
+
| `desc` | `ReactNode` | - | Header description (ReactNode) |
|
|
533
|
+
| `layout` | `'default'` \| `'bottom-sheet'` \| `'slide-left'` \| `'slide-right'` \| `'full-page'` \| `'full-page-reverse'` \| `'draggable'` | `"default"` | Layout |
|
|
534
|
+
| `showDim` | `boolean` | `true` | Show dim overlay |
|
|
535
|
+
| `dimClose` | `boolean` | `true` | Close on dim click |
|
|
536
|
+
| `hideHeader` | `boolean` | `false` | Hide header |
|
|
537
|
+
| `hideFooter` | `boolean` | `true` | Hide footer |
|
|
538
|
+
| `footer` | `ReactNode` | - | Custom footer (ReactElement) |
|
|
539
|
+
| `animation` | `object` | - | Modal animation |
|
|
540
|
+
| `enableDrag` | `boolean` | `true` | Enable drag (bottom-sheet/draggable layouts) |
|
|
541
|
+
| `dragPersistKey` | `string` | - | Drag position persistence key |
|
|
542
|
+
| `close` | `ReactNode` | - | Modal close function (isAnimation?: boolean) => void (auto-injected) |
|
|
543
|
+
| `children` | `ReactNode` | - | Modal body (ReactNode, required) |
|
|
544
|
+
| `className` | `string` | - | Root wrapper style |
|
|
545
|
+
| `innerClassName` | `string` | - | Modal body style |
|
|
546
|
+
| `bodyClassName` | `string` | - | Body area style |
|
|
547
|
+
| `footerClassName` | `string` | - | Footer area style |
|
|
548
|
+
| `dimClassName` | `string` | - | Dim overlay style |
|
|
549
|
+
| `headerClassName` | `string` | - | Header area style |
|
|
550
|
+
|
|
551
|
+
### modal()
|
|
552
|
+
|
|
553
|
+
modal() function call options. component automatically receives close/resolve as props.
|
|
354
554
|
|
|
355
|
-
|
|
555
|
+
| Prop | Type | Default | Description |
|
|
556
|
+
|---|---|---|---|
|
|
557
|
+
| `component` | `ReactNode` | - | Modal component (required). Automatically receives close/resolve as props |
|
|
558
|
+
| `props` | `Record<string, any>` | - | Props to pass to component |
|
|
559
|
+
| `id` | `string` | - | Modal ID (used for duplicate check) |
|
|
560
|
+
| `layout` | `'default'` \| `'bottom-sheet'` \| `'slide-left'` \| `'slide-right'` \| `'full-page'` \| `'full-page-reverse'` \| `'draggable'` | - | Layout |
|
|
561
|
+
| `animation` | `object` | - | Modal animation |
|
|
562
|
+
| `scrollEnable` | `boolean` | - | Allow background scroll |
|
|
563
|
+
| `isToggle` | `boolean` | - | Toggle mode (close on re-call of same modal) |
|
|
564
|
+
| `isAlone` | `boolean` | - | Alone mode (close all existing modals before opening) |
|
|
565
|
+
| `duplicateCheck` | `boolean` | - | Prevent duplicate opening of same component |
|
|
566
|
+
| `disableEscapeKeyPress` | `boolean` | - | Disable close via ESC key |
|
|
567
|
+
| `componentName` | `string` | - | Modal identifier name (used for duplicate check, modal search) |
|
|
568
|
+
| `onOpen` | `ReactNode` | - | Callback when modal opens |
|
|
569
|
+
| `onClose` | `ReactNode` | - | Callback when modal closes |
|
|
570
|
+
|
|
571
|
+
**IMPORTANT: Modal components MUST be wrapped with `ModalTemplate`.**
|
|
356
572
|
|
|
357
573
|
```tsx
|
|
358
574
|
import { ModalTemplate } from '@nexus-cross/design-system/modal';
|
|
359
575
|
|
|
360
|
-
// 모달 컴포넌트는 반드시 close와 resolve를 props로 받는다
|
|
361
576
|
function MyModal({ close, resolve }: { close: () => void; resolve: (value: any) => void }) {
|
|
362
577
|
return (
|
|
363
578
|
<ModalTemplate
|
|
364
|
-
title="
|
|
365
|
-
desc="
|
|
579
|
+
title="Modal Title"
|
|
580
|
+
desc="Modal description (optional)"
|
|
366
581
|
close={close}
|
|
367
|
-
layout="default"
|
|
368
|
-
hideFooter
|
|
582
|
+
layout="default"
|
|
583
|
+
hideFooter
|
|
369
584
|
>
|
|
370
585
|
<div className="space-y-4">
|
|
371
|
-
<p className="text-text-secondary"
|
|
372
|
-
<Button onClick={() => resolve({ confirmed: true })}
|
|
586
|
+
<p className="text-text-secondary">Modal content</p>
|
|
587
|
+
<Button onClick={() => resolve({ confirmed: true })}>Confirm</Button>
|
|
373
588
|
</div>
|
|
374
589
|
</ModalTemplate>
|
|
375
590
|
);
|
|
376
591
|
}
|
|
377
592
|
```
|
|
378
593
|
|
|
379
|
-
###
|
|
380
|
-
|
|
381
|
-
| Prop | Type | Default | Description |
|
|
382
|
-
|---|---|---|---|
|
|
383
|
-
| `title` | `ReactNode` | - | 헤더 제목 |
|
|
384
|
-
| `desc` | `ReactNode` | - | 헤더 설명 |
|
|
385
|
-
| `close` | `() => void` (필수) | - | 닫기 함수 (props로 자동 주입됨) |
|
|
386
|
-
| `layout` | `'default' \| 'bottom-sheet' \| 'slide-left' \| 'slide-right' \| 'full-page' \| 'full-page-reverse' \| 'draggable'` | `'default'` | 레이아웃 |
|
|
387
|
-
| `showDim` | `boolean` | `true` | 딤 배경 표시 |
|
|
388
|
-
| `dimClose` | `boolean` | `true` | 딤 클릭 시 닫기 |
|
|
389
|
-
| `hideHeader` | `boolean` | `false` | 헤더 숨김 |
|
|
390
|
-
| `hideFooter` | `boolean` | `true` | 푸터 숨김 |
|
|
391
|
-
| `footer` | `ReactElement` | - | 커스텀 푸터 |
|
|
392
|
-
| `enableDrag` | `boolean` | `false` | 드래그 활성화 (draggable 레이아웃) |
|
|
393
|
-
| `dragPersistKey` | `string` | - | 드래그 위치 저장 키 |
|
|
394
|
-
| `innerClassName` | `string` | - | 모달 본체 스타일 오버라이드 |
|
|
395
|
-
| `bodyClassName` | `string` | - | 바디 영역 스타일 오버라이드 |
|
|
396
|
-
|
|
397
|
-
### 모달 호출
|
|
594
|
+
### Calling a Modal
|
|
398
595
|
|
|
399
596
|
```tsx
|
|
400
597
|
import { modal, useModal, ModalContainer } from '@nexus-cross/design-system/modal';
|
|
401
598
|
|
|
402
|
-
//
|
|
599
|
+
// ModalContainer MUST be placed at the app root
|
|
403
600
|
<ModalContainer />
|
|
404
601
|
|
|
405
|
-
//
|
|
602
|
+
// Method 1: modal() function
|
|
406
603
|
const result = await modal({
|
|
407
604
|
component: MyModal,
|
|
408
|
-
props: { /*
|
|
605
|
+
props: { /* additional props */ },
|
|
409
606
|
});
|
|
410
607
|
|
|
411
|
-
//
|
|
608
|
+
// Method 2: useModal() hook
|
|
412
609
|
const { modal: openModal } = useModal();
|
|
413
610
|
openModal({
|
|
414
611
|
component: MyModal,
|
|
415
|
-
options: { isAlone: true },
|
|
612
|
+
options: { isAlone: true },
|
|
416
613
|
});
|
|
417
614
|
```
|
|
418
615
|
|
|
419
|
-
###
|
|
616
|
+
### Prohibited
|
|
420
617
|
|
|
421
|
-
-
|
|
422
|
-
- `close` prop
|
|
423
|
-
-
|
|
618
|
+
- Do NOT create modal components without ModalTemplate (using plain `<div>`)
|
|
619
|
+
- Do NOT define the `close` prop manually (the system injects it automatically)
|
|
620
|
+
- Do NOT implement a separate dim/overlay inside the modal
|
|
424
621
|
|
|
425
622
|
---
|
|
426
623
|
|
|
427
|
-
##
|
|
428
|
-
|
|
429
|
-
토스트 알림. Sonner 기반.
|
|
624
|
+
## Tab
|
|
430
625
|
|
|
431
|
-
|
|
432
|
-
import { toast, Toaster } from '@nexus-cross/design-system';
|
|
626
|
+
Tab navigation. line/pill variants.
|
|
433
627
|
|
|
434
|
-
|
|
435
|
-
|
|
628
|
+
| Prop | Type | Default | Description |
|
|
629
|
+
|---|---|---|---|
|
|
630
|
+
| `items` | `object`[] | - | Tab item array (required) |
|
|
631
|
+
| `activeKey` | `string` | - | Controlled mode active key |
|
|
632
|
+
| `defaultActiveKey` | `string` | - | Uncontrolled initial key |
|
|
633
|
+
| `variant` | `'line'` \| `'pill'` | `"line"` | Style |
|
|
634
|
+
| `size` | `'sm'` \| `'md'` | `"md"` | Size |
|
|
635
|
+
| `destroyInactive` | `boolean` | `false` | Unmount inactive panels |
|
|
636
|
+
| `onTabChange` | `ReactNode` | - | Tab change callback (key: string) => void |
|
|
637
|
+
| `className` | `string` | - | Root style |
|
|
638
|
+
| `tabListClassName` | `string` | - | Tab list style |
|
|
639
|
+
| `tabPanelClassName` | `string` | - | Tab panel style |
|
|
436
640
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
641
|
+
```tsx
|
|
642
|
+
<Tab
|
|
643
|
+
items={[
|
|
644
|
+
{ key: 'a', label: 'Tab A', children: <p>A</p> },
|
|
645
|
+
{ key: 'b', label: 'Tab B', children: <p>B</p> },
|
|
646
|
+
]}
|
|
647
|
+
defaultActiveKey="a"
|
|
648
|
+
variant="pill"
|
|
649
|
+
/>
|
|
442
650
|
```
|
|
443
651
|
|
|
444
652
|
---
|
|
445
653
|
|
|
446
|
-
##
|
|
654
|
+
## Carousel
|
|
447
655
|
|
|
448
|
-
|
|
656
|
+
Carousel. Based on Embla Carousel. Sub-components: CarouselSlide, CarouselPrev, CarouselNext, CarouselDots.
|
|
449
657
|
|
|
450
|
-
| Prop | Type | Description |
|
|
451
|
-
|
|
452
|
-
| `
|
|
453
|
-
| `
|
|
454
|
-
| `
|
|
455
|
-
| `
|
|
456
|
-
| `
|
|
658
|
+
| Prop | Type | Default | Description |
|
|
659
|
+
|---|---|---|---|
|
|
660
|
+
| `opts` | `Record<string, any>` | - | Embla options (loop, align, etc.) |
|
|
661
|
+
| `plugins` | `ReactNode`[] | - | Embla plugins |
|
|
662
|
+
| `onApiChange` | `ReactNode` | - | Embla API change callback (api: CarouselApi) => void |
|
|
663
|
+
| `children` | `ReactNode` | - | Carousel slides and sub-components (ReactNode) |
|
|
664
|
+
| `className` | `string` | - | Style override |
|
|
665
|
+
|
|
666
|
+
Sub-components: `CarouselSlide`, `CarouselPrev`, `CarouselNext`, `CarouselDots`
|
|
457
667
|
|
|
458
668
|
```tsx
|
|
459
|
-
<
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
</InfiniteScroll>
|
|
669
|
+
<Carousel opts={{ loop: true }}>
|
|
670
|
+
<CarouselSlide>Slide 1</CarouselSlide>
|
|
671
|
+
<CarouselSlide>Slide 2</CarouselSlide>
|
|
672
|
+
<CarouselPrev />
|
|
673
|
+
<CarouselNext />
|
|
674
|
+
<CarouselDots />
|
|
675
|
+
</Carousel>
|
|
467
676
|
```
|
|
468
677
|
|
|
469
678
|
---
|
|
470
679
|
|
|
471
|
-
##
|
|
680
|
+
## Pagination
|
|
472
681
|
|
|
473
|
-
|
|
682
|
+
Pagination. Previous/next + page number buttons.
|
|
474
683
|
|
|
475
684
|
| Prop | Type | Default | Description |
|
|
476
685
|
|---|---|---|---|
|
|
477
|
-
| `
|
|
478
|
-
| `
|
|
479
|
-
| `
|
|
480
|
-
| `
|
|
686
|
+
| `currentPage` | `number` | - | Current page (1-based, required) |
|
|
687
|
+
| `totalPages` | `number` | - | Total page count (required) |
|
|
688
|
+
| `siblingCount` | `number` | `1` | Number of pages shown on each side of current |
|
|
689
|
+
| `showEdges` | `boolean` | - | Always show first/last page |
|
|
690
|
+
| `size` | `'sm'` \| `'md'` | `"md"` | Size |
|
|
691
|
+
| `onPageChange` | `ReactNode` | - | Page change callback (page: number) => void, required |
|
|
692
|
+
| `className` | `string` | - | <nav> style |
|
|
481
693
|
|
|
482
694
|
```tsx
|
|
483
|
-
<
|
|
695
|
+
<Pagination currentPage={2} totalPages={10} onPageChange={setPage} />
|
|
484
696
|
```
|
|
485
697
|
|
|
486
698
|
---
|
|
487
699
|
|
|
488
|
-
##
|
|
489
|
-
|
|
490
|
-
### useModal
|
|
700
|
+
## Avatar
|
|
491
701
|
|
|
492
|
-
|
|
493
|
-
const { open, close } = useModal();
|
|
494
|
-
open(MyComponent, { title: '제목' });
|
|
495
|
-
```
|
|
702
|
+
Avatar. Supports image, fallback text, and children.
|
|
496
703
|
|
|
497
|
-
|
|
704
|
+
| Prop | Type | Default | Description |
|
|
705
|
+
|---|---|---|---|
|
|
706
|
+
| `src` | `string` | - | Image URL |
|
|
707
|
+
| `alt` | `string` | - | Alt text |
|
|
708
|
+
| `fallback` | `ReactNode` | - | Displayed on image load failure (ReactNode) |
|
|
709
|
+
| `size` | `'xs'` \| `'sm'` \| `'md'` \| `'lg'` \| `'xl'` | `"md"` | Size |
|
|
710
|
+
| `shape` | `'circle'` \| `'square'` | `"circle"` | Shape |
|
|
711
|
+
| `children` | `ReactNode` | - | Custom image element (e.g. Next.js Image) |
|
|
712
|
+
| `onImageError` | `ReactNode` | - | Image load error callback () => void |
|
|
713
|
+
| `className` | `string` | - | Style override |
|
|
498
714
|
|
|
499
715
|
```tsx
|
|
500
|
-
|
|
501
|
-
<
|
|
716
|
+
<Avatar src="/user.png" alt="User" size="lg" />
|
|
717
|
+
<Avatar fallback="JD" shape="square" size="sm" />
|
|
502
718
|
```
|
|
503
719
|
|
|
504
|
-
|
|
720
|
+
---
|
|
505
721
|
|
|
506
|
-
|
|
507
|
-
const { isMobile, isTablet, isDesktop } = useCheckDevice();
|
|
508
|
-
```
|
|
722
|
+
## Counter
|
|
509
723
|
|
|
510
|
-
|
|
724
|
+
Number count animation.
|
|
725
|
+
|
|
726
|
+
| Prop | Type | Default | Description |
|
|
727
|
+
|---|---|---|---|
|
|
728
|
+
| `endValue` | `number` | - | Target value (required) |
|
|
729
|
+
| `startValue` | `number` | `0` | Start value |
|
|
730
|
+
| `duration` | `number` | `1500` | Animation duration (ms) |
|
|
731
|
+
| `delay` | `number` | `0` | Start delay (ms) |
|
|
732
|
+
| `separator` | `boolean` | `true` | Thousands separator |
|
|
733
|
+
| `digits` | `number` | `0` | Decimal places |
|
|
734
|
+
| `triggerOnView` | `boolean` | `false` | Start on viewport entry |
|
|
735
|
+
| `onEnd` | `ReactNode` | - | Count complete callback () => void |
|
|
736
|
+
| `style` | `ReactNode` | - | Inline style (CSSProperties) |
|
|
737
|
+
| `className` | `string` | - | Style override |
|
|
511
738
|
|
|
512
739
|
```tsx
|
|
513
|
-
|
|
514
|
-
<div ref={ref}>드롭다운 내용</div>
|
|
740
|
+
<Counter endValue={1234} duration={2000} separator />
|
|
515
741
|
```
|
|
516
742
|
|
|
517
743
|
---
|
|
518
744
|
|
|
519
|
-
##
|
|
745
|
+
## Countdown
|
|
520
746
|
|
|
521
|
-
|
|
747
|
+
Countdown timer.
|
|
522
748
|
|
|
523
749
|
| Prop | Type | Default | Description |
|
|
524
750
|
|---|---|---|---|
|
|
525
|
-
| `
|
|
526
|
-
| `
|
|
527
|
-
| `
|
|
528
|
-
| `
|
|
529
|
-
| `
|
|
751
|
+
| `endTimestamp` | `number` | - | End time (Unix ms, required) |
|
|
752
|
+
| `separator` | `ReactNode` | `":"` | Separator (ReactNode) |
|
|
753
|
+
| `showDays` | `boolean` | `true` | Show days unit |
|
|
754
|
+
| `labels` | `object` | - | Unit labels |
|
|
755
|
+
| `render` | `ReactNode` | - | Custom render function |
|
|
756
|
+
| `onEnd` | `ReactNode` | - | Countdown end callback () => void |
|
|
757
|
+
| `className` | `string` | - | Style override |
|
|
530
758
|
|
|
531
759
|
```tsx
|
|
532
|
-
<
|
|
760
|
+
<Countdown endTimestamp={Date.now() + 60_000} showDays={false} onEnd={handleEnd} />
|
|
533
761
|
```
|
|
534
762
|
|
|
535
763
|
---
|
|
536
764
|
|
|
537
|
-
##
|
|
765
|
+
## Marquee
|
|
538
766
|
|
|
539
|
-
|
|
767
|
+
Marquee (scrolling text/elements).
|
|
540
768
|
|
|
541
769
|
| Prop | Type | Default | Description |
|
|
542
770
|
|---|---|---|---|
|
|
543
|
-
| `
|
|
544
|
-
| `
|
|
545
|
-
| `
|
|
546
|
-
| `
|
|
547
|
-
| `
|
|
548
|
-
| `
|
|
771
|
+
| `direction` | `'left'` \| `'right'` \| `'up'` \| `'down'` | `"left"` | Direction |
|
|
772
|
+
| `speed` | `number` | `40` | Animation speed (seconds) |
|
|
773
|
+
| `pauseOnHover` | `boolean` | `false` | Pause on hover |
|
|
774
|
+
| `gap` | `number` | `16` | Item gap (px) |
|
|
775
|
+
| `children` | `ReactNode` | - | Content to repeat (ReactNode, required) |
|
|
776
|
+
| `style` | `ReactNode` | - | Inline style (CSSProperties) |
|
|
777
|
+
| `className` | `string` | - | Style override |
|
|
549
778
|
|
|
550
779
|
```tsx
|
|
551
|
-
<
|
|
552
|
-
<
|
|
780
|
+
<Marquee direction="left" speed={30} pauseOnHover>
|
|
781
|
+
<span>Scrolling text</span>
|
|
782
|
+
</Marquee>
|
|
553
783
|
```
|
|
554
784
|
|
|
555
785
|
---
|
|
556
786
|
|
|
557
|
-
##
|
|
787
|
+
## VirtualList
|
|
788
|
+
|
|
789
|
+
Virtual scroll list. Based on @tanstack/react-virtual.
|
|
790
|
+
|
|
791
|
+
| Prop | Type | Default | Description |
|
|
792
|
+
|---|---|---|---|
|
|
793
|
+
| `items` | `ReactNode`[] | - | Data array (required) |
|
|
794
|
+
| `estimateSize` | `number` \| `ReactNode` | - | Estimated item height (number or (index) => number, required) |
|
|
795
|
+
| `renderItem` | `ReactNode` | - | Item renderer (item, index, virtualItem) => ReactNode (required) |
|
|
796
|
+
| `overscan` | `number` | `5` | Overscan count |
|
|
797
|
+
| `gap` | `number` | `0` | Item gap (px) |
|
|
798
|
+
| `className` | `string` | - | Scroll container style |
|
|
799
|
+
| `style` | `ReactNode` | - | Inline style (CSSProperties) |
|
|
800
|
+
| `endReachedThreshold` | `number` | `200` | End detection threshold (px) |
|
|
801
|
+
| `onEndReached` | `ReactNode` | - | End reached callback () => void |
|
|
802
|
+
|
|
803
|
+
### VirtualGrid
|
|
558
804
|
|
|
559
|
-
|
|
805
|
+
Virtual scroll grid. Based on @tanstack/react-virtual.
|
|
560
806
|
|
|
561
807
|
| Prop | Type | Default | Description |
|
|
562
808
|
|---|---|---|---|
|
|
563
|
-
| `
|
|
564
|
-
| `
|
|
565
|
-
| `
|
|
566
|
-
| `
|
|
567
|
-
| `
|
|
568
|
-
| `
|
|
569
|
-
| `
|
|
570
|
-
| `
|
|
809
|
+
| `items` | `ReactNode`[] | - | Data array (required) |
|
|
810
|
+
| `columns` | `number` | - | Column count (required) |
|
|
811
|
+
| `estimateSize` | `number` \| `ReactNode` | - | Estimated item height (required) |
|
|
812
|
+
| `renderItem` | `ReactNode` | - | Item renderer (item, index) => ReactNode (required) |
|
|
813
|
+
| `overscan` | `number` | `3` | Overscan count |
|
|
814
|
+
| `gap` | `number` | `0` | Item gap (px) |
|
|
815
|
+
| `className` | `string` | - | Scroll container style |
|
|
816
|
+
| `style` | `ReactNode` | - | Inline style (CSSProperties) |
|
|
817
|
+
| `endReachedThreshold` | `number` | `200` | End detection threshold (px) |
|
|
818
|
+
| `onEndReached` | `ReactNode` | - | End reached callback () => void |
|
|
571
819
|
|
|
572
820
|
```tsx
|
|
573
|
-
<
|
|
821
|
+
<VirtualList
|
|
822
|
+
items={data}
|
|
823
|
+
estimateSize={48}
|
|
824
|
+
renderItem={(item) => <div>{item.name}</div>}
|
|
825
|
+
onEndReached={loadMore}
|
|
826
|
+
/>
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
### VirtualGrid
|
|
830
|
+
|
|
831
|
+
Same as VirtualList + `columns: number` (required).
|
|
832
|
+
|
|
833
|
+
```tsx
|
|
834
|
+
<VirtualGrid items={data} estimateSize={120} columns={3} renderItem={(item) => <Card {...item} />} />
|
|
574
835
|
```
|
|
575
836
|
|
|
576
837
|
---
|
|
577
838
|
|
|
578
|
-
##
|
|
839
|
+
## DataList
|
|
579
840
|
|
|
580
|
-
|
|
841
|
+
Data list. Automatically handles loading/skeleton/empty/data states based on list. Built-in ErrorBoundary.
|
|
581
842
|
|
|
582
843
|
| Prop | Type | Default | Description |
|
|
583
844
|
|---|---|---|---|
|
|
584
|
-
| `
|
|
585
|
-
| `
|
|
586
|
-
| `
|
|
587
|
-
| `
|
|
588
|
-
| `
|
|
589
|
-
| `
|
|
845
|
+
| `list` | `ReactNode`[] | - | Data array to render. null = loading state (required) |
|
|
846
|
+
| `noDataMessage` | `ReactNode` | - | Message for empty array (string | ReactElement) |
|
|
847
|
+
| `errorFallback` | `ReactNode` | - | Fallback on error (ReactNode) |
|
|
848
|
+
| `loadingElement` | `ReactNode` | - | Custom loading element (default: Spinner) |
|
|
849
|
+
| `skeletonElement` | `ReactNode` | - | Skeleton element during loading (ReactElement) |
|
|
850
|
+
| `skeletonCount` | `number` | `3` | Skeleton repeat count |
|
|
851
|
+
| `loading` | `boolean` | `false` | Force loading state |
|
|
852
|
+
| `children` | `ReactNode` | - | Item render function: ({ item, index }) => ReactNode (required) |
|
|
853
|
+
| `className` | `string` | - | Root element style |
|
|
854
|
+
|
|
855
|
+
When list is null → **loading**, [] → **empty state**, array → **render**. ErrorBoundary built-in.
|
|
856
|
+
|
|
857
|
+
- **Default loading**: Without skeletonElement, a Spinner is shown automatically
|
|
858
|
+
- **Skeleton loading**: Pass a custom skeleton component to skeletonElement, rendered skeletonCount times
|
|
590
859
|
|
|
591
860
|
```tsx
|
|
592
|
-
|
|
861
|
+
// Basic usage — Spinner shown automatically when list is null
|
|
862
|
+
<DataList list={users} noDataMessage="No users found">
|
|
863
|
+
{({ item, index }) => <UserCard key={item.id} user={item} />}
|
|
864
|
+
</DataList>
|
|
865
|
+
|
|
866
|
+
// Skeleton loading — implement and pass a custom skeleton component
|
|
867
|
+
function UserSkeleton() {
|
|
868
|
+
return (
|
|
869
|
+
<div className="flex items-center gap-3 px-4 py-3">
|
|
870
|
+
<Skeleton circle width={32} height={32} />
|
|
871
|
+
<div className="flex-1 space-y-1.5">
|
|
872
|
+
<Skeleton className="h-3 w-24" />
|
|
873
|
+
<Skeleton className="h-2.5 w-16" />
|
|
874
|
+
</div>
|
|
875
|
+
</div>
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
<DataList
|
|
880
|
+
list={products}
|
|
881
|
+
skeletonElement={<UserSkeleton />}
|
|
882
|
+
skeletonCount={5}
|
|
883
|
+
>
|
|
884
|
+
{({ item }) => <ProductCard key={item.id} {...item} />}
|
|
885
|
+
</DataList>
|
|
593
886
|
```
|
|
594
887
|
|
|
595
888
|
---
|
|
596
889
|
|
|
597
|
-
##
|
|
890
|
+
## InfiniteScroll
|
|
598
891
|
|
|
599
|
-
|
|
892
|
+
Infinite scroll. Based on IntersectionObserver.
|
|
600
893
|
|
|
601
894
|
| Prop | Type | Default | Description |
|
|
602
895
|
|---|---|---|---|
|
|
603
|
-
| `
|
|
604
|
-
| `
|
|
605
|
-
| `
|
|
606
|
-
| `
|
|
896
|
+
| `list` | `ReactNode`[] | - | Current data array (required) |
|
|
897
|
+
| `totalCount` | `number` | - | Total count (mutually exclusive with hasMore) |
|
|
898
|
+
| `hasMore` | `boolean` | - | Has more items (mutually exclusive with totalCount) |
|
|
899
|
+
| `tag` | `string` | `"div"` | Children wrapper tag |
|
|
900
|
+
| `rootMargin` | `number` | `100` | Detection margin (px) |
|
|
901
|
+
| `loading` | `boolean` | - | Loading state |
|
|
902
|
+
| `loadingElement` | `ReactNode` | - | Custom loading element |
|
|
903
|
+
| `handleLoadMore` | `ReactNode` | - | Load more callback () => void, required |
|
|
904
|
+
| `scrollTarget` | `ReactNode` | - | Scroll target element (HTMLElement | Document | MutableRefObject) |
|
|
905
|
+
| `children` | `ReactNode` | - | List item rendering (ReactNode, required) |
|
|
906
|
+
| `className` | `string` | - | Style override |
|
|
607
907
|
|
|
608
908
|
```tsx
|
|
609
|
-
<
|
|
610
|
-
|
|
611
|
-
|
|
909
|
+
<InfiniteScroll
|
|
910
|
+
list={items}
|
|
911
|
+
totalCount={100}
|
|
912
|
+
loading={isLoading}
|
|
913
|
+
handleLoadMore={fetchMore}
|
|
914
|
+
>
|
|
915
|
+
{items.map(item => <Card key={item.id} {...item} />)}
|
|
916
|
+
</InfiniteScroll>
|
|
612
917
|
```
|
|
613
918
|
|
|
614
919
|
---
|
|
615
920
|
|
|
616
|
-
##
|
|
921
|
+
## Ellipsis
|
|
617
922
|
|
|
618
|
-
|
|
923
|
+
Text ellipsis. Built-in show more/less toggle.
|
|
619
924
|
|
|
620
925
|
| Prop | Type | Default | Description |
|
|
621
926
|
|---|---|---|---|
|
|
622
|
-
| `
|
|
623
|
-
| `
|
|
624
|
-
| `
|
|
625
|
-
| `
|
|
626
|
-
| `
|
|
627
|
-
| `
|
|
628
|
-
| `
|
|
927
|
+
| `content` | `ReactNode` | `""` | Body text (ReactNode) |
|
|
928
|
+
| `lineClamp` | `number` | `2` | Line clamp limit |
|
|
929
|
+
| `triggerMore` | `ReactNode` | `"more"` | Show more text (ReactNode) |
|
|
930
|
+
| `triggerLess` | `ReactNode` | `"less"` | Show less text (ReactNode) |
|
|
931
|
+
| `defaultShortened` | `boolean` | `true` | Initial collapsed state |
|
|
932
|
+
| `observingEnvs` | `boolean`[] | - | Re-measure on external condition change |
|
|
933
|
+
| `onShowMoreLessClick` | `ReactNode` | - | Show more/less click callback () => void |
|
|
934
|
+
| `className` | `string` | - | Style override |
|
|
629
935
|
|
|
630
936
|
```tsx
|
|
631
|
-
<
|
|
632
|
-
items={[
|
|
633
|
-
{ key: 'a', label: 'Tab A', children: <p>A</p> },
|
|
634
|
-
{ key: 'b', label: 'Tab B', children: <p>B</p> },
|
|
635
|
-
]}
|
|
636
|
-
defaultActiveKey="a"
|
|
637
|
-
variant="pill"
|
|
638
|
-
/>
|
|
937
|
+
<Ellipsis content={longText} lineClamp={3} triggerMore="more" triggerLess="less" />
|
|
639
938
|
```
|
|
640
939
|
|
|
641
940
|
---
|
|
642
941
|
|
|
643
|
-
##
|
|
942
|
+
## NumberInput
|
|
644
943
|
|
|
645
|
-
|
|
944
|
+
Number input. Accelerated increment on long press. Exposes increment/decrement methods via ref. numberInputBind(ref, direction) binds same acceleration to external buttons.
|
|
646
945
|
|
|
647
946
|
| Prop | Type | Default | Description |
|
|
648
947
|
|---|---|---|---|
|
|
649
|
-
| `
|
|
650
|
-
| `
|
|
651
|
-
| `
|
|
652
|
-
|
|
653
|
-
|
|
948
|
+
| `value` | `number` \| `string` | - | Current value |
|
|
949
|
+
| `size` | `'sm'` \| `'md'` \| `'lg'` \| `'xl'` | `"md"` | Size |
|
|
950
|
+
| `error` | `boolean` | - | Error state |
|
|
951
|
+
| `min` | `number` | - | Minimum value |
|
|
952
|
+
| `max` | `number` | - | Maximum value |
|
|
953
|
+
| `step` | `number` | `1` | Step increment |
|
|
954
|
+
| `digit` | `number` | `0` | Decimal places |
|
|
955
|
+
| `hideButtons` | `boolean` | `false` | Hide default spin buttons. Use with numberInputBind for external button event binding |
|
|
956
|
+
| `disabled` | `boolean` | - | Disabled |
|
|
957
|
+
| `readOnly` | `boolean` | - | Read-only (includes hiding spin buttons) |
|
|
958
|
+
| `placeholder` | `string` | - | Placeholder |
|
|
959
|
+
| `name` | `string` | - | Form field name |
|
|
960
|
+
| `id` | `string` | - | Element ID |
|
|
961
|
+
| `autoFocus` | `boolean` | - | Auto focus |
|
|
962
|
+
| `onValueChange` | `ReactNode` | - | Value change callback (value: number | undefined) => void |
|
|
963
|
+
| `onBlur` | `ReactNode` | - | Blur callback |
|
|
964
|
+
| `onFocus` | `ReactNode` | - | Focus callback |
|
|
965
|
+
| `className` | `string` | - | Style override |
|
|
966
|
+
|
|
967
|
+
Press-and-hold accelerates increment/decrement (100ms → 75ms → 50ms → 30ms). Default is right-side vertical spin buttons. Use `numberInputBind(ref, direction)` to bind the same acceleration events to external buttons for free placement.
|
|
654
968
|
|
|
655
969
|
```tsx
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
970
|
+
// Basic usage — right-side vertical spin buttons
|
|
971
|
+
<NumberInput
|
|
972
|
+
value={count}
|
|
973
|
+
onValueChange={setCount}
|
|
974
|
+
min={0}
|
|
975
|
+
max={100}
|
|
976
|
+
step={5}
|
|
977
|
+
/>
|
|
978
|
+
|
|
979
|
+
// External buttons — bind events with numberInputBind
|
|
980
|
+
const ref = useRef<NumberInputRef>(null);
|
|
981
|
+
|
|
982
|
+
<NumberInput ref={ref} value={count} onValueChange={setCount} hideButtons />
|
|
983
|
+
|
|
984
|
+
// Buttons can be placed anywhere, completely separate from NumberInput
|
|
985
|
+
<button {...numberInputBind(ref, 'decrement')}><MinusIcon /></button>
|
|
986
|
+
<button {...numberInputBind(ref, 'increment')}><PlusIcon /></button>
|
|
987
|
+
|
|
988
|
+
// Single invocation (onClick)
|
|
989
|
+
<button onClick={() => ref.current?.increment()}>+1</button>
|
|
663
990
|
```
|
|
664
991
|
|
|
665
992
|
---
|
|
666
993
|
|
|
667
|
-
##
|
|
994
|
+
## Hooks
|
|
668
995
|
|
|
669
|
-
|
|
996
|
+
### useModal
|
|
670
997
|
|
|
671
|
-
|
|
998
|
+
```tsx
|
|
999
|
+
const { open, close } = useModal();
|
|
1000
|
+
open(MyComponent, { title: 'Title' });
|
|
1001
|
+
```
|
|
672
1002
|
|
|
673
|
-
|
|
674
|
-
|---|---|---|---|
|
|
675
|
-
| `items` | `T[]` (필수) | - | 데이터 배열 |
|
|
676
|
-
| `estimateSize` | `number \| (index: number) => number` (필수) | - | 예상 아이템 높이 |
|
|
677
|
-
| `renderItem` | `(item: T, index: number) => ReactNode` (필수) | - | 렌더러 |
|
|
678
|
-
| `overscan` | `number` | `5` | 오버스캔 |
|
|
679
|
-
| `gap` | `number` | `0` | 아이템 간격 |
|
|
680
|
-
| `onEndReached` | `() => void` | - | 끝 도달 콜백 |
|
|
681
|
-
| `endReachedThreshold` | `number` | `200` | 끝 감지 임계값 (px) |
|
|
1003
|
+
### useInView
|
|
682
1004
|
|
|
683
1005
|
```tsx
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
estimateSize={48}
|
|
687
|
-
renderItem={(item) => <div>{item.name}</div>}
|
|
688
|
-
onEndReached={loadMore}
|
|
689
|
-
/>
|
|
1006
|
+
const { ref, inView } = useInView({ threshold: 0.5 });
|
|
1007
|
+
<div ref={ref}>{inView && <LazyContent />}</div>
|
|
690
1008
|
```
|
|
691
1009
|
|
|
692
|
-
###
|
|
1010
|
+
### useCheckDevice
|
|
693
1011
|
|
|
694
|
-
|
|
1012
|
+
```tsx
|
|
1013
|
+
const { isMobile, isTablet, isDesktop } = useCheckDevice();
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
### useClickOutside
|
|
695
1017
|
|
|
696
1018
|
```tsx
|
|
697
|
-
|
|
1019
|
+
const ref = useClickOutside<HTMLDivElement>(() => setOpen(false));
|
|
1020
|
+
<div ref={ref}>Dropdown content</div>
|
|
698
1021
|
```
|
|
1022
|
+
|
|
1023
|
+
---
|
|
1024
|
+
|
|
1025
|
+
## Toast (Imperative API)
|
|
1026
|
+
|
|
1027
|
+
Toast notifications. Sonner-based.
|
|
1028
|
+
|
|
1029
|
+
```tsx
|
|
1030
|
+
import { toast, Toaster } from '@nexus-cross/design-system';
|
|
1031
|
+
|
|
1032
|
+
// Place Toaster at app root
|
|
1033
|
+
<Toaster position="top-right" />
|
|
1034
|
+
|
|
1035
|
+
// Usage
|
|
1036
|
+
toast('Saved successfully');
|
|
1037
|
+
toast.success('Success!');
|
|
1038
|
+
toast.error('An error occurred');
|
|
1039
|
+
toast.loading('Processing...');
|
|
1040
|
+
```
|
|
1041
|
+
|