@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.
Files changed (42) hide show
  1. package/README.md +2 -0
  2. package/components/data-display/AppPreview.jsx +150 -0
  3. package/components/data-display/DataTable.jsx +65 -0
  4. package/components/data-display/FileTree.jsx +123 -0
  5. package/components/data-display/KpiCard.jsx +57 -0
  6. package/components/data-display/VersionRow.jsx +103 -0
  7. package/components/feedback/Avatar.jsx +28 -0
  8. package/components/feedback/Badge.jsx +32 -0
  9. package/components/feedback/ChatMessage.jsx +42 -0
  10. package/components/feedback/StatusDot.jsx +55 -0
  11. package/components/feedback/StatusIndicator.jsx +40 -0
  12. package/components/inputs/Button.jsx +48 -0
  13. package/components/inputs/Checkbox.jsx +90 -0
  14. package/components/inputs/FilterBar.jsx +64 -0
  15. package/components/inputs/IconButton.jsx +43 -0
  16. package/components/inputs/IconToggle.jsx +44 -0
  17. package/components/inputs/NaiaChatInput.jsx +173 -0
  18. package/components/inputs/NaiaSendButton.jsx +36 -0
  19. package/components/inputs/PillSelect.jsx +175 -0
  20. package/components/inputs/PropertyField.jsx +58 -0
  21. package/components/inputs/SuggestionPill.jsx +28 -0
  22. package/components/inputs/TextInput.jsx +96 -0
  23. package/components/inputs/Toggle.jsx +73 -0
  24. package/components/layout/AppHeader.jsx +56 -0
  25. package/components/layout/BottomBar.jsx +81 -0
  26. package/components/layout/Card.jsx +57 -0
  27. package/components/layout/Panel.jsx +26 -0
  28. package/components/layout/Toolbar.jsx +89 -0
  29. package/components/navigation/Breadcrumb.jsx +43 -0
  30. package/components/navigation/Dropdown.jsx +104 -0
  31. package/components/navigation/SidebarNav.jsx +82 -0
  32. package/components/navigation/SidebarTabs.jsx +99 -0
  33. package/components/navigation/TabBar.jsx +61 -0
  34. package/components/overlays/Modal.jsx +101 -0
  35. package/components/shared/index.jsx +112 -0
  36. package/index.css +3 -0
  37. package/index.js +50 -0
  38. package/neptune-components.css +1771 -0
  39. package/package.json +45 -0
  40. package/registry.json +1215 -0
  41. package/tokens/neptune-design-tokens.css +730 -0
  42. package/tokens/neptune-design-tokens.json +191 -0
package/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # neptune-design-system
2
+ Design System for Neptune Cockpit 2.0
@@ -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
+ }