@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,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
+ }