@orsetra/shared-ui 1.0.32 → 1.0.34

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.
@@ -33,6 +33,7 @@ export { Collapsible, CollapsibleTrigger, CollapsibleContent } from './collapsib
33
33
  export { Command, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandShortcut, CommandSeparator } from './command'
34
34
  export { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuCheckboxItem, ContextMenuRadioItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuShortcut, ContextMenuGroup, ContextMenuPortal, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuRadioGroup } from './context-menu'
35
35
  export { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription } from './dialog'
36
+ export { ProjectSelectorModal, type Project, type ProjectSelectorModalProps } from './project-selector-modal'
36
37
  export { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerFooter, DrawerTitle, DrawerDescription } from './drawer'
37
38
  export { Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField } from './form'
38
39
  export { HoverCard, HoverCardTrigger, HoverCardContent } from './hover-card'
@@ -0,0 +1,185 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ Dialog,
6
+ DialogContent,
7
+ DialogDescription,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ } from "./dialog"
11
+ import { Button } from "./button"
12
+ import { Label } from "./label"
13
+ import {
14
+ Select,
15
+ SelectContent,
16
+ SelectItem,
17
+ SelectTrigger,
18
+ SelectValue,
19
+ } from "./select"
20
+ import { Loader2, FolderKanban } from "lucide-react"
21
+
22
+ export interface Project {
23
+ name: string
24
+ alias?: string
25
+ description?: string
26
+ }
27
+
28
+ export interface ProjectSelectorModalProps {
29
+ open: boolean
30
+ onOpenChange?: (open: boolean) => void
31
+ getProjects: () => Promise<Project[]>
32
+ storage: {
33
+ getItem: (key: string) => string | null
34
+ setItem: (key: string, value: string) => void
35
+ }
36
+ storageKey?: string
37
+ doInit: () => void | Promise<void>
38
+ title?: string
39
+ description?: string
40
+ }
41
+
42
+ export function ProjectSelectorModal({
43
+ open,
44
+ onOpenChange,
45
+ getProjects,
46
+ storage,
47
+ storageKey = "current-project",
48
+ doInit,
49
+ title = "Select Project",
50
+ description = "Choose a project to continue. This will be used for all operations in this application.",
51
+ }: ProjectSelectorModalProps) {
52
+ const [projects, setProjects] = React.useState<Project[]>([])
53
+ const [loading, setLoading] = React.useState(true)
54
+ const [selectedProject, setSelectedProject] = React.useState<string>("")
55
+ const [submitting, setSubmitting] = React.useState(false)
56
+ const [error, setError] = React.useState<string | null>(null)
57
+
58
+ React.useEffect(() => {
59
+ if (open) {
60
+ loadProjects()
61
+ // Try to load current project from storage
62
+ const currentProject = storage.getItem(storageKey)
63
+ if (currentProject) {
64
+ setSelectedProject(currentProject)
65
+ }
66
+ }
67
+ }, [open, storage, storageKey])
68
+
69
+ const loadProjects = async () => {
70
+ setLoading(true)
71
+ setError(null)
72
+ try {
73
+ const projectsList = await getProjects()
74
+ setProjects(projectsList)
75
+
76
+ // If no project selected yet and we have projects, select the first one
77
+ if (!selectedProject && projectsList.length > 0) {
78
+ setSelectedProject(projectsList[0].name)
79
+ }
80
+ } catch (err) {
81
+ console.error("Failed to load projects:", err)
82
+ setError(err instanceof Error ? err.message : "Failed to load projects")
83
+ } finally {
84
+ setLoading(false)
85
+ }
86
+ }
87
+
88
+ const handleSubmit = async (e: React.FormEvent) => {
89
+ e.preventDefault()
90
+
91
+ if (!selectedProject) {
92
+ setError("Please select a project")
93
+ return
94
+ }
95
+
96
+ setSubmitting(true)
97
+ setError(null)
98
+
99
+ try {
100
+ // Save to storage
101
+ storage.setItem(storageKey, selectedProject)
102
+
103
+ // Call doInit
104
+ await Promise.resolve(doInit())
105
+
106
+ // Close modal
107
+ onOpenChange?.(false)
108
+ } catch (err) {
109
+ console.error("Failed to initialize:", err)
110
+ setError(err instanceof Error ? err.message : "Failed to initialize")
111
+ setSubmitting(false)
112
+ }
113
+ }
114
+
115
+ return (
116
+ <Dialog open={open} onOpenChange={onOpenChange}>
117
+ <DialogContent className="rounded-none sm:max-w-md" onInteractOutside={(e) => e.preventDefault()}>
118
+ <DialogHeader>
119
+ <DialogTitle className="flex items-center gap-2">
120
+ <FolderKanban className="h-5 w-5 text-ibm-blue-60" />
121
+ {title}
122
+ </DialogTitle>
123
+ <DialogDescription>{description}</DialogDescription>
124
+ </DialogHeader>
125
+
126
+ <form onSubmit={handleSubmit} className="space-y-4">
127
+ {loading ? (
128
+ <div className="flex items-center justify-center py-8">
129
+ <Loader2 className="h-6 w-6 animate-spin text-ibm-blue-60" />
130
+ <span className="ml-2 text-sm text-ibm-gray-70">Loading projects...</span>
131
+ </div>
132
+ ) : error ? (
133
+ <div className="rounded-none border border-red-200 bg-red-50 p-3 text-sm text-red-800">
134
+ {error}
135
+ </div>
136
+ ) : projects.length === 0 ? (
137
+ <div className="text-center py-8 text-sm text-ibm-gray-60">
138
+ No projects available
139
+ </div>
140
+ ) : (
141
+ <div className="space-y-2">
142
+ <Label htmlFor="project">
143
+ Project <span className="text-red-600">*</span>
144
+ </Label>
145
+ <Select value={selectedProject} onValueChange={setSelectedProject}>
146
+ <SelectTrigger id="project" className="rounded-none">
147
+ <SelectValue placeholder="Select a project" />
148
+ </SelectTrigger>
149
+ <SelectContent className="rounded-none">
150
+ {projects.map((project) => (
151
+ <SelectItem key={project.name} value={project.name} className="rounded-none">
152
+ <div className="flex flex-col">
153
+ <span className="font-medium">{project.alias || project.name}</span>
154
+ {project.description && (
155
+ <span className="text-xs text-ibm-gray-60">{project.description}</span>
156
+ )}
157
+ </div>
158
+ </SelectItem>
159
+ ))}
160
+ </SelectContent>
161
+ </Select>
162
+ </div>
163
+ )}
164
+
165
+ <div className="flex items-center justify-end gap-3 pt-2">
166
+ <Button
167
+ type="submit"
168
+ className="rounded-none"
169
+ disabled={submitting || loading || !selectedProject || projects.length === 0}
170
+ >
171
+ {submitting ? (
172
+ <>
173
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
174
+ Initializing...
175
+ </>
176
+ ) : (
177
+ "Continue"
178
+ )}
179
+ </Button>
180
+ </div>
181
+ </form>
182
+ </DialogContent>
183
+ </Dialog>
184
+ )
185
+ }
package/lib/utils.ts CHANGED
@@ -4,3 +4,25 @@ import { twMerge } from "tailwind-merge"
4
4
  export function cn(...inputs: ClassValue[]) {
5
5
  return twMerge(clsx(inputs))
6
6
  }
7
+
8
+ export function getTypeColor(type: string) {
9
+ switch (type) {
10
+ case "production":
11
+ return "bg-red-10 text-red-70 border-red-30"
12
+ case "staging":
13
+ return "bg-yellow-10 text-yellow-70 border-yellow-30"
14
+ case "development":
15
+ case "dev":
16
+ return "bg-blue-10 text-blue-70 border-blue-30"
17
+ case "test":
18
+ return "bg-purple-10 text-purple-70 border-purple-30"
19
+ case "sandbox":
20
+ return "bg-green-10 text-green-70 border-green-30"
21
+ case "ACTIVE":
22
+ return "bg-green-10 text-green-70 border-green-30"
23
+ case "INACTIVE":
24
+ return "bg-red-10 text-red-70 border-red-30"
25
+ default:
26
+ return "bg-gray-10 text-gray-70 border-gray-30"
27
+ }
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orsetra/shared-ui",
3
- "version": "1.0.32",
3
+ "version": "1.0.34",
4
4
  "description": "Shared UI components for Orsetra platform",
5
5
  "main": "./index.ts",
6
6
  "types": "./index.ts",