@unicitylabs/sphere-ui 0.1.21 → 0.1.23

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,4 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
 
4
4
  type DateRangePreset = '1d' | '7d' | '30d' | '90d';
@@ -25,7 +25,7 @@ declare const PRESET_LABELS: Record<DateRangePreset, string>;
25
25
  * Emits a normalized DateRangeValue that maps 1:1 to the backend
26
26
  * ?range=... query: presets pass the label, "custom" passes from+to.
27
27
  */
28
- declare function DateRangePicker({ value, onChange, presets, className, }: DateRangePickerProps): react_jsx_runtime.JSX.Element;
28
+ declare function DateRangePicker({ value, onChange, presets, className, }: DateRangePickerProps): react.JSX.Element;
29
29
 
30
30
  interface TimeseriesSeries {
31
31
  /** Key inside each data point (e.g. 'installs'). */
@@ -55,7 +55,7 @@ interface TimeseriesChartProps {
55
55
  * etc. Encapsulates the Recharts wiring so both admin and developer
56
56
  * views render charts identically.
57
57
  */
58
- declare function TimeseriesChart({ data, series, variant, height, stacked, showLegend, showGrid, className, emptyState, }: TimeseriesChartProps): react_jsx_runtime.JSX.Element;
58
+ declare function TimeseriesChart({ data, series, variant, height, stacked, showLegend, showGrid, className, emptyState, }: TimeseriesChartProps): react.JSX.Element;
59
59
 
60
60
  interface KPICardProps {
61
61
  /** Short uppercase label above the value. */
@@ -84,7 +84,7 @@ interface KPICardProps {
84
84
  * Same shape as dev-portal's admin-card pattern so both frontends
85
85
  * render KPIs identically.
86
86
  */
87
- declare function KPICard({ label, value, icon, previousValue, hint, title, format, accentColor, className, }: KPICardProps): react_jsx_runtime.JSX.Element;
87
+ declare function KPICard({ label, value, icon, previousValue, hint, title, format, accentColor, className, }: KPICardProps): react.JSX.Element;
88
88
 
89
89
  interface TopEntity {
90
90
  id: string;
@@ -116,7 +116,7 @@ interface TopEntitiesTableProps {
116
116
  * Ranked list with a horizontal bar fill per row. Used for "top quests",
117
117
  * "top installed projects", "top users", etc. — any cross-entity ranking.
118
118
  */
119
- declare function TopEntitiesTable({ entities, title, valueLabel, secondaryLabel, hideBars, emptyState, accentColor, className, }: TopEntitiesTableProps): react_jsx_runtime.JSX.Element;
119
+ declare function TopEntitiesTable({ entities, title, valueLabel, secondaryLabel, hideBars, emptyState, accentColor, className, }: TopEntitiesTableProps): react.JSX.Element;
120
120
 
121
121
  interface AnalyticsSkeletonProps {
122
122
  /** Rendered above the grid — usually the existing page header (title + DateRangePicker). */
@@ -137,6 +137,6 @@ interface AnalyticsSkeletonProps {
137
137
  * `header` they render in the real state so the date-range picker
138
138
  * stays interactive while data loads.
139
139
  */
140
- declare function AnalyticsSkeleton({ header, showKpiRows, showCharts, showBottomTables, className, }: AnalyticsSkeletonProps): react_jsx_runtime.JSX.Element;
140
+ declare function AnalyticsSkeleton({ header, showKpiRows, showCharts, showBottomTables, className, }: AnalyticsSkeletonProps): react.JSX.Element;
141
141
 
142
142
  export { AnalyticsSkeleton, type AnalyticsSkeletonProps, type DateRangeLabel, DateRangePicker, type DateRangePickerProps, type DateRangePreset, type DateRangeValue, KPICard, type KPICardProps, TimeseriesChart, type TimeseriesChartProps, type TimeseriesSeries, TopEntitiesTable, type TopEntitiesTableProps, type TopEntity, PRESET_LABELS as dateRangePresetLabels };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
1
  import * as react from 'react';
3
2
  import { ReactNode, ButtonHTMLAttributes, InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes, MouseEvent, Ref } from 'react';
4
3
  import { ColumnDef } from '@tanstack/react-table';
@@ -17,7 +16,7 @@ interface DashboardLayoutProps {
17
16
  * Dashboard shell with sidebar + main content area.
18
17
  * Used by sphere-backoffice and sphere-dev-portal.
19
18
  */
20
- declare function DashboardLayout({ logo, nav, footer, children }: DashboardLayoutProps): react_jsx_runtime.JSX.Element;
19
+ declare function DashboardLayout({ logo, nav, footer, children }: DashboardLayoutProps): react.JSX.Element;
21
20
 
22
21
  interface AppLogoProps {
23
22
  /** Short code displayed in the icon (e.g. "SQ", "SD") */
@@ -33,7 +32,7 @@ interface AppLogoProps {
33
32
  * App logo for dashboard sidebar.
34
33
  * Orange icon + Anton title + Geist subtitle.
35
34
  */
36
- declare function AppLogo({ icon, title, subtitle, onClick }: AppLogoProps): react_jsx_runtime.JSX.Element;
35
+ declare function AppLogo({ icon, title, subtitle, onClick }: AppLogoProps): react.JSX.Element;
37
36
 
38
37
  interface NavItem {
39
38
  to: string;
@@ -60,7 +59,7 @@ interface SidebarNavProps {
60
59
  * Sidebar navigation with grouped items, icons, and badges.
61
60
  * Matches the admin panel sidebar style exactly.
62
61
  */
63
- declare function SidebarNav({ groups, currentPath, onNavigate }: SidebarNavProps): react_jsx_runtime.JSX.Element;
62
+ declare function SidebarNav({ groups, currentPath, onNavigate }: SidebarNavProps): react.JSX.Element;
64
63
 
65
64
  interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
66
65
  error?: boolean;
@@ -89,12 +88,12 @@ interface FieldProps {
89
88
  className?: string;
90
89
  children: ReactNode;
91
90
  }
92
- declare function Field({ label, required, error, hint, className, children }: FieldProps): react_jsx_runtime.JSX.Element;
91
+ declare function Field({ label, required, error, hint, className, children }: FieldProps): react.JSX.Element;
93
92
  interface SectionProps {
94
93
  title: string;
95
94
  children: ReactNode;
96
95
  }
97
- declare function Section({ title, children }: SectionProps): react_jsx_runtime.JSX.Element;
96
+ declare function Section({ title, children }: SectionProps): react.JSX.Element;
98
97
 
99
98
  interface FormModalProps {
100
99
  title: string;
@@ -121,21 +120,21 @@ interface StatusBadgeProps {
121
120
  status: string;
122
121
  className?: string;
123
122
  }
124
- declare function StatusBadge({ status, className }: StatusBadgeProps): react_jsx_runtime.JSX.Element;
123
+ declare function StatusBadge({ status, className }: StatusBadgeProps): react.JSX.Element;
125
124
 
126
125
  interface SearchInputProps {
127
126
  value: string;
128
127
  onChange: (value: string) => void;
129
128
  placeholder?: string;
130
129
  }
131
- declare function SearchInput({ value, onChange, placeholder }: SearchInputProps): react_jsx_runtime.JSX.Element;
130
+ declare function SearchInput({ value, onChange, placeholder }: SearchInputProps): react.JSX.Element;
132
131
 
133
132
  interface EmptyStateProps {
134
133
  title: string;
135
134
  description?: string;
136
135
  action?: ReactNode;
137
136
  }
138
- declare function EmptyState({ title, description, action }: EmptyStateProps): react_jsx_runtime.JSX.Element;
137
+ declare function EmptyState({ title, description, action }: EmptyStateProps): react.JSX.Element;
139
138
 
140
139
  interface SkeletonProps {
141
140
  width?: string;
@@ -143,7 +142,7 @@ interface SkeletonProps {
143
142
  radius?: string;
144
143
  className?: string;
145
144
  }
146
- declare function Skeleton({ width, height, radius, className, }: SkeletonProps): react_jsx_runtime.JSX.Element;
145
+ declare function Skeleton({ width, height, radius, className, }: SkeletonProps): react.JSX.Element;
147
146
 
148
147
  interface SkeletonTextProps {
149
148
  lines?: number;
@@ -151,14 +150,14 @@ interface SkeletonTextProps {
151
150
  gap?: string;
152
151
  className?: string;
153
152
  }
154
- declare function SkeletonText({ lines, lineHeight, gap, className, }: SkeletonTextProps): react_jsx_runtime.JSX.Element;
153
+ declare function SkeletonText({ lines, lineHeight, gap, className, }: SkeletonTextProps): react.JSX.Element;
155
154
 
156
155
  type SkeletonCircleSize = 'sm' | 'md' | 'lg' | (string & {});
157
156
  interface SkeletonCircleProps {
158
157
  size?: SkeletonCircleSize;
159
158
  className?: string;
160
159
  }
161
- declare function SkeletonCircle({ size, className }: SkeletonCircleProps): react_jsx_runtime.JSX.Element;
160
+ declare function SkeletonCircle({ size, className }: SkeletonCircleProps): react.JSX.Element;
162
161
 
163
162
  interface SelectOption {
164
163
  value: string;
@@ -172,7 +171,7 @@ interface CustomSelectProps {
172
171
  className?: string;
173
172
  size?: 'sm' | 'md';
174
173
  }
175
- declare function CustomSelect({ options, value, onChange, placeholder, className, size, }: CustomSelectProps): react_jsx_runtime.JSX.Element;
174
+ declare function CustomSelect({ options, value, onChange, placeholder, className, size, }: CustomSelectProps): react.JSX.Element;
176
175
 
177
176
  interface PageShellProps {
178
177
  title: string;
@@ -181,7 +180,7 @@ interface PageShellProps {
181
180
  maxWidth?: string;
182
181
  children: ReactNode;
183
182
  }
184
- declare function PageShell({ title, subtitle, action, maxWidth, children }: PageShellProps): react_jsx_runtime.JSX.Element;
183
+ declare function PageShell({ title, subtitle, action, maxWidth, children }: PageShellProps): react.JSX.Element;
185
184
 
186
185
  interface DataTableProps<T> {
187
186
  columns: ColumnDef<T, unknown>[];
@@ -192,21 +191,21 @@ interface DataTableProps<T> {
192
191
  enableSearch?: boolean;
193
192
  onRowClick?: (row: T) => void;
194
193
  }
195
- declare function DataTable<T>({ columns, data, isLoading, emptyMessage, searchPlaceholder, enableSearch, onRowClick, }: DataTableProps<T>): react_jsx_runtime.JSX.Element;
194
+ declare function DataTable<T>({ columns, data, isLoading, emptyMessage, searchPlaceholder, enableSearch, onRowClick, }: DataTableProps<T>): react.JSX.Element;
196
195
 
197
196
  interface AlertBannerProps {
198
197
  type: 'warning' | 'info';
199
198
  title: string;
200
199
  children: ReactNode;
201
200
  }
202
- declare function AlertBanner({ type, title, children }: AlertBannerProps): react_jsx_runtime.JSX.Element;
201
+ declare function AlertBanner({ type, title, children }: AlertBannerProps): react.JSX.Element;
203
202
 
204
203
  interface AddressDisplayProps {
205
204
  address: string;
206
205
  nametag?: string | null;
207
206
  truncate?: boolean;
208
207
  }
209
- declare function AddressDisplay({ address, nametag, truncate }: AddressDisplayProps): react_jsx_runtime.JSX.Element;
208
+ declare function AddressDisplay({ address, nametag, truncate }: AddressDisplayProps): react.JSX.Element;
210
209
 
211
210
  interface JsonPanelProps<T> {
212
211
  /** Current form state to display as JSON */
@@ -218,11 +217,11 @@ interface JsonPanelProps<T> {
218
217
  /** Panel title */
219
218
  title?: string;
220
219
  }
221
- declare function JsonPanel<T extends Record<string, unknown>>({ value, onChange, excludeKeys, title, }: JsonPanelProps<T>): react_jsx_runtime.JSX.Element;
220
+ declare function JsonPanel<T extends Record<string, unknown>>({ value, onChange, excludeKeys, title, }: JsonPanelProps<T>): react.JSX.Element;
222
221
  declare function JsonToggleButton({ active, onClick }: {
223
222
  active: boolean;
224
223
  onClick: () => void;
225
- }): react_jsx_runtime.JSX.Element;
224
+ }): react.JSX.Element;
226
225
 
227
226
  declare function tagColor(tag: string): {
228
227
  bg: string;
@@ -234,7 +233,7 @@ declare function ChainInput({ chains, suggestions, onChange, size }: {
234
233
  suggestions: string[];
235
234
  onChange: (chains: Record<string, number>) => void;
236
235
  size?: 'sm' | 'md';
237
- }): react_jsx_runtime.JSX.Element;
236
+ }): react.JSX.Element;
238
237
 
239
238
  interface MemoCondition {
240
239
  key: string;
@@ -245,7 +244,7 @@ interface MemoCondition {
245
244
  declare function MemoConditionsEditor({ conditions, onChange }: {
246
245
  conditions: MemoCondition[];
247
246
  onChange: (conditions: MemoCondition[]) => void;
248
- }): react_jsx_runtime.JSX.Element;
247
+ }): react.JSX.Element;
249
248
 
250
249
  /**
251
250
  * Consistent SVG icon set for the admin panel.
@@ -256,27 +255,27 @@ interface IconProps {
256
255
  className?: string;
257
256
  style?: React.CSSProperties;
258
257
  }
259
- declare const IconBack: (p: IconProps) => react_jsx_runtime.JSX.Element;
260
- declare const IconUndo: (p: IconProps) => react_jsx_runtime.JSX.Element;
261
- declare const IconQuests: (p: IconProps) => react_jsx_runtime.JSX.Element;
262
- declare const IconTracks: (p: IconProps) => react_jsx_runtime.JSX.Element;
263
- declare const IconSettings: (p: IconProps) => react_jsx_runtime.JSX.Element;
264
- declare const IconChain: (p: IconProps) => react_jsx_runtime.JSX.Element;
265
- declare const IconPlus: (p: IconProps) => react_jsx_runtime.JSX.Element;
266
- declare const IconEdit: (p: IconProps) => react_jsx_runtime.JSX.Element;
267
- declare const IconTrash: (p: IconProps) => react_jsx_runtime.JSX.Element;
268
- declare const IconX: (p: IconProps) => react_jsx_runtime.JSX.Element;
269
- declare const IconCheck: (p: IconProps) => react_jsx_runtime.JSX.Element;
270
- declare const IconSearch: (p: IconProps) => react_jsx_runtime.JSX.Element;
271
- declare const IconChevronUp: (p: IconProps) => react_jsx_runtime.JSX.Element;
272
- declare const IconChevronDown: (p: IconProps) => react_jsx_runtime.JSX.Element;
273
- declare const IconChevronsDown: (p: IconProps) => react_jsx_runtime.JSX.Element;
274
- declare const IconChevronsRight: (p: IconProps) => react_jsx_runtime.JSX.Element;
275
- declare const IconArrowRight: (p: IconProps) => react_jsx_runtime.JSX.Element;
276
- declare const IconPlay: (p: IconProps) => react_jsx_runtime.JSX.Element;
277
- declare const IconStar: (p: IconProps) => react_jsx_runtime.JSX.Element;
278
- declare const IconDiamond: (p: IconProps) => react_jsx_runtime.JSX.Element;
279
- declare const IconCircle: (p: IconProps) => react_jsx_runtime.JSX.Element;
258
+ declare const IconBack: (p: IconProps) => react.JSX.Element;
259
+ declare const IconUndo: (p: IconProps) => react.JSX.Element;
260
+ declare const IconQuests: (p: IconProps) => react.JSX.Element;
261
+ declare const IconTracks: (p: IconProps) => react.JSX.Element;
262
+ declare const IconSettings: (p: IconProps) => react.JSX.Element;
263
+ declare const IconChain: (p: IconProps) => react.JSX.Element;
264
+ declare const IconPlus: (p: IconProps) => react.JSX.Element;
265
+ declare const IconEdit: (p: IconProps) => react.JSX.Element;
266
+ declare const IconTrash: (p: IconProps) => react.JSX.Element;
267
+ declare const IconX: (p: IconProps) => react.JSX.Element;
268
+ declare const IconCheck: (p: IconProps) => react.JSX.Element;
269
+ declare const IconSearch: (p: IconProps) => react.JSX.Element;
270
+ declare const IconChevronUp: (p: IconProps) => react.JSX.Element;
271
+ declare const IconChevronDown: (p: IconProps) => react.JSX.Element;
272
+ declare const IconChevronsDown: (p: IconProps) => react.JSX.Element;
273
+ declare const IconChevronsRight: (p: IconProps) => react.JSX.Element;
274
+ declare const IconArrowRight: (p: IconProps) => react.JSX.Element;
275
+ declare const IconPlay: (p: IconProps) => react.JSX.Element;
276
+ declare const IconStar: (p: IconProps) => react.JSX.Element;
277
+ declare const IconDiamond: (p: IconProps) => react.JSX.Element;
278
+ declare const IconCircle: (p: IconProps) => react.JSX.Element;
280
279
 
281
280
  type MediaKind = 'logo' | 'banner' | 'screenshot' | 'image' | 'background';
282
281
  type MediaMime = 'image/png' | 'image/jpeg' | 'image/webp' | 'image/svg+xml';
@@ -322,7 +321,7 @@ interface MediaUploaderProps {
322
321
  deferUpload?: boolean;
323
322
  onFileSelected?: (file: File | null) => void;
324
323
  }
325
- declare function MediaUploader({ kind, ownerType, ownerId, value, onChange, uploadFn, label, deferUpload, onFileSelected, }: MediaUploaderProps): react_jsx_runtime.JSX.Element;
324
+ declare function MediaUploader({ kind, ownerType, ownerId, value, onChange, uploadFn, label, deferUpload, onFileSelected, }: MediaUploaderProps): react.JSX.Element;
326
325
 
327
326
  interface MediaItem {
328
327
  type: 'screenshot' | 'video';
@@ -336,7 +335,7 @@ interface MediaGalleryProps {
336
335
  uploadFn: MediaUploadFn;
337
336
  max?: number;
338
337
  }
339
- declare function MediaGallery({ ownerType, ownerId, items, onChange, uploadFn, max }: MediaGalleryProps): react_jsx_runtime.JSX.Element;
338
+ declare function MediaGallery({ ownerType, ownerId, items, onChange, uploadFn, max }: MediaGalleryProps): react.JSX.Element;
340
339
 
341
340
  interface MarketplaceProjectCardProps {
342
341
  name: string;
@@ -365,7 +364,7 @@ interface MarketplaceProjectCardProps {
365
364
  *
366
365
  * No `<Link>` dependency — wrap externally if router navigation is needed.
367
366
  */
368
- declare function MarketplaceProjectCard({ name, tagline, logoUrl, bannerUrl, accentColor, category, users, quests, positivePercent, ratingCount, installState, onInstallClick, onClick, }: MarketplaceProjectCardProps): react_jsx_runtime.JSX.Element;
367
+ declare function MarketplaceProjectCard({ name, tagline, logoUrl, bannerUrl, accentColor, category, users, quests, positivePercent, ratingCount, installState, onInstallClick, onClick, }: MarketplaceProjectCardProps): react.JSX.Element;
369
368
 
370
369
  interface FeaturedProjectCardProps {
371
370
  name: string;
@@ -386,7 +385,7 @@ interface FeaturedProjectCardProps {
386
385
  * Wide hero-style card used in marketplace featured rails.
387
386
  * No `<Link>` dependency — wrap externally if router navigation is needed.
388
387
  */
389
- declare function FeaturedProjectCard({ name, tagline, logoUrl, bannerUrl, accentColor, users, quests, positivePercent, ratingCount, onClick, }: FeaturedProjectCardProps): react_jsx_runtime.JSX.Element;
388
+ declare function FeaturedProjectCard({ name, tagline, logoUrl, bannerUrl, accentColor, users, quests, positivePercent, ratingCount, onClick, }: FeaturedProjectCardProps): react.JSX.Element;
390
389
 
391
390
  /** Loose typing — dnd-kit listeners/attributes don't fit React's HTMLButtonAttributes due to motion.button overrides. */
392
391
  type ButtonExtraProps = {
@@ -419,7 +418,7 @@ interface InstalledProjectIconProps {
419
418
  * primary action, `onContextMenu` for right-click, and
420
419
  * `topRightAction` to overlay a small action button on the tile.
421
420
  */
422
- declare function InstalledProjectIcon({ name, logoUrl, accentColor, onClick, onContextMenu, topRightAction, showLabel, buttonRef, buttonProps, }: InstalledProjectIconProps): react_jsx_runtime.JSX.Element;
421
+ declare function InstalledProjectIcon({ name, logoUrl, accentColor, onClick, onContextMenu, topRightAction, showLabel, buttonRef, buttonProps, }: InstalledProjectIconProps): react.JSX.Element;
423
422
 
424
423
  type ProjectLogoSize = 'sm' | 'md' | 'lg';
425
424
  interface ProjectLogoProps {
@@ -443,7 +442,7 @@ interface ProjectLogoProps {
443
442
  * cards, install previews) so the icon looks the same regardless of
444
443
  * surrounding chrome.
445
444
  */
446
- declare function ProjectLogo({ name, logoUrl, accentColor, size, className, children, }: ProjectLogoProps): react_jsx_runtime.JSX.Element;
445
+ declare function ProjectLogo({ name, logoUrl, accentColor, size, className, children, }: ProjectLogoProps): react.JSX.Element;
447
446
 
448
447
  interface QuestPreviewSummary {
449
448
  slug: string;
@@ -490,7 +489,7 @@ interface ProjectPagePreviewProps {
490
489
  * Used by dev-portal & backoffice as a live preview while editing a project.
491
490
  * No router / hooks / data fetching — every value comes via props.
492
491
  */
493
- declare function ProjectPagePreview({ name, tagline, description, logoUrl, bannerUrl, accentColor, category, websiteUrl, discordUrl, twitterUrl, media, users, activeQuests, positivePercent, ratingCount, quests, achievements, tags, }: ProjectPagePreviewProps): react_jsx_runtime.JSX.Element;
492
+ declare function ProjectPagePreview({ name, tagline, description, logoUrl, bannerUrl, accentColor, category, websiteUrl, discordUrl, twitterUrl, media, users, activeQuests, positivePercent, ratingCount, quests, achievements, tags, }: ProjectPagePreviewProps): react.JSX.Element;
494
493
 
495
494
  declare const MEDIA_LIMITS: Record<MediaKind, MediaLimit>;
496
495
  declare function isMimeAllowed(kind: MediaKind, mime: string): mime is MediaMime;
package/dist/index.js CHANGED
@@ -1648,7 +1648,7 @@ function MediaUploader({
1648
1648
  placeholder: "https://...",
1649
1649
  value: urlInput,
1650
1650
  onChange: (e) => setUrlInput(e.target.value),
1651
- onBlur: () => urlInput && onChange(urlInput)
1651
+ onBlur: () => onChange(urlInput.trim() || null)
1652
1652
  }
1653
1653
  )
1654
1654
  ] })
@@ -1784,6 +1784,9 @@ function getInitials(name) {
1784
1784
  }
1785
1785
  return name.slice(0, 2).toUpperCase();
1786
1786
  }
1787
+ function isPlaceholderUrl(url) {
1788
+ return /^https?:\/\/(?:placehold\.co|placeholder\.com|via\.placeholder\.com|ui-avatars\.com|placekitten\.com|dummyimage\.com)\b/i.test(url);
1789
+ }
1787
1790
  function ProjectLogo({
1788
1791
  name,
1789
1792
  logoUrl,
@@ -1793,7 +1796,8 @@ function ProjectLogo({
1793
1796
  children
1794
1797
  }) {
1795
1798
  const [imgError, setImgError] = useState9(false);
1796
- const showImage = logoUrl && !imgError;
1799
+ const isReal = !!logoUrl && !isPlaceholderUrl(logoUrl);
1800
+ const showImage = isReal && !imgError;
1797
1801
  const tileBackground = `linear-gradient(to bottom right, ${accentColor}, color-mix(in srgb, ${accentColor} 60%, white))`;
1798
1802
  return /* @__PURE__ */ jsxs20(
1799
1803
  "div",
@@ -1810,7 +1814,8 @@ function ProjectLogo({
1810
1814
  }
1811
1815
  }
1812
1816
  ),
1813
- /* @__PURE__ */ jsx25("div", { className: "absolute top-0 right-0 w-1/2 h-1/2 bg-white/10 rounded-bl-full pointer-events-none" }),
1817
+ /* @__PURE__ */ jsx25("div", { className: "absolute inset-x-0 top-0 h-1/2 bg-linear-to-b from-white/25 to-transparent pointer-events-none" }),
1818
+ /* @__PURE__ */ jsx25("div", { className: "absolute top-0 right-0 w-1/2 h-1/2 bg-white/30 rounded-bl-full pointer-events-none" }),
1814
1819
  showImage ? /* @__PURE__ */ jsx25(
1815
1820
  "img",
1816
1821
  {
@@ -2131,7 +2136,6 @@ function ProjectPagePreview({
2131
2136
  achievements = [],
2132
2137
  tags = []
2133
2138
  }) {
2134
- const placeholderLogo = `https://placehold.co/80x80/${accentColor.slice(1)}/white?text=${name[0] ?? "?"}`;
2135
2139
  return /* @__PURE__ */ jsxs24(
2136
2140
  motion4.div,
2137
2141
  {
@@ -2158,15 +2162,13 @@ function ProjectPagePreview({
2158
2162
  "Explore"
2159
2163
  ] })
2160
2164
  ] }),
2161
- /* @__PURE__ */ jsx29("div", { className: "absolute -bottom-6 left-6 sm:left-8 z-10", children: /* @__PURE__ */ jsx29(
2162
- "img",
2165
+ /* @__PURE__ */ jsx29("div", { className: "absolute -bottom-6 left-6 sm:left-8 z-10 rounded-2xl border-4 border-white dark:border-[#060606] shadow-xl overflow-hidden", children: /* @__PURE__ */ jsx29(
2166
+ ProjectLogo,
2163
2167
  {
2164
- src: logoUrl ?? placeholderLogo,
2165
- alt: name,
2166
- className: "w-16 h-16 sm:w-20 sm:h-20 rounded-2xl object-cover border-4 border-white dark:border-[#060606] shadow-xl",
2167
- onError: (e) => {
2168
- e.target.src = placeholderLogo;
2169
- }
2168
+ name,
2169
+ logoUrl,
2170
+ accentColor,
2171
+ size: "lg"
2170
2172
  }
2171
2173
  ) })
2172
2174
  ] }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unicitylabs/sphere-ui",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -43,16 +43,18 @@
43
43
  "test": "vitest",
44
44
  "test:run": "vitest run"
45
45
  },
46
+ "dependencies": {
47
+ "framer-motion": "^11.18.2",
48
+ "lucide-react": ">=0.400.0",
49
+ "react-dropzone": "^14.4.1"
50
+ },
46
51
  "peerDependencies": {
47
52
  "@dnd-kit/core": "^6.0.0",
48
53
  "@dnd-kit/sortable": "^8.0.0 || ^10.0.0",
49
54
  "@tanstack/react-query": "^5.0.0",
50
55
  "@tanstack/react-table": "^8.0.0",
51
- "framer-motion": "^11.18.2",
52
- "lucide-react": ">=0.400.0",
53
56
  "react": "^19.0.0",
54
57
  "react-dom": "^19.0.0",
55
- "react-dropzone": "^14.4.1",
56
58
  "recharts": "^3.0.0"
57
59
  },
58
60
  "peerDependenciesMeta": {