@lateralus-ai/shipping-ui 2.0.0-dev.3 → 2.0.0-dev.30

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.
Files changed (98) hide show
  1. package/dist/components/Entry.d.ts +27 -7
  2. package/dist/components/PageHeader.d.ts +41 -0
  3. package/dist/components/ScrollableList.d.ts +22 -0
  4. package/dist/components/Tabs.d.ts +5 -5
  5. package/dist/components/index.d.ts +4 -2
  6. package/dist/domain/Filters/FilterDropdown.d.ts +119 -0
  7. package/dist/domain/Filters/FilterPill.d.ts +22 -5
  8. package/dist/domain/Filters/FilterPills.d.ts +18 -0
  9. package/dist/domain/Filters/FilteredPill.d.ts +15 -5
  10. package/dist/domain/Filters/formatActiveFilterChipLabel.d.ts +11 -0
  11. package/dist/domain/Filters/index.d.ts +8 -3
  12. package/dist/index.cjs +33 -33
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.esm.js +6383 -4792
  15. package/dist/patterns/Search/ResultRow.d.ts +11 -5
  16. package/dist/patterns/Sidebar/sidebar-styles.d.ts +2 -1
  17. package/dist/primitives/Badge.d.ts +7 -4
  18. package/dist/primitives/Button.d.ts +3 -1
  19. package/dist/primitives/Switch.d.ts +5 -0
  20. package/dist/primitives/index.d.ts +1 -1
  21. package/dist/tailwind-theme.d.ts +8 -4
  22. package/dist/theme-entry-Dwr2mV7_.js +1 -0
  23. package/dist/{theme-entry-CxDa1D0_.mjs → theme-entry-tLBc6zGT.mjs} +12 -8
  24. package/dist/theme.cjs +1 -1
  25. package/dist/theme.esm.js +1 -1
  26. package/package.json +3 -2
  27. package/src/components/EmptyState.tsx +44 -44
  28. package/src/components/Entry.tsx +119 -45
  29. package/src/components/Modal.tsx +141 -141
  30. package/src/components/PageHeader.tsx +132 -0
  31. package/src/components/ScrollableList.tsx +61 -0
  32. package/src/components/Tabs.tsx +33 -26
  33. package/src/components/index.ts +15 -1
  34. package/src/domain/Filters/FilterDropdown.tsx +540 -0
  35. package/src/domain/Filters/FilterPill.tsx +74 -25
  36. package/src/domain/Filters/FilterPills.tsx +34 -0
  37. package/src/domain/Filters/FilteredPill.tsx +59 -39
  38. package/src/domain/Filters/FiltersBar.tsx +72 -76
  39. package/src/domain/Filters/formatActiveFilterChipLabel.ts +23 -0
  40. package/src/domain/Filters/index.ts +37 -3
  41. package/src/icons/arrow-paths.ts +8 -8
  42. package/src/icons/chevron-paths.ts +17 -17
  43. package/src/icons/icons-data.ts +656 -656
  44. package/src/index.ts +11 -0
  45. package/src/patterns/Search/ResultRow.tsx +24 -38
  46. package/src/patterns/Search/SearchModal.tsx +26 -8
  47. package/src/patterns/Sidebar/CollapsibleNavGroup.tsx +1 -1
  48. package/src/patterns/Sidebar/SidebarAction.tsx +26 -17
  49. package/src/patterns/Sidebar/SidebarEntry.tsx +54 -39
  50. package/src/patterns/Sidebar/SidebarLink.tsx +30 -20
  51. package/src/patterns/Sidebar/assets/logo-compliance-mark.png +5 -5
  52. package/src/patterns/Sidebar/assets/logo-compliance-mask.png +13 -13
  53. package/src/patterns/Sidebar/assets/logo-compliance.svg +17 -17
  54. package/src/patterns/Sidebar/assets/logo-technical-avatar.svg +17 -17
  55. package/src/patterns/Sidebar/assets/logo-technical-mark.png +5 -5
  56. package/src/patterns/Sidebar/assets/logo-technical.svg +16 -16
  57. package/src/patterns/Sidebar/sidebar-styles.ts +2 -1
  58. package/src/primitives/Badge.tsx +20 -10
  59. package/src/primitives/Button.tsx +21 -11
  60. package/src/primitives/Logo.tsx +20 -8
  61. package/src/primitives/Switch.tsx +27 -7
  62. package/src/primitives/button-styles.ts +92 -92
  63. package/src/primitives/index.ts +1 -1
  64. package/src/stories/canvases/ButtonsCanvas.tsx +107 -107
  65. package/src/stories/canvases/ButtonsMatrixCanvas.tsx +65 -65
  66. package/src/stories/canvases/ContentCanvas.tsx +253 -14
  67. package/src/stories/canvases/FiltersCanvas.tsx +171 -41
  68. package/src/stories/canvases/figma-buttons-layout.ts +83 -83
  69. package/src/stories/canvases/figma-widths.ts +28 -28
  70. package/src/stories/canvases/helpers.tsx +146 -146
  71. package/src/stories/components/Buttons.stories.tsx +11 -11
  72. package/src/stories/components/Chat.stories.tsx +11 -11
  73. package/src/stories/components/Content.stories.tsx +11 -11
  74. package/src/stories/components/Core.stories.tsx +11 -11
  75. package/src/stories/components/DomainForms.stories.tsx +11 -11
  76. package/src/stories/components/Filters.stories.tsx +11 -11
  77. package/src/stories/components/Forms.stories.tsx +11 -11
  78. package/src/stories/components/Icons.stories.tsx +11 -11
  79. package/src/stories/components/Illustrations.stories.tsx +11 -11
  80. package/src/stories/components/Library.stories.tsx +11 -11
  81. package/src/stories/components/Modals.stories.tsx +11 -11
  82. package/src/stories/components/Report.stories.tsx +11 -11
  83. package/src/stories/components/ReportLayout.stories.tsx +11 -11
  84. package/src/stories/components/Search.stories.tsx +11 -11
  85. package/src/stories/components/Settings.stories.tsx +11 -11
  86. package/src/stories/components/Ships.stories.tsx +11 -11
  87. package/src/stories/components/SidebarLayouts.stories.tsx +11 -11
  88. package/src/stories/components/Skeletons.stories.tsx +11 -11
  89. package/src/stories/components/Workflows.stories.tsx +11 -11
  90. package/src/stories/style-guide/Buttons.stories.tsx +11 -11
  91. package/src/stories/style-guide/ColorTokens.stories.tsx +11 -11
  92. package/src/stories/style-guide/Colors.stories.tsx +11 -11
  93. package/src/stories/style-guide/RaiseLevels.stories.tsx +11 -11
  94. package/src/stories/style-guide/Typography.stories.tsx +11 -11
  95. package/src/tailwind-theme.ts +8 -4
  96. package/src/theme-entry.ts +2 -2
  97. package/src/utils/cn.ts +28 -1
  98. package/dist/theme-entry-D2X3Ptjf.js +0 -1
@@ -1,141 +1,141 @@
1
- import * as DialogPrimitive from "@radix-ui/react-dialog";
2
- import {
3
- forwardRef,
4
- type ComponentPropsWithoutRef,
5
- type ElementRef,
6
- type ReactNode,
7
- } from "react";
8
- import { cn } from "../utils/cn";
9
-
10
- export type ModalProps = ComponentPropsWithoutRef<typeof DialogPrimitive.Root>;
11
-
12
- /** Generic dialog shell — portal, backdrop, focus trap, Escape. */
13
- export const Modal = DialogPrimitive.Root;
14
-
15
- export const ModalTrigger = DialogPrimitive.Trigger;
16
-
17
- export const ModalClose = DialogPrimitive.Close;
18
-
19
- export type ModalPortalProps = ComponentPropsWithoutRef<
20
- typeof DialogPrimitive.Portal
21
- >;
22
-
23
- export const ModalPortal = DialogPrimitive.Portal;
24
-
25
- export type ModalOverlayProps = ComponentPropsWithoutRef<
26
- typeof DialogPrimitive.Overlay
27
- >;
28
-
29
- /** Dark gray backdrop (app convention; Figma search frames omit overlay). */
30
- export const ModalOverlay = forwardRef<
31
- ElementRef<typeof DialogPrimitive.Overlay>,
32
- ModalOverlayProps
33
- >(({ className, ...props }, ref) => (
34
- <DialogPrimitive.Overlay
35
- ref={ref}
36
- className={cn(
37
- "fixed inset-0 z-[999] bg-grey-900/65 transition-opacity",
38
- "data-[state=open]:animate-in data-[state=open]:fade-in",
39
- "data-[state=closed]:animate-out data-[state=closed]:fade-out",
40
- className,
41
- )}
42
- {...props}
43
- />
44
- ));
45
- ModalOverlay.displayName = DialogPrimitive.Overlay.displayName;
46
-
47
- export type ModalContentProps = ComponentPropsWithoutRef<
48
- typeof DialogPrimitive.Content
49
- > & {
50
- /** When false, backdrop is omitted. Defaults to true. */
51
- showOverlay?: boolean;
52
- overlayClassName?: string;
53
- };
54
-
55
- export const ModalContent = forwardRef<
56
- ElementRef<typeof DialogPrimitive.Content>,
57
- ModalContentProps
58
- >(
59
- (
60
- {
61
- className,
62
- children,
63
- showOverlay = true,
64
- overlayClassName,
65
- ...props
66
- },
67
- ref,
68
- ) => (
69
- <ModalPortal>
70
- {showOverlay && <ModalOverlay className={overlayClassName} />}
71
- <DialogPrimitive.Content
72
- ref={ref}
73
- className={cn(
74
- "fixed left-1/2 top-1/2 z-[999] w-full max-w-xl -translate-x-1/2 -translate-y-1/2",
75
- "overflow-hidden rounded-xl border border-divider-primary bg-background-primary shadow-raise3",
76
- "outline-none",
77
- "data-[state=open]:animate-in data-[state=open]:fade-in-90",
78
- "data-[state=closed]:animate-out data-[state=closed]:fade-out",
79
- className,
80
- )}
81
- {...props}
82
- >
83
- {children}
84
- </DialogPrimitive.Content>
85
- </ModalPortal>
86
- ),
87
- );
88
- ModalContent.displayName = DialogPrimitive.Content.displayName;
89
-
90
- export type ModalTitleProps = ComponentPropsWithoutRef<
91
- typeof DialogPrimitive.Title
92
- >;
93
-
94
- export const ModalTitle = forwardRef<
95
- ElementRef<typeof DialogPrimitive.Title>,
96
- ModalTitleProps
97
- >(({ className, ...props }, ref) => (
98
- <DialogPrimitive.Title
99
- ref={ref}
100
- className={cn("text-subheader-em text-display-on-light-primary", className)}
101
- {...props}
102
- />
103
- ));
104
- ModalTitle.displayName = DialogPrimitive.Title.displayName;
105
-
106
- export type ModalDescriptionProps = ComponentPropsWithoutRef<
107
- typeof DialogPrimitive.Description
108
- >;
109
-
110
- export const ModalDescription = forwardRef<
111
- ElementRef<typeof DialogPrimitive.Description>,
112
- ModalDescriptionProps
113
- >(({ className, ...props }, ref) => (
114
- <DialogPrimitive.Description
115
- ref={ref}
116
- className={cn("text-caption-2 text-display-on-light-secondary", className)}
117
- {...props}
118
- />
119
- ));
120
- ModalDescription.displayName = DialogPrimitive.Description.displayName;
121
-
122
- export type ModalHeaderSlotProps = {
123
- children: ReactNode;
124
- className?: string;
125
- };
126
-
127
- export const ModalHeaderSlot = ({
128
- children,
129
- className,
130
- }: ModalHeaderSlotProps) => (
131
- <div className={cn("shrink-0", className)}>{children}</div>
132
- );
133
-
134
- export type ModalBodyProps = {
135
- children: ReactNode;
136
- className?: string;
137
- };
138
-
139
- export const ModalBody = ({ children, className }: ModalBodyProps) => (
140
- <div className={cn("min-h-0 overflow-auto", className)}>{children}</div>
141
- );
1
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
2
+ import {
3
+ forwardRef,
4
+ type ComponentPropsWithoutRef,
5
+ type ElementRef,
6
+ type ReactNode,
7
+ } from "react";
8
+ import { cn } from "../utils/cn";
9
+
10
+ export type ModalProps = ComponentPropsWithoutRef<typeof DialogPrimitive.Root>;
11
+
12
+ /** Generic dialog shell — portal, backdrop, focus trap, Escape. */
13
+ export const Modal = DialogPrimitive.Root;
14
+
15
+ export const ModalTrigger = DialogPrimitive.Trigger;
16
+
17
+ export const ModalClose = DialogPrimitive.Close;
18
+
19
+ export type ModalPortalProps = ComponentPropsWithoutRef<
20
+ typeof DialogPrimitive.Portal
21
+ >;
22
+
23
+ export const ModalPortal = DialogPrimitive.Portal;
24
+
25
+ export type ModalOverlayProps = ComponentPropsWithoutRef<
26
+ typeof DialogPrimitive.Overlay
27
+ >;
28
+
29
+ /** Dark gray backdrop (app convention; Figma search frames omit overlay). */
30
+ export const ModalOverlay = forwardRef<
31
+ ElementRef<typeof DialogPrimitive.Overlay>,
32
+ ModalOverlayProps
33
+ >(({ className, ...props }, ref) => (
34
+ <DialogPrimitive.Overlay
35
+ ref={ref}
36
+ className={cn(
37
+ "fixed inset-0 z-[999] bg-grey-900/65 transition-opacity",
38
+ "data-[state=open]:animate-in data-[state=open]:fade-in",
39
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out",
40
+ className,
41
+ )}
42
+ {...props}
43
+ />
44
+ ));
45
+ ModalOverlay.displayName = DialogPrimitive.Overlay.displayName;
46
+
47
+ export type ModalContentProps = ComponentPropsWithoutRef<
48
+ typeof DialogPrimitive.Content
49
+ > & {
50
+ /** When false, backdrop is omitted. Defaults to true. */
51
+ showOverlay?: boolean;
52
+ overlayClassName?: string;
53
+ };
54
+
55
+ export const ModalContent = forwardRef<
56
+ ElementRef<typeof DialogPrimitive.Content>,
57
+ ModalContentProps
58
+ >(
59
+ (
60
+ {
61
+ className,
62
+ children,
63
+ showOverlay = true,
64
+ overlayClassName,
65
+ ...props
66
+ },
67
+ ref,
68
+ ) => (
69
+ <ModalPortal>
70
+ {showOverlay && <ModalOverlay className={overlayClassName} />}
71
+ <DialogPrimitive.Content
72
+ ref={ref}
73
+ className={cn(
74
+ "fixed left-1/2 top-1/2 z-[999] w-full max-w-xl -translate-x-1/2 -translate-y-1/2",
75
+ "overflow-hidden rounded-xl border border-divider-primary bg-background-primary shadow-raise3",
76
+ "outline-none",
77
+ "data-[state=open]:animate-in data-[state=open]:fade-in-90",
78
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out",
79
+ className,
80
+ )}
81
+ {...props}
82
+ >
83
+ {children}
84
+ </DialogPrimitive.Content>
85
+ </ModalPortal>
86
+ ),
87
+ );
88
+ ModalContent.displayName = DialogPrimitive.Content.displayName;
89
+
90
+ export type ModalTitleProps = ComponentPropsWithoutRef<
91
+ typeof DialogPrimitive.Title
92
+ >;
93
+
94
+ export const ModalTitle = forwardRef<
95
+ ElementRef<typeof DialogPrimitive.Title>,
96
+ ModalTitleProps
97
+ >(({ className, ...props }, ref) => (
98
+ <DialogPrimitive.Title
99
+ ref={ref}
100
+ className={cn("text-subheader-em text-display-on-light-primary", className)}
101
+ {...props}
102
+ />
103
+ ));
104
+ ModalTitle.displayName = DialogPrimitive.Title.displayName;
105
+
106
+ export type ModalDescriptionProps = ComponentPropsWithoutRef<
107
+ typeof DialogPrimitive.Description
108
+ >;
109
+
110
+ export const ModalDescription = forwardRef<
111
+ ElementRef<typeof DialogPrimitive.Description>,
112
+ ModalDescriptionProps
113
+ >(({ className, ...props }, ref) => (
114
+ <DialogPrimitive.Description
115
+ ref={ref}
116
+ className={cn("text-caption-2 text-display-on-light-secondary", className)}
117
+ {...props}
118
+ />
119
+ ));
120
+ ModalDescription.displayName = DialogPrimitive.Description.displayName;
121
+
122
+ export type ModalHeaderSlotProps = {
123
+ children: ReactNode;
124
+ className?: string;
125
+ };
126
+
127
+ export const ModalHeaderSlot = ({
128
+ children,
129
+ className,
130
+ }: ModalHeaderSlotProps) => (
131
+ <div className={cn("shrink-0", className)}>{children}</div>
132
+ );
133
+
134
+ export type ModalBodyProps = {
135
+ children: ReactNode;
136
+ className?: string;
137
+ };
138
+
139
+ export const ModalBody = ({ children, className }: ModalBodyProps) => (
140
+ <div className={cn("min-h-0 overflow-auto", className)}>{children}</div>
141
+ );
@@ -0,0 +1,132 @@
1
+ import {
2
+ type ComponentPropsWithoutRef,
3
+ type MouseEvent,
4
+ type ReactNode,
5
+ } from "react";
6
+ import { cn } from "../utils/cn";
7
+
8
+ export type PageHeaderCrumb = {
9
+ label: string;
10
+ /** Renders a real `<a href>` so native browser controls work. */
11
+ href: string;
12
+ /**
13
+ * Optional SPA handler. Callers should `preventDefault` only for unmodified
14
+ * primary clicks; modifier / middle-click keep native anchor behavior.
15
+ */
16
+ onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
17
+ };
18
+
19
+ export type PageHeaderProps = {
20
+ /** Leading icon (Figma 24×24). */
21
+ icon?: ReactNode;
22
+ /** Standard title, or current (last) segment when `crumbs` is set. */
23
+ title: ReactNode;
24
+ /**
25
+ * Parent breadcrumb links. When present, renders Nested: icon + parents + `/`
26
+ * + current `title` (non-link).
27
+ */
28
+ crumbs?: PageHeaderCrumb[];
29
+ /** Right-hand actions slot (buttons, menus, etc.). */
30
+ actions?: ReactNode;
31
+ className?: string;
32
+ };
33
+
34
+ export type PageHeaderShellProps = ComponentPropsWithoutRef<"div">;
35
+
36
+ export type PageHeaderBodyProps = ComponentPropsWithoutRef<"div">;
37
+
38
+ /**
39
+ * Page title row — Figma `Header` (389:8850).
40
+ * Variants: Standard (icon + title) | Nested (icon + crumb trail).
41
+ */
42
+ function PageHeaderRoot({
43
+ icon,
44
+ title,
45
+ crumbs,
46
+ actions,
47
+ className,
48
+ }: PageHeaderProps) {
49
+ const isNested = Boolean(crumbs?.length);
50
+
51
+ return (
52
+ <div
53
+ data-variant={isNested ? "nested" : "standard"}
54
+ className={cn(
55
+ "flex w-full shrink-0 items-center justify-between gap-4",
56
+ "min-h-10",
57
+ className,
58
+ )}
59
+ >
60
+ <div className="flex min-w-0 flex-1 items-center gap-2">
61
+ {icon != null && (
62
+ <span className="inline-flex size-6 shrink-0 items-center justify-center [&>svg]:size-6">
63
+ {icon}
64
+ </span>
65
+ )}
66
+
67
+ {isNested ? (
68
+ <nav aria-label="Breadcrumb" className="min-w-0">
69
+ <ol className="flex min-w-0 flex-wrap items-center gap-2">
70
+ {crumbs!.map((crumb) => (
71
+ <li key={`${crumb.href}-${crumb.label}`} className="contents">
72
+ <a
73
+ href={crumb.href}
74
+ onClick={crumb.onClick}
75
+ className="truncate text-caption-1-em text-display-on-light-secondary transition-colors hover:text-display-on-light-primary"
76
+ >
77
+ {crumb.label}
78
+ </a>
79
+ <span
80
+ aria-hidden
81
+ className="text-caption-1-em text-display-on-light-secondary"
82
+ >
83
+ /
84
+ </span>
85
+ </li>
86
+ ))}
87
+ <li
88
+ aria-current="page"
89
+ className="min-w-0 truncate text-caption-1-em text-display-on-light-primary"
90
+ >
91
+ {title}
92
+ </li>
93
+ </ol>
94
+ </nav>
95
+ ) : (
96
+ <h1 className="min-w-0 truncate text-caption-1-em text-display-on-light-primary">
97
+ {title}
98
+ </h1>
99
+ )}
100
+ </div>
101
+
102
+ {actions != null && (
103
+ <div className="flex shrink-0 items-center gap-2">{actions}</div>
104
+ )}
105
+ </div>
106
+ );
107
+ }
108
+
109
+ /** Flex column shell — pin header, scroll body (avoids brittle CSS sticky). */
110
+ function PageHeaderShell({ className, ...props }: PageHeaderShellProps) {
111
+ return (
112
+ <div
113
+ className={cn("flex h-full min-h-0 flex-col", className)}
114
+ {...props}
115
+ />
116
+ );
117
+ }
118
+
119
+ /** Scrollable page content under a pinned PageHeader. */
120
+ function PageHeaderBody({ className, ...props }: PageHeaderBodyProps) {
121
+ return (
122
+ <div
123
+ className={cn("min-h-0 flex-1 overflow-y-auto", className)}
124
+ {...props}
125
+ />
126
+ );
127
+ }
128
+
129
+ export const PageHeader = Object.assign(PageHeaderRoot, {
130
+ Shell: PageHeaderShell,
131
+ Body: PageHeaderBody,
132
+ });
@@ -0,0 +1,61 @@
1
+ import { type ComponentPropsWithoutRef } from "react";
2
+ import { cn } from "../utils/cn";
3
+
4
+ export type ScrollableListProps = ComponentPropsWithoutRef<"div">;
5
+ export type ScrollableListHeaderProps = ComponentPropsWithoutRef<"div">;
6
+ export type ScrollableListBodyProps = ComponentPropsWithoutRef<"div">;
7
+
8
+ /**
9
+ * Fill-height list chrome — Figma `Table` (6154:152285).
10
+ * Outer grey-100 frame + header slot + internally scrolling grey-50 body.
11
+ * Column layout and rows are caller-owned children.
12
+ */
13
+ function ScrollableListRoot({ className, ...props }: ScrollableListProps) {
14
+ return (
15
+ <div
16
+ className={cn(
17
+ "flex min-h-0 flex-1 flex-col gap-2 overflow-hidden rounded-lg bg-grey-100 p-1",
18
+ className,
19
+ )}
20
+ {...props}
21
+ />
22
+ );
23
+ }
24
+
25
+ /**
26
+ * Column title bar. Default left padding aligns with a 24px leading icon +
27
+ * 8px gap + 8px row padding (Figma). Override `className` when rows have no icon.
28
+ */
29
+ function ScrollableListHeader({
30
+ className,
31
+ ...props
32
+ }: ScrollableListHeaderProps) {
33
+ return (
34
+ <div
35
+ className={cn(
36
+ "flex shrink-0 items-start gap-8 pl-10 pr-2 py-1",
37
+ "text-caption-2-em text-display-on-light-secondary",
38
+ className,
39
+ )}
40
+ {...props}
41
+ />
42
+ );
43
+ }
44
+
45
+ /** Internally scrolling row region. */
46
+ function ScrollableListBody({ className, ...props }: ScrollableListBodyProps) {
47
+ return (
48
+ <div
49
+ className={cn(
50
+ "flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto rounded bg-grey-50 p-1",
51
+ className,
52
+ )}
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+
58
+ export const ScrollableList = Object.assign(ScrollableListRoot, {
59
+ Header: ScrollableListHeader,
60
+ Body: ScrollableListBody,
61
+ });
@@ -11,26 +11,37 @@ import { cn } from "../utils/cn";
11
11
 
12
12
  export type TabsType = "tabs" | "pills";
13
13
 
14
+ /** Soft = search filters (filled idle, light accent active). Solid = list filters (grey-100 idle, blue-600 active). */
15
+ export type TabsAppearance = "soft" | "solid";
16
+
14
17
  type TabsContextValue = {
15
18
  type: TabsType;
19
+ appearance: TabsAppearance;
16
20
  };
17
21
 
18
- const TabsContext = createContext<TabsContextValue>({ type: "tabs" });
22
+ const TabsContext = createContext<TabsContextValue>({
23
+ type: "tabs",
24
+ appearance: "soft",
25
+ });
19
26
 
20
27
  export type TabsProps = ComponentPropsWithoutRef<typeof TabsPrimitive.Root> & {
21
28
  type?: TabsType;
29
+ /** Only applies when `type="pills"`. Defaults to `soft`. */
30
+ appearance?: TabsAppearance;
22
31
  };
23
32
 
24
33
  export const Tabs = ({
25
34
  type = "tabs",
35
+ appearance = "soft",
26
36
  className,
27
37
  children,
28
38
  ...props
29
39
  }: TabsProps) => (
30
- <TabsContext.Provider value={{ type }}>
40
+ <TabsContext.Provider value={{ type, appearance }}>
31
41
  <TabsPrimitive.Root
32
42
  className={cn("flex flex-col", className)}
33
43
  data-type={type}
44
+ data-appearance={type === "pills" ? appearance : undefined}
34
45
  {...props}
35
46
  >
36
47
  {children}
@@ -51,7 +62,7 @@ export const TabsList = forwardRef<
51
62
  ref={ref}
52
63
  className={cn(
53
64
  "flex items-center",
54
- type === "tabs" && "gap-1 border-b border-divider-primary",
65
+ type === "tabs" && "gap-6 border-b border-divider-primary",
55
66
  type === "pills" && "gap-2",
56
67
  className,
57
68
  )}
@@ -64,52 +75,48 @@ TabsList.displayName = TabsPrimitive.List.displayName;
64
75
  export type TabsTriggerProps = ComponentPropsWithoutRef<
65
76
  typeof TabsPrimitive.Trigger
66
77
  > & {
67
- /** Optional count badge (pill variant / search filters). */
68
- count?: number;
69
78
  children: ReactNode;
70
79
  };
71
80
 
72
81
  export const TabsTrigger = forwardRef<
73
82
  ElementRef<typeof TabsPrimitive.Trigger>,
74
83
  TabsTriggerProps
75
- >(({ className, count, children, ...props }, ref) => {
76
- const { type } = useContext(TabsContext);
84
+ >(({ className, children, ...props }, ref) => {
85
+ const { type, appearance } = useContext(TabsContext);
77
86
 
78
87
  return (
79
88
  <TabsPrimitive.Trigger
80
89
  ref={ref}
81
90
  className={cn(
82
- "inline-flex items-center justify-center transition-colors outline-none",
91
+ "inline-flex items-center justify-center gap-2.5 transition-colors outline-none",
83
92
  "disabled:pointer-events-none disabled:opacity-50",
84
93
  type === "tabs" && [
85
- "relative px-3 py-2 text-caption-2 text-display-on-light-secondary",
94
+ "relative h-12 gap-2 px-0 py-0 text-caption-2 text-display-on-light-secondary",
86
95
  "hover:text-display-on-light-primary",
87
96
  "data-[state=active]:text-caption-2-em data-[state=active]:text-display-on-light-primary",
88
97
  "data-[state=active]:after:absolute data-[state=active]:after:inset-x-0 data-[state=active]:after:-bottom-px",
89
98
  "data-[state=active]:after:h-0.5 data-[state=active]:after:rounded-full data-[state=active]:after:bg-grey-900",
90
99
  ],
91
- type === "pills" && [
92
- "min-h-9 gap-2.5 rounded-full px-3 py-1 text-caption-2-em",
93
- "bg-grey-100 text-display-on-light-secondary",
94
- "hover:bg-grey-200",
95
- "data-[state=active]:bg-accent-bg-light data-[state=active]:text-display-on-light-primary",
96
- ],
100
+ type === "pills" &&
101
+ appearance === "soft" && [
102
+ "min-h-9 rounded-full px-3 py-1 text-caption-2-em",
103
+ "bg-grey-100 text-display-on-light-secondary",
104
+ "hover:bg-grey-200",
105
+ "data-[state=active]:bg-accent-bg-light data-[state=active]:text-display-on-light-primary",
106
+ ],
107
+ type === "pills" &&
108
+ appearance === "solid" && [
109
+ "min-h-9 rounded-full px-3 py-1 text-caption-2-em",
110
+ "bg-grey-100 text-display-on-light-tertiary",
111
+ "hover:bg-grey-200 hover:text-display-on-light-primary",
112
+ "data-[state=active]:bg-blue-600 data-[state=active]:text-white",
113
+ "data-[state=active]:hover:bg-blue-600",
114
+ ],
97
115
  className,
98
116
  )}
99
117
  {...props}
100
118
  >
101
119
  {children}
102
- {typeof count === "number" && (
103
- <span
104
- className={cn(
105
- "inline-flex size-6 shrink-0 items-center justify-center rounded-full text-footnote-em",
106
- "bg-accent-bg-light text-display-on-light-tertiary",
107
- )}
108
- aria-label={`Count: ${count}`}
109
- >
110
- {count > 99 ? "99+" : count}
111
- </span>
112
- )}
113
120
  </TabsPrimitive.Trigger>
114
121
  );
115
122
  });
@@ -15,9 +15,23 @@ export {
15
15
  type TabsContentProps,
16
16
  type TabsItem,
17
17
  type TabsType,
18
+ type TabsAppearance,
18
19
  } from "./Tabs";
19
20
  export { Header, type HeaderProps, type HeaderVariant } from "./Header";
20
- export { Entry, type EntryProps, type EntryType, type EntryState } from "./Entry";
21
+ export {
22
+ PageHeader,
23
+ type PageHeaderProps,
24
+ type PageHeaderCrumb,
25
+ type PageHeaderShellProps,
26
+ type PageHeaderBodyProps,
27
+ } from "./PageHeader";
28
+ export {
29
+ ScrollableList,
30
+ type ScrollableListProps,
31
+ type ScrollableListHeaderProps,
32
+ type ScrollableListBodyProps,
33
+ } from "./ScrollableList";
34
+ export { Entry, type EntryProps, type EntryType, type EntryState, type EntryVariant } from "./Entry";
21
35
  export { EmptyState, type EmptyStateProps } from "./EmptyState";
22
36
  export {
23
37
  Modal,