@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
@@ -0,0 +1,40 @@
1
+ /**
2
+ * StatusIndicator component - Animated status dot with label
3
+ *
4
+ * Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
5
+ *
6
+ * Displays a colored dot with an optional pulse animation and accompanying label.
7
+ * Used to indicate status of processes, services, or system components.
8
+ *
9
+ * @component
10
+ * @param {Object} props - Component props
11
+ * @param {string} props.label - Text label describing the status
12
+ * @param {string} [props.status='running'] - Status state: 'running', 'paused', 'error', 'stopped'
13
+ * @param {boolean} [props.pulse=true] - Whether to show pulse animation (default true for running status)
14
+ * @returns {JSX.Element} Styled status indicator with dot and label
15
+ *
16
+ * @example
17
+ * <StatusIndicator label="API Server" status="running" pulse={true} />
18
+ * <StatusIndicator label="Database" status="error" pulse={false} />
19
+ */
20
+ export default function StatusIndicator({
21
+ label,
22
+ status = "running",
23
+ pulse,
24
+ ...props
25
+ }) {
26
+ // Default pulse behavior: true for running, false for others
27
+ const showPulse = pulse !== undefined ? pulse : status === "running";
28
+
29
+ return (
30
+ <div
31
+ className={`neptune-status-indicator neptune-status-indicator--${status} ${showPulse ? "neptune-status-indicator--pulse" : ""}`}
32
+ {...props}
33
+ >
34
+ <div className="neptune-status-indicator__dot" />
35
+ <span className="neptune-status-indicator__label">
36
+ {label}
37
+ </span>
38
+ </div>
39
+ );
40
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Button component for user interactions
3
+ *
4
+ * Supports multiple variants (primary, secondary, tertiary, ghost, danger) and
5
+ * sizes (sm, default, lg). Can include an icon alongside text via the icon prop.
6
+ *
7
+ * Theme-aware: when rendered inside a data-theme="naia" container, accent
8
+ * colors automatically switch from orange to purple — no variant change needed.
9
+ *
10
+ * Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
11
+ *
12
+ * @component
13
+ * @param {Object} props - Component props
14
+ * @param {string} [props.variant='primary'] - 'primary' | 'secondary' | 'tertiary' | 'ghost' | 'danger'
15
+ * @param {string} [props.size='default'] - 'sm' | 'default' | 'lg'
16
+ * @param {boolean} [props.disabled=false]
17
+ * @param {React.ReactNode} [props.icon] - Optional icon before text
18
+ * @param {React.ReactNode} [props.children] - Button text
19
+ * @param {Function} [props.onClick]
20
+ * @param {string} [props.className]
21
+ * @param {Object} [props.style] - Dynamic inline overrides only
22
+ */
23
+ export default function Button({
24
+ variant = "primary",
25
+ size = "default",
26
+ disabled = false,
27
+ icon,
28
+ children,
29
+ onClick,
30
+ className = "",
31
+ style = {},
32
+ ...props
33
+ }) {
34
+ return (
35
+ <button
36
+ className={`neptune-button neptune-button--${variant} neptune-button--${size} ${
37
+ disabled ? "neptune-button--disabled" : ""
38
+ } ${className}`}
39
+ style={style}
40
+ disabled={disabled}
41
+ onClick={onClick}
42
+ {...props}
43
+ >
44
+ {icon && <span className="neptune-button__icon">{icon}</span>}
45
+ {children && <span className="neptune-button__text">{children}</span>}
46
+ </button>
47
+ );
48
+ }
@@ -0,0 +1,90 @@
1
+ import { Check } from "lucide-react";
2
+
3
+ /**
4
+ * Checkbox component for boolean selection
5
+ * Displays a checkmark icon when checked, supports label
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 {boolean} [props.checked=false] - Whether the checkbox is in the checked 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 checkbox
14
+ * @param {boolean} [props.disabled=false] - Whether the checkbox is disabled
15
+ * @param {string} [props.className] - Additional CSS classes
16
+ * @param {Object} [props.style] - Inline styles
17
+ * @returns {JSX.Element} Styled checkbox element with optional label
18
+ *
19
+ * @example
20
+ * // Basic checkbox
21
+ * <Checkbox
22
+ * checked={isChecked}
23
+ * onChange={(checked) => setIsChecked(checked)}
24
+ * />
25
+ *
26
+ * @example
27
+ * // Checkbox with label
28
+ * <Checkbox
29
+ * label="I agree to the terms"
30
+ * checked={agreed}
31
+ * onChange={(checked) => setAgreed(checked)}
32
+ * />
33
+ *
34
+ * @example
35
+ * // Disabled checkbox
36
+ * <Checkbox
37
+ * label="Unavailable option"
38
+ * checked={false}
39
+ * disabled={true}
40
+ * />
41
+ */
42
+ export default function Checkbox({
43
+ checked = false,
44
+ onChange,
45
+ label,
46
+ disabled = false,
47
+ className = "",
48
+ style = {},
49
+ ...props
50
+ }) {
51
+ const handleChange = () => {
52
+ if (!disabled && onChange) {
53
+ onChange(!checked);
54
+ }
55
+ };
56
+
57
+ return (
58
+ <label
59
+ className={`neptune-checkbox ${checked ? "neptune-checkbox--checked" : ""} ${
60
+ disabled ? "neptune-checkbox--disabled" : ""
61
+ } ${className}`}
62
+ style={style}
63
+ {...props}
64
+ >
65
+ <div className="neptune-checkbox__box">
66
+ {checked && (
67
+ <Check
68
+ size={16}
69
+ strokeWidth={3}
70
+ className="neptune-checkbox__icon"
71
+ />
72
+ )}
73
+ </div>
74
+
75
+ {label && (
76
+ <span className="neptune-checkbox__label">
77
+ {label}
78
+ </span>
79
+ )}
80
+
81
+ <input
82
+ type="checkbox"
83
+ checked={checked}
84
+ onChange={handleChange}
85
+ disabled={disabled}
86
+ className="neptune-checkbox__input"
87
+ />
88
+ </label>
89
+ );
90
+ }
@@ -0,0 +1,64 @@
1
+ import Button from "./Button.jsx";
2
+
3
+ /**
4
+ * FilterBar — Horizontal row of filter pill buttons
5
+ *
6
+ * Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
7
+ *
8
+ * Renders an array of filter labels as Button components. The currently active
9
+ * filter uses the "secondary" variant; inactive filters use "tertiary". All
10
+ * buttons are size="sm". Calls `onChange` with the selected filter string
11
+ * when clicked.
12
+ *
13
+ * Theme-aware: inherits theme from parent container via Button's theme support.
14
+ *
15
+ * @component
16
+ * @param {Object} props - Component props
17
+ * @param {string[]} [props.filters=[]] - Array of filter label strings
18
+ * @param {string} [props.active] - Currently active filter value
19
+ * @param {Function} [props.onChange] - Called with the selected filter string
20
+ * @param {string} [props.className] - Additional CSS classes
21
+ * @param {React.CSSProperties} [props.style] - Additional inline styles
22
+ * @returns {JSX.Element} FilterBar component
23
+ *
24
+ * @example
25
+ * <FilterBar
26
+ * filters={["All", "Published", "Draft", "Archived"]}
27
+ * active="All"
28
+ * onChange={(filter) => console.log("Selected:", filter)}
29
+ * />
30
+ *
31
+ * @example
32
+ * // Inside a Naia-themed container
33
+ * <div data-theme="naia">
34
+ * <FilterBar filters={["Recent", "Starred"]} active="Recent" onChange={setFilter} />
35
+ * </div>
36
+ */
37
+ export default function FilterBar({
38
+ filters = [],
39
+ active,
40
+ onChange,
41
+ className = "",
42
+ style = {},
43
+ }) {
44
+ return (
45
+ <div
46
+ className={`neptune-filter-bar ${className}`}
47
+ style={style}
48
+ role="group"
49
+ aria-label="Filters"
50
+ >
51
+ {filters.map((filter) => (
52
+ <Button
53
+ key={filter}
54
+ variant={filter === active ? "secondary" : "tertiary"}
55
+ size="sm"
56
+ onClick={() => onChange && onChange(filter)}
57
+ aria-pressed={filter === active}
58
+ >
59
+ {filter}
60
+ </Button>
61
+ ))}
62
+ </div>
63
+ );
64
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * IconButton — Icon-only button for toolbars, headers, and compact actions
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.icon - Icon element (required)
9
+ * @param {string} [props.variant='subtle'] - 'subtle' | 'outline' | 'secondary' | 'ghost'
10
+ * @param {string} [props.size='default'] - 'sm' (24px) | 'default' (32px)
11
+ * @param {boolean} [props.disabled=false]
12
+ * @param {string} [props.title] - Tooltip / aria-label
13
+ * @param {Function} [props.onClick]
14
+ * @param {string} [props.className]
15
+ * @param {Object} [props.style] - Dynamic inline overrides only
16
+ */
17
+ export default function IconButton({
18
+ icon,
19
+ variant = "subtle",
20
+ size = "default",
21
+ disabled = false,
22
+ title,
23
+ onClick,
24
+ className = "",
25
+ style = {},
26
+ ...props
27
+ }) {
28
+ return (
29
+ <button
30
+ className={`neptune-icon-button neptune-icon-button--${variant} neptune-icon-button--${size} ${
31
+ disabled ? "neptune-icon-button--disabled" : ""
32
+ } ${className}`}
33
+ style={style}
34
+ disabled={disabled}
35
+ title={title}
36
+ onClick={onClick}
37
+ aria-label={title}
38
+ {...props}
39
+ >
40
+ {icon}
41
+ </button>
42
+ );
43
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * IconToggle — Stateful icon button that toggles between pressed/unpressed
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.icon - Icon element (required)
9
+ * @param {boolean} [props.pressed=false] - Whether pressed/active
10
+ * @param {Function} [props.onToggle] - Toggle handler, receives new boolean
11
+ * @param {string} [props.size='default'] - 'sm' (24px) | 'default' (32px)
12
+ * @param {boolean} [props.disabled=false]
13
+ * @param {string} [props.title] - Tooltip / aria-label
14
+ * @param {string} [props.className]
15
+ * @param {Object} [props.style] - Dynamic inline overrides only
16
+ */
17
+ export default function IconToggle({
18
+ icon,
19
+ pressed = false,
20
+ onToggle,
21
+ size = "default",
22
+ disabled = false,
23
+ title,
24
+ className = "",
25
+ style = {},
26
+ ...props
27
+ }) {
28
+ return (
29
+ <button
30
+ className={`neptune-icon-toggle neptune-icon-toggle--${size} ${pressed ? "neptune-icon-toggle--pressed" : ""} ${
31
+ disabled ? "neptune-icon-toggle--disabled" : ""
32
+ } ${className}`}
33
+ style={style}
34
+ disabled={disabled}
35
+ title={title}
36
+ onClick={() => !disabled && onToggle && onToggle(!pressed)}
37
+ aria-pressed={pressed}
38
+ aria-label={title}
39
+ {...props}
40
+ >
41
+ {icon}
42
+ </button>
43
+ );
44
+ }
@@ -0,0 +1,173 @@
1
+ import { useState, useRef, useEffect, useCallback } from "react";
2
+ import PillSelect from "./PillSelect.jsx";
3
+ import NaiaSendButton from "./NaiaSendButton.jsx";
4
+ import SuggestionPill from "./SuggestionPill.jsx";
5
+
6
+ /**
7
+ * NaiaChatInput — Full chat input area with suggestions, pill selects, and send button
8
+ *
9
+ * Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
10
+ *
11
+ * A themed chat input that composes PillSelect, SuggestionPill, and NaiaSendButton
12
+ * into a cohesive input area for Naia AI panels. The textarea auto-resizes as the
13
+ * user types. Suggestion pills render above the container. Mode, model, and autonomy
14
+ * PillSelects render in the footer alongside the send button.
15
+ *
16
+ * Theme-aware: accent colors adapt automatically in Naia context.
17
+ *
18
+ * @component
19
+ * @param {Object} props
20
+ * @param {string} [props.value=''] - Controlled textarea value
21
+ * @param {Function} [props.onChange] - Called with new string value on input
22
+ * @param {Function} [props.onSend] - Called with trimmed string when user sends (Enter or button)
23
+ * @param {string} [props.placeholder='Ask Naia...'] - Placeholder text
24
+ * @param {string[]} [props.suggestions] - Suggestion pill labels shown above the input
25
+ * @param {Function} [props.onSuggestionClick] - Called with the suggestion string when clicked
26
+ * @param {{value: string, options: Array, onChange: Function}} [props.mode] - Mode PillSelect config
27
+ * @param {{value: string, options: Array, onChange: Function}} [props.model] - Model PillSelect config
28
+ * @param {{value: string, options: Array, onChange: Function}} [props.autonomy] - Autonomy PillSelect config
29
+ * @param {React.ReactNode} [props.modeIcon] - Icon for the mode PillSelect trigger (e.g. NaiaLogo)
30
+ * @param {string} [props.className]
31
+ * @param {React.CSSProperties} [props.style]
32
+ *
33
+ * @example
34
+ * <NaiaChatInput
35
+ * value={msg}
36
+ * onChange={setMsg}
37
+ * onSend={(text) => send(text)}
38
+ * placeholder="Plan, Build, / for commands..."
39
+ * suggestions={["Create a Fiori app", "Check agent errors"]}
40
+ * onSuggestionClick={(s) => send(s)}
41
+ * mode={{ value: "code", options: modeOpts, onChange: setMode }}
42
+ * model={{ value: "opus", options: modelOpts, onChange: setModel }}
43
+ * autonomy={{ value: "balanced", options: autoOpts, onChange: setAutonomy }}
44
+ * modeIcon={<NaiaLogo size={12} />}
45
+ * />
46
+ */
47
+ export default function NaiaChatInput({
48
+ value: controlledValue,
49
+ onChange,
50
+ onSend,
51
+ placeholder = "Ask Naia...",
52
+ suggestions,
53
+ onSuggestionClick,
54
+ mode,
55
+ model,
56
+ autonomy,
57
+ modeIcon,
58
+ className = "",
59
+ style = {},
60
+ }) {
61
+ const [internalValue, setInternalValue] = useState("");
62
+ const [focused, setFocused] = useState(false);
63
+ const textareaRef = useRef(null);
64
+
65
+ const value = controlledValue !== undefined ? controlledValue : internalValue;
66
+
67
+ const updateValue = useCallback(
68
+ (newVal) => {
69
+ if (onChange) onChange(newVal);
70
+ else setInternalValue(newVal);
71
+ },
72
+ [onChange]
73
+ );
74
+
75
+ // Auto-resize textarea (truly dynamic — must stay as inline style)
76
+ useEffect(() => {
77
+ const el = textareaRef.current;
78
+ if (el) {
79
+ el.style.height = "auto";
80
+ el.style.height = Math.min(el.scrollHeight, 140) + "px";
81
+ }
82
+ }, [value]);
83
+
84
+ const handleKeyDown = (e) => {
85
+ if (e.key === "Enter" && !e.shiftKey) {
86
+ e.preventDefault();
87
+ handleSend();
88
+ }
89
+ };
90
+
91
+ const handleSend = () => {
92
+ const trimmed = value.trim();
93
+ if (trimmed && onSend) {
94
+ onSend(trimmed);
95
+ updateValue("");
96
+ }
97
+ };
98
+
99
+ const hasContent = value.trim().length > 0;
100
+ const hasSuggestions = suggestions && suggestions.length > 0;
101
+
102
+ return (
103
+ <div data-theme="naia" className={`neptune-naia-chat-input-area ${className}`}>
104
+ {/* Suggestion pills row */}
105
+ {hasSuggestions && (
106
+ <div className="neptune-naia-chat-input__suggestions">
107
+ {suggestions.map((label, i) => (
108
+ <SuggestionPill
109
+ key={i}
110
+ onClick={() => onSuggestionClick && onSuggestionClick(label)}
111
+ >
112
+ {label}
113
+ </SuggestionPill>
114
+ ))}
115
+ </div>
116
+ )}
117
+
118
+ {/* Rounded container */}
119
+ <div
120
+ className={`neptune-naia-chat-input__container ${focused ? "neptune-naia-chat-input__container--focused" : ""}`}
121
+ style={style}
122
+ >
123
+ {/* Textarea */}
124
+ <div className="neptune-naia-chat-input__textarea-wrap">
125
+ <textarea
126
+ ref={textareaRef}
127
+ value={value}
128
+ onChange={(e) => updateValue(e.target.value)}
129
+ onKeyDown={handleKeyDown}
130
+ onFocus={() => setFocused(true)}
131
+ onBlur={() => setFocused(false)}
132
+ placeholder={placeholder}
133
+ rows={1}
134
+ className="neptune-naia-chat-input__textarea"
135
+ aria-label={placeholder}
136
+ />
137
+ </div>
138
+
139
+ {/* Footer row: pill selects + send button */}
140
+ <div className="neptune-naia-chat-input__footer">
141
+ {mode && (
142
+ <PillSelect
143
+ value={mode.value}
144
+ options={mode.options}
145
+ onChange={mode.onChange}
146
+ icon={modeIcon}
147
+ />
148
+ )}
149
+ {model && (
150
+ <PillSelect
151
+ value={model.value}
152
+ options={model.options}
153
+ onChange={model.onChange}
154
+ />
155
+ )}
156
+ {autonomy && (
157
+ <PillSelect
158
+ value={autonomy.value}
159
+ options={autonomy.options}
160
+ onChange={autonomy.onChange}
161
+ />
162
+ )}
163
+
164
+ <NaiaSendButton
165
+ onClick={handleSend}
166
+ disabled={!hasContent}
167
+ style={{ marginLeft: "auto" }}
168
+ />
169
+ </div>
170
+ </div>
171
+ </div>
172
+ );
173
+ }
@@ -0,0 +1,36 @@
1
+ import { Send } from "lucide-react";
2
+
3
+ /**
4
+ * NaiaSendButton — Circular send button for chat inputs
5
+ *
6
+ * Styles are in neptune-components.css. The `size` prop is dynamic (arbitrary px)
7
+ * so it remains as an inline style.
8
+ *
9
+ * @component
10
+ * @param {Object} props
11
+ * @param {Function} [props.onClick]
12
+ * @param {boolean} [props.disabled=false]
13
+ * @param {number} [props.size=28] - Button diameter in px
14
+ * @param {string} [props.className]
15
+ * @param {Object} [props.style] - Dynamic inline overrides only
16
+ */
17
+ export default function NaiaSendButton({
18
+ onClick,
19
+ disabled = false,
20
+ size = 28,
21
+ className = "",
22
+ style = {},
23
+ }) {
24
+ return (
25
+ <button
26
+ className={`neptune-naia-send-button ${className}`}
27
+ style={{ width: `${size}px`, height: `${size}px`, ...style }}
28
+ onClick={!disabled ? onClick : undefined}
29
+ disabled={disabled}
30
+ aria-label="Send message"
31
+ title="Send message"
32
+ >
33
+ <Send size={13} />
34
+ </button>
35
+ );
36
+ }