@orsetra/shared-ui 1.3.14 → 1.3.15
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import React, { useState, useEffect } from "react"
|
|
3
|
+
import React, { useState, useEffect, useRef } from "react"
|
|
4
4
|
import { Input } from "./input"
|
|
5
5
|
import { Button } from "./button"
|
|
6
6
|
import { X, Plus } from "lucide-react"
|
|
@@ -18,6 +18,8 @@ interface KVInputProps {
|
|
|
18
18
|
valuePlaceholder?: string
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
const BTN = "rounded-none h-9 w-9 p-0 flex-shrink-0"
|
|
22
|
+
|
|
21
23
|
export function KVInput({
|
|
22
24
|
value = {},
|
|
23
25
|
onChange,
|
|
@@ -25,93 +27,103 @@ export function KVInput({
|
|
|
25
27
|
keyPlaceholder = "Key",
|
|
26
28
|
valuePlaceholder = "Value",
|
|
27
29
|
}: KVInputProps) {
|
|
28
|
-
const [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
})
|
|
30
|
+
const [committed, setCommitted] = useState<KVPair[]>(() =>
|
|
31
|
+
Object.entries(value).map(([k, v]) => ({ key: k, value: v }))
|
|
32
|
+
)
|
|
33
|
+
const [draft, setDraft] = useState<KVPair>({ key: "", value: "" })
|
|
34
|
+
const skipSync = useRef(false)
|
|
32
35
|
|
|
36
|
+
// Sync from parent only when value changes externally (not from our own emit)
|
|
33
37
|
useEffect(() => {
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
setPairs(newPairs)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
38
|
+
if (skipSync.current) { skipSync.current = false; return }
|
|
39
|
+
setCommitted(Object.entries(value).map(([k, v]) => ({ key: k, value: v })))
|
|
40
|
+
setDraft({ key: "", value: "" })
|
|
40
41
|
}, [value])
|
|
41
42
|
|
|
42
|
-
const
|
|
43
|
-
const newPairs = [...pairs]
|
|
44
|
-
newPairs[index][field] = newValue
|
|
45
|
-
setPairs(newPairs)
|
|
46
|
-
|
|
43
|
+
const emit = (comm: KVPair[]) => {
|
|
47
44
|
const result: Record<string, string> = {}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
result[pair.key] = pair.value
|
|
51
|
-
}
|
|
52
|
-
})
|
|
45
|
+
comm.forEach(p => { if (p.key) result[p.key] = p.value })
|
|
46
|
+
skipSync.current = true
|
|
53
47
|
onChange?.(result)
|
|
54
48
|
}
|
|
55
49
|
|
|
56
|
-
const
|
|
57
|
-
|
|
50
|
+
const handleCommit = () => {
|
|
51
|
+
if (!draft.key) return
|
|
52
|
+
const next = [...committed, { ...draft }]
|
|
53
|
+
setCommitted(next)
|
|
54
|
+
setDraft({ key: "", value: "" })
|
|
55
|
+
emit(next)
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
const handleRemove = (index: number) => {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
})
|
|
71
|
-
onChange?.(result)
|
|
59
|
+
const next = committed.filter((_, i) => i !== index)
|
|
60
|
+
setCommitted(next)
|
|
61
|
+
emit(next)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const handleCommittedChange = (index: number, field: "key" | "value", val: string) => {
|
|
65
|
+
const next = committed.map((p, i) => i === index ? { ...p, [field]: val } : p)
|
|
66
|
+
setCommitted(next)
|
|
67
|
+
emit(next)
|
|
72
68
|
}
|
|
73
69
|
|
|
74
70
|
return (
|
|
75
71
|
<div className="space-y-2">
|
|
76
|
-
{
|
|
72
|
+
{committed.map((pair, index) => (
|
|
77
73
|
<div key={index} className="flex gap-2">
|
|
78
74
|
<Input
|
|
79
75
|
value={pair.key}
|
|
80
|
-
onChange={(e) =>
|
|
76
|
+
onChange={(e) => handleCommittedChange(index, "key", e.target.value)}
|
|
81
77
|
disabled={disabled}
|
|
82
78
|
placeholder={keyPlaceholder}
|
|
83
79
|
className="rounded-none flex-1"
|
|
84
80
|
/>
|
|
85
81
|
<Input
|
|
86
82
|
value={pair.value}
|
|
87
|
-
onChange={(e) =>
|
|
83
|
+
onChange={(e) => handleCommittedChange(index, "value", e.target.value)}
|
|
88
84
|
disabled={disabled}
|
|
89
85
|
placeholder={valuePlaceholder}
|
|
90
86
|
className="rounded-none flex-1"
|
|
91
87
|
/>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
</Button>
|
|
102
|
-
)}
|
|
88
|
+
<Button
|
|
89
|
+
type="button"
|
|
90
|
+
variant="secondary"
|
|
91
|
+
onClick={() => handleRemove(index)}
|
|
92
|
+
disabled={disabled}
|
|
93
|
+
className={BTN}
|
|
94
|
+
>
|
|
95
|
+
<X className="h-4 w-4" />
|
|
96
|
+
</Button>
|
|
103
97
|
</div>
|
|
104
98
|
))}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
99
|
+
|
|
100
|
+
{/* Draft row — + enabled only when key is filled */}
|
|
101
|
+
<div className="flex gap-2">
|
|
102
|
+
<Input
|
|
103
|
+
value={draft.key}
|
|
104
|
+
onChange={(e) => setDraft(d => ({ ...d, key: e.target.value }))}
|
|
105
|
+
disabled={disabled}
|
|
106
|
+
placeholder={keyPlaceholder}
|
|
107
|
+
required
|
|
108
|
+
className="rounded-none flex-1"
|
|
109
|
+
/>
|
|
110
|
+
<Input
|
|
111
|
+
value={draft.value}
|
|
112
|
+
onChange={(e) => setDraft(d => ({ ...d, value: e.target.value }))}
|
|
113
|
+
disabled={disabled}
|
|
114
|
+
placeholder={valuePlaceholder}
|
|
115
|
+
className="rounded-none flex-1"
|
|
116
|
+
/>
|
|
117
|
+
<Button
|
|
118
|
+
type="button"
|
|
119
|
+
variant="secondary"
|
|
120
|
+
onClick={handleCommit}
|
|
121
|
+
disabled={disabled || !draft.key}
|
|
122
|
+
className={BTN}
|
|
123
|
+
>
|
|
124
|
+
<Plus className="h-4 w-4" />
|
|
125
|
+
</Button>
|
|
126
|
+
</div>
|
|
115
127
|
</div>
|
|
116
128
|
)
|
|
117
129
|
}
|
|
@@ -31,8 +31,9 @@ SidePanelOverlay.displayName = DialogPrimitive.Overlay.displayName
|
|
|
31
31
|
interface SidePanelProps {
|
|
32
32
|
open: boolean
|
|
33
33
|
onOpenChange: (open: boolean) => void
|
|
34
|
-
title:
|
|
35
|
-
description?:
|
|
34
|
+
title: React.ReactNode
|
|
35
|
+
description?: React.ReactNode
|
|
36
|
+
headerExtra?: React.ReactNode
|
|
36
37
|
children: React.ReactNode
|
|
37
38
|
actions: React.ReactNode
|
|
38
39
|
side?: "left" | "right"
|
|
@@ -45,6 +46,7 @@ const SidePanel = ({
|
|
|
45
46
|
onOpenChange,
|
|
46
47
|
title,
|
|
47
48
|
description,
|
|
49
|
+
headerExtra,
|
|
48
50
|
children,
|
|
49
51
|
actions,
|
|
50
52
|
side = "right",
|
|
@@ -77,6 +79,11 @@ const SidePanel = ({
|
|
|
77
79
|
</DialogPrimitive.Description>
|
|
78
80
|
)}
|
|
79
81
|
</div>
|
|
82
|
+
{headerExtra && (
|
|
83
|
+
<div className="flex items-center mr-3">
|
|
84
|
+
{headerExtra}
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
80
87
|
<DialogPrimitive.Close
|
|
81
88
|
className="rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ibm-blue-60 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-ibm-gray-10"
|
|
82
89
|
onClick={onClose}
|