@lessly/ui 0.5.0 → 0.7.0

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/dist/index.d.ts CHANGED
@@ -652,6 +652,8 @@ declare function ConfirmDialog({ open, onOpenChange, trigger, title, description
652
652
 
653
653
  interface UserMenuUser {
654
654
  name: string;
655
+ /** Second line of the row. The shipped rail shows the org name here. */
656
+ subtitle?: string;
655
657
  email?: string;
656
658
  avatarUrl?: string;
657
659
  }
@@ -822,10 +824,22 @@ declare function SidebarNav({ items, collapsed, basePath, LinkComponent, isItemA
822
824
 
823
825
  interface AppSidebarProps {
824
826
  items: NavItem[];
827
+ /** Expanded-slot mark/wordmark, rendered when `header` is absent. Also the collapsed-rail
828
+ * fallback when `logoCollapsed` isn't given — see `logoCollapsed`. */
825
829
  logo?: React$1.ReactNode;
830
+ /** Collapsed-rail identity — a small mark, shown overlaid by the pin on hover/focus when
831
+ * `collapsible`. A collapsible rail should supply this (or `logo`): `header` is expanded-only
832
+ * and is never read while collapsed, so without a mark the collapsed rail shows just the pin. */
826
833
  logoCollapsed?: React$1.ReactNode;
834
+ /** Identity slot above the rail — e.g. a <SidebarProductHeader/>. Falls back to `logo`.
835
+ * Expanded-only: rendering it in the 52px collapsed rail would overflow, so the collapsed
836
+ * branch never reads it — supply `logoCollapsed` (or `logo`) for that state instead. */
827
837
  header?: React$1.ReactNode;
838
+ /** Account slot below the rail — e.g. a <UserMenu/>. Its divider renders only with it. */
828
839
  footer?: React$1.ReactNode;
840
+ /** Render the built-in collapse pin. Off by default: the shipped rail has none.
841
+ * `collapsed` is still honoured without it, for consumers driving collapse themselves. */
842
+ collapsible?: boolean;
829
843
  collapsed?: boolean;
830
844
  defaultCollapsed?: boolean;
831
845
  onCollapsedChange?: (collapsed: boolean) => void;
@@ -834,7 +848,38 @@ interface AppSidebarProps {
834
848
  isItemActive?: (item: NavItem, pathname: string) => boolean;
835
849
  className?: string;
836
850
  }
837
- declare function AppSidebar({ items, logo, logoCollapsed, header, footer, collapsed: controlled, defaultCollapsed, onCollapsedChange, basePath, LinkComponent, isItemActive, className, }: AppSidebarProps): React$1.JSX.Element;
851
+ declare function AppSidebar({ items, logo, logoCollapsed, header, footer, collapsible, collapsed: controlled, defaultCollapsed, onCollapsedChange, basePath, LinkComponent, isItemActive, className, }: AppSidebarProps): React$1.JSX.Element;
852
+
853
+ interface SidebarProductHeaderOwnProps {
854
+ /** Product name. Rendered at 13/600, truncated. */
855
+ name: string;
856
+ /** Leading mark, rendered in a fixed 28px box (the shipped tile). */
857
+ icon?: React$1.ReactNode;
858
+ /** Right-aligned adornment. Defaults to the 14px ChevronsUpDown of the shipped header;
859
+ * pass `null` for none. */
860
+ trailing?: React$1.ReactNode;
861
+ className?: string;
862
+ }
863
+ /** Render the single child element instead of a button — e.g. a MenuPopup trigger.
864
+ * The header composes its content from props, so the child's own children are replaced. */
865
+ type SidebarProductHeaderProps = SidebarProductHeaderOwnProps & Omit<React$1.ComponentPropsWithoutRef<'button'>, 'children' | 'name'> & ({
866
+ asChild: true;
867
+ children: React$1.ReactElement;
868
+ } | {
869
+ asChild?: false;
870
+ children?: never;
871
+ });
872
+ /** Identity row for the sidebar header slot: product tile + name, opening the product menu.
873
+ *
874
+ * Deliberately inert on hover — no background, no transition — unlike the rail rows below
875
+ * it. `rounded-xl` is not dead weight despite nothing tinting the box: this declares no
876
+ * focus-visible ring, so the UA default ring is the only focus affordance, and that ring
877
+ * follows border-radius.
878
+ *
879
+ * Pair with `AppSidebar`'s `header` slot: the slot's 12px sides plus this trigger's own 6px
880
+ * land the tile at 18px, while the trigger's box stays flush with the rail rows at 12px —
881
+ * which anchors a `MenuPopup` at left:12 with the default sideOffset, no alignOffset. */
882
+ declare const SidebarProductHeader: React$1.ForwardRefExoticComponent<SidebarProductHeaderProps & React$1.RefAttributes<HTMLButtonElement>>;
838
883
 
839
884
  interface AppShellProps {
840
885
  /** The sidebar, typically an <AppSidebar/>. Rendered in the desktop rail and,
@@ -941,24 +986,48 @@ interface ConnectionCardProps extends React$1.HTMLAttributes<HTMLDivElement> {
941
986
  glyph?: React$1.ReactNode;
942
987
  /** Override the default status label (e.g. "Token expired" for `error`). */
943
988
  statusLabel?: string;
944
- /** How many products hold a segment of this connection. */
989
+ /**
990
+ * How many products hold a segment of this connection. `undefined` renders the
991
+ * "Not granted to any product yet" default — pass it ONLY on a surface where
992
+ * that claim is true and knowable (the org roster); on a product surface pass
993
+ * neither `grantedCount` nor a footer `action` to render no footer at all.
994
+ */
945
995
  grantedCount?: number;
946
- /** Footer action — typically a <Button> ("Manage", "Reconnect", "Connect"). */
996
+ /**
997
+ * Footer action — typically a <Button> ("Manage", "Reconnect", "Connect").
998
+ * Flat mode: a disconnected card shows it in the header. Collapsible mode: the
999
+ * header is a disclosure trigger (nesting a button would be invalid), so the
1000
+ * action always renders in the expanded footer instead.
1001
+ */
947
1002
  action?: React$1.ReactNode;
948
1003
  /**
949
1004
  * Blast-radius disclosure. Some providers (GitHub, ClickUp) grant all-or-nothing
950
- * access to the whole external org — there is no per-repo/per-space permission, so
951
- * a product's segment is enforced by Lessly's gateway, not the provider. When set,
952
- * a warning notice is shown, e.g. "Full org access — all repositories."
1005
+ * access to the whole external org — there is no per-repo/per-space permission,
1006
+ * and the vended credential genuinely reaches the whole external org. Copy passed
1007
+ * here must state that blast radius honestly (e.g. "Full org access — any product
1008
+ * granted this connection can reach every repository"); do NOT claim the segment
1009
+ * is enforced by Lessly — for these providers it is not.
953
1010
  */
954
1011
  access?: React$1.ReactNode;
955
1012
  /**
956
- * Drawer content rendered below the card body — the org surface's accordion
957
- * drawer in the integration prototype (the per-product grants roster, inline
958
- * requests, "+ Grant to a product"). The caller owns expand/collapse: pass
959
- * children only while open. Omitted = the flat card, unchanged.
1013
+ * Drawer content rendered below the card body — the per-product grants roster,
1014
+ * inline requests, "+ Grant to a product". Flat mode: the caller owns visibility,
1015
+ * pass children only while open (unchanged behavior). Collapsible mode: pass
1016
+ * children unconditionally the card shows them while expanded.
960
1017
  */
961
1018
  children?: React$1.ReactNode;
1019
+ /**
1020
+ * Disclosure mode: the header row toggles the card open/closed; the footer and
1021
+ * drawer render only while open. The `access` blast-radius notice stays visible
1022
+ * even while collapsed — it is a safety disclosure, not a detail. Collapsed by
1023
+ * default.
1024
+ */
1025
+ collapsible?: boolean;
1026
+ /** Uncontrolled initial state (collapsible mode). Default: false (collapsed). */
1027
+ defaultOpen?: boolean;
1028
+ /** Controlled open state (collapsible mode); pair with `onOpenChange`. */
1029
+ open?: boolean;
1030
+ onOpenChange?: (open: boolean) => void;
962
1031
  }
963
1032
  declare const ConnectionCard: React$1.ForwardRefExoticComponent<ConnectionCardProps & React$1.RefAttributes<HTMLDivElement>>;
964
1033
 
@@ -986,4 +1055,4 @@ interface RequestRowProps extends Omit<React$1.HTMLAttributes<HTMLDivElement>, '
986
1055
  }
987
1056
  declare const RequestRow: React$1.ForwardRefExoticComponent<RequestRowProps & React$1.RefAttributes<HTMLDivElement>>;
988
1057
 
989
- export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppShell, type AppShellProps, AppSidebar, type AppSidebarProps, AspectRatio, AuthenticatedLayout, Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, type CarouselApi, CarouselContent, type CarouselContextProps, CarouselItem, CarouselNext, type CarouselOptions, type CarouselPlugin, CarouselPrevious, type CarouselProps, Checkbox, Col, type ColProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ConfirmDialog, type ConfirmDialogProps, type ConnectionAuth, ConnectionCard, type ConnectionCardProps, type ConnectionScope, type ConnectionStatus, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, type CopyButtonProps, DatePicker, type DatePickerProps, type DeltaDirection, type DeltaTone, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DocsLink, type DocsLinkProps, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, ExtensionIcon, type ExtensionIconProps, ExtensionLink, type ExtensionLinkProps, type ExtensionLinkUiMode, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Grid, GridOverlay, type GridOverlayProps, type GridProps, HoverCard, HoverCardContent, HoverCardTrigger, InlineError, type InlineErrorProps, Input, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Label, MenuDivider, MenuPopup, type MenuPopupProps, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, type NavItem, type NavLinkComponent, NavRow, type NavRowProps, type NavRowVariant, NavSectionLabel, type NavSectionLabelProps, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, type PaginationLinkProps, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, RequestRow, type RequestRowProps, type RequestState, type RequestSurface, type ResolvedTheme, type Responsive, ScrollArea, ScrollBar, SectionIntro, type SectionIntroProps, SegmentChip, type SegmentChipProps, Select, type SelectOption, type SelectProps, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarNav, SidebarNavLink, type SidebarNavLinkProps, type SidebarNavProps, Skeleton, Slider, StatCard, type StatCardProps, StatusDot, type StatusDotProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, type Theme, ThemeToggle, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UseSidebarOptions, type UseSidebarResult, UserMenu, type UserMenuItem, type UserMenuProps, type UserMenuUser, alertVariants, badgeVariants, buttonVariants, cn, isKnownExtensionIcon, navigationMenuTriggerStyle, segmentChipVariants, statusDotVariants, toggleVariants, useCarousel, useFormField, useIsMobile, useSidebar, useTheme };
1058
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, AlertDescription, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AlertTitle, AppShell, type AppShellProps, AppSidebar, type AppSidebarProps, AspectRatio, AuthenticatedLayout, Avatar, AvatarFallback, AvatarImage, Badge, type BadgeProps, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, type ButtonProps, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, type CarouselApi, CarouselContent, type CarouselContextProps, CarouselItem, CarouselNext, type CarouselOptions, type CarouselPlugin, CarouselPrevious, type CarouselProps, Checkbox, Col, type ColProps, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ConfirmDialog, type ConfirmDialogProps, type ConnectionAuth, ConnectionCard, type ConnectionCardProps, type ConnectionScope, type ConnectionStatus, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, type CopyButtonProps, DatePicker, type DatePickerProps, type DeltaDirection, type DeltaTone, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, DocsLink, type DocsLinkProps, Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, ExtensionIcon, type ExtensionIconProps, ExtensionLink, type ExtensionLinkProps, type ExtensionLinkUiMode, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Grid, GridOverlay, type GridOverlayProps, type GridProps, HoverCard, HoverCardContent, HoverCardTrigger, InlineError, type InlineErrorProps, Input, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, type InputProps, Label, MenuDivider, MenuPopup, type MenuPopupProps, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, type NavItem, type NavLinkComponent, NavRow, type NavRowProps, type NavRowVariant, NavSectionLabel, type NavSectionLabelProps, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, type PaginationLinkProps, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, RequestRow, type RequestRowProps, type RequestState, type RequestSurface, type ResolvedTheme, type Responsive, ScrollArea, ScrollBar, SectionIntro, type SectionIntroProps, SegmentChip, type SegmentChipProps, Select, type SelectOption, type SelectProps, Separator, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SidebarNav, SidebarNavLink, type SidebarNavLinkProps, type SidebarNavProps, SidebarProductHeader, type SidebarProductHeaderProps, Skeleton, Slider, StatCard, type StatCardProps, StatusDot, type StatusDotProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, type Theme, ThemeToggle, Toaster, Toggle, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, type UseSidebarOptions, type UseSidebarResult, UserMenu, type UserMenuItem, type UserMenuProps, type UserMenuUser, alertVariants, badgeVariants, buttonVariants, cn, isKnownExtensionIcon, navigationMenuTriggerStyle, segmentChipVariants, statusDotVariants, toggleVariants, useCarousel, useFormField, useIsMobile, useSidebar, useTheme };
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import { Slot } from "@radix-ui/react-slot";
11
11
  import { cva } from "class-variance-authority";
12
12
  import { jsx } from "react/jsx-runtime";
13
13
  var buttonVariants = cva(
14
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus disabled:pointer-events-none disabled:opacity-50",
14
+ "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus disabled:pointer-events-none disabled:opacity-50",
15
15
  {
16
16
  variants: {
17
17
  variant: {
@@ -2618,8 +2618,9 @@ function initialsOf(name) {
2618
2618
  if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
2619
2619
  return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
2620
2620
  }
2621
- function UserMenu({ user, items, collapsed = false, align = "end" }) {
2622
- const avatar = /* @__PURE__ */ jsxs25(Avatar, { className: collapsed ? "size-9" : "size-8", children: [
2621
+ function UserMenu({ user, items, collapsed = false, align = "start" }) {
2622
+ const secondLine = user.subtitle ?? user.email;
2623
+ const avatar = /* @__PURE__ */ jsxs25(Avatar, { className: collapsed ? "size-9" : "size-7", children: [
2623
2624
  user.avatarUrl && /* @__PURE__ */ jsx52(AvatarImage, { src: user.avatarUrl, alt: user.name }),
2624
2625
  /* @__PURE__ */ jsx52(AvatarFallback, { className: "text-xs", children: initialsOf(user.name) })
2625
2626
  ] });
@@ -2635,14 +2636,14 @@ function UserMenu({ user, items, collapsed = false, align = "end" }) {
2635
2636
  "button",
2636
2637
  {
2637
2638
  type: "button",
2638
- className: "flex w-full items-center gap-2.5 rounded-xl px-2.5 py-1.5 text-left transition-colors hover:bg-[var(--hov-bg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
2639
+ className: "flex w-full items-center gap-2.5 rounded-xl px-1.5 py-[5px] text-left transition-colors hover:bg-[var(--hov-bg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
2639
2640
  children: [
2640
2641
  avatar,
2641
- /* @__PURE__ */ jsxs25("span", { className: "min-w-0 flex-1", children: [
2642
- /* @__PURE__ */ jsx52("span", { className: "block truncate text-sm font-medium text-text-primary", children: user.name }),
2643
- user.email && /* @__PURE__ */ jsx52("span", { className: "block truncate text-xs text-text-tertiary", children: user.email })
2642
+ /* @__PURE__ */ jsxs25("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
2643
+ /* @__PURE__ */ jsx52("span", { className: "truncate text-sm leading-[normal] font-medium text-text-primary", children: user.name }),
2644
+ secondLine && /* @__PURE__ */ jsx52("span", { className: "truncate text-xs leading-[normal] text-text-tertiary", children: secondLine })
2644
2645
  ] }),
2645
- /* @__PURE__ */ jsx52(ChevronsUpDown, { className: "size-4 shrink-0 text-icon-secondary", "aria-hidden": "true" })
2646
+ /* @__PURE__ */ jsx52(ChevronsUpDown, { size: 14, "aria-hidden": "true", className: "shrink-0 text-text-tertiary" })
2646
2647
  ]
2647
2648
  }
2648
2649
  );
@@ -2661,7 +2662,7 @@ function UserMenu({ user, items, collapsed = false, align = "end" }) {
2661
2662
  children: [
2662
2663
  /* @__PURE__ */ jsxs25(DropdownMenuLabel, { className: "px-2 pt-1.5 pb-1 text-xs font-medium text-text-tertiary", children: [
2663
2664
  /* @__PURE__ */ jsx52("span", { className: "block truncate text-sm font-medium text-text-primary", children: user.name }),
2664
- user.email && /* @__PURE__ */ jsx52("span", { className: "block truncate text-xs font-normal text-text-tertiary", children: user.email })
2665
+ secondLine && /* @__PURE__ */ jsx52("span", { className: "block truncate text-xs font-normal text-text-tertiary", children: secondLine })
2665
2666
  ] }),
2666
2667
  /* @__PURE__ */ jsx52(DropdownMenuSeparator, { className: "mx-0 shrink-0 bg-border-subtle" }),
2667
2668
  items.map((item, i) => {
@@ -2938,8 +2939,8 @@ function SidebarNav({
2938
2939
  "nav",
2939
2940
  {
2940
2941
  className: cn(
2941
- "flex flex-col gap-0.5",
2942
- collapsed ? "items-center px-1" : "px-3",
2942
+ "flex flex-col gap-2",
2943
+ collapsed && "items-center",
2943
2944
  className
2944
2945
  ),
2945
2946
  children: items.map((item) => {
@@ -2964,13 +2965,27 @@ function SidebarNav({
2964
2965
 
2965
2966
  // src/components/app-sidebar.tsx
2966
2967
  import { PanelLeft } from "lucide-react";
2967
- import { jsx as jsx59, jsxs as jsxs29 } from "react/jsx-runtime";
2968
+ import { Fragment as Fragment3, jsx as jsx59, jsxs as jsxs29 } from "react/jsx-runtime";
2969
+ function Divider({ collapsed, testId, className }) {
2970
+ return /* @__PURE__ */ jsx59(
2971
+ "div",
2972
+ {
2973
+ "data-testid": testId,
2974
+ className: cn(
2975
+ "h-px shrink-0 bg-border-subtle",
2976
+ collapsed ? "ml-[6px] w-[40px]" : "ml-[14px] w-[220px]",
2977
+ className
2978
+ )
2979
+ }
2980
+ );
2981
+ }
2968
2982
  function AppSidebar({
2969
2983
  items,
2970
2984
  logo,
2971
2985
  logoCollapsed,
2972
2986
  header,
2973
2987
  footer,
2988
+ collapsible = false,
2974
2989
  collapsed: controlled,
2975
2990
  defaultCollapsed,
2976
2991
  onCollapsedChange,
@@ -2984,18 +2999,49 @@ function AppSidebar({
2984
2999
  defaultCollapsed,
2985
3000
  onCollapsedChange
2986
3001
  });
3002
+ const pin = collapsible ? /* @__PURE__ */ jsx59(
3003
+ "button",
3004
+ {
3005
+ type: "button",
3006
+ onClick: toggle,
3007
+ "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
3008
+ "aria-expanded": !collapsed,
3009
+ className: "flex size-6 shrink-0 items-center justify-center rounded-md text-text-secondary transition-colors hover:bg-[var(--hov-bg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
3010
+ children: /* @__PURE__ */ jsx59(PanelLeft, { className: "size-4", "aria-hidden": "true" })
3011
+ }
3012
+ ) : null;
2987
3013
  return /* @__PURE__ */ jsxs29(
2988
3014
  "aside",
2989
3015
  {
2990
3016
  "data-collapsed": collapsed,
2991
3017
  className: cn(
2992
- "flex h-full flex-col overflow-hidden rounded-2xl border border-border-subtle bg-bg-surface transition-[width] duration-200 ease-in-out",
2993
- collapsed ? "w-[52px]" : "w-[220px]",
3018
+ "flex h-full shrink-0 flex-col overflow-hidden transition-[width] duration-200 ease-in-out",
3019
+ collapsed ? "w-[52px]" : "w-[248px]",
2994
3020
  className
2995
3021
  ),
2996
3022
  children: [
2997
- collapsed ? /* @__PURE__ */ jsx59("div", { className: "flex justify-center border-b border-border-subtle p-2", children: header ?? /* @__PURE__ */ jsxs29("div", { className: "group relative flex size-8 items-center justify-center", children: [
2998
- /* @__PURE__ */ jsx59("span", { className: "pointer-events-none transition-opacity group-hover:opacity-0 group-focus-within:opacity-0", children: logoCollapsed ?? logo }),
3023
+ /* @__PURE__ */ jsx59("div", { className: cn("flex items-center gap-2", collapsed ? "justify-center px-1 pt-2" : "px-3 pt-[13px]"), children: collapsed ? collapsible ? logoCollapsed ?? logo ? (
3024
+ /* The collapsed rail (52px) can't fit the logo mark beside the pin side by side —
3025
+ so the pin overlays the mark instead, revealed on hover/focus, same as pre-flat-rail. */
3026
+ /* @__PURE__ */ jsxs29("div", { className: "group relative flex size-8 items-center justify-center", children: [
3027
+ /* @__PURE__ */ jsx59("span", { className: "pointer-events-none transition-opacity group-hover:opacity-0 group-focus-within:opacity-0", children: logoCollapsed ?? logo }),
3028
+ /* @__PURE__ */ jsx59(
3029
+ "button",
3030
+ {
3031
+ type: "button",
3032
+ onClick: toggle,
3033
+ "aria-label": "Expand sidebar",
3034
+ "aria-expanded": false,
3035
+ className: "absolute inset-0 flex items-center justify-center rounded-md text-text-secondary opacity-0 transition-opacity hover:bg-[var(--hov-bg)] focus-visible:outline-none focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-border-focus group-hover:opacity-100 group-focus-within:opacity-100",
3036
+ children: /* @__PURE__ */ jsx59(PanelLeft, { className: "size-4", "aria-hidden": "true" })
3037
+ }
3038
+ )
3039
+ ] })
3040
+ ) : (
3041
+ /* No mark to hide behind (no `logoCollapsed`/`logo` given — `header` doesn't
3042
+ count, it's expanded-only). Hover-revealing the pin over nothing would leave
3043
+ an invisible 32px box as the only way back to expanded, so render it plain
3044
+ and fully visible instead. */
2999
3045
  /* @__PURE__ */ jsx59(
3000
3046
  "button",
3001
3047
  {
@@ -3003,80 +3049,107 @@ function AppSidebar({
3003
3049
  onClick: toggle,
3004
3050
  "aria-label": "Expand sidebar",
3005
3051
  "aria-expanded": false,
3006
- className: "absolute inset-0 flex items-center justify-center rounded-md text-text-secondary opacity-0 transition-opacity hover:bg-[var(--hov-bg)] focus-visible:outline-none focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-border-focus group-hover:opacity-100 group-focus-within:opacity-100",
3052
+ className: "flex size-8 shrink-0 items-center justify-center rounded-md text-text-secondary transition-colors hover:bg-[var(--hov-bg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
3007
3053
  children: /* @__PURE__ */ jsx59(PanelLeft, { className: "size-4", "aria-hidden": "true" })
3008
3054
  }
3009
3055
  )
3010
- ] }) }) : /* @__PURE__ */ jsxs29("div", { className: "flex items-center justify-between border-b border-border-subtle p-3", children: [
3056
+ ) : logoCollapsed ?? logo : /* @__PURE__ */ jsxs29(Fragment3, { children: [
3011
3057
  header ?? logo,
3012
- /* @__PURE__ */ jsx59(
3013
- "button",
3014
- {
3015
- type: "button",
3016
- onClick: toggle,
3017
- "aria-label": "Collapse sidebar",
3018
- "aria-expanded": true,
3019
- className: "flex size-6 items-center justify-center rounded-md text-text-secondary transition-colors hover:bg-[var(--hov-bg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
3020
- children: /* @__PURE__ */ jsx59(PanelLeft, { className: "size-4", "aria-hidden": "true" })
3021
- }
3022
- )
3023
- ] }),
3024
- /* @__PURE__ */ jsx59("div", { className: "flex-1 overflow-y-auto overflow-x-hidden pt-3 pb-2", children: /* @__PURE__ */ jsx59(SidebarNav, { items, collapsed, basePath, LinkComponent, isItemActive }) }),
3025
- footer && /* @__PURE__ */ jsx59("div", { className: cn("border-t border-border-subtle", collapsed ? "px-1 py-2" : "px-3 py-3"), children: footer })
3058
+ pin
3059
+ ] }) }),
3060
+ /* @__PURE__ */ jsx59(Divider, { collapsed, testId: "sidebar-divider-header", className: "mt-3" }),
3061
+ /* @__PURE__ */ jsx59(
3062
+ "div",
3063
+ {
3064
+ "data-testid": "sidebar-scroll",
3065
+ className: cn(
3066
+ "min-h-0 flex-1 overflow-y-auto overflow-x-hidden pt-3 pb-2",
3067
+ collapsed ? "px-1" : "px-3"
3068
+ ),
3069
+ children: /* @__PURE__ */ jsx59(SidebarNav, { items, collapsed, basePath, LinkComponent, isItemActive })
3070
+ }
3071
+ ),
3072
+ footer && /* @__PURE__ */ jsxs29(Fragment3, { children: [
3073
+ /* @__PURE__ */ jsx59(Divider, { collapsed, testId: "sidebar-divider-account" }),
3074
+ /* @__PURE__ */ jsx59("div", { className: cn("py-2", collapsed ? "px-1" : "px-3"), children: footer })
3075
+ ] })
3026
3076
  ]
3027
3077
  }
3028
3078
  );
3029
3079
  }
3030
3080
 
3031
- // src/components/app-shell.tsx
3081
+ // src/components/sidebar-product-header.tsx
3032
3082
  import * as React50 from "react";
3083
+ import { Slot as Slot5 } from "@radix-ui/react-slot";
3084
+ import { ChevronsUpDown as ChevronsUpDown2 } from "lucide-react";
3085
+ import { Fragment as Fragment4, jsx as jsx60, jsxs as jsxs30 } from "react/jsx-runtime";
3086
+ var SidebarProductHeader = React50.forwardRef(
3087
+ function SidebarProductHeader2({ name, icon, trailing, asChild = false, className, children, ...props }, ref) {
3088
+ const adornment = trailing === void 0 ? /* @__PURE__ */ jsx60(ChevronsUpDown2, { size: 14, "aria-hidden": "true", className: "shrink-0 text-text-tertiary" }) : trailing;
3089
+ const content = /* @__PURE__ */ jsxs30(Fragment4, { children: [
3090
+ icon && /* @__PURE__ */ jsx60("span", { className: "flex size-7 shrink-0 items-center justify-center overflow-hidden rounded-md", "aria-hidden": "true", children: icon }),
3091
+ /* @__PURE__ */ jsx60("span", { className: "min-w-0 flex-1 truncate text-sm font-semibold text-text-primary", children: name }),
3092
+ adornment && /* @__PURE__ */ jsx60("span", { className: "flex shrink-0 items-center", children: adornment })
3093
+ ] });
3094
+ const classes = cn("flex w-full items-center gap-2.5 rounded-xl px-1.5 py-0 text-left", className);
3095
+ if (asChild) {
3096
+ const child = React50.Children.only(children);
3097
+ return /* @__PURE__ */ jsx60(Slot5, { ref, className: classes, ...props, children: React50.cloneElement(child, void 0, content) });
3098
+ }
3099
+ return /* @__PURE__ */ jsx60("button", { ref, type: "button", className: classes, ...props, children: content });
3100
+ }
3101
+ );
3102
+ SidebarProductHeader.displayName = "SidebarProductHeader";
3103
+
3104
+ // src/components/app-shell.tsx
3105
+ import * as React51 from "react";
3033
3106
  import { Menu as Menu2 } from "lucide-react";
3034
3107
  import { useLocation as useLocation2 } from "react-router";
3035
- import { jsx as jsx60, jsxs as jsxs30 } from "react/jsx-runtime";
3108
+ import { jsx as jsx61, jsxs as jsxs31 } from "react/jsx-runtime";
3036
3109
  function expandedSidebar(sidebar) {
3037
- return React50.isValidElement(sidebar) ? React50.cloneElement(sidebar, { collapsed: false }) : sidebar;
3110
+ return React51.isValidElement(sidebar) ? React51.cloneElement(sidebar, { collapsed: false }) : sidebar;
3038
3111
  }
3039
3112
  function AppShell({ sidebar, topBar, children, className }) {
3040
3113
  const { pathname } = useLocation2();
3041
- const [mobileOpen, setMobileOpen] = React50.useState(false);
3042
- React50.useEffect(() => {
3114
+ const [mobileOpen, setMobileOpen] = React51.useState(false);
3115
+ React51.useEffect(() => {
3043
3116
  setMobileOpen(false);
3044
3117
  }, [pathname]);
3045
3118
  const isMobile = useIsMobile();
3046
3119
  if (isMobile) {
3047
- return /* @__PURE__ */ jsx60(TooltipProvider, { children: /* @__PURE__ */ jsxs30("div", { className: cn("flex min-h-screen flex-col bg-bg-sunken", className), children: [
3048
- /* @__PURE__ */ jsxs30("header", { className: "flex shrink-0 items-center justify-between border-b border-border-subtle bg-bg-surface px-4 py-3", children: [
3049
- /* @__PURE__ */ jsxs30(Sheet, { open: mobileOpen, onOpenChange: setMobileOpen, children: [
3050
- /* @__PURE__ */ jsx60(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx60(Button, { variant: "ghost", size: "icon", className: "size-8 text-text-secondary", "aria-label": "Open navigation", children: /* @__PURE__ */ jsx60(Menu2, { className: "size-5" }) }) }),
3051
- /* @__PURE__ */ jsxs30(SheetContent, { side: "left", className: "w-[240px] bg-bg-surface p-0", children: [
3052
- /* @__PURE__ */ jsx60(SheetTitle, { className: "sr-only", children: "Navigation" }),
3053
- /* @__PURE__ */ jsx60(SheetDescription, { className: "sr-only", children: "Application navigation menu" }),
3120
+ return /* @__PURE__ */ jsx61(TooltipProvider, { children: /* @__PURE__ */ jsxs31("div", { className: cn("flex min-h-screen flex-col bg-bg-surface", className), children: [
3121
+ /* @__PURE__ */ jsxs31("header", { className: "flex shrink-0 items-center justify-between border-b border-border-subtle bg-bg-surface px-4 py-3", children: [
3122
+ /* @__PURE__ */ jsxs31(Sheet, { open: mobileOpen, onOpenChange: setMobileOpen, children: [
3123
+ /* @__PURE__ */ jsx61(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx61(Button, { variant: "ghost", size: "icon", className: "size-8 text-text-secondary", "aria-label": "Open navigation", children: /* @__PURE__ */ jsx61(Menu2, { className: "size-5" }) }) }),
3124
+ /* @__PURE__ */ jsxs31(SheetContent, { side: "left", className: "w-[248px] bg-bg-surface p-0", children: [
3125
+ /* @__PURE__ */ jsx61(SheetTitle, { className: "sr-only", children: "Navigation" }),
3126
+ /* @__PURE__ */ jsx61(SheetDescription, { className: "sr-only", children: "Application navigation menu" }),
3054
3127
  expandedSidebar(sidebar)
3055
3128
  ] })
3056
3129
  ] }),
3057
- /* @__PURE__ */ jsx60("div", { className: "flex items-center gap-2", children: topBar })
3130
+ /* @__PURE__ */ jsx61("div", { className: "flex items-center gap-2", children: topBar })
3058
3131
  ] }),
3059
- /* @__PURE__ */ jsx60("main", { className: "flex-1 overflow-auto p-4", children })
3132
+ /* @__PURE__ */ jsx61("main", { className: "flex-1 overflow-auto", children })
3060
3133
  ] }) });
3061
3134
  }
3062
- return /* @__PURE__ */ jsx60(TooltipProvider, { children: /* @__PURE__ */ jsxs30("div", { className: cn("flex min-h-screen gap-2 bg-bg-sunken p-4", className), children: [
3063
- /* @__PURE__ */ jsx60("div", { className: "sticky top-4 h-[calc(100vh-32px)] shrink-0", children: sidebar }),
3064
- /* @__PURE__ */ jsxs30("div", { className: "flex min-w-0 flex-1 flex-col overflow-hidden rounded-2xl border border-border-subtle bg-bg-surface", children: [
3065
- /* @__PURE__ */ jsx60("div", { className: "flex min-h-[52px] shrink-0 items-center justify-end gap-2 border-b border-border-subtle px-5", children: topBar }),
3066
- /* @__PURE__ */ jsx60("main", { className: "flex-1 overflow-auto px-7 py-8", children })
3135
+ return /* @__PURE__ */ jsx61(TooltipProvider, { children: /* @__PURE__ */ jsxs31("div", { className: cn("flex min-h-screen gap-3 bg-bg-surface p-4", className), children: [
3136
+ /* @__PURE__ */ jsx61("div", { className: "sticky top-4 h-[calc(100vh-32px)] shrink-0", children: sidebar }),
3137
+ /* @__PURE__ */ jsxs31("div", { className: "flex min-w-0 flex-1 flex-col overflow-hidden", children: [
3138
+ topBar && /* @__PURE__ */ jsx61("div", { className: "flex shrink-0 items-center justify-end gap-2", children: topBar }),
3139
+ /* @__PURE__ */ jsx61("main", { className: "flex-1 overflow-y-auto", children })
3067
3140
  ] })
3068
3141
  ] }) });
3069
3142
  }
3070
3143
  var AuthenticatedLayout = AppShell;
3071
3144
 
3072
3145
  // src/components/grid.tsx
3073
- import * as React51 from "react";
3074
- import { Slot as Slot5 } from "@radix-ui/react-slot";
3075
- import { jsx as jsx61 } from "react/jsx-runtime";
3076
- var Grid = React51.forwardRef(
3146
+ import * as React52 from "react";
3147
+ import { Slot as Slot6 } from "@radix-ui/react-slot";
3148
+ import { jsx as jsx62 } from "react/jsx-runtime";
3149
+ var Grid = React52.forwardRef(
3077
3150
  ({ fluid = false, asChild = false, className, ...props }, ref) => {
3078
- const Comp = asChild ? Slot5 : "div";
3079
- return /* @__PURE__ */ jsx61(
3151
+ const Comp = asChild ? Slot6 : "div";
3152
+ return /* @__PURE__ */ jsx62(
3080
3153
  Comp,
3081
3154
  {
3082
3155
  ref,
@@ -3094,9 +3167,9 @@ var Grid = React51.forwardRef(
3094
3167
  Grid.displayName = "Grid";
3095
3168
 
3096
3169
  // src/components/col.tsx
3097
- import * as React52 from "react";
3098
- import { Slot as Slot6 } from "@radix-ui/react-slot";
3099
- import { jsx as jsx62 } from "react/jsx-runtime";
3170
+ import * as React53 from "react";
3171
+ import { Slot as Slot7 } from "@radix-ui/react-slot";
3172
+ import { jsx as jsx63 } from "react/jsx-runtime";
3100
3173
  var TIER_MAX = { base: 4, md: 8, xl: 12 };
3101
3174
  var SPAN = {
3102
3175
  base: { 1: "col-span-1", 2: "col-span-2", 3: "col-span-3", 4: "col-span-4" },
@@ -3216,22 +3289,22 @@ function responsiveClasses(span, offset) {
3216
3289
  }
3217
3290
  return cn(...spanClasses, ...startClasses);
3218
3291
  }
3219
- var Col = React52.forwardRef(
3292
+ var Col = React53.forwardRef(
3220
3293
  ({ span, offset, asChild = false, className, ...props }, ref) => {
3221
- const Comp = asChild ? Slot6 : "div";
3222
- return /* @__PURE__ */ jsx62(Comp, { ref, className: cn(responsiveClasses(span, offset), className), ...props });
3294
+ const Comp = asChild ? Slot7 : "div";
3295
+ return /* @__PURE__ */ jsx63(Comp, { ref, className: cn(responsiveClasses(span, offset), className), ...props });
3223
3296
  }
3224
3297
  );
3225
3298
  Col.displayName = "Col";
3226
3299
 
3227
3300
  // src/components/grid-overlay.tsx
3228
- import * as React53 from "react";
3229
- import { jsx as jsx63 } from "react/jsx-runtime";
3301
+ import * as React54 from "react";
3302
+ import { jsx as jsx64 } from "react/jsx-runtime";
3230
3303
  var BASELINE = "var(--grid-gutter) / 2";
3231
- var GridOverlay = React53.forwardRef(
3304
+ var GridOverlay = React54.forwardRef(
3232
3305
  ({ columns, baseline = false, className, style, ...props }, ref) => {
3233
3306
  const count = Number.isFinite(columns) ? Math.max(1, Math.floor(columns)) : 1;
3234
- return /* @__PURE__ */ jsx63(
3307
+ return /* @__PURE__ */ jsx64(
3235
3308
  "div",
3236
3309
  {
3237
3310
  ref,
@@ -3248,7 +3321,7 @@ var GridOverlay = React53.forwardRef(
3248
3321
  ...style
3249
3322
  },
3250
3323
  ...props,
3251
- children: Array.from({ length: count }).map((_, i) => /* @__PURE__ */ jsx63("span", { style: { backgroundColor: "var(--bg-brand)", opacity: 0.1 } }, i))
3324
+ children: Array.from({ length: count }).map((_, i) => /* @__PURE__ */ jsx64("span", { style: { backgroundColor: "var(--bg-brand)", opacity: 0.1 } }, i))
3252
3325
  }
3253
3326
  );
3254
3327
  }
@@ -3256,10 +3329,10 @@ var GridOverlay = React53.forwardRef(
3256
3329
  GridOverlay.displayName = "GridOverlay";
3257
3330
 
3258
3331
  // src/components/segment-chip.tsx
3259
- import * as React54 from "react";
3332
+ import * as React55 from "react";
3260
3333
  import { cva as cva9 } from "class-variance-authority";
3261
3334
  import { Link2 as Link22, KeyRound, Clock, Plus } from "lucide-react";
3262
- import { jsx as jsx64, jsxs as jsxs31 } from "react/jsx-runtime";
3335
+ import { jsx as jsx65, jsxs as jsxs32 } from "react/jsx-runtime";
3263
3336
  var segmentChipVariants = cva9(
3264
3337
  "inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1 text-sm font-medium leading-tight",
3265
3338
  {
@@ -3280,11 +3353,11 @@ var kindIcons = {
3280
3353
  pending: Clock,
3281
3354
  available: Plus
3282
3355
  };
3283
- var SegmentChip = React54.forwardRef(
3356
+ var SegmentChip = React55.forwardRef(
3284
3357
  ({ kind, resource, icon, hideIcon = false, className, ...props }, ref) => {
3285
3358
  const Icon = icon ?? kindIcons[kind ?? "granted"];
3286
- return /* @__PURE__ */ jsxs31("span", { ref, className: cn(segmentChipVariants({ kind }), className), ...props, children: [
3287
- !hideIcon && /* @__PURE__ */ jsx64(Icon, { className: "size-3.5 shrink-0 opacity-90", "aria-hidden": "true" }),
3359
+ return /* @__PURE__ */ jsxs32("span", { ref, className: cn(segmentChipVariants({ kind }), className), ...props, children: [
3360
+ !hideIcon && /* @__PURE__ */ jsx65(Icon, { className: "size-3.5 shrink-0 opacity-90", "aria-hidden": "true" }),
3288
3361
  resource
3289
3362
  ] });
3290
3363
  }
@@ -3292,9 +3365,9 @@ var SegmentChip = React54.forwardRef(
3292
3365
  SegmentChip.displayName = "SegmentChip";
3293
3366
 
3294
3367
  // src/components/connection-card.tsx
3295
- import * as React55 from "react";
3296
- import { AlertTriangle } from "lucide-react";
3297
- import { Fragment as Fragment3, jsx as jsx65, jsxs as jsxs32 } from "react/jsx-runtime";
3368
+ import * as React56 from "react";
3369
+ import { AlertTriangle, ChevronDown as ChevronDown4 } from "lucide-react";
3370
+ import { Fragment as Fragment5, jsx as jsx66, jsxs as jsxs33 } from "react/jsx-runtime";
3298
3371
  var authLabels = { app: "App", oauth: "OAuth", token: "token" };
3299
3372
  var statusMeta = {
3300
3373
  connected: { label: "Connected", badge: "success", dot: "success" },
@@ -3304,85 +3377,155 @@ var statusMeta = {
3304
3377
  function monogram(name) {
3305
3378
  return name.replace(/[^a-zA-Z0-9 ]/g, " ").split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
3306
3379
  }
3307
- var ConnectionCard = React55.forwardRef(
3308
- ({ name, scope, auth, status, glyph, statusLabel, grantedCount, action, access, children, className, ...props }, ref) => {
3380
+ var ConnectionCard = React56.forwardRef(
3381
+ ({
3382
+ name,
3383
+ scope,
3384
+ auth,
3385
+ status,
3386
+ glyph,
3387
+ statusLabel,
3388
+ grantedCount,
3389
+ action,
3390
+ access,
3391
+ children,
3392
+ collapsible = false,
3393
+ defaultOpen,
3394
+ open,
3395
+ onOpenChange,
3396
+ className,
3397
+ ...props
3398
+ }, ref) => {
3309
3399
  const meta = statusMeta[status];
3310
3400
  const connected = status !== "disconnected";
3311
- return /* @__PURE__ */ jsxs32(Card, { ref, className: cn(status === "disconnected" && "border-dashed border-border-default", className), ...props, children: [
3312
- /* @__PURE__ */ jsxs32("div", { className: "flex items-center gap-3 p-4", children: [
3313
- /* @__PURE__ */ jsx65(
3314
- "div",
3315
- {
3316
- "aria-hidden": "true",
3317
- className: "flex size-8 shrink-0 items-center justify-center rounded-lg border border-border-subtle bg-bg-elevated text-sm font-bold text-text-secondary",
3318
- children: glyph ?? monogram(name)
3319
- }
3320
- ),
3321
- /* @__PURE__ */ jsxs32("div", { className: "min-w-0", children: [
3322
- /* @__PURE__ */ jsx65("p", { className: "truncate text-sm font-semibold text-text-primary", children: name }),
3323
- /* @__PURE__ */ jsxs32("div", { className: "mt-0.5 flex flex-wrap gap-1", children: [
3324
- /* @__PURE__ */ jsx65(Badge, { variant: "secondary", children: scope }),
3325
- /* @__PURE__ */ jsx65(Badge, { variant: "secondary", children: authLabels[auth] })
3326
- ] })
3327
- ] }),
3328
- /* @__PURE__ */ jsx65("div", { className: "ml-auto shrink-0", children: connected ? /* @__PURE__ */ jsxs32(Badge, { variant: meta.badge, className: "gap-1.5", children: [
3329
- /* @__PURE__ */ jsx65(StatusDot, { status: meta.dot, size: "sm" }),
3330
- statusLabel ?? meta.label
3331
- ] }) : action })
3332
- ] }),
3333
- access && /* @__PURE__ */ jsxs32(
3334
- "div",
3401
+ const glyphBox = /* @__PURE__ */ jsx66(
3402
+ "div",
3403
+ {
3404
+ "aria-hidden": "true",
3405
+ className: "flex size-8 shrink-0 items-center justify-center rounded-lg border border-border-subtle bg-bg-elevated text-sm font-bold text-text-secondary",
3406
+ children: glyph ?? monogram(name)
3407
+ }
3408
+ );
3409
+ const identity = /* @__PURE__ */ jsxs33("div", { className: "min-w-0", children: [
3410
+ /* @__PURE__ */ jsx66("p", { className: "truncate text-sm font-semibold text-text-primary", children: name }),
3411
+ /* @__PURE__ */ jsxs33("div", { className: "mt-0.5 flex flex-wrap gap-1", children: [
3412
+ /* @__PURE__ */ jsx66(Badge, { variant: "secondary", children: scope }),
3413
+ /* @__PURE__ */ jsx66(Badge, { variant: "secondary", children: authLabels[auth] })
3414
+ ] })
3415
+ ] });
3416
+ const statusBadge = /* @__PURE__ */ jsxs33(Badge, { variant: meta.badge, className: "gap-1.5", children: [
3417
+ /* @__PURE__ */ jsx66(StatusDot, { status: meta.dot, size: "sm" }),
3418
+ statusLabel ?? meta.label
3419
+ ] });
3420
+ const accessNote = access ? /* @__PURE__ */ jsxs33(
3421
+ "div",
3422
+ {
3423
+ role: "note",
3424
+ className: "flex items-start gap-2 border-t border-border-subtle bg-bg-warning-subtle px-4 py-2.5 text-xs font-medium text-text-warning",
3425
+ children: [
3426
+ /* @__PURE__ */ jsx66(AlertTriangle, { className: "mt-0.5 size-3.5 shrink-0", "aria-hidden": "true" }),
3427
+ /* @__PURE__ */ jsx66("span", { children: access })
3428
+ ]
3429
+ }
3430
+ ) : null;
3431
+ const footerAction = collapsible ? action : connected && action;
3432
+ const footer = grantedCount !== void 0 || footerAction ? /* @__PURE__ */ jsxs33("div", { className: "flex items-center justify-between border-t border-border-subtle px-4 py-3", children: [
3433
+ /* @__PURE__ */ jsx66("span", { className: "text-sm text-text-secondary", children: grantedCount !== void 0 ? /* @__PURE__ */ jsxs33(Fragment5, { children: [
3434
+ "Granted to ",
3435
+ /* @__PURE__ */ jsx66("span", { className: "font-semibold text-text-primary", children: grantedCount }),
3436
+ " ",
3437
+ grantedCount === 1 ? "product" : "products"
3438
+ ] }) : /* @__PURE__ */ jsx66("span", { className: "text-text-tertiary", children: "Not granted to any product yet" }) }),
3439
+ footerAction
3440
+ ] }) : null;
3441
+ const drawer = children ? /* @__PURE__ */ jsx66("div", { "data-slot": "drawer", className: "border-t border-border-subtle", children }) : null;
3442
+ if (!collapsible) {
3443
+ return /* @__PURE__ */ jsxs33(
3444
+ Card,
3335
3445
  {
3336
- role: "note",
3337
- className: "flex items-start gap-2 border-t border-border-subtle bg-bg-warning-subtle px-4 py-2.5 text-xs font-medium text-text-warning",
3446
+ ref,
3447
+ className: cn(status === "disconnected" && "border-dashed border-border-default", className),
3448
+ ...props,
3338
3449
  children: [
3339
- /* @__PURE__ */ jsx65(AlertTriangle, { className: "mt-0.5 size-3.5 shrink-0", "aria-hidden": "true" }),
3340
- /* @__PURE__ */ jsx65("span", { children: access })
3450
+ /* @__PURE__ */ jsxs33("div", { className: "flex items-center gap-3 p-4", children: [
3451
+ glyphBox,
3452
+ identity,
3453
+ /* @__PURE__ */ jsx66("div", { className: "ml-auto shrink-0", children: connected ? statusBadge : action })
3454
+ ] }),
3455
+ accessNote,
3456
+ footer,
3457
+ drawer
3341
3458
  ]
3342
3459
  }
3343
- ),
3344
- (grantedCount !== void 0 || connected && action) && /* @__PURE__ */ jsxs32("div", { className: "flex items-center justify-between border-t border-border-subtle px-4 py-3", children: [
3345
- /* @__PURE__ */ jsx65("span", { className: "text-sm text-text-secondary", children: grantedCount !== void 0 ? /* @__PURE__ */ jsxs32(Fragment3, { children: [
3346
- "Granted to ",
3347
- /* @__PURE__ */ jsx65("span", { className: "font-semibold text-text-primary", children: grantedCount }),
3348
- " ",
3349
- grantedCount === 1 ? "product" : "products"
3350
- ] }) : /* @__PURE__ */ jsx65("span", { className: "text-text-tertiary", children: "Not granted to any product yet" }) }),
3351
- connected && action
3352
- ] }),
3353
- children && /* @__PURE__ */ jsx65("div", { "data-slot": "drawer", className: "border-t border-border-subtle", children })
3354
- ] });
3460
+ );
3461
+ }
3462
+ return /* @__PURE__ */ jsx66(Collapsible, { defaultOpen, open, onOpenChange, children: /* @__PURE__ */ jsxs33(
3463
+ Card,
3464
+ {
3465
+ ref,
3466
+ className: cn(status === "disconnected" && "border-dashed border-border-default", className),
3467
+ ...props,
3468
+ children: [
3469
+ /* @__PURE__ */ jsx66(CollapsibleTrigger2, { asChild: true, children: /* @__PURE__ */ jsxs33(
3470
+ "button",
3471
+ {
3472
+ type: "button",
3473
+ className: "group flex w-full cursor-pointer items-center gap-3 rounded-[inherit] p-4 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
3474
+ children: [
3475
+ glyphBox,
3476
+ identity,
3477
+ /* @__PURE__ */ jsxs33("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: [
3478
+ statusBadge,
3479
+ /* @__PURE__ */ jsx66(
3480
+ ChevronDown4,
3481
+ {
3482
+ "aria-hidden": "true",
3483
+ className: "size-4 shrink-0 text-text-tertiary transition-transform group-data-[state=open]:rotate-180"
3484
+ }
3485
+ )
3486
+ ] })
3487
+ ]
3488
+ }
3489
+ ) }),
3490
+ accessNote,
3491
+ /* @__PURE__ */ jsxs33(CollapsibleContent2, { children: [
3492
+ footer,
3493
+ drawer
3494
+ ] })
3495
+ ]
3496
+ }
3497
+ ) });
3355
3498
  }
3356
3499
  );
3357
3500
  ConnectionCard.displayName = "ConnectionCard";
3358
3501
 
3359
3502
  // src/components/request-row.tsx
3360
- import * as React56 from "react";
3361
- import { Fragment as Fragment4, jsx as jsx66, jsxs as jsxs33 } from "react/jsx-runtime";
3503
+ import * as React57 from "react";
3504
+ import { Fragment as Fragment6, jsx as jsx67, jsxs as jsxs34 } from "react/jsx-runtime";
3362
3505
  function monogram2(name) {
3363
3506
  return name.replace(/[^a-zA-Z0-9 ]/g, " ").split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
3364
3507
  }
3365
- var RequestRow = React56.forwardRef(
3508
+ var RequestRow = React57.forwardRef(
3366
3509
  ({ surface, name, state: state2, glyph, subtitle, grantedResource, onApprove, onDeny, onRequest, action, className, ...props }, ref) => {
3367
3510
  let controls = action;
3368
3511
  if (controls === void 0) {
3369
3512
  if (surface === "org" && state2 === "pending") {
3370
- controls = /* @__PURE__ */ jsxs33(Fragment4, { children: [
3371
- /* @__PURE__ */ jsx66(Button, { variant: "outline", size: "sm", onClick: onDeny, children: "Deny" }),
3372
- /* @__PURE__ */ jsx66(Button, { size: "sm", onClick: onApprove, children: "Approve" })
3513
+ controls = /* @__PURE__ */ jsxs34(Fragment6, { children: [
3514
+ /* @__PURE__ */ jsx67(Button, { variant: "outline", size: "sm", onClick: onDeny, children: "Deny" }),
3515
+ /* @__PURE__ */ jsx67(Button, { size: "sm", onClick: onApprove, children: "Approve" })
3373
3516
  ] });
3374
3517
  } else if (state2 === "available") {
3375
- controls = /* @__PURE__ */ jsx66(Button, { size: "sm", onClick: onRequest, children: "Request" });
3518
+ controls = /* @__PURE__ */ jsx67(Button, { size: "sm", onClick: onRequest, children: "Request" });
3376
3519
  } else if (state2 === "pending") {
3377
- controls = /* @__PURE__ */ jsxs33(Badge, { variant: "secondary", className: "gap-1.5", children: [
3378
- /* @__PURE__ */ jsx66(StatusDot, { status: "neutral", size: "sm" }),
3520
+ controls = /* @__PURE__ */ jsxs34(Badge, { variant: "secondary", className: "gap-1.5", children: [
3521
+ /* @__PURE__ */ jsx67(StatusDot, { status: "neutral", size: "sm" }),
3379
3522
  "Pending"
3380
3523
  ] });
3381
3524
  } else if (state2 === "granted") {
3382
- controls = /* @__PURE__ */ jsx66(SegmentChip, { kind: "granted", resource: grantedResource ?? "a segment" });
3525
+ controls = /* @__PURE__ */ jsx67(SegmentChip, { kind: "granted", resource: grantedResource ?? "a segment" });
3383
3526
  }
3384
3527
  }
3385
- return /* @__PURE__ */ jsxs33(
3528
+ return /* @__PURE__ */ jsxs34(
3386
3529
  "div",
3387
3530
  {
3388
3531
  ref,
@@ -3392,7 +3535,7 @@ var RequestRow = React56.forwardRef(
3392
3535
  ),
3393
3536
  ...props,
3394
3537
  children: [
3395
- /* @__PURE__ */ jsx66(
3538
+ /* @__PURE__ */ jsx67(
3396
3539
  "div",
3397
3540
  {
3398
3541
  "aria-hidden": "true",
@@ -3400,11 +3543,11 @@ var RequestRow = React56.forwardRef(
3400
3543
  children: glyph ?? monogram2(name)
3401
3544
  }
3402
3545
  ),
3403
- /* @__PURE__ */ jsxs33("div", { className: "min-w-0", children: [
3404
- /* @__PURE__ */ jsx66("p", { className: "truncate text-sm font-semibold text-text-primary", children: name }),
3405
- subtitle && /* @__PURE__ */ jsx66("p", { className: "truncate text-xs text-text-tertiary", children: subtitle })
3546
+ /* @__PURE__ */ jsxs34("div", { className: "min-w-0", children: [
3547
+ /* @__PURE__ */ jsx67("p", { className: "truncate text-sm font-semibold text-text-primary", children: name }),
3548
+ subtitle && /* @__PURE__ */ jsx67("p", { className: "truncate text-xs text-text-tertiary", children: subtitle })
3406
3549
  ] }),
3407
- /* @__PURE__ */ jsx66("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: controls })
3550
+ /* @__PURE__ */ jsx67("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: controls })
3408
3551
  ]
3409
3552
  }
3410
3553
  );
@@ -3610,6 +3753,7 @@ export {
3610
3753
  SheetTrigger,
3611
3754
  SidebarNav,
3612
3755
  SidebarNavLink,
3756
+ SidebarProductHeader,
3613
3757
  Skeleton,
3614
3758
  Slider,
3615
3759
  StatCard,
package/dist/styles.css CHANGED
@@ -11,13 +11,13 @@
11
11
  */
12
12
 
13
13
  :root {
14
- --bg-primary: #0e1015;
14
+ --bg-primary: #191b24;
15
15
  --bg-secondary: #12141b;
16
- --bg-surface: #191b24;
17
- --bg-elevated: #1f222d;
18
- --bg-sunken: #08090c;
16
+ --bg-surface: #1f222d;
17
+ --bg-elevated: #252936;
18
+ --bg-sunken: #0e1015;
19
19
  --bg-brand: #06297d;
20
- --bg-brand-subtle: #0e204c;
20
+ --bg-brand-subtle: #1f2541;
21
21
  --bg-brand-hover: #08349b;
22
22
  --bg-brand-bright: #165ff2;
23
23
  --bg-brand-bright-hover: #3d7af4;
@@ -33,7 +33,7 @@
33
33
  --bg-rose-subtle: #1a090b;
34
34
  --text-primary: #eeeff0;
35
35
  --text-secondary: #a5a9b5;
36
- --text-tertiary: #8a91a7;
36
+ --text-tertiary: #8a93a8;
37
37
  --text-disabled: #60687f;
38
38
  --text-inverse: #0e1015;
39
39
  --text-on-brand: #f6f7f7;
@@ -57,7 +57,7 @@
57
57
  --icon-success: #2eda76;
58
58
  --icon-warning: #efb042;
59
59
  --icon-danger: #f06a6a;
60
- --border-subtle: #30343f;
60
+ --border-subtle: #2a2e3b;
61
61
  --border-default: #60687f;
62
62
  --border-strong: #737c95;
63
63
  --border-brand: #165ff2;
@@ -69,7 +69,7 @@
69
69
  --field-bg-hover: #12141b;
70
70
  --field-bg-focus: #0e1015;
71
71
  --field-bg-valid: #191b24;
72
- --field-border: #30343f;
72
+ --field-border: #2a2e3b;
73
73
  --field-border-hover: #393d4b;
74
74
  --field-border-focus: #165ff2;
75
75
  --field-border-filled: #393d4b;
@@ -271,12 +271,12 @@
271
271
  }
272
272
 
273
273
  .light {
274
- --bg-primary: #eeeff0;
275
- --bg-secondary: #e1e2e4;
276
- --bg-surface: #f6f7f7;
277
- --bg-elevated: #ffffff;
278
- --bg-sunken: #cfd1d7;
279
- --bg-brand-subtle: #eaeef4;
274
+ --bg-primary: #f6f7f7;
275
+ --bg-secondary: #eeeff0;
276
+ --bg-surface: #f2f3f4;
277
+ --bg-elevated: #edeef0;
278
+ --bg-sunken: #e1e2e4;
279
+ --bg-brand-subtle: #e4ecfe;
280
280
  --bg-brand-hover: #05226a;
281
281
  --bg-brand-bright-hover: #0b4cd5;
282
282
  --bg-success-subtle: #ebf3ef;
@@ -291,7 +291,7 @@
291
291
  --text-tertiary: #535a6e;
292
292
  --text-disabled: #737c95;
293
293
  --text-inverse: #f6f7f7;
294
- --text-brand: #0940b8;
294
+ --text-brand: #0b4cd5;
295
295
  --text-success: #13703c;
296
296
  --text-warning: #7a5409;
297
297
  --text-danger: #94120e;
@@ -303,7 +303,7 @@
303
303
  --icon-primary: #252936;
304
304
  --icon-secondary: #535a6e;
305
305
  --icon-disabled: #8a91a7;
306
- --icon-brand: #0940b8;
306
+ --icon-brand: #165ff2;
307
307
  --icon-success: #13703c;
308
308
  --icon-warning: #7a5409;
309
309
  --icon-danger: #94120e;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessly/ui",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Lessly design system — shared UI primitives, tokens, and theme",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.30.3",
@@ -57,7 +57,7 @@
57
57
  }
58
58
  },
59
59
  "dependencies": {
60
- "@lessly/tokens": "^0.1.0",
60
+ "@lessly/tokens": "^0.2.0",
61
61
  "@radix-ui/react-accordion": "^1.2.16",
62
62
  "@radix-ui/react-alert-dialog": "^1.1.19",
63
63
  "@radix-ui/react-aspect-ratio": "^1.1.11",