@1889ca/ui 0.1.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.
@@ -0,0 +1,111 @@
1
+ /** Contract: contracts/packages-ui-components/rules.md */
2
+
3
+ .ui-item-card-wrap {
4
+ position: relative;
5
+ cursor: pointer;
6
+ user-select: none;
7
+ }
8
+
9
+ .ui-item-card-wrap[draggable='true'] {
10
+ cursor: grab;
11
+ }
12
+
13
+ .ui-item-card-wrap[draggable='true']:active {
14
+ cursor: grabbing;
15
+ }
16
+
17
+ .ui-item-card-wrap.dragging {
18
+ opacity: 0.4;
19
+ }
20
+
21
+ .ui-item-card--selected .ui-item-card {
22
+ border-color: var(--ui-accent);
23
+ box-shadow:
24
+ 0 0 0 1px var(--ui-accent),
25
+ 0 0 24px var(--ui-accent-glow),
26
+ inset 0 0 20px rgba(124, 108, 240, 0.06);
27
+ }
28
+
29
+ .ui-item-card--selected .ui-item-card-thumb::after {
30
+ content: '';
31
+ position: absolute;
32
+ inset: 0;
33
+ background: linear-gradient(135deg, rgba(124, 108, 240, 0.12), transparent);
34
+ pointer-events: none;
35
+ }
36
+
37
+ .ui-item-card {
38
+ display: flex;
39
+ flex-direction: column;
40
+ transition: border-color 0.2s var(--ui-ease), transform 0.2s var(--ui-ease-spring), box-shadow 0.2s var(--ui-ease);
41
+ padding: 0;
42
+ overflow: hidden;
43
+ border-radius: var(--ui-radius);
44
+ background: var(--ui-surface);
45
+ backdrop-filter: blur(var(--ui-glass-blur));
46
+ border: 1px solid var(--ui-border);
47
+ border-top-color: var(--ui-border-strong);
48
+ }
49
+
50
+ .ui-item-card:hover {
51
+ border-color: var(--ui-border-strong);
52
+ transform: translateY(-2px) scale(1.01);
53
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
54
+ }
55
+
56
+ .ui-item-card-thumb {
57
+ position: relative;
58
+ aspect-ratio: 1;
59
+ background: var(--ui-surface-solid);
60
+ overflow: hidden;
61
+ border-bottom: 1px solid var(--ui-border-subtle);
62
+ }
63
+
64
+ .ui-item-card-thumb img {
65
+ width: 100%;
66
+ height: 100%;
67
+ object-fit: cover;
68
+ transition: transform 0.3s var(--ui-ease);
69
+ }
70
+
71
+ .ui-item-card:hover .ui-item-card-thumb img {
72
+ transform: scale(1.04);
73
+ }
74
+
75
+ .ui-item-card-thumb-empty {
76
+ width: 100%;
77
+ height: 100%;
78
+ background:
79
+ radial-gradient(
80
+ circle at 50% 40%,
81
+ var(--ui-accent-subtle),
82
+ transparent 70%
83
+ ),
84
+ var(--ui-surface-solid);
85
+ }
86
+
87
+ .ui-item-card-info {
88
+ display: flex;
89
+ align-items: baseline;
90
+ gap: 0.4rem;
91
+ padding: 0.5rem 0.6rem;
92
+ }
93
+
94
+ .ui-item-card-name {
95
+ font-size: 0.8rem;
96
+ font-weight: 600;
97
+ color: var(--ui-text);
98
+ white-space: nowrap;
99
+ overflow: hidden;
100
+ text-overflow: ellipsis;
101
+ flex: 1;
102
+ min-width: 0;
103
+ }
104
+
105
+ .ui-item-card-meta {
106
+ font-size: 0.65rem;
107
+ color: var(--ui-text-subtle);
108
+ white-space: nowrap;
109
+ flex-shrink: 0;
110
+ font-variant-numeric: tabular-nums;
111
+ }
@@ -0,0 +1,42 @@
1
+ /** Contract: contracts/packages-ui-components/rules.md */
2
+
3
+ /**
4
+ * Grid card for an item (project, file, etc).
5
+ * Shows a square thumbnail, name, and optional metadata.
6
+ *
7
+ * @param {string} thumbnail - Image URL (null = empty state)
8
+ * @param {string} name - Item name
9
+ * @param {string} meta - Secondary text (e.g. "2K · 1:1")
10
+ * @param {boolean} selected - Selection highlight
11
+ * @param {boolean} draggable - Enable drag
12
+ * @param {function} onClick - Click handler (receives event)
13
+ * @param {function} onDoubleClick - Double-click to open
14
+ * @param {function} onContextMenu - Right-click handler
15
+ * @param {function} onDragStart - Drag start handler
16
+ * @param {function} onDragEnd - Drag end handler
17
+ */
18
+ export default function ItemCard({ thumbnail, name, meta, selected, draggable, onClick, onDoubleClick, onContextMenu, onDragStart, onDragEnd }) {
19
+ return (
20
+ <li
21
+ className={`ui-item-card-wrap${selected ? ' ui-item-card--selected' : ''}`}
22
+ draggable={draggable}
23
+ onClick={onClick}
24
+ onDoubleClick={onDoubleClick}
25
+ onContextMenu={onContextMenu}
26
+ onDragStart={onDragStart}
27
+ onDragEnd={onDragEnd}
28
+ >
29
+ <div className="ui-item-card">
30
+ <div className="ui-item-card-thumb">
31
+ {thumbnail
32
+ ? <img src={thumbnail} alt={name} draggable={false} />
33
+ : <div className="ui-item-card-thumb-empty" />}
34
+ </div>
35
+ <div className="ui-item-card-info">
36
+ <span className="ui-item-card-name">{name}</span>
37
+ {meta && <span className="ui-item-card-meta">{meta}</span>}
38
+ </div>
39
+ </div>
40
+ </li>
41
+ );
42
+ }
@@ -0,0 +1,13 @@
1
+ /** Contract: contracts/packages-ui-components/rules.md */
2
+
3
+ .ui-item-grid {
4
+ list-style: none;
5
+ display: grid;
6
+ grid-template-columns: repeat(auto-fill, minmax(var(--ui-grid-min, 160px), 1fr));
7
+ gap: 0.75rem;
8
+ }
9
+
10
+ .ui-item-grid--drop-active {
11
+ background: var(--ui-accent-subtle);
12
+ border-radius: var(--ui-radius);
13
+ }
@@ -0,0 +1,31 @@
1
+ /** Contract: contracts/packages-ui-components/rules.md */
2
+
3
+ /**
4
+ * Responsive grid for items (projects, files, etc).
5
+ * Click on empty space deselects. Supports drag-over styling for folder drops.
6
+ *
7
+ * @param {string} minItemWidth - CSS min-width for grid items (default: '160px')
8
+ * @param {boolean} dropActive - Shows drop-target highlight
9
+ * @param {function} onGridClick - Called when clicking empty grid area
10
+ * @param {function} onDragOver - Drag over handler for folder-drop
11
+ * @param {function} onDragLeave - Drag leave handler
12
+ * @param {function} onDrop - Drop handler
13
+ */
14
+ export default function ItemGrid({ minItemWidth = '160px', dropActive, onGridClick, onDragOver, onDragLeave, onDrop, children }) {
15
+ function handleClick(e) {
16
+ if (e.target === e.currentTarget && onGridClick) onGridClick(e);
17
+ }
18
+
19
+ return (
20
+ <ul
21
+ className={`ui-item-grid${dropActive ? ' ui-item-grid--drop-active' : ''}`}
22
+ style={{ '--ui-grid-min': minItemWidth }}
23
+ onClick={handleClick}
24
+ onDragOver={onDragOver}
25
+ onDragLeave={onDragLeave}
26
+ onDrop={onDrop}
27
+ >
28
+ {children}
29
+ </ul>
30
+ );
31
+ }
@@ -0,0 +1,80 @@
1
+ /** Contract: contracts/packages-ui-components/rules.md */
2
+
3
+ .ui-lightbox-backdrop {
4
+ position: fixed;
5
+ inset: 0;
6
+ z-index: 200;
7
+ background: rgba(0, 0, 0, 0.85);
8
+ backdrop-filter: blur(8px);
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ cursor: pointer;
13
+ animation: ui-lightbox-fade-in 0.2s var(--ui-ease);
14
+ }
15
+
16
+ @keyframes ui-lightbox-fade-in {
17
+ from { opacity: 0; }
18
+ to { opacity: 1; }
19
+ }
20
+
21
+ .ui-lightbox-content {
22
+ display: flex;
23
+ flex-direction: column;
24
+ align-items: center;
25
+ gap: 1rem;
26
+ max-width: 90vw;
27
+ max-height: 90vh;
28
+ cursor: default;
29
+ animation: ui-lightbox-img-in 0.3s var(--ui-ease-spring);
30
+ }
31
+
32
+ @keyframes ui-lightbox-img-in {
33
+ from { opacity: 0; transform: scale(0.94); }
34
+ to { opacity: 1; transform: scale(1); }
35
+ }
36
+
37
+ .ui-lightbox-img {
38
+ max-width: 90vw;
39
+ max-height: 80vh;
40
+ object-fit: contain;
41
+ border-radius: var(--ui-radius-lg);
42
+ box-shadow: 0 20px 80px rgba(0, 0, 0, 0.6), 0 0 1px rgba(255, 255, 255, 0.1);
43
+ }
44
+
45
+ .ui-lightbox-caption {
46
+ font-size: 0.85rem;
47
+ color: var(--ui-text-muted);
48
+ }
49
+
50
+ .ui-lightbox-nav {
51
+ position: fixed;
52
+ top: 50%;
53
+ transform: translateY(-50%);
54
+ background: rgba(18, 18, 24, 0.6);
55
+ backdrop-filter: blur(var(--ui-glass-blur));
56
+ border: 1px solid var(--ui-border);
57
+ color: var(--ui-text);
58
+ width: 44px;
59
+ height: 44px;
60
+ border-radius: 50%;
61
+ display: flex;
62
+ align-items: center;
63
+ justify-content: center;
64
+ cursor: pointer;
65
+ transition: all 0.2s var(--ui-ease);
66
+ }
67
+
68
+ .ui-lightbox-nav:hover {
69
+ background: rgba(18, 18, 24, 0.85);
70
+ border-color: var(--ui-border-strong);
71
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.4);
72
+ }
73
+
74
+ .ui-lightbox-nav svg {
75
+ width: 20px;
76
+ height: 20px;
77
+ }
78
+
79
+ .ui-lightbox-prev { left: 1.5rem; }
80
+ .ui-lightbox-next { right: 1.5rem; }
@@ -0,0 +1,41 @@
1
+ /** Contract: contracts/packages-ui-components/rules.md */
2
+ import { useEffect, useCallback } from 'react';
3
+
4
+ /**
5
+ * Fullscreen image viewer with keyboard navigation.
6
+ * Space/Escape closes, ArrowLeft/ArrowRight navigates.
7
+ */
8
+ export default function Lightbox({ src, name, onClose, onPrev, onNext }) {
9
+ const handleKey = useCallback((e) => {
10
+ if (e.key === 'Escape' || e.key === ' ') {
11
+ e.preventDefault();
12
+ onClose();
13
+ }
14
+ if (e.key === 'ArrowLeft' && onPrev) onPrev();
15
+ if (e.key === 'ArrowRight' && onNext) onNext();
16
+ }, [onClose, onPrev, onNext]);
17
+
18
+ useEffect(() => {
19
+ document.addEventListener('keydown', handleKey);
20
+ return () => document.removeEventListener('keydown', handleKey);
21
+ }, [handleKey]);
22
+
23
+ return (
24
+ <div className="ui-lightbox-backdrop" onClick={onClose}>
25
+ <div className="ui-lightbox-content" onClick={(e) => e.stopPropagation()}>
26
+ <img src={src} alt={name} className="ui-lightbox-img" />
27
+ {name && <div className="ui-lightbox-caption">{name}</div>}
28
+ </div>
29
+ {onPrev && (
30
+ <button className="ui-lightbox-nav ui-lightbox-prev" onClick={(e) => { e.stopPropagation(); onPrev(); }}>
31
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M15 18l-6-6 6-6" /></svg>
32
+ </button>
33
+ )}
34
+ {onNext && (
35
+ <button className="ui-lightbox-nav ui-lightbox-next" onClick={(e) => { e.stopPropagation(); onNext(); }}>
36
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M9 18l6-6-6-6" /></svg>
37
+ </button>
38
+ )}
39
+ </div>
40
+ );
41
+ }
@@ -0,0 +1,72 @@
1
+ /** Contract: contracts/packages-ui-components/rules.md */
2
+
3
+ .ui-modal-backdrop {
4
+ position: fixed;
5
+ inset: 0;
6
+ background: rgba(0, 0, 0, 0.5);
7
+ backdrop-filter: blur(4px);
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ z-index: 100;
12
+ animation: ui-modal-bg-in 0.2s var(--ui-ease);
13
+ }
14
+
15
+ @keyframes ui-modal-bg-in {
16
+ from { opacity: 0; }
17
+ to { opacity: 1; }
18
+ }
19
+
20
+ .ui-modal {
21
+ background: rgba(18, 18, 24, 0.85);
22
+ backdrop-filter: blur(var(--ui-glass-blur-heavy));
23
+ border: 1px solid var(--ui-border);
24
+ border-top-color: var(--ui-border-strong);
25
+ border-radius: var(--ui-radius-lg);
26
+ width: 320px;
27
+ box-shadow: var(--ui-shadow-float);
28
+ animation: ui-modal-in 0.25s var(--ui-ease-spring);
29
+ }
30
+
31
+ @keyframes ui-modal-in {
32
+ from { opacity: 0; transform: scale(0.95) translateY(8px); }
33
+ to { opacity: 1; transform: scale(1) translateY(0); }
34
+ }
35
+
36
+ .ui-modal-header {
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: space-between;
40
+ padding: 1rem 1.25rem;
41
+ border-bottom: 1px solid var(--ui-border);
42
+ }
43
+
44
+ .ui-modal-title {
45
+ font-size: 0.9rem;
46
+ font-weight: 600;
47
+ color: var(--ui-text);
48
+ }
49
+
50
+ .ui-modal-close {
51
+ background: none;
52
+ border: none;
53
+ color: var(--ui-text-subtle);
54
+ font-size: 1.2rem;
55
+ cursor: pointer;
56
+ padding: 0.2rem;
57
+ line-height: 1;
58
+ transition: color 0.15s;
59
+ border-radius: var(--ui-radius-sm);
60
+ }
61
+
62
+ .ui-modal-close:hover {
63
+ color: var(--ui-text);
64
+ background: var(--ui-surface-raised);
65
+ }
66
+
67
+ .ui-modal-body {
68
+ display: flex;
69
+ flex-direction: column;
70
+ gap: 0.75rem;
71
+ padding: 1.25rem;
72
+ }
@@ -0,0 +1,23 @@
1
+ /** Contract: contracts/packages-ui-components/rules.md */
2
+
3
+ /**
4
+ * Generic modal with backdrop, title bar, and close button.
5
+ * Renders children in the body. Closes on backdrop click.
6
+ */
7
+ export default function Modal({ open, title, onClose, width, children }) {
8
+ if (!open) return null;
9
+
10
+ return (
11
+ <div className="ui-modal-backdrop" onClick={onClose}>
12
+ <div className="ui-modal" style={width ? { width } : undefined} onClick={(e) => e.stopPropagation()}>
13
+ <div className="ui-modal-header">
14
+ <span className="ui-modal-title">{title}</span>
15
+ <button className="ui-modal-close" type="button" onClick={onClose}>×</button>
16
+ </div>
17
+ <div className="ui-modal-body">
18
+ {children}
19
+ </div>
20
+ </div>
21
+ </div>
22
+ );
23
+ }
@@ -0,0 +1,76 @@
1
+ /** Contract: contracts/packages-ui-components/rules.md */
2
+
3
+ .ui-page-header {
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: space-between;
7
+ padding: 0.75rem 1.5rem;
8
+ border-bottom: 1px solid var(--ui-border);
9
+ background: rgba(8, 8, 12, 0.75);
10
+ backdrop-filter: blur(var(--ui-glass-blur-heavy));
11
+ box-shadow: 0 1px 0 rgba(255, 255, 255, 0.03);
12
+ position: sticky;
13
+ top: 0;
14
+ z-index: 10;
15
+ }
16
+
17
+ .ui-page-header-left {
18
+ display: flex;
19
+ align-items: center;
20
+ gap: 0.5rem;
21
+ min-width: 0;
22
+ }
23
+
24
+ .ui-page-header-actions {
25
+ display: flex;
26
+ align-items: center;
27
+ gap: 0.5rem;
28
+ }
29
+
30
+ /* Breadcrumbs */
31
+
32
+ .ui-breadcrumb {
33
+ display: flex;
34
+ align-items: center;
35
+ gap: 0.4rem;
36
+ font-size: 0.85rem;
37
+ }
38
+
39
+ .ui-breadcrumb-segment {
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 0.4rem;
43
+ }
44
+
45
+ .ui-breadcrumb-sep {
46
+ color: var(--ui-text-subtle);
47
+ font-size: 0.8rem;
48
+ }
49
+
50
+ .ui-breadcrumb-link {
51
+ color: var(--ui-text-muted);
52
+ font-size: 0.85rem;
53
+ cursor: pointer;
54
+ transition: color 0.2s var(--ui-ease);
55
+ }
56
+
57
+ .ui-breadcrumb-link:hover {
58
+ color: var(--ui-text);
59
+ }
60
+
61
+ .ui-breadcrumb-current {
62
+ font-size: 0.9rem;
63
+ font-weight: 600;
64
+ color: var(--ui-text);
65
+ }
66
+
67
+ /* Selection bar */
68
+
69
+ .ui-selection-count {
70
+ font-size: 0.82rem;
71
+ color: var(--ui-accent);
72
+ padding: 0.15rem 0.5rem;
73
+ background: var(--ui-accent-subtle);
74
+ border-radius: var(--ui-radius-sm);
75
+ font-variant-numeric: tabular-nums;
76
+ }
@@ -0,0 +1,45 @@
1
+ /** Contract: contracts/packages-ui-components/rules.md */
2
+
3
+ /**
4
+ * Sticky page header with left (logo + breadcrumbs) and right (actions) slots.
5
+ * Switches to a selection bar when selectionCount > 0.
6
+ *
7
+ * @param {ReactNode} logo - Logo element
8
+ * @param {Array} breadcrumbs - [{label, onClick?}] for breadcrumb trail
9
+ * @param {ReactNode} actions - Normal action buttons
10
+ * @param {number} selectionCount - Number of selected items (0 = normal mode)
11
+ * @param {ReactNode} selectionActions - Actions shown in selection mode
12
+ */
13
+ export default function PageHeader({ logo, breadcrumbs, actions, selectionCount = 0, selectionActions }) {
14
+ return (
15
+ <header className="ui-page-header">
16
+ <div className="ui-page-header-left">
17
+ {logo}
18
+ {breadcrumbs && breadcrumbs.length > 0 && (
19
+ <nav className="ui-breadcrumb">
20
+ {breadcrumbs.map((crumb, i) => (
21
+ <span key={i} className="ui-breadcrumb-segment">
22
+ <span className="ui-breadcrumb-sep">/</span>
23
+ {crumb.onClick ? (
24
+ <span className="ui-breadcrumb-link" onClick={crumb.onClick}>{crumb.label}</span>
25
+ ) : (
26
+ crumb.node || <span className="ui-breadcrumb-current">{crumb.label}</span>
27
+ )}
28
+ </span>
29
+ ))}
30
+ </nav>
31
+ )}
32
+ </div>
33
+ {selectionCount > 0 ? (
34
+ <div className="ui-page-header-actions">
35
+ <span className="ui-selection-count">{selectionCount} selected</span>
36
+ {selectionActions}
37
+ </div>
38
+ ) : (
39
+ <div className="ui-page-header-actions">
40
+ {actions}
41
+ </div>
42
+ )}
43
+ </header>
44
+ );
45
+ }
@@ -0,0 +1,82 @@
1
+ /** Contract: contracts/packages-ui-components/rules.md */
2
+
3
+ .ui-slide-panel {
4
+ position: fixed;
5
+ top: 0;
6
+ height: 100dvh;
7
+ background: rgba(12, 12, 18, 0.85);
8
+ backdrop-filter: blur(var(--ui-glass-blur-heavy));
9
+ border-right: 1px solid var(--ui-border);
10
+ transition: transform 0.3s var(--ui-ease);
11
+ z-index: 50;
12
+ display: flex;
13
+ flex-direction: column;
14
+ box-shadow: var(--ui-shadow-float);
15
+ }
16
+
17
+ .ui-slide-panel-header {
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: space-between;
21
+ padding: 0.85rem 1rem;
22
+ border-bottom: 1px solid var(--ui-border);
23
+ flex-shrink: 0;
24
+ }
25
+
26
+ .ui-slide-panel-title {
27
+ font-size: 0.82rem;
28
+ font-weight: 600;
29
+ color: var(--ui-text);
30
+ }
31
+
32
+ .ui-slide-panel-close {
33
+ background: none;
34
+ border: none;
35
+ color: var(--ui-text-muted);
36
+ font-size: 1.1rem;
37
+ cursor: pointer;
38
+ line-height: 1;
39
+ border-radius: var(--ui-radius-sm);
40
+ padding: 0.1rem 0.2rem;
41
+ transition: color 0.15s, background 0.15s;
42
+ }
43
+
44
+ .ui-slide-panel-close:hover {
45
+ color: var(--ui-text);
46
+ background: var(--ui-surface-raised);
47
+ }
48
+
49
+ .ui-slide-panel-tabs {
50
+ display: flex;
51
+ border-bottom: 1px solid var(--ui-border);
52
+ flex-shrink: 0;
53
+ }
54
+
55
+ .ui-slide-panel-body {
56
+ flex: 1;
57
+ overflow-y: auto;
58
+ padding: 0.75rem;
59
+ }
60
+
61
+ /* Tab button primitive */
62
+ .ui-tab {
63
+ flex: 1;
64
+ padding: 0.5rem 0.5rem;
65
+ font-size: 0.72rem;
66
+ font-weight: 500;
67
+ color: var(--ui-text-muted);
68
+ background: transparent;
69
+ border: none;
70
+ border-bottom: 2px solid transparent;
71
+ cursor: pointer;
72
+ transition: color 0.2s var(--ui-ease), border-color 0.2s var(--ui-ease);
73
+ }
74
+
75
+ .ui-tab:hover {
76
+ color: var(--ui-text);
77
+ }
78
+
79
+ .ui-tab--active {
80
+ color: var(--ui-accent);
81
+ border-bottom-color: var(--ui-accent);
82
+ }
@@ -0,0 +1,37 @@
1
+ /** Contract: contracts/packages-ui-components/rules.md */
2
+
3
+ /**
4
+ * Slide-out side panel. Slides in from the specified side (default: left).
5
+ * Generic container — consumers provide tabs, body content, etc.
6
+ *
7
+ * @param {boolean} open - Whether panel is visible
8
+ * @param {string} title - Panel title
9
+ * @param {string} side - 'left' or 'right' (default: 'left')
10
+ * @param {number|string} width - Panel width (default: 260)
11
+ * @param {function} onClose - Close handler
12
+ * @param {ReactNode} tabs - Optional tab bar content
13
+ * @param {ReactNode} children - Panel body content
14
+ */
15
+ export default function SlidePanel({ open, title, side = 'left', width = 260, onClose, tabs, children }) {
16
+ const isRight = side === 'right';
17
+ const style = {
18
+ width: typeof width === 'number' ? `${width}px` : width,
19
+ [isRight ? 'right' : 'left']: 0,
20
+ transform: open
21
+ ? 'translateX(0)'
22
+ : `translateX(${isRight ? '100%' : '-100%'})`,
23
+ };
24
+
25
+ return (
26
+ <div className="ui-slide-panel" style={style}>
27
+ <div className="ui-slide-panel-header">
28
+ <span className="ui-slide-panel-title">{title}</span>
29
+ <button className="ui-slide-panel-close" type="button" onClick={onClose}>×</button>
30
+ </div>
31
+ {tabs && <div className="ui-slide-panel-tabs">{tabs}</div>}
32
+ <div className="ui-slide-panel-body">
33
+ {children}
34
+ </div>
35
+ </div>
36
+ );
37
+ }