diff --git a/apps/ui/content/docs/(root)/changelog.mdx b/apps/ui/content/docs/(root)/changelog.mdx index 650b81ec5..24d405e65 100644 --- a/apps/ui/content/docs/(root)/changelog.mdx +++ b/apps/ui/content/docs/(root)/changelog.mdx @@ -3,6 +3,73 @@ title: Changelog description: Breaking changes, migration guides, and notable updates. --- +## October 18, 2026 + +### Avatar group clipping and inherited sizing + +**`AvatarGroup`** and **`AvatarGroupCount`** are new. Overlapping avatars are separated by masking each one with its neighbour's circle, so the gap between them is **transparent rather than a coloured ring**. A stack now keeps its shape on gradients, photographs and translucent panels, where a ring tinted with the page background does not. + +```tsx + + … + … + +2 + +``` + +**`topmost`** picks which end of the stack stays a whole circle — **`"first"`** by default, **`"last"`** for the conventional order. + +**`Avatar`** now takes every dimension from one inherited custom property, **`--avatar-size`** (default 32px). Font size scales with it, and a group derives its overlap, gap and initials nudge from it. Set it on the avatar, the group, or any ancestor: + +```tsx +
{/* every avatar below is 40px */}
+``` + +**`--avatar-overlap`**, **`--avatar-gap`** and **`--avatar-nudge`** tune a group and inherit the same way. + +Two defaults changed: **`AvatarFallback`** uses **`bg-border`** instead of **`bg-muted`**, which is twice the contrast, and **`Avatar`** no longer paints **`bg-background`** behind its content. **`AvatarImage`** and **`AvatarFallback`** now follow the root radius through **`rounded-[inherit]`**, so a rounded avatar no longer has to repeat its radius on the fallback. + +Browsers without **`mask-composite`** (Chrome and Edge 111–119) fall back to a plain overlap with no gap. Nothing else degrades. + +**Migration:** + +Existing **`Avatar`** JSX requires no changes. A **`size-*`** class still resizes a single avatar, because tailwind-merge replaces the base size utility — but it leaves **`--avatar-size`** untouched, so the font size and any group geometry stay at the inherited value. Prefer the property: + +```diff +- ++ +``` + +Once an avatar is sized this way, a hand-tuned font size on its fallback is usually redundant: + +```diff +- JD ++ JD +``` + +Replacing a hand-rolled ring stack with **`AvatarGroup`** means dropping the negative spacing and the ring utilities: + +```diff +-
+- … +-
++ ++ … ++ +``` + +If you relied on the lighter **`bg-muted`** fallback or on **`bg-background`** behind a transparent image, set those explicitly on your own instances. + +```bash +npx shadcn@latest add @coss/avatar +``` + +**Agent migration prompt:** + +```text +Update the local coss Avatar component to the version with AvatarGroup, then migrate call sites. Install with `npx shadcn@latest add @coss/avatar`. Avatar now derives its box, font size and group geometry from one inherited custom property, --avatar-size (default 32px); --avatar-overlap, --avatar-gap and --avatar-nudge tune groups and inherit the same way. Replace size-* classes on Avatar with [--avatar-size:--spacing(N)] using the same N, and set it on a shared ancestor when several avatars share a size. After converting, delete hand-tuned text-* classes on AvatarFallback unless the design needs a size other than the derived one — the derived size is --avatar-size * 0.375. Replace hand-rolled overlapping stacks (flex with -space-x-* plus ring-2 ring-background on each avatar) with AvatarGroup, removing the negative spacing and ring utilities; add topmost="last" if the original kept the last avatar whole. Use AvatarGroupCount for a trailing "+N" chip instead of an Avatar with a fallback. Inside a group every avatar must be the same size, so set --avatar-size on the group, never on individual avatars. A status dot or badge on a clipped avatar is clipped with it; keep those on the avatar that stays whole, and place them in a relative wrapper as a sibling of Avatar rather than inside it. Note two visual default changes to review: AvatarFallback uses bg-border instead of bg-muted, and Avatar no longer paints bg-background. Run formatting and typechecking, then visually check avatars at every size the app uses, including any on gradients or images. +``` + ## August 19, 2026 ### Tabs sizing and segmented control styles diff --git a/apps/ui/content/docs/components/avatar.mdx b/apps/ui/content/docs/components/avatar.mdx index 4c584671e..d20787aea 100644 --- a/apps/ui/content/docs/components/avatar.mdx +++ b/apps/ui/content/docs/components/avatar.mdx @@ -50,7 +50,13 @@ npm install @base-ui/react ## Usage ```tsx -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { + Avatar, + AvatarFallback, + AvatarGroup, + AvatarGroupCount, + AvatarImage, +} from "@/components/ui/avatar" ``` ```tsx @@ -74,19 +80,106 @@ Use `keepMounted` when the image relies on native lazy loading or an optimized i
``` +## Sizing + +Every dimension of an avatar comes from one inherited custom property, `--avatar-size`. It defaults to 32px, and the font size, the group overlap, the gap and the initials nudge are all derived from it. + +Set it on the avatar, on a group, or on any ancestor: + +```tsx +… + +… + +
+ {/* every avatar below is 40px */} +
+``` + +A `size-*` utility still resizes a single avatar, because tailwind-merge replaces the base size utility. It does **not** update `--avatar-size`, so the font size and any group geometry stay at the inherited value. Prefer `--avatar-size` and reach for `size-*` only when you want the box to change and nothing else. + + + +Because the property is inherited, one declaration on a container sizes every avatar inside it. + ## API Reference ### Avatar -Root component. Styled wrapper for `Avatar.Root` from Base UI with default size of 32px (8 units) and rounded-full styling. +Root component. Styled wrapper for `Avatar.Root` from Base UI, round by default and sized from `--avatar-size`. + +It declares no props of its own; everything from `Avatar.Root` passes through, including `className` and `render`. + +It renders a ``. To make an avatar interactive, use `render` rather than wrapping it: + +```tsx +}>… +}>… +``` + +An interactive avatar needs an accessible name. `AvatarImage`'s `alt` supplies one; a fallback-only avatar does not, so add `aria-label`. ### AvatarImage -Image element for the avatar. Styled wrapper for `Avatar.Image` from Base UI with support for `keepMounted` images. +Image element. Styled wrapper for `Avatar.Image` from Base UI. + +| Prop | Type | Default | Description | +| ----------------------- | ------------------------------------- | ------- | -------------------------------------------------------------------------------------------- | +| `keepMounted` | `boolean` | `false` | Keep the image mounted and let it load in place, for `loading="lazy"` and `next/image` | +| `onLoadingStatusChange` | `(status: ImageLoadingStatus) => void` | - | Fired when loading status changes: `"idle"`, `"loading"`, `"loaded"` or `"error"` | ### AvatarFallback -Fallback content displayed when the image fails to load. Styled wrapper for `Avatar.Fallback` from Base UI with muted background. +Shown while the image loads or when it fails, and whenever no image is given. Base UI unmounts it once the image loads. + +| Prop | Type | Default | Description | +| ------- | -------- | ------- | ------------------------------------------------------ | +| `delay` | `number` | `0` | Milliseconds to wait before showing the fallback | + +It inherits its font size from the avatar, so it scales automatically. Override it on the fallback when a specific size is needed: + +```tsx +JD +``` + +### AvatarGroup + +Lays out overlapping avatars. Each avatar is masked with a circle matching its neighbour, so the avatars are separated by a **transparent gap rather than a ring**. The stack therefore holds its shape on gradients, images and translucent panels, where a ring tinted with the page background would not. + +| Prop | Type | Default | Description | +| --------- | ------------------- | --------- | -------------------------------------------- | +| `topmost` | `"first" \| "last"` | `"first"` | Which end of the stack stays a whole circle. | + +Geometry is derived from the inherited `--avatar-size`, so sizing a stack is the same gesture as sizing one avatar. Three further properties tune the layout, and all of them inherit: + +| Property | Default | Description | +| ------------------ | -------------------------------- | ---------------------------------------- | +| `--avatar-overlap` | `calc(var(--avatar-size) * 0.3)` | How far each avatar sits over the previous one. | +| `--avatar-gap` | `calc(var(--avatar-size) / 16)` | Thickness of the transparent gap. | +| `--avatar-nudge` | `calc((var(--avatar-overlap) + var(--avatar-gap)) * 0.3)` | How far initials shift out of the clipped edge. Set to `0` to centre them geometrically. | + +```tsx + + … + +``` + +**Constraints worth knowing:** + +- Every avatar in a group must be the same size. The mask is positioned from the group's `--avatar-size`, so a per-avatar override misplaces the cut. Set the size on the group. +- A status dot or badge on a clipped avatar is clipped with it, and is cropped to the circle. Put one on the avatar that stays whole — the first with `topmost="first"`, the last with `topmost="last"`. +- Browsers without `mask-composite` (Chrome and Edge 111–119) fall back to a plain overlap with no gap. Nothing else degrades. + +### AvatarGroupCount + +The "+N" chip that ends a group. It is an `Avatar` with a fallback, so it is sized, overlapped and clipped like its neighbours; its label uses `text-muted-foreground`. + +```tsx + + … + +2 + +``` ## Examples @@ -94,10 +187,6 @@ Fallback content displayed when the image fails to load. Styled wrapper for `Ava -### Different Sizes - - - ### Different Radius @@ -105,3 +194,13 @@ Fallback content displayed when the image fails to load. Styled wrapper for `Ava ### Group Avatars + +### Last Avatar On Top + +Set `topmost="last"` for the conventional order, where the last avatar is the one left intact. + + + +## Changelog + +- [October 18, 2026](/ui/docs/changelog#avatar-group-clipping-and-inherited-sizing) — **`AvatarGroup`** and **`AvatarGroupCount`** added; overlapping avatars are separated by a transparent clipped gap instead of a ring. **`Avatar`** now derives its box, type and group geometry from the inherited **`--avatar-size`**. **`AvatarFallback`** moves from **`bg-muted`** to **`bg-border`**, and **`Avatar`** no longer paints **`bg-background`**. diff --git a/apps/ui/public/r/avatar.json b/apps/ui/public/r/avatar.json index 677171f31..61982d84c 100644 --- a/apps/ui/public/r/avatar.json +++ b/apps/ui/public/r/avatar.json @@ -7,7 +7,7 @@ "files": [ { "path": "registry/default/ui/avatar.tsx", - "content": "\"use client\";\n\nimport { Avatar as AvatarPrimitive } from \"@base-ui/react/avatar\";\nimport type React from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\n\nexport function Avatar({\n className,\n ...props\n}: AvatarPrimitive.Root.Props): React.ReactElement {\n return (\n \n );\n}\n\nexport function AvatarImage({\n className,\n ...props\n}: AvatarPrimitive.Image.Props): React.ReactElement {\n return (\n \n );\n}\n\nexport function AvatarFallback({\n className,\n ...props\n}: AvatarPrimitive.Fallback.Props): React.ReactElement {\n return (\n \n );\n}\n\nexport { AvatarPrimitive };\n", + "content": "\"use client\";\n\nimport { Avatar as AvatarPrimitive } from \"@base-ui/react/avatar\";\nimport type React from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\n\nconst AVATAR_GROUP_CUT =\n \"url(\\\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2 2'%3E%3Ccircle cx='1' cy='1' r='1'/%3E%3C/svg%3E\\\")\";\n\nexport function Avatar({\n className,\n ...props\n}: AvatarPrimitive.Root.Props): React.ReactElement {\n return (\n \n );\n}\n\nexport function AvatarImage({\n className,\n ...props\n}: AvatarPrimitive.Image.Props): React.ReactElement {\n return (\n \n );\n}\n\nexport function AvatarFallback({\n className,\n ...props\n}: AvatarPrimitive.Fallback.Props): React.ReactElement {\n return (\n \n );\n}\n\nexport interface AvatarGroupProps extends React.ComponentProps<\"div\"> {\n topmost?: \"first\" | \"last\";\n}\n\nexport function AvatarGroup({\n className,\n style,\n topmost = \"first\",\n ...props\n}: AvatarGroupProps): React.ReactElement {\n return (\n \n );\n}\n\nexport function AvatarGroupCount({\n children,\n className,\n ...props\n}: AvatarPrimitive.Root.Props): React.ReactElement {\n return (\n \n {children}\n
\n );\n}\n\nexport { AvatarPrimitive };\n", "type": "registry:ui" } ], diff --git a/apps/ui/public/r/p-avatar-11.json b/apps/ui/public/r/p-avatar-11.json index 969a48fde..7fb55e6f9 100644 --- a/apps/ui/public/r/p-avatar-11.json +++ b/apps/ui/public/r/p-avatar-11.json @@ -9,7 +9,7 @@ "files": [ { "path": "registry/default/particles/p-avatar-11.tsx", - "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport { Badge } from \"@/registry/default/ui/badge\";\n\nexport default function Particle() {\n return (\n
\n \n \n LT\n \n \n 6\n \n
\n );\n}\n", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport { Badge } from \"@/registry/default/ui/badge\";\n\nexport default function Particle() {\n return (\n
\n \n \n LT\n \n \n 6\n \n
\n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-avatar-13.json b/apps/ui/public/r/p-avatar-13.json index 5929e2606..6693ebbc9 100644 --- a/apps/ui/public/r/p-avatar-13.json +++ b/apps/ui/public/r/p-avatar-13.json @@ -8,7 +8,7 @@ "files": [ { "path": "registry/default/particles/p-avatar-13.tsx", - "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n
\n \n \n U1\n \n \n \n U2\n \n \n \n U3\n \n
\n );\n}\n", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarGroup,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n \n \n \n FK\n \n \n \n NE\n \n \n \n EW\n \n \n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-avatar-14.json b/apps/ui/public/r/p-avatar-14.json index 656e83c07..9f03bfe04 100644 --- a/apps/ui/public/r/p-avatar-14.json +++ b/apps/ui/public/r/p-avatar-14.json @@ -8,7 +8,7 @@ "files": [ { "path": "registry/default/particles/p-avatar-14.tsx", - "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n
\n \n \n U1\n \n \n \n U2\n \n \n \n U3\n \n
\n );\n}\n", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarGroup,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n \n \n \n FK\n \n \n \n NE\n \n \n \n EW\n \n \n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-avatar-15.json b/apps/ui/public/r/p-avatar-15.json new file mode 100644 index 000000000..3a4037b58 --- /dev/null +++ b/apps/ui/public/r/p-avatar-15.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "p-avatar-15", + "description": "Avatar group with the last avatar on top", + "registryDependencies": [ + "@coss/avatar" + ], + "files": [ + { + "path": "registry/default/particles/p-avatar-15.tsx", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarGroup,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n \n \n \n FK\n \n \n \n NE\n \n \n \n EW\n \n \n );\n}\n", + "type": "registry:block" + } + ], + "categories": [ + "avatar" + ], + "type": "registry:block" +} \ No newline at end of file diff --git a/apps/ui/public/r/p-avatar-16.json b/apps/ui/public/r/p-avatar-16.json new file mode 100644 index 000000000..1292f88e0 --- /dev/null +++ b/apps/ui/public/r/p-avatar-16.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "p-avatar-16", + "description": "Avatar group with a count and the last avatar on top", + "registryDependencies": [ + "@coss/avatar" + ], + "files": [ + { + "path": "registry/default/particles/p-avatar-16.tsx", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarGroup,\n AvatarGroupCount,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n \n \n \n FK\n \n \n \n NE\n \n \n \n EW\n \n +2\n \n );\n}\n", + "type": "registry:block" + } + ], + "categories": [ + "avatar" + ], + "type": "registry:block" +} \ No newline at end of file diff --git a/apps/ui/public/r/p-avatar-17.json b/apps/ui/public/r/p-avatar-17.json new file mode 100644 index 000000000..87698a3bd --- /dev/null +++ b/apps/ui/public/r/p-avatar-17.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "p-avatar-17", + "description": "Avatar rendered as a button", + "registryDependencies": [ + "@coss/avatar" + ], + "files": [ + { + "path": "registry/default/particles/p-avatar-17.tsx", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n }\n >\n \n FK\n \n );\n}\n", + "type": "registry:block" + } + ], + "categories": [ + "avatar" + ], + "type": "registry:block" +} \ No newline at end of file diff --git a/apps/ui/public/r/p-avatar-18.json b/apps/ui/public/r/p-avatar-18.json new file mode 100644 index 000000000..4dd646320 --- /dev/null +++ b/apps/ui/public/r/p-avatar-18.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "p-avatar-18", + "description": "Avatar rendered as a link", + "registryDependencies": [ + "@coss/avatar" + ], + "files": [ + { + "path": "registry/default/particles/p-avatar-18.tsx", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nconst profileLink = (\n // biome-ignore lint/a11y/useAnchorContent: Avatar supplies the link content through the render prop.\n \n);\n\nexport default function Particle() {\n return (\n \n \n NE\n \n );\n}\n", + "type": "registry:block" + } + ], + "categories": [ + "avatar" + ], + "type": "registry:block" +} \ No newline at end of file diff --git a/apps/ui/public/r/p-avatar-19.json b/apps/ui/public/r/p-avatar-19.json new file mode 100644 index 000000000..3739c0ebf --- /dev/null +++ b/apps/ui/public/r/p-avatar-19.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "p-avatar-19", + "description": "Avatars sized from a parent element", + "registryDependencies": [ + "@coss/avatar" + ], + "files": [ + { + "path": "registry/default/particles/p-avatar-19.tsx", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nconst people = [\n {\n initials: \"FK\",\n name: \"Frida Kahlo\",\n photo: \"photo-1543610892-0b1f7e6d8ac1\",\n role: \"Design\",\n },\n {\n initials: \"NE\",\n name: \"Nadia Ekman\",\n photo: \"photo-1628157588553-5eeea00af15c\",\n role: \"Engineering\",\n },\n { initials: \"EW\", name: \"Eli Wren\", photo: \"\", role: \"Research\" },\n];\n\nexport default function Particle() {\n return (\n
\n {people.map((person) => (\n
\n \n {person.photo ? (\n \n ) : null}\n {person.initials}\n \n
\n {person.name}\n \n {person.role}\n \n
\n
\n ))}\n
\n );\n}\n", + "type": "registry:block" + } + ], + "categories": [ + "avatar" + ], + "type": "registry:block" +} \ No newline at end of file diff --git a/apps/ui/public/r/p-avatar-20.json b/apps/ui/public/r/p-avatar-20.json new file mode 100644 index 000000000..9150d82a3 --- /dev/null +++ b/apps/ui/public/r/p-avatar-20.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "p-avatar-20", + "description": "Avatar with a tooltip", + "registryDependencies": [ + "@coss/avatar", + "@coss/tooltip" + ], + "files": [ + { + "path": "registry/default/particles/p-avatar-20.tsx", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport {\n Tooltip,\n TooltipPopup,\n TooltipTrigger,\n} from \"@/registry/default/ui/tooltip\";\n\nexport default function Particle() {\n return (\n \n }>\n \n FK\n \n Frida Kahlo\n \n );\n}\n", + "type": "registry:block" + } + ], + "categories": [ + "avatar" + ], + "type": "registry:block" +} \ No newline at end of file diff --git a/apps/ui/public/r/p-avatar-21.json b/apps/ui/public/r/p-avatar-21.json new file mode 100644 index 000000000..54894b1e4 --- /dev/null +++ b/apps/ui/public/r/p-avatar-21.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "p-avatar-21", + "description": "Avatar group with a tooltip per member", + "registryDependencies": [ + "@coss/avatar", + "@coss/tooltip" + ], + "files": [ + { + "path": "registry/default/particles/p-avatar-21.tsx", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarGroup,\n AvatarGroupCount,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport {\n Tooltip,\n TooltipPopup,\n TooltipTrigger,\n} from \"@/registry/default/ui/tooltip\";\n\nconst people = [\n {\n initials: \"FK\",\n name: \"Frida Kahlo\",\n photo: \"photo-1543610892-0b1f7e6d8ac1\",\n },\n {\n initials: \"NE\",\n name: \"Nadia Ekman\",\n photo: \"photo-1628157588553-5eeea00af15c\",\n },\n {\n initials: \"EW\",\n name: \"Eli Wren\",\n photo: \"photo-1655874819398-c6dfbec68ac7\",\n },\n];\n\nexport default function Particle() {\n return (\n \n {people.map((person) => (\n \n }>\n \n {person.initials}\n \n {person.name}\n \n ))}\n +2\n \n );\n}\n", + "type": "registry:block" + } + ], + "categories": [ + "avatar" + ], + "type": "registry:block" +} \ No newline at end of file diff --git a/apps/ui/public/r/p-avatar-3.json b/apps/ui/public/r/p-avatar-3.json index bdb073b40..14714628b 100644 --- a/apps/ui/public/r/p-avatar-3.json +++ b/apps/ui/public/r/p-avatar-3.json @@ -8,7 +8,7 @@ "files": [ { "path": "registry/default/particles/p-avatar-3.tsx", - "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n
\n \n \n AV\n \n \n \n AV\n \n \n \n AV\n \n
\n );\n}\n", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n
\n
\n \n \n AV\n \n \n \n AV\n \n \n \n AV\n \n \n \n AV\n \n \n \n AV\n \n
\n
\n \n AV\n \n \n AV\n \n \n AV\n \n \n AV\n \n \n AV\n \n
\n
\n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-avatar-5.json b/apps/ui/public/r/p-avatar-5.json index a2dbb8f0e..e882b9a97 100644 --- a/apps/ui/public/r/p-avatar-5.json +++ b/apps/ui/public/r/p-avatar-5.json @@ -8,7 +8,7 @@ "files": [ { "path": "registry/default/particles/p-avatar-5.tsx", - "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n
\n \n \n U1\n \n \n \n U2\n \n \n \n U3\n \n
\n );\n}\n", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarGroup,\n AvatarGroupCount,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n \n \n \n FK\n \n \n \n NE\n \n \n \n EW\n \n +2\n \n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-avatar-7.json b/apps/ui/public/r/p-avatar-7.json index a30f51ca3..40ab53303 100644 --- a/apps/ui/public/r/p-avatar-7.json +++ b/apps/ui/public/r/p-avatar-7.json @@ -8,7 +8,7 @@ "files": [ { "path": "registry/default/particles/p-avatar-7.tsx", - "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n
\n \n \n LT\n \n \n
\n );\n}\n", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n
\n \n \n LT\n \n Online\n \n
\n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-avatar-8.json b/apps/ui/public/r/p-avatar-8.json index 94bf63f4f..2055cc95f 100644 --- a/apps/ui/public/r/p-avatar-8.json +++ b/apps/ui/public/r/p-avatar-8.json @@ -8,7 +8,7 @@ "files": [ { "path": "registry/default/particles/p-avatar-8.tsx", - "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n
\n \n \n LT\n \n \n
\n );\n}\n", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n
\n \n \n LT\n \n Offline\n \n
\n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-avatar-9.json b/apps/ui/public/r/p-avatar-9.json index b197a3f6a..922311906 100644 --- a/apps/ui/public/r/p-avatar-9.json +++ b/apps/ui/public/r/p-avatar-9.json @@ -8,7 +8,7 @@ "files": [ { "path": "registry/default/particles/p-avatar-9.tsx", - "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n
\n \n \n LT\n \n \n
\n );\n}\n", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\n\nexport default function Particle() {\n return (\n
\n \n \n LT\n \n Online\n \n
\n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-button-28.json b/apps/ui/public/r/p-button-28.json index da5cc68a0..a27e4eac0 100644 --- a/apps/ui/public/r/p-button-28.json +++ b/apps/ui/public/r/p-button-28.json @@ -9,7 +9,7 @@ "files": [ { "path": "registry/default/particles/p-button-28.tsx", - "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport { Button } from \"@/registry/default/ui/button\";\n\nexport default function Particle() {\n return (\n \n );\n}\n", + "content": "import {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport { Button } from \"@/registry/default/ui/button\";\n\nexport default function Particle() {\n return (\n \n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-combobox-19.json b/apps/ui/public/r/p-combobox-19.json index 21a141aa1..c1e9b04e1 100644 --- a/apps/ui/public/r/p-combobox-19.json +++ b/apps/ui/public/r/p-combobox-19.json @@ -14,7 +14,7 @@ "files": [ { "path": "registry/default/particles/p-combobox-19.tsx", - "content": "\"use client\";\n\nimport { SearchIcon, XIcon } from \"lucide-react\";\nimport { useState } from \"react\";\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport { Badge } from \"@/registry/default/ui/badge\";\nimport { Button } from \"@/registry/default/ui/button\";\nimport {\n Combobox,\n ComboboxEmpty,\n ComboboxInput,\n ComboboxItem,\n ComboboxList,\n ComboboxPopup,\n} from \"@/registry/default/ui/combobox\";\n\ntype TeamMember = {\n avatar: string;\n initials: string;\n label: string;\n priority: \"Lowest\" | \"Low\" | \"Medium\" | \"High\" | \"Highest\";\n value: string;\n weight: number;\n};\n\nconst teamMembers: TeamMember[] = [\n {\n avatar:\n \"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=72&h=72&dpr=2&q=80\",\n initials: \"JH\",\n label: \"Jenny Hamilton\",\n priority: \"Highest\",\n value: \"jenny\",\n weight: 200,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1628157588553-5eeea00af15c?w=72&h=72&dpr=2&q=80\",\n initials: \"PS\",\n label: \"Paul Smith\",\n priority: \"Medium\",\n value: \"paul\",\n weight: 100,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1655874819398-c6dfbec68ac7?w=72&h=72&dpr=2&q=80\",\n initials: \"LW\",\n label: \"Luna Wyen\",\n priority: \"High\",\n value: \"luna\",\n weight: 150,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=72&h=72&dpr=2&q=80\",\n initials: \"AC\",\n label: \"Alex Chen\",\n priority: \"Low\",\n value: \"alex\",\n weight: 100,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=72&h=72&dpr=2&q=80\",\n initials: \"SJ\",\n label: \"Sarah Johnson\",\n priority: \"Medium\",\n value: \"sarah\",\n weight: 50,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=72&h=72&dpr=2&q=80\",\n initials: \"ED\",\n label: \"Emma Davis\",\n priority: \"Lowest\",\n value: \"emma\",\n weight: 100,\n },\n];\n\nexport default function Particle() {\n const [open, setOpen] = useState(false);\n const [selected, setSelected] = useState(\n teamMembers.slice(0, 2),\n );\n\n return (\n
\n {\n setSelected(value);\n setOpen(false);\n }}\n open={open}\n value={selected}\n >\n }\n />\n \n No team members found.\n \n {(item: TeamMember) => (\n \n {item.label}\n \n )}\n \n \n \n {selected.length > 0 && (\n
    \n {selected.map((member) => (\n \n \n \n \n {member.initials}\n \n \n {member.label}\n \n {member.priority}\n \n \n {member.weight}%\n \n \n setSelected((current) =>\n current.filter((item) => item.value !== member.value),\n )\n }\n size=\"icon-xs\"\n variant=\"ghost\"\n >\n \n \n \n ))}\n
\n )}\n
\n );\n}\n", + "content": "\"use client\";\n\nimport { SearchIcon, XIcon } from \"lucide-react\";\nimport { useState } from \"react\";\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport { Badge } from \"@/registry/default/ui/badge\";\nimport { Button } from \"@/registry/default/ui/button\";\nimport {\n Combobox,\n ComboboxEmpty,\n ComboboxInput,\n ComboboxItem,\n ComboboxList,\n ComboboxPopup,\n} from \"@/registry/default/ui/combobox\";\n\ntype TeamMember = {\n avatar: string;\n initials: string;\n label: string;\n priority: \"Lowest\" | \"Low\" | \"Medium\" | \"High\" | \"Highest\";\n value: string;\n weight: number;\n};\n\nconst teamMembers: TeamMember[] = [\n {\n avatar:\n \"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=72&h=72&dpr=2&q=80\",\n initials: \"JH\",\n label: \"Jenny Hamilton\",\n priority: \"Highest\",\n value: \"jenny\",\n weight: 200,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1628157588553-5eeea00af15c?w=72&h=72&dpr=2&q=80\",\n initials: \"PS\",\n label: \"Paul Smith\",\n priority: \"Medium\",\n value: \"paul\",\n weight: 100,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1655874819398-c6dfbec68ac7?w=72&h=72&dpr=2&q=80\",\n initials: \"LW\",\n label: \"Luna Wyen\",\n priority: \"High\",\n value: \"luna\",\n weight: 150,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=72&h=72&dpr=2&q=80\",\n initials: \"AC\",\n label: \"Alex Chen\",\n priority: \"Low\",\n value: \"alex\",\n weight: 100,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=72&h=72&dpr=2&q=80\",\n initials: \"SJ\",\n label: \"Sarah Johnson\",\n priority: \"Medium\",\n value: \"sarah\",\n weight: 50,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=72&h=72&dpr=2&q=80\",\n initials: \"ED\",\n label: \"Emma Davis\",\n priority: \"Lowest\",\n value: \"emma\",\n weight: 100,\n },\n];\n\nexport default function Particle() {\n const [open, setOpen] = useState(false);\n const [selected, setSelected] = useState(\n teamMembers.slice(0, 2),\n );\n\n return (\n
\n {\n setSelected(value);\n setOpen(false);\n }}\n open={open}\n value={selected}\n >\n }\n />\n \n No team members found.\n \n {(item: TeamMember) => (\n \n {item.label}\n \n )}\n \n \n \n {selected.length > 0 && (\n
    \n {selected.map((member) => (\n \n \n \n {member.initials}\n \n {member.label}\n \n {member.priority}\n \n \n {member.weight}%\n \n \n setSelected((current) =>\n current.filter((item) => item.value !== member.value),\n )\n }\n size=\"icon-xs\"\n variant=\"ghost\"\n >\n \n \n \n ))}\n
\n )}\n
\n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-combobox-20.json b/apps/ui/public/r/p-combobox-20.json index 0d1a22d6c..4d889628c 100644 --- a/apps/ui/public/r/p-combobox-20.json +++ b/apps/ui/public/r/p-combobox-20.json @@ -14,7 +14,7 @@ "files": [ { "path": "registry/default/particles/p-combobox-20.tsx", - "content": "\"use client\";\n\nimport { SearchIcon, XIcon } from \"lucide-react\";\nimport { useState } from \"react\";\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport { Badge } from \"@/registry/default/ui/badge\";\nimport { Button } from \"@/registry/default/ui/button\";\nimport {\n Combobox,\n ComboboxEmpty,\n ComboboxInput,\n ComboboxItem,\n ComboboxList,\n ComboboxPopup,\n} from \"@/registry/default/ui/combobox\";\n\ntype TeamMember = {\n avatar: string;\n initials: string;\n label: string;\n priority: \"Lowest\" | \"Low\" | \"Medium\" | \"High\" | \"Highest\";\n value: string;\n weight: number;\n};\n\nconst teamMembers: TeamMember[] = [\n {\n avatar:\n \"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=72&h=72&dpr=2&q=80\",\n initials: \"JH\",\n label: \"Jenny Hamilton\",\n priority: \"Highest\",\n value: \"jenny\",\n weight: 200,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1628157588553-5eeea00af15c?w=72&h=72&dpr=2&q=80\",\n initials: \"PS\",\n label: \"Paul Smith\",\n priority: \"Medium\",\n value: \"paul\",\n weight: 100,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1655874819398-c6dfbec68ac7?w=72&h=72&dpr=2&q=80\",\n initials: \"LW\",\n label: \"Luna Wyen\",\n priority: \"High\",\n value: \"luna\",\n weight: 150,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=72&h=72&dpr=2&q=80\",\n initials: \"AC\",\n label: \"Alex Chen\",\n priority: \"Low\",\n value: \"alex\",\n weight: 100,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=72&h=72&dpr=2&q=80\",\n initials: \"SJ\",\n label: \"Sarah Johnson\",\n priority: \"Medium\",\n value: \"sarah\",\n weight: 50,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=72&h=72&dpr=2&q=80\",\n initials: \"ED\",\n label: \"Emma Davis\",\n priority: \"Lowest\",\n value: \"emma\",\n weight: 100,\n },\n];\n\nexport default function Particle() {\n const [open, setOpen] = useState(false);\n const [selected, setSelected] = useState(\n teamMembers.slice(0, 2),\n );\n\n return (\n
\n {\n setSelected(value);\n setOpen(false);\n }}\n open={open}\n value={selected}\n >\n }\n />\n \n No team members found.\n \n {(item: TeamMember) => (\n \n {item.label}\n \n )}\n \n \n \n {selected.length > 0 && (\n
    \n {selected.map((member) => (\n \n \n \n \n {member.initials}\n \n \n {member.label}\n \n {member.priority}\n \n \n {member.weight}%\n \n \n setSelected((current) =>\n current.filter((item) => item.value !== member.value),\n )\n }\n size=\"icon-xs\"\n variant=\"ghost\"\n >\n \n \n \n ))}\n
\n )}\n
\n );\n}\n", + "content": "\"use client\";\n\nimport { SearchIcon, XIcon } from \"lucide-react\";\nimport { useState } from \"react\";\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport { Badge } from \"@/registry/default/ui/badge\";\nimport { Button } from \"@/registry/default/ui/button\";\nimport {\n Combobox,\n ComboboxEmpty,\n ComboboxInput,\n ComboboxItem,\n ComboboxList,\n ComboboxPopup,\n} from \"@/registry/default/ui/combobox\";\n\ntype TeamMember = {\n avatar: string;\n initials: string;\n label: string;\n priority: \"Lowest\" | \"Low\" | \"Medium\" | \"High\" | \"Highest\";\n value: string;\n weight: number;\n};\n\nconst teamMembers: TeamMember[] = [\n {\n avatar:\n \"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=72&h=72&dpr=2&q=80\",\n initials: \"JH\",\n label: \"Jenny Hamilton\",\n priority: \"Highest\",\n value: \"jenny\",\n weight: 200,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1628157588553-5eeea00af15c?w=72&h=72&dpr=2&q=80\",\n initials: \"PS\",\n label: \"Paul Smith\",\n priority: \"Medium\",\n value: \"paul\",\n weight: 100,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1655874819398-c6dfbec68ac7?w=72&h=72&dpr=2&q=80\",\n initials: \"LW\",\n label: \"Luna Wyen\",\n priority: \"High\",\n value: \"luna\",\n weight: 150,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=72&h=72&dpr=2&q=80\",\n initials: \"AC\",\n label: \"Alex Chen\",\n priority: \"Low\",\n value: \"alex\",\n weight: 100,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=72&h=72&dpr=2&q=80\",\n initials: \"SJ\",\n label: \"Sarah Johnson\",\n priority: \"Medium\",\n value: \"sarah\",\n weight: 50,\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=72&h=72&dpr=2&q=80\",\n initials: \"ED\",\n label: \"Emma Davis\",\n priority: \"Lowest\",\n value: \"emma\",\n weight: 100,\n },\n];\n\nexport default function Particle() {\n const [open, setOpen] = useState(false);\n const [selected, setSelected] = useState(\n teamMembers.slice(0, 2),\n );\n\n return (\n
\n {\n setSelected(value);\n setOpen(false);\n }}\n open={open}\n value={selected}\n >\n }\n />\n \n No team members found.\n \n {(item: TeamMember) => (\n \n {item.label}\n \n )}\n \n \n \n {selected.length > 0 && (\n
    \n {selected.map((member) => (\n \n \n \n {member.initials}\n \n {member.label}\n \n {member.priority}\n \n \n {member.weight}%\n \n \n setSelected((current) =>\n current.filter((item) => item.value !== member.value),\n )\n }\n size=\"icon-xs\"\n variant=\"ghost\"\n >\n \n \n \n ))}\n
\n )}\n
\n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-group-23.json b/apps/ui/public/r/p-group-23.json index ef090276a..b4094a953 100644 --- a/apps/ui/public/r/p-group-23.json +++ b/apps/ui/public/r/p-group-23.json @@ -12,7 +12,7 @@ "files": [ { "path": "registry/default/particles/p-group-23.tsx", - "content": "\"use client\";\n\nimport {\n ChevronsUpDownIcon,\n FunnelIcon,\n SearchIcon,\n XIcon,\n} from \"lucide-react\";\nimport { useState } from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport { Badge } from \"@/registry/default/ui/badge\";\nimport { Button, buttonVariants } from \"@/registry/default/ui/button\";\nimport {\n Combobox,\n ComboboxEmpty,\n ComboboxInput,\n ComboboxItem,\n ComboboxList,\n ComboboxPopup,\n ComboboxTrigger,\n} from \"@/registry/default/ui/combobox\";\nimport { Group, GroupSeparator, GroupText } from \"@/registry/default/ui/group\";\n\ntype FilterOption = {\n id: string;\n label: string;\n avatar?: string;\n};\n\nconst members: FilterOption[] = [\n {\n avatar:\n \"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=64&h=64&fit=crop&crop=faces\",\n id: \"alex-chen\",\n label: \"Alex Chen\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=64&h=64&fit=crop&crop=faces\",\n id: \"sarah-johnson\",\n label: \"Sarah Johnson\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=64&h=64&fit=crop&crop=faces\",\n id: \"marcus-williams\",\n label: \"Marcus Williams\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=64&h=64&fit=crop&crop=faces\",\n id: \"emma-davis\",\n label: \"Emma Davis\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=64&h=64&fit=crop&crop=faces\",\n id: \"james-miller\",\n label: \"James Miller\",\n },\n];\n\nfunction getInitials(name: string): string {\n const parts = name.trim().split(/\\s+/);\n if (parts.length === 1) {\n return parts[0]?.charAt(0).toUpperCase() ?? \"\";\n }\n const first = parts[0]?.charAt(0) ?? \"\";\n const last = parts[parts.length - 1]?.charAt(0) ?? \"\";\n return (first + last).toUpperCase();\n}\n\nfunction MemberAvatar({\n name,\n avatarUrl,\n className,\n}: {\n name: string;\n avatarUrl?: string;\n className?: string;\n}) {\n return (\n \n {avatarUrl ? : null}\n \n {getInitials(name)}\n \n \n );\n}\n\nexport default function Particle() {\n const [selectedMembers, setSelectedMembers] = useState(\n members.slice(0, 2),\n );\n\n const renderTriggerContent = () => {\n if (selectedMembers.length === 0) return \"Select\";\n const firstMember = selectedMembers[0];\n const remainingCount = selectedMembers.length - 1;\n\n return (\n
\n \n {firstMember?.label}\n {remainingCount > 0 && (\n \n +{remainingCount}\n \n )}\n
\n );\n };\n\n return (\n \n \n \n Member\n \n \n {\n if (Array.isArray(value)) {\n setSelectedMembers(value);\n }\n }}\n value={selectedMembers}\n >\n \n }\n >\n {renderTriggerContent()}\n {selectedMembers.length === 0 && (\n \n )}\n \n \n
\n }\n />\n
\n No members found.\n \n {(option: FilterOption) => (\n \n
\n \n {option.label}\n
\n
\n )}\n
\n
\n \n \n setSelectedMembers([])}\n size=\"icon-sm\"\n variant=\"outline\"\n >\n \n \n
\n );\n}\n", + "content": "\"use client\";\n\nimport {\n ChevronsUpDownIcon,\n FunnelIcon,\n SearchIcon,\n XIcon,\n} from \"lucide-react\";\nimport { useState } from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport { Badge } from \"@/registry/default/ui/badge\";\nimport { Button, buttonVariants } from \"@/registry/default/ui/button\";\nimport {\n Combobox,\n ComboboxEmpty,\n ComboboxInput,\n ComboboxItem,\n ComboboxList,\n ComboboxPopup,\n ComboboxTrigger,\n} from \"@/registry/default/ui/combobox\";\nimport { Group, GroupSeparator, GroupText } from \"@/registry/default/ui/group\";\n\ntype FilterOption = {\n id: string;\n label: string;\n avatar?: string;\n};\n\nconst members: FilterOption[] = [\n {\n avatar:\n \"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=64&h=64&fit=crop&crop=faces\",\n id: \"alex-chen\",\n label: \"Alex Chen\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=64&h=64&fit=crop&crop=faces\",\n id: \"sarah-johnson\",\n label: \"Sarah Johnson\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=64&h=64&fit=crop&crop=faces\",\n id: \"marcus-williams\",\n label: \"Marcus Williams\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=64&h=64&fit=crop&crop=faces\",\n id: \"emma-davis\",\n label: \"Emma Davis\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=64&h=64&fit=crop&crop=faces\",\n id: \"james-miller\",\n label: \"James Miller\",\n },\n];\n\nfunction getInitials(name: string): string {\n const parts = name.trim().split(/\\s+/);\n if (parts.length === 1) {\n return parts[0]?.charAt(0).toUpperCase() ?? \"\";\n }\n const first = parts[0]?.charAt(0) ?? \"\";\n const last = parts[parts.length - 1]?.charAt(0) ?? \"\";\n return (first + last).toUpperCase();\n}\n\nfunction MemberAvatar({\n name,\n avatarUrl,\n className,\n}: {\n name: string;\n avatarUrl?: string;\n className?: string;\n}) {\n return (\n \n {avatarUrl ? : null}\n {getInitials(name)}\n \n );\n}\n\nexport default function Particle() {\n const [selectedMembers, setSelectedMembers] = useState(\n members.slice(0, 2),\n );\n\n const renderTriggerContent = () => {\n if (selectedMembers.length === 0) return \"Select\";\n const firstMember = selectedMembers[0];\n const remainingCount = selectedMembers.length - 1;\n\n return (\n
\n \n {firstMember?.label}\n {remainingCount > 0 && (\n \n +{remainingCount}\n \n )}\n
\n );\n };\n\n return (\n \n \n \n Member\n \n \n {\n if (Array.isArray(value)) {\n setSelectedMembers(value);\n }\n }}\n value={selectedMembers}\n >\n \n }\n >\n {renderTriggerContent()}\n {selectedMembers.length === 0 && (\n \n )}\n \n \n
\n }\n />\n
\n No members found.\n \n {(option: FilterOption) => (\n \n
\n \n {option.label}\n
\n
\n )}\n
\n
\n \n \n setSelectedMembers([])}\n size=\"icon-sm\"\n variant=\"outline\"\n >\n \n \n
\n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-select-19.json b/apps/ui/public/r/p-select-19.json index 3a93969d1..4579fab58 100644 --- a/apps/ui/public/r/p-select-19.json +++ b/apps/ui/public/r/p-select-19.json @@ -9,7 +9,7 @@ "files": [ { "path": "registry/default/particles/p-select-19.tsx", - "content": "\"use client\";\n\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport {\n Select,\n SelectGroup,\n SelectGroupLabel,\n SelectItem,\n SelectPopup,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/default/ui/select\";\n\nconst users = [\n {\n avatar:\n \"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=72&h=72&dpr=2&q=80\",\n initials: \"JH\",\n label: \"Jenny Hamilton\",\n value: \"jenny\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1628157588553-5eeea00af15c?w=72&h=72&dpr=2&q=80\",\n initials: \"PS\",\n label: \"Paul Smith\",\n value: \"paul\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1655874819398-c6dfbec68ac7?w=72&h=72&dpr=2&q=80\",\n initials: \"LW\",\n label: \"Luna Wyen\",\n value: \"luna\",\n },\n];\n\nexport default function Particle() {\n return (\n \n );\n}\n", + "content": "\"use client\";\n\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport {\n Select,\n SelectGroup,\n SelectGroupLabel,\n SelectItem,\n SelectPopup,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/default/ui/select\";\n\nconst users = [\n {\n avatar:\n \"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=72&h=72&dpr=2&q=80\",\n initials: \"JH\",\n label: \"Jenny Hamilton\",\n value: \"jenny\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1628157588553-5eeea00af15c?w=72&h=72&dpr=2&q=80\",\n initials: \"PS\",\n label: \"Paul Smith\",\n value: \"paul\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1655874819398-c6dfbec68ac7?w=72&h=72&dpr=2&q=80\",\n initials: \"LW\",\n label: \"Luna Wyen\",\n value: \"luna\",\n },\n];\n\nexport default function Particle() {\n return (\n \n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-select-20.json b/apps/ui/public/r/p-select-20.json index 84d5952aa..6f7b7ec50 100644 --- a/apps/ui/public/r/p-select-20.json +++ b/apps/ui/public/r/p-select-20.json @@ -9,7 +9,7 @@ "files": [ { "path": "registry/default/particles/p-select-20.tsx", - "content": "\"use client\";\n\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport {\n Select,\n SelectItem,\n SelectPopup,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/default/ui/select\";\n\nconst users = [\n {\n avatar:\n \"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=72&h=72&dpr=2&q=80\",\n initials: \"JH\",\n label: \"Jenny Hamilton\",\n username: \"@jennycodes\",\n value: \"jenny\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1628157588553-5eeea00af15c?w=72&h=72&dpr=2&q=80\",\n initials: \"PS\",\n label: \"Paul Smith\",\n username: \"@paulsmith\",\n value: \"paul\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1655874819398-c6dfbec68ac7?w=72&h=72&dpr=2&q=80\",\n initials: \"LW\",\n label: \"Luna Wyen\",\n username: \"@wyen.luna\",\n value: \"luna\",\n },\n];\n\nexport default function Particle() {\n return (\n \n );\n}\n", + "content": "\"use client\";\n\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport {\n Select,\n SelectItem,\n SelectPopup,\n SelectTrigger,\n SelectValue,\n} from \"@/registry/default/ui/select\";\n\nconst users = [\n {\n avatar:\n \"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=72&h=72&dpr=2&q=80\",\n initials: \"JH\",\n label: \"Jenny Hamilton\",\n username: \"@jennycodes\",\n value: \"jenny\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1628157588553-5eeea00af15c?w=72&h=72&dpr=2&q=80\",\n initials: \"PS\",\n label: \"Paul Smith\",\n username: \"@paulsmith\",\n value: \"paul\",\n },\n {\n avatar:\n \"https://images.unsplash.com/photo-1655874819398-c6dfbec68ac7?w=72&h=72&dpr=2&q=80\",\n initials: \"LW\",\n label: \"Luna Wyen\",\n username: \"@wyen.luna\",\n value: \"luna\",\n },\n];\n\nexport default function Particle() {\n return (\n \n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/p-skeleton-1.json b/apps/ui/public/r/p-skeleton-1.json index 24cbbe520..cbb770a41 100644 --- a/apps/ui/public/r/p-skeleton-1.json +++ b/apps/ui/public/r/p-skeleton-1.json @@ -10,7 +10,7 @@ "files": [ { "path": "registry/default/particles/p-skeleton-1.tsx", - "content": "\"use client\";\n\nimport { UserRoundPlusIcon, UsersRoundIcon } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport { Button } from \"@/registry/default/ui/button\";\nimport { Skeleton } from \"@/registry/default/ui/skeleton\";\n\nconst users = [\n {\n delay: 3000,\n fallback: \"SJ\",\n followers: \"15k\",\n image:\n \"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=80&h=80&dpr=2&q=80\",\n name: \"Sarah Johnson\",\n role: \"Design Engineer\",\n },\n {\n delay: 4000,\n fallback: \"MA\",\n followers: \"8k\",\n image:\n \"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=80&h=80&dpr=2&q=80\",\n name: \"Mark Bennett Andersson\",\n role: \"Product Designer\",\n },\n {\n delay: 3400,\n fallback: \"AR\",\n followers: \"12k\",\n image:\n \"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=80&h=80&dpr=2&q=80\",\n name: \"Alex Rivera\",\n role: \"UI/UX Designer\",\n },\n];\n\nfunction UserCard({ delay, user }: { delay: number; user: (typeof users)[0] }) {\n const [isLoaded, setIsLoaded] = useState(false);\n\n useEffect(() => {\n const timer = setTimeout(() => {\n setIsLoaded(true);\n }, delay);\n\n return () => clearTimeout(timer);\n }, [delay]);\n\n if (!isLoaded) {\n return ;\n }\n\n return (\n <>\n \n \n {user.fallback}\n \n
\n

{user.name}

\n
\n {user.role}\n
\n \n \n {user.followers}\n followers\n \n
\n
\n
\n \n \n );\n}\n\nfunction UserCardSkeleton() {\n return (\n <>\n \n
\n \n
\n \n \n
\n
\n \n \n );\n}\n\nexport default function Particle() {\n return (\n
\n {users.map((user) => (\n
\n \n
\n ))}\n
\n );\n}\n", + "content": "\"use client\";\n\nimport { UserRoundPlusIcon, UsersRoundIcon } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport { Button } from \"@/registry/default/ui/button\";\nimport { Skeleton } from \"@/registry/default/ui/skeleton\";\n\nconst users = [\n {\n delay: 3000,\n fallback: \"SJ\",\n followers: \"15k\",\n image:\n \"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=80&h=80&dpr=2&q=80\",\n name: \"Sarah Johnson\",\n role: \"Design Engineer\",\n },\n {\n delay: 4000,\n fallback: \"MA\",\n followers: \"8k\",\n image:\n \"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=80&h=80&dpr=2&q=80\",\n name: \"Mark Bennett Andersson\",\n role: \"Product Designer\",\n },\n {\n delay: 3400,\n fallback: \"AR\",\n followers: \"12k\",\n image:\n \"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=80&h=80&dpr=2&q=80\",\n name: \"Alex Rivera\",\n role: \"UI/UX Designer\",\n },\n];\n\nfunction UserCard({ delay, user }: { delay: number; user: (typeof users)[0] }) {\n const [isLoaded, setIsLoaded] = useState(false);\n\n useEffect(() => {\n const timer = setTimeout(() => {\n setIsLoaded(true);\n }, delay);\n\n return () => clearTimeout(timer);\n }, [delay]);\n\n if (!isLoaded) {\n return ;\n }\n\n return (\n <>\n \n \n {user.fallback}\n \n
\n

{user.name}

\n
\n {user.role}\n
\n \n \n {user.followers}\n followers\n \n
\n
\n
\n \n \n );\n}\n\nfunction UserCardSkeleton() {\n return (\n <>\n \n
\n \n
\n \n \n
\n
\n \n \n );\n}\n\nexport default function Particle() {\n return (\n
\n {users.map((user) => (\n
\n \n
\n ))}\n
\n );\n}\n", "type": "registry:block" } ], diff --git a/apps/ui/public/r/registry.json b/apps/ui/public/r/registry.json index 796bc1326..42fcf51e7 100644 --- a/apps/ui/public/r/registry.json +++ b/apps/ui/public/r/registry.json @@ -1860,6 +1860,26 @@ ], "type": "registry:block" }, + { + "categories": [ + "avatar" + ], + "dependencies": [ + "lucide-react" + ], + "description": "Avatar with user icon fallback", + "files": [ + { + "path": "registry/default/particles/p-avatar-6.tsx", + "type": "registry:block" + } + ], + "name": "p-avatar-6", + "registryDependencies": [ + "@coss/avatar" + ], + "type": "registry:block" + }, { "categories": [ "avatar" @@ -1877,6 +1897,23 @@ ], "type": "registry:block" }, + { + "categories": [ + "avatar" + ], + "description": "Avatars sized from a parent element", + "files": [ + { + "path": "registry/default/particles/p-avatar-19.tsx", + "type": "registry:block" + } + ], + "name": "p-avatar-19", + "registryDependencies": [ + "@coss/avatar" + ], + "type": "registry:block" + }, { "categories": [ "avatar" @@ -1898,14 +1935,14 @@ "categories": [ "avatar" ], - "description": "Overlapping avatar group", + "description": "Avatar rendered as a button", "files": [ { - "path": "registry/default/particles/p-avatar-5.tsx", + "path": "registry/default/particles/p-avatar-17.tsx", "type": "registry:block" } ], - "name": "p-avatar-5", + "name": "p-avatar-17", "registryDependencies": [ "@coss/avatar" ], @@ -1915,22 +1952,37 @@ "categories": [ "avatar" ], - "dependencies": [ - "lucide-react" - ], - "description": "Avatar with user icon fallback", + "description": "Avatar rendered as a link", "files": [ { - "path": "registry/default/particles/p-avatar-6.tsx", + "path": "registry/default/particles/p-avatar-18.tsx", "type": "registry:block" } ], - "name": "p-avatar-6", + "name": "p-avatar-18", "registryDependencies": [ "@coss/avatar" ], "type": "registry:block" }, + { + "categories": [ + "avatar" + ], + "description": "Avatar with a tooltip", + "files": [ + { + "path": "registry/default/particles/p-avatar-20.tsx", + "type": "registry:block" + } + ], + "name": "p-avatar-20", + "registryDependencies": [ + "@coss/avatar", + "@coss/tooltip" + ], + "type": "registry:block" + }, { "categories": [ "avatar" @@ -2035,6 +2087,75 @@ ], "type": "registry:block" }, + { + "categories": [ + "avatar" + ], + "description": "Overlapping avatar group", + "files": [ + { + "path": "registry/default/particles/p-avatar-5.tsx", + "type": "registry:block" + } + ], + "name": "p-avatar-5", + "registryDependencies": [ + "@coss/avatar" + ], + "type": "registry:block" + }, + { + "categories": [ + "avatar" + ], + "description": "Avatar group with a count and the last avatar on top", + "files": [ + { + "path": "registry/default/particles/p-avatar-16.tsx", + "type": "registry:block" + } + ], + "name": "p-avatar-16", + "registryDependencies": [ + "@coss/avatar" + ], + "type": "registry:block" + }, + { + "categories": [ + "avatar" + ], + "description": "Avatar group with the last avatar on top", + "files": [ + { + "path": "registry/default/particles/p-avatar-15.tsx", + "type": "registry:block" + } + ], + "name": "p-avatar-15", + "registryDependencies": [ + "@coss/avatar" + ], + "type": "registry:block" + }, + { + "categories": [ + "avatar" + ], + "description": "Avatar group with a tooltip per member", + "files": [ + { + "path": "registry/default/particles/p-avatar-21.tsx", + "type": "registry:block" + } + ], + "name": "p-avatar-21", + "registryDependencies": [ + "@coss/avatar", + "@coss/tooltip" + ], + "type": "registry:block" + }, { "categories": [ "avatar" diff --git a/apps/ui/registry.json b/apps/ui/registry.json index 796bc1326..42fcf51e7 100644 --- a/apps/ui/registry.json +++ b/apps/ui/registry.json @@ -1860,6 +1860,26 @@ ], "type": "registry:block" }, + { + "categories": [ + "avatar" + ], + "dependencies": [ + "lucide-react" + ], + "description": "Avatar with user icon fallback", + "files": [ + { + "path": "registry/default/particles/p-avatar-6.tsx", + "type": "registry:block" + } + ], + "name": "p-avatar-6", + "registryDependencies": [ + "@coss/avatar" + ], + "type": "registry:block" + }, { "categories": [ "avatar" @@ -1877,6 +1897,23 @@ ], "type": "registry:block" }, + { + "categories": [ + "avatar" + ], + "description": "Avatars sized from a parent element", + "files": [ + { + "path": "registry/default/particles/p-avatar-19.tsx", + "type": "registry:block" + } + ], + "name": "p-avatar-19", + "registryDependencies": [ + "@coss/avatar" + ], + "type": "registry:block" + }, { "categories": [ "avatar" @@ -1898,14 +1935,14 @@ "categories": [ "avatar" ], - "description": "Overlapping avatar group", + "description": "Avatar rendered as a button", "files": [ { - "path": "registry/default/particles/p-avatar-5.tsx", + "path": "registry/default/particles/p-avatar-17.tsx", "type": "registry:block" } ], - "name": "p-avatar-5", + "name": "p-avatar-17", "registryDependencies": [ "@coss/avatar" ], @@ -1915,22 +1952,37 @@ "categories": [ "avatar" ], - "dependencies": [ - "lucide-react" - ], - "description": "Avatar with user icon fallback", + "description": "Avatar rendered as a link", "files": [ { - "path": "registry/default/particles/p-avatar-6.tsx", + "path": "registry/default/particles/p-avatar-18.tsx", "type": "registry:block" } ], - "name": "p-avatar-6", + "name": "p-avatar-18", "registryDependencies": [ "@coss/avatar" ], "type": "registry:block" }, + { + "categories": [ + "avatar" + ], + "description": "Avatar with a tooltip", + "files": [ + { + "path": "registry/default/particles/p-avatar-20.tsx", + "type": "registry:block" + } + ], + "name": "p-avatar-20", + "registryDependencies": [ + "@coss/avatar", + "@coss/tooltip" + ], + "type": "registry:block" + }, { "categories": [ "avatar" @@ -2035,6 +2087,75 @@ ], "type": "registry:block" }, + { + "categories": [ + "avatar" + ], + "description": "Overlapping avatar group", + "files": [ + { + "path": "registry/default/particles/p-avatar-5.tsx", + "type": "registry:block" + } + ], + "name": "p-avatar-5", + "registryDependencies": [ + "@coss/avatar" + ], + "type": "registry:block" + }, + { + "categories": [ + "avatar" + ], + "description": "Avatar group with a count and the last avatar on top", + "files": [ + { + "path": "registry/default/particles/p-avatar-16.tsx", + "type": "registry:block" + } + ], + "name": "p-avatar-16", + "registryDependencies": [ + "@coss/avatar" + ], + "type": "registry:block" + }, + { + "categories": [ + "avatar" + ], + "description": "Avatar group with the last avatar on top", + "files": [ + { + "path": "registry/default/particles/p-avatar-15.tsx", + "type": "registry:block" + } + ], + "name": "p-avatar-15", + "registryDependencies": [ + "@coss/avatar" + ], + "type": "registry:block" + }, + { + "categories": [ + "avatar" + ], + "description": "Avatar group with a tooltip per member", + "files": [ + { + "path": "registry/default/particles/p-avatar-21.tsx", + "type": "registry:block" + } + ], + "name": "p-avatar-21", + "registryDependencies": [ + "@coss/avatar", + "@coss/tooltip" + ], + "type": "registry:block" + }, { "categories": [ "avatar" diff --git a/apps/ui/registry/__index__.tsx b/apps/ui/registry/__index__.tsx index 3e7665705..e69406f43 100644 --- a/apps/ui/registry/__index__.tsx +++ b/apps/ui/registry/__index__.tsx @@ -1707,6 +1707,24 @@ export const Index: Record = { categories: ["avatar"], meta: undefined, }, + "p-avatar-6": { + name: "p-avatar-6", + description: "Avatar with user icon fallback", + type: "registry:block", + registryDependencies: ["@coss/avatar"], + files: [{ + path: "registry/default/particles/p-avatar-6.tsx", + type: "registry:block", + target: "" + }], + component: React.lazy(async () => { + const mod = await import("@/registry/default/particles/p-avatar-6.tsx") + const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + return { default: mod.default || mod[exportName] } + }), + categories: ["avatar"], + meta: undefined, + }, "p-avatar-3": { name: "p-avatar-3", description: "Avatars with different sizes", @@ -1725,6 +1743,24 @@ export const Index: Record = { categories: ["avatar"], meta: undefined, }, + "p-avatar-19": { + name: "p-avatar-19", + description: "Avatars sized from a parent element", + type: "registry:block", + registryDependencies: ["@coss/avatar"], + files: [{ + path: "registry/default/particles/p-avatar-19.tsx", + type: "registry:block", + target: "" + }], + component: React.lazy(async () => { + const mod = await import("@/registry/default/particles/p-avatar-19.tsx") + const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + return { default: mod.default || mod[exportName] } + }), + categories: ["avatar"], + meta: undefined, + }, "p-avatar-4": { name: "p-avatar-4", description: "Avatars with different radii", @@ -1743,36 +1779,54 @@ export const Index: Record = { categories: ["avatar"], meta: undefined, }, - "p-avatar-5": { - name: "p-avatar-5", - description: "Overlapping avatar group", + "p-avatar-17": { + name: "p-avatar-17", + description: "Avatar rendered as a button", type: "registry:block", registryDependencies: ["@coss/avatar"], files: [{ - path: "registry/default/particles/p-avatar-5.tsx", + path: "registry/default/particles/p-avatar-17.tsx", type: "registry:block", target: "" }], component: React.lazy(async () => { - const mod = await import("@/registry/default/particles/p-avatar-5.tsx") + const mod = await import("@/registry/default/particles/p-avatar-17.tsx") const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name return { default: mod.default || mod[exportName] } }), categories: ["avatar"], meta: undefined, }, - "p-avatar-6": { - name: "p-avatar-6", - description: "Avatar with user icon fallback", + "p-avatar-18": { + name: "p-avatar-18", + description: "Avatar rendered as a link", type: "registry:block", registryDependencies: ["@coss/avatar"], files: [{ - path: "registry/default/particles/p-avatar-6.tsx", + path: "registry/default/particles/p-avatar-18.tsx", type: "registry:block", target: "" }], component: React.lazy(async () => { - const mod = await import("@/registry/default/particles/p-avatar-6.tsx") + const mod = await import("@/registry/default/particles/p-avatar-18.tsx") + const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + return { default: mod.default || mod[exportName] } + }), + categories: ["avatar"], + meta: undefined, + }, + "p-avatar-20": { + name: "p-avatar-20", + description: "Avatar with a tooltip", + type: "registry:block", + registryDependencies: ["@coss/avatar","@coss/tooltip"], + files: [{ + path: "registry/default/particles/p-avatar-20.tsx", + type: "registry:block", + target: "" + }], + component: React.lazy(async () => { + const mod = await import("@/registry/default/particles/p-avatar-20.tsx") const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name return { default: mod.default || mod[exportName] } }), @@ -1887,6 +1941,78 @@ export const Index: Record = { categories: ["avatar"], meta: undefined, }, + "p-avatar-5": { + name: "p-avatar-5", + description: "Overlapping avatar group", + type: "registry:block", + registryDependencies: ["@coss/avatar"], + files: [{ + path: "registry/default/particles/p-avatar-5.tsx", + type: "registry:block", + target: "" + }], + component: React.lazy(async () => { + const mod = await import("@/registry/default/particles/p-avatar-5.tsx") + const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + return { default: mod.default || mod[exportName] } + }), + categories: ["avatar"], + meta: undefined, + }, + "p-avatar-16": { + name: "p-avatar-16", + description: "Avatar group with a count and the last avatar on top", + type: "registry:block", + registryDependencies: ["@coss/avatar"], + files: [{ + path: "registry/default/particles/p-avatar-16.tsx", + type: "registry:block", + target: "" + }], + component: React.lazy(async () => { + const mod = await import("@/registry/default/particles/p-avatar-16.tsx") + const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + return { default: mod.default || mod[exportName] } + }), + categories: ["avatar"], + meta: undefined, + }, + "p-avatar-15": { + name: "p-avatar-15", + description: "Avatar group with the last avatar on top", + type: "registry:block", + registryDependencies: ["@coss/avatar"], + files: [{ + path: "registry/default/particles/p-avatar-15.tsx", + type: "registry:block", + target: "" + }], + component: React.lazy(async () => { + const mod = await import("@/registry/default/particles/p-avatar-15.tsx") + const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + return { default: mod.default || mod[exportName] } + }), + categories: ["avatar"], + meta: undefined, + }, + "p-avatar-21": { + name: "p-avatar-21", + description: "Avatar group with a tooltip per member", + type: "registry:block", + registryDependencies: ["@coss/avatar","@coss/tooltip"], + files: [{ + path: "registry/default/particles/p-avatar-21.tsx", + type: "registry:block", + target: "" + }], + component: React.lazy(async () => { + const mod = await import("@/registry/default/particles/p-avatar-21.tsx") + const exportName = Object.keys(mod).find(key => typeof mod[key] === 'function' || typeof mod[key] === 'object') || item.name + return { default: mod.default || mod[exportName] } + }), + categories: ["avatar"], + meta: undefined, + }, "p-avatar-13": { name: "p-avatar-13", description: "Small overlapping avatar group", diff --git a/apps/ui/registry/default/particles/p-avatar-11.tsx b/apps/ui/registry/default/particles/p-avatar-11.tsx index 44a73f3c1..58c637083 100644 --- a/apps/ui/registry/default/particles/p-avatar-11.tsx +++ b/apps/ui/registry/default/particles/p-avatar-11.tsx @@ -13,7 +13,7 @@ export default function Particle() { alt="User" src="https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=128&h=128&dpr=2&q=80" /> - LT + LT - + + - U1 + FK - + - U2 + NE - + - U3 + EW - + ); } diff --git a/apps/ui/registry/default/particles/p-avatar-14.tsx b/apps/ui/registry/default/particles/p-avatar-14.tsx index a189c73f3..e89fdd0c0 100644 --- a/apps/ui/registry/default/particles/p-avatar-14.tsx +++ b/apps/ui/registry/default/particles/p-avatar-14.tsx @@ -1,33 +1,34 @@ import { Avatar, AvatarFallback, + AvatarGroup, AvatarImage, } from "@/registry/default/ui/avatar"; export default function Particle() { return ( -
- + + - U1 + FK - + - U2 + NE - + - U3 + EW -
+ ); } diff --git a/apps/ui/registry/default/particles/p-avatar-15.tsx b/apps/ui/registry/default/particles/p-avatar-15.tsx new file mode 100644 index 000000000..b1f3fa1c5 --- /dev/null +++ b/apps/ui/registry/default/particles/p-avatar-15.tsx @@ -0,0 +1,34 @@ +import { + Avatar, + AvatarFallback, + AvatarGroup, + AvatarImage, +} from "@/registry/default/ui/avatar"; + +export default function Particle() { + return ( + + + + FK + + + + NE + + + + EW + + + ); +} diff --git a/apps/ui/registry/default/particles/p-avatar-16.tsx b/apps/ui/registry/default/particles/p-avatar-16.tsx new file mode 100644 index 000000000..dae6de617 --- /dev/null +++ b/apps/ui/registry/default/particles/p-avatar-16.tsx @@ -0,0 +1,36 @@ +import { + Avatar, + AvatarFallback, + AvatarGroup, + AvatarGroupCount, + AvatarImage, +} from "@/registry/default/ui/avatar"; + +export default function Particle() { + return ( + + + + FK + + + + NE + + + + EW + + +2 + + ); +} diff --git a/apps/ui/registry/default/particles/p-avatar-17.tsx b/apps/ui/registry/default/particles/p-avatar-17.tsx new file mode 100644 index 000000000..176829608 --- /dev/null +++ b/apps/ui/registry/default/particles/p-avatar-17.tsx @@ -0,0 +1,20 @@ +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@/registry/default/ui/avatar"; + +export default function Particle() { + return ( + } + > + + FK + + ); +} diff --git a/apps/ui/registry/default/particles/p-avatar-18.tsx b/apps/ui/registry/default/particles/p-avatar-18.tsx new file mode 100644 index 000000000..adb965b85 --- /dev/null +++ b/apps/ui/registry/default/particles/p-avatar-18.tsx @@ -0,0 +1,25 @@ +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@/registry/default/ui/avatar"; + +const profileLink = ( + // biome-ignore lint/a11y/useAnchorContent: Avatar supplies the link content through the render prop. +
+); + +export default function Particle() { + return ( + + + NE + + ); +} diff --git a/apps/ui/registry/default/particles/p-avatar-19.tsx b/apps/ui/registry/default/particles/p-avatar-19.tsx new file mode 100644 index 000000000..7e85f7f28 --- /dev/null +++ b/apps/ui/registry/default/particles/p-avatar-19.tsx @@ -0,0 +1,47 @@ +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@/registry/default/ui/avatar"; + +const people = [ + { + initials: "FK", + name: "Frida Kahlo", + photo: "photo-1543610892-0b1f7e6d8ac1", + role: "Design", + }, + { + initials: "NE", + name: "Nadia Ekman", + photo: "photo-1628157588553-5eeea00af15c", + role: "Engineering", + }, + { initials: "EW", name: "Eli Wren", photo: "", role: "Research" }, +]; + +export default function Particle() { + return ( +
+ {people.map((person) => ( +
+ + {person.photo ? ( + + ) : null} + {person.initials} + +
+ {person.name} + + {person.role} + +
+
+ ))} +
+ ); +} diff --git a/apps/ui/registry/default/particles/p-avatar-20.tsx b/apps/ui/registry/default/particles/p-avatar-20.tsx new file mode 100644 index 000000000..43a69eb56 --- /dev/null +++ b/apps/ui/registry/default/particles/p-avatar-20.tsx @@ -0,0 +1,25 @@ +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@/registry/default/ui/avatar"; +import { + Tooltip, + TooltipPopup, + TooltipTrigger, +} from "@/registry/default/ui/tooltip"; + +export default function Particle() { + return ( + + }> + + FK + + Frida Kahlo + + ); +} diff --git a/apps/ui/registry/default/particles/p-avatar-21.tsx b/apps/ui/registry/default/particles/p-avatar-21.tsx new file mode 100644 index 000000000..88e7935d1 --- /dev/null +++ b/apps/ui/registry/default/particles/p-avatar-21.tsx @@ -0,0 +1,50 @@ +import { + Avatar, + AvatarFallback, + AvatarGroup, + AvatarGroupCount, + AvatarImage, +} from "@/registry/default/ui/avatar"; +import { + Tooltip, + TooltipPopup, + TooltipTrigger, +} from "@/registry/default/ui/tooltip"; + +const people = [ + { + initials: "FK", + name: "Frida Kahlo", + photo: "photo-1543610892-0b1f7e6d8ac1", + }, + { + initials: "NE", + name: "Nadia Ekman", + photo: "photo-1628157588553-5eeea00af15c", + }, + { + initials: "EW", + name: "Eli Wren", + photo: "photo-1655874819398-c6dfbec68ac7", + }, +]; + +export default function Particle() { + return ( + + {people.map((person) => ( + + }> + + {person.initials} + + {person.name} + + ))} + +2 + + ); +} diff --git a/apps/ui/registry/default/particles/p-avatar-3.tsx b/apps/ui/registry/default/particles/p-avatar-3.tsx index 26c6cdaa8..a0a9e22af 100644 --- a/apps/ui/registry/default/particles/p-avatar-3.tsx +++ b/apps/ui/registry/default/particles/p-avatar-3.tsx @@ -6,28 +6,61 @@ import { export default function Particle() { return ( -
- - - AV - - - - AV - - - - AV - +
+
+ + + AV + + + + AV + + + + AV + + + + AV + + + + AV + +
+
+ + AV + + + AV + + + AV + + + AV + + + AV + +
); } diff --git a/apps/ui/registry/default/particles/p-avatar-5.tsx b/apps/ui/registry/default/particles/p-avatar-5.tsx index 13ca6396b..bfb4e6348 100644 --- a/apps/ui/registry/default/particles/p-avatar-5.tsx +++ b/apps/ui/registry/default/particles/p-avatar-5.tsx @@ -1,33 +1,36 @@ import { Avatar, AvatarFallback, + AvatarGroup, + AvatarGroupCount, AvatarImage, } from "@/registry/default/ui/avatar"; export default function Particle() { return ( -
- + + - U1 + FK - + - U2 + NE - + - U3 + EW -
+ +2 + ); } diff --git a/apps/ui/registry/default/particles/p-avatar-9.tsx b/apps/ui/registry/default/particles/p-avatar-9.tsx index 89955462c..316574702 100644 --- a/apps/ui/registry/default/particles/p-avatar-9.tsx +++ b/apps/ui/registry/default/particles/p-avatar-9.tsx @@ -12,7 +12,7 @@ export default function Particle() { alt="User" src="https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?w=128&h=128&dpr=2&q=80" /> - LT + LT Online - + - + - - {member.initials} - + {member.initials} {member.label} diff --git a/apps/ui/registry/default/particles/p-combobox-20.tsx b/apps/ui/registry/default/particles/p-combobox-20.tsx index 04670f1f8..605335232 100644 --- a/apps/ui/registry/default/particles/p-combobox-20.tsx +++ b/apps/ui/registry/default/particles/p-combobox-20.tsx @@ -127,11 +127,9 @@ export default function Particle() { className="flex items-center gap-2 p-1 ps-2 text-base sm:text-sm" key={member.value} > - + - - {member.initials} - + {member.initials} {member.label} diff --git a/apps/ui/registry/default/particles/p-group-23.tsx b/apps/ui/registry/default/particles/p-group-23.tsx index b65178eac..cd7aa9509 100644 --- a/apps/ui/registry/default/particles/p-group-23.tsx +++ b/apps/ui/registry/default/particles/p-group-23.tsx @@ -85,11 +85,9 @@ function MemberAvatar({ className?: string; }) { return ( - + {avatarUrl ? : null} - - {getInitials(name)} - + {getInitials(name)} ); } diff --git a/apps/ui/registry/default/particles/p-select-19.tsx b/apps/ui/registry/default/particles/p-select-19.tsx index 73d14a25a..716d89f41 100644 --- a/apps/ui/registry/default/particles/p-select-19.tsx +++ b/apps/ui/registry/default/particles/p-select-19.tsx @@ -46,11 +46,9 @@ export default function Particle() { {(item) => ( - + - - {item.initials} - + {item.initials} {item.label} @@ -63,11 +61,9 @@ export default function Particle() { {users.map((item) => ( - + - - {item.initials} - + {item.initials} {item.label} diff --git a/apps/ui/registry/default/particles/p-select-20.tsx b/apps/ui/registry/default/particles/p-select-20.tsx index e84222afe..7ed36c047 100644 --- a/apps/ui/registry/default/particles/p-select-20.tsx +++ b/apps/ui/registry/default/particles/p-select-20.tsx @@ -47,7 +47,7 @@ export default function Particle() { {(item) => ( - + {item.initials} @@ -65,7 +65,7 @@ export default function Particle() { {users.map((item) => ( - + {item.initials} diff --git a/apps/ui/registry/default/particles/p-skeleton-1.tsx b/apps/ui/registry/default/particles/p-skeleton-1.tsx index 50cec461e..f8b4d14f2 100644 --- a/apps/ui/registry/default/particles/p-skeleton-1.tsx +++ b/apps/ui/registry/default/particles/p-skeleton-1.tsx @@ -57,7 +57,7 @@ function UserCard({ delay, user }: { delay: number; user: (typeof users)[0] }) { return ( <> - + {user.fallback} diff --git a/apps/ui/registry/default/ui/avatar.tsx b/apps/ui/registry/default/ui/avatar.tsx index 1e9c551e0..7292973e5 100644 --- a/apps/ui/registry/default/ui/avatar.tsx +++ b/apps/ui/registry/default/ui/avatar.tsx @@ -4,6 +4,9 @@ import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"; import type React from "react"; import { cn } from "@/registry/default/lib/utils"; +const AVATAR_GROUP_CUT = + "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2 2'%3E%3Ccircle cx='1' cy='1' r='1'/%3E%3C/svg%3E\")"; + export function Avatar({ className, ...props @@ -11,7 +14,7 @@ export function Avatar({ return ( { + topmost?: "first" | "last"; +} + +export function AvatarGroup({ + className, + style, + topmost = "first", + ...props +}: AvatarGroupProps): React.ReactElement { + return ( +
+ ); +} + +export function AvatarGroupCount({ + children, + className, + ...props +}: AvatarPrimitive.Root.Props): React.ReactElement { + return ( + + {children} + + ); +} + export { AvatarPrimitive }; diff --git a/apps/ui/registry/registry-particles.ts b/apps/ui/registry/registry-particles.ts index 6cd9b954a..7f7cbcbfe 100644 --- a/apps/ui/registry/registry-particles.ts +++ b/apps/ui/registry/registry-particles.ts @@ -387,6 +387,15 @@ export const particles: ParticleItem[] = [ registryDependencies: ["@coss/avatar"], type: "registry:block", }, + { + categories: categories("avatar"), + dependencies: ["lucide-react"], + description: "Avatar with user icon fallback", + files: [{ path: "particles/p-avatar-6.tsx", type: "registry:block" }], + name: "p-avatar-6", + registryDependencies: ["@coss/avatar"], + type: "registry:block", + }, { categories: categories("avatar"), description: "Avatars with different sizes", @@ -395,6 +404,14 @@ export const particles: ParticleItem[] = [ registryDependencies: ["@coss/avatar"], type: "registry:block", }, + { + categories: categories("avatar"), + description: "Avatars sized from a parent element", + files: [{ path: "particles/p-avatar-19.tsx", type: "registry:block" }], + name: "p-avatar-19", + registryDependencies: ["@coss/avatar"], + type: "registry:block", + }, { categories: categories("avatar"), description: "Avatars with different radii", @@ -405,21 +422,28 @@ export const particles: ParticleItem[] = [ }, { categories: categories("avatar"), - description: "Overlapping avatar group", - files: [{ path: "particles/p-avatar-5.tsx", type: "registry:block" }], - name: "p-avatar-5", + description: "Avatar rendered as a button", + files: [{ path: "particles/p-avatar-17.tsx", type: "registry:block" }], + name: "p-avatar-17", registryDependencies: ["@coss/avatar"], type: "registry:block", }, { categories: categories("avatar"), - dependencies: ["lucide-react"], - description: "Avatar with user icon fallback", - files: [{ path: "particles/p-avatar-6.tsx", type: "registry:block" }], - name: "p-avatar-6", + description: "Avatar rendered as a link", + files: [{ path: "particles/p-avatar-18.tsx", type: "registry:block" }], + name: "p-avatar-18", registryDependencies: ["@coss/avatar"], type: "registry:block", }, + { + categories: categories("avatar"), + description: "Avatar with a tooltip", + files: [{ path: "particles/p-avatar-20.tsx", type: "registry:block" }], + name: "p-avatar-20", + registryDependencies: ["@coss/avatar", "@coss/tooltip"], + type: "registry:block", + }, { categories: categories("avatar"), description: "Avatar with emerald status dot", @@ -468,6 +492,38 @@ export const particles: ParticleItem[] = [ registryDependencies: ["@coss/avatar"], type: "registry:block", }, + { + categories: categories("avatar"), + description: "Overlapping avatar group", + files: [{ path: "particles/p-avatar-5.tsx", type: "registry:block" }], + name: "p-avatar-5", + registryDependencies: ["@coss/avatar"], + type: "registry:block", + }, + { + categories: categories("avatar"), + description: "Avatar group with a count and the last avatar on top", + files: [{ path: "particles/p-avatar-16.tsx", type: "registry:block" }], + name: "p-avatar-16", + registryDependencies: ["@coss/avatar"], + type: "registry:block", + }, + { + categories: categories("avatar"), + description: "Avatar group with the last avatar on top", + files: [{ path: "particles/p-avatar-15.tsx", type: "registry:block" }], + name: "p-avatar-15", + registryDependencies: ["@coss/avatar"], + type: "registry:block", + }, + { + categories: categories("avatar"), + description: "Avatar group with a tooltip per member", + files: [{ path: "particles/p-avatar-21.tsx", type: "registry:block" }], + name: "p-avatar-21", + registryDependencies: ["@coss/avatar", "@coss/tooltip"], + type: "registry:block", + }, { categories: categories("avatar"), description: "Small overlapping avatar group", diff --git a/packages/ui/src/components/avatar.tsx b/packages/ui/src/components/avatar.tsx index 6f658c559..23538db7b 100644 --- a/packages/ui/src/components/avatar.tsx +++ b/packages/ui/src/components/avatar.tsx @@ -4,6 +4,9 @@ import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"; import { cn } from "@coss/ui/lib/utils"; import type React from "react"; +const AVATAR_GROUP_CUT = + "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2 2'%3E%3Ccircle cx='1' cy='1' r='1'/%3E%3C/svg%3E\")"; + export function Avatar({ className, ...props @@ -11,7 +14,7 @@ export function Avatar({ return ( { + topmost?: "first" | "last"; +} + +export function AvatarGroup({ + className, + style, + topmost = "first", + ...props +}: AvatarGroupProps): React.ReactElement { + return ( +
+ ); +} + +export function AvatarGroupCount({ + children, + className, + ...props +}: AvatarPrimitive.Root.Props): React.ReactElement { + return ( + + {children} + + ); +} + export { AvatarPrimitive };