@opencosmos/ui 1.3.3 → 1.3.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencosmos/ui",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "OpenCosmos UI — Make it Lovable. 100 accessible React components, three themes, user-controlled motion.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -120,9 +120,11 @@ export interface AppSidebarProps {
120
120
  logo?: React.ReactNode;
121
121
  /** Wordmark shown next to the logo when expanded */
122
122
  title?: string;
123
- /** Navigation items */
123
+ /** Navigation items rendered at the top (below the header) */
124
124
  items?: AppSidebarNavItem[];
125
- /** Body slot rendered in the scrollable mid-section (e.g. conversation history). Only visible when expanded. */
125
+ /** Navigation items rendered at the bottom (above the footer) */
126
+ bottomItems?: AppSidebarNavItem[];
127
+ /** Body slot — rendered in the flex-1 mid-section (e.g. conversation history). Only visible when expanded. */
126
128
  children?: React.ReactNode;
127
129
  /** Footer slot — auth section, user avatar, sign-in prompt, etc. */
128
130
  footer?: React.ReactNode;
@@ -134,6 +136,7 @@ export function AppSidebar({
134
136
  logo,
135
137
  title,
136
138
  items = [],
139
+ bottomItems = [],
137
140
  children,
138
141
  footer,
139
142
  className,
@@ -261,6 +264,50 @@ export function AppSidebar({
261
264
  {children}
262
265
  </div>
263
266
 
267
+ {/* ── Bottom nav items ───────────────────────────────────────────── */}
268
+ {bottomItems.length > 0 && (
269
+ <nav className="px-2 py-2 space-y-1 shrink-0 border-t border-foreground/8" aria-label="Bottom navigation">
270
+ {bottomItems.map((item) => (
271
+ <a
272
+ key={item.label}
273
+ href={item.href}
274
+ target={item.external ? '_blank' : undefined}
275
+ rel={item.external ? 'noopener noreferrer' : undefined}
276
+ title={!isOpen ? item.label : undefined}
277
+ aria-label={!isOpen ? item.label : undefined}
278
+ className={cn(
279
+ 'flex items-center rounded-lg transition-colors duration-150',
280
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus)]',
281
+ isOpen
282
+ ? 'gap-3 px-3 py-3'
283
+ : 'justify-center w-9 h-9 mx-auto',
284
+ item.active
285
+ ? 'bg-foreground/8 text-foreground font-medium'
286
+ : 'text-[var(--color-text-secondary)] hover:bg-foreground/5 hover:text-[var(--color-text-primary)]'
287
+ )}
288
+ >
289
+ <span className="shrink-0 flex items-center justify-center w-4 h-4">
290
+ {item.icon}
291
+ </span>
292
+ <span
293
+ className="text-sm whitespace-nowrap"
294
+ style={{
295
+ opacity: isOpen ? 1 : 0,
296
+ width: isOpen ? 'auto' : 0,
297
+ overflow: 'hidden',
298
+ pointerEvents: isOpen ? 'auto' : 'none',
299
+ transition: shouldAnimate
300
+ ? `opacity ${Math.round(duration * 0.55)}ms ease-out`
301
+ : 'none',
302
+ }}
303
+ >
304
+ {item.label}
305
+ </span>
306
+ </a>
307
+ ))}
308
+ </nav>
309
+ )}
310
+
264
311
  {/* ── Footer ─────────────────────────────────────────────────────── */}
265
312
  {footer && (
266
313
  <div