@lessly/ui 0.2.0 → 0.3.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/README.md CHANGED
@@ -71,10 +71,11 @@ npm package published to npmjs always contains the package, never the Storybook
71
71
  | `pnpm test` | Vitest |
72
72
  | `pnpm type-check` | `tsc --noEmit` |
73
73
  | `pnpm lint` | ESLint |
74
- | `pnpm changeset` / `pnpm release:version` | Versioning (describe change → apply bump). To release, push a `v<version>` git tag — see [`RELEASING.md`](./RELEASING.md); do **not** use `changeset tag`, whose `name@version` tags do not match the workflow's `v*` trigger. |
74
+ | `pnpm changeset` / `pnpm release:version` | Versioning via Changesets (describe change → apply bump + changelog) — see [`RELEASING.md`](./RELEASING.md) |
75
+ | `pnpm release:publish` | CI-only publish wrapper (publish to npmjs if needed + `changeset tag`) — used by `.github/workflows/version.yml` |
75
76
 
76
77
  ## Releasing
77
78
 
78
- Versioning is via Changesets; publishing to the public npm registry is
79
- automated by a tag-triggered GitHub Actions workflow
80
- (`.github/workflows/release.yml`). See [`RELEASING.md`](./RELEASING.md).
79
+ Add a changeset (`pnpm changeset`) to each significant PR; merging the
80
+ auto-generated "Release: version packages" PR publishes to the public npm
81
+ registry and tags `v<version>`. See [`RELEASING.md`](./RELEASING.md).
package/dist/index.d.ts CHANGED
@@ -730,6 +730,67 @@ interface UseSidebarResult {
730
730
  */
731
731
  declare function useSidebar(options?: UseSidebarOptions): UseSidebarResult;
732
732
 
733
+ type NavRowVariant = 'rail' | 'menu';
734
+ interface NavRowOwnProps {
735
+ /** Leading glyph. Rendered in a fixed 20px box; SVGs are normalized to 18px.
736
+ * A falsy value (`null`/`false`/`0`/`''`) renders nothing — there is no reserved
737
+ * empty slot, so the row's content shifts left when `icon` is falsy. */
738
+ icon?: React$1.ReactNode;
739
+ label: string;
740
+ active?: boolean;
741
+ variant: NavRowVariant;
742
+ /** Right-aligned adornment: chevron, checkmark, action. Keeps its own size.
743
+ * A falsy value (`null`/`false`/`0`/`''`) renders nothing — e.g. passing
744
+ * `trailing={count}` with `count === 0` produces no adornment, not a "0". */
745
+ trailing?: React$1.ReactNode;
746
+ /** Icon-only row (collapsed rails). `label` becomes the aria-label. */
747
+ hideLabel?: boolean;
748
+ }
749
+ /** Render the single child element instead of a button, e.g. a router <Link>.
750
+ * The row composes its content from props, so the child's own children are replaced —
751
+ * `children` is only accepted when `asChild` is true; otherwise it is rejected at the
752
+ * type level rather than silently dropped at render time. */
753
+ type NavRowProps = NavRowOwnProps & Omit<React$1.ComponentPropsWithoutRef<'button'>, 'children'> & ({
754
+ asChild: true;
755
+ children: React$1.ReactElement;
756
+ } | {
757
+ asChild?: false;
758
+ children?: never;
759
+ });
760
+ /** Nav row primitive (rail and menu variants). Composes an icon, label and trailing slot
761
+ * into a button, or into a single child via `asChild` (e.g. a router `<Link>`).
762
+ *
763
+ * Rail rows are designed for the `bg-surface` canvas: in the light theme `--hov-bg`
764
+ * equals `--color-bg-elevated`/`--menu-bg` (`#ffffff`), so a rail `NavRow` placed on an
765
+ * elevated/white surface has no hover contrast in light mode. This is the approved
766
+ * design and the token is not expected to change — avoid using rail `NavRow` off the
767
+ * `bg-surface` canvas in light theme. */
768
+ declare const NavRow: React$1.ForwardRefExoticComponent<NavRowProps & React$1.RefAttributes<HTMLButtonElement>>;
769
+
770
+ type NavSectionLabelProps = React$1.HTMLAttributes<HTMLDivElement>;
771
+ /** Section header for nav rails and menu popups. Insets match the 0.3.0 prototype
772
+ * (4px 4px 2px 8px), so it hangs slightly left of the rows it labels. */
773
+ declare const NavSectionLabel: React$1.ForwardRefExoticComponent<NavSectionLabelProps & React$1.RefAttributes<HTMLDivElement>>;
774
+
775
+ interface MenuPopupProps {
776
+ /** The element that opens the menu. Rendered via PopoverTrigger asChild. */
777
+ trigger: React$1.ReactNode;
778
+ children: React$1.ReactNode;
779
+ open?: boolean;
780
+ defaultOpen?: boolean;
781
+ onOpenChange?: (open: boolean) => void;
782
+ /** Which way the panel opens from its trigger. Default 'down'. */
783
+ direction?: 'up' | 'down';
784
+ align?: 'start' | 'center' | 'end';
785
+ /** Gap between trigger and panel, in px. Default 6 (prototype). */
786
+ sideOffset?: number;
787
+ className?: string;
788
+ }
789
+ /** Popup menu shell. Wraps the repo's Radix Popover, so outside-click close, focus
790
+ * management and portalling come from the primitive rather than being re-implemented. */
791
+ declare function MenuPopup({ trigger, children, open, defaultOpen, onOpenChange, direction, align, sideOffset, className, }: MenuPopupProps): React$1.JSX.Element;
792
+ declare const MenuDivider: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLDivElement> & React$1.RefAttributes<HTMLDivElement>>;
793
+
733
794
  interface NavItem {
734
795
  label: string;
735
796
  href: string;
@@ -788,4 +849,4 @@ declare function AppShell({ sidebar, topBar, children, className }: AppShellProp
788
849
  /** Alias of AppShell — reads better in app router files. */
789
850
  declare const AuthenticatedLayout: typeof AppShell;
790
851
 
791
- 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, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, type CarouselApi, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ConfirmDialog, type ConfirmDialogProps, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, type CopyButtonProps, DatePicker, type DatePickerProps, 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, HoverCard, HoverCardContent, HoverCardTrigger, InlineError, type InlineErrorProps, Input, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, Label, Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarItem, MenubarLabel, MenubarMenu, MenubarPortal, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, type NavItem, type NavLinkComponent, NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, ScrollArea, ScrollBar, SectionIntro, Select, type SelectOption, 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, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, 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, statusDotVariants, toggleVariants, useCarousel, useFormField, useIsMobile, useSidebar, useTheme };
852
+ 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, Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, Calendar, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, type CarouselApi, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, Checkbox, Collapsible, CollapsibleContent, CollapsibleTrigger, Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, ConfirmDialog, type ConfirmDialogProps, ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, CopyButton, type CopyButtonProps, DatePicker, type DatePickerProps, 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, HoverCard, HoverCardContent, HoverCardTrigger, InlineError, type InlineErrorProps, Input, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, 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, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Progress, RadioGroup, RadioGroupItem, ScrollArea, ScrollBar, SectionIntro, Select, type SelectOption, 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, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, type TextareaProps, 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, statusDotVariants, toggleVariants, useCarousel, useFormField, useIsMobile, useSidebar, useTheme };
package/dist/index.js CHANGED
@@ -2632,7 +2632,7 @@ function UserMenu({ user, items, collapsed = false, align = "end" }) {
2632
2632
  "button",
2633
2633
  {
2634
2634
  type: "button",
2635
- className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors hover:bg-bg-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
2635
+ 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",
2636
2636
  children: [
2637
2637
  avatar,
2638
2638
  /* @__PURE__ */ jsxs25("span", { className: "min-w-0 flex-1", children: [
@@ -2649,30 +2649,41 @@ function UserMenu({ user, items, collapsed = false, align = "end" }) {
2649
2649
  /* @__PURE__ */ jsx52(TooltipTrigger, { asChild: true, children: menuTrigger }),
2650
2650
  /* @__PURE__ */ jsx52(TooltipContent, { side: "right", children: user.name })
2651
2651
  ] }) }) : menuTrigger,
2652
- /* @__PURE__ */ jsxs25(DropdownMenuContent, { align, className: "w-56", children: [
2653
- /* @__PURE__ */ jsxs25(DropdownMenuLabel, { children: [
2654
- /* @__PURE__ */ jsx52("span", { className: "block truncate text-sm font-medium text-text-primary", children: user.name }),
2655
- user.email && /* @__PURE__ */ jsx52("span", { className: "block truncate text-xs font-normal text-text-tertiary", children: user.email })
2656
- ] }),
2657
- /* @__PURE__ */ jsx52(DropdownMenuSeparator, {}),
2658
- items.map((item, i) => {
2659
- const Icon = item.icon;
2660
- const content = /* @__PURE__ */ jsxs25(Fragment, { children: [
2661
- Icon && /* @__PURE__ */ jsx52(Icon, { className: "size-4", "aria-hidden": "true" }),
2662
- item.label
2663
- ] });
2664
- return /* @__PURE__ */ jsx52(
2665
- DropdownMenuItem,
2666
- {
2667
- onSelect: item.onSelect,
2668
- className: cn("gap-2", item.destructive && "text-text-danger focus:text-text-danger"),
2669
- asChild: Boolean(item.href),
2670
- children: item.href ? /* @__PURE__ */ jsx52("a", { href: item.href, children: content }) : content
2671
- },
2672
- `${item.label}-${i}`
2673
- );
2674
- })
2675
- ] })
2652
+ /* @__PURE__ */ jsxs25(
2653
+ DropdownMenuContent,
2654
+ {
2655
+ align,
2656
+ sideOffset: 6,
2657
+ className: "flex w-56 flex-col gap-px rounded-xl border-border-subtle bg-[var(--menu-bg)] p-1 shadow-(--menu-shadow)",
2658
+ children: [
2659
+ /* @__PURE__ */ jsxs25(DropdownMenuLabel, { className: "px-2 pt-1.5 pb-1 text-xs font-medium text-text-tertiary", children: [
2660
+ /* @__PURE__ */ jsx52("span", { className: "block truncate text-sm font-medium text-text-primary", children: user.name }),
2661
+ user.email && /* @__PURE__ */ jsx52("span", { className: "block truncate text-xs font-normal text-text-tertiary", children: user.email })
2662
+ ] }),
2663
+ /* @__PURE__ */ jsx52(DropdownMenuSeparator, { className: "mx-0 shrink-0 bg-border-subtle" }),
2664
+ items.map((item, i) => {
2665
+ const Icon = item.icon;
2666
+ const content = /* @__PURE__ */ jsxs25(Fragment, { children: [
2667
+ Icon && /* @__PURE__ */ jsx52(Icon, { className: "size-4", "aria-hidden": "true" }),
2668
+ item.label
2669
+ ] });
2670
+ return /* @__PURE__ */ jsx52(
2671
+ DropdownMenuItem,
2672
+ {
2673
+ onSelect: item.onSelect,
2674
+ className: cn(
2675
+ "gap-2 rounded-md px-2 py-1.5 text-sm font-medium text-text-secondary focus:bg-[var(--menu-hov)] focus:text-text-primary",
2676
+ item.destructive && "text-text-danger focus:text-text-danger"
2677
+ ),
2678
+ asChild: Boolean(item.href),
2679
+ children: item.href ? /* @__PURE__ */ jsx52("a", { href: item.href, children: content }) : content
2680
+ },
2681
+ `${item.label}-${i}`
2682
+ );
2683
+ })
2684
+ ]
2685
+ }
2686
+ )
2676
2687
  ] });
2677
2688
  return menu;
2678
2689
  }
@@ -2770,9 +2781,113 @@ function useSidebar(options = {}) {
2770
2781
  return { collapsed, setCollapsed, toggle };
2771
2782
  }
2772
2783
 
2784
+ // src/components/nav-row.tsx
2785
+ import * as React47 from "react";
2786
+ import { Slot as Slot4 } from "@radix-ui/react-slot";
2787
+ import { Fragment as Fragment2, jsx as jsx55, jsxs as jsxs26 } from "react/jsx-runtime";
2788
+ var base = "flex w-full items-center text-left text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus";
2789
+ var shape = {
2790
+ rail: "gap-[14px] rounded-xl px-2.5 py-2",
2791
+ menu: "gap-2 rounded-md px-2 py-1.5"
2792
+ };
2793
+ var state = {
2794
+ rail: {
2795
+ idle: "font-medium text-text-secondary hover:bg-[var(--hov-bg)] hover:text-text-primary",
2796
+ active: "bg-bg-brand-subtle font-semibold text-text-brand"
2797
+ },
2798
+ menu: {
2799
+ idle: "font-medium text-text-secondary hover:bg-[var(--menu-hov)]",
2800
+ active: "font-semibold text-text-brand hover:bg-[var(--menu-hov)]"
2801
+ }
2802
+ };
2803
+ var NavRow = React47.forwardRef(function NavRow2({ icon, label, active = false, variant, trailing, hideLabel = false, asChild = false, className, children, ...props }, ref) {
2804
+ const content = /* @__PURE__ */ jsxs26(Fragment2, { children: [
2805
+ icon && /* @__PURE__ */ jsx55(
2806
+ "span",
2807
+ {
2808
+ className: "flex size-5 shrink-0 items-center justify-center [&_svg]:size-[18px]",
2809
+ "aria-hidden": "true",
2810
+ children: icon
2811
+ }
2812
+ ),
2813
+ !hideLabel && /* @__PURE__ */ jsx55("span", { className: "min-w-0 flex-1 truncate", children: label }),
2814
+ trailing && /* @__PURE__ */ jsx55("span", { className: "flex shrink-0 items-center", children: trailing })
2815
+ ] });
2816
+ const classes = cn(base, shape[variant], state[variant][active ? "active" : "idle"], className);
2817
+ if (asChild) {
2818
+ const child = React47.Children.only(children);
2819
+ return /* @__PURE__ */ jsx55(Slot4, { ref, className: classes, "aria-label": hideLabel ? label : void 0, ...props, children: React47.cloneElement(child, void 0, content) });
2820
+ }
2821
+ return /* @__PURE__ */ jsx55("button", { ref, type: "button", className: classes, "aria-label": hideLabel ? label : void 0, ...props, children: content });
2822
+ });
2823
+ NavRow.displayName = "NavRow";
2824
+
2825
+ // src/components/nav-section-label.tsx
2826
+ import * as React48 from "react";
2827
+ import { jsx as jsx56 } from "react/jsx-runtime";
2828
+ var NavSectionLabel = React48.forwardRef(
2829
+ function NavSectionLabel2({ className, ...props }, ref) {
2830
+ return /* @__PURE__ */ jsx56(
2831
+ "div",
2832
+ {
2833
+ ref,
2834
+ className: cn("pt-1 pr-1 pb-0.5 pl-2 text-xs font-medium text-text-tertiary", className),
2835
+ ...props
2836
+ }
2837
+ );
2838
+ }
2839
+ );
2840
+ NavSectionLabel.displayName = "NavSectionLabel";
2841
+
2842
+ // src/components/menu-popup.tsx
2843
+ import * as React49 from "react";
2844
+ import { jsx as jsx57, jsxs as jsxs27 } from "react/jsx-runtime";
2845
+ function MenuPopup({
2846
+ trigger,
2847
+ children,
2848
+ open,
2849
+ defaultOpen,
2850
+ onOpenChange,
2851
+ direction = "down",
2852
+ align = "start",
2853
+ sideOffset = 6,
2854
+ className
2855
+ }) {
2856
+ return /* @__PURE__ */ jsxs27(Popover, { open, defaultOpen, onOpenChange, children: [
2857
+ /* @__PURE__ */ jsx57(PopoverTrigger, { asChild: true, children: trigger }),
2858
+ /* @__PURE__ */ jsx57(
2859
+ PopoverContent,
2860
+ {
2861
+ side: direction === "up" ? "top" : "bottom",
2862
+ align,
2863
+ sideOffset,
2864
+ className: cn(
2865
+ "flex w-56 flex-col gap-px rounded-xl border border-border-subtle bg-[var(--menu-bg)] p-1 shadow-(--menu-shadow)",
2866
+ className
2867
+ ),
2868
+ children
2869
+ }
2870
+ )
2871
+ ] });
2872
+ }
2873
+ var MenuDivider = React49.forwardRef(
2874
+ function MenuDivider2({ className, ...props }, ref) {
2875
+ return /* @__PURE__ */ jsx57(
2876
+ "div",
2877
+ {
2878
+ ref,
2879
+ role: "separator",
2880
+ className: cn("my-1 h-px shrink-0 bg-border-subtle", className),
2881
+ ...props
2882
+ }
2883
+ );
2884
+ }
2885
+ );
2886
+ MenuDivider.displayName = "MenuDivider";
2887
+
2773
2888
  // src/components/sidebar-nav.tsx
2774
2889
  import { Link as Link3, useLocation } from "react-router";
2775
- import { jsx as jsx55, jsxs as jsxs26 } from "react/jsx-runtime";
2890
+ import { jsx as jsx58, jsxs as jsxs28 } from "react/jsx-runtime";
2776
2891
  function isActivePath(pathname, full, exact) {
2777
2892
  if (exact) return pathname === full;
2778
2893
  return pathname === full || pathname.startsWith(`${full}/`);
@@ -2788,28 +2903,23 @@ function SidebarNavLink({
2788
2903
  const Comp = LinkComponent ?? Link3;
2789
2904
  const to = `${basePath}${item.href}`;
2790
2905
  const Icon = item.icon;
2791
- const link = /* @__PURE__ */ jsxs26(
2792
- Comp,
2906
+ const link = /* @__PURE__ */ jsx58(
2907
+ NavRow,
2793
2908
  {
2794
- to,
2795
- className: cn(
2796
- "flex items-center rounded-lg text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
2797
- collapsed ? "size-9 justify-center" : "w-full gap-2.5 px-2.5 py-[7px]",
2798
- active ? "bg-bg-secondary font-medium text-text-brand" : "text-text-secondary hover:bg-bg-secondary hover:text-text-primary",
2799
- className
2800
- ),
2801
- "aria-current": active ? "page" : void 0,
2802
- "aria-label": collapsed ? item.label : void 0,
2803
- children: [
2804
- Icon && /* @__PURE__ */ jsx55(Icon, { className: "size-[18px] shrink-0", "aria-hidden": "true" }),
2805
- !collapsed && item.label
2806
- ]
2909
+ variant: "rail",
2910
+ active,
2911
+ label: item.label,
2912
+ hideLabel: collapsed,
2913
+ icon: Icon ? /* @__PURE__ */ jsx58(Icon, { "aria-hidden": "true" }) : void 0,
2914
+ className: cn(collapsed && "size-9 justify-center px-0 py-0", className),
2915
+ asChild: true,
2916
+ children: /* @__PURE__ */ jsx58(Comp, { to, "aria-current": active ? "page" : void 0 })
2807
2917
  }
2808
2918
  );
2809
2919
  if (!collapsed) return link;
2810
- return /* @__PURE__ */ jsxs26(Tooltip, { children: [
2811
- /* @__PURE__ */ jsx55(TooltipTrigger, { asChild: true, children: link }),
2812
- /* @__PURE__ */ jsx55(TooltipContent, { side: "right", children: item.label })
2920
+ return /* @__PURE__ */ jsxs28(Tooltip, { children: [
2921
+ /* @__PURE__ */ jsx58(TooltipTrigger, { asChild: true, children: link }),
2922
+ /* @__PURE__ */ jsx58(TooltipContent, { side: "right", children: item.label })
2813
2923
  ] });
2814
2924
  }
2815
2925
  function SidebarNav({
@@ -2821,18 +2931,18 @@ function SidebarNav({
2821
2931
  className
2822
2932
  }) {
2823
2933
  const { pathname } = useLocation();
2824
- const nav = /* @__PURE__ */ jsx55(
2934
+ const nav = /* @__PURE__ */ jsx58(
2825
2935
  "nav",
2826
2936
  {
2827
2937
  className: cn(
2828
2938
  "flex flex-col gap-0.5",
2829
- collapsed ? "items-center px-1" : "px-2",
2939
+ collapsed ? "items-center px-1" : "px-3",
2830
2940
  className
2831
2941
  ),
2832
2942
  children: items.map((item) => {
2833
2943
  const full = `${basePath}${item.href}`;
2834
2944
  const active = isItemActive ? isItemActive(item, pathname) : isActivePath(pathname, full, item.exact);
2835
- return /* @__PURE__ */ jsx55(
2945
+ return /* @__PURE__ */ jsx58(
2836
2946
  SidebarNavLink,
2837
2947
  {
2838
2948
  item,
@@ -2846,12 +2956,12 @@ function SidebarNav({
2846
2956
  })
2847
2957
  }
2848
2958
  );
2849
- return collapsed ? /* @__PURE__ */ jsx55(TooltipProvider, { children: nav }) : nav;
2959
+ return collapsed ? /* @__PURE__ */ jsx58(TooltipProvider, { children: nav }) : nav;
2850
2960
  }
2851
2961
 
2852
2962
  // src/components/app-sidebar.tsx
2853
2963
  import { PanelLeft } from "lucide-react";
2854
- import { jsx as jsx56, jsxs as jsxs27 } from "react/jsx-runtime";
2964
+ import { jsx as jsx59, jsxs as jsxs29 } from "react/jsx-runtime";
2855
2965
  function AppSidebar({
2856
2966
  items,
2857
2967
  logo,
@@ -2871,7 +2981,7 @@ function AppSidebar({
2871
2981
  defaultCollapsed,
2872
2982
  onCollapsedChange
2873
2983
  });
2874
- return /* @__PURE__ */ jsxs27(
2984
+ return /* @__PURE__ */ jsxs29(
2875
2985
  "aside",
2876
2986
  {
2877
2987
  "data-collapsed": collapsed,
@@ -2881,76 +2991,76 @@ function AppSidebar({
2881
2991
  className
2882
2992
  ),
2883
2993
  children: [
2884
- collapsed ? /* @__PURE__ */ jsx56("div", { className: "flex justify-center border-b border-border-subtle p-2", children: header ?? /* @__PURE__ */ jsxs27("div", { className: "group relative flex size-8 items-center justify-center", children: [
2885
- /* @__PURE__ */ jsx56("span", { className: "pointer-events-none transition-opacity group-hover:opacity-0 group-focus-within:opacity-0", children: logoCollapsed ?? logo }),
2886
- /* @__PURE__ */ jsx56(
2994
+ 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: [
2995
+ /* @__PURE__ */ jsx59("span", { className: "pointer-events-none transition-opacity group-hover:opacity-0 group-focus-within:opacity-0", children: logoCollapsed ?? logo }),
2996
+ /* @__PURE__ */ jsx59(
2887
2997
  "button",
2888
2998
  {
2889
2999
  type: "button",
2890
3000
  onClick: toggle,
2891
3001
  "aria-label": "Expand sidebar",
2892
3002
  "aria-expanded": false,
2893
- className: "absolute inset-0 flex items-center justify-center rounded-md text-text-secondary opacity-0 transition-opacity hover:bg-bg-secondary 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",
2894
- children: /* @__PURE__ */ jsx56(PanelLeft, { className: "size-4", "aria-hidden": "true" })
3003
+ 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",
3004
+ children: /* @__PURE__ */ jsx59(PanelLeft, { className: "size-4", "aria-hidden": "true" })
2895
3005
  }
2896
3006
  )
2897
- ] }) }) : /* @__PURE__ */ jsxs27("div", { className: "flex items-center justify-between border-b border-border-subtle p-2", children: [
3007
+ ] }) }) : /* @__PURE__ */ jsxs29("div", { className: "flex items-center justify-between border-b border-border-subtle p-3", children: [
2898
3008
  header ?? logo,
2899
- /* @__PURE__ */ jsx56(
3009
+ /* @__PURE__ */ jsx59(
2900
3010
  "button",
2901
3011
  {
2902
3012
  type: "button",
2903
3013
  onClick: toggle,
2904
3014
  "aria-label": "Collapse sidebar",
2905
3015
  "aria-expanded": true,
2906
- className: "flex size-6 items-center justify-center rounded-md text-text-secondary transition-colors hover:bg-bg-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
2907
- children: /* @__PURE__ */ jsx56(PanelLeft, { className: "size-4", "aria-hidden": "true" })
3016
+ 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",
3017
+ children: /* @__PURE__ */ jsx59(PanelLeft, { className: "size-4", "aria-hidden": "true" })
2908
3018
  }
2909
3019
  )
2910
3020
  ] }),
2911
- /* @__PURE__ */ jsx56("div", { className: "flex-1 overflow-y-auto overflow-x-hidden py-2", children: /* @__PURE__ */ jsx56(SidebarNav, { items, collapsed, basePath, LinkComponent, isItemActive }) }),
2912
- footer && /* @__PURE__ */ jsx56("div", { className: cn("border-t border-border-subtle", collapsed ? "px-1 py-2" : "px-2 py-2"), children: footer })
3021
+ /* @__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 }) }),
3022
+ footer && /* @__PURE__ */ jsx59("div", { className: cn("border-t border-border-subtle", collapsed ? "px-1 py-2" : "px-3 py-3"), children: footer })
2913
3023
  ]
2914
3024
  }
2915
3025
  );
2916
3026
  }
2917
3027
 
2918
3028
  // src/components/app-shell.tsx
2919
- import * as React47 from "react";
3029
+ import * as React50 from "react";
2920
3030
  import { Menu as Menu2 } from "lucide-react";
2921
3031
  import { useLocation as useLocation2 } from "react-router";
2922
- import { jsx as jsx57, jsxs as jsxs28 } from "react/jsx-runtime";
3032
+ import { jsx as jsx60, jsxs as jsxs30 } from "react/jsx-runtime";
2923
3033
  function expandedSidebar(sidebar) {
2924
- return React47.isValidElement(sidebar) ? React47.cloneElement(sidebar, { collapsed: false }) : sidebar;
3034
+ return React50.isValidElement(sidebar) ? React50.cloneElement(sidebar, { collapsed: false }) : sidebar;
2925
3035
  }
2926
3036
  function AppShell({ sidebar, topBar, children, className }) {
2927
3037
  const { pathname } = useLocation2();
2928
- const [mobileOpen, setMobileOpen] = React47.useState(false);
2929
- React47.useEffect(() => {
3038
+ const [mobileOpen, setMobileOpen] = React50.useState(false);
3039
+ React50.useEffect(() => {
2930
3040
  setMobileOpen(false);
2931
3041
  }, [pathname]);
2932
3042
  const isMobile = useIsMobile();
2933
3043
  if (isMobile) {
2934
- return /* @__PURE__ */ jsx57(TooltipProvider, { children: /* @__PURE__ */ jsxs28("div", { className: cn("flex min-h-screen flex-col bg-bg-sunken", className), children: [
2935
- /* @__PURE__ */ jsxs28("header", { className: "flex shrink-0 items-center justify-between border-b border-border-subtle bg-bg-surface px-4 py-3", children: [
2936
- /* @__PURE__ */ jsxs28(Sheet, { open: mobileOpen, onOpenChange: setMobileOpen, children: [
2937
- /* @__PURE__ */ jsx57(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx57(Button, { variant: "ghost", size: "icon", className: "size-8 text-text-secondary", "aria-label": "Open navigation", children: /* @__PURE__ */ jsx57(Menu2, { className: "size-5" }) }) }),
2938
- /* @__PURE__ */ jsxs28(SheetContent, { side: "left", className: "w-[240px] bg-bg-surface p-0", children: [
2939
- /* @__PURE__ */ jsx57(SheetTitle, { className: "sr-only", children: "Navigation" }),
2940
- /* @__PURE__ */ jsx57(SheetDescription, { className: "sr-only", children: "Application navigation menu" }),
3044
+ return /* @__PURE__ */ jsx60(TooltipProvider, { children: /* @__PURE__ */ jsxs30("div", { className: cn("flex min-h-screen flex-col bg-bg-sunken", className), children: [
3045
+ /* @__PURE__ */ jsxs30("header", { className: "flex shrink-0 items-center justify-between border-b border-border-subtle bg-bg-surface px-4 py-3", children: [
3046
+ /* @__PURE__ */ jsxs30(Sheet, { open: mobileOpen, onOpenChange: setMobileOpen, children: [
3047
+ /* @__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" }) }) }),
3048
+ /* @__PURE__ */ jsxs30(SheetContent, { side: "left", className: "w-[240px] bg-bg-surface p-0", children: [
3049
+ /* @__PURE__ */ jsx60(SheetTitle, { className: "sr-only", children: "Navigation" }),
3050
+ /* @__PURE__ */ jsx60(SheetDescription, { className: "sr-only", children: "Application navigation menu" }),
2941
3051
  expandedSidebar(sidebar)
2942
3052
  ] })
2943
3053
  ] }),
2944
- /* @__PURE__ */ jsx57("div", { className: "flex items-center gap-2", children: topBar })
3054
+ /* @__PURE__ */ jsx60("div", { className: "flex items-center gap-2", children: topBar })
2945
3055
  ] }),
2946
- /* @__PURE__ */ jsx57("main", { className: "flex-1 overflow-auto p-4", children })
3056
+ /* @__PURE__ */ jsx60("main", { className: "flex-1 overflow-auto p-4", children })
2947
3057
  ] }) });
2948
3058
  }
2949
- return /* @__PURE__ */ jsx57(TooltipProvider, { children: /* @__PURE__ */ jsxs28("div", { className: cn("flex min-h-screen gap-3 bg-bg-sunken p-6", className), children: [
2950
- /* @__PURE__ */ jsx57("div", { className: "sticky top-6 h-[calc(100vh-48px)] shrink-0", children: sidebar }),
2951
- /* @__PURE__ */ jsxs28("div", { className: "flex min-w-0 flex-1 flex-col overflow-hidden rounded-2xl border border-border-subtle bg-bg-surface", children: [
2952
- /* @__PURE__ */ jsx57("div", { className: "flex min-h-[52px] shrink-0 items-center justify-end gap-2 border-b border-border-subtle px-5", children: topBar }),
2953
- /* @__PURE__ */ jsx57("main", { className: "flex-1 overflow-auto px-7 py-8", children })
3059
+ return /* @__PURE__ */ jsx60(TooltipProvider, { children: /* @__PURE__ */ jsxs30("div", { className: cn("flex min-h-screen gap-2 bg-bg-sunken p-4", className), children: [
3060
+ /* @__PURE__ */ jsx60("div", { className: "sticky top-4 h-[calc(100vh-32px)] shrink-0", children: sidebar }),
3061
+ /* @__PURE__ */ jsxs30("div", { className: "flex min-w-0 flex-1 flex-col overflow-hidden rounded-2xl border border-border-subtle bg-bg-surface", children: [
3062
+ /* @__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 }),
3063
+ /* @__PURE__ */ jsx60("main", { className: "flex-1 overflow-auto px-7 py-8", children })
2954
3064
  ] })
2955
3065
  ] }) });
2956
3066
  }
@@ -3089,6 +3199,8 @@ export {
3089
3199
  InputOTPSeparator,
3090
3200
  InputOTPSlot,
3091
3201
  Label2 as Label,
3202
+ MenuDivider,
3203
+ MenuPopup,
3092
3204
  Menubar,
3093
3205
  MenubarCheckboxItem,
3094
3206
  MenubarContent,
@@ -3105,6 +3217,8 @@ export {
3105
3217
  MenubarSubContent,
3106
3218
  MenubarSubTrigger,
3107
3219
  MenubarTrigger,
3220
+ NavRow,
3221
+ NavSectionLabel,
3108
3222
  NavigationMenu,
3109
3223
  NavigationMenuContent,
3110
3224
  NavigationMenuIndicator,
package/dist/styles.css CHANGED
@@ -107,6 +107,14 @@
107
107
  --color-border-success: #1da456;
108
108
  --color-border-warning: #b3780e;
109
109
  --color-border-danger: #e82020;
110
+ /* === Nav / menu surfaces (0.3.0 sidebar) === */
111
+ /* --menu-shadow is a literal: this package ships no --shadow-* variables (shadows live
112
+ in the generated Tailwind preset's boxShadow map), so var(--shadow-md) would resolve
113
+ to nothing. Value mirrors the 0.3.0 prototype DS's own --shadow-md. */
114
+ --hov-bg: color-mix(in srgb, var(--color-text-primary) 7%, transparent);
115
+ --menu-bg: var(--color-bg-elevated);
116
+ --menu-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
117
+ --menu-hov: var(--hov-bg);
110
118
  }
111
119
 
112
120
  .light {
@@ -116,7 +124,7 @@
116
124
  --color-bg-elevated: #ffffff;
117
125
  --color-bg-sunken: #e1e2e4;
118
126
  --color-bg-brand: #06297d;
119
- --color-bg-brand-subtle: #eaeef4;
127
+ --color-bg-brand-subtle: #e4ecfe;
120
128
  --color-bg-brand-bright: #165ff2;
121
129
  --color-bg-brand-hover: #05226a;
122
130
  --color-bg-success: #13703c;
@@ -137,7 +145,7 @@
137
145
  --color-text-on-success: #f6f7f7;
138
146
  --color-text-on-warning: #08090c;
139
147
  --color-text-on-danger: #f6f7f7;
140
- --color-text-brand: #0940b8;
148
+ --color-text-brand: #0b4cd5;
141
149
  --color-text-success: #13703c;
142
150
  --color-text-warning: #7a5409;
143
151
  --color-text-danger: #94120e;
@@ -146,7 +154,7 @@
146
154
  --color-icon-primary: #252936;
147
155
  --color-icon-secondary: #535a6e;
148
156
  --color-icon-disabled: #8a91a7;
149
- --color-icon-brand: #0940b8;
157
+ --color-icon-brand: #165ff2;
150
158
  --color-icon-success: #13703c;
151
159
  --color-icon-warning: #7a5409;
152
160
  --color-icon-danger: #94120e;
@@ -158,6 +166,11 @@
158
166
  --color-border-success: #188a49;
159
167
  --color-border-warning: #b3780e;
160
168
  --color-border-danger: #e82020;
169
+ /* Light keeps opaque plates: a translucent text-mix would vanish on a white menu. */
170
+ --hov-bg: #ffffff;
171
+ --menu-bg: #ffffff;
172
+ --menu-shadow: 0 12px 32px rgba(16, 24, 40, 0.1), 0 2px 8px rgba(16, 24, 40, 0.06);
173
+ --menu-hov: #f2f4f7;
161
174
  }
162
175
 
163
176
  /* === Base === */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessly/ui",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Lessly design system — shared UI primitives, tokens, and theme",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -110,7 +110,8 @@
110
110
  "storybook": "storybook dev -p 6006 --no-open",
111
111
  "build-storybook": "storybook build",
112
112
  "changeset": "changeset",
113
- "release:version": "changeset version",
113
+ "release:version": "changeset version && node scripts/stamp-changelog-date.mjs",
114
+ "release:publish": "node scripts/release-publish.mjs",
114
115
  "release:tag": "changeset tag"
115
116
  }
116
117
  }