@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,89 @@
1
+ import Button from "../inputs/Button.jsx";
2
+ import IconButton from "../inputs/IconButton.jsx";
3
+
4
+ /**
5
+ * Toolbar component - Application toolbar with actions
6
+ *
7
+ * Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
8
+ *
9
+ * Displays a labeled toolbar with optional badge and action buttons.
10
+ * Uses Button and IconButton from the design system for all actions.
11
+ *
12
+ * @component
13
+ * @param {Object} props - Component props
14
+ * @param {string} [props.label="Application"] - Toolbar label text
15
+ * @param {Object} [props.badge] - Badge object with {text} properties
16
+ * @param {string} [props.badge.text] - Badge text content
17
+ * @param {Array} [props.actions=[]] - Array of action objects
18
+ * @param {string} [props.actions[].label] - Action button label (if absent, renders icon-only)
19
+ * @param {React.ReactNode} [props.actions[].icon] - Icon component for button
20
+ * @param {string} [props.actions[].variant] - Button variant: 'primary' | 'secondary' | 'tertiary' | 'icon' | 'icon-outline'
21
+ * @param {Function} [props.actions[].onClick] - Click handler for action
22
+ * @param {React.ReactNode} [props.left] - Custom left-side content (overrides label+badge)
23
+ * @param {React.ReactNode} [props.right] - Custom right-side content (overrides actions)
24
+ * @param {React.ReactNode} [props.children] - Additional content
25
+ * @returns {JSX.Element} Toolbar component
26
+ */
27
+ export default function Toolbar({
28
+ label = "Application",
29
+ badge,
30
+ actions = [],
31
+ left,
32
+ right,
33
+ children,
34
+ }) {
35
+ return (
36
+ <div className="neptune-toolbar">
37
+ {/* Left side */}
38
+ {left || (
39
+ <div className="neptune-toolbar__left">
40
+ <span className="neptune-toolbar__label">
41
+ {label}
42
+ </span>
43
+ {badge && (
44
+ <span className="neptune-toolbar__badge">
45
+ {badge.text}
46
+ </span>
47
+ )}
48
+ </div>
49
+ )}
50
+
51
+ {/* Middle */}
52
+ {children}
53
+
54
+ {/* Right side */}
55
+ {right || (
56
+ <div className="neptune-toolbar__right">
57
+ {actions.map((action, index) => {
58
+ const r6 = { borderRadius: "6px" };
59
+ if (action.variant === "icon" || action.variant === "icon-outline") {
60
+ return (
61
+ <IconButton
62
+ key={index}
63
+ variant={action.variant === "icon-outline" ? "outline" : "subtle"}
64
+ icon={action.icon}
65
+ title={action.label}
66
+ size="sm"
67
+ onClick={action.onClick}
68
+ style={r6}
69
+ />
70
+ );
71
+ }
72
+ return (
73
+ <Button
74
+ key={index}
75
+ variant={action.variant || "secondary"}
76
+ size="sm"
77
+ icon={action.icon}
78
+ onClick={action.onClick}
79
+ style={r6}
80
+ >
81
+ {action.label}
82
+ </Button>
83
+ );
84
+ })}
85
+ </div>
86
+ )}
87
+ </div>
88
+ );
89
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Breadcrumb — Navigation breadcrumb trail
3
+ *
4
+ * Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
5
+ *
6
+ * @component
7
+ * @param {Object} props
8
+ * @param {Array<{label: string, icon?: React.ReactNode, onClick?: Function}>} props.items - Breadcrumb items, last one is active
9
+ * @param {string} [props.className]
10
+ * @param {Object} [props.style]
11
+ *
12
+ * @example
13
+ * <Breadcrumb items={[
14
+ * { label: "Cockpit", onClick: () => navigate("/") },
15
+ * { label: "Application", icon: <FolderOpen size={13} /> },
16
+ * ]} />
17
+ */
18
+ export default function Breadcrumb({
19
+ items = [],
20
+ className = "",
21
+ style = {},
22
+ }) {
23
+ return (
24
+ <nav className={`neptune-breadcrumb ${className}`} style={style} aria-label="Breadcrumb">
25
+ {items.map((item, i) => {
26
+ const isLast = i === items.length - 1;
27
+ return (
28
+ <span key={i} style={{ display: "contents" }}>
29
+ {i > 0 && <span className="neptune-breadcrumb__separator">/</span>}
30
+ <button
31
+ className={`neptune-breadcrumb__item ${isLast ? "neptune-breadcrumb__item--active" : ""}`}
32
+ onClick={!isLast ? item.onClick : undefined}
33
+ aria-current={isLast ? "page" : undefined}
34
+ >
35
+ {item.icon}
36
+ <span>{item.label}</span>
37
+ </button>
38
+ </span>
39
+ );
40
+ })}
41
+ </nav>
42
+ );
43
+ }
@@ -0,0 +1,104 @@
1
+ import { Check } from "lucide-react";
2
+
3
+ /**
4
+ * Dropdown component for menu selection with check mark on active item
5
+ * Displays a list of selectable items with visual indication of the selected item.
6
+ * Supports both simple string items and rich object items with subtitles and extras.
7
+ *
8
+ * Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
9
+ *
10
+ * @component
11
+ * @param {Object} props - Component props
12
+ * @param {Array<string | {id: string, label: string, subtitle?: string, extra?: React.ReactNode}>} props.items - Array of item labels (strings) or rich item objects
13
+ * @param {number} [props.selectedIndex=0] - Index of the currently selected item
14
+ * @param {Function} props.onSelect - Callback fired when an item is selected, receives the item index
15
+ * @param {Function} [props.renderItem] - Custom render function: (item, index, isSelected) => ReactNode. Overrides default rendering when provided.
16
+ * @param {boolean} [props.open=true] - Whether the dropdown is visible
17
+ * @param {string} [props.className] - Additional CSS classes
18
+ * @param {Object} [props.style] - Inline styles
19
+ * @returns {JSX.Element} Styled dropdown menu
20
+ *
21
+ * @example
22
+ * // Basic dropdown with strings (backward compatible)
23
+ * const [selected, setSelected] = useState(0);
24
+ * <Dropdown
25
+ * items={['Option A', 'Option B', 'Option C']}
26
+ * selectedIndex={selected}
27
+ * onSelect={setSelected}
28
+ * />
29
+ *
30
+ * @example
31
+ * // Rich items with subtitles
32
+ * <Dropdown
33
+ * items={[
34
+ * { id: 'a', label: 'Option A', subtitle: 'Description of A' },
35
+ * { id: 'b', label: 'Option B', subtitle: 'Description of B', extra: <Badge label="New" /> },
36
+ * ]}
37
+ * selectedIndex={0}
38
+ * onSelect={setSelected}
39
+ * />
40
+ *
41
+ * @example
42
+ * // Custom renderItem
43
+ * <Dropdown
44
+ * items={myItems}
45
+ * selectedIndex={0}
46
+ * onSelect={setSelected}
47
+ * renderItem={(item, index, isSelected) => (
48
+ * <div>{item.label} {isSelected && '(selected)'}</div>
49
+ * )}
50
+ * />
51
+ */
52
+ export default function Dropdown({
53
+ items = [],
54
+ selectedIndex = 0,
55
+ onSelect,
56
+ renderItem,
57
+ open = true,
58
+ className = "",
59
+ style = {},
60
+ ...props
61
+ }) {
62
+ return (
63
+ <div
64
+ className={`neptune-dropdown ${!open ? "neptune-dropdown--closed" : ""} ${className}`}
65
+ style={style}
66
+ {...props}
67
+ >
68
+ {items.map((item, index) => {
69
+ const isSelected = index === selectedIndex;
70
+ const isString = typeof item === "string";
71
+ const label = isString ? item : item.label;
72
+ const subtitle = isString ? undefined : item.subtitle;
73
+ const extra = isString ? undefined : item.extra;
74
+
75
+ return (
76
+ <button
77
+ key={isString ? index : (item.id || index)}
78
+ className={`neptune-dropdown__item ${isSelected ? "neptune-dropdown__item--selected" : ""}`}
79
+ onClick={() => onSelect && onSelect(index)}
80
+ >
81
+ {renderItem ? (
82
+ renderItem(item, index, isSelected)
83
+ ) : (
84
+ <>
85
+ <div className="neptune-dropdown__item-content">
86
+ <span className="neptune-dropdown__item-label">{label}</span>
87
+ {subtitle && <span className="neptune-dropdown__item-subtitle">{subtitle}</span>}
88
+ </div>
89
+ <div className="neptune-dropdown__item-right">
90
+ {extra && <span className="neptune-dropdown__item-extra">{extra}</span>}
91
+ {isSelected && (
92
+ <span className="neptune-dropdown__item-check">
93
+ <Check size={16} />
94
+ </span>
95
+ )}
96
+ </div>
97
+ </>
98
+ )}
99
+ </button>
100
+ );
101
+ })}
102
+ </div>
103
+ );
104
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * SidebarNav component for main navigation sidebar with expanded and collapsed modes
3
+ * Supports both icon + label display (expanded) and icon-only (collapsed) layouts.
4
+ * Items can include dividers via `{type: "divider"}` to visually separate nav sections.
5
+ * Optional header and footer props for logo/branding and bottom-anchored controls.
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 {Array<{label: string, icon: React.ReactNode, active?: boolean} | {type: "divider"}>} props.items - Navigation items with labels and icons, or divider objects
12
+ * @param {boolean} [props.collapsed=false] - Whether sidebar is in collapsed mode (icon-only)
13
+ * @param {Function} [props.onItemClick] - Callback fired when a nav item is clicked, receives (item, index)
14
+ * @param {React.ReactNode} [props.header] - Content rendered at the top of the nav (e.g. logo area)
15
+ * @param {React.ReactNode} [props.footer] - Content rendered at the bottom of the nav (e.g. settings, collapse toggle)
16
+ * @param {string} [props.className] - Additional CSS classes
17
+ * @param {Object} [props.style] - Inline styles
18
+ * @returns {JSX.Element} Styled sidebar navigation component
19
+ *
20
+ * @example
21
+ * // With header and footer
22
+ * <SidebarNav
23
+ * items={[
24
+ * { label: 'Home', icon: <HomeIcon />, active: true },
25
+ * { label: 'Projects', icon: <AppWindowIcon /> },
26
+ * { type: 'divider' },
27
+ * { label: 'Settings', icon: <SettingsIcon /> },
28
+ * ]}
29
+ * header={<div className="logo-area">Neptune</div>}
30
+ * footer={<button>Collapse</button>}
31
+ * collapsed={false}
32
+ * onItemClick={(item, index) => console.log(item.label)}
33
+ * />
34
+ */
35
+ export default function SidebarNav({
36
+ items = [],
37
+ collapsed = false,
38
+ onItemClick,
39
+ header,
40
+ footer,
41
+ className = "",
42
+ style = {},
43
+ ...props
44
+ }) {
45
+ return (
46
+ <nav
47
+ className={`neptune-sidebar-nav ${collapsed ? "neptune-sidebar-nav--collapsed" : ""} ${className}`}
48
+ style={style}
49
+ {...props}
50
+ >
51
+ {header && (
52
+ <div className="neptune-sidebar-nav__header">{header}</div>
53
+ )}
54
+ <div className="neptune-sidebar-nav__items">
55
+ {items.map((item, index) => {
56
+ if (item.type === "divider") {
57
+ return <div key={`divider-${index}`} className="neptune-sidebar-nav__divider" />;
58
+ }
59
+ const isActive = item.active ?? false;
60
+ return (
61
+ <button
62
+ key={index}
63
+ className={`neptune-sidebar-nav__item ${isActive ? "neptune-sidebar-nav__item--active" : ""}`}
64
+ onClick={() => onItemClick && onItemClick(item, index)}
65
+ title={collapsed ? item.label : undefined}
66
+ >
67
+ <span className="neptune-sidebar-nav__icon">
68
+ {item.icon}
69
+ </span>
70
+ {!collapsed && (
71
+ <span className="neptune-sidebar-nav__label">{item.label}</span>
72
+ )}
73
+ </button>
74
+ );
75
+ })}
76
+ </div>
77
+ {footer && (
78
+ <div className="neptune-sidebar-nav__footer">{footer}</div>
79
+ )}
80
+ </nav>
81
+ );
82
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * SidebarTabs component for tab switching within sidebar panels
3
+ * Displays tabs in horizontal pill-style layout when expanded,
4
+ * and as a vertical icon column when collapsed.
5
+ * Tabs can be simple strings or objects with icon, label, activeColor, and badge.
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 {Array<string | {label: string, icon?: React.ReactNode, activeColor?: string, badge?: React.ReactNode}>} props.tabs
12
+ * Array of tab labels (strings) or objects with label, optional icon, optional activeColor, optional badge
13
+ * @param {number} [props.activeIndex=0] - Index of the currently active tab
14
+ * @param {Function} props.onChange - Callback fired when a tab is clicked, receives the new index
15
+ * @param {boolean} [props.collapsed=false] - Whether sidebar is in collapsed mode (icon-only vertical)
16
+ * @param {string} [props.className] - Additional CSS classes
17
+ * @param {Object} [props.style] - Inline styles
18
+ * @returns {JSX.Element} Styled sidebar tab switcher
19
+ *
20
+ * @example
21
+ * // String tabs (backward compatible)
22
+ * const [activeTab, setActiveTab] = useState(0);
23
+ * <SidebarTabs
24
+ * tabs={['Files', 'Pages', 'Components']}
25
+ * activeIndex={activeTab}
26
+ * onChange={setActiveTab}
27
+ * collapsed={false}
28
+ * />
29
+ *
30
+ * @example
31
+ * // Tabs with icons, custom active color, and badges
32
+ * <SidebarTabs
33
+ * tabs={[
34
+ * { label: 'Naia', icon: <NaiaLogo size={11} />, activeColor: 'var(--npt-accent-secondary)' },
35
+ * { label: 'More Agents', icon: <BotIcon size={11} /> },
36
+ * { label: 'Versions', icon: <HistoryIcon size={11} />, badge: '5' },
37
+ * ]}
38
+ * activeIndex={activeTab}
39
+ * onChange={setActiveTab}
40
+ * />
41
+ */
42
+ export default function SidebarTabs({
43
+ tabs = [],
44
+ activeIndex = 0,
45
+ onChange,
46
+ collapsed = false,
47
+ className = "",
48
+ style = {},
49
+ ...props
50
+ }) {
51
+ return (
52
+ <div
53
+ className={`neptune-sidebar-tabs ${collapsed ? "neptune-sidebar-tabs--collapsed" : ""} ${className}`}
54
+ style={style}
55
+ {...props}
56
+ >
57
+ {tabs.map((tab, index) => {
58
+ const isString = typeof tab === "string";
59
+ const label = isString ? tab : tab.label;
60
+ const icon = isString ? undefined : tab.icon;
61
+ const activeColor = isString ? undefined : tab.activeColor;
62
+ const badge = isString ? undefined : tab.badge;
63
+ const isActive = index === activeIndex;
64
+
65
+ // If this tab has a custom activeColor and is active, apply it to the bottom border
66
+ const tabStyle = isActive && activeColor
67
+ ? { borderBottomColor: activeColor }
68
+ : undefined;
69
+
70
+ // For the icon, apply activeColor when active
71
+ const iconStyle = isActive && activeColor
72
+ ? { color: activeColor }
73
+ : undefined;
74
+
75
+ return (
76
+ <button
77
+ key={index}
78
+ className={`neptune-sidebar-tabs__tab ${isActive ? "neptune-sidebar-tabs__tab--active" : ""}`}
79
+ style={tabStyle}
80
+ onClick={() => onChange && onChange(index)}
81
+ title={collapsed ? label : undefined}
82
+ >
83
+ {collapsed ? (
84
+ icon || label.charAt(0).toUpperCase()
85
+ ) : (
86
+ <>
87
+ {icon && <span className="neptune-sidebar-tabs__icon" style={iconStyle}>{icon}</span>}
88
+ {label}
89
+ {badge != null && (
90
+ <span className="neptune-sidebar-tabs__badge">{badge}</span>
91
+ )}
92
+ </>
93
+ )}
94
+ </button>
95
+ );
96
+ })}
97
+ </div>
98
+ );
99
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * TabBar component for horizontal tab navigation with underline active indicator
3
+ * Displays a row of tabs with visual indication of the active tab.
4
+ * Tabs can be simple strings or objects with icon and label.
5
+ *
6
+ * Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
7
+ *
8
+ * @component
9
+ * @param {Object} props - Component props
10
+ * @param {Array<string | {label: string, icon?: React.ReactNode}>} props.tabs - Array of tab labels (strings) or objects with label and optional icon
11
+ * @param {number} [props.activeIndex=0] - Index of the currently active tab
12
+ * @param {Function} props.onChange - Callback fired when a tab is clicked, receives the new index
13
+ * @param {string} [props.className] - Additional CSS classes
14
+ * @param {Object} [props.style] - Inline styles
15
+ * @returns {JSX.Element} Styled horizontal tab bar with underline indicator
16
+ *
17
+ * @example
18
+ * // Basic usage with strings (backward compatible)
19
+ * const [activeTab, setActiveTab] = useState(0);
20
+ * <TabBar tabs={['Overview', 'Details', 'Settings']} activeIndex={activeTab} onChange={setActiveTab} />
21
+ *
22
+ * @example
23
+ * // Tabs with icons
24
+ * <TabBar
25
+ * tabs={[
26
+ * { label: 'Pages', icon: <LayoutIcon size={13} /> },
27
+ * { label: 'Scripts', icon: <CodeIcon size={13} /> },
28
+ * ]}
29
+ * activeIndex={activeTab}
30
+ * onChange={setActiveTab}
31
+ * />
32
+ */
33
+ export default function TabBar({
34
+ tabs = [],
35
+ activeIndex = 0,
36
+ onChange,
37
+ className = "",
38
+ style = {},
39
+ ...props
40
+ }) {
41
+ return (
42
+ <div className={`neptune-tabbar ${className}`} style={style} {...props}>
43
+ {tabs.map((tab, index) => {
44
+ const isString = typeof tab === "string";
45
+ const label = isString ? tab : tab.label;
46
+ const icon = isString ? undefined : tab.icon;
47
+
48
+ return (
49
+ <button
50
+ key={index}
51
+ className={`neptune-tabbar__tab ${index === activeIndex ? "neptune-tabbar__tab--active" : ""}`}
52
+ onClick={() => onChange && onChange(index)}
53
+ >
54
+ {icon && <span className="neptune-tabbar__icon">{icon}</span>}
55
+ {label}
56
+ </button>
57
+ );
58
+ })}
59
+ </div>
60
+ );
61
+ }
@@ -0,0 +1,101 @@
1
+ import { useEffect, useCallback } from "react";
2
+
3
+ /**
4
+ * Modal — General-purpose modal overlay
5
+ *
6
+ * Styles are in neptune-components.css. Only pass `style` for truly dynamic overrides.
7
+ *
8
+ * A centered overlay dialog with a backdrop, rounded panel, header with title
9
+ * and close button. Closes on Escape key press and backdrop click.
10
+ * Uses surface-overlay for background, border-default for the border, and
11
+ * shadow-xl for elevation.
12
+ *
13
+ * @component
14
+ * @param {Object} props - Component props
15
+ * @param {boolean} [props.open=false] - Whether the modal is visible
16
+ * @param {Function} [props.onClose] - Called when the modal should close (Escape, backdrop, close button)
17
+ * @param {string} [props.title] - Header title text
18
+ * @param {React.ReactNode} [props.children] - Modal body content
19
+ * @param {string} [props.width='480px'] - Panel width (CSS value)
20
+ * @param {string} [props.className] - Additional CSS classes for the panel
21
+ * @param {React.CSSProperties} [props.style] - Additional inline styles for the panel
22
+ * @returns {JSX.Element|null} Modal component or null when closed
23
+ *
24
+ * @example
25
+ * <Modal open={isOpen} onClose={() => setIsOpen(false)} title="Confirm">
26
+ * <p>Are you sure?</p>
27
+ * </Modal>
28
+ *
29
+ * @example
30
+ * <Modal open={true} onClose={handleClose} title="Settings" width="600px">
31
+ * <SettingsForm />
32
+ * </Modal>
33
+ */
34
+ export default function Modal({
35
+ open = false,
36
+ onClose,
37
+ title,
38
+ children,
39
+ width = "480px",
40
+ className = "",
41
+ style = {},
42
+ }) {
43
+ const handleKeyDown = useCallback(
44
+ (e) => {
45
+ if (e.key === "Escape" && onClose) {
46
+ onClose();
47
+ }
48
+ },
49
+ [onClose]
50
+ );
51
+
52
+ useEffect(() => {
53
+ if (open) {
54
+ document.addEventListener("keydown", handleKeyDown);
55
+ return () => document.removeEventListener("keydown", handleKeyDown);
56
+ }
57
+ }, [open, handleKeyDown]);
58
+
59
+ if (!open) return null;
60
+
61
+ const handleBackdropClick = (e) => {
62
+ if (e.target === e.currentTarget && onClose) {
63
+ onClose();
64
+ }
65
+ };
66
+
67
+ return (
68
+ <div
69
+ className="neptune-modal-backdrop"
70
+ onClick={handleBackdropClick}
71
+ role="dialog"
72
+ aria-modal="true"
73
+ aria-label={title || "Dialog"}
74
+ >
75
+ <div
76
+ className={`neptune-modal ${className}`}
77
+ style={{ width, ...style }}
78
+ >
79
+ {title && (
80
+ <div className="neptune-modal__header">
81
+ <h2 className="neptune-modal__title">{title}</h2>
82
+ <button
83
+ className="neptune-modal__close"
84
+ onClick={onClose}
85
+ aria-label="Close dialog"
86
+ title="Close"
87
+ >
88
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
89
+ <line x1="18" y1="6" x2="6" y2="18" />
90
+ <line x1="6" y1="6" x2="18" y2="18" />
91
+ </svg>
92
+ </button>
93
+ </div>
94
+ )}
95
+ <div className="neptune-modal__body">
96
+ {children}
97
+ </div>
98
+ </div>
99
+ </div>
100
+ );
101
+ }