@lessly/ui 0.4.0 → 0.5.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 +29 -12
- package/dist/index.d.ts +76 -2
- package/dist/index.js +223 -33
- package/dist/styles.css +690 -102
- package/dist/tailwind-preset.d.ts +4 -393
- package/dist/tailwind-preset.js +4 -424
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -6,20 +6,25 @@ Module Federation host provides to federation extension remotes at runtime, and
|
|
|
6
6
|
it is published to the public npm registry (npmjs.com).
|
|
7
7
|
|
|
8
8
|
> **Contract:** the package name (`@lessly/ui`), its exports (`.`,
|
|
9
|
-
> `./tailwind-preset`, `./styles.css`), and its
|
|
10
|
-
>
|
|
11
|
-
> host and every federation
|
|
9
|
+
> `./tailwind-preset`, `./styles.css`), and its peer dependencies (React 19,
|
|
10
|
+
> react-router 7, optional `tailwindcss >=3.4`) are a cross-project contract. Do
|
|
11
|
+
> not change them without coordinating the workspace host and every federation
|
|
12
|
+
> remote.
|
|
12
13
|
|
|
13
14
|
## What's inside
|
|
14
15
|
|
|
15
|
-
- **Components:**
|
|
16
|
-
Select,
|
|
16
|
+
- **Components:** shadcn/radix primitives themed by Lessly tokens (Button,
|
|
17
|
+
Dialog, Select, Tabs, …) plus layout: `Grid` / `Col` (the adaptive 4 / 8 / 12
|
|
18
|
+
column grid) and `GridOverlay` (a design-aid overlay). The full catalog lives
|
|
19
|
+
in Storybook at [ui.lessly.com](https://ui.lessly.com).
|
|
17
20
|
- **Theme:** `useTheme` hook + `ThemeToggle` (toggles the `light` class on
|
|
18
21
|
`<html>`; `:root` is dark, `.light` is light).
|
|
19
|
-
- **Tokens:** `tailwind-preset`
|
|
20
|
-
`
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
- **Tokens:** the `tailwind-preset` re-exports the token vocabulary from
|
|
23
|
+
[`@lessly/tokens`](https://www.npmjs.com/package/@lessly/tokens) (generated
|
|
24
|
+
upstream in `lessly-hub/design-system`) with `darkMode: 'class'` added, and
|
|
25
|
+
`styles.css` bundles the package's `theme.css` (CSS variables, `.light`
|
|
26
|
+
overrides, typography classes) plus this library's fonts and base layer.
|
|
27
|
+
Token values are never edited here — they change by bumping `@lessly/tokens`.
|
|
23
28
|
|
|
24
29
|
## Install
|
|
25
30
|
|
|
@@ -37,13 +42,25 @@ import { Button, useTheme } from '@lessly/ui';
|
|
|
37
42
|
import '@lessly/ui/styles.css';
|
|
38
43
|
```
|
|
39
44
|
|
|
40
|
-
Your app's Tailwind config must use the preset so token utilities resolve
|
|
45
|
+
Your app's Tailwind config must use the preset so token utilities resolve. The
|
|
46
|
+
preset needs Tailwind CSS >= 3.4 (declared as an optional peer dependency —
|
|
47
|
+
only required if you consume the preset):
|
|
41
48
|
|
|
42
49
|
```ts
|
|
43
50
|
import { lesslyPreset } from '@lessly/ui/tailwind-preset';
|
|
44
|
-
export default {
|
|
51
|
+
export default {
|
|
52
|
+
presets: [lesslyPreset],
|
|
53
|
+
// Scan the library too — its components (Grid, Col, …) use literal token
|
|
54
|
+
// utilities (`md:col-span-5`, `gap-gutter`) that Tailwind must see to generate.
|
|
55
|
+
content: ['./src/**/*.{ts,tsx}', './node_modules/@lessly/ui/dist/**/*.js'],
|
|
56
|
+
};
|
|
45
57
|
```
|
|
46
58
|
|
|
59
|
+
CSS variables use the upstream unprefixed names (`--bg-primary`,
|
|
60
|
+
`--text-primary`, …). The old `--color-*` names still resolve through a
|
|
61
|
+
deprecated alias layer in `styles.css` and will be removed in the next major —
|
|
62
|
+
migrate any direct `var(--color-*)` reads.
|
|
63
|
+
|
|
47
64
|
## Storybook
|
|
48
65
|
|
|
49
66
|
```bash
|
|
@@ -65,7 +82,7 @@ npm package published to npmjs always contains the package, never the Storybook
|
|
|
65
82
|
|
|
66
83
|
| Script | Description |
|
|
67
84
|
| --- | --- |
|
|
68
|
-
| `pnpm build` | tsup package build → `dist/` (+
|
|
85
|
+
| `pnpm build` | tsup package build → `dist/` (+ assembles `styles.css` from the @lessly/tokens theme, deprecated `--color-*` aliases, ui-local pieces, and the animation layer) — the npm package published to npmjs |
|
|
69
86
|
| `pnpm build-storybook` | Storybook static site → `storybook-static/` — what the static-service pipeline deploys to `ui.lessly.com` |
|
|
70
87
|
| `pnpm storybook` | Storybook dev server on `http://localhost:6006` |
|
|
71
88
|
| `pnpm test` | Vitest |
|
package/dist/index.d.ts
CHANGED
|
@@ -848,6 +848,73 @@ declare function AppShell({ sidebar, topBar, children, className }: AppShellProp
|
|
|
848
848
|
/** Alias of AppShell — reads better in app router files. */
|
|
849
849
|
declare const AuthenticatedLayout: typeof AppShell;
|
|
850
850
|
|
|
851
|
+
/**
|
|
852
|
+
* Grid — the adaptive 4 / 8 / 12 column container. Column count switches at the
|
|
853
|
+
* token breakpoints (4 below md, 8 at md/768, 12 at xl/1280). Gutter, side
|
|
854
|
+
* margins and the max-width are tokenised: `gap-gutter`, `px-grid-*` and
|
|
855
|
+
* `max-w-container` are var-backed by @lessly/tokens theme.css (--grid-*).
|
|
856
|
+
* Place <Col> children inside.
|
|
857
|
+
*
|
|
858
|
+
* The grid owns the container — it centres and caps content at the container
|
|
859
|
+
* token (1440) by default. Set `fluid` for a full-bleed row that keeps the
|
|
860
|
+
* gutter and side margins but drops the max-width cap.
|
|
861
|
+
*
|
|
862
|
+
* Note: the token utilities are custom theme keys tailwind-merge doesn't
|
|
863
|
+
* classify, so a `className` like `px-6` or `gap-2` does NOT replace them —
|
|
864
|
+
* both classes are emitted and CSS source order decides. Don't override the
|
|
865
|
+
* grid's spacing via className; use `fluid` or wrap the grid instead.
|
|
866
|
+
*/
|
|
867
|
+
interface GridProps extends React$1.HTMLAttributes<HTMLDivElement> {
|
|
868
|
+
/** Drop the max-width cap for a full-bleed row (gutter + margins stay). */
|
|
869
|
+
fluid?: boolean;
|
|
870
|
+
/** Render as the child element via Radix Slot instead of a div. */
|
|
871
|
+
asChild?: boolean;
|
|
872
|
+
}
|
|
873
|
+
declare const Grid: React$1.ForwardRefExoticComponent<GridProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Col — a child of <Grid>. Spans columns of the adaptive 4 / 8 / 12 track using
|
|
877
|
+
* literal `col-span-*` / `col-start-*` classes. Pure CSS: no context, no
|
|
878
|
+
* measurement.
|
|
879
|
+
*
|
|
880
|
+
* Spans and offsets are expressed as literal Tailwind classes (never CSS
|
|
881
|
+
* variables), so the values are visible to Tailwind's content scanner. The
|
|
882
|
+
* lookup maps below hold every class as a verbatim string for that reason — do
|
|
883
|
+
* NOT rebuild them with template literals (`col-span-${n}`), which the JIT would
|
|
884
|
+
* never see and so would never generate.
|
|
885
|
+
*/
|
|
886
|
+
/** Constant across tiers (number) or set per breakpoint ({ base, md, xl }). */
|
|
887
|
+
type Responsive = number | {
|
|
888
|
+
base?: number;
|
|
889
|
+
md?: number;
|
|
890
|
+
xl?: number;
|
|
891
|
+
};
|
|
892
|
+
interface ColProps extends React$1.HTMLAttributes<HTMLDivElement> {
|
|
893
|
+
/**
|
|
894
|
+
* Columns to span. Number = same on every tier, clamped to each tier's column
|
|
895
|
+
* count (with a dev warning past a tier's max — express full width as
|
|
896
|
+
* `{ base: 4, md: 8, xl: 12 }` instead of `12`); object = per breakpoint.
|
|
897
|
+
*/
|
|
898
|
+
span?: Responsive;
|
|
899
|
+
/**
|
|
900
|
+
* Leading empty columns (→ col-start). Number or per-breakpoint object.
|
|
901
|
+
* Clamped per tier (with a dev warning) so offset + span stay inside the
|
|
902
|
+
* tier's track instead of forcing an implicit column.
|
|
903
|
+
*/
|
|
904
|
+
offset?: Responsive;
|
|
905
|
+
/** Render as the child element via Radix Slot instead of a div. */
|
|
906
|
+
asChild?: boolean;
|
|
907
|
+
}
|
|
908
|
+
declare const Col: React$1.ForwardRefExoticComponent<ColProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
909
|
+
|
|
910
|
+
interface GridOverlayProps extends React$1.HTMLAttributes<HTMLDivElement> {
|
|
911
|
+
/** Number of columns to paint (e.g. 4, 8 or 12). Fixed, viewport-independent. */
|
|
912
|
+
columns: number;
|
|
913
|
+
/** Also paint the baseline rhythm (horizontal lines) behind the columns. */
|
|
914
|
+
baseline?: boolean;
|
|
915
|
+
}
|
|
916
|
+
declare const GridOverlay: React$1.ForwardRefExoticComponent<GridOverlayProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
917
|
+
|
|
851
918
|
declare const segmentChipVariants: (props?: ({
|
|
852
919
|
kind?: "granted" | "own" | "pending" | "available" | null | undefined;
|
|
853
920
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
@@ -864,7 +931,7 @@ declare const SegmentChip: React$1.ForwardRefExoticComponent<SegmentChipProps &
|
|
|
864
931
|
type ConnectionScope = 'org' | 'org/product' | 'product';
|
|
865
932
|
type ConnectionAuth = 'app' | 'oauth' | 'token';
|
|
866
933
|
type ConnectionStatus = 'connected' | 'error' | 'disconnected';
|
|
867
|
-
interface ConnectionCardProps extends
|
|
934
|
+
interface ConnectionCardProps extends React$1.HTMLAttributes<HTMLDivElement> {
|
|
868
935
|
/** Display name, e.g. "GitHub — apliteni". */
|
|
869
936
|
name: string;
|
|
870
937
|
scope: ConnectionScope;
|
|
@@ -885,6 +952,13 @@ interface ConnectionCardProps extends Omit<React$1.HTMLAttributes<HTMLDivElement
|
|
|
885
952
|
* a warning notice is shown, e.g. "Full org access — all repositories."
|
|
886
953
|
*/
|
|
887
954
|
access?: React$1.ReactNode;
|
|
955
|
+
/**
|
|
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.
|
|
960
|
+
*/
|
|
961
|
+
children?: React$1.ReactNode;
|
|
888
962
|
}
|
|
889
963
|
declare const ConnectionCard: React$1.ForwardRefExoticComponent<ConnectionCardProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
890
964
|
|
|
@@ -912,4 +986,4 @@ interface RequestRowProps extends Omit<React$1.HTMLAttributes<HTMLDivElement>, '
|
|
|
912
986
|
}
|
|
913
987
|
declare const RequestRow: React$1.ForwardRefExoticComponent<RequestRowProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
914
988
|
|
|
915
|
-
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, 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, 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, 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 };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -3069,17 +3069,203 @@ function AppShell({ sidebar, topBar, children, className }) {
|
|
|
3069
3069
|
}
|
|
3070
3070
|
var AuthenticatedLayout = AppShell;
|
|
3071
3071
|
|
|
3072
|
-
// src/components/
|
|
3072
|
+
// src/components/grid.tsx
|
|
3073
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(
|
|
3077
|
+
({ fluid = false, asChild = false, className, ...props }, ref) => {
|
|
3078
|
+
const Comp = asChild ? Slot5 : "div";
|
|
3079
|
+
return /* @__PURE__ */ jsx61(
|
|
3080
|
+
Comp,
|
|
3081
|
+
{
|
|
3082
|
+
ref,
|
|
3083
|
+
className: cn(
|
|
3084
|
+
"grid grid-cols-4 md:grid-cols-8 xl:grid-cols-12",
|
|
3085
|
+
"gap-gutter px-grid-sm md:px-grid-md xl:px-grid-xl",
|
|
3086
|
+
!fluid && "mx-auto max-w-container",
|
|
3087
|
+
className
|
|
3088
|
+
),
|
|
3089
|
+
...props
|
|
3090
|
+
}
|
|
3091
|
+
);
|
|
3092
|
+
}
|
|
3093
|
+
);
|
|
3094
|
+
Grid.displayName = "Grid";
|
|
3095
|
+
|
|
3096
|
+
// 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";
|
|
3100
|
+
var TIER_MAX = { base: 4, md: 8, xl: 12 };
|
|
3101
|
+
var SPAN = {
|
|
3102
|
+
base: { 1: "col-span-1", 2: "col-span-2", 3: "col-span-3", 4: "col-span-4" },
|
|
3103
|
+
md: {
|
|
3104
|
+
1: "md:col-span-1",
|
|
3105
|
+
2: "md:col-span-2",
|
|
3106
|
+
3: "md:col-span-3",
|
|
3107
|
+
4: "md:col-span-4",
|
|
3108
|
+
5: "md:col-span-5",
|
|
3109
|
+
6: "md:col-span-6",
|
|
3110
|
+
7: "md:col-span-7",
|
|
3111
|
+
8: "md:col-span-8"
|
|
3112
|
+
},
|
|
3113
|
+
xl: {
|
|
3114
|
+
1: "xl:col-span-1",
|
|
3115
|
+
2: "xl:col-span-2",
|
|
3116
|
+
3: "xl:col-span-3",
|
|
3117
|
+
4: "xl:col-span-4",
|
|
3118
|
+
5: "xl:col-span-5",
|
|
3119
|
+
6: "xl:col-span-6",
|
|
3120
|
+
7: "xl:col-span-7",
|
|
3121
|
+
8: "xl:col-span-8",
|
|
3122
|
+
9: "xl:col-span-9",
|
|
3123
|
+
10: "xl:col-span-10",
|
|
3124
|
+
11: "xl:col-span-11",
|
|
3125
|
+
12: "xl:col-span-12"
|
|
3126
|
+
}
|
|
3127
|
+
};
|
|
3128
|
+
var START = {
|
|
3129
|
+
base: { 1: "col-start-1", 2: "col-start-2", 3: "col-start-3", 4: "col-start-4" },
|
|
3130
|
+
md: {
|
|
3131
|
+
1: "md:col-start-1",
|
|
3132
|
+
2: "md:col-start-2",
|
|
3133
|
+
3: "md:col-start-3",
|
|
3134
|
+
4: "md:col-start-4",
|
|
3135
|
+
5: "md:col-start-5",
|
|
3136
|
+
6: "md:col-start-6",
|
|
3137
|
+
7: "md:col-start-7",
|
|
3138
|
+
8: "md:col-start-8"
|
|
3139
|
+
},
|
|
3140
|
+
xl: {
|
|
3141
|
+
1: "xl:col-start-1",
|
|
3142
|
+
2: "xl:col-start-2",
|
|
3143
|
+
3: "xl:col-start-3",
|
|
3144
|
+
4: "xl:col-start-4",
|
|
3145
|
+
5: "xl:col-start-5",
|
|
3146
|
+
6: "xl:col-start-6",
|
|
3147
|
+
7: "xl:col-start-7",
|
|
3148
|
+
8: "xl:col-start-8",
|
|
3149
|
+
9: "xl:col-start-9",
|
|
3150
|
+
10: "xl:col-start-10",
|
|
3151
|
+
11: "xl:col-start-11",
|
|
3152
|
+
12: "xl:col-start-12"
|
|
3153
|
+
}
|
|
3154
|
+
};
|
|
3155
|
+
var TIERS = ["base", "md", "xl"];
|
|
3156
|
+
function tiersOf(value) {
|
|
3157
|
+
return typeof value === "number" ? TIERS : Object.keys(value);
|
|
3158
|
+
}
|
|
3159
|
+
function resolveTiers(value, kind) {
|
|
3160
|
+
const out = {};
|
|
3161
|
+
if (value == null) return out;
|
|
3162
|
+
for (const tier of tiersOf(value)) {
|
|
3163
|
+
if (!(tier in TIER_MAX)) {
|
|
3164
|
+
if (process.env.NODE_ENV !== "production") {
|
|
3165
|
+
console.warn(`[Col] unknown breakpoint "${tier}" (grid tiers are base/md/xl); skipped.`);
|
|
3166
|
+
}
|
|
3167
|
+
continue;
|
|
3168
|
+
}
|
|
3169
|
+
const requested = typeof value === "number" ? value : value[tier];
|
|
3170
|
+
if (requested == null) continue;
|
|
3171
|
+
const index = kind === "offset" ? requested + 1 : requested;
|
|
3172
|
+
if (!Number.isFinite(index)) {
|
|
3173
|
+
if (process.env.NODE_ENV !== "production") {
|
|
3174
|
+
console.warn(`[Col] ${kind} ${requested} is not a finite number at the "${tier}" tier; skipped.`);
|
|
3175
|
+
}
|
|
3176
|
+
continue;
|
|
3177
|
+
}
|
|
3178
|
+
const rounded = Math.round(index);
|
|
3179
|
+
const clamped = Math.min(Math.max(1, rounded), TIER_MAX[tier]);
|
|
3180
|
+
if (process.env.NODE_ENV !== "production" && (rounded < 1 || rounded > TIER_MAX[tier])) {
|
|
3181
|
+
console.warn(
|
|
3182
|
+
`[Col] ${kind} ${requested} is out of range at the "${tier}" tier (${TIER_MAX[tier]} columns); clamped.`
|
|
3183
|
+
);
|
|
3184
|
+
}
|
|
3185
|
+
out[tier] = clamped;
|
|
3186
|
+
}
|
|
3187
|
+
return out;
|
|
3188
|
+
}
|
|
3189
|
+
function responsiveClasses(span, offset) {
|
|
3190
|
+
const spans = resolveTiers(span, "span");
|
|
3191
|
+
const starts = resolveTiers(offset, "offset");
|
|
3192
|
+
const spanClasses = [];
|
|
3193
|
+
const startClasses = [];
|
|
3194
|
+
let effectiveSpan = 1;
|
|
3195
|
+
let requestedStart;
|
|
3196
|
+
let emittedStart;
|
|
3197
|
+
for (const tier of TIERS) {
|
|
3198
|
+
const tierSpan = spans[tier];
|
|
3199
|
+
if (tierSpan != null) {
|
|
3200
|
+
effectiveSpan = tierSpan;
|
|
3201
|
+
spanClasses.push(SPAN[tier][tierSpan]);
|
|
3202
|
+
}
|
|
3203
|
+
if (starts[tier] != null) requestedStart = starts[tier];
|
|
3204
|
+
if (requestedStart == null) continue;
|
|
3205
|
+
const lastFittingStart = TIER_MAX[tier] - effectiveSpan + 1;
|
|
3206
|
+
const tierStart = Math.min(requestedStart, lastFittingStart);
|
|
3207
|
+
if (tierStart < requestedStart && process.env.NODE_ENV !== "production") {
|
|
3208
|
+
console.warn(
|
|
3209
|
+
`[Col] span ${effectiveSpan} + offset ${requestedStart - 1} spill past the "${tier}" tier (${TIER_MAX[tier]} columns); offset clamped to ${tierStart - 1}.`
|
|
3210
|
+
);
|
|
3211
|
+
}
|
|
3212
|
+
if (starts[tier] != null || tierStart !== emittedStart) {
|
|
3213
|
+
emittedStart = tierStart;
|
|
3214
|
+
startClasses.push(START[tier][tierStart]);
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
return cn(...spanClasses, ...startClasses);
|
|
3218
|
+
}
|
|
3219
|
+
var Col = React52.forwardRef(
|
|
3220
|
+
({ 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 });
|
|
3223
|
+
}
|
|
3224
|
+
);
|
|
3225
|
+
Col.displayName = "Col";
|
|
3226
|
+
|
|
3227
|
+
// src/components/grid-overlay.tsx
|
|
3228
|
+
import * as React53 from "react";
|
|
3229
|
+
import { jsx as jsx63 } from "react/jsx-runtime";
|
|
3230
|
+
var BASELINE = "var(--grid-gutter) / 2";
|
|
3231
|
+
var GridOverlay = React53.forwardRef(
|
|
3232
|
+
({ columns, baseline = false, className, style, ...props }, ref) => {
|
|
3233
|
+
const count = Number.isFinite(columns) ? Math.max(1, Math.floor(columns)) : 1;
|
|
3234
|
+
return /* @__PURE__ */ jsx63(
|
|
3235
|
+
"div",
|
|
3236
|
+
{
|
|
3237
|
+
ref,
|
|
3238
|
+
"aria-hidden": true,
|
|
3239
|
+
className: cn("pointer-events-none absolute inset-0", className),
|
|
3240
|
+
style: {
|
|
3241
|
+
display: "grid",
|
|
3242
|
+
gridTemplateColumns: `repeat(${count}, minmax(0, 1fr))`,
|
|
3243
|
+
gridTemplateRows: "1fr",
|
|
3244
|
+
gap: "var(--grid-gutter)",
|
|
3245
|
+
...baseline && {
|
|
3246
|
+
backgroundImage: `repeating-linear-gradient(to bottom, transparent 0, transparent calc(${BASELINE} - 1px), var(--border-subtle) calc(${BASELINE} - 1px), var(--border-subtle) calc(${BASELINE}))`
|
|
3247
|
+
},
|
|
3248
|
+
...style
|
|
3249
|
+
},
|
|
3250
|
+
...props,
|
|
3251
|
+
children: Array.from({ length: count }).map((_, i) => /* @__PURE__ */ jsx63("span", { style: { backgroundColor: "var(--bg-brand)", opacity: 0.1 } }, i))
|
|
3252
|
+
}
|
|
3253
|
+
);
|
|
3254
|
+
}
|
|
3255
|
+
);
|
|
3256
|
+
GridOverlay.displayName = "GridOverlay";
|
|
3257
|
+
|
|
3258
|
+
// src/components/segment-chip.tsx
|
|
3259
|
+
import * as React54 from "react";
|
|
3074
3260
|
import { cva as cva9 } from "class-variance-authority";
|
|
3075
3261
|
import { Link2 as Link22, KeyRound, Clock, Plus } from "lucide-react";
|
|
3076
|
-
import { jsx as
|
|
3262
|
+
import { jsx as jsx64, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
3077
3263
|
var segmentChipVariants = cva9(
|
|
3078
3264
|
"inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1 text-sm font-medium leading-tight",
|
|
3079
3265
|
{
|
|
3080
3266
|
variants: {
|
|
3081
3267
|
kind: {
|
|
3082
|
-
granted: "border-dashed border-teal
|
|
3268
|
+
granted: "border-dashed border-text-teal/40 bg-bg-teal-subtle text-text-teal",
|
|
3083
3269
|
own: "border-border-warning bg-bg-warning-subtle text-text-warning",
|
|
3084
3270
|
pending: "border-dashed border-border-default bg-bg-secondary text-text-tertiary",
|
|
3085
3271
|
available: "border-border-subtle bg-transparent text-text-secondary"
|
|
@@ -3094,11 +3280,11 @@ var kindIcons = {
|
|
|
3094
3280
|
pending: Clock,
|
|
3095
3281
|
available: Plus
|
|
3096
3282
|
};
|
|
3097
|
-
var SegmentChip =
|
|
3283
|
+
var SegmentChip = React54.forwardRef(
|
|
3098
3284
|
({ kind, resource, icon, hideIcon = false, className, ...props }, ref) => {
|
|
3099
3285
|
const Icon = icon ?? kindIcons[kind ?? "granted"];
|
|
3100
3286
|
return /* @__PURE__ */ jsxs31("span", { ref, className: cn(segmentChipVariants({ kind }), className), ...props, children: [
|
|
3101
|
-
!hideIcon && /* @__PURE__ */
|
|
3287
|
+
!hideIcon && /* @__PURE__ */ jsx64(Icon, { className: "size-3.5 shrink-0 opacity-90", "aria-hidden": "true" }),
|
|
3102
3288
|
resource
|
|
3103
3289
|
] });
|
|
3104
3290
|
}
|
|
@@ -3106,9 +3292,9 @@ var SegmentChip = React51.forwardRef(
|
|
|
3106
3292
|
SegmentChip.displayName = "SegmentChip";
|
|
3107
3293
|
|
|
3108
3294
|
// src/components/connection-card.tsx
|
|
3109
|
-
import * as
|
|
3295
|
+
import * as React55 from "react";
|
|
3110
3296
|
import { AlertTriangle } from "lucide-react";
|
|
3111
|
-
import { Fragment as Fragment3, jsx as
|
|
3297
|
+
import { Fragment as Fragment3, jsx as jsx65, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
3112
3298
|
var authLabels = { app: "App", oauth: "OAuth", token: "token" };
|
|
3113
3299
|
var statusMeta = {
|
|
3114
3300
|
connected: { label: "Connected", badge: "success", dot: "success" },
|
|
@@ -3118,13 +3304,13 @@ var statusMeta = {
|
|
|
3118
3304
|
function monogram(name) {
|
|
3119
3305
|
return name.replace(/[^a-zA-Z0-9 ]/g, " ").split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
|
|
3120
3306
|
}
|
|
3121
|
-
var ConnectionCard =
|
|
3122
|
-
({ name, scope, auth, status, glyph, statusLabel, grantedCount, action, access, className, ...props }, ref) => {
|
|
3307
|
+
var ConnectionCard = React55.forwardRef(
|
|
3308
|
+
({ name, scope, auth, status, glyph, statusLabel, grantedCount, action, access, children, className, ...props }, ref) => {
|
|
3123
3309
|
const meta = statusMeta[status];
|
|
3124
3310
|
const connected = status !== "disconnected";
|
|
3125
3311
|
return /* @__PURE__ */ jsxs32(Card, { ref, className: cn(status === "disconnected" && "border-dashed border-border-default", className), ...props, children: [
|
|
3126
3312
|
/* @__PURE__ */ jsxs32("div", { className: "flex items-center gap-3 p-4", children: [
|
|
3127
|
-
/* @__PURE__ */
|
|
3313
|
+
/* @__PURE__ */ jsx65(
|
|
3128
3314
|
"div",
|
|
3129
3315
|
{
|
|
3130
3316
|
"aria-hidden": "true",
|
|
@@ -3133,14 +3319,14 @@ var ConnectionCard = React52.forwardRef(
|
|
|
3133
3319
|
}
|
|
3134
3320
|
),
|
|
3135
3321
|
/* @__PURE__ */ jsxs32("div", { className: "min-w-0", children: [
|
|
3136
|
-
/* @__PURE__ */
|
|
3322
|
+
/* @__PURE__ */ jsx65("p", { className: "truncate text-sm font-semibold text-text-primary", children: name }),
|
|
3137
3323
|
/* @__PURE__ */ jsxs32("div", { className: "mt-0.5 flex flex-wrap gap-1", children: [
|
|
3138
|
-
/* @__PURE__ */
|
|
3139
|
-
/* @__PURE__ */
|
|
3324
|
+
/* @__PURE__ */ jsx65(Badge, { variant: "secondary", children: scope }),
|
|
3325
|
+
/* @__PURE__ */ jsx65(Badge, { variant: "secondary", children: authLabels[auth] })
|
|
3140
3326
|
] })
|
|
3141
3327
|
] }),
|
|
3142
|
-
/* @__PURE__ */
|
|
3143
|
-
/* @__PURE__ */
|
|
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" }),
|
|
3144
3330
|
statusLabel ?? meta.label
|
|
3145
3331
|
] }) : action })
|
|
3146
3332
|
] }),
|
|
@@ -3150,49 +3336,50 @@ var ConnectionCard = React52.forwardRef(
|
|
|
3150
3336
|
role: "note",
|
|
3151
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",
|
|
3152
3338
|
children: [
|
|
3153
|
-
/* @__PURE__ */
|
|
3154
|
-
/* @__PURE__ */
|
|
3339
|
+
/* @__PURE__ */ jsx65(AlertTriangle, { className: "mt-0.5 size-3.5 shrink-0", "aria-hidden": "true" }),
|
|
3340
|
+
/* @__PURE__ */ jsx65("span", { children: access })
|
|
3155
3341
|
]
|
|
3156
3342
|
}
|
|
3157
3343
|
),
|
|
3158
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: [
|
|
3159
|
-
/* @__PURE__ */
|
|
3345
|
+
/* @__PURE__ */ jsx65("span", { className: "text-sm text-text-secondary", children: grantedCount !== void 0 ? /* @__PURE__ */ jsxs32(Fragment3, { children: [
|
|
3160
3346
|
"Granted to ",
|
|
3161
|
-
/* @__PURE__ */
|
|
3347
|
+
/* @__PURE__ */ jsx65("span", { className: "font-semibold text-text-primary", children: grantedCount }),
|
|
3162
3348
|
" ",
|
|
3163
3349
|
grantedCount === 1 ? "product" : "products"
|
|
3164
|
-
] }) : /* @__PURE__ */
|
|
3350
|
+
] }) : /* @__PURE__ */ jsx65("span", { className: "text-text-tertiary", children: "Not granted to any product yet" }) }),
|
|
3165
3351
|
connected && action
|
|
3166
|
-
] })
|
|
3352
|
+
] }),
|
|
3353
|
+
children && /* @__PURE__ */ jsx65("div", { "data-slot": "drawer", className: "border-t border-border-subtle", children })
|
|
3167
3354
|
] });
|
|
3168
3355
|
}
|
|
3169
3356
|
);
|
|
3170
3357
|
ConnectionCard.displayName = "ConnectionCard";
|
|
3171
3358
|
|
|
3172
3359
|
// src/components/request-row.tsx
|
|
3173
|
-
import * as
|
|
3174
|
-
import { Fragment as Fragment4, jsx as
|
|
3360
|
+
import * as React56 from "react";
|
|
3361
|
+
import { Fragment as Fragment4, jsx as jsx66, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
3175
3362
|
function monogram2(name) {
|
|
3176
3363
|
return name.replace(/[^a-zA-Z0-9 ]/g, " ").split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
|
|
3177
3364
|
}
|
|
3178
|
-
var RequestRow =
|
|
3365
|
+
var RequestRow = React56.forwardRef(
|
|
3179
3366
|
({ surface, name, state: state2, glyph, subtitle, grantedResource, onApprove, onDeny, onRequest, action, className, ...props }, ref) => {
|
|
3180
3367
|
let controls = action;
|
|
3181
3368
|
if (controls === void 0) {
|
|
3182
3369
|
if (surface === "org" && state2 === "pending") {
|
|
3183
3370
|
controls = /* @__PURE__ */ jsxs33(Fragment4, { children: [
|
|
3184
|
-
/* @__PURE__ */
|
|
3185
|
-
/* @__PURE__ */
|
|
3371
|
+
/* @__PURE__ */ jsx66(Button, { variant: "outline", size: "sm", onClick: onDeny, children: "Deny" }),
|
|
3372
|
+
/* @__PURE__ */ jsx66(Button, { size: "sm", onClick: onApprove, children: "Approve" })
|
|
3186
3373
|
] });
|
|
3187
3374
|
} else if (state2 === "available") {
|
|
3188
|
-
controls = /* @__PURE__ */
|
|
3375
|
+
controls = /* @__PURE__ */ jsx66(Button, { size: "sm", onClick: onRequest, children: "Request" });
|
|
3189
3376
|
} else if (state2 === "pending") {
|
|
3190
3377
|
controls = /* @__PURE__ */ jsxs33(Badge, { variant: "secondary", className: "gap-1.5", children: [
|
|
3191
|
-
/* @__PURE__ */
|
|
3378
|
+
/* @__PURE__ */ jsx66(StatusDot, { status: "neutral", size: "sm" }),
|
|
3192
3379
|
"Pending"
|
|
3193
3380
|
] });
|
|
3194
3381
|
} else if (state2 === "granted") {
|
|
3195
|
-
controls = /* @__PURE__ */
|
|
3382
|
+
controls = /* @__PURE__ */ jsx66(SegmentChip, { kind: "granted", resource: grantedResource ?? "a segment" });
|
|
3196
3383
|
}
|
|
3197
3384
|
}
|
|
3198
3385
|
return /* @__PURE__ */ jsxs33(
|
|
@@ -3205,7 +3392,7 @@ var RequestRow = React53.forwardRef(
|
|
|
3205
3392
|
),
|
|
3206
3393
|
...props,
|
|
3207
3394
|
children: [
|
|
3208
|
-
/* @__PURE__ */
|
|
3395
|
+
/* @__PURE__ */ jsx66(
|
|
3209
3396
|
"div",
|
|
3210
3397
|
{
|
|
3211
3398
|
"aria-hidden": "true",
|
|
@@ -3214,10 +3401,10 @@ var RequestRow = React53.forwardRef(
|
|
|
3214
3401
|
}
|
|
3215
3402
|
),
|
|
3216
3403
|
/* @__PURE__ */ jsxs33("div", { className: "min-w-0", children: [
|
|
3217
|
-
/* @__PURE__ */
|
|
3218
|
-
subtitle && /* @__PURE__ */
|
|
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 })
|
|
3219
3406
|
] }),
|
|
3220
|
-
/* @__PURE__ */
|
|
3407
|
+
/* @__PURE__ */ jsx66("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: controls })
|
|
3221
3408
|
]
|
|
3222
3409
|
}
|
|
3223
3410
|
);
|
|
@@ -3272,6 +3459,7 @@ export {
|
|
|
3272
3459
|
CarouselNext,
|
|
3273
3460
|
CarouselPrevious,
|
|
3274
3461
|
Checkbox,
|
|
3462
|
+
Col,
|
|
3275
3463
|
Collapsible,
|
|
3276
3464
|
CollapsibleContent2 as CollapsibleContent,
|
|
3277
3465
|
CollapsibleTrigger2 as CollapsibleTrigger,
|
|
@@ -3349,6 +3537,8 @@ export {
|
|
|
3349
3537
|
FormItem,
|
|
3350
3538
|
FormLabel,
|
|
3351
3539
|
FormMessage,
|
|
3540
|
+
Grid,
|
|
3541
|
+
GridOverlay,
|
|
3352
3542
|
HoverCard,
|
|
3353
3543
|
HoverCardContent,
|
|
3354
3544
|
HoverCardTrigger,
|