@rovula/ui 0.1.21 → 0.1.23
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/dist/cjs/bundle.css +204 -26
- package/dist/cjs/bundle.js +675 -675
- package/dist/cjs/bundle.js.map +1 -1
- package/dist/cjs/types/components/Badge/Badge.d.ts +40 -0
- package/dist/cjs/types/components/Badge/Badge.stories.d.ts +295 -0
- package/dist/cjs/types/components/Badge/Badge.styles.d.ts +7 -0
- package/dist/cjs/types/components/Badge/index.d.ts +2 -0
- package/dist/cjs/types/components/Dropdown/Dropdown.d.ts +4 -8
- package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +1 -6
- package/dist/cjs/types/components/DropdownMenu/DropdownMenu.d.ts +5 -1
- package/dist/cjs/types/components/DropdownMenu/DropdownMenu.stories.d.ts +16 -0
- package/dist/cjs/types/index.d.ts +3 -1
- package/dist/cjs/types/patterns/menu/Menu.d.ts +72 -0
- package/dist/cjs/types/{components/Menu → patterns/menu}/Menu.stories.d.ts +18 -10
- package/dist/cjs/types/utils/mergeRefs.d.ts +20 -0
- package/dist/components/ActionButton/ActionButton.styles.js +9 -1
- package/dist/components/Avatar/Avatar.styles.js +2 -2
- package/dist/components/Badge/Badge.js +36 -0
- package/dist/components/Badge/Badge.stories.js +51 -0
- package/dist/components/Badge/Badge.styles.js +62 -0
- package/dist/components/Badge/index.js +2 -0
- package/dist/components/Dropdown/Dropdown.js +54 -163
- package/dist/components/Dropdown/Dropdown.stories.js +29 -0
- package/dist/components/DropdownMenu/DropdownMenu.js +24 -11
- package/dist/components/DropdownMenu/DropdownMenu.stories.js +54 -10
- package/dist/components/TextInput/TextInput.js +9 -4
- package/dist/esm/bundle.css +204 -26
- package/dist/esm/bundle.js +1545 -1545
- package/dist/esm/bundle.js.map +1 -1
- package/dist/esm/types/components/Badge/Badge.d.ts +40 -0
- package/dist/esm/types/components/Badge/Badge.stories.d.ts +295 -0
- package/dist/esm/types/components/Badge/Badge.styles.d.ts +7 -0
- package/dist/esm/types/components/Badge/index.d.ts +2 -0
- package/dist/esm/types/components/Dropdown/Dropdown.d.ts +4 -8
- package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +1 -6
- package/dist/esm/types/components/DropdownMenu/DropdownMenu.d.ts +5 -1
- package/dist/esm/types/components/DropdownMenu/DropdownMenu.stories.d.ts +16 -0
- package/dist/esm/types/index.d.ts +3 -1
- package/dist/esm/types/patterns/menu/Menu.d.ts +72 -0
- package/dist/esm/types/{components/Menu → patterns/menu}/Menu.stories.d.ts +18 -10
- package/dist/esm/types/utils/mergeRefs.d.ts +20 -0
- package/dist/index.d.ts +118 -73
- package/dist/index.js +2 -1
- package/dist/patterns/menu/Menu.js +95 -0
- package/dist/patterns/menu/Menu.stories.js +611 -0
- package/dist/src/theme/global.css +393 -43
- package/dist/utils/mergeRefs.js +42 -0
- package/package.json +1 -1
- package/src/components/ActionButton/ActionButton.styles.ts +9 -1
- package/src/components/Avatar/Avatar.styles.ts +2 -2
- package/src/components/Badge/Badge.stories.tsx +128 -0
- package/src/components/Badge/Badge.styles.ts +70 -0
- package/src/components/Badge/Badge.tsx +103 -0
- package/src/components/Badge/index.ts +3 -0
- package/src/components/Dropdown/Dropdown.stories.tsx +170 -1
- package/src/components/Dropdown/Dropdown.tsx +186 -276
- package/src/components/DropdownMenu/DropdownMenu.stories.tsx +1050 -113
- package/src/components/DropdownMenu/DropdownMenu.tsx +117 -56
- package/src/components/TextInput/TextInput.tsx +42 -32
- package/src/index.ts +3 -1
- package/src/patterns/menu/Menu.stories.tsx +1100 -0
- package/src/patterns/menu/Menu.tsx +286 -0
- package/src/theme/presets/colors.js +14 -0
- package/src/theme/themes/variable-mapping.css +30 -0
- package/src/theme/themes/variable.css +37 -6
- package/src/theme/themes/xspector/baseline.css +0 -1
- package/src/theme/tokens/baseline.css +2 -1
- package/src/theme/tokens/components/badge.css +54 -0
- package/src/theme/tokens/components/dropdown-menu.css +15 -4
- package/src/utils/mergeRefs.ts +46 -0
- package/dist/cjs/types/components/Menu/Menu.d.ts +0 -65
- package/dist/cjs/types/components/Menu/helpers.d.ts +0 -19
- package/dist/cjs/types/components/Menu/index.d.ts +0 -4
- package/dist/components/Menu/Menu.js +0 -64
- package/dist/components/Menu/Menu.stories.js +0 -406
- package/dist/components/Menu/helpers.js +0 -28
- package/dist/components/Menu/index.js +0 -3
- package/dist/esm/types/components/Menu/Menu.d.ts +0 -65
- package/dist/esm/types/components/Menu/helpers.d.ts +0 -19
- package/dist/esm/types/components/Menu/index.d.ts +0 -4
- package/src/components/Menu/Menu.stories.tsx +0 -586
- package/src/components/Menu/Menu.tsx +0 -235
- package/src/components/Menu/helpers.ts +0 -45
- package/src/components/Menu/index.ts +0 -7
- package/src/theme/themes/xspector/components/dropdown-menu.css +0 -28
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { ReactNode } from "react";
|
|
4
|
+
import { cn } from "@/utils/cn";
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu as DropdownMenuRoot,
|
|
7
|
+
DropdownMenuTrigger,
|
|
8
|
+
DropdownMenuContent,
|
|
9
|
+
DropdownMenuItem,
|
|
10
|
+
DropdownMenuCheckboxItem,
|
|
11
|
+
DropdownMenuRadioItem,
|
|
12
|
+
DropdownMenuRadioGroup,
|
|
13
|
+
DropdownMenuSeparator,
|
|
14
|
+
DropdownMenuLabel,
|
|
15
|
+
DropdownMenuSub,
|
|
16
|
+
DropdownMenuSubTrigger,
|
|
17
|
+
DropdownMenuSubContent,
|
|
18
|
+
} from "../../components/DropdownMenu/DropdownMenu";
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Types
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
export type MenuOption = {
|
|
25
|
+
value: string;
|
|
26
|
+
label: ReactNode;
|
|
27
|
+
/**
|
|
28
|
+
* Visual type of the item
|
|
29
|
+
* - "default": selected indicator (check icon on left)
|
|
30
|
+
* - "checkbox": visual checkbox
|
|
31
|
+
* - "radio": radio selection (single-select in group)
|
|
32
|
+
*/
|
|
33
|
+
type?: "default" | "checkbox" | "radio";
|
|
34
|
+
icon?: ReactNode;
|
|
35
|
+
disabled?: boolean;
|
|
36
|
+
danger?: boolean;
|
|
37
|
+
checked?: boolean;
|
|
38
|
+
onClick?: () => void;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type MenuItemType =
|
|
42
|
+
| { type: "item"; item: MenuOption }
|
|
43
|
+
| { type: "separator" }
|
|
44
|
+
| { type: "label"; label: string }
|
|
45
|
+
| { type: "custom"; render: () => ReactNode }
|
|
46
|
+
| {
|
|
47
|
+
type: "submenu";
|
|
48
|
+
label: string;
|
|
49
|
+
icon?: ReactNode;
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
/** Nested items — rendered via DropdownMenuSub */
|
|
52
|
+
items: MenuItemType[];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type MenuProps = {
|
|
56
|
+
/** Trigger element — optional, ถ้าไม่ส่งให้ใช้ open/onOpenChange ควบคุมเอง */
|
|
57
|
+
trigger?: ReactNode;
|
|
58
|
+
items: MenuItemType[];
|
|
59
|
+
selectedValues?: string[];
|
|
60
|
+
/**
|
|
61
|
+
* Callback on item select
|
|
62
|
+
* - "checkbox" → caller toggles in selectedValues
|
|
63
|
+
* - "radio" → caller sets single value in selectedValues
|
|
64
|
+
* - "default" → same
|
|
65
|
+
*/
|
|
66
|
+
onSelect?: (value: string, item: MenuOption) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Optional header rendered above the item list — stays fixed while list scrolls.
|
|
69
|
+
* ใช้สำหรับ pattern เช่น "Change Status" (title + close button)
|
|
70
|
+
* หรือ "Manage Column" (title + Hide all / Show all / Done)
|
|
71
|
+
*/
|
|
72
|
+
header?: ReactNode;
|
|
73
|
+
/** Controlled open state */
|
|
74
|
+
open?: boolean;
|
|
75
|
+
/** Called when open state changes (controlled or uncontrolled) */
|
|
76
|
+
onOpenChange?: (open: boolean) => void;
|
|
77
|
+
/** Alignment of the menu content relative to the trigger */
|
|
78
|
+
align?: "start" | "center" | "end";
|
|
79
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
80
|
+
sideOffset?: number;
|
|
81
|
+
contentClassName?: string;
|
|
82
|
+
/** data-testid attached to the menu content element */
|
|
83
|
+
testId?: string;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// renderMenuItems — recursive, used for both root and submenu levels
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
function renderMenuItems(
|
|
91
|
+
items: MenuItemType[],
|
|
92
|
+
selectedValues: string[],
|
|
93
|
+
onSelect?: MenuProps["onSelect"],
|
|
94
|
+
): ReactNode[] {
|
|
95
|
+
const result: ReactNode[] = [];
|
|
96
|
+
let radioBuffer: MenuOption[] = [];
|
|
97
|
+
|
|
98
|
+
const flushRadioBuffer = (key: string) => {
|
|
99
|
+
if (radioBuffer.length === 0) return;
|
|
100
|
+
const buffer = radioBuffer;
|
|
101
|
+
radioBuffer = [];
|
|
102
|
+
|
|
103
|
+
const radioValue =
|
|
104
|
+
buffer.find((o) => selectedValues.includes(o.value))?.value ?? "";
|
|
105
|
+
|
|
106
|
+
result.push(
|
|
107
|
+
<DropdownMenuRadioGroup
|
|
108
|
+
key={key}
|
|
109
|
+
value={radioValue}
|
|
110
|
+
onValueChange={(val: string) => {
|
|
111
|
+
const opt = buffer.find((o) => o.value === val);
|
|
112
|
+
if (opt) {
|
|
113
|
+
onSelect?.(val, opt);
|
|
114
|
+
opt.onClick?.();
|
|
115
|
+
}
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
{buffer.map((opt) => (
|
|
119
|
+
<DropdownMenuRadioItem
|
|
120
|
+
key={opt.value}
|
|
121
|
+
value={opt.value}
|
|
122
|
+
disabled={opt.disabled}
|
|
123
|
+
icon={opt.icon as React.ReactNode}
|
|
124
|
+
>
|
|
125
|
+
{opt.label}
|
|
126
|
+
</DropdownMenuRadioItem>
|
|
127
|
+
))}
|
|
128
|
+
</DropdownMenuRadioGroup>,
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
items.forEach((item, index) => {
|
|
133
|
+
// Accumulate consecutive radio items
|
|
134
|
+
if (item.type === "item" && item.item.type === "radio") {
|
|
135
|
+
radioBuffer.push(item.item);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Flush radio buffer before non-radio item
|
|
140
|
+
flushRadioBuffer(`radio-${index}`);
|
|
141
|
+
|
|
142
|
+
if (item.type === "separator") {
|
|
143
|
+
result.push(<DropdownMenuSeparator key={`sep-${index}`} />);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (item.type === "label") {
|
|
148
|
+
result.push(
|
|
149
|
+
<DropdownMenuLabel key={`lbl-${index}`}>{item.label}</DropdownMenuLabel>,
|
|
150
|
+
);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (item.type === "custom") {
|
|
155
|
+
result.push(
|
|
156
|
+
<React.Fragment key={`custom-${index}`}>{item.render()}</React.Fragment>,
|
|
157
|
+
);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (item.type === "submenu") {
|
|
162
|
+
result.push(
|
|
163
|
+
<DropdownMenuSub key={`sub-${index}`}>
|
|
164
|
+
<DropdownMenuSubTrigger disabled={item.disabled}>
|
|
165
|
+
{/* mirror DropdownMenuItem icon layout */}
|
|
166
|
+
<div className="flex shrink-0 flex-row gap-1">
|
|
167
|
+
<span className="size-4" />
|
|
168
|
+
{item.icon}
|
|
169
|
+
</div>
|
|
170
|
+
{item.label}
|
|
171
|
+
</DropdownMenuSubTrigger>
|
|
172
|
+
<DropdownMenuSubContent>
|
|
173
|
+
{renderMenuItems(item.items, selectedValues, onSelect)}
|
|
174
|
+
</DropdownMenuSubContent>
|
|
175
|
+
</DropdownMenuSub>,
|
|
176
|
+
);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (item.type === "item") {
|
|
181
|
+
const opt = item.item;
|
|
182
|
+
const isSelected = opt.checked ?? selectedValues.includes(opt.value);
|
|
183
|
+
|
|
184
|
+
if (opt.type === "checkbox") {
|
|
185
|
+
result.push(
|
|
186
|
+
<DropdownMenuCheckboxItem
|
|
187
|
+
key={opt.value}
|
|
188
|
+
checked={isSelected}
|
|
189
|
+
disabled={opt.disabled}
|
|
190
|
+
onCheckedChange={() => {
|
|
191
|
+
onSelect?.(opt.value, opt);
|
|
192
|
+
opt.onClick?.();
|
|
193
|
+
}}
|
|
194
|
+
>
|
|
195
|
+
{opt.label}
|
|
196
|
+
</DropdownMenuCheckboxItem>,
|
|
197
|
+
);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// default item
|
|
202
|
+
result.push(
|
|
203
|
+
<DropdownMenuItem
|
|
204
|
+
key={opt.value}
|
|
205
|
+
selected={isSelected}
|
|
206
|
+
icon={opt.icon as React.ReactNode}
|
|
207
|
+
disabled={opt.disabled}
|
|
208
|
+
className={cn(opt.danger && "text-red-500")}
|
|
209
|
+
onSelect={() => {
|
|
210
|
+
onSelect?.(opt.value, opt);
|
|
211
|
+
opt.onClick?.();
|
|
212
|
+
}}
|
|
213
|
+
>
|
|
214
|
+
{opt.label}
|
|
215
|
+
</DropdownMenuItem>,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
flushRadioBuffer("radio-end");
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
// Menu — top-level pattern component built on DropdownMenu
|
|
226
|
+
//
|
|
227
|
+
// ใช้งาน:
|
|
228
|
+
// <Menu trigger={<Button>Open</Button>} items={[...]} onSelect={...} />
|
|
229
|
+
//
|
|
230
|
+
// ได้ของฟรีจาก DropdownMenu:
|
|
231
|
+
// - Radix positioning (flip, scroll, sideOffset)
|
|
232
|
+
// - Portal (z-index, escape stacking context)
|
|
233
|
+
// - Escape to close, click-outside to close
|
|
234
|
+
// - Keyboard navigation (Arrow, Enter, Escape)
|
|
235
|
+
// - Full a11y / WAI-ARIA
|
|
236
|
+
// - Sub-menu support via DropdownMenuSub
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
export const Menu = ({
|
|
240
|
+
trigger,
|
|
241
|
+
items,
|
|
242
|
+
selectedValues = [],
|
|
243
|
+
onSelect,
|
|
244
|
+
header,
|
|
245
|
+
open,
|
|
246
|
+
onOpenChange,
|
|
247
|
+
align = "start",
|
|
248
|
+
side = "bottom",
|
|
249
|
+
sideOffset = 4,
|
|
250
|
+
contentClassName,
|
|
251
|
+
testId,
|
|
252
|
+
}: MenuProps) => (
|
|
253
|
+
<DropdownMenuRoot open={open} onOpenChange={onOpenChange}>
|
|
254
|
+
{trigger && (
|
|
255
|
+
<DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
|
|
256
|
+
)}
|
|
257
|
+
<DropdownMenuContent
|
|
258
|
+
align={align}
|
|
259
|
+
side={side}
|
|
260
|
+
sideOffset={sideOffset}
|
|
261
|
+
className={contentClassName}
|
|
262
|
+
data-testid={testId}
|
|
263
|
+
>
|
|
264
|
+
{header && (
|
|
265
|
+
<div className="sticky top-0 z-10 bg-modal-surface border-b border-[var(--dropdown-menu-seperator-bg)]">
|
|
266
|
+
{header}
|
|
267
|
+
</div>
|
|
268
|
+
)}
|
|
269
|
+
{renderMenuItems(items, selectedValues, onSelect)}
|
|
270
|
+
</DropdownMenuContent>
|
|
271
|
+
</DropdownMenuRoot>
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
Menu.displayName = "Menu";
|
|
275
|
+
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// Re-exports — backward compat for consumers using Menu's sub-components
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
export {
|
|
281
|
+
DropdownMenuItem as MenuItem,
|
|
282
|
+
DropdownMenuSeparator as MenuSeparator,
|
|
283
|
+
DropdownMenuLabel as MenuLabel,
|
|
284
|
+
} from "../../components/DropdownMenu/DropdownMenu";
|
|
285
|
+
|
|
286
|
+
export default Menu;
|
|
@@ -159,6 +159,20 @@ module.exports = {
|
|
|
159
159
|
"bg-stroke4": withColorMixin("--bg-stroke4"),
|
|
160
160
|
"bg-stroke5": withColorMixin("--bg-stroke5"),
|
|
161
161
|
|
|
162
|
+
// Table
|
|
163
|
+
"table-bg-main": withColorMixin("--table-bg-main"),
|
|
164
|
+
"table-bg-line": withColorMixin("--table-bg-line"),
|
|
165
|
+
"table-bg-a": withColorMixin("--table-bg-a"),
|
|
166
|
+
"table-bg-b": withColorMixin("--table-bg-b"),
|
|
167
|
+
"table-bg-hover": withColorMixin("--table-bg-hover"),
|
|
168
|
+
"table-panel-hover": withColorMixin("--table-panel-hover"),
|
|
169
|
+
"table-panel-sub-line": withColorMixin("--table-panel-sub-line"),
|
|
170
|
+
"table-panel-main-line": withColorMixin("--table-panel-main-line"),
|
|
171
|
+
"table-panel-selected": withColorMixin("--table-panel-selected"),
|
|
172
|
+
|
|
173
|
+
// Modal dropdown surface
|
|
174
|
+
"modal-dropdown-surface": withColorMixin("--modal-dropdown-surface"),
|
|
175
|
+
|
|
162
176
|
// Common
|
|
163
177
|
"common-white": withColorMixin("--common-white"),
|
|
164
178
|
"common-black": withColorMixin("--common-black"),
|
|
@@ -353,6 +353,16 @@
|
|
|
353
353
|
--bg-stroke3: var(--bg-stroke3-report-xspector-light-mode);
|
|
354
354
|
--bg-stroke4: var(--bg-stroke4-report-xspector-light-mode);
|
|
355
355
|
--bg-stroke5: var(--bg-stroke5-report-xspector-light-mode);
|
|
356
|
+
--table-bg-main: var(--table-bg-main-report-xspector-light-mode);
|
|
357
|
+
--table-bg-line: var(--table-bg-line-report-xspector-light-mode);
|
|
358
|
+
--table-bg-a: var(--table-bg-a-report-xspector-light-mode);
|
|
359
|
+
--table-bg-b: var(--table-bg-b-report-xspector-light-mode);
|
|
360
|
+
--table-bg-hover: var(--table-bg-hover-report-xspector-light-mode);
|
|
361
|
+
--table-panel-hover: var(--table-panel-hover-report-xspector-light-mode);
|
|
362
|
+
--table-panel-sub-line: var(--table-panel-sub-line-report-xspector-light-mode);
|
|
363
|
+
--table-panel-main-line: var(--table-panel-main-line-report-xspector-light-mode);
|
|
364
|
+
--table-panel-selected: var(--table-panel-selected-report-xspector-light-mode);
|
|
365
|
+
--modal-dropdown-surface: var(--modal-dropdown-surface-report-xspector-light-mode);
|
|
356
366
|
}
|
|
357
367
|
|
|
358
368
|
:root[data-theme="xspector"] {
|
|
@@ -707,6 +717,16 @@
|
|
|
707
717
|
--bg-stroke3: var(--bg-stroke3-xspector);
|
|
708
718
|
--bg-stroke4: var(--bg-stroke4-xspector);
|
|
709
719
|
--bg-stroke5: var(--bg-stroke5-xspector);
|
|
720
|
+
--table-bg-main: var(--table-bg-main-xspector);
|
|
721
|
+
--table-bg-line: var(--table-bg-line-xspector);
|
|
722
|
+
--table-bg-a: var(--table-bg-a-xspector);
|
|
723
|
+
--table-bg-b: var(--table-bg-b-xspector);
|
|
724
|
+
--table-bg-hover: var(--table-bg-hover-xspector);
|
|
725
|
+
--table-panel-hover: var(--table-panel-hover-xspector);
|
|
726
|
+
--table-panel-sub-line: var(--table-panel-sub-line-xspector);
|
|
727
|
+
--table-panel-main-line: var(--table-panel-main-line-xspector);
|
|
728
|
+
--table-panel-selected: var(--table-panel-selected-xspector);
|
|
729
|
+
--modal-dropdown-surface: var(--modal-dropdown-surface-xspector);
|
|
710
730
|
}
|
|
711
731
|
|
|
712
732
|
:root[data-theme="skyller"] {
|
|
@@ -1061,4 +1081,14 @@
|
|
|
1061
1081
|
--bg-stroke3: var(--bg-stroke3-skyller);
|
|
1062
1082
|
--bg-stroke4: var(--bg-stroke4-skyller);
|
|
1063
1083
|
--bg-stroke5: var(--bg-stroke5-skyller);
|
|
1084
|
+
--table-bg-main: var(--table-bg-main-skyller);
|
|
1085
|
+
--table-bg-line: var(--table-bg-line-skyller);
|
|
1086
|
+
--table-bg-a: var(--table-bg-a-skyller);
|
|
1087
|
+
--table-bg-b: var(--table-bg-b-skyller);
|
|
1088
|
+
--table-bg-hover: var(--table-bg-hover-skyller);
|
|
1089
|
+
--table-panel-hover: var(--table-panel-hover-skyller);
|
|
1090
|
+
--table-panel-sub-line: var(--table-panel-sub-line-skyller);
|
|
1091
|
+
--table-panel-main-line: var(--table-panel-main-line-skyller);
|
|
1092
|
+
--table-panel-selected: var(--table-panel-selected-skyller);
|
|
1093
|
+
--modal-dropdown-surface: var(--modal-dropdown-surface-skyller);
|
|
1064
1094
|
}
|
|
@@ -1020,13 +1020,13 @@
|
|
|
1020
1020
|
--page-bg-main-xspector: #091d33;
|
|
1021
1021
|
--page-bg-main-report-xspector-light-mode: #ffffff;
|
|
1022
1022
|
--page-bg-main-skyller: #ffffff;
|
|
1023
|
-
--page-bg-circle-top-g-in-xspector: #
|
|
1023
|
+
--page-bg-circle-top-g-in-xspector: #042c57;
|
|
1024
1024
|
--page-bg-circle-top-g-in-report-xspector-light-mode: #adcad6;
|
|
1025
1025
|
--page-bg-circle-top-g-in-skyller: #cfe4fd;
|
|
1026
1026
|
--page-bg-circle-top-g-out-xspector: rgba(9 29 51 / 0);
|
|
1027
1027
|
--page-bg-circle-top-g-out-report-xspector-light-mode: rgba(255 255 255 / 0);
|
|
1028
1028
|
--page-bg-circle-top-g-out-skyller: rgba(255 255 255 / 0);
|
|
1029
|
-
--page-bg-circle-bottom-g-in-xspector: #
|
|
1029
|
+
--page-bg-circle-bottom-g-in-xspector: #042c57;
|
|
1030
1030
|
--page-bg-circle-bottom-g-in-report-xspector-light-mode: #adcad6;
|
|
1031
1031
|
--page-bg-circle-bottom-g-in-skyller: #bed0f9;
|
|
1032
1032
|
--page-bg-circle-bottom-g-out-xspector: rgba(9 29 51 / 0);
|
|
@@ -1035,7 +1035,7 @@
|
|
|
1035
1035
|
--text-white-xspector: #ffffff;
|
|
1036
1036
|
--text-white-report-xspector-light-mode: #ffffff;
|
|
1037
1037
|
--text-white-skyller: #ffffff;
|
|
1038
|
-
--modal-line-xspector:
|
|
1038
|
+
--modal-line-xspector: #4a4a4a;
|
|
1039
1039
|
--modal-line-report-xspector-light-mode: #cfcfcf;
|
|
1040
1040
|
--modal-line-skyller: #d4d4d4;
|
|
1041
1041
|
--bg-bg4-xspector: #0f2a46;
|
|
@@ -1044,15 +1044,46 @@
|
|
|
1044
1044
|
--bg-bg5-xspector: #000000;
|
|
1045
1045
|
--bg-bg5-report-xspector-light-mode: #000000;
|
|
1046
1046
|
--bg-bg5-skyller: #000000;
|
|
1047
|
-
--bg-stroke3-xspector: #
|
|
1048
|
-
--bg-stroke3-report-xspector-light-mode: #
|
|
1049
|
-
--bg-stroke3-skyller: #
|
|
1047
|
+
--bg-stroke3-xspector: #2d2e30;
|
|
1048
|
+
--bg-stroke3-report-xspector-light-mode: #e5e5e5;
|
|
1049
|
+
--bg-stroke3-skyller: #d4d4d4;
|
|
1050
1050
|
--bg-stroke4-xspector: #000000;
|
|
1051
1051
|
--bg-stroke4-report-xspector-light-mode: #000000;
|
|
1052
1052
|
--bg-stroke4-skyller: #000000;
|
|
1053
1053
|
--bg-stroke5-xspector: #000000;
|
|
1054
1054
|
--bg-stroke5-report-xspector-light-mode: #000000;
|
|
1055
1055
|
--bg-stroke5-skyller: #000000;
|
|
1056
|
+
--table-bg-main-xspector: #091a2a;
|
|
1057
|
+
--table-bg-main-report-xspector-light-mode: #e5e5e5;
|
|
1058
|
+
--table-bg-main-skyller: #f5f5f5;
|
|
1059
|
+
--table-bg-line-xspector: #1c3955;
|
|
1060
|
+
--table-bg-line-report-xspector-light-mode: #d2d2d2;
|
|
1061
|
+
--table-bg-line-skyller: #ececec;
|
|
1062
|
+
--table-bg-a-xspector: #0c2845;
|
|
1063
|
+
--table-bg-a-report-xspector-light-mode: #f3f3f3;
|
|
1064
|
+
--table-bg-a-skyller: #fdfdfd;
|
|
1065
|
+
--table-bg-b-xspector: #0f2f50;
|
|
1066
|
+
--table-bg-b-report-xspector-light-mode: #f3f3f3;
|
|
1067
|
+
--table-bg-b-skyller: #f1f1f1;
|
|
1068
|
+
--table-panel-hover-xspector: #343638;
|
|
1069
|
+
--table-panel-hover-report-xspector-light-mode: #d4d4d4;
|
|
1070
|
+
--table-panel-hover-skyller: #eff6fe;
|
|
1071
|
+
--table-bg-hover-xspector: #103861;
|
|
1072
|
+
--table-bg-hover-report-xspector-light-mode: #ffffff;
|
|
1073
|
+
--table-bg-hover-skyller: #eff6fe;
|
|
1074
|
+
--table-panel-sub-line-xspector: #393b3f;
|
|
1075
|
+
--table-panel-sub-line-report-xspector-light-mode: #d4d4d4;
|
|
1076
|
+
--table-panel-sub-line-skyller: #ececec;
|
|
1077
|
+
--table-panel-main-line-xspector: #606b77;
|
|
1078
|
+
--table-panel-main-line-report-xspector-light-mode: #d4d4d4;
|
|
1079
|
+
--table-panel-main-line-skyller: #d4d4d4;
|
|
1080
|
+
--table-panel-selected-xspector: #6f6700;
|
|
1081
|
+
--table-panel-selected-report-xspector-light-mode: #d4d4d4;
|
|
1082
|
+
--table-panel-selected-skyller: #bfdbfd;
|
|
1083
|
+
--modal-dropdown-surface-xspector: #252628;
|
|
1084
|
+
--modal-dropdown-surface-report-xspector-light-mode: #ffffff;
|
|
1085
|
+
--modal-dropdown-surface-skyller: #ffffff;
|
|
1086
|
+
--modal-dropdown-surface-skyller: #ffffff;
|
|
1056
1087
|
|
|
1057
1088
|
/* BUTTON RADIUS */
|
|
1058
1089
|
--button-l-round: 8px;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
/* --------------------------------------------------------------------------------- */
|
|
3
|
+
/* Badge Component Tokens */
|
|
4
|
+
/* --------------------------------------------------------------------------------- */
|
|
5
|
+
/* Naming Convention: --badge-[color]-[property] */
|
|
6
|
+
/* Colors: [default, success, warning, info, error] */
|
|
7
|
+
/* Severity: [highest, high, medium, low, lowest] */
|
|
8
|
+
/* --------------------------------------------------------------------------------- */
|
|
9
|
+
|
|
10
|
+
/* ------------------------------------------------------------------ */
|
|
11
|
+
/* Status Badge — Default (grey/white) */
|
|
12
|
+
/* ------------------------------------------------------------------ */
|
|
13
|
+
--badge-default-bg: rgba(145, 158, 171, 0.12);
|
|
14
|
+
--badge-default-border: rgba(158, 158, 158, 0.48);
|
|
15
|
+
--badge-default-text: var(--text-contrast-max, white);
|
|
16
|
+
|
|
17
|
+
/* ------------------------------------------------------------------ */
|
|
18
|
+
/* Status Badge — Success (green) */
|
|
19
|
+
/* ------------------------------------------------------------------ */
|
|
20
|
+
--badge-success-bg: rgba(84, 214, 44, 0.12);
|
|
21
|
+
--badge-success-border: rgba(84, 214, 44, 0.48);
|
|
22
|
+
--badge-success-text: var(--state-success-default);
|
|
23
|
+
|
|
24
|
+
/* ------------------------------------------------------------------ */
|
|
25
|
+
/* Status Badge — Warning (yellow) */
|
|
26
|
+
/* ------------------------------------------------------------------ */
|
|
27
|
+
--badge-warning-bg: rgba(255, 193, 7, 0.12);
|
|
28
|
+
--badge-warning-border: rgba(255, 193, 7, 0.48);
|
|
29
|
+
--badge-warning-text: var(--state-warning-default);
|
|
30
|
+
|
|
31
|
+
/* ------------------------------------------------------------------ */
|
|
32
|
+
/* Status Badge — Info (blue) */
|
|
33
|
+
/* ------------------------------------------------------------------ */
|
|
34
|
+
--badge-info-bg: rgba(41, 152, 255, 0.12);
|
|
35
|
+
--badge-info-border: rgba(41, 152, 255, 0.48);
|
|
36
|
+
--badge-info-text: var(--state-info-default);
|
|
37
|
+
|
|
38
|
+
/* ------------------------------------------------------------------ */
|
|
39
|
+
/* Status Badge — Error (red) */
|
|
40
|
+
/* ------------------------------------------------------------------ */
|
|
41
|
+
--badge-error-bg: rgba(255, 72, 66, 0.12);
|
|
42
|
+
--badge-error-border: rgba(255, 72, 66, 0.48);
|
|
43
|
+
--badge-error-text: var(--state-error-default);
|
|
44
|
+
|
|
45
|
+
/* ------------------------------------------------------------------ */
|
|
46
|
+
/* Severity Badge — solid pill */
|
|
47
|
+
/* ------------------------------------------------------------------ */
|
|
48
|
+
--badge-severity-text: var(--text-white, white);
|
|
49
|
+
--badge-severity-highest-bg: var(--state-error-default);
|
|
50
|
+
--badge-severity-high-bg: var(--ramps-warning-800);
|
|
51
|
+
--badge-severity-medium-bg: var(--state-warning-default);
|
|
52
|
+
--badge-severity-low-bg: var(--state-success-default);
|
|
53
|
+
--badge-severity-lowest-bg: var(--state-info-default);
|
|
54
|
+
}
|
|
@@ -6,22 +6,33 @@
|
|
|
6
6
|
/* Element: [default, hover, selected, disabled] */
|
|
7
7
|
/* ------------------------------------------------------------------ */
|
|
8
8
|
|
|
9
|
-
--dropdown-menu-seperator-bg: var(--
|
|
10
|
-
--dropdown-menu-shadow: 0px
|
|
9
|
+
--dropdown-menu-seperator-bg: var(--modal-line);
|
|
10
|
+
--dropdown-menu-shadow: 0px 12px 24px -4px rgba(0, 0, 0, 0.12);
|
|
11
11
|
|
|
12
12
|
/* Default State */
|
|
13
13
|
--dropdown-menu-default-bg: transparent;
|
|
14
14
|
--dropdown-menu-default-text: var(--text-g-contrast-high);
|
|
15
15
|
|
|
16
16
|
/* Hover State */
|
|
17
|
-
--dropdown-menu-hover-bg: var(--
|
|
17
|
+
--dropdown-menu-hover-bg: var(--modal-highlight);
|
|
18
18
|
--dropdown-menu-hover-text: var(--text-g-contrast-high);
|
|
19
19
|
|
|
20
20
|
/* Selected State */
|
|
21
|
-
--dropdown-menu-selected-bg:
|
|
21
|
+
--dropdown-menu-selected-bg: var(--modal-highlight);
|
|
22
22
|
--dropdown-menu-selected-text: var(--text-g-contrast-high);
|
|
23
23
|
|
|
24
24
|
/* Disabled State */
|
|
25
25
|
--dropdown-menu-disabled-bg: transparent;
|
|
26
26
|
--dropdown-menu-disabled-text: var(--state-disable-solid);
|
|
27
|
+
|
|
28
|
+
/* Checkbox Item */
|
|
29
|
+
--dropdown-menu-checkbox-border: var(--function-default-solid);
|
|
30
|
+
--dropdown-menu-checkbox-checked-bg: var(--function-active-solid);
|
|
31
|
+
--dropdown-menu-checkbox-checked-icon: var(--function-active-icon);
|
|
32
|
+
--dropdown-menu-checkbox-disabled-border: var(--state-disable-outline);
|
|
33
|
+
--dropdown-menu-checkbox-disabled-checked-bg: var(--state-disable-solid);
|
|
34
|
+
|
|
35
|
+
/* Radio Item */
|
|
36
|
+
--dropdown-menu-radio-disabled-text: var(--state-disable-solid);
|
|
37
|
+
--dropdown-menu-radio-selected-disabled-text: var(--state-disable-outline);
|
|
27
38
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, { useCallback, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Merges multiple refs into a single callback ref.
|
|
5
|
+
* NOTE: This creates a new function on every call — do NOT use inline in render.
|
|
6
|
+
* Use `useStableMergedRef` instead when you need a stable ref identity.
|
|
7
|
+
*/
|
|
8
|
+
export function mergeRefs<T>(
|
|
9
|
+
...refs: (React.Ref<T> | undefined | null)[]
|
|
10
|
+
): React.RefCallback<T> {
|
|
11
|
+
return (node) => {
|
|
12
|
+
refs.forEach((ref) => {
|
|
13
|
+
if (!ref) return;
|
|
14
|
+
if (typeof ref === "function") {
|
|
15
|
+
ref(node);
|
|
16
|
+
} else {
|
|
17
|
+
(ref as React.MutableRefObject<T | null>).current = node;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns a **stable** callback ref (never changes identity) that forwards the
|
|
25
|
+
* node to all given refs. Safe to use inline in JSX — will not cause
|
|
26
|
+
* detach/re-attach loops in libraries like Headless UI that watch refs.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const MyInput = forwardRef((props, ref) => {
|
|
30
|
+
* const internalRef = useRef(null);
|
|
31
|
+
* const stableRef = useStableMergedRef(ref, internalRef);
|
|
32
|
+
* return <input ref={stableRef} />;
|
|
33
|
+
* });
|
|
34
|
+
*/
|
|
35
|
+
export function useStableMergedRef<T>(
|
|
36
|
+
...refs: (React.Ref<T> | undefined | null)[]
|
|
37
|
+
): React.RefCallback<T> {
|
|
38
|
+
// Store the latest merge logic in a ref so the callback never goes stale,
|
|
39
|
+
// while the callback itself keeps a stable identity (empty useCallback deps).
|
|
40
|
+
const latestImpl = useRef<React.RefCallback<T>>();
|
|
41
|
+
latestImpl.current = mergeRefs(...refs);
|
|
42
|
+
|
|
43
|
+
return useCallback((node: T | null) => {
|
|
44
|
+
latestImpl.current?.(node);
|
|
45
|
+
}, []); // ← empty deps = identity never changes = no detach/re-attach loop
|
|
46
|
+
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import React, { CSSProperties, ReactNode } from "react";
|
|
2
|
-
export type MenuOption = {
|
|
3
|
-
value: string;
|
|
4
|
-
label: ReactNode;
|
|
5
|
-
/**
|
|
6
|
-
* Visual type - กำหนดว่าจะแสดง icon อะไร
|
|
7
|
-
* - "default": ไม่มี icon (แค่ highlight background)
|
|
8
|
-
* - "checkbox": แสดง ✓ icon
|
|
9
|
-
* - "radio": แสดง ● icon
|
|
10
|
-
*/
|
|
11
|
-
type?: "default" | "checkbox" | "radio";
|
|
12
|
-
icon?: ReactNode;
|
|
13
|
-
disabled?: boolean;
|
|
14
|
-
danger?: boolean;
|
|
15
|
-
checked?: boolean;
|
|
16
|
-
onClick?: () => void;
|
|
17
|
-
};
|
|
18
|
-
export type MenuItemType = {
|
|
19
|
-
type: "item";
|
|
20
|
-
item: MenuOption;
|
|
21
|
-
} | {
|
|
22
|
-
type: "separator";
|
|
23
|
-
} | {
|
|
24
|
-
type: "label";
|
|
25
|
-
label: string;
|
|
26
|
-
} | {
|
|
27
|
-
type: "custom";
|
|
28
|
-
render: () => ReactNode;
|
|
29
|
-
};
|
|
30
|
-
export type MenuProps = {
|
|
31
|
-
items: MenuItemType[];
|
|
32
|
-
/**
|
|
33
|
-
* Selected values - ใช้กับ type="item"
|
|
34
|
-
*/
|
|
35
|
-
selectedValues?: string[];
|
|
36
|
-
/**
|
|
37
|
-
* Callback เมื่อเลือก item
|
|
38
|
-
* - ถ้า item.type="checkbox" → toggle checked state
|
|
39
|
-
* - ถ้า item.type="radio" → single select (clear others)
|
|
40
|
-
* - ถ้า item.type="default" หรือไม่ระบุ → ตาม selectedValues
|
|
41
|
-
*/
|
|
42
|
-
onSelect?: (value: string, item: MenuOption) => void;
|
|
43
|
-
className?: string;
|
|
44
|
-
style?: CSSProperties;
|
|
45
|
-
isAbove?: boolean;
|
|
46
|
-
};
|
|
47
|
-
export declare const Menu: React.ForwardRefExoticComponent<MenuProps & React.RefAttributes<HTMLDivElement>>;
|
|
48
|
-
type MenuItemProps = {
|
|
49
|
-
option: MenuOption;
|
|
50
|
-
visualType: "default" | "checkbox" | "radio";
|
|
51
|
-
isChecked: boolean;
|
|
52
|
-
onSelect: () => void;
|
|
53
|
-
className?: string;
|
|
54
|
-
};
|
|
55
|
-
export declare const MenuItem: React.ForwardRefExoticComponent<MenuItemProps & React.RefAttributes<HTMLDivElement>>;
|
|
56
|
-
type MenuSeparatorProps = {
|
|
57
|
-
className?: string;
|
|
58
|
-
};
|
|
59
|
-
export declare const MenuSeparator: React.ForwardRefExoticComponent<MenuSeparatorProps & React.RefAttributes<HTMLDivElement>>;
|
|
60
|
-
type MenuLabelProps = {
|
|
61
|
-
children: ReactNode;
|
|
62
|
-
className?: string;
|
|
63
|
-
};
|
|
64
|
-
export declare const MenuLabel: React.ForwardRefExoticComponent<MenuLabelProps & React.RefAttributes<HTMLDivElement>>;
|
|
65
|
-
export default Menu;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { MenuItemType } from "./Menu";
|
|
2
|
-
/**
|
|
3
|
-
* Helper function to convert simple options to MenuItemType
|
|
4
|
-
* Useful for integrating with Dropdown component
|
|
5
|
-
*/
|
|
6
|
-
export declare function optionsToMenuItems(options: Array<{
|
|
7
|
-
value: string;
|
|
8
|
-
label: string | React.ReactNode;
|
|
9
|
-
disabled?: boolean;
|
|
10
|
-
renderLabel?: any;
|
|
11
|
-
}>): MenuItemType[];
|
|
12
|
-
/**
|
|
13
|
-
* Helper to add separator between menu items
|
|
14
|
-
*/
|
|
15
|
-
export declare function withSeparator(items: MenuItemType[], atIndex: number): MenuItemType[];
|
|
16
|
-
/**
|
|
17
|
-
* Helper to add label/header to menu items
|
|
18
|
-
*/
|
|
19
|
-
export declare function withLabel(label: string, items: MenuItemType[]): MenuItemType[];
|