@orsetra/shared-ui 1.0.11 → 1.0.13

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.
@@ -5,6 +5,7 @@ import { usePathname } from "next/navigation"
5
5
  import { MainSidebar, Sidebar, SidebarProvider, useSidebar, type SidebarMode } from "./index"
6
6
  import { UserMenu, Button } from "../ui"
7
7
  import { getMenuFromPath } from "../../lib/menu-utils"
8
+ import { useIsMobile } from "../../hooks/use-mobile"
8
9
  import { Menu } from "lucide-react"
9
10
 
10
11
  export interface SidebarMenus {
@@ -22,10 +23,19 @@ interface LayoutContainerProps {
22
23
  function LayoutContent({ children, sidebarMenus, user, onSignOut, mode = 'expanded' }: LayoutContainerProps) {
23
24
  const pathname = usePathname()
24
25
  const { setOpen } = useSidebar()
26
+ const isMobile = useIsMobile()
25
27
  const [isMainSidebarOpen, setIsMainSidebarOpen] = useState(false)
26
28
  const [currentMenu, setCurrentMenu] = useState<string>("overview")
27
29
  const isMinimized = mode === 'minimized'
28
- const isHidden = mode === 'hidden'
30
+ // Force hidden mode on mobile
31
+ const isHidden = mode === 'hidden' || isMobile
32
+
33
+ // Close sidebar on route change (mobile)
34
+ useEffect(() => {
35
+ if (isMobile) {
36
+ setIsMainSidebarOpen(false)
37
+ }
38
+ }, [pathname, isMobile])
29
39
 
30
40
  useEffect(() => {
31
41
  const contextualMenu = getMenuFromPath(pathname)
@@ -49,7 +59,7 @@ function LayoutContent({ children, sidebarMenus, user, onSignOut, mode = 'expand
49
59
 
50
60
  return (
51
61
  <div className="flex h-screen w-full bg-white">
52
- {/* Sidebar secondaire - caché en mode minimized et hidden */}
62
+ {/* Desktop sidebar - hidden on mobile (isHidden is true on mobile) */}
53
63
  {!isMinimized && !isHidden && (
54
64
  <Sidebar
55
65
  currentMenu={currentMenu}
@@ -57,41 +67,27 @@ function LayoutContent({ children, sidebarMenus, user, onSignOut, mode = 'expand
57
67
  />
58
68
  )}
59
69
 
60
- {/* MainSidebar - en mode hidden, il s'ouvre comme un overlay */}
61
- {!isHidden && (
62
- <MainSidebar
63
- isOpen={isMainSidebarOpen}
64
- onToggle={handleMainSidebarToggle}
65
- onMenuSelect={handleMenuSelect}
66
- currentMenu={currentMenu}
67
- onSecondarySidebarOpen={handleSecondarySidebarOpen}
68
- mode={mode}
69
- />
70
- )}
71
-
72
- {/* MainSidebar overlay en mode hidden */}
73
- {isHidden && (
74
- <MainSidebar
75
- isOpen={isMainSidebarOpen}
76
- onToggle={handleMainSidebarToggle}
77
- onMenuSelect={handleMenuSelect}
78
- currentMenu={currentMenu}
79
- onSecondarySidebarOpen={handleSecondarySidebarOpen}
80
- mode="expanded"
81
- />
82
- )}
70
+ {/* MainSidebar - always available, opens as overlay when isHidden */}
71
+ <MainSidebar
72
+ isOpen={isMainSidebarOpen}
73
+ onToggle={handleMainSidebarToggle}
74
+ onMenuSelect={handleMenuSelect}
75
+ currentMenu={currentMenu}
76
+ onSecondarySidebarOpen={handleSecondarySidebarOpen}
77
+ mode={isHidden ? "expanded" : mode}
78
+ />
83
79
 
84
- <div className="flex-1 flex flex-col">
80
+ <div className="flex-1 flex flex-col min-w-0">
85
81
  <header className="h-14 bg-white border-b border-ui-border flex-shrink-0">
86
- <div className="h-full px-6 flex items-center justify-between">
87
- {/* Bouton menu - affiché en mode hidden */}
82
+ <div className="h-full px-4 md:px-6 flex items-center justify-between">
83
+ {/* Menu button - shown when sidebar is hidden (including on mobile) */}
88
84
  {isHidden ? (
89
85
  <Button
90
86
  variant="ghost"
91
87
  size="sm"
92
88
  onClick={handleMainSidebarToggle}
93
89
  className="h-8 w-8 p-0 hover:bg-ui-background text-text-secondary"
94
- title="Ouvrir le menu principal"
90
+ title="Ouvrir le menu"
95
91
  >
96
92
  <Menu className="h-5 w-5" />
97
93
  </Button>
@@ -0,0 +1,117 @@
1
+ "use client"
2
+
3
+ import { useState, useRef } from "react"
4
+ import { FileCode, Package, Boxes, Pencil } from "lucide-react"
5
+ import { ImageCropDialog } from "./image-crop-dialog"
6
+
7
+ // Get icon for asset type
8
+ export const getAssetIcon = (type?: string) => {
9
+ if (!type) return Boxes
10
+ if (type.startsWith('operation')) return FileCode
11
+ if (type.startsWith('component')) return Package
12
+ return Boxes
13
+ }
14
+
15
+ // Get color for asset type
16
+ export const getAssetColor = (type?: string) => {
17
+ if (!type) return 'text-purple-600 bg-purple-50'
18
+ if (type.startsWith('operation')) return 'text-blue-600 bg-blue-50'
19
+ if (type.startsWith('component')) return 'text-green-600 bg-green-50'
20
+ return 'text-purple-600 bg-purple-50'
21
+ }
22
+
23
+ interface AssetLogoProps {
24
+ type: string
25
+ size?: 'sm' | 'md' | 'lg'
26
+ editable?: boolean
27
+ logoUrl?: string | null
28
+ onLogoChange?: (logoBase64: string) => void
29
+ }
30
+
31
+ export function AssetLogo({ type, size = 'md', editable = false, logoUrl, onLogoChange }: AssetLogoProps) {
32
+ const [isHovered, setIsHovered] = useState(false)
33
+ const [showCropDialog, setShowCropDialog] = useState(false)
34
+ const [tempImageSrc, setTempImageSrc] = useState<string>("")
35
+ const fileInputRef = useRef<HTMLInputElement>(null)
36
+
37
+ const Icon = getAssetIcon(type)
38
+ const colorClass = getAssetColor(type)
39
+
40
+ const sizeClasses = {
41
+ sm: 'w-8 h-8',
42
+ md: 'w-12 h-12',
43
+ lg: 'w-16 h-16'
44
+ }
45
+
46
+ const iconSizes = {
47
+ sm: 'h-4 w-4',
48
+ md: 'h-6 w-6',
49
+ lg: 'h-8 w-8'
50
+ }
51
+
52
+ const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
53
+ const file = e.target.files?.[0]
54
+ if (file) {
55
+ const reader = new FileReader()
56
+ reader.onload = () => {
57
+ setTempImageSrc(reader.result as string)
58
+ setShowCropDialog(true)
59
+ }
60
+ reader.readAsDataURL(file)
61
+ }
62
+ }
63
+
64
+ const handleCropComplete = (croppedImageBase64: string) => {
65
+ if (onLogoChange) {
66
+ onLogoChange(croppedImageBase64)
67
+ }
68
+ }
69
+
70
+ const handleEditClick = () => {
71
+ fileInputRef.current?.click()
72
+ }
73
+
74
+ return (
75
+ <>
76
+ <div
77
+ className={`${sizeClasses[size]} rounded-full ${!logoUrl ? colorClass : 'bg-white border-2 border-gray-200'} flex items-center justify-center flex-shrink-0 relative group cursor-pointer`}
78
+ onMouseEnter={() => editable && setIsHovered(true)}
79
+ onMouseLeave={() => editable && setIsHovered(false)}
80
+ onClick={editable ? handleEditClick : undefined}
81
+ >
82
+ {logoUrl ? (
83
+ <img
84
+ src={logoUrl}
85
+ alt="Asset logo"
86
+ className="w-full h-full rounded-full object-cover"
87
+ />
88
+ ) : (
89
+ <Icon className={iconSizes[size]} />
90
+ )}
91
+
92
+ {editable && isHovered && (
93
+ <div className="absolute inset-0 bg-black bg-opacity-50 rounded-full flex items-center justify-center">
94
+ <Pencil className="h-4 w-4 text-white" />
95
+ </div>
96
+ )}
97
+ </div>
98
+
99
+ <input
100
+ ref={fileInputRef}
101
+ type="file"
102
+ accept="image/*"
103
+ onChange={handleFileSelect}
104
+ className="hidden"
105
+ />
106
+
107
+ {showCropDialog && (
108
+ <ImageCropDialog
109
+ open={showCropDialog}
110
+ onClose={() => setShowCropDialog(false)}
111
+ imageSrc={tempImageSrc}
112
+ onCropComplete={handleCropComplete}
113
+ />
114
+ )}
115
+ </>
116
+ )
117
+ }
@@ -20,30 +20,33 @@ export function AssetsHeader({
20
20
  subtitle,
21
21
  }: AssetsHeaderProps) {
22
22
  return (
23
- <div className="border-b bg-white flex items-center justify-between px-3 py-3 sticky top-0 z-10">
24
- {/* Left side - Back link, Logo and Title */}
25
- <div className="flex items-center gap-3">
26
- {backLink && (
27
- <Link href={backLink}>
28
- <ArrowLeft className="h-5 w-5 text-text-secondary" />
29
- </Link>
30
- )}
31
- {logo && <div className="flex-shrink-0">{logo}</div>}
32
- <div className="flex flex-col">
33
- {typeof title === 'string' ? (
34
- <h2 className="text-base font-semibold text-text-primary">{title}</h2>
35
- ) : (
36
- title
37
- )}
38
- {subtitle && (
39
- <p className="text-xs text-gray-500 mt-0.5">{subtitle}</p>
23
+ <div className="border-b bg-white sticky top-0 z-10">
24
+ {/* Mobile: Stack vertically, Desktop: Horizontal */}
25
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between px-3 sm:px-4 py-3 gap-3 sm:gap-0">
26
+ {/* Left side - Back link, Logo and Title */}
27
+ <div className="flex items-center gap-3 min-w-0">
28
+ {backLink && (
29
+ <Link href={backLink} className="flex-shrink-0">
30
+ <ArrowLeft className="h-5 w-5 text-text-secondary" />
31
+ </Link>
40
32
  )}
33
+ {logo && <div className="flex-shrink-0">{logo}</div>}
34
+ <div className="flex flex-col min-w-0">
35
+ {typeof title === 'string' ? (
36
+ <h2 className="text-sm sm:text-base font-semibold text-text-primary truncate">{title}</h2>
37
+ ) : (
38
+ title
39
+ )}
40
+ {subtitle && (
41
+ <p className="text-xs text-gray-500 mt-0.5 truncate">{subtitle}</p>
42
+ )}
43
+ </div>
41
44
  </div>
42
- </div>
43
45
 
44
- {/* Right side - Actions */}
45
- <div className="flex items-center justify-end gap-2 py-2 pr-3">
46
- {actions}
46
+ {/* Right side - Actions */}
47
+ <div className="flex items-center justify-start sm:justify-end gap-2 flex-wrap">
48
+ {actions}
49
+ </div>
47
50
  </div>
48
51
  </div>
49
52
  )
@@ -0,0 +1,90 @@
1
+ "use client"
2
+
3
+ import { useState, useRef, useEffect } from "react"
4
+ import { Pencil, Check, X } from "lucide-react"
5
+ import { Input } from "./input"
6
+ import { Button } from "./button"
7
+
8
+ interface EditableTitleProps {
9
+ value: string
10
+ onSave: (newValue: string) => void
11
+ className?: string
12
+ }
13
+
14
+ export function EditableTitle({ value, onSave, className = "" }: EditableTitleProps) {
15
+ const [isEditing, setIsEditing] = useState(false)
16
+ const [isHovered, setIsHovered] = useState(false)
17
+ const [editValue, setEditValue] = useState(value)
18
+ const inputRef = useRef<HTMLInputElement>(null)
19
+
20
+ useEffect(() => {
21
+ if (isEditing && inputRef.current) {
22
+ inputRef.current.focus()
23
+ inputRef.current.select()
24
+ }
25
+ }, [isEditing])
26
+
27
+ const handleSave = () => {
28
+ if (editValue.trim() && editValue !== value) {
29
+ onSave(editValue.trim())
30
+ }
31
+ setIsEditing(false)
32
+ }
33
+
34
+ const handleCancel = () => {
35
+ setEditValue(value)
36
+ setIsEditing(false)
37
+ }
38
+
39
+ const handleKeyDown = (e: React.KeyboardEvent) => {
40
+ if (e.key === 'Enter') {
41
+ handleSave()
42
+ } else if (e.key === 'Escape') {
43
+ handleCancel()
44
+ }
45
+ }
46
+
47
+ if (isEditing) {
48
+ return (
49
+ <div className="flex items-center gap-2">
50
+ <Input
51
+ ref={inputRef}
52
+ value={editValue}
53
+ onChange={(e) => setEditValue(e.target.value)}
54
+ onKeyDown={handleKeyDown}
55
+ className="h-8 min-w-[300px]"
56
+ />
57
+ <Button
58
+ size="sm"
59
+ variant="default"
60
+ onClick={handleSave}
61
+ className="h-8 w-8 p-0"
62
+ >
63
+ <Check className="h-4 w-4" />
64
+ </Button>
65
+ <Button
66
+ size="sm"
67
+ variant="secondary"
68
+ onClick={handleCancel}
69
+ className="h-8 w-8 p-0"
70
+ >
71
+ <X className="h-4 w-4" />
72
+ </Button>
73
+ </div>
74
+ )
75
+ }
76
+
77
+ return (
78
+ <div
79
+ className={`flex items-center gap-2 group cursor-pointer ${className}`}
80
+ onMouseEnter={() => setIsHovered(true)}
81
+ onMouseLeave={() => setIsHovered(false)}
82
+ onClick={() => setIsEditing(true)}
83
+ >
84
+ <h2 className="text-base font-semibold text-text-primary">{value}</h2>
85
+ {isHovered && (
86
+ <Pencil className="h-3 w-3 text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity" />
87
+ )}
88
+ </div>
89
+ )
90
+ }
@@ -0,0 +1,157 @@
1
+ "use client"
2
+
3
+ import { useState, useCallback } from "react"
4
+ import Cropper from "react-easy-crop"
5
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "./dialog"
6
+ import { Button } from "./button"
7
+ import { Slider } from "./slider"
8
+
9
+ interface ImageCropDialogProps {
10
+ open: boolean
11
+ onClose: () => void
12
+ imageSrc: string
13
+ onCropComplete: (croppedImageBase64: string) => void
14
+ }
15
+
16
+ interface Area {
17
+ x: number
18
+ y: number
19
+ width: number
20
+ height: number
21
+ }
22
+
23
+ const createImage = (url: string): Promise<HTMLImageElement> =>
24
+ new Promise((resolve, reject) => {
25
+ const image = new Image()
26
+ image.addEventListener('load', () => resolve(image))
27
+ image.addEventListener('error', (error) => reject(error))
28
+ image.src = url
29
+ })
30
+
31
+ async function getCroppedImg(imageSrc: string, pixelCrop: Area): Promise<string> {
32
+ const image = await createImage(imageSrc)
33
+ const canvas = document.createElement('canvas')
34
+ const ctx = canvas.getContext('2d')
35
+
36
+ if (!ctx) {
37
+ throw new Error('No 2d context')
38
+ }
39
+
40
+ // Set canvas size to a square (optimized size for logo)
41
+ const size = 128
42
+ canvas.width = size
43
+ canvas.height = size
44
+
45
+ ctx.drawImage(
46
+ image,
47
+ pixelCrop.x,
48
+ pixelCrop.y,
49
+ pixelCrop.width,
50
+ pixelCrop.height,
51
+ 0,
52
+ 0,
53
+ size,
54
+ size
55
+ )
56
+
57
+ // Use JPEG with quality 0.8 for better compression
58
+ // Falls back to PNG if the image has transparency
59
+ let dataUrl = canvas.toDataURL('image/jpeg', 0.8)
60
+
61
+ // If the compressed image is still too large (>50KB), reduce quality further
62
+ if (dataUrl.length > 50000) {
63
+ dataUrl = canvas.toDataURL('image/jpeg', 0.6)
64
+ }
65
+
66
+ return dataUrl
67
+ }
68
+
69
+ export function ImageCropDialog({ open, onClose, imageSrc, onCropComplete }: ImageCropDialogProps) {
70
+ const [crop, setCrop] = useState({ x: 0, y: 0 })
71
+ const [zoom, setZoom] = useState(1)
72
+ const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null)
73
+ const [isSaving, setIsSaving] = useState(false)
74
+
75
+ const onCropChange = useCallback((crop: { x: number; y: number }) => {
76
+ setCrop(crop)
77
+ }, [])
78
+
79
+ const onZoomChange = useCallback((zoom: number) => {
80
+ setZoom(zoom)
81
+ }, [])
82
+
83
+ const onCropCompleteCallback = useCallback(
84
+ (_croppedArea: Area, croppedAreaPixels: Area) => {
85
+ setCroppedAreaPixels(croppedAreaPixels)
86
+ },
87
+ []
88
+ )
89
+
90
+ const handleSave = async () => {
91
+ if (!croppedAreaPixels) return
92
+
93
+ setIsSaving(true)
94
+ try {
95
+ const croppedImage = await getCroppedImg(imageSrc, croppedAreaPixels)
96
+ onCropComplete(croppedImage)
97
+ onClose()
98
+ } catch (error) {
99
+ console.error('Error cropping image:', error)
100
+ } finally {
101
+ setIsSaving(false)
102
+ }
103
+ }
104
+
105
+ return (
106
+ <Dialog open={open} onOpenChange={onClose}>
107
+ <DialogContent className="sm:max-w-[600px]">
108
+ <DialogHeader>
109
+ <DialogTitle>Crop Image</DialogTitle>
110
+ </DialogHeader>
111
+ <div className="space-y-4">
112
+ <div className="relative h-[400px] bg-ibm-gray-20 rounded-lg">
113
+ <Cropper
114
+ image={imageSrc}
115
+ crop={crop}
116
+ zoom={zoom}
117
+ aspect={1}
118
+ cropShape="round"
119
+ showGrid={false}
120
+ onCropChange={onCropChange}
121
+ onZoomChange={onZoomChange}
122
+ onCropComplete={onCropCompleteCallback}
123
+ />
124
+ </div>
125
+ <div className="space-y-2">
126
+ <label className="text-sm font-medium">Zoom</label>
127
+ <Slider
128
+ value={[zoom]}
129
+ onValueChange={(value) => setZoom(value[0])}
130
+ min={1}
131
+ max={3}
132
+ step={0.1}
133
+ className="w-full"
134
+ />
135
+ </div>
136
+ </div>
137
+ <DialogFooter>
138
+ <Button
139
+ variant="secondary"
140
+ onClick={onClose}
141
+ disabled={isSaving}
142
+ className="rounded-none"
143
+ >
144
+ Cancel
145
+ </Button>
146
+ <Button
147
+ onClick={handleSave}
148
+ disabled={isSaving}
149
+ className="rounded-none"
150
+ >
151
+ {isSaving ? "Saving..." : "OK"}
152
+ </Button>
153
+ </DialogFooter>
154
+ </DialogContent>
155
+ </Dialog>
156
+ )
157
+ }
@@ -11,8 +11,12 @@ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './tool
11
11
  export { PageHeader } from './page-header'
12
12
  export { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, type ChartConfig } from './chart'
13
13
  export { SearchInput } from './search-input'
14
+ export { AssetsHeader } from './assets-header'
15
+ export { AssetLogo, getAssetIcon, getAssetColor } from './asset-logo'
16
+ export { EditableTitle } from './editable-title'
17
+ export { ImageCropDialog } from './image-crop-dialog'
14
18
  // Note: The following components are not exported as they depend on app-specific services or models:
15
- // - AssetsHeader, CertificateEditor, EnvironmentSettings, EnvironmentVariablesConfig
19
+ // - CertificateEditor, EnvironmentSettings, EnvironmentVariablesConfig
16
20
  // - FileImport, ProcessStatus, ResourceSettings, SecretExplorer, SecretPropertiesEditor, SelectedAsset
17
21
  export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from './accordion'
18
22
  export { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel } from './alert-dialog'
@@ -12,23 +12,23 @@ interface PageHeaderProps {
12
12
 
13
13
  export function PageHeader({ title, description, backLink, actions, statusNode}: PageHeaderProps) {
14
14
  return (
15
- <div className="mb-8">
16
- <div className="flex items-center justify-between">
17
- <div className="flex items-center space-x-3">
15
+ <div className="mb-4 sm:mb-8">
16
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
17
+ <div className="flex items-center space-x-3 min-w-0">
18
18
  {backLink && (
19
- <Link href={backLink}>
20
- <ArrowLeft className="h-6 w-6 text-text-secondary" />
19
+ <Link href={backLink} className="flex-shrink-0">
20
+ <ArrowLeft className="h-5 w-5 sm:h-6 sm:w-6 text-text-secondary" />
21
21
  </Link>
22
22
  )}
23
- <div>
23
+ <div className="min-w-0">
24
24
  <div className="flex items-center space-x-3">
25
- <h1 className="text-2xl font-semibold text-text-primary">{title}</h1>
26
- {statusNode && <div className="mt-2">{statusNode}</div>}
25
+ <h1 className="text-xl sm:text-2xl font-semibold text-text-primary truncate">{title}</h1>
26
+ {statusNode && <div className="mt-2 flex-shrink-0">{statusNode}</div>}
27
27
  </div>
28
- {description && <p className="text-base text-text-secondary">{description}</p>}
28
+ {description && <p className="text-sm sm:text-base text-text-secondary line-clamp-2">{description}</p>}
29
29
  </div>
30
30
  </div>
31
- {actions && <div className="flex items-center space-x-4">{actions}</div>}
31
+ {actions && <div className="flex items-center space-x-2 sm:space-x-4 flex-shrink-0">{actions}</div>}
32
32
  </div>
33
33
  </div>
34
34
  )
package/package.json CHANGED
@@ -1,94 +1,95 @@
1
- {
2
- "name": "@orsetra/shared-ui",
3
- "version": "1.0.11",
4
- "description": "Shared UI components for Orsetra platform",
5
- "main": "./index.ts",
6
- "types": "./index.ts",
7
- "exports": {
8
- ".": "./index.ts",
9
- "./components/ui": "./components/ui/index.ts",
10
- "./components/layout": "./components/layout/index.ts",
11
- "./hooks": "./hooks/index.ts",
12
- "./lib/*": "./lib/*.ts"
13
- },
14
- "files": [
15
- "index.ts",
16
- "components",
17
- "hooks",
18
- "lib",
19
- "README.md"
20
- ],
21
- "publishConfig": {
22
- "access": "public"
23
- },
24
- "repository": {
25
- "type": "git",
26
- "url": "https://github.com/orsetra/console-ui.git",
27
- "directory": "packages/shared-ui"
28
- },
29
- "keywords": [
30
- "react",
31
- "components",
32
- "ui",
33
- "orsetra",
34
- "radix-ui",
35
- "tailwind"
36
- ],
37
- "peerDependencies": {
38
- "react": "^18.0.0 || ^19.0.0",
39
- "react-dom": "^18.0.0 || ^19.0.0",
40
- "next": "^14.0.0 || ^15.0.0"
41
- },
42
- "dependencies": {
43
- "react-avatar": "^5.0.3",
44
- "react-hook-form": "^7.54.0",
45
- "react-resizable-panels": "^2.1.7",
46
- "@hookform/resolvers": "^3.9.1",
47
- "zod": "^3.24.1",
48
- "clsx": "^2.1.1",
49
- "tailwind-merge": "^2.5.5",
50
- "class-variance-authority": "^0.7.1",
51
- "lucide-react": "^0.454.0",
52
- "react-day-picker": "8.10.1",
53
- "embla-carousel-react": "8.5.1",
54
- "cmdk": "1.0.4",
55
- "recharts": "^2.15.0",
56
- "date-fns": "4.1.0",
57
- "input-otp": "1.4.1",
58
- "vaul": "^1.1.1",
59
- "next-themes": "^0.4.4",
60
- "sonner": "^1.7.1",
61
- "@radix-ui/react-accordion": "1.2.2",
62
- "@radix-ui/react-alert-dialog": "1.1.4",
63
- "@radix-ui/react-aspect-ratio": "1.1.1",
64
- "@radix-ui/react-avatar": "1.1.2",
65
- "@radix-ui/react-checkbox": "1.1.3",
66
- "@radix-ui/react-collapsible": "1.1.2",
67
- "@radix-ui/react-context-menu": "2.2.4",
68
- "@radix-ui/react-dialog": "1.1.4",
69
- "@radix-ui/react-dropdown-menu": "2.1.4",
70
- "@radix-ui/react-hover-card": "1.1.4",
71
- "@radix-ui/react-label": "2.1.1",
72
- "@radix-ui/react-menubar": "1.1.4",
73
- "@radix-ui/react-navigation-menu": "1.2.3",
74
- "@radix-ui/react-popover": "1.1.4",
75
- "@radix-ui/react-progress": "1.1.1",
76
- "@radix-ui/react-radio-group": "1.2.2",
77
- "@radix-ui/react-scroll-area": "1.2.2",
78
- "@radix-ui/react-select": "2.1.4",
79
- "@radix-ui/react-separator": "1.1.1",
80
- "@radix-ui/react-slider": "1.2.2",
81
- "@radix-ui/react-slot": "1.1.1",
82
- "@radix-ui/react-switch": "1.1.2",
83
- "@radix-ui/react-tabs": "1.1.2",
84
- "@radix-ui/react-toast": "1.2.4",
85
- "@radix-ui/react-toggle": "1.1.1",
86
- "@radix-ui/react-toggle-group": "1.1.1",
87
- "@radix-ui/react-tooltip": "1.1.6"
88
- },
89
- "devDependencies": {
90
- "@types/react": "^19",
91
- "next": "^15.0.0",
92
- "typescript": "^5"
93
- }
94
- }
1
+ {
2
+ "name": "@orsetra/shared-ui",
3
+ "version": "1.0.13",
4
+ "description": "Shared UI components for Orsetra platform",
5
+ "main": "./index.ts",
6
+ "types": "./index.ts",
7
+ "exports": {
8
+ ".": "./index.ts",
9
+ "./components/ui": "./components/ui/index.ts",
10
+ "./components/layout": "./components/layout/index.ts",
11
+ "./hooks": "./hooks/index.ts",
12
+ "./lib/*": "./lib/*.ts"
13
+ },
14
+ "files": [
15
+ "index.ts",
16
+ "components",
17
+ "hooks",
18
+ "lib",
19
+ "README.md"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/orsetra/console-ui.git",
27
+ "directory": "packages/shared-ui"
28
+ },
29
+ "keywords": [
30
+ "react",
31
+ "components",
32
+ "ui",
33
+ "orsetra",
34
+ "radix-ui",
35
+ "tailwind"
36
+ ],
37
+ "peerDependencies": {
38
+ "react": "^18.0.0 || ^19.0.0",
39
+ "react-dom": "^18.0.0 || ^19.0.0",
40
+ "next": "^14.0.0 || ^15.0.0"
41
+ },
42
+ "dependencies": {
43
+ "react-avatar": "^5.0.3",
44
+ "react-easy-crop": "^5.0.8",
45
+ "react-hook-form": "^7.54.0",
46
+ "react-resizable-panels": "^2.1.7",
47
+ "@hookform/resolvers": "^3.9.1",
48
+ "zod": "^3.24.1",
49
+ "clsx": "^2.1.1",
50
+ "tailwind-merge": "^2.5.5",
51
+ "class-variance-authority": "^0.7.1",
52
+ "lucide-react": "^0.454.0",
53
+ "react-day-picker": "8.10.1",
54
+ "embla-carousel-react": "8.5.1",
55
+ "cmdk": "1.0.4",
56
+ "recharts": "^2.15.0",
57
+ "date-fns": "4.1.0",
58
+ "input-otp": "1.4.1",
59
+ "vaul": "^1.1.1",
60
+ "next-themes": "^0.4.4",
61
+ "sonner": "^1.7.1",
62
+ "@radix-ui/react-accordion": "1.2.2",
63
+ "@radix-ui/react-alert-dialog": "1.1.4",
64
+ "@radix-ui/react-aspect-ratio": "1.1.1",
65
+ "@radix-ui/react-avatar": "1.1.2",
66
+ "@radix-ui/react-checkbox": "1.1.3",
67
+ "@radix-ui/react-collapsible": "1.1.2",
68
+ "@radix-ui/react-context-menu": "2.2.4",
69
+ "@radix-ui/react-dialog": "1.1.4",
70
+ "@radix-ui/react-dropdown-menu": "2.1.4",
71
+ "@radix-ui/react-hover-card": "1.1.4",
72
+ "@radix-ui/react-label": "2.1.1",
73
+ "@radix-ui/react-menubar": "1.1.4",
74
+ "@radix-ui/react-navigation-menu": "1.2.3",
75
+ "@radix-ui/react-popover": "1.1.4",
76
+ "@radix-ui/react-progress": "1.1.1",
77
+ "@radix-ui/react-radio-group": "1.2.2",
78
+ "@radix-ui/react-scroll-area": "1.2.2",
79
+ "@radix-ui/react-select": "2.1.4",
80
+ "@radix-ui/react-separator": "1.1.1",
81
+ "@radix-ui/react-slider": "1.2.2",
82
+ "@radix-ui/react-slot": "1.1.1",
83
+ "@radix-ui/react-switch": "1.1.2",
84
+ "@radix-ui/react-tabs": "1.1.2",
85
+ "@radix-ui/react-toast": "1.2.4",
86
+ "@radix-ui/react-toggle": "1.1.1",
87
+ "@radix-ui/react-toggle-group": "1.1.1",
88
+ "@radix-ui/react-tooltip": "1.1.6"
89
+ },
90
+ "devDependencies": {
91
+ "@types/react": "^19",
92
+ "next": "^15.0.0",
93
+ "typescript": "^5"
94
+ }
95
+ }