@inversestudio/neptune-components 1.0.0
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 +2 -0
- package/components/data-display/AppPreview.jsx +150 -0
- package/components/data-display/DataTable.jsx +65 -0
- package/components/data-display/FileTree.jsx +123 -0
- package/components/data-display/KpiCard.jsx +57 -0
- package/components/data-display/VersionRow.jsx +103 -0
- package/components/feedback/Avatar.jsx +28 -0
- package/components/feedback/Badge.jsx +32 -0
- package/components/feedback/ChatMessage.jsx +42 -0
- package/components/feedback/StatusDot.jsx +55 -0
- package/components/feedback/StatusIndicator.jsx +40 -0
- package/components/inputs/Button.jsx +48 -0
- package/components/inputs/Checkbox.jsx +90 -0
- package/components/inputs/FilterBar.jsx +64 -0
- package/components/inputs/IconButton.jsx +43 -0
- package/components/inputs/IconToggle.jsx +44 -0
- package/components/inputs/NaiaChatInput.jsx +173 -0
- package/components/inputs/NaiaSendButton.jsx +36 -0
- package/components/inputs/PillSelect.jsx +175 -0
- package/components/inputs/PropertyField.jsx +58 -0
- package/components/inputs/SuggestionPill.jsx +28 -0
- package/components/inputs/TextInput.jsx +96 -0
- package/components/inputs/Toggle.jsx +73 -0
- package/components/layout/AppHeader.jsx +56 -0
- package/components/layout/BottomBar.jsx +81 -0
- package/components/layout/Card.jsx +57 -0
- package/components/layout/Panel.jsx +26 -0
- package/components/layout/Toolbar.jsx +89 -0
- package/components/navigation/Breadcrumb.jsx +43 -0
- package/components/navigation/Dropdown.jsx +104 -0
- package/components/navigation/SidebarNav.jsx +82 -0
- package/components/navigation/SidebarTabs.jsx +99 -0
- package/components/navigation/TabBar.jsx +61 -0
- package/components/overlays/Modal.jsx +101 -0
- package/components/shared/index.jsx +112 -0
- package/index.css +3 -0
- package/index.js +50 -0
- package/neptune-components.css +1771 -0
- package/package.json +45 -0
- package/registry.json +1215 -0
- package/tokens/neptune-design-tokens.css +730 -0
- package/tokens/neptune-design-tokens.json +191 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { useState, useRef, useEffect, cloneElement, isValidElement } from "react";
|
|
2
|
+
import { ChevronDown, Check } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* PillSelect — Pill-shaped dropdown select for compact option picking
|
|
6
|
+
*
|
|
7
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
8
|
+
*
|
|
9
|
+
* Renders a small pill trigger that opens an upward dropdown menu. Used in
|
|
10
|
+
* the Naia chat input footer for mode, model, and autonomy selectors.
|
|
11
|
+
* Options can carry custom colors (for mode pill), descriptions, badges,
|
|
12
|
+
* and icons. Theme-aware via --npt- tokens.
|
|
13
|
+
*
|
|
14
|
+
* @component
|
|
15
|
+
* @param {Object} props
|
|
16
|
+
* @param {string} props.value - Currently selected option id
|
|
17
|
+
* @param {Array<{id: string, label: string, desc?: string, badge?: string, icon?: React.ReactNode, color?: string, colorBg?: string, colorText?: string}>} props.options - Selectable options
|
|
18
|
+
* @param {Function} props.onChange - Called with selected option id
|
|
19
|
+
* @param {React.ReactNode} [props.icon] - Optional icon rendered before the label in the trigger
|
|
20
|
+
* @param {"left"|"right"} [props.align="left"] - Dropdown horizontal alignment
|
|
21
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
22
|
+
* @param {React.CSSProperties} [props.style] - Additional inline styles
|
|
23
|
+
* @returns {JSX.Element} PillSelect component
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* <PillSelect
|
|
27
|
+
* value="code"
|
|
28
|
+
* options={[
|
|
29
|
+
* { id: "code", label: "Code", colorBg: "var(--npt-accent-secondary)", colorText: "var(--npt-accent-secondary)" },
|
|
30
|
+
* { id: "plan", label: "Plan" },
|
|
31
|
+
* ]}
|
|
32
|
+
* onChange={(id) => setMode(id)}
|
|
33
|
+
* />
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // With icon and right-aligned dropdown
|
|
37
|
+
* <PillSelect
|
|
38
|
+
* value="opus"
|
|
39
|
+
* icon={<NaiaLogo size={12} />}
|
|
40
|
+
* align="right"
|
|
41
|
+
* options={[{ id: "opus", label: "Opus 4.6" }, { id: "sonnet", label: "Sonnet 4" }]}
|
|
42
|
+
* onChange={setModel}
|
|
43
|
+
* />
|
|
44
|
+
*/
|
|
45
|
+
export default function PillSelect({
|
|
46
|
+
value,
|
|
47
|
+
options = [],
|
|
48
|
+
onChange,
|
|
49
|
+
icon,
|
|
50
|
+
align = "left",
|
|
51
|
+
className = "",
|
|
52
|
+
style = {},
|
|
53
|
+
}) {
|
|
54
|
+
const [open, setOpen] = useState(false);
|
|
55
|
+
const containerRef = useRef(null);
|
|
56
|
+
|
|
57
|
+
const selected = options.find((o) => o.id === value);
|
|
58
|
+
|
|
59
|
+
// Close on outside click
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!open) return;
|
|
62
|
+
const handleClick = (e) => {
|
|
63
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
64
|
+
setOpen(false);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
document.addEventListener("mousedown", handleClick);
|
|
68
|
+
return () => document.removeEventListener("mousedown", handleClick);
|
|
69
|
+
}, [open]);
|
|
70
|
+
|
|
71
|
+
// Close on Escape
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!open) return;
|
|
74
|
+
const handleKey = (e) => {
|
|
75
|
+
if (e.key === "Escape") setOpen(false);
|
|
76
|
+
};
|
|
77
|
+
document.addEventListener("keydown", handleKey);
|
|
78
|
+
return () => document.removeEventListener("keydown", handleKey);
|
|
79
|
+
}, [open]);
|
|
80
|
+
|
|
81
|
+
const hasCustomColors = selected?.colorBg && selected?.colorText;
|
|
82
|
+
|
|
83
|
+
// Dynamic trigger styles only for custom colors — otherwise CSS handles it
|
|
84
|
+
const triggerDynamicStyle = hasCustomColors
|
|
85
|
+
? {
|
|
86
|
+
background: selected.colorBg,
|
|
87
|
+
color: selected.colorText,
|
|
88
|
+
borderColor: `color-mix(in srgb, ${selected.colorText} 40%, transparent)`,
|
|
89
|
+
...style,
|
|
90
|
+
}
|
|
91
|
+
: style;
|
|
92
|
+
|
|
93
|
+
const handleSelect = (id) => {
|
|
94
|
+
if (onChange) onChange(id);
|
|
95
|
+
setOpen(false);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div
|
|
100
|
+
ref={containerRef}
|
|
101
|
+
className={`neptune-pill-select ${className}`}
|
|
102
|
+
>
|
|
103
|
+
{/* Trigger */}
|
|
104
|
+
<button
|
|
105
|
+
className="neptune-pill-select__trigger"
|
|
106
|
+
style={triggerDynamicStyle}
|
|
107
|
+
onClick={() => setOpen((prev) => !prev)}
|
|
108
|
+
aria-haspopup="listbox"
|
|
109
|
+
aria-expanded={open}
|
|
110
|
+
aria-label={selected ? `${selected.label} selected` : "Select option"}
|
|
111
|
+
>
|
|
112
|
+
{icon && <span className="neptune-pill-select__trigger-icon">{
|
|
113
|
+
isValidElement(icon) && selected?.colorText
|
|
114
|
+
? cloneElement(icon, { color: selected.colorText })
|
|
115
|
+
: icon
|
|
116
|
+
}</span>}
|
|
117
|
+
<span className="neptune-pill-select__trigger-label">
|
|
118
|
+
{selected?.label ?? "Select"}
|
|
119
|
+
</span>
|
|
120
|
+
<ChevronDown
|
|
121
|
+
size={10}
|
|
122
|
+
className={`neptune-pill-select__chevron ${open ? "neptune-pill-select__chevron--open" : ""}`}
|
|
123
|
+
/>
|
|
124
|
+
</button>
|
|
125
|
+
|
|
126
|
+
{/* Dropdown menu */}
|
|
127
|
+
{open && (
|
|
128
|
+
<div className={`neptune-pill-select__dropdown neptune-pill-select__dropdown--${align}`} role="listbox">
|
|
129
|
+
{options.map((opt) => {
|
|
130
|
+
const isSelected = opt.id === value;
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<button
|
|
134
|
+
key={opt.id}
|
|
135
|
+
role="option"
|
|
136
|
+
aria-selected={isSelected}
|
|
137
|
+
className={`neptune-pill-select__item ${isSelected ? "neptune-pill-select__item--selected" : ""}`}
|
|
138
|
+
onClick={() => handleSelect(opt.id)}
|
|
139
|
+
>
|
|
140
|
+
{/* Check icon column */}
|
|
141
|
+
<span className="neptune-pill-select__item-check">
|
|
142
|
+
<Check size={10} />
|
|
143
|
+
</span>
|
|
144
|
+
|
|
145
|
+
{/* Label + desc column */}
|
|
146
|
+
<span className="neptune-pill-select__item-content">
|
|
147
|
+
<span className="neptune-pill-select__item-label-row">
|
|
148
|
+
{opt.icon && (
|
|
149
|
+
<span className="neptune-pill-select__item-icon">
|
|
150
|
+
{opt.icon}
|
|
151
|
+
</span>
|
|
152
|
+
)}
|
|
153
|
+
<span className="neptune-pill-select__item-label">
|
|
154
|
+
{opt.label}
|
|
155
|
+
</span>
|
|
156
|
+
{opt.badge && (
|
|
157
|
+
<span className="neptune-pill-select__item-badge">
|
|
158
|
+
{opt.badge}
|
|
159
|
+
</span>
|
|
160
|
+
)}
|
|
161
|
+
</span>
|
|
162
|
+
{opt.desc && (
|
|
163
|
+
<span className="neptune-pill-select__item-desc">
|
|
164
|
+
{opt.desc}
|
|
165
|
+
</span>
|
|
166
|
+
)}
|
|
167
|
+
</span>
|
|
168
|
+
</button>
|
|
169
|
+
);
|
|
170
|
+
})}
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PropertyField — Label + monospace input for property editors
|
|
3
|
+
*
|
|
4
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
5
|
+
*
|
|
6
|
+
* Used in settings panels and modals for editing key-value pairs.
|
|
7
|
+
* Supports a highlight state for modified/active properties.
|
|
8
|
+
*
|
|
9
|
+
* @component
|
|
10
|
+
* @param {Object} props
|
|
11
|
+
* @param {string} props.label - Property name/label
|
|
12
|
+
* @param {string} [props.value=''] - Current value
|
|
13
|
+
* @param {Function} [props.onChange] - Change handler, receives event
|
|
14
|
+
* @param {boolean} [props.highlight=false] - Whether to show accent highlight (modified state)
|
|
15
|
+
* @param {number} [props.labelWidth=144] - Label width in px
|
|
16
|
+
* @param {boolean} [props.disabled=false]
|
|
17
|
+
* @param {string} [props.className]
|
|
18
|
+
* @param {Object} [props.style]
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* <PropertyField label="Name" value="OrderList" onChange={handleChange} />
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* <PropertyField label="title" value="Home" highlight />
|
|
25
|
+
*/
|
|
26
|
+
export default function PropertyField({
|
|
27
|
+
label,
|
|
28
|
+
value = "",
|
|
29
|
+
onChange,
|
|
30
|
+
highlight = false,
|
|
31
|
+
labelWidth = 144,
|
|
32
|
+
disabled = false,
|
|
33
|
+
className = "",
|
|
34
|
+
style = {},
|
|
35
|
+
}) {
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
className={`neptune-property-field ${highlight ? "neptune-property-field--highlight" : ""} ${
|
|
39
|
+
disabled ? "neptune-property-field--disabled" : ""
|
|
40
|
+
} ${className}`}
|
|
41
|
+
style={style}
|
|
42
|
+
>
|
|
43
|
+
<span
|
|
44
|
+
className="neptune-property-field__label"
|
|
45
|
+
style={{ width: `${labelWidth}px` }}
|
|
46
|
+
>
|
|
47
|
+
{label}
|
|
48
|
+
</span>
|
|
49
|
+
<input
|
|
50
|
+
type="text"
|
|
51
|
+
defaultValue={value}
|
|
52
|
+
onChange={onChange}
|
|
53
|
+
disabled={disabled}
|
|
54
|
+
className="neptune-property-field__input"
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SuggestionPill — Chip button for AI quick-action suggestions
|
|
3
|
+
*
|
|
4
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
5
|
+
*
|
|
6
|
+
* @component
|
|
7
|
+
* @param {Object} props
|
|
8
|
+
* @param {React.ReactNode} props.children - Pill label text
|
|
9
|
+
* @param {Function} [props.onClick]
|
|
10
|
+
* @param {string} [props.className]
|
|
11
|
+
* @param {Object} [props.style] - Dynamic inline overrides only
|
|
12
|
+
*/
|
|
13
|
+
export default function SuggestionPill({
|
|
14
|
+
children,
|
|
15
|
+
onClick,
|
|
16
|
+
className = "",
|
|
17
|
+
style = {},
|
|
18
|
+
}) {
|
|
19
|
+
return (
|
|
20
|
+
<button
|
|
21
|
+
className={`neptune-suggestion-pill ${className}`}
|
|
22
|
+
style={style}
|
|
23
|
+
onClick={onClick}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
</button>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Search } from "lucide-react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TextInput component for text entry
|
|
5
|
+
* Supports search variant with optional keyboard shortcut hint display
|
|
6
|
+
*
|
|
7
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
8
|
+
*
|
|
9
|
+
* @component
|
|
10
|
+
* @param {Object} props - Component props
|
|
11
|
+
* @param {string} [props.value] - Current input value
|
|
12
|
+
* @param {Function} [props.onChange] - Change handler that receives the event
|
|
13
|
+
* @param {string} [props.placeholder=''] - Placeholder text
|
|
14
|
+
* @param {string} [props.label] - Optional label text displayed above input
|
|
15
|
+
* @param {string} [props.variant='default'] - Input style: 'default' | 'search'
|
|
16
|
+
* @param {string} [props.shortcutHint] - Keyboard shortcut hint (e.g., '⌘K') displayed in search variant
|
|
17
|
+
* @param {boolean} [props.disabled=false] - Whether the input is disabled
|
|
18
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
19
|
+
* @param {Object} [props.style] - Inline styles
|
|
20
|
+
* @returns {JSX.Element} Styled input element with optional label
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Basic input
|
|
24
|
+
* <TextInput
|
|
25
|
+
* placeholder="Enter text..."
|
|
26
|
+
* value={text}
|
|
27
|
+
* onChange={(e) => setText(e.target.value)}
|
|
28
|
+
* />
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Search input with shortcut hint
|
|
32
|
+
* <TextInput
|
|
33
|
+
* variant="search"
|
|
34
|
+
* placeholder="Search..."
|
|
35
|
+
* shortcutHint="⌘K"
|
|
36
|
+
* value={search}
|
|
37
|
+
* onChange={(e) => setSearch(e.target.value)}
|
|
38
|
+
* />
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* // Input with label
|
|
42
|
+
* <TextInput
|
|
43
|
+
* label="Email Address"
|
|
44
|
+
* placeholder="user@example.com"
|
|
45
|
+
* value={email}
|
|
46
|
+
* onChange={(e) => setEmail(e.target.value)}
|
|
47
|
+
* />
|
|
48
|
+
*/
|
|
49
|
+
export default function TextInput({
|
|
50
|
+
value,
|
|
51
|
+
onChange,
|
|
52
|
+
placeholder = "",
|
|
53
|
+
label,
|
|
54
|
+
variant = "default",
|
|
55
|
+
shortcutHint,
|
|
56
|
+
disabled = false,
|
|
57
|
+
className = "",
|
|
58
|
+
style = {},
|
|
59
|
+
...props
|
|
60
|
+
}) {
|
|
61
|
+
return (
|
|
62
|
+
<div className={`neptune-text-input neptune-text-input--${variant} ${disabled ? "neptune-text-input--disabled" : ""} ${className}`}>
|
|
63
|
+
{label && (
|
|
64
|
+
<label className="neptune-text-input__label">
|
|
65
|
+
{label}
|
|
66
|
+
</label>
|
|
67
|
+
)}
|
|
68
|
+
|
|
69
|
+
<div className="neptune-text-input__wrapper">
|
|
70
|
+
{variant === "search" && (
|
|
71
|
+
<Search
|
|
72
|
+
size={16}
|
|
73
|
+
className="neptune-text-input__search-icon"
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
76
|
+
|
|
77
|
+
<input
|
|
78
|
+
type="text"
|
|
79
|
+
value={value}
|
|
80
|
+
onChange={onChange}
|
|
81
|
+
placeholder={placeholder}
|
|
82
|
+
disabled={disabled}
|
|
83
|
+
className="neptune-text-input__field"
|
|
84
|
+
style={style}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
{variant === "search" && shortcutHint && (
|
|
89
|
+
<span className="neptune-text-input__shortcut-hint">
|
|
90
|
+
{shortcutHint}
|
|
91
|
+
</span>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toggle switch component for boolean state control
|
|
3
|
+
*
|
|
4
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
5
|
+
*
|
|
6
|
+
* Active state uses green (success) in base theme. In a data-theme="naia"
|
|
7
|
+
* container, active state automatically uses purple via accent-primary.
|
|
8
|
+
*
|
|
9
|
+
* @component
|
|
10
|
+
* @param {Object} props - Component props
|
|
11
|
+
* @param {boolean} [props.checked=false] - Whether the toggle is in the on state
|
|
12
|
+
* @param {Function} [props.onChange] - Change handler that receives the new boolean value
|
|
13
|
+
* @param {string} [props.label] - Optional label text displayed next to toggle
|
|
14
|
+
* @param {boolean} [props.disabled=false] - Whether the toggle is disabled
|
|
15
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
16
|
+
* @param {Object} [props.style] - Inline styles
|
|
17
|
+
* @returns {JSX.Element} Styled toggle switch element
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* <Toggle checked={isEnabled} onChange={setIsEnabled} />
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* <Toggle label="Dark mode" checked={isDark} onChange={setIsDark} />
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // Naia-themed: purple active state
|
|
27
|
+
* <div data-theme="naia">
|
|
28
|
+
* <Toggle checked={true} onChange={fn} />
|
|
29
|
+
* </div>
|
|
30
|
+
*/
|
|
31
|
+
export default function Toggle({
|
|
32
|
+
checked = false,
|
|
33
|
+
onChange,
|
|
34
|
+
label,
|
|
35
|
+
size = "default",
|
|
36
|
+
disabled = false,
|
|
37
|
+
className = "",
|
|
38
|
+
style = {},
|
|
39
|
+
...props
|
|
40
|
+
}) {
|
|
41
|
+
const handleToggle = () => {
|
|
42
|
+
if (!disabled && onChange) {
|
|
43
|
+
onChange(!checked);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<label
|
|
49
|
+
className={`neptune-toggle ${size === "sm" ? "neptune-toggle--sm" : ""} ${
|
|
50
|
+
checked ? "neptune-toggle--checked" : ""
|
|
51
|
+
} ${disabled ? "neptune-toggle--disabled" : ""} ${className}`}
|
|
52
|
+
style={style}
|
|
53
|
+
{...props}
|
|
54
|
+
>
|
|
55
|
+
<button
|
|
56
|
+
className="neptune-toggle__switch"
|
|
57
|
+
onClick={handleToggle}
|
|
58
|
+
disabled={disabled}
|
|
59
|
+
role="switch"
|
|
60
|
+
aria-checked={checked}
|
|
61
|
+
type="button"
|
|
62
|
+
>
|
|
63
|
+
<span className="neptune-toggle__thumb" />
|
|
64
|
+
</button>
|
|
65
|
+
|
|
66
|
+
{label && (
|
|
67
|
+
<span className="neptune-toggle__label">
|
|
68
|
+
{label}
|
|
69
|
+
</span>
|
|
70
|
+
)}
|
|
71
|
+
</label>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppHeader — Top-level application header bar
|
|
3
|
+
*
|
|
4
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
5
|
+
*
|
|
6
|
+
* Flexible slot-based layout: left (logo + breadcrumb), right (actions + avatar),
|
|
7
|
+
* and children (middle content). All content is passed as props — the component
|
|
8
|
+
* provides the container styling only.
|
|
9
|
+
*
|
|
10
|
+
* @component
|
|
11
|
+
* @param {Object} props
|
|
12
|
+
* @param {React.ReactNode} props.left - Left content (logo, breadcrumb, title)
|
|
13
|
+
* @param {React.ReactNode} [props.right] - Right content (buttons, icons, avatar)
|
|
14
|
+
* @param {React.ReactNode} [props.children] - Middle content (optional)
|
|
15
|
+
* @param {string} [props.variant='default'] - 'default' | 'builder' (adjusts padding)
|
|
16
|
+
* @param {string} [props.className]
|
|
17
|
+
* @param {Object} [props.style]
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* <AppHeader
|
|
21
|
+
* variant="builder"
|
|
22
|
+
* left={<><NeptuneLogo size={28} /><Breadcrumb items={[...]} /></>}
|
|
23
|
+
* right={<>
|
|
24
|
+
* <Button variant="ghost" size="sm" icon={<Info size={10} />}>Learning Hub</Button>
|
|
25
|
+
* <IconButton variant="subtle" icon={<Search size={14} />} title="Search" />
|
|
26
|
+
* <Avatar initials="M" />
|
|
27
|
+
* </>}
|
|
28
|
+
* />
|
|
29
|
+
*/
|
|
30
|
+
export default function AppHeader({
|
|
31
|
+
left,
|
|
32
|
+
right,
|
|
33
|
+
children,
|
|
34
|
+
variant = "default",
|
|
35
|
+
className = "",
|
|
36
|
+
style = {},
|
|
37
|
+
}) {
|
|
38
|
+
return (
|
|
39
|
+
<header
|
|
40
|
+
className={`neptune-app-header ${variant === "builder" ? "neptune-app-header--builder" : ""} ${className}`}
|
|
41
|
+
style={style}
|
|
42
|
+
>
|
|
43
|
+
<div className="neptune-app-header__left">
|
|
44
|
+
{left}
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
{children}
|
|
48
|
+
|
|
49
|
+
{right && (
|
|
50
|
+
<div className="neptune-app-header__right">
|
|
51
|
+
{right}
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
</header>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import StatusDot from "../feedback/StatusDot.jsx";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BottomBar — Bottom status bar with breakpoint and theme toggles
|
|
5
|
+
*
|
|
6
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
7
|
+
*
|
|
8
|
+
* Breakpoints and themes accept either strings ("Mobile") or objects
|
|
9
|
+
* ({ id: "tablet", icon: <TabletIcon />, label: "Tablet" }) for icon-only buttons.
|
|
10
|
+
*
|
|
11
|
+
* @component
|
|
12
|
+
* @param {Object} props
|
|
13
|
+
* @param {Object} [props.status] - Status indicator { label, status, pulse, color }
|
|
14
|
+
* @param {Array} [props.breakpoints=[]] - Breakpoint items (string or { id, icon, label })
|
|
15
|
+
* @param {string} [props.activeBreakpoint] - Currently active breakpoint id/name
|
|
16
|
+
* @param {Function} [props.onBreakpointChange] - Callback(id)
|
|
17
|
+
* @param {Array} [props.themes=[]] - Theme items (string or { id, icon, label })
|
|
18
|
+
* @param {string} [props.activeTheme] - Currently active theme id/name
|
|
19
|
+
* @param {Function} [props.onThemeChange] - Callback(id)
|
|
20
|
+
* @param {string} [props.className]
|
|
21
|
+
* @param {Object} [props.style]
|
|
22
|
+
*/
|
|
23
|
+
export default function BottomBar({
|
|
24
|
+
status,
|
|
25
|
+
breakpoints = [],
|
|
26
|
+
activeBreakpoint,
|
|
27
|
+
onBreakpointChange,
|
|
28
|
+
themes = [],
|
|
29
|
+
activeTheme,
|
|
30
|
+
onThemeChange,
|
|
31
|
+
className = "",
|
|
32
|
+
style = {},
|
|
33
|
+
}) {
|
|
34
|
+
const renderItem = (item, activeId, onChange) => {
|
|
35
|
+
const isObj = typeof item === "object" && item !== null;
|
|
36
|
+
const id = isObj ? item.id : item;
|
|
37
|
+
const label = isObj ? item.label : item;
|
|
38
|
+
const icon = isObj ? item.icon : null;
|
|
39
|
+
const isActive = activeId === id;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<button
|
|
43
|
+
key={id}
|
|
44
|
+
className={`neptune-bottom-bar__btn ${isActive ? "neptune-bottom-bar__btn--active" : ""}`}
|
|
45
|
+
onClick={() => onChange && onChange(id)}
|
|
46
|
+
title={label}
|
|
47
|
+
>
|
|
48
|
+
{icon || label}
|
|
49
|
+
</button>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className={`neptune-bottom-bar ${className}`} style={style}>
|
|
55
|
+
{status && (
|
|
56
|
+
<div className="neptune-bottom-bar__status">
|
|
57
|
+
<StatusDot
|
|
58
|
+
status={status.status || "success"}
|
|
59
|
+
pulse={status.pulse}
|
|
60
|
+
color={status.color}
|
|
61
|
+
/>
|
|
62
|
+
<span className="neptune-bottom-bar__status-label">
|
|
63
|
+
{status.label}
|
|
64
|
+
</span>
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{breakpoints.length > 0 && (
|
|
69
|
+
<div className="neptune-bottom-bar__group">
|
|
70
|
+
{breakpoints.map((bp) => renderItem(bp, activeBreakpoint, onBreakpointChange))}
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
|
|
74
|
+
{themes.length > 0 && (
|
|
75
|
+
<div className="neptune-bottom-bar__group">
|
|
76
|
+
{themes.map((th) => renderItem(th, activeTheme, onThemeChange))}
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card — Generic card container with optional interactivity
|
|
3
|
+
*
|
|
4
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
5
|
+
*
|
|
6
|
+
* A versatile card component with surface-raised background, subtle border,
|
|
7
|
+
* and rounded corners. When `interactive` is true and `onClick` is provided,
|
|
8
|
+
* the card gains a hover shadow and pointer cursor, making it suitable for
|
|
9
|
+
* clickable list items, selection cards, or dashboard tiles.
|
|
10
|
+
*
|
|
11
|
+
* Different from Panel (which is a simpler wrapper). Card supports hover
|
|
12
|
+
* elevation and click interactions.
|
|
13
|
+
*
|
|
14
|
+
* @component
|
|
15
|
+
* @param {Object} props - Component props
|
|
16
|
+
* @param {React.ReactNode} [props.children] - Card content
|
|
17
|
+
* @param {Function} [props.onClick] - Click handler (only effective when interactive)
|
|
18
|
+
* @param {boolean} [props.interactive=false] - Enables hover effects and pointer cursor
|
|
19
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
20
|
+
* @param {React.CSSProperties} [props.style] - Additional inline styles
|
|
21
|
+
* @returns {JSX.Element} Card component
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Static card
|
|
25
|
+
* <Card>
|
|
26
|
+
* <h3>Dashboard</h3>
|
|
27
|
+
* <p>Overview of your metrics.</p>
|
|
28
|
+
* </Card>
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Interactive card
|
|
32
|
+
* <Card interactive onClick={() => navigate("/details")}>
|
|
33
|
+
* <h3>Project Alpha</h3>
|
|
34
|
+
* <p>Click to view details</p>
|
|
35
|
+
* </Card>
|
|
36
|
+
*/
|
|
37
|
+
export default function Card({
|
|
38
|
+
children,
|
|
39
|
+
onClick,
|
|
40
|
+
interactive = false,
|
|
41
|
+
className = "",
|
|
42
|
+
style = {},
|
|
43
|
+
}) {
|
|
44
|
+
const isClickable = interactive && onClick;
|
|
45
|
+
const Tag = isClickable ? "button" : "div";
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Tag
|
|
49
|
+
className={`neptune-card ${interactive ? "neptune-card--interactive" : ""} ${className}`}
|
|
50
|
+
style={style}
|
|
51
|
+
onClick={isClickable ? onClick : undefined}
|
|
52
|
+
{...(isClickable ? { type: "button" } : {})}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
</Tag>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panel component - General purpose panel/card wrapper
|
|
3
|
+
*
|
|
4
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
5
|
+
*
|
|
6
|
+
* A versatile container component used throughout the Neptune UI
|
|
7
|
+
* for grouping and displaying content with consistent styling.
|
|
8
|
+
* Provides padding, border, background, and shadow using design tokens.
|
|
9
|
+
*
|
|
10
|
+
* @component
|
|
11
|
+
* @param {Object} props - Component props
|
|
12
|
+
* @param {React.ReactNode} props.children - Panel content
|
|
13
|
+
* @param {React.CSSProperties} [props.style] - Additional inline styles
|
|
14
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
15
|
+
* @returns {JSX.Element} Panel component
|
|
16
|
+
*/
|
|
17
|
+
export default function Panel({ children, style, className = "" }) {
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
className={`neptune-panel ${className}`}
|
|
21
|
+
style={style}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|