@orsetra/shared-ui 1.1.9 → 1.1.11
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.
- package/components/extends/ArrayItemGroup/index.tsx +58 -0
- package/components/extends/CPUNumber/index.tsx +33 -0
- package/components/extends/ClassStorageSelect/index.tsx +11 -0
- package/components/extends/ClusterSelect/index.tsx +11 -0
- package/components/extends/ComponentSelect/index.tsx +114 -0
- package/components/extends/DiskNumber/index.tsx +34 -0
- package/components/extends/EnvSelect/index.tsx +11 -0
- package/components/extends/Group/index.tsx +152 -0
- package/components/extends/Ignore/index.tsx +11 -0
- package/components/extends/ImageInput/index.less +49 -0
- package/components/extends/ImageInput/index.tsx +195 -0
- package/components/extends/ImageSecretSelect/index.tsx +170 -0
- package/components/extends/KV/index.tsx +23 -0
- package/components/extends/MemoryNumber/index.tsx +34 -0
- package/components/extends/Numbers/index.tsx +23 -0
- package/components/extends/PVCSelect/index.tsx +12 -0
- package/components/extends/PolicySelect/index.tsx +55 -0
- package/components/extends/SecretKeySelect/index.tsx +97 -0
- package/components/extends/SecretSelect/index.tsx +78 -0
- package/components/extends/StepSelect/index.tsx +76 -0
- package/components/extends/Strings/index.tsx +23 -0
- package/components/extends/Switch/index.tsx +23 -0
- package/components/extends/index.ts +21 -0
- package/components/ui/combobox.tsx +137 -0
- package/components/ui/index.ts +3 -0
- package/components/ui/kv-input.tsx +117 -0
- package/components/ui/multi-select.tsx +151 -0
- package/components/ui/numbers-input.tsx +87 -0
- package/components/ui/strings-input.tsx +83 -0
- package/index.ts +12 -0
- package/package.json +2 -2
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Check, ChevronDown } from "lucide-react"
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
import { Button } from "./button"
|
|
7
|
+
import {
|
|
8
|
+
Command,
|
|
9
|
+
CommandEmpty,
|
|
10
|
+
CommandGroup,
|
|
11
|
+
CommandInput,
|
|
12
|
+
CommandItem,
|
|
13
|
+
} from "./command"
|
|
14
|
+
import {
|
|
15
|
+
Popover,
|
|
16
|
+
PopoverContent,
|
|
17
|
+
PopoverTrigger,
|
|
18
|
+
} from "./popover"
|
|
19
|
+
|
|
20
|
+
export interface ComboboxOption {
|
|
21
|
+
label: string
|
|
22
|
+
value: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ComboboxProps {
|
|
26
|
+
placeholder?: string
|
|
27
|
+
value?: string
|
|
28
|
+
defaultValue?: string
|
|
29
|
+
onChange?: (value: string) => void
|
|
30
|
+
dataSource?: ComboboxOption[]
|
|
31
|
+
disabled?: boolean
|
|
32
|
+
id?: string
|
|
33
|
+
locale?: any
|
|
34
|
+
enableInput?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function Combobox({
|
|
38
|
+
placeholder = "Select...",
|
|
39
|
+
value = "",
|
|
40
|
+
defaultValue = "",
|
|
41
|
+
onChange,
|
|
42
|
+
dataSource = [],
|
|
43
|
+
disabled = false,
|
|
44
|
+
id,
|
|
45
|
+
enableInput = false,
|
|
46
|
+
}: ComboboxProps) {
|
|
47
|
+
const [open, setOpen] = React.useState(false)
|
|
48
|
+
const [selected, setSelected] = React.useState<string>(value || defaultValue)
|
|
49
|
+
const [inputValue, setInputValue] = React.useState<string>(value || defaultValue)
|
|
50
|
+
|
|
51
|
+
React.useEffect(() => {
|
|
52
|
+
if (value !== undefined) {
|
|
53
|
+
setSelected(value)
|
|
54
|
+
setInputValue(value)
|
|
55
|
+
}
|
|
56
|
+
}, [value])
|
|
57
|
+
|
|
58
|
+
const handleSelect = (currentValue: string) => {
|
|
59
|
+
const newValue = currentValue === selected ? "" : currentValue
|
|
60
|
+
setSelected(newValue)
|
|
61
|
+
setInputValue(newValue)
|
|
62
|
+
onChange?.(newValue)
|
|
63
|
+
setOpen(false)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const handleInputChange = (newValue: string) => {
|
|
67
|
+
setInputValue(newValue)
|
|
68
|
+
if (enableInput) {
|
|
69
|
+
setSelected(newValue)
|
|
70
|
+
onChange?.(newValue)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const getLabel = (value: string) => {
|
|
75
|
+
return dataSource.find((option) => option.value === value)?.label || value
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const displayValue = selected ? getLabel(selected) : placeholder
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
82
|
+
<PopoverTrigger asChild>
|
|
83
|
+
<Button
|
|
84
|
+
id={id}
|
|
85
|
+
variant="secondary"
|
|
86
|
+
role="combobox"
|
|
87
|
+
aria-expanded={open}
|
|
88
|
+
disabled={disabled}
|
|
89
|
+
className={cn(
|
|
90
|
+
"w-full justify-between h-10",
|
|
91
|
+
disabled && "opacity-50 cursor-not-allowed",
|
|
92
|
+
!selected && "text-gray-500"
|
|
93
|
+
)}
|
|
94
|
+
style={{ borderRadius: 0 }}
|
|
95
|
+
>
|
|
96
|
+
<span className="truncate">{displayValue}</span>
|
|
97
|
+
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
98
|
+
</Button>
|
|
99
|
+
</PopoverTrigger>
|
|
100
|
+
<PopoverContent className="w-full p-0" align="start" style={{ borderRadius: 0 }}>
|
|
101
|
+
<Command>
|
|
102
|
+
<CommandInput
|
|
103
|
+
placeholder={placeholder}
|
|
104
|
+
value={inputValue}
|
|
105
|
+
onValueChange={handleInputChange}
|
|
106
|
+
/>
|
|
107
|
+
<CommandEmpty>
|
|
108
|
+
{enableInput && inputValue ? (
|
|
109
|
+
<div className="px-2 py-1.5 text-sm">
|
|
110
|
+
Press Enter to use "{inputValue}"
|
|
111
|
+
</div>
|
|
112
|
+
) : (
|
|
113
|
+
"No results found."
|
|
114
|
+
)}
|
|
115
|
+
</CommandEmpty>
|
|
116
|
+
<CommandGroup className="max-h-64 overflow-auto">
|
|
117
|
+
{dataSource.map((option) => (
|
|
118
|
+
<CommandItem
|
|
119
|
+
key={option.value}
|
|
120
|
+
value={option.value}
|
|
121
|
+
onSelect={() => handleSelect(option.value)}
|
|
122
|
+
>
|
|
123
|
+
<Check
|
|
124
|
+
className={cn(
|
|
125
|
+
"mr-2 h-4 w-4",
|
|
126
|
+
selected === option.value ? "opacity-100" : "opacity-0"
|
|
127
|
+
)}
|
|
128
|
+
/>
|
|
129
|
+
{option.label}
|
|
130
|
+
</CommandItem>
|
|
131
|
+
))}
|
|
132
|
+
</CommandGroup>
|
|
133
|
+
</Command>
|
|
134
|
+
</PopoverContent>
|
|
135
|
+
</Popover>
|
|
136
|
+
)
|
|
137
|
+
}
|
package/components/ui/index.ts
CHANGED
|
@@ -61,3 +61,6 @@ export { Textarea } from './textarea'
|
|
|
61
61
|
export { Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from './toast'
|
|
62
62
|
export { ToggleGroup, ToggleGroupItem } from './toggle-group'
|
|
63
63
|
export { Toggle } from './toggle'
|
|
64
|
+
export { StringsInput } from './strings-input'
|
|
65
|
+
export { KVInput } from './kv-input'
|
|
66
|
+
export { NumbersInput } from './numbers-input'
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect } from "react"
|
|
4
|
+
import { Input } from "./input"
|
|
5
|
+
import { Button } from "./button"
|
|
6
|
+
import { X, Plus } from "lucide-react"
|
|
7
|
+
|
|
8
|
+
interface KVPair {
|
|
9
|
+
key: string
|
|
10
|
+
value: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface KVInputProps {
|
|
14
|
+
value?: Record<string, string>
|
|
15
|
+
onChange?: (value: Record<string, string>) => void
|
|
16
|
+
disabled?: boolean
|
|
17
|
+
keyPlaceholder?: string
|
|
18
|
+
valuePlaceholder?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function KVInput({
|
|
22
|
+
value = {},
|
|
23
|
+
onChange,
|
|
24
|
+
disabled = false,
|
|
25
|
+
keyPlaceholder = "Key",
|
|
26
|
+
valuePlaceholder = "Value",
|
|
27
|
+
}: KVInputProps) {
|
|
28
|
+
const [pairs, setPairs] = useState<KVPair[]>(() => {
|
|
29
|
+
const entries = Object.entries(value)
|
|
30
|
+
return entries.length > 0 ? entries.map(([key, value]) => ({ key, value })) : [{ key: "", value: "" }]
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (value && Object.keys(value).length > 0) {
|
|
35
|
+
const newPairs = Object.entries(value).map(([key, val]) => ({ key, value: val }))
|
|
36
|
+
if (JSON.stringify(newPairs) !== JSON.stringify(pairs)) {
|
|
37
|
+
setPairs(newPairs)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}, [value])
|
|
41
|
+
|
|
42
|
+
const handleChange = (index: number, field: "key" | "value", newValue: string) => {
|
|
43
|
+
const newPairs = [...pairs]
|
|
44
|
+
newPairs[index][field] = newValue
|
|
45
|
+
setPairs(newPairs)
|
|
46
|
+
|
|
47
|
+
const result: Record<string, string> = {}
|
|
48
|
+
newPairs.forEach(pair => {
|
|
49
|
+
if (pair.key) {
|
|
50
|
+
result[pair.key] = pair.value
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
onChange?.(result)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const handleAdd = () => {
|
|
57
|
+
setPairs([...pairs, { key: "", value: "" }])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const handleRemove = (index: number) => {
|
|
61
|
+
const newPairs = pairs.filter((_, i) => i !== index)
|
|
62
|
+
const finalPairs = newPairs.length > 0 ? newPairs : [{ key: "", value: "" }]
|
|
63
|
+
setPairs(finalPairs)
|
|
64
|
+
|
|
65
|
+
const result: Record<string, string> = {}
|
|
66
|
+
finalPairs.forEach(pair => {
|
|
67
|
+
if (pair.key) {
|
|
68
|
+
result[pair.key] = pair.value
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
onChange?.(result)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div className="space-y-2">
|
|
76
|
+
{pairs.map((pair, index) => (
|
|
77
|
+
<div key={index} className="flex gap-2">
|
|
78
|
+
<Input
|
|
79
|
+
value={pair.key}
|
|
80
|
+
onChange={(e) => handleChange(index, "key", e.target.value)}
|
|
81
|
+
disabled={disabled}
|
|
82
|
+
placeholder={keyPlaceholder}
|
|
83
|
+
className="rounded-none flex-1"
|
|
84
|
+
/>
|
|
85
|
+
<Input
|
|
86
|
+
value={pair.value}
|
|
87
|
+
onChange={(e) => handleChange(index, "value", e.target.value)}
|
|
88
|
+
disabled={disabled}
|
|
89
|
+
placeholder={valuePlaceholder}
|
|
90
|
+
className="rounded-none flex-1"
|
|
91
|
+
/>
|
|
92
|
+
{pairs.length > 1 && (
|
|
93
|
+
<Button
|
|
94
|
+
type="button"
|
|
95
|
+
variant="secondary"
|
|
96
|
+
onClick={() => handleRemove(index)}
|
|
97
|
+
disabled={disabled}
|
|
98
|
+
className="rounded-none h-9 w-9 p-0"
|
|
99
|
+
>
|
|
100
|
+
<X className="h-4 w-4" />
|
|
101
|
+
</Button>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
))}
|
|
105
|
+
<Button
|
|
106
|
+
type="button"
|
|
107
|
+
variant="secondary"
|
|
108
|
+
onClick={handleAdd}
|
|
109
|
+
disabled={disabled}
|
|
110
|
+
className="rounded-none"
|
|
111
|
+
>
|
|
112
|
+
<Plus className="h-4 w-4 mr-2" />
|
|
113
|
+
Add Item
|
|
114
|
+
</Button>
|
|
115
|
+
</div>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Check, X, ChevronDown } from "lucide-react"
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
import { Badge } from "./badge"
|
|
7
|
+
import { Button } from "./button"
|
|
8
|
+
import {
|
|
9
|
+
Command,
|
|
10
|
+
CommandEmpty,
|
|
11
|
+
CommandGroup,
|
|
12
|
+
CommandInput,
|
|
13
|
+
CommandItem,
|
|
14
|
+
} from "./command"
|
|
15
|
+
import {
|
|
16
|
+
Popover,
|
|
17
|
+
PopoverContent,
|
|
18
|
+
PopoverTrigger,
|
|
19
|
+
} from "./popover"
|
|
20
|
+
|
|
21
|
+
export interface MultiSelectOption {
|
|
22
|
+
label: string
|
|
23
|
+
value: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface MultiSelectProps {
|
|
27
|
+
placeholder?: string
|
|
28
|
+
value?: string[]
|
|
29
|
+
defaultValue?: string[]
|
|
30
|
+
onChange?: (value: string[]) => void
|
|
31
|
+
dataSource?: MultiSelectOption[]
|
|
32
|
+
disabled?: boolean
|
|
33
|
+
id?: string
|
|
34
|
+
locale?: any
|
|
35
|
+
mode?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function MultiSelect({
|
|
39
|
+
placeholder = "Select items...",
|
|
40
|
+
value = [],
|
|
41
|
+
defaultValue = [],
|
|
42
|
+
onChange,
|
|
43
|
+
dataSource = [],
|
|
44
|
+
disabled = false,
|
|
45
|
+
id,
|
|
46
|
+
}: MultiSelectProps) {
|
|
47
|
+
const [open, setOpen] = React.useState(false)
|
|
48
|
+
const [selected, setSelected] = React.useState<string[]>(value || defaultValue)
|
|
49
|
+
|
|
50
|
+
React.useEffect(() => {
|
|
51
|
+
if (value !== undefined) {
|
|
52
|
+
setSelected(value)
|
|
53
|
+
}
|
|
54
|
+
}, [value])
|
|
55
|
+
|
|
56
|
+
const handleSelect = (currentValue: string) => {
|
|
57
|
+
const newSelected = selected.includes(currentValue)
|
|
58
|
+
? selected.filter((item) => item !== currentValue)
|
|
59
|
+
: [...selected, currentValue]
|
|
60
|
+
|
|
61
|
+
setSelected(newSelected)
|
|
62
|
+
onChange?.(newSelected)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const handleRemove = (valueToRemove: string, e: React.MouseEvent) => {
|
|
66
|
+
e.stopPropagation()
|
|
67
|
+
const newSelected = selected.filter((item) => item !== valueToRemove)
|
|
68
|
+
setSelected(newSelected)
|
|
69
|
+
onChange?.(newSelected)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const getLabel = (value: string) => {
|
|
73
|
+
return dataSource.find((option) => option.value === value)?.label || value
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
78
|
+
<PopoverTrigger asChild>
|
|
79
|
+
<Button
|
|
80
|
+
id={id}
|
|
81
|
+
variant="secondary"
|
|
82
|
+
role="combobox"
|
|
83
|
+
aria-expanded={open}
|
|
84
|
+
disabled={disabled}
|
|
85
|
+
className={cn(
|
|
86
|
+
"w-full justify-between h-auto min-h-10",
|
|
87
|
+
disabled && "opacity-50 cursor-not-allowed"
|
|
88
|
+
)}
|
|
89
|
+
style={{ borderRadius: 0 }}
|
|
90
|
+
>
|
|
91
|
+
<div className="flex flex-wrap gap-1 flex-1">
|
|
92
|
+
{selected.length > 0 ? (
|
|
93
|
+
selected.map((item) => (
|
|
94
|
+
<Badge
|
|
95
|
+
key={item}
|
|
96
|
+
variant="secondary"
|
|
97
|
+
className="mr-1"
|
|
98
|
+
>
|
|
99
|
+
{getLabel(item)}
|
|
100
|
+
{!disabled && (
|
|
101
|
+
<button
|
|
102
|
+
className="ml-1 ring-offset-background rounded-full outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
|
103
|
+
onKeyDown={(e) => {
|
|
104
|
+
if (e.key === "Enter") {
|
|
105
|
+
handleRemove(item, e as any)
|
|
106
|
+
}
|
|
107
|
+
}}
|
|
108
|
+
onMouseDown={(e) => {
|
|
109
|
+
e.preventDefault()
|
|
110
|
+
e.stopPropagation()
|
|
111
|
+
}}
|
|
112
|
+
onClick={(e) => handleRemove(item, e)}
|
|
113
|
+
>
|
|
114
|
+
<X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
|
|
115
|
+
</button>
|
|
116
|
+
)}
|
|
117
|
+
</Badge>
|
|
118
|
+
))
|
|
119
|
+
) : (
|
|
120
|
+
<span className="text-gray-500">{placeholder}</span>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
<ChevronDown className="h-4 w-4 shrink-0 opacity-50" />
|
|
124
|
+
</Button>
|
|
125
|
+
</PopoverTrigger>
|
|
126
|
+
<PopoverContent className="w-full p-0" align="start" style={{ borderRadius: 0 }}>
|
|
127
|
+
<Command>
|
|
128
|
+
<CommandInput placeholder="Search..." />
|
|
129
|
+
<CommandEmpty>No results found.</CommandEmpty>
|
|
130
|
+
<CommandGroup className="max-h-64 overflow-auto">
|
|
131
|
+
{dataSource.map((option) => (
|
|
132
|
+
<CommandItem
|
|
133
|
+
key={option.value}
|
|
134
|
+
value={option.value}
|
|
135
|
+
onSelect={() => handleSelect(option.value)}
|
|
136
|
+
>
|
|
137
|
+
<Check
|
|
138
|
+
className={cn(
|
|
139
|
+
"mr-2 h-4 w-4",
|
|
140
|
+
selected.includes(option.value) ? "opacity-100" : "opacity-0"
|
|
141
|
+
)}
|
|
142
|
+
/>
|
|
143
|
+
{option.label}
|
|
144
|
+
</CommandItem>
|
|
145
|
+
))}
|
|
146
|
+
</CommandGroup>
|
|
147
|
+
</Command>
|
|
148
|
+
</PopoverContent>
|
|
149
|
+
</Popover>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect } from "react"
|
|
4
|
+
import { Input } from "./input"
|
|
5
|
+
import { Button } from "./button"
|
|
6
|
+
import { X, Plus } from "lucide-react"
|
|
7
|
+
|
|
8
|
+
interface NumbersInputProps {
|
|
9
|
+
value?: number[]
|
|
10
|
+
onChange?: (value: number[]) => void
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
placeholder?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function NumbersInput({
|
|
16
|
+
value = [],
|
|
17
|
+
onChange,
|
|
18
|
+
disabled = false,
|
|
19
|
+
placeholder = "Enter number",
|
|
20
|
+
}: NumbersInputProps) {
|
|
21
|
+
const [items, setItems] = useState<number[]>(value.length > 0 ? value : [0])
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (value && value.length > 0 && JSON.stringify(value) !== JSON.stringify(items)) {
|
|
25
|
+
setItems(value)
|
|
26
|
+
}
|
|
27
|
+
}, [value])
|
|
28
|
+
|
|
29
|
+
const handleChange = (index: number, newValue: string) => {
|
|
30
|
+
const numValue = newValue === "" ? 0 : Number(newValue)
|
|
31
|
+
const newItems = [...items]
|
|
32
|
+
newItems[index] = numValue
|
|
33
|
+
setItems(newItems)
|
|
34
|
+
onChange?.(newItems)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const handleAdd = () => {
|
|
38
|
+
const newItems = [...items, 0]
|
|
39
|
+
setItems(newItems)
|
|
40
|
+
onChange?.(newItems)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const handleRemove = (index: number) => {
|
|
44
|
+
const newItems = items.filter((_, i) => i !== index)
|
|
45
|
+
const finalItems = newItems.length > 0 ? newItems : [0]
|
|
46
|
+
setItems(finalItems)
|
|
47
|
+
onChange?.(finalItems)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="space-y-2">
|
|
52
|
+
{items.map((item, index) => (
|
|
53
|
+
<div key={index} className="flex gap-2">
|
|
54
|
+
<Input
|
|
55
|
+
type="number"
|
|
56
|
+
value={item}
|
|
57
|
+
onChange={(e) => handleChange(index, e.target.value)}
|
|
58
|
+
disabled={disabled}
|
|
59
|
+
placeholder={placeholder}
|
|
60
|
+
className="rounded-none flex-1"
|
|
61
|
+
/>
|
|
62
|
+
{items.length > 1 && (
|
|
63
|
+
<Button
|
|
64
|
+
type="button"
|
|
65
|
+
variant="secondary"
|
|
66
|
+
onClick={() => handleRemove(index)}
|
|
67
|
+
disabled={disabled}
|
|
68
|
+
className="rounded-none h-9 w-9 p-0"
|
|
69
|
+
>
|
|
70
|
+
<X className="h-4 w-4" />
|
|
71
|
+
</Button>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
))}
|
|
75
|
+
<Button
|
|
76
|
+
type="button"
|
|
77
|
+
variant="secondary"
|
|
78
|
+
onClick={handleAdd}
|
|
79
|
+
disabled={disabled}
|
|
80
|
+
className="rounded-none"
|
|
81
|
+
>
|
|
82
|
+
<Plus className="h-4 w-4 mr-2" />
|
|
83
|
+
Add Item
|
|
84
|
+
</Button>
|
|
85
|
+
</div>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect } from "react"
|
|
4
|
+
import { Input } from "./input"
|
|
5
|
+
import { Button } from "./button"
|
|
6
|
+
import { X, Plus } from "lucide-react"
|
|
7
|
+
|
|
8
|
+
interface StringsInputProps {
|
|
9
|
+
value?: string[]
|
|
10
|
+
onChange?: (value: string[]) => void
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
placeholder?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function StringsInput({
|
|
16
|
+
value = [],
|
|
17
|
+
onChange,
|
|
18
|
+
disabled = false,
|
|
19
|
+
placeholder = "Enter value",
|
|
20
|
+
}: StringsInputProps) {
|
|
21
|
+
const [items, setItems] = useState<string[]>(value.length > 0 ? value : [""])
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (value && value.length > 0 && JSON.stringify(value) !== JSON.stringify(items)) {
|
|
25
|
+
setItems(value)
|
|
26
|
+
}
|
|
27
|
+
}, [value])
|
|
28
|
+
|
|
29
|
+
const handleChange = (index: number, newValue: string) => {
|
|
30
|
+
const newItems = [...items]
|
|
31
|
+
newItems[index] = newValue
|
|
32
|
+
setItems(newItems)
|
|
33
|
+
onChange?.(newItems.filter(item => item !== ""))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const handleAdd = () => {
|
|
37
|
+
const newItems = [...items, ""]
|
|
38
|
+
setItems(newItems)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const handleRemove = (index: number) => {
|
|
42
|
+
const newItems = items.filter((_, i) => i !== index)
|
|
43
|
+
setItems(newItems.length > 0 ? newItems : [""])
|
|
44
|
+
onChange?.(newItems.filter(item => item !== ""))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className="space-y-2">
|
|
49
|
+
{items.map((item, index) => (
|
|
50
|
+
<div key={index} className="flex gap-2">
|
|
51
|
+
<Input
|
|
52
|
+
value={item}
|
|
53
|
+
onChange={(e) => handleChange(index, e.target.value)}
|
|
54
|
+
disabled={disabled}
|
|
55
|
+
placeholder={placeholder}
|
|
56
|
+
className="rounded-none flex-1"
|
|
57
|
+
/>
|
|
58
|
+
{items.length > 1 && (
|
|
59
|
+
<Button
|
|
60
|
+
type="button"
|
|
61
|
+
variant="secondary"
|
|
62
|
+
onClick={() => handleRemove(index)}
|
|
63
|
+
disabled={disabled}
|
|
64
|
+
className="rounded-none h-9 w-9 p-0"
|
|
65
|
+
>
|
|
66
|
+
<X className="h-4 w-4" />
|
|
67
|
+
</Button>
|
|
68
|
+
)}
|
|
69
|
+
</div>
|
|
70
|
+
))}
|
|
71
|
+
<Button
|
|
72
|
+
type="button"
|
|
73
|
+
variant="secondary"
|
|
74
|
+
onClick={handleAdd}
|
|
75
|
+
disabled={disabled}
|
|
76
|
+
className="rounded-none"
|
|
77
|
+
>
|
|
78
|
+
<Plus className="h-4 w-4 mr-2" />
|
|
79
|
+
Add Item
|
|
80
|
+
</Button>
|
|
81
|
+
</div>
|
|
82
|
+
)
|
|
83
|
+
}
|
package/index.ts
CHANGED
|
@@ -8,8 +8,20 @@ export { default as HttpClient, useHttpClient, ApiError } from './lib/http-clien
|
|
|
8
8
|
// UI Components
|
|
9
9
|
export * from './components/ui'
|
|
10
10
|
|
|
11
|
+
// Extended Components for Dynamic Forms
|
|
12
|
+
export * from './components/extends'
|
|
13
|
+
|
|
11
14
|
// Layout Components
|
|
12
15
|
export * from './components/layout'
|
|
13
16
|
|
|
14
17
|
// Hooks
|
|
15
18
|
export * from './hooks'
|
|
19
|
+
|
|
20
|
+
// Context
|
|
21
|
+
export * from './context'
|
|
22
|
+
|
|
23
|
+
// Internationalization
|
|
24
|
+
export { default as i18n } from './i18n'
|
|
25
|
+
|
|
26
|
+
// Utilities (additional exports)
|
|
27
|
+
export { locale } from './utils/locale'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orsetra/shared-ui",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.11",
|
|
4
4
|
"description": "Shared UI components for Orsetra platform",
|
|
5
5
|
"main": "./index.ts",
|
|
6
6
|
"types": "./index.ts",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@hookform/resolvers": "^3.9.1",
|
|
44
|
-
"@orsetra/shared-types": "^1.0.
|
|
44
|
+
"@orsetra/shared-types": "^1.0.4",
|
|
45
45
|
"@radix-ui/react-accordion": "1.2.2",
|
|
46
46
|
"@radix-ui/react-alert-dialog": "1.1.4",
|
|
47
47
|
"@radix-ui/react-aspect-ratio": "1.1.1",
|