@nexus-cross/design-system 2.0.0 → 2.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/README.md +375 -0
- package/cursor-rules/nexus-ui-api.mdc +366 -7
- package/dist/chunks/{chunk-2T7RUYEK.js → chunk-2BINGHGR.js} +11 -3
- package/dist/chunks/chunk-3NSJMG3G.mjs +5 -0
- package/dist/chunks/{chunk-QOREDNWO.mjs → chunk-53BHDUID.mjs} +2 -1
- package/dist/chunks/{chunk-QZ4QR3XV.mjs → chunk-ATZE57ZO.mjs} +11 -3
- package/dist/chunks/{chunk-OX5MEJ7B.js → chunk-HU6E2R2T.js} +2 -1
- package/dist/chunks/chunk-JICW6KWH.js +7 -0
- package/dist/chunks/{chunk-5J63FUAS.mjs → chunk-LNC3TV6N.mjs} +53 -2
- package/dist/chunks/{chunk-BJM3NDT2.mjs → chunk-RL5UAEGQ.mjs} +11 -3
- package/dist/chunks/{chunk-LAGQ7J5A.js → chunk-VCN7DMCQ.js} +53 -2
- package/dist/chunks/{chunk-2ZXDXO4I.js → chunk-VDEB5BMT.js} +11 -3
- package/dist/components/ImageUpload.d.ts +14 -0
- package/dist/components/ImageUpload.d.ts.map +1 -1
- package/dist/components/NumberInput.d.ts +20 -1
- package/dist/components/NumberInput.d.ts.map +1 -1
- package/dist/components/Select.d.ts +5 -1
- package/dist/components/Select.d.ts.map +1 -1
- package/dist/components/Tab.d.ts +12 -1
- package/dist/components/Tab.d.ts.map +1 -1
- package/dist/image-upload.js +3 -3
- package/dist/image-upload.mjs +1 -1
- package/dist/index.js +16 -16
- package/dist/index.mjs +4 -4
- package/dist/number-input.js +4 -4
- package/dist/number-input.mjs +1 -1
- package/dist/schemas/_all.json +48 -10
- package/dist/schemas/image-upload.d.ts +6 -0
- package/dist/schemas/image-upload.d.ts.map +1 -1
- package/dist/schemas/imageUpload.json +19 -1
- package/dist/schemas/number-input.d.ts +9 -3
- package/dist/schemas/number-input.d.ts.map +1 -1
- package/dist/schemas/numberInput.json +10 -3
- package/dist/schemas/spinner.json +2 -2
- package/dist/schemas/tab.d.ts +11 -0
- package/dist/schemas/tab.d.ts.map +1 -1
- package/dist/schemas/tab.json +17 -4
- package/dist/schemas.js +48 -10
- package/dist/schemas.mjs +48 -10
- package/dist/select.js +5 -5
- package/dist/select.mjs +1 -1
- package/dist/styles/.generated/built.d.ts +1 -1
- package/dist/styles/.generated/built.d.ts.map +1 -1
- package/dist/styles/layer.js +2 -2
- package/dist/styles/layer.mjs +1 -1
- package/dist/styles.css +185 -44
- package/dist/styles.js +2 -2
- package/dist/styles.layered.css +185 -44
- package/dist/styles.mjs +1 -1
- package/dist/tab.js +4 -4
- package/dist/tab.mjs +1 -1
- package/dist/tokens/TOKENS.md +13 -0
- package/dist/tokens/company.css +21 -1
- package/dist/tokens/css.css +21 -1
- package/dist/tokens/data/color.json +32 -0
- package/dist/tokens/data/space.json +1 -1
- package/dist/tokens/data/typography.json +55 -1
- package/dist/tokens-domains/data/gamehub/domain.json +258 -0
- package/dist/tokens-domains/data/index.ts +3 -1
- package/dist/tokens-domains/data/prediction/domain.json +0 -12
- package/dist/tokens-domains/gamehub.md +62 -0
- package/dist/tokens-domains/prediction-vars.css +1 -5
- package/dist/tokens-domains/prediction.css +1 -5
- package/dist/tokens-domains/prediction.md +0 -1
- package/package.json +3 -3
- package/dist/chunks/chunk-3SCSND6S.js +0 -7
- package/dist/chunks/chunk-QWK4CLS2.mjs +0 -5
|
@@ -660,10 +660,10 @@ ANTI-PATTERNS:
|
|
|
660
660
|
| Prop | Type | Default | Description |
|
|
661
661
|
|---|---|---|---|
|
|
662
662
|
| `size` | `number` | `20` | Size in px |
|
|
663
|
-
| `color` | `string` | - | Color (CSS color value
|
|
663
|
+
| `color` | `string` | - | Color (CSS color value). Default: brand primary (--color-accent-primary) |
|
|
664
664
|
| `aria-label` | `string` | `"Loading"` | Accessibility label |
|
|
665
665
|
| `style` | `ReactNode` | - | Inline style (CSSProperties) |
|
|
666
|
-
| `className` | `string` | - | Color override
|
|
666
|
+
| `className` | `string` | - | Color override (e.g. text-text-muted) — utility class wins over the default primary |
|
|
667
667
|
|
|
668
668
|
```tsx
|
|
669
669
|
<Spinner size={24} />
|
|
@@ -904,7 +904,7 @@ ANTI-PATTERNS:
|
|
|
904
904
|
| `shouldScaleBackground` | `boolean` | - | Background scale effect (default false) |
|
|
905
905
|
| `children` | `ReactNode` | - | Drawer sub-components (ReactNode, required) |
|
|
906
906
|
|
|
907
|
-
###
|
|
907
|
+
### Drawer.Content
|
|
908
908
|
|
|
909
909
|
Drawer.Content area.
|
|
910
910
|
|
|
@@ -917,6 +917,44 @@ Drawer.Content area.
|
|
|
917
917
|
| `overlayClassName` | `string` | - | Overlay style |
|
|
918
918
|
| `className` | `string` | - | Panel style |
|
|
919
919
|
|
|
920
|
+
### Drawer.Trigger
|
|
921
|
+
|
|
922
|
+
Drawer open trigger.
|
|
923
|
+
|
|
924
|
+
| Prop | Type | Default | Description |
|
|
925
|
+
|---|---|---|---|
|
|
926
|
+
| `asChild` | `boolean` | - | Render as child element |
|
|
927
|
+
| `children` | `ReactNode` | - | Trigger element (ReactNode, required) |
|
|
928
|
+
| `className` | `string` | - | Style override |
|
|
929
|
+
|
|
930
|
+
### Drawer.Close
|
|
931
|
+
|
|
932
|
+
Drawer close button.
|
|
933
|
+
|
|
934
|
+
| Prop | Type | Default | Description |
|
|
935
|
+
|---|---|---|---|
|
|
936
|
+
| `asChild` | `boolean` | - | Render as child element |
|
|
937
|
+
| `children` | `ReactNode` | - | Close element (ReactNode, required) |
|
|
938
|
+
| `className` | `string` | - | Style override |
|
|
939
|
+
|
|
940
|
+
### Drawer.Title
|
|
941
|
+
|
|
942
|
+
Drawer title (required for accessibility).
|
|
943
|
+
|
|
944
|
+
| Prop | Type | Default | Description |
|
|
945
|
+
|---|---|---|---|
|
|
946
|
+
| `children` | `ReactNode` | - | Title text (ReactNode, required) |
|
|
947
|
+
| `className` | `string` | - | Style override |
|
|
948
|
+
|
|
949
|
+
### Drawer.Description
|
|
950
|
+
|
|
951
|
+
Drawer description.
|
|
952
|
+
|
|
953
|
+
| Prop | Type | Default | Description |
|
|
954
|
+
|---|---|---|---|
|
|
955
|
+
| `children` | `ReactNode` | - | Description text (ReactNode, required) |
|
|
956
|
+
| `className` | `string` | - | Style override |
|
|
957
|
+
|
|
920
958
|
```tsx
|
|
921
959
|
<Drawer direction="bottom">
|
|
922
960
|
<Drawer.Trigger asChild>
|
|
@@ -1062,6 +1100,14 @@ WHEN TO USE:
|
|
|
1062
1100
|
• Immediate filter/option toggle → ToggleGroup
|
|
1063
1101
|
• Stacked collapsible sections → Accordion
|
|
1064
1102
|
|
|
1103
|
+
WIDTH BEHAVIOR (commonly misunderstood):
|
|
1104
|
+
• Default: each trigger sizes to its label content; the list is as wide as the sum of triggers
|
|
1105
|
+
• Want all triggers to evenly fill a parent? → set `fullWidth` (NOT just `tabListClassName="w-full"`)
|
|
1106
|
+
`tabListClassName="w-full"` only widens the list; triggers stay content-width because flex children
|
|
1107
|
+
default to `flex: 0 1 auto`. `fullWidth` adds `flex: 1` to every trigger.
|
|
1108
|
+
• Want one specific trigger wider? → use `items[i].className="flex-2"` (or any flex utility)
|
|
1109
|
+
• Want all triggers wider but not equal? → use `tabItemClassName="min-w-[120px]"` etc.
|
|
1110
|
+
|
|
1065
1111
|
destroyInactive=true unmounts hidden panels (saves memory but loses state).
|
|
1066
1112
|
|
|
1067
1113
|
ANTI-PATTERNS:
|
|
@@ -1069,6 +1115,7 @@ ANTI-PATTERNS:
|
|
|
1069
1115
|
✗ Tab with 8+ items — consider sub-routing or DropdownMenu
|
|
1070
1116
|
✗ Using Tab for form value selection → RadioGroup
|
|
1071
1117
|
✗ Custom <button> + onClick + state → Tab (a11y, keyboard, focus management)
|
|
1118
|
+
✗ `tabListClassName="w-full"` expecting triggers to stretch → use `fullWidth` prop
|
|
1072
1119
|
|
|
1073
1120
|
| Prop | Type | Default | Description |
|
|
1074
1121
|
|---|---|---|---|
|
|
@@ -1077,13 +1124,16 @@ ANTI-PATTERNS:
|
|
|
1077
1124
|
| `defaultActiveKey` | `string` | - | Uncontrolled initial key |
|
|
1078
1125
|
| `variant` | `'line'` \| `'pill'` | `"line"` | Style |
|
|
1079
1126
|
| `size` | `'sm'` \| `'md'` | `"md"` | Size |
|
|
1127
|
+
| `fullWidth` | `boolean` | `false` | Stretch all triggers to evenly fill the tab list width (`flex: 1`) and set the list to `width: 100%`. Use for top-level navigation tabs that should span the container. Defaults to false (each trigger sizes to its content). |
|
|
1080
1128
|
| `destroyInactive` | `boolean` | `false` | Unmount inactive panels |
|
|
1081
1129
|
| `onTabChange` | `ReactNode` | - | Tab change callback (key: string) => void |
|
|
1082
|
-
| `className` | `string` | - | Root
|
|
1083
|
-
| `tabListClassName` | `string` | - |
|
|
1084
|
-
| `
|
|
1130
|
+
| `className` | `string` | - | Root wrapper className |
|
|
1131
|
+
| `tabListClassName` | `string` | - | className for the tab list (the row of triggers). Width utilities (`w-full`, `max-w-md`, etc.) apply here. Note: `w-full` alone does NOT stretch individual triggers — combine with `fullWidth=true` for that. |
|
|
1132
|
+
| `tabItemClassName` | `string` | - | className applied to EVERY tab trigger <button>. Combine with `fullWidth` for equal-width tabs, or pass spacing/typography utilities to restyle all triggers at once. For per-item overrides use `items[].className`. |
|
|
1133
|
+
| `tabPanelClassName` | `string` | - | className for each panel <div> |
|
|
1085
1134
|
|
|
1086
1135
|
```tsx
|
|
1136
|
+
// Basic — triggers size to their content
|
|
1087
1137
|
<Tab
|
|
1088
1138
|
items={[
|
|
1089
1139
|
{ key: 'a', label: 'Tab A', children: <p>A</p> },
|
|
@@ -1092,6 +1142,28 @@ ANTI-PATTERNS:
|
|
|
1092
1142
|
defaultActiveKey="a"
|
|
1093
1143
|
variant="pill"
|
|
1094
1144
|
/>
|
|
1145
|
+
|
|
1146
|
+
// Equal-width tabs filling the parent (do NOT just use tabListClassName="w-full" —
|
|
1147
|
+
// that widens the list but leaves triggers content-sized).
|
|
1148
|
+
<Tab
|
|
1149
|
+
fullWidth
|
|
1150
|
+
variant="pill"
|
|
1151
|
+
items={[
|
|
1152
|
+
{ key: 'overview', label: 'Overview', children: <Overview /> },
|
|
1153
|
+
{ key: 'history', label: 'History', children: <History /> },
|
|
1154
|
+
{ key: 'settings', label: 'Settings', children: <Settings /> },
|
|
1155
|
+
]}
|
|
1156
|
+
/>
|
|
1157
|
+
|
|
1158
|
+
// Per-item sizing override — make one trigger twice as wide.
|
|
1159
|
+
<Tab
|
|
1160
|
+
fullWidth
|
|
1161
|
+
items={[
|
|
1162
|
+
{ key: 'a', label: 'A', children: <A /> },
|
|
1163
|
+
{ key: 'b', label: 'B (wider)', className: 'flex-[2]', children: <B /> },
|
|
1164
|
+
{ key: 'c', label: 'C', children: <C /> },
|
|
1165
|
+
]}
|
|
1166
|
+
/>
|
|
1095
1167
|
```
|
|
1096
1168
|
|
|
1097
1169
|
---
|
|
@@ -1121,6 +1193,32 @@ ANTI-PATTERNS:
|
|
|
1121
1193
|
| `children` | `ReactNode` | - | Carousel slides and sub-components (ReactNode) |
|
|
1122
1194
|
| `className` | `string` | - | Style override |
|
|
1123
1195
|
|
|
1196
|
+
### Carousel.Slide
|
|
1197
|
+
|
|
1198
|
+
Carousel slide. Used inside Carousel.
|
|
1199
|
+
|
|
1200
|
+
| Prop | Type | Default | Description |
|
|
1201
|
+
|---|---|---|---|
|
|
1202
|
+
| `className` | `string` | - | Slide style (use basis-1/3 etc. for multi-slide view) |
|
|
1203
|
+
| `children` | `ReactNode` | - | Slide content (ReactNode, required) |
|
|
1204
|
+
|
|
1205
|
+
### Carousel.Button
|
|
1206
|
+
|
|
1207
|
+
CarouselPrev / CarouselNext. Previous/next navigation buttons.
|
|
1208
|
+
|
|
1209
|
+
| Prop | Type | Default | Description |
|
|
1210
|
+
|---|---|---|---|
|
|
1211
|
+
| `className` | `string` | - | Button style override |
|
|
1212
|
+
| `children` | `ReactNode` | - | Custom icon (ReactNode, default: chevron) |
|
|
1213
|
+
|
|
1214
|
+
### Carousel.Dots
|
|
1215
|
+
|
|
1216
|
+
CarouselDots. Slide indicator dots.
|
|
1217
|
+
|
|
1218
|
+
| Prop | Type | Default | Description |
|
|
1219
|
+
|---|---|---|---|
|
|
1220
|
+
| `className` | `string` | - | Dot container style |
|
|
1221
|
+
|
|
1124
1222
|
Sub-components: `CarouselSlide`, `CarouselPrev`, `CarouselNext`, `CarouselDots`
|
|
1125
1223
|
|
|
1126
1224
|
```tsx
|
|
@@ -1621,13 +1719,16 @@ ANTI-PATTERNS:
|
|
|
1621
1719
|
✗ <TextInput type="number"> → <NumberInput> (loses keyboard ↑↓, step, accelerated long-press, max click)
|
|
1622
1720
|
✗ Custom +/- buttons + <input> → variant="bind" (or numberInputBind for external)
|
|
1623
1721
|
✗ Manual thousand separators for currency → PriceInput
|
|
1722
|
+
✗ Inline unit text inside label/description → use prefixIcon/suffixIcon (e.g. suffixIcon="%")
|
|
1624
1723
|
|
|
1625
1724
|
| Prop | Type | Default | Description |
|
|
1626
1725
|
|---|---|---|---|
|
|
1627
1726
|
| `variant` | `'basic'` \| `'bind'` | `"basic"` | Variant: basic (right chevron arrows) or bind (left/right +/- buttons) |
|
|
1628
1727
|
| `value` | `number` \| `string` | - | Current value |
|
|
1629
|
-
| `size` | `'lg'` \| `'xl'` | `"
|
|
1728
|
+
| `size` | `'md'` \| `'lg'` \| `'xl'` | `"md"` | Size (matches TextInput scale) |
|
|
1630
1729
|
| `error` | `boolean` | - | Error state |
|
|
1730
|
+
| `prefixIcon` | `ReactNode` | - | Prefix node — currency symbol, unit, or icon (ReactNode) |
|
|
1731
|
+
| `suffixIcon` | `ReactNode` | - | Suffix node — unit (%, kg, 원...) or icon. Pair with hideButtons or variant="bind" so it does not collide with arrow buttons |
|
|
1631
1732
|
| `min` | `number` | - | Minimum value |
|
|
1632
1733
|
| `max` | `number` | - | Maximum value. When set, "Max {value}" is displayed in the header. Clicking it fills the input with the max value |
|
|
1633
1734
|
| `step` | `number` | `1` | Step increment |
|
|
@@ -1649,9 +1750,12 @@ ANTI-PATTERNS:
|
|
|
1649
1750
|
|
|
1650
1751
|
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.
|
|
1651
1752
|
|
|
1753
|
+
Sizes (md / lg / xl) match TextInput. Use `prefixIcon` / `suffixIcon` for unit indicators (currency, %, kg…). `suffixIcon` works best with `hideButtons` or `variant="bind"` so it does not collide with the right-side arrow buttons.
|
|
1754
|
+
|
|
1652
1755
|
```tsx
|
|
1653
1756
|
// Basic usage — right-side vertical spin buttons
|
|
1654
1757
|
<NumberInput
|
|
1758
|
+
label="Quantity"
|
|
1655
1759
|
value={count}
|
|
1656
1760
|
onValueChange={setCount}
|
|
1657
1761
|
min={0}
|
|
@@ -1659,6 +1763,14 @@ Press-and-hold accelerates increment/decrement (100ms → 75ms → 50ms → 30ms
|
|
|
1659
1763
|
step={5}
|
|
1660
1764
|
/>
|
|
1661
1765
|
|
|
1766
|
+
// Prefix / suffix — unit · currency
|
|
1767
|
+
<NumberInput label="Price" prefixIcon="$" digit={2} placeholder="0.00" />
|
|
1768
|
+
<NumberInput label="Discount" suffixIcon="%" hideButtons max={100} />
|
|
1769
|
+
<NumberInput label="Weight" suffixIcon="kg" variant="bind" />
|
|
1770
|
+
|
|
1771
|
+
// Bind variant — left/right ± buttons (good for mobile)
|
|
1772
|
+
<NumberInput variant="bind" label="Quantity" value={count} onValueChange={setCount} max={99} />
|
|
1773
|
+
|
|
1662
1774
|
// External buttons — bind events with numberInputBind
|
|
1663
1775
|
const ref = useRef<NumberInputRef>(null);
|
|
1664
1776
|
|
|
@@ -2101,15 +2213,22 @@ ImageUpload — drag-and-drop image upload with preview, file-type/size validati
|
|
|
2101
2213
|
|
|
2102
2214
|
WHEN TO USE:
|
|
2103
2215
|
• Single image upload: avatar, cover, KYC document, post thumbnail
|
|
2216
|
+
• Banner / hero / thumbnail slot where the picked image should fill the box → previewMode="cover"
|
|
2104
2217
|
• Multiple files / non-image → not yet supported; build custom or use a file dropzone
|
|
2105
2218
|
• Inline image picker without preview → custom <input type="file">
|
|
2106
2219
|
|
|
2107
2220
|
accept whitelist + maxSize together cover validation. onError fires with i18n-ready string.
|
|
2108
2221
|
|
|
2222
|
+
PREVIEW MODES:
|
|
2223
|
+
• card (default) — 작은 썸네일 + 우측에 "이미지 변경" 버튼 + 포맷 텍스트. form 필드용.
|
|
2224
|
+
• cover — 빈 상태 박스를 그대로 유지하고 이미지가 전체를 가득 채움. 클릭 시 변경, 우측 상단 X로 삭제.
|
|
2225
|
+
크기/비율은 className(h-64, aspect-video, w-full 등) 또는 size prop으로 조정.
|
|
2226
|
+
|
|
2109
2227
|
ANTI-PATTERNS:
|
|
2110
2228
|
✗ <input type="file"> + manual preview → ImageUpload (handles drag, validation, preview)
|
|
2111
2229
|
✗ ImageUpload without onError handler → silent failure on validation reject
|
|
2112
2230
|
✗ Storing image as data-URL value (use File via onChange and upload to server)
|
|
2231
|
+
✗ card 모드에서 작은 썸네일이 어색한 배너/카드 슬롯 → previewMode="cover"
|
|
2113
2232
|
|
|
2114
2233
|
| Prop | Type | Default | Description |
|
|
2115
2234
|
|---|---|---|---|
|
|
@@ -2125,7 +2244,247 @@ ANTI-PATTERNS:
|
|
|
2125
2244
|
| `description` | `ReactNode` | - | Field description below the upload area (ReactNode) |
|
|
2126
2245
|
| `disabled` | `boolean` | `false` | Disabled state |
|
|
2127
2246
|
| `size` | `'sm'` \| `'md'` \| `'lg'` | `"md"` | Upload area size |
|
|
2247
|
+
| `previewMode` | `'card'` \| `'cover'` | `"card"` | Preview layout after upload. "card" = small thumbnail + change button beside it (default). "cover" = image fills the entire box (banner / thumbnail slot UX). In cover mode, click the box to change, click the X in the top-right to remove. Box dimensions follow size or className (h-64, aspect-video, etc.) |
|
|
2248
|
+
| `previewFit` | `'cover'` \| `'contain'` | `"cover"` | Object-fit for previewMode="cover". "cover" fills the box (may crop), "contain" fits entirely inside (may letterbox). |
|
|
2249
|
+
| `className` | `string` | - | Style override |
|
|
2250
|
+
|
|
2251
|
+
Two preview layouts via `previewMode`:
|
|
2252
|
+
- `'card'` (default) — small thumbnail + side "이미지 변경" button + format text. Best for form fields.
|
|
2253
|
+
- `'cover'` — image fills the entire box (banner / thumbnail slot UX). Click box to change, X to remove. Box dimensions follow `size` or className (`h-64`, `aspect-video`, `w-full` …).
|
|
2254
|
+
|
|
2255
|
+
```tsx
|
|
2256
|
+
import { ImageUpload } from '@nexus-cross/design-system';
|
|
2257
|
+
import { Controller } from 'react-hook-form';
|
|
2258
|
+
|
|
2259
|
+
// Form field — card preview (default)
|
|
2260
|
+
<Controller
|
|
2261
|
+
control={control}
|
|
2262
|
+
name="thumbnail"
|
|
2263
|
+
render={({ field }) => (
|
|
2264
|
+
<ImageUpload
|
|
2265
|
+
label="썸네일"
|
|
2266
|
+
description="JPG · PNG · 최대 2MB"
|
|
2267
|
+
value={field.value}
|
|
2268
|
+
onChange={(file) => field.onChange(file ? URL.createObjectURL(file) : null)}
|
|
2269
|
+
onError={(msg) => toast.error(msg)}
|
|
2270
|
+
/>
|
|
2271
|
+
)}
|
|
2272
|
+
/>
|
|
2273
|
+
|
|
2274
|
+
// Banner / hero slot — cover preview, custom aspect ratio
|
|
2275
|
+
<ImageUpload
|
|
2276
|
+
previewMode="cover"
|
|
2277
|
+
className="aspect-[16/9] w-full"
|
|
2278
|
+
size="lg"
|
|
2279
|
+
value={banner}
|
|
2280
|
+
onChange={(file) => setBanner(file ? URL.createObjectURL(file) : null)}
|
|
2281
|
+
placeholder="배너 이미지 업로드"
|
|
2282
|
+
/>
|
|
2283
|
+
|
|
2284
|
+
// Card slot — fixed height, contain (no crop)
|
|
2285
|
+
<ImageUpload
|
|
2286
|
+
previewMode="cover"
|
|
2287
|
+
previewFit="contain"
|
|
2288
|
+
className="h-48"
|
|
2289
|
+
value={cover}
|
|
2290
|
+
onChange={handleCover}
|
|
2291
|
+
/>
|
|
2292
|
+
```
|
|
2293
|
+
|
|
2294
|
+
---
|
|
2295
|
+
|
|
2296
|
+
## Table
|
|
2297
|
+
|
|
2298
|
+
Table — tabular data with sortable columns, skeleton/loading/empty states. Compound: TableRow + TdColumn.
|
|
2299
|
+
|
|
2300
|
+
WHEN TO USE:
|
|
2301
|
+
• Comparable structured data (financial reports, transaction history, leaderboard)
|
|
2302
|
+
• Sortable per-column → enableSorting on TdColumn
|
|
2303
|
+
• Card-style entities → DataGrid
|
|
2304
|
+
• Single-column read-only list → DataList
|
|
2305
|
+
• Huge dataset (>500 rows) → wrap with VirtualList logic or paginate
|
|
2306
|
+
|
|
2307
|
+
list={null} → loading state. list=[] → noDataMsg. children: ({ item, index }) => <TableRow>...</TableRow>.
|
|
2308
|
+
|
|
2309
|
+
ANTI-PATTERNS:
|
|
2310
|
+
✗ Native <table> + manual loading/empty handling → Table component
|
|
2311
|
+
✗ Forcing card-style data into Table (use DataGrid)
|
|
2312
|
+
✗ Sorting in client when server pagination is in use → server-side sort
|
|
2313
|
+
|
|
2314
|
+
| Prop | Type | Default | Description |
|
|
2315
|
+
|---|---|---|---|
|
|
2316
|
+
| `list` | `ReactNode`[] | - | Data array. null/undefined = loading state (required) |
|
|
2317
|
+
| `children` | `ReactNode` | - | Row render function ({ item, index }) => ReactNode (required) |
|
|
2318
|
+
| `hideThead` | `boolean` | - | Hide table header |
|
|
2319
|
+
| `loading` | `boolean` | - | Force loading state |
|
|
2320
|
+
| `loadingType` | `'loading'` \| `'skeleton'` | `"skeleton"` | Loading display type |
|
|
2321
|
+
| `loadingElement` | `ReactNode` | - | Custom loading element (ReactElement) |
|
|
2322
|
+
| `skeletonCount` | `number` | `10` | Skeleton row count |
|
|
2323
|
+
| `noDataMsg` | `ReactNode` | - | No data message (ReactElement | string) |
|
|
2324
|
+
| `notification` | `ReactNode` | - | Table top notification area (ReactNode) |
|
|
2325
|
+
| `sortUpElement` | `ReactNode` | - | Ascending sort icon (ReactElement) |
|
|
2326
|
+
| `sortDownElement` | `ReactNode` | - | Descending sort icon (ReactElement) |
|
|
2327
|
+
| `className` | `string` | - | Table wrapper style |
|
|
2328
|
+
| `theadClassName` | `string` | - | Header row style |
|
|
2329
|
+
|
|
2330
|
+
### Table.Row
|
|
2331
|
+
|
|
2332
|
+
TableRow — a single row inside Table. Wraps TdColumn cells.
|
|
2333
|
+
|
|
2334
|
+
WHEN TO USE:
|
|
2335
|
+
• variant="accent" highlights selected/current row
|
|
2336
|
+
• onClick for row-level navigation (e.g. open detail page)
|
|
2337
|
+
• Per-row checkbox/action → place inside a TdColumn child
|
|
2338
|
+
|
|
2339
|
+
| Prop | Type | Default | Description |
|
|
2340
|
+
|---|---|---|---|
|
|
2341
|
+
| `variant` | `'default'` \| `'accent'` | `"default"` | Row style |
|
|
2128
2342
|
| `className` | `string` | - | Style override |
|
|
2343
|
+
| `children` | `ReactNode` | - | TdColumn list (ReactNode, required) |
|
|
2344
|
+
| `onClick` | `ReactNode` | - | Row click callback (e: MouseEvent) => void |
|
|
2345
|
+
|
|
2346
|
+
### Td
|
|
2347
|
+
|
|
2348
|
+
TdColumn — table cell. Drives both <th> and <td> for the column. Provides sorting, alignment, overflow handling.
|
|
2349
|
+
|
|
2350
|
+
WHEN TO USE:
|
|
2351
|
+
• textOverflow="truncate" (default) for fixed-width columns; "wrap" for narrative
|
|
2352
|
+
• align="right" for numeric/currency columns (a11y readability)
|
|
2353
|
+
• enableSorting=true when column is sortable; supply order + handleClickSort for controlled sort
|
|
2354
|
+
• highlightKey to group columns that highlight together on hover
|
|
2355
|
+
|
|
2356
|
+
ANTI-PATTERNS:
|
|
2357
|
+
✗ enableSorting without handleClickSort → sort indicator is dead
|
|
2358
|
+
✗ align="left" for currency/number columns
|
|
2359
|
+
✗ Mixing text and numeric in one column without consistent align
|
|
2360
|
+
|
|
2361
|
+
| Prop | Type | Default | Description |
|
|
2362
|
+
|---|---|---|---|
|
|
2363
|
+
| `label` | `ReactNode` | - | Header label (ReactElement | string) |
|
|
2364
|
+
| `fieldId` | `string` | - | Column identifier (sort key, required) |
|
|
2365
|
+
| `size` | `number` \| `string` | - | Column width (number → px, string → CSS value) |
|
|
2366
|
+
| `align` | `'left'` \| `'center'` \| `'right'` | `"left"` | Text alignment |
|
|
2367
|
+
| `textOverflow` | `'auto'` \| `'truncate'` \| `'wrap'` \| `'break-all'` | `"truncate"` | Text overflow handling |
|
|
2368
|
+
| `highlightKey` | `string` | - | Hover highlight group key |
|
|
2369
|
+
| `colSpan` | `number` | - | Column span |
|
|
2370
|
+
| `rowSpan` | `number` | - | Row span |
|
|
2371
|
+
| `thColSpan` | `number` | - | Header colSpan (<th>) |
|
|
2372
|
+
| `thRowSpan` | `number` | - | Header rowSpan (<th>) |
|
|
2373
|
+
| `enableSorting` | `boolean` | - | Enable sorting |
|
|
2374
|
+
| `order` | `'desc'` \| `'asc'` \| `''` | - | Current sort direction |
|
|
2375
|
+
| `sortValue` | `string` \| `number` | - | Sort criterion value |
|
|
2376
|
+
| `handleClickSort` | `ReactNode` | - | Sort click callback ({ index, fieldId, order }) => void |
|
|
2377
|
+
| `children` | `ReactNode` | - | Cell content (ReactNode, required) |
|
|
2378
|
+
| `className` | `string` | - | Style override |
|
|
2379
|
+
|
|
2380
|
+
Composable table with `<Table>`, `<Table.Row>`, `<Td>`. Headers go in the first `<Table.Row>` with `isHeader`. Width / alignment is set per cell via `<Td>` props (do NOT add custom `<th>/<td>` markup).
|
|
2381
|
+
|
|
2382
|
+
```tsx
|
|
2383
|
+
import { Table } from '@nexus-cross/design-system';
|
|
2384
|
+
|
|
2385
|
+
<Table>
|
|
2386
|
+
<Table.Row isHeader>
|
|
2387
|
+
<Td width="40%">Name</Td>
|
|
2388
|
+
<Td width="20%" align="center">Status</Td>
|
|
2389
|
+
<Td width="20%" align="right">Amount</Td>
|
|
2390
|
+
<Td width="20%" align="right">Date</Td>
|
|
2391
|
+
</Table.Row>
|
|
2392
|
+
|
|
2393
|
+
{items.map((item) => (
|
|
2394
|
+
<Table.Row key={item.id} onClick={() => onRowClick(item)}>
|
|
2395
|
+
<Td>{item.name}</Td>
|
|
2396
|
+
<Td align="center"><Badge>{item.status}</Badge></Td>
|
|
2397
|
+
<Td align="right">{formatPrice(item.amount)}</Td>
|
|
2398
|
+
<Td align="right">{item.date}</Td>
|
|
2399
|
+
</Table.Row>
|
|
2400
|
+
))}
|
|
2401
|
+
</Table>
|
|
2402
|
+
|
|
2403
|
+
// For large datasets / virtualization → use DataGrid instead.
|
|
2404
|
+
// For card-style key/value pairs → use DataList.
|
|
2405
|
+
```
|
|
2406
|
+
|
|
2407
|
+
---
|
|
2408
|
+
|
|
2409
|
+
## ErrorBoundary
|
|
2410
|
+
|
|
2411
|
+
ErrorBoundary — catches render-time errors in subtree and shows fallback UI.
|
|
2412
|
+
|
|
2413
|
+
WHEN TO USE:
|
|
2414
|
+
• Wrap risky areas: third-party widgets, dynamic imports, untrusted content rendering
|
|
2415
|
+
• Async errors / promise rejections → NOT caught (use try/catch in handlers)
|
|
2416
|
+
• Per-route error wrapping → use framework's error.tsx (Next.js) where possible
|
|
2417
|
+
|
|
2418
|
+
DataList / DataGrid have ErrorBoundary built-in — don't double-wrap.
|
|
2419
|
+
|
|
2420
|
+
ANTI-PATTERNS:
|
|
2421
|
+
✗ ErrorBoundary at app root only — granular boundaries give better UX (one widget fails, page survives)
|
|
2422
|
+
✗ Showing raw Error.message in production (leak risk) — use friendly fallback
|
|
2423
|
+
✗ Forgetting onError logging → silent failures in production
|
|
2424
|
+
|
|
2425
|
+
| Prop | Type | Default | Description |
|
|
2426
|
+
|---|---|---|---|
|
|
2427
|
+
| `children` | `ReactNode` | - | Child elements to wrap (ReactNode, required) |
|
|
2428
|
+
| `fallback` | `ReactNode` | - | Fallback UI on error (ReactNode) |
|
|
2429
|
+
| `onError` | `ReactNode` | - | Error callback (error: Error, errorInfo: ErrorInfo) => void |
|
|
2430
|
+
|
|
2431
|
+
React error boundary using class component pattern. Wrap any subtree that may throw — it shows `fallback` and (optionally) calls `onError` for logging. `resetKeys` reset the boundary when their values change (great for route changes).
|
|
2432
|
+
|
|
2433
|
+
```tsx
|
|
2434
|
+
import { ErrorBoundary } from '@nexus-cross/design-system';
|
|
2435
|
+
|
|
2436
|
+
<ErrorBoundary
|
|
2437
|
+
fallback={<EmptyState title="Something went wrong" />}
|
|
2438
|
+
onError={(error, info) => reportError(error, info)}
|
|
2439
|
+
resetKeys={[location.pathname]}
|
|
2440
|
+
>
|
|
2441
|
+
<MyRoute />
|
|
2442
|
+
</ErrorBoundary>
|
|
2443
|
+
|
|
2444
|
+
// Per-component fallback function (receives error + reset)
|
|
2445
|
+
<ErrorBoundary fallback={(err, reset) => (
|
|
2446
|
+
<Alert intent="error">
|
|
2447
|
+
{err.message}
|
|
2448
|
+
<Button onClick={reset}>Retry</Button>
|
|
2449
|
+
</Alert>
|
|
2450
|
+
)}>
|
|
2451
|
+
<RiskyWidget />
|
|
2452
|
+
</ErrorBoundary>
|
|
2453
|
+
```
|
|
2454
|
+
|
|
2455
|
+
---
|
|
2456
|
+
|
|
2457
|
+
## ClientOnly
|
|
2458
|
+
|
|
2459
|
+
ClientOnly — defers children to client-side render, preventing SSR hydration mismatch.
|
|
2460
|
+
|
|
2461
|
+
WHEN TO USE:
|
|
2462
|
+
• Wrap components that read window/document/localStorage at render time
|
|
2463
|
+
• Components depending on browser-only APIs (IntersectionObserver eager init, geolocation)
|
|
2464
|
+
• Server-renderable content → DON'T wrap (loses SEO/SSR perf benefits)
|
|
2465
|
+
• Conditional based on data → use proper SSR-safe state instead
|
|
2466
|
+
|
|
2467
|
+
Pass fallback to avoid layout shift during hydration.
|
|
2468
|
+
|
|
2469
|
+
ANTI-PATTERNS:
|
|
2470
|
+
✗ Wrapping the entire page in ClientOnly (defeats SSR)
|
|
2471
|
+
✗ ClientOnly without fallback for above-the-fold UI (CLS)
|
|
2472
|
+
✗ Using ClientOnly to "fix" any hydration warning — root-cause the mismatch first
|
|
2473
|
+
|
|
2474
|
+
| Prop | Type | Default | Description |
|
|
2475
|
+
|---|---|---|---|
|
|
2476
|
+
| `children` | `ReactNode` | - | Element to render only on client (ReactNode, required) |
|
|
2477
|
+
| `fallback` | `ReactNode` | - | Fallback UI during SSR (ReactNode, default: null) |
|
|
2478
|
+
|
|
2479
|
+
Renders children only on the client. Use to skip SSR for components that depend on `window`/`document` (Carousel, DatePicker, ImageUpload preview, etc.) when adopting this design system inside Next.js / Remix.
|
|
2480
|
+
|
|
2481
|
+
```tsx
|
|
2482
|
+
import { ClientOnly } from '@nexus-cross/design-system';
|
|
2483
|
+
|
|
2484
|
+
<ClientOnly fallback={<Skeleton className="h-64 w-full" />}>
|
|
2485
|
+
<Carousel slides={slides} />
|
|
2486
|
+
</ClientOnly>
|
|
2487
|
+
```
|
|
2129
2488
|
|
|
2130
2489
|
---
|
|
2131
2490
|
|
|
@@ -31,12 +31,14 @@ var numberInputVariants = classVarianceAuthority.cva("nexus-number-input", {
|
|
|
31
31
|
basic: "nexus-number-input--basic",
|
|
32
32
|
bind: "nexus-number-input--bind"
|
|
33
33
|
},
|
|
34
|
+
// TextInput과 동일한 사이즈 체계 (md / lg / xl, default md)
|
|
34
35
|
size: {
|
|
36
|
+
md: "nexus-number-input--md",
|
|
35
37
|
lg: "nexus-number-input--lg",
|
|
36
38
|
xl: "nexus-number-input--xl"
|
|
37
39
|
}
|
|
38
40
|
},
|
|
39
|
-
defaultVariants: { variant: "basic", size: "
|
|
41
|
+
defaultVariants: { variant: "basic", size: "md" }
|
|
40
42
|
});
|
|
41
43
|
var CHEVRON_PATH = "M3.606.179C3.82-.06 4.18-.06 4.394.179L7.846 4.01C8.18 4.382 7.934 5 7.452 5H.548C.066 5-.18 4.382.154 4.01L3.606.179Z";
|
|
42
44
|
var ChevronUpIcon = () => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -106,6 +108,8 @@ function numberInputBind(ref, direction) {
|
|
|
106
108
|
var NumberInput = React__namespace.forwardRef(
|
|
107
109
|
({
|
|
108
110
|
className,
|
|
111
|
+
containerClassName,
|
|
112
|
+
inputClassName,
|
|
109
113
|
variant = "basic",
|
|
110
114
|
size,
|
|
111
115
|
error,
|
|
@@ -118,6 +122,8 @@ var NumberInput = React__namespace.forwardRef(
|
|
|
118
122
|
description,
|
|
119
123
|
showMax,
|
|
120
124
|
hideButtons = false,
|
|
125
|
+
prefixIcon,
|
|
126
|
+
suffixIcon,
|
|
121
127
|
disabled,
|
|
122
128
|
readOnly,
|
|
123
129
|
onValueChange,
|
|
@@ -288,7 +294,7 @@ var NumberInput = React__namespace.forwardRef(
|
|
|
288
294
|
}
|
|
289
295
|
)
|
|
290
296
|
] }),
|
|
291
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "nexus-number-input__container", children: [
|
|
297
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: chunkCZC76ZD5_js.cn("nexus-number-input__container", containerClassName), children: [
|
|
292
298
|
!isBasic && showBtns && /* @__PURE__ */ jsxRuntime.jsx(
|
|
293
299
|
"button",
|
|
294
300
|
{
|
|
@@ -303,6 +309,7 @@ var NumberInput = React__namespace.forwardRef(
|
|
|
303
309
|
children: /* @__PURE__ */ jsxRuntime.jsx(MinusIcon, {})
|
|
304
310
|
}
|
|
305
311
|
),
|
|
312
|
+
prefixIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nexus-number-input__icon nexus-number-input__icon--prefix", children: prefixIcon }),
|
|
306
313
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
307
314
|
"input",
|
|
308
315
|
{
|
|
@@ -310,7 +317,7 @@ var NumberInput = React__namespace.forwardRef(
|
|
|
310
317
|
type: "text",
|
|
311
318
|
inputMode: "decimal",
|
|
312
319
|
role: "spinbutton",
|
|
313
|
-
className: "nexus-number-input__field",
|
|
320
|
+
className: chunkCZC76ZD5_js.cn("nexus-number-input__field", inputClassName),
|
|
314
321
|
value: internalValue,
|
|
315
322
|
disabled,
|
|
316
323
|
readOnly,
|
|
@@ -325,6 +332,7 @@ var NumberInput = React__namespace.forwardRef(
|
|
|
325
332
|
...props
|
|
326
333
|
}
|
|
327
334
|
),
|
|
335
|
+
suffixIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "nexus-number-input__icon nexus-number-input__icon--suffix", children: suffixIcon }),
|
|
328
336
|
!isBasic && showBtns && /* @__PURE__ */ jsxRuntime.jsx(
|
|
329
337
|
"button",
|
|
330
338
|
{
|