@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
package/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AppPreview — Mock application preview for the builder workspace
|
|
5
|
+
*
|
|
6
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
7
|
+
*
|
|
8
|
+
* Renders a skeleton wireframe of a typical app page (title bar, filter bar,
|
|
9
|
+
* data table rows). Uses neutral greys independent of the main theme so the
|
|
10
|
+
* preview looks like an actual embedded app, not part of the cockpit chrome.
|
|
11
|
+
*
|
|
12
|
+
* @component
|
|
13
|
+
* @param {Object} props
|
|
14
|
+
* @param {string} [props.title='Filterable List'] - App bar title
|
|
15
|
+
* @param {string} [props.breakpoint='browser'] - 'browser' | 'tablet' | 'phone'
|
|
16
|
+
* @param {string} [props.theme='dark'] - 'dark' | 'light' — independent of parent theme
|
|
17
|
+
* @param {string[]} [props.columns] - Column headers for the table
|
|
18
|
+
* @param {number} [props.rows=8] - Number of skeleton rows
|
|
19
|
+
* @param {React.ReactNode} [props.badge] - Optional badge in the title bar (e.g. "Modified by Naia")
|
|
20
|
+
* @param {string} [props.className]
|
|
21
|
+
* @param {Object} [props.style]
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* <AppPreview title="Order List" breakpoint="browser" theme="dark" />
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* <AppPreview breakpoint="phone" theme="light" columns={["ID", "Name"]} rows={6} />
|
|
28
|
+
*/
|
|
29
|
+
export default function AppPreview({
|
|
30
|
+
title = "Filterable List",
|
|
31
|
+
breakpoint = "browser",
|
|
32
|
+
theme = "dark",
|
|
33
|
+
columns,
|
|
34
|
+
rows = 8,
|
|
35
|
+
badge,
|
|
36
|
+
className = "",
|
|
37
|
+
style = {},
|
|
38
|
+
}) {
|
|
39
|
+
const isDark = theme === "dark";
|
|
40
|
+
const containerRef = useRef(null);
|
|
41
|
+
|
|
42
|
+
// Neutral grey palette from --npt-neutral-* tokens
|
|
43
|
+
const bg = isDark ? "var(--npt-neutral-900)" : "var(--npt-neutral-50)";
|
|
44
|
+
const surface = isDark ? "var(--npt-neutral-800)" : "var(--npt-neutral-50)";
|
|
45
|
+
const dim = isDark ? "var(--npt-neutral-700)" : "var(--npt-neutral-150)";
|
|
46
|
+
const border = isDark ? "var(--npt-neutral-650)" : "var(--npt-neutral-200)";
|
|
47
|
+
const text = isDark ? "var(--npt-neutral-150)" : "var(--npt-neutral-900)";
|
|
48
|
+
const textDim = isDark ? "var(--npt-neutral-400)" : "var(--npt-neutral-400)";
|
|
49
|
+
const accent = isDark ? "var(--npt-orange-350)" : "var(--npt-orange-500)";
|
|
50
|
+
|
|
51
|
+
const statusColors = [
|
|
52
|
+
isDark ? "color-mix(in srgb, var(--npt-green-400) 15%, transparent)" : "color-mix(in srgb, var(--npt-green-550) 12%, transparent)",
|
|
53
|
+
isDark ? "color-mix(in srgb, var(--npt-orange-300) 15%, transparent)" : "color-mix(in srgb, var(--npt-orange-550) 12%, transparent)",
|
|
54
|
+
isDark ? "color-mix(in srgb, var(--npt-purple-300) 15%, transparent)" : "color-mix(in srgb, var(--npt-purple-500) 12%, transparent)",
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const defaultColumns = breakpoint === "phone"
|
|
58
|
+
? []
|
|
59
|
+
: breakpoint === "tablet"
|
|
60
|
+
? ["Order ID", "Customer", "Date"]
|
|
61
|
+
: ["Order ID", "Customer", "Date", "Amount", "Status"];
|
|
62
|
+
|
|
63
|
+
const cols = columns || defaultColumns;
|
|
64
|
+
const rowCount = breakpoint === "phone" ? Math.min(rows, 6) : rows;
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
ref={containerRef}
|
|
69
|
+
className={`neptune-app-preview ${className}`}
|
|
70
|
+
style={{ backgroundColor: bg, ...style }}
|
|
71
|
+
>
|
|
72
|
+
<div
|
|
73
|
+
className={`neptune-app-preview__frame neptune-app-preview__frame--${breakpoint}`}
|
|
74
|
+
style={{
|
|
75
|
+
backgroundColor: surface,
|
|
76
|
+
borderColor: border,
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
{/* App bar */}
|
|
80
|
+
<div
|
|
81
|
+
className="neptune-app-preview__titlebar"
|
|
82
|
+
style={{ backgroundColor: dim, borderColor: border }}
|
|
83
|
+
>
|
|
84
|
+
<span style={{ fontSize: 14, fontWeight: 500, color: text }}>{title}</span>
|
|
85
|
+
{badge}
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
{/* Filter bar */}
|
|
89
|
+
<div
|
|
90
|
+
className="neptune-app-preview__filterbar"
|
|
91
|
+
style={{ borderColor: border }}
|
|
92
|
+
>
|
|
93
|
+
<div style={{ height: 28, flex: 1, borderRadius: 4, backgroundColor: dim }} />
|
|
94
|
+
<div style={{ height: 28, width: breakpoint === "phone" ? 60 : 80, borderRadius: 4, backgroundColor: dim }} />
|
|
95
|
+
{breakpoint !== "phone" && (
|
|
96
|
+
<div style={{ height: 28, width: 80, borderRadius: 4, backgroundColor: dim }} />
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
{/* Table header */}
|
|
101
|
+
<div className="neptune-app-preview__table">
|
|
102
|
+
{cols.length > 0 && (
|
|
103
|
+
<div
|
|
104
|
+
className="neptune-app-preview__row"
|
|
105
|
+
style={{ borderColor: border }}
|
|
106
|
+
>
|
|
107
|
+
{cols.map((col) => (
|
|
108
|
+
<div key={col} style={{ flex: 1 }}>
|
|
109
|
+
<span style={{ fontSize: 12, fontWeight: 500, color: textDim }}>{col}</span>
|
|
110
|
+
</div>
|
|
111
|
+
))}
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
|
|
115
|
+
{/* Skeleton rows */}
|
|
116
|
+
{[...Array(rowCount)].map((_, i) => (
|
|
117
|
+
<div
|
|
118
|
+
key={i}
|
|
119
|
+
className="neptune-app-preview__row"
|
|
120
|
+
style={{ borderColor: border }}
|
|
121
|
+
>
|
|
122
|
+
{breakpoint === "phone" ? (
|
|
123
|
+
<div style={{ flex: 1, display: "flex", flexDirection: "column", gap: 6 }}>
|
|
124
|
+
<div style={{ height: 8, borderRadius: 4, backgroundColor: dim, width: "70%" }} />
|
|
125
|
+
<div style={{ height: 8, borderRadius: 4, backgroundColor: dim, width: "45%" }} />
|
|
126
|
+
</div>
|
|
127
|
+
) : (
|
|
128
|
+
cols.map((col, j) => (
|
|
129
|
+
<div key={j} style={{ flex: 1 }}>
|
|
130
|
+
{col === "Status" ? (
|
|
131
|
+
<div style={{ height: 16, width: 56, borderRadius: 4, backgroundColor: statusColors[i % 3] }} />
|
|
132
|
+
) : (
|
|
133
|
+
<div style={{
|
|
134
|
+
height: 8,
|
|
135
|
+
borderRadius: 4,
|
|
136
|
+
backgroundColor: i === 0 && j === 0 ? accent : dim,
|
|
137
|
+
width: j === 0 ? "60%" : j === 1 ? "75%" : j === 2 ? "50%" : "40%",
|
|
138
|
+
opacity: i === 0 && j === 0 ? 0.6 : 1,
|
|
139
|
+
}} />
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
))
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
))}
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataTable component - Tabular data display with columns and rows
|
|
3
|
+
*
|
|
4
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
5
|
+
*
|
|
6
|
+
* Renders a data table with headers and rows. Supports custom cell rendering
|
|
7
|
+
* via renderCell function. Includes proper borders, spacing, and typography.
|
|
8
|
+
*
|
|
9
|
+
* @component
|
|
10
|
+
* @param {Object} props - Component props
|
|
11
|
+
* @param {string[]} props.columns - Array of column header names
|
|
12
|
+
* @param {Object[]} props.rows - Array of row objects with data values
|
|
13
|
+
* @param {Function} [props.renderCell] - Optional function(row, column, index) to customize cell rendering
|
|
14
|
+
* @returns {JSX.Element} Styled data table
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const columns = ['Name', 'Status', 'Updated'];
|
|
18
|
+
* const rows = [
|
|
19
|
+
* { Name: 'API Service', Status: 'Running', Updated: '2 min ago' },
|
|
20
|
+
* { Name: 'Database', Status: 'Running', Updated: '5 min ago' }
|
|
21
|
+
* ];
|
|
22
|
+
* <DataTable columns={columns} rows={rows} />
|
|
23
|
+
*/
|
|
24
|
+
export default function DataTable({
|
|
25
|
+
columns = [],
|
|
26
|
+
rows = [],
|
|
27
|
+
renderCell,
|
|
28
|
+
...props
|
|
29
|
+
}) {
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
className="neptune-data-table"
|
|
33
|
+
{...props}
|
|
34
|
+
>
|
|
35
|
+
<table>
|
|
36
|
+
<thead>
|
|
37
|
+
<tr>
|
|
38
|
+
{columns.map((column, idx) => (
|
|
39
|
+
<th key={idx}>
|
|
40
|
+
{column}
|
|
41
|
+
</th>
|
|
42
|
+
))}
|
|
43
|
+
</tr>
|
|
44
|
+
</thead>
|
|
45
|
+
<tbody>
|
|
46
|
+
{rows.map((row, rowIdx) => (
|
|
47
|
+
<tr key={rowIdx}>
|
|
48
|
+
{columns.map((column, colIdx) => {
|
|
49
|
+
const cellContent = renderCell
|
|
50
|
+
? renderCell(row, column, rowIdx)
|
|
51
|
+
: row[column];
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<td key={`${rowIdx}-${colIdx}`}>
|
|
55
|
+
{cellContent}
|
|
56
|
+
</td>
|
|
57
|
+
);
|
|
58
|
+
})}
|
|
59
|
+
</tr>
|
|
60
|
+
))}
|
|
61
|
+
</tbody>
|
|
62
|
+
</table>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { ChevronDown, ChevronRight, FolderOpen, FileText } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* FileTreeNode — recursive internal component for rendering tree nodes
|
|
6
|
+
*/
|
|
7
|
+
function FileTreeNode({ item, depth = 0, selectedName, onSelect, parents = [] }) {
|
|
8
|
+
const [open, setOpen] = useState(item.defaultOpen || item.open || false);
|
|
9
|
+
const isFolder = !!item.children;
|
|
10
|
+
const isSelected = !isFolder && selectedName != null && item.name === selectedName;
|
|
11
|
+
|
|
12
|
+
function handleClick() {
|
|
13
|
+
if (isFolder) {
|
|
14
|
+
setOpen(!open);
|
|
15
|
+
}
|
|
16
|
+
if (onSelect) {
|
|
17
|
+
onSelect(item, { depth, parents, isFolder });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div>
|
|
23
|
+
<button
|
|
24
|
+
className={[
|
|
25
|
+
"neptune-file-tree__item",
|
|
26
|
+
isFolder && "neptune-file-tree__item--folder",
|
|
27
|
+
isSelected && "neptune-file-tree__item--active",
|
|
28
|
+
].filter(Boolean).join(" ")}
|
|
29
|
+
style={{ paddingLeft: `${depth * 14 + 10}px` }}
|
|
30
|
+
onClick={handleClick}
|
|
31
|
+
>
|
|
32
|
+
{isFolder
|
|
33
|
+
? (open
|
|
34
|
+
? <ChevronDown size={12} className="neptune-file-tree__chevron" />
|
|
35
|
+
: <ChevronRight size={12} className="neptune-file-tree__chevron" />)
|
|
36
|
+
: <span className="neptune-file-tree__chevron" />}
|
|
37
|
+
{item.icon ? (
|
|
38
|
+
<span className="neptune-file-tree__icon">{item.icon}</span>
|
|
39
|
+
) : isFolder ? (
|
|
40
|
+
<FolderOpen size={12} className="neptune-file-tree__icon" />
|
|
41
|
+
) : (
|
|
42
|
+
<FileText size={12} className="neptune-file-tree__icon" />
|
|
43
|
+
)}
|
|
44
|
+
<span className="neptune-file-tree__label">{item.name}</span>
|
|
45
|
+
</button>
|
|
46
|
+
{isFolder && open && item.children.map((child, i) => (
|
|
47
|
+
<FileTreeNode
|
|
48
|
+
key={i}
|
|
49
|
+
item={child}
|
|
50
|
+
depth={depth + 1}
|
|
51
|
+
selectedName={selectedName}
|
|
52
|
+
onSelect={onSelect}
|
|
53
|
+
parents={[...parents, item.name]}
|
|
54
|
+
/>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* FileTree component - Hierarchical file and folder tree display with recursive folder support
|
|
62
|
+
*
|
|
63
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
64
|
+
*
|
|
65
|
+
* Renders a nested file/folder structure with proper indentation based on depth.
|
|
66
|
+
* Items with `children` arrays are treated as folders that can be opened/closed.
|
|
67
|
+
* Manages open/close state internally. Supports `defaultOpen` or `open` per item.
|
|
68
|
+
* Depth-based padding is computed dynamically as an inline style.
|
|
69
|
+
*
|
|
70
|
+
* @component
|
|
71
|
+
* @param {Object} props - Component props
|
|
72
|
+
* @param {Object[]} props.items - Array of tree items (recursive), each with:
|
|
73
|
+
* - {string} name - Item display name
|
|
74
|
+
* - {Object[]} [children] - Child items (presence makes it a folder)
|
|
75
|
+
* - {boolean} [defaultOpen] - Whether folder starts open (alias: `open`)
|
|
76
|
+
* - {React.ReactNode} [icon] - Custom icon to override the default file/folder icon
|
|
77
|
+
* @param {Function} [props.onSelect] - Callback when an item is clicked: (item, meta)
|
|
78
|
+
* - meta: { depth, parents, isFolder }
|
|
79
|
+
* @param {string} [props.selectedName] - Name of the currently selected item (highlights matching)
|
|
80
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
81
|
+
* @param {Object} [props.style] - Inline style overrides
|
|
82
|
+
* @returns {JSX.Element} Styled file tree
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* const items = [
|
|
86
|
+
* { name: 'src', children: [
|
|
87
|
+
* { name: 'components', children: [
|
|
88
|
+
* { name: 'Button.jsx' },
|
|
89
|
+
* { name: 'Input.jsx' },
|
|
90
|
+
* ], defaultOpen: true },
|
|
91
|
+
* { name: 'index.js' },
|
|
92
|
+
* ], defaultOpen: true },
|
|
93
|
+
* { name: 'package.json' },
|
|
94
|
+
* ];
|
|
95
|
+
* <FileTree items={items} selectedName="Button.jsx" onSelect={(item) => console.log(item.name)} />
|
|
96
|
+
*/
|
|
97
|
+
export default function FileTree({
|
|
98
|
+
items = [],
|
|
99
|
+
onSelect,
|
|
100
|
+
selectedName,
|
|
101
|
+
className = "",
|
|
102
|
+
style = {},
|
|
103
|
+
...props
|
|
104
|
+
}) {
|
|
105
|
+
return (
|
|
106
|
+
<div
|
|
107
|
+
className={`neptune-file-tree ${className}`}
|
|
108
|
+
style={style}
|
|
109
|
+
{...props}
|
|
110
|
+
>
|
|
111
|
+
{items.map((item, idx) => (
|
|
112
|
+
<FileTreeNode
|
|
113
|
+
key={idx}
|
|
114
|
+
item={item}
|
|
115
|
+
depth={0}
|
|
116
|
+
selectedName={selectedName}
|
|
117
|
+
onSelect={onSelect}
|
|
118
|
+
parents={[]}
|
|
119
|
+
/>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KpiCard component - Key Performance Indicator card display
|
|
3
|
+
*
|
|
4
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
5
|
+
*
|
|
6
|
+
* Displays a metric or KPI with an icon, large value, label, and optional subtitle.
|
|
7
|
+
* Commonly used in dashboards to highlight important metrics and statistics.
|
|
8
|
+
*
|
|
9
|
+
* @component
|
|
10
|
+
* @param {Object} props - Component props
|
|
11
|
+
* @param {React.ComponentType} props.icon - Icon component to display (receives size prop)
|
|
12
|
+
* @param {string|number} props.value - The primary value to display prominently
|
|
13
|
+
* @param {string} props.label - Label describing what the KPI measures
|
|
14
|
+
* @param {string} [props.subtitle] - Optional additional context or description
|
|
15
|
+
* @returns {JSX.Element} Styled KPI card
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* import { Activity } from 'lucide-react';
|
|
19
|
+
* <KpiCard
|
|
20
|
+
* icon={Activity}
|
|
21
|
+
* value="1,234"
|
|
22
|
+
* label="Active Deployments"
|
|
23
|
+
* subtitle="Up 12% from last week"
|
|
24
|
+
* />
|
|
25
|
+
*/
|
|
26
|
+
export default function KpiCard({
|
|
27
|
+
icon: Icon,
|
|
28
|
+
value,
|
|
29
|
+
label,
|
|
30
|
+
subtitle,
|
|
31
|
+
...props
|
|
32
|
+
}) {
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
className="neptune-kpi-card"
|
|
36
|
+
{...props}
|
|
37
|
+
>
|
|
38
|
+
<div className="neptune-kpi-card__icon-wrapper">
|
|
39
|
+
{Icon && <Icon size={20} />}
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div className="neptune-kpi-card__content">
|
|
43
|
+
<div className="neptune-kpi-card__value">
|
|
44
|
+
{value}
|
|
45
|
+
</div>
|
|
46
|
+
<div className="neptune-kpi-card__label">
|
|
47
|
+
{label}
|
|
48
|
+
</div>
|
|
49
|
+
{subtitle && (
|
|
50
|
+
<div className="neptune-kpi-card__subtitle">
|
|
51
|
+
{subtitle}
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VersionRow — Row item for version/history lists
|
|
3
|
+
*
|
|
4
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
5
|
+
*
|
|
6
|
+
* A clickable row that displays a version description, author, and timestamp.
|
|
7
|
+
* Supports an expandable detail section that slides open when toggled.
|
|
8
|
+
* An optional `actions` slot renders controls (buttons, menus) on the right side.
|
|
9
|
+
*
|
|
10
|
+
* Uses the naia-row hover pattern (surface-raised on hover) and surface-sunken
|
|
11
|
+
* for the expanded detail area.
|
|
12
|
+
*
|
|
13
|
+
* @component
|
|
14
|
+
* @param {Object} props - Component props
|
|
15
|
+
* @param {string} [props.description] - Version description text
|
|
16
|
+
* @param {string} [props.author] - Author name
|
|
17
|
+
* @param {string} [props.time] - Timestamp or relative time string
|
|
18
|
+
* @param {React.ReactNode} [props.actions] - Optional action buttons rendered on the right
|
|
19
|
+
* @param {boolean} [props.expanded=false] - Whether the detail section is open
|
|
20
|
+
* @param {Function} [props.onToggle] - Called when the row is clicked to expand/collapse
|
|
21
|
+
* @param {React.ReactNode} [props.detail] - Content shown in the expanded detail area
|
|
22
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
23
|
+
* @param {React.CSSProperties} [props.style] - Additional inline styles
|
|
24
|
+
* @returns {JSX.Element} VersionRow component
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* <VersionRow
|
|
28
|
+
* description="Updated hero banner copy"
|
|
29
|
+
* author="Jane D."
|
|
30
|
+
* time="2 hours ago"
|
|
31
|
+
* onToggle={() => setExpanded(!expanded)}
|
|
32
|
+
* expanded={expanded}
|
|
33
|
+
* detail={<p>Changed headline from "Welcome" to "Get Started"</p>}
|
|
34
|
+
* />
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* // With action buttons
|
|
38
|
+
* <VersionRow
|
|
39
|
+
* description="Published v2.1.0"
|
|
40
|
+
* author="Deploy Bot"
|
|
41
|
+
* time="Yesterday"
|
|
42
|
+
* actions={<Button size="sm" variant="tertiary">Restore</Button>}
|
|
43
|
+
* />
|
|
44
|
+
*/
|
|
45
|
+
export default function VersionRow({
|
|
46
|
+
description,
|
|
47
|
+
author,
|
|
48
|
+
time,
|
|
49
|
+
actions,
|
|
50
|
+
expanded = false,
|
|
51
|
+
onToggle,
|
|
52
|
+
detail,
|
|
53
|
+
className = "",
|
|
54
|
+
style = {},
|
|
55
|
+
}) {
|
|
56
|
+
return (
|
|
57
|
+
<div className={`neptune-version-row ${expanded ? "neptune-version-row--expanded" : ""} ${className}`}>
|
|
58
|
+
<div
|
|
59
|
+
className="neptune-version-row__header"
|
|
60
|
+
style={style}
|
|
61
|
+
onClick={onToggle}
|
|
62
|
+
role={onToggle ? "button" : undefined}
|
|
63
|
+
tabIndex={onToggle ? 0 : undefined}
|
|
64
|
+
aria-expanded={onToggle ? expanded : undefined}
|
|
65
|
+
onKeyDown={
|
|
66
|
+
onToggle
|
|
67
|
+
? (e) => {
|
|
68
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
onToggle();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
: undefined
|
|
74
|
+
}
|
|
75
|
+
>
|
|
76
|
+
{onToggle && (
|
|
77
|
+
<svg className="neptune-version-row__chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
78
|
+
<polyline points="9 18 15 12 9 6" />
|
|
79
|
+
</svg>
|
|
80
|
+
)}
|
|
81
|
+
<span className="neptune-version-row__description">{description}</span>
|
|
82
|
+
<span className="neptune-version-row__meta">
|
|
83
|
+
{author && <span>{author}</span>}
|
|
84
|
+
{author && time && <span>·</span>}
|
|
85
|
+
{time && <span>{time}</span>}
|
|
86
|
+
</span>
|
|
87
|
+
{actions && (
|
|
88
|
+
<span
|
|
89
|
+
className="neptune-version-row__actions"
|
|
90
|
+
onClick={(e) => e.stopPropagation()}
|
|
91
|
+
>
|
|
92
|
+
{actions}
|
|
93
|
+
</span>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
{expanded && detail && (
|
|
97
|
+
<div className="neptune-version-row__detail">
|
|
98
|
+
{detail}
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Avatar — Circular user avatar with initials or image
|
|
3
|
+
*
|
|
4
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
5
|
+
*
|
|
6
|
+
* @component
|
|
7
|
+
* @param {Object} props
|
|
8
|
+
* @param {string} [props.initials] - One or two letter initials
|
|
9
|
+
* @param {string} [props.src] - Image URL (overrides initials)
|
|
10
|
+
* @param {string} [props.alt] - Alt text for image
|
|
11
|
+
* @param {"sm"|"default"|"lg"} [props.size="default"]
|
|
12
|
+
* @param {string} [props.className]
|
|
13
|
+
* @param {Object} [props.style] - Dynamic inline overrides only
|
|
14
|
+
*/
|
|
15
|
+
export default function Avatar({
|
|
16
|
+
initials,
|
|
17
|
+
src,
|
|
18
|
+
alt = "",
|
|
19
|
+
size = "default",
|
|
20
|
+
className = "",
|
|
21
|
+
style = {},
|
|
22
|
+
}) {
|
|
23
|
+
return (
|
|
24
|
+
<div className={`neptune-avatar neptune-avatar--${size} ${className}`} style={style}>
|
|
25
|
+
{src ? <img src={src} alt={alt} /> : initials}
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Badge — Multi-purpose badge/tag for status, environment, and module indication
|
|
3
|
+
*
|
|
4
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
5
|
+
*
|
|
6
|
+
* @component
|
|
7
|
+
* @param {Object} props
|
|
8
|
+
* @param {string} props.label - Text to display
|
|
9
|
+
* @param {string} [props.variant='default'] - 'default' | 'success' | 'warning' | 'error' | 'info' |
|
|
10
|
+
* 'env-prod' | 'env-qa' | 'env-dev' | 'module-ui' | 'module-script' | 'module-api' | 'module-data'
|
|
11
|
+
* @param {string} [props.size='default'] - 'default' | 'sm'
|
|
12
|
+
* @param {string} [props.className]
|
|
13
|
+
* @param {Object} [props.style] - Dynamic inline overrides only
|
|
14
|
+
*/
|
|
15
|
+
export default function Badge({
|
|
16
|
+
label,
|
|
17
|
+
variant = "default",
|
|
18
|
+
size = "default",
|
|
19
|
+
className = "",
|
|
20
|
+
style = {},
|
|
21
|
+
...props
|
|
22
|
+
}) {
|
|
23
|
+
return (
|
|
24
|
+
<span
|
|
25
|
+
className={`neptune-badge neptune-badge--${variant} neptune-badge--${size === "sm" ? "sm" : "default-size"} ${className}`}
|
|
26
|
+
style={style}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
{label}
|
|
30
|
+
</span>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatMessage component - Chat message bubble for Naia AI conversations
|
|
3
|
+
*
|
|
4
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
5
|
+
*
|
|
6
|
+
* Displays a message bubble styled differently for user and Naia messages.
|
|
7
|
+
* Naia messages appear left-aligned with aubergine text, user messages
|
|
8
|
+
* appear right-aligned with white text on aubergine background.
|
|
9
|
+
*
|
|
10
|
+
* @component
|
|
11
|
+
* @param {Object} props - Component props
|
|
12
|
+
* @param {string} props.message - The message text content
|
|
13
|
+
* @param {string} [props.sender='user'] - Sender type: 'user' or 'naia'
|
|
14
|
+
* @returns {JSX.Element} Styled chat message bubble
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* <ChatMessage message="Hello, how can I help?" sender="naia" />
|
|
18
|
+
* <ChatMessage message="I need assistance with my deployment" sender="user" />
|
|
19
|
+
*/
|
|
20
|
+
export default function ChatMessage({
|
|
21
|
+
message,
|
|
22
|
+
sender = "user",
|
|
23
|
+
className = "",
|
|
24
|
+
style = {},
|
|
25
|
+
...props
|
|
26
|
+
}) {
|
|
27
|
+
const lines = typeof message === "string" ? message.split("\n") : null;
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
className={`neptune-chat-message neptune-chat-message--${sender} ${className}`}
|
|
32
|
+
style={style}
|
|
33
|
+
{...props}
|
|
34
|
+
>
|
|
35
|
+
<div className="neptune-chat-message__bubble">
|
|
36
|
+
{lines
|
|
37
|
+
? lines.map((line, i) => <div key={i}>{line || <br />}</div>)
|
|
38
|
+
: message}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatusDot — Animated status indicator dot
|
|
3
|
+
*
|
|
4
|
+
* Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
|
|
5
|
+
*
|
|
6
|
+
* A small colored circle that optionally pulses to indicate live/active status.
|
|
7
|
+
* Used in the BottomBar for "Live Preview" and in status indicators throughout
|
|
8
|
+
* the cockpit.
|
|
9
|
+
*
|
|
10
|
+
* @component
|
|
11
|
+
* @param {Object} props
|
|
12
|
+
* @param {string} [props.status='success'] - Status type: 'success' | 'warning' | 'error' | 'info'
|
|
13
|
+
* @param {boolean} [props.pulse=false] - Whether the dot should animate with a pulse
|
|
14
|
+
* @param {number} [props.size=8] - Diameter in pixels
|
|
15
|
+
* @param {string} [props.color] - Override color (CSS value). If set, ignores status prop for color.
|
|
16
|
+
* @param {string} [props.className]
|
|
17
|
+
* @param {React.CSSProperties} [props.style]
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Pulsing green dot for live status
|
|
21
|
+
* <StatusDot status="success" pulse />
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Static error dot
|
|
25
|
+
* <StatusDot status="error" />
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Custom color and size
|
|
29
|
+
* <StatusDot color="var(--npt-accent-primary)" size={6} />
|
|
30
|
+
*/
|
|
31
|
+
export default function StatusDot({
|
|
32
|
+
status = "success",
|
|
33
|
+
pulse = false,
|
|
34
|
+
size = 8,
|
|
35
|
+
color,
|
|
36
|
+
className = "",
|
|
37
|
+
style = {},
|
|
38
|
+
}) {
|
|
39
|
+
// Dynamic inline styles only for custom size and color override
|
|
40
|
+
const dynamicStyle = {
|
|
41
|
+
width: `${size}px`,
|
|
42
|
+
height: `${size}px`,
|
|
43
|
+
...(color ? { backgroundColor: color } : {}),
|
|
44
|
+
...style,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<span
|
|
49
|
+
className={`neptune-status-dot ${!color ? `neptune-status-dot--${status}` : ""} ${pulse ? "neptune-status-dot--pulse" : ""} ${className}`}
|
|
50
|
+
style={dynamicStyle}
|
|
51
|
+
role="status"
|
|
52
|
+
aria-label={`${status} status${pulse ? " (active)" : ""}`}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|