@conscia-labs/design-system 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
@@ -270,6 +270,54 @@ export function ProductShell({
270
270
  }
271
271
  ```
272
272
 
273
+ ### Sidebar variants and semantic surfaces
274
+
275
+ `AppSidebar` keeps the historical dark treatment by default. Consumers that
276
+ want the sidebar to follow the application appearance can opt into the
277
+ refreshed hierarchy with `variant="auto"`; `variant="light"` is available for
278
+ an explicitly light sidebar.
279
+
280
+ ```tsx
281
+ <AppShell>
282
+ <AppSidebar variant="auto">
283
+ <AppSidebarHeader>
284
+ <ProductIdentity label="Conscia" description="Administration" />
285
+ </AppSidebarHeader>
286
+ <AppSidebarContent>{navigation}</AppSidebarContent>
287
+ <AppSidebarFooter>{accountMenu}</AppSidebarFooter>
288
+ </AppSidebar>
289
+ </AppShell>
290
+ ```
291
+
292
+ The default is intentionally backwards compatible. The sidebar scope exposes
293
+ reusable semantic aliases for its canvas, header, content, hover, active,
294
+ search, footer, text, icon, group label, count, border, and focus-ring roles.
295
+ Use the generated utilities such as `bg-sidebar-canvas`,
296
+ `bg-sidebar-hover`, `text-sidebar-primary-text`, and
297
+ `text-sidebar-metadata-text` in shared or application-owned compositions.
298
+ Legacy aliases including `bg-sidebar`, `bg-sidebar-accent`, and
299
+ `text-sidebar-foreground` remain supported.
300
+
301
+ `SidebarSearch` owns only the trigger, expanded field, Escape handling, and
302
+ focus handoff. Applications provide the query value and filtering behavior.
303
+ `NavigationGroup` accepts an optional `count`; application-owned group labels
304
+ can still be supplied as arbitrary React nodes. Routing, conversation rows,
305
+ row actions, account menus, permissions, and appearance controls remain
306
+ application-owned.
307
+
308
+ For migration, replace a consuming application's light-only `--sidebar-*`
309
+ root override with `variant="auto"` on its shared `AppSidebar`. Remove
310
+ descendant opacity and background overrides as each shell adopts the semantic
311
+ aliases. Keep product-specific selectors only where they encode behavior or
312
+ content rather than shared sidebar presentation.
313
+
314
+ The ownership boundary is:
315
+
316
+ | Shared design system | Application-owned |
317
+ | --- | --- |
318
+ | Sidebar variant tokens, surface hierarchy, geometry, responsive drawer, focus states, active/hover/disabled styling, tooltips, and search affordance behavior | Routes, permissions, navigation data, query/filter state, conversation or inventory data, row actions, account/profile menus, sign-out, and product-specific persistence keys |
319
+ | `NavigationGroup` keyboard expansion and `SidebarNavigation` collapsed flyouts | Group labels/content, link destinations, active-route calculation, and business-specific empty states |
320
+
273
321
  ## Choosing the right component
274
322
 
275
323
  ### Table or DataTable?
@@ -11,6 +11,8 @@ declare function Button({ className, variant, size, asChild, type, ...props }: R
11
11
  asChild?: boolean;
12
12
  }): React.JSX.Element;
13
13
 
14
+ declare function Input({ className, type, ...props }: React.ComponentProps<"input">): React.JSX.Element;
15
+
14
16
  declare function Separator({ className, orientation, decorative, ...props }: React.ComponentProps<typeof SeparatorPrimitive.Root>): React.JSX.Element;
15
17
 
16
18
  declare function Card({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
@@ -19,4 +21,4 @@ declare function CardTitle({ className, ...props }: React.ComponentProps<"div">)
19
21
  declare function CardDescription({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
20
22
  declare function CardContent({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
21
23
 
22
- export { Button as B, Card as C, Separator as S, CardContent as a, CardDescription as b, CardHeader as c, CardTitle as d, buttonVariants as e };
24
+ export { Button as B, Card as C, Input as I, Separator as S, CardContent as a, CardDescription as b, CardHeader as c, CardTitle as d, buttonVariants as e };
@@ -25,6 +25,7 @@ import {
25
25
  DropdownMenuItem,
26
26
  DropdownMenuLabel,
27
27
  DropdownMenuTrigger,
28
+ Input,
28
29
  Select,
29
30
  SelectContent,
30
31
  SelectItem,
@@ -51,7 +52,7 @@ import {
51
52
  TooltipContent,
52
53
  TooltipProvider,
53
54
  TooltipTrigger
54
- } from "./chunk-YHPZ5CQU.js";
55
+ } from "./chunk-HLND6FZS.js";
55
56
  import {
56
57
  cn
57
58
  } from "./chunk-JU5DQXFC.js";
@@ -366,7 +367,7 @@ function DetailBackLink({ label, className, ...props }) {
366
367
  // src/patterns/app-shell.tsx
367
368
  import * as React2 from "react";
368
369
  import { Slot } from "@radix-ui/react-slot";
369
- import { PanelLeftIcon } from "lucide-react";
370
+ import { PanelLeftIcon, Search, X } from "lucide-react";
370
371
  import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
371
372
  var SIDEBAR_COOKIE_NAME = "conscia_sidebar_state";
372
373
  var SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
@@ -504,7 +505,7 @@ function AppShell({
504
505
  suppressHydrationWarning: true,
505
506
  className: cn(
506
507
  "group/shell min-h-svh bg-canvas text-foreground",
507
- "[--ds-app-sidebar-width:16rem] [--ds-app-sidebar-width-mobile:18rem] data-[sidebar-state=collapsed]:[--ds-app-sidebar-width:3.5rem]",
508
+ "[--ds-app-sidebar-width:var(--ds-sidebar-width)] [--ds-app-sidebar-width-mobile:var(--ds-sidebar-width-mobile)] data-[sidebar-state=collapsed]:[--ds-app-sidebar-width:3.5rem]",
508
509
  className
509
510
  ),
510
511
  style,
@@ -515,6 +516,7 @@ function AppShell({
515
516
  }
516
517
  function AppSidebar({
517
518
  side = "left",
519
+ variant = "dark",
518
520
  className,
519
521
  children,
520
522
  title = "Application navigation",
@@ -530,7 +532,8 @@ function AppSidebar({
530
532
  SheetContent,
531
533
  {
532
534
  side,
533
- className: "w-72 border-sidebar-border bg-sidebar p-0 text-sidebar-foreground [&>[data-slot=sheet-close]]:right-3 [&>[data-slot=sheet-close]]:top-3 [&>[data-slot=sheet-close]]:text-sidebar-foreground/64 [&>[data-slot=sheet-close]]:hover:text-sidebar-foreground",
535
+ "data-sidebar-variant": variant,
536
+ className: "w-[var(--ds-sidebar-width-mobile)] border-sidebar-border bg-sidebar-canvas p-0 text-sidebar-primary-text [&>[data-slot=sheet-close]]:right-3 [&>[data-slot=sheet-close]]:top-3 [&>[data-slot=sheet-close]]:text-sidebar-secondary-text [&>[data-slot=sheet-close]]:hover:text-sidebar-primary-text",
534
537
  children: [
535
538
  /* @__PURE__ */ jsxs4(SheetHeader, { className: "sr-only", children: [
536
539
  /* @__PURE__ */ jsx5(SheetTitle, { children: title }),
@@ -545,8 +548,9 @@ function AppSidebar({
545
548
  {
546
549
  "data-slot": "app-sidebar",
547
550
  "data-side": side,
551
+ "data-sidebar-variant": variant,
548
552
  className: cn(
549
- "fixed inset-y-0 z-20 hidden w-[var(--ds-app-sidebar-width)] shrink-0 flex-col border-sidebar-border bg-sidebar text-sidebar-foreground transition-[width] duration-200 ease-linear lg:flex",
553
+ "fixed inset-y-0 z-20 hidden w-[var(--ds-app-sidebar-width)] shrink-0 flex-col border-sidebar-border bg-sidebar-canvas text-sidebar-primary-text transition-[width] duration-200 ease-linear lg:flex",
550
554
  side === "left" ? "left-0 border-r" : "right-0 border-l",
551
555
  "group-data-[sidebar-state=collapsed]/shell:w-[var(--ds-app-sidebar-width)]",
552
556
  className
@@ -566,7 +570,7 @@ function AppSidebarHeader({
566
570
  {
567
571
  "data-slot": "app-sidebar-header",
568
572
  className: cn(
569
- "flex min-h-14 shrink-0 items-center border-b border-sidebar-border px-3",
573
+ "flex min-h-[var(--ds-sidebar-header-height)] min-w-0 shrink-0 items-center overflow-hidden border-b border-sidebar-border bg-sidebar-header px-3",
570
574
  className
571
575
  ),
572
576
  ...props
@@ -582,7 +586,7 @@ function AppSidebarContent({
582
586
  {
583
587
  "data-slot": "app-sidebar-content",
584
588
  className: cn(
585
- "flex min-h-0 flex-1 flex-col gap-[var(--ds-space-control)] overflow-y-auto px-2.5 py-3",
589
+ "flex min-h-0 min-w-0 flex-1 flex-col gap-[var(--ds-sidebar-group-gap)] overflow-x-hidden overflow-y-auto bg-sidebar-content px-[var(--ds-sidebar-content-padding-x)] py-[var(--ds-sidebar-content-padding-y)]",
586
590
  className
587
591
  ),
588
592
  ...props
@@ -598,7 +602,7 @@ function AppSidebarFooter({
598
602
  {
599
603
  "data-slot": "app-sidebar-footer",
600
604
  className: cn(
601
- "mt-auto flex shrink-0 flex-col gap-2 border-t border-sidebar-border p-2.5",
605
+ "mt-auto flex min-w-0 shrink-0 flex-col gap-2 overflow-hidden border-t border-sidebar-border bg-sidebar-footer p-[var(--ds-sidebar-footer-padding)]",
602
606
  className
603
607
  ),
604
608
  ...props
@@ -617,10 +621,10 @@ function ProductIdentity({
617
621
  "data-slot": "product-identity",
618
622
  className: cn("flex min-w-0 items-center gap-2", className),
619
623
  children: [
620
- icon ? /* @__PURE__ */ jsx5("div", { className: "flex size-7 shrink-0 items-center justify-center rounded-[var(--ds-radius-control)] bg-sidebar-accent text-sidebar-foreground", children: icon }) : null,
624
+ icon ? /* @__PURE__ */ jsx5("div", { className: "flex size-7 shrink-0 items-center justify-center rounded-[var(--ds-radius-control)] bg-sidebar-hover text-sidebar-icon", children: icon }) : null,
621
625
  /* @__PURE__ */ jsxs4("div", { className: "min-w-0 group-data-[sidebar-state=collapsed]/shell:hidden", children: [
622
- /* @__PURE__ */ jsx5("div", { className: "truncate text-sm font-semibold leading-5 text-sidebar-foreground", children: label }),
623
- description ? /* @__PURE__ */ jsx5("div", { className: "truncate text-[var(--ds-metadata)] leading-4 text-sidebar-foreground/62", children: description }) : null
626
+ /* @__PURE__ */ jsx5("div", { className: "truncate text-sm font-semibold leading-5 text-sidebar-primary-text", children: label }),
627
+ description ? /* @__PURE__ */ jsx5("div", { className: "truncate text-[var(--ds-metadata)] leading-4 text-sidebar-metadata-text", children: description }) : null
624
628
  ] })
625
629
  ]
626
630
  }
@@ -641,6 +645,7 @@ function SidebarSeparator({
641
645
  }
642
646
  function NavigationGroup({
643
647
  label,
648
+ count,
644
649
  children,
645
650
  className
646
651
  }) {
@@ -650,7 +655,17 @@ function NavigationGroup({
650
655
  "data-slot": "navigation-group",
651
656
  className: cn("flex min-w-0 flex-col gap-1", className),
652
657
  children: [
653
- label ? /* @__PURE__ */ jsx5("div", { className: "px-2 pb-1 pt-2 text-[0.6875rem] font-medium uppercase tracking-normal text-sidebar-foreground/52 group-data-[sidebar-state=collapsed]/shell:hidden", children: label }) : null,
658
+ label ? /* @__PURE__ */ jsxs4(
659
+ "div",
660
+ {
661
+ "data-slot": "navigation-group-label",
662
+ className: "flex min-h-6 items-center gap-2 px-2 pb-1 pt-2 text-[length:var(--ds-sidebar-group-label-size)] font-semibold uppercase leading-5 tracking-[0.08em] text-sidebar-group-label group-data-[sidebar-state=collapsed]/shell:hidden",
663
+ children: [
664
+ label,
665
+ count !== void 0 ? /* @__PURE__ */ jsx5("span", { className: "ml-auto shrink-0 text-[length:var(--ds-metadata)] font-medium normal-case tracking-normal text-sidebar-group-count", children: count }) : null
666
+ ]
667
+ }
668
+ ) : null,
654
669
  /* @__PURE__ */ jsx5("div", { className: "flex min-w-0 flex-col gap-1", children })
655
670
  ]
656
671
  }
@@ -658,13 +673,13 @@ function NavigationGroup({
658
673
  }
659
674
  function navigationItemClasses(active) {
660
675
  return cn(
661
- "flex h-11 min-w-0 items-center gap-2 rounded-[var(--ds-radius-control)] px-2 text-sm font-medium text-sidebar-foreground/78 outline-none transition-colors duration-150 lg:h-[var(--ds-sidebar-item-height)]",
662
- "hover:bg-sidebar-accent hover:text-sidebar-foreground",
663
- "focus-visible:ring-[3px] focus-visible:ring-ring/35",
664
- "disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50",
676
+ "flex min-h-[var(--ds-sidebar-item-height-touch)] min-w-0 cursor-pointer items-center gap-2 rounded-[var(--ds-radius-control)] px-2 text-sm font-medium text-sidebar-secondary-text outline-none transition-colors duration-150 lg:h-[var(--ds-sidebar-item-height)] lg:min-h-0",
677
+ "hover:bg-sidebar-hover hover:text-sidebar-primary-text",
678
+ "focus-visible:ring-[3px] focus-visible:ring-sidebar-focus-ring",
679
+ "disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-50",
665
680
  "group-data-[sidebar-state=collapsed]/shell:justify-center group-data-[sidebar-state=collapsed]/shell:px-0",
666
- "[&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:stroke-[1.75]",
667
- active && "bg-sidebar-active text-sidebar-active-foreground shadow-[inset_2px_0_0_var(--sidebar-active-indicator)] hover:bg-sidebar-active hover:text-sidebar-active-foreground"
681
+ "[&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:stroke-[1.75] [&>svg]:text-sidebar-icon",
682
+ active && "bg-sidebar-active text-sidebar-active-foreground shadow-[inset_3px_0_0_var(--sidebar-active-indicator)] hover:bg-sidebar-active hover:text-sidebar-active-foreground [&>svg]:text-sidebar-active-foreground"
668
683
  );
669
684
  }
670
685
  function NavigationItem({
@@ -718,10 +733,10 @@ function NavigationSubItem({
718
733
  "data-slot": "navigation-sub-item",
719
734
  "data-active": active ? "true" : void 0,
720
735
  className: cn(
721
- "flex h-11 min-w-0 items-center gap-2 rounded-[var(--ds-radius-control)] px-2 text-sm text-sidebar-foreground/72 outline-none transition-colors duration-150 lg:h-[calc(var(--ds-sidebar-item-height)-0.25rem)]",
722
- "hover:bg-sidebar-accent hover:text-sidebar-foreground focus-visible:ring-[3px] focus-visible:ring-ring/35",
723
- "group-data-[sidebar-state=collapsed]/shell:hidden [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:stroke-[1.75]",
724
- active && "bg-sidebar-active text-sidebar-active-foreground shadow-[inset_2px_0_0_var(--sidebar-active-indicator)] hover:bg-sidebar-active hover:text-sidebar-active-foreground",
736
+ "flex min-h-[var(--ds-sidebar-item-height-touch)] min-w-0 cursor-pointer items-center gap-2 rounded-[var(--ds-radius-control)] px-2 text-sm text-sidebar-secondary-text outline-none transition-colors duration-150 lg:h-[calc(var(--ds-sidebar-item-height)-0.25rem)] lg:min-h-0",
737
+ "hover:bg-sidebar-hover hover:text-sidebar-primary-text focus-visible:ring-[3px] focus-visible:ring-sidebar-focus-ring",
738
+ "group-data-[sidebar-state=collapsed]/shell:hidden [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:stroke-[1.75] [&>svg]:text-sidebar-icon",
739
+ active && "bg-sidebar-active text-sidebar-active-foreground shadow-[inset_3px_0_0_var(--sidebar-active-indicator)] hover:bg-sidebar-active hover:text-sidebar-active-foreground [&>svg]:text-sidebar-active-foreground",
725
740
  className
726
741
  ),
727
742
  ...props
@@ -749,7 +764,7 @@ function SidebarTrigger({
749
764
  onClick,
750
765
  ...props
751
766
  }) {
752
- const { toggleSidebar } = useAppShell();
767
+ const { isMobile, mobileOpen, sidebarOpen, toggleSidebar } = useAppShell();
753
768
  return /* @__PURE__ */ jsxs4(
754
769
  Button,
755
770
  {
@@ -757,7 +772,11 @@ function SidebarTrigger({
757
772
  type: "button",
758
773
  variant: "ghost",
759
774
  size: "icon",
760
- className: cn("shrink-0", className),
775
+ className: cn(
776
+ "shrink-0 cursor-pointer text-sidebar-secondary-text hover:bg-sidebar-hover hover:text-sidebar-primary-text focus-visible:ring-sidebar-focus-ring",
777
+ className
778
+ ),
779
+ "aria-expanded": isMobile ? mobileOpen : sidebarOpen,
761
780
  onClick: (event) => {
762
781
  onClick?.(event);
763
782
  toggleSidebar();
@@ -770,6 +789,104 @@ function SidebarTrigger({
770
789
  }
771
790
  );
772
791
  }
792
+ function SidebarSearch({
793
+ expanded: expandedProp,
794
+ defaultExpanded = false,
795
+ onExpandedChange,
796
+ triggerLabel = "Search navigation",
797
+ shortcut,
798
+ className,
799
+ onKeyDown,
800
+ ...props
801
+ }) {
802
+ const [expandedState, setExpandedState] = React2.useState(defaultExpanded);
803
+ const expanded = expandedProp ?? expandedState;
804
+ const inputRef = React2.useRef(null);
805
+ const triggerRef = React2.useRef(null);
806
+ const mountedRef = React2.useRef(false);
807
+ const setExpanded = React2.useCallback(
808
+ (nextExpanded) => {
809
+ if (expandedProp === void 0) {
810
+ setExpandedState(nextExpanded);
811
+ }
812
+ onExpandedChange?.(nextExpanded);
813
+ },
814
+ [expandedProp, onExpandedChange]
815
+ );
816
+ React2.useEffect(() => {
817
+ if (expanded) {
818
+ queueMicrotask(() => inputRef.current?.focus());
819
+ } else if (mountedRef.current) {
820
+ queueMicrotask(() => triggerRef.current?.focus());
821
+ }
822
+ mountedRef.current = true;
823
+ }, [expanded]);
824
+ if (!expanded) {
825
+ return /* @__PURE__ */ jsxs4(
826
+ "button",
827
+ {
828
+ ref: triggerRef,
829
+ type: "button",
830
+ "data-slot": "sidebar-search-trigger",
831
+ className: cn(
832
+ "flex min-h-[var(--ds-sidebar-item-height-touch)] w-full cursor-pointer items-center gap-2 rounded-[var(--ds-radius-control)] px-2 text-left text-sm font-medium text-sidebar-secondary-text outline-none transition-colors hover:bg-sidebar-hover hover:text-sidebar-primary-text focus-visible:ring-[3px] focus-visible:ring-sidebar-focus-ring lg:min-h-[var(--ds-sidebar-item-height)]",
833
+ "group-data-[sidebar-state=collapsed]/shell:justify-center group-data-[sidebar-state=collapsed]/shell:px-0",
834
+ className
835
+ ),
836
+ onClick: () => setExpanded(true),
837
+ "aria-label": triggerLabel,
838
+ children: [
839
+ /* @__PURE__ */ jsx5(Search, { className: "size-4 shrink-0 text-sidebar-icon" }),
840
+ /* @__PURE__ */ jsx5("span", { className: "truncate group-data-[sidebar-state=collapsed]/shell:hidden", children: triggerLabel }),
841
+ shortcut ? /* @__PURE__ */ jsx5("kbd", { className: "ml-auto rounded border border-sidebar-border px-1.5 py-0.5 text-[length:var(--ds-metadata)] font-medium text-sidebar-metadata-text group-data-[sidebar-state=collapsed]/shell:hidden", children: shortcut }) : null
842
+ ]
843
+ }
844
+ );
845
+ }
846
+ return /* @__PURE__ */ jsxs4(
847
+ "div",
848
+ {
849
+ "data-slot": "sidebar-search",
850
+ "data-expanded": "true",
851
+ className: cn(
852
+ "flex min-h-[var(--ds-sidebar-item-height-touch)] items-center gap-2 rounded-[var(--ds-radius-control)] border border-sidebar-border bg-sidebar-search px-2 text-sidebar-primary-text shadow-[var(--ds-shadow-raised)] lg:min-h-[var(--ds-sidebar-item-height)]",
853
+ className
854
+ ),
855
+ children: [
856
+ /* @__PURE__ */ jsx5(Search, { className: "size-4 shrink-0 text-sidebar-icon" }),
857
+ /* @__PURE__ */ jsx5(
858
+ Input,
859
+ {
860
+ ref: inputRef,
861
+ type: "search",
862
+ className: "h-auto min-w-0 flex-1 border-0 bg-transparent px-0 py-1 text-sm shadow-none focus-visible:border-0 focus-visible:ring-0",
863
+ onKeyDown: (event) => {
864
+ if (event.key === "Escape") {
865
+ event.preventDefault();
866
+ setExpanded(false);
867
+ }
868
+ onKeyDown?.(event);
869
+ },
870
+ ...props
871
+ }
872
+ ),
873
+ /* @__PURE__ */ jsx5(
874
+ "button",
875
+ {
876
+ type: "button",
877
+ "data-slot": "sidebar-search-close",
878
+ className: "grid size-7 shrink-0 cursor-pointer place-items-center rounded-[var(--ds-radius-control)] text-sidebar-secondary-text outline-none transition-colors hover:bg-sidebar-hover hover:text-sidebar-primary-text focus-visible:ring-2 focus-visible:ring-sidebar-focus-ring",
879
+ onClick: () => {
880
+ setExpanded(false);
881
+ },
882
+ "aria-label": "Close search",
883
+ children: /* @__PURE__ */ jsx5(X, { className: "size-4" })
884
+ }
885
+ )
886
+ ]
887
+ }
888
+ );
889
+ }
773
890
  function MainRegion({
774
891
  className,
775
892
  ...props
@@ -1727,6 +1844,7 @@ export {
1727
1844
  NavigationSubItem,
1728
1845
  NavigationSubList,
1729
1846
  SidebarTrigger,
1847
+ SidebarSearch,
1730
1848
  MainRegion,
1731
1849
  TopBar,
1732
1850
  PageFrame,
@@ -1761,4 +1879,4 @@ export {
1761
1879
  ErrorState,
1762
1880
  ValueMeter
1763
1881
  };
1764
- //# sourceMappingURL=chunk-5MGPU2JE.js.map
1882
+ //# sourceMappingURL=chunk-6VZDLHRF.js.map