Feed Post
Social post card: avatar header, body, attached index item and stat row.
import { FeedPost, FeedPostContent, FeedPostFooter, FeedPostHeader, FeedPostIndex, FeedPostStat,} from "@/components/feed-post/feed-post";import { Button } from "@/components/ui/button";import { IconPlaceholder } from "@/edmi/icon-placeholder";
export default function Demo() { return ( <FeedPost className="w-[460px] max-w-full"> <FeedPostHeader name="Dewi Lestari" handle="@dewi" time="2h" initials="DL" /> <FeedPostContent> Rebalanced MAG4 after NVDAx drifted to 32.4%. Fees this week: 4.1 USDC. </FeedPostContent> <FeedPostIndex icon={ <IconPlaceholder lucide="ChartLineIcon" tabler="IconChartLine" hugeicons="ChartIcon" phosphor="ChartLineIcon" remixicon="RiLineChartLine" /> } title="MAG4 · Magnificent Four" description="$0.9998 · +0.53%" action={<Button size="sm">Join</Button>} /> <FeedPostFooter> <FeedPostStat> <IconPlaceholder lucide="HeartIcon" tabler="IconBell" hugeicons="Notification02Icon" phosphor="HeartIcon" remixicon="RiHeartLine" /> 24 </FeedPostStat> <FeedPostStat> <IconPlaceholder lucide="ListIcon" tabler="IconListDetails" hugeicons="Menu01Icon" phosphor="ListIcon" remixicon="RiListUnordered" /> 6 </FeedPostStat> <FeedPostStat> <IconPlaceholder lucide="LinkIcon" tabler="IconLink" hugeicons="LinkIcon" phosphor="LinkIcon" remixicon="RiLinksLine" /> Share </FeedPostStat> </FeedPostFooter> </FeedPost> );}<script setup lang="ts">import { ChartLineIcon, HeartIcon, GlobeIcon, ListIcon } from "@lucide/vue";import { Button } from "@/components/ui/button";import { FeedPost, FeedPostContent, FeedPostFooter, FeedPostHeader, FeedPostIndex, FeedPostStat,} from "@/components/ui/feed-post";</script>
<template> <FeedPost class="w-[460px] max-w-full"> <FeedPostHeader name="Dewi Lestari" handle="@dewi" time="2h" initials="DL" /> <FeedPostContent> Rebalanced MAG4 after NVDAx drifted to 32.4%. Fees this week: 4.1 USDC. </FeedPostContent> <FeedPostIndex title="MAG4 · Magnificent Four" description="$0.9998 · +0.53%"> <template #icon><ChartLineIcon /></template> <template #action><Button size="sm">Join</Button></template> </FeedPostIndex> <FeedPostFooter> <FeedPostStat><HeartIcon />24</FeedPostStat> <FeedPostStat><ListIcon />6</FeedPostStat> <FeedPostStat><GlobeIcon />Share</FeedPostStat> </FeedPostFooter> </FeedPost></template><script lang="ts"> import { Button } from "$lib/components/ui/button"; import * as FeedPost from "$lib/components/ui/feed-post"; import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte";</script>
<FeedPost.Root class="w-[460px] max-w-full"> <FeedPost.Header name="Dewi Lestari" handle="@dewi" time="2h" initials="DL" /> <FeedPost.Content> Rebalanced MAG4 after NVDAx drifted to 32.4%. Fees this week: 4.1 USDC. </FeedPost.Content> <FeedPost.Index title="MAG4 · Magnificent Four" description="$0.9998 · +0.53%"> {#snippet icon()} <IconPlaceholder lucide="ChartLineIcon" tabler="IconChartLine" hugeicons="ChartIcon" phosphor="ChartLineIcon" remixicon="RiLineChartLine" /> {/snippet} {#snippet action()} <Button size="sm">Join</Button> {/snippet} </FeedPost.Index> <FeedPost.Footer> <FeedPost.Stat> <IconPlaceholder lucide="HeartIcon" tabler="IconBell" hugeicons="Notification02Icon" phosphor="HeartIcon" remixicon="RiHeartLine" /> 24 </FeedPost.Stat> <FeedPost.Stat> <IconPlaceholder lucide="ListIcon" tabler="IconListDetails" hugeicons="Menu01Icon" phosphor="ListIcon" remixicon="RiListUnordered" /> 6 </FeedPost.Stat> <FeedPost.Stat> <IconPlaceholder lucide="LinkIcon" tabler="IconLink" hugeicons="LinkIcon" phosphor="LinkIcon" remixicon="RiLinksLine" /> Share </FeedPost.Stat> </FeedPost.Footer></FeedPost.Root>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/feed-postpnpm dlx shadcn@latest add @edmi-ui/feed-postyarn dlx shadcn@latest add @edmi-ui/feed-postbunx shadcn@latest add @edmi-ui/feed-postPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
avatar,button,card,item,elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/feed-postpnpm dlx shadcn-vue@latest add @edmi-ui/feed-postyarn dlx shadcn-vue@latest add @edmi-ui/feed-postbunx shadcn-vue@latest add @edmi-ui/feed-postPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @lucide/vueTerminal window pnpm add @lucide/vueTerminal window yarn add @lucide/vueTerminal window bun add @lucide/vueAdd the Edmi components this one builds on:
avatar,button,card,item,elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/feed-post.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/feed-post.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/feed-post.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/feed-post.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
avatar,button,card,item,elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
import { FeedPost, FeedPostContent, FeedPostHeader, FeedPostIndex,} from "@/components/feed-post/feed-post";import { Button } from "@/components/ui/button";
<FeedPost className="w-[460px] max-w-full"> <FeedPostHeader name="Dewi Lestari" handle="@dewi" time="2h" initials="DL" /> <FeedPostContent> Rebalanced MAG4 after NVDAx drifted to 32.4%. Fees this week: 4.1 USDC. </FeedPostContent> <FeedPostIndex icon={ <ChartLineIcon /> } title="MAG4 · Magnificent Four" description="$0.9998 · +0.53%" action={<Button size="sm">Join</Button>} /> {/* … */}</FeedPost><script setup lang="ts">import { Button } from "@/components/ui/button";import { FeedPost, FeedPostContent, FeedPostFooter, FeedPostHeader, FeedPostIndex, FeedPostStat,} from "@/components/ui/feed-post";</script>
<template> <FeedPost class="w-[460px] max-w-full"> <FeedPostHeader name="Dewi Lestari" handle="@dewi" time="2h" initials="DL" /> <FeedPostContent> Rebalanced MAG4 after NVDAx drifted to 32.4%. Fees this week: 4.1 USDC. </FeedPostContent> <FeedPostIndex title="MAG4 · Magnificent Four" description="$0.9998 · +0.53%"> <template #icon><ChartLineIcon /></template> <template #action><Button size="sm">Join</Button></template> </FeedPostIndex> <FeedPostFooter> <FeedPostStat><HeartIcon />24</FeedPostStat> <FeedPostStat><ListIcon />6</FeedPostStat> <FeedPostStat><GlobeIcon />Share</FeedPostStat> </FeedPostFooter> </FeedPost></template><script lang="ts"> import * as FeedPost from "$lib/components/ui/feed-post"; import { Button } from "$lib/components/ui/button";</script>
<FeedPost.Root class="w-[460px] max-w-full"> <FeedPost.Header name="Dewi Lestari" handle="@dewi" time="2h" initials="DL" /> <FeedPost.Content> Rebalanced MAG4 after NVDAx drifted to 32.4%. Fees this week: 4.1 USDC. </FeedPost.Content> <FeedPost.Index title="MAG4 · Magnificent Four" description="$0.9998 · +0.53%"> {#snippet icon()} <ChartLineIcon /> {/snippet} {#snippet action()} <Button size="sm">Join</Button> {/snippet} </FeedPost.Index> <!-- … --></FeedPost.Root>Component source
Section titled “Component source”import { cn } from "cn";import type * as React from "react";import { DotsThreeOutlineIcon } from "@phosphor-icons/react";import { Avatar, AvatarFallback, AvatarImage } from "@/registry/edmi/ui/avatar";import { Button } from "@/registry/edmi/ui/button";import { Card } from "@/registry/edmi/ui/card";import type { Elevation } from "@/registry/edmi/ui/elevation";import { Item, ItemActions, ItemContent, ItemDescription, ItemMedia, ItemTitle,} from "@/registry/edmi/ui/item";
type FeedPostProps = React.ComponentProps<typeof Card> & { /** ✦ depth of the card: sunken -1, flat 0, raised +1, floating +2. */ elevation?: Elevation;};
function FeedPost({ className, elevation, ...props }: FeedPostProps) { return ( <Card data-slot="feed-post" elevation={elevation} className={cn("gap-0 px-[18px] py-[18px]", className)} {...props} /> );}
type FeedPostHeaderProps = Omit<React.ComponentProps<"div">, "children"> & { name: string; handle?: string; time?: string; /** Image URL; falls back to `initials`. */ avatarSrc?: string; initials?: string; /** Right slot. Defaults to a ghost "more" icon button. */ actions?: React.ReactNode;};
function FeedPostHeader({ className, name, handle, time, avatarSrc, initials, actions, ...props}: FeedPostHeaderProps) { return ( <div data-slot="feed-post-header" className={cn("flex items-center gap-2.5", className)} {...props} > <Avatar className="size-9"> {avatarSrc ? <AvatarImage src={avatarSrc} alt={name} /> : null} <AvatarFallback className="bg-linear-to-br from-info to-brand text-xs text-white"> {initials ?? name.slice(0, 2).toUpperCase()} </AvatarFallback> </Avatar> <div className="min-w-0 flex-1 truncate text-sm font-semibold"> {name}{" "} {handle || time ? ( <span className="font-normal text-muted-foreground"> {[handle, time].filter(Boolean).join(" · ")} </span> ) : null} </div> {actions ?? ( <Button variant="ghost" size="icon-sm" aria-label="More"> <DotsThreeOutlineIcon /> </Button> )} </div> );}
function FeedPostContent({ className, ...props }: React.ComponentProps<"p">) { return ( <p data-slot="feed-post-content" className={cn("mt-3 text-sm leading-[1.55]", className)} {...props} /> );}
type FeedPostIndexProps = Omit<React.ComponentProps<typeof Item>, "title"> & { title: React.ReactNode; description?: React.ReactNode; /** Leading icon element (an `IconPlaceholder`). */ icon?: React.ReactNode; /** Trailing slot, e.g. a "Join" button. */ action?: React.ReactNode;};
/** Attached index: an outline Item (media icon, title, mono description, action). */function FeedPostIndex({ className, title, description, icon, action, ...props}: FeedPostIndexProps) { return ( <Item data-slot="feed-post-index" variant="outline" size="sm" className={cn("mt-3", className)} {...props} > {icon ? <ItemMedia variant="icon">{icon}</ItemMedia> : null} <ItemContent> <ItemTitle>{title}</ItemTitle> {description ? ( <ItemDescription className="font-mono text-[11.5px]"> {description} </ItemDescription> ) : null} </ItemContent> {action ? <ItemActions>{action}</ItemActions> : null} </Item> );}
function FeedPostFooter({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="feed-post-footer" className={cn( "mt-3 flex items-center gap-[18px] text-[13px] text-muted-foreground", className, )} {...props} /> );}
function FeedPostStat({ className, ...props }: React.ComponentProps<"span">) { return ( <span data-slot="feed-post-stat" className={cn( "inline-flex items-center gap-1.5 [&_svg]:size-[15px]", className, )} {...props} /> );}
export { FeedPost, FeedPostContent, FeedPostFooter, FeedPostHeader, FeedPostIndex, FeedPostStat,};<script setup lang="ts">import type { HTMLAttributes } from 'vue'import { Card } from '@/registry/edmi/ui/card'import type { Elevation } from '@/registry/edmi/ui/elevation'import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{ /** ✦ depth of the card: sunken -1, flat 0, raised +1, floating +2 */ elevation?: Elevation class?: HTMLAttributes['class']}>()</script>
<template> <Card :elevation="elevation" data-slot="feed-post" :class="cn('gap-0 px-[18px] py-[18px]', props.class)"> <slot /> </Card></template><script setup lang="ts">import type { HTMLAttributes } from 'vue'import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{ class?: HTMLAttributes['class'] }>()</script>
<template> <p data-slot="feed-post-content" :class="cn('mt-3 text-sm leading-[1.55]', props.class)"> <slot /> </p></template><script setup lang="ts">import type { HTMLAttributes } from 'vue'import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{ class?: HTMLAttributes['class'] }>()</script>
<template> <div data-slot="feed-post-footer" :class="cn('mt-3 flex items-center gap-[18px] text-[13px] text-muted-foreground', props.class)" > <slot /> </div></template><script setup lang="ts">import type { HTMLAttributes } from 'vue'import { computed } from 'vue'import { MoreHorizontal } from '@lucide/vue'import { Avatar, AvatarFallback, AvatarImage } from '@/registry/edmi/ui/avatar'import { Button } from '@/registry/edmi/ui/button'import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{ class?: HTMLAttributes['class'] name: string handle?: string time?: string /** Image URL; falls back to `initials`. */ avatarSrc?: string initials?: string}>()
const meta = computed(() => [props.handle, props.time].filter(Boolean).join(' · '))</script>
<template> <div data-slot="feed-post-header" :class="cn('flex items-center gap-2.5', props.class)"> <Avatar class="size-9"> <AvatarImage v-if="avatarSrc" :src="avatarSrc" :alt="name" /> <AvatarFallback class="bg-linear-to-br from-info to-brand text-xs text-white"> {{ initials ?? name.slice(0, 2).toUpperCase() }} </AvatarFallback> </Avatar> <div class="min-w-0 flex-1 truncate text-sm font-semibold"> {{ name }} <span v-if="meta" class="font-normal text-muted-foreground">{{ meta }}</span> </div> <!-- Right slot. Defaults to a ghost "more" icon button. --> <slot name="actions"> <Button variant="ghost" size="icon-sm" aria-label="More"> <MoreHorizontal /> </Button> </slot> </div></template><script setup lang="ts">import type { HTMLAttributes } from 'vue'import { Item, ItemActions, ItemContent, ItemDescription, ItemMedia, ItemTitle,} from '@/registry/edmi/ui/item'import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{ class?: HTMLAttributes['class'] /** Title text, or use slot `title` for rich content. */ title?: string description?: string}>()</script>
<template> <!-- Attached index: an outline Item (media icon, title, mono description, action). --> <Item data-slot="feed-post-index" variant="outline" size="sm" :class="cn('mt-3', props.class)"> <ItemMedia v-if="$slots.icon" variant="icon"> <slot name="icon" /> </ItemMedia> <ItemContent> <ItemTitle><slot name="title">{{ title }}</slot></ItemTitle> <ItemDescription v-if="description" class="font-mono text-[11.5px]"> {{ description }} </ItemDescription> </ItemContent> <ItemActions v-if="$slots.action"> <slot name="action" /> </ItemActions> </Item></template><script setup lang="ts">import type { HTMLAttributes } from 'vue'import { cn } from '@/registry/edmi/lib/utils'
const props = defineProps<{ class?: HTMLAttributes['class'] }>()</script>
<template> <span data-slot="feed-post-stat" :class="cn('inline-flex items-center gap-1.5 [&_svg]:size-[15px]', props.class)"> <slot /> </span></template>export { default as FeedPost } from "./FeedPost.vue";export { default as FeedPostContent } from "./FeedPostContent.vue";export { default as FeedPostFooter } from "./FeedPostFooter.vue";export { default as FeedPostHeader } from "./FeedPostHeader.vue";export { default as FeedPostIndex } from "./FeedPostIndex.vue";export { default as FeedPostStat } from "./FeedPostStat.vue";<script lang="ts"> import { cn } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLParagraphElement> = $props();</script>
<p data-slot="feed-post-content" class={cn("mt-3 text-sm leading-[1.55]", className)} {...restProps}> {@render children?.()}</p><script lang="ts"> import { cn } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLDivElement> = $props();</script>
<div data-slot="feed-post-footer" class={cn("mt-3 flex items-center gap-[18px] text-[13px] text-muted-foreground", className)} {...restProps}> {@render children?.()}</div><script lang="ts"> import { cn } from "$lib/utils.js"; import { Avatar, AvatarFallback, AvatarImage } from "$lib/registry/ui/avatar/index.js"; import { Button } from "$lib/registry/ui/button/index.js"; import DotsThreeOutlineIcon from "phosphor-svelte/lib/DotsThreeOutline"; import type { Snippet } from "svelte"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, name, handle, time, avatarSrc, initials, actions, ...restProps }: Omit<HTMLAttributes<HTMLDivElement>, "children"> & { name: string; handle?: string; time?: string; /** Image URL; falls back to `initials`. */ avatarSrc?: string; initials?: string; /** Right slot. Defaults to a ghost "more" icon button. */ actions?: Snippet; } = $props();
const meta = $derived([handle, time].filter(Boolean).join(" · "));</script>
<div data-slot="feed-post-header" class={cn("flex items-center gap-2.5", className)} {...restProps}> <Avatar class="size-9"> {#if avatarSrc}<AvatarImage src={avatarSrc} alt={name} />{/if} <AvatarFallback class="bg-linear-to-br from-info to-brand text-xs text-white"> {initials ?? name.slice(0, 2).toUpperCase()} </AvatarFallback> </Avatar> <div class="min-w-0 flex-1 truncate text-sm font-semibold"> {name} {#if meta}<span class="font-normal text-muted-foreground">{meta}</span>{/if} </div> {#if actions} {@render actions()} {:else} <Button variant="ghost" size="icon-sm" aria-label="More"> <DotsThreeOutlineIcon /> </Button> {/if}</div><script lang="ts"> import { cn } from "$lib/utils.js"; import * as Item from "$lib/registry/ui/item/index.js"; import type { Snippet } from "svelte"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, title, description, icon, action, ...restProps }: Omit<HTMLAttributes<HTMLDivElement>, "title" | "children"> & { /** Title text, or a snippet for rich content. */ title: string | Snippet; description?: string; /** Leading icon (an `IconPlaceholder`). */ icon?: Snippet; /** Trailing slot, e.g. a "Join" button. */ action?: Snippet; } = $props();</script>
<!-- Attached index: an outline Item (media icon, title, mono description, action). --><Item.Root data-slot="feed-post-index" variant="outline" size="sm" class={cn("mt-3", className)} {...restProps}> {#if icon} <Item.Media variant="icon">{@render icon()}</Item.Media> {/if} <Item.Content> <Item.Title>{#if typeof title === "function"}{@render title()}{:else}{title}{/if}</Item.Title> {#if description} <Item.Description class="font-mono text-[11.5px]">{description}</Item.Description> {/if} </Item.Content> {#if action} <Item.Actions>{@render action()}</Item.Actions> {/if}</Item.Root><script lang="ts"> import { cn } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLSpanElement> = $props();</script>
<span data-slot="feed-post-stat" class={cn("inline-flex items-center gap-1.5 [&_svg]:size-[15px]", className)} {...restProps}> {@render children?.()}</span><script lang="ts"> import { cn } from "$lib/utils.js"; import { Card } from "$lib/registry/ui/card/index.js"; import type { Elevation } from "$lib/registry/ui/elevation/index.js"; import type { ComponentProps } from "svelte";
let { ref = $bindable(null), class: className, elevation = "auto", children, ...restProps }: ComponentProps<typeof Card> & { /** ✦ depth of the card: sunken -1, flat 0, raised +1, floating +2. */ elevation?: Elevation; } = $props();</script>
<Card bind:ref {elevation} data-slot="feed-post" class={cn("gap-0 px-[18px] py-[18px]", className)} {...restProps}> {@render children?.()}</Card>import Root from "./feed-post.svelte";import Content from "./feed-post-content.svelte";import Footer from "./feed-post-footer.svelte";import Header from "./feed-post-header.svelte";import Index from "./feed-post-index.svelte";import Stat from "./feed-post-stat.svelte";
export { Content, Content as FeedPostContent, Footer, Footer as FeedPostFooter, Header, Header as FeedPostHeader, Index, Index as FeedPostIndex, Root, // Root as FeedPost, Stat, Stat as FeedPostStat,};Elevation ✦
Section titled “Elevation ✦”See the Elevation guide for the levels, roles and layered mode.
Flat by default. elevation is forwarded to the card: sunken (-1) a soft inset well, raised (+1) the bevel, floating (+2) the bevel plus one soft drop. In a layered ElevationProvider the card resolves to its role default (raised).
Flat (0)
Raised (+1)
Floating (+2)
Flat (0)
Raised (+1)
Floating (+2)
Flat (0)
Raised (+1)
Floating (+2)
import { FeedPost, FeedPostContent, FeedPostFooter, FeedPostHeader, FeedPostIndex, FeedPostStat,} from "@/components/feed-post/feed-post";import { Button } from "@/components/ui/button";import type { Elevation } from "@/components/ui/elevation";import { IconPlaceholder } from "@/edmi/icon-placeholder";
function Sample({ elevation }: { elevation: Elevation }) { return ( <FeedPost elevation={elevation} className="w-[460px] max-w-full"> <FeedPostHeader name="Dewi Lestari" handle="@dewi" time="2h" initials="DL" /> <FeedPostContent> Rebalanced MAG4 after NVDAx drifted to 32.4%. Fees this week: 4.1 USDC. </FeedPostContent> <FeedPostIndex icon={ <IconPlaceholder lucide="ChartLineIcon" tabler="IconChartLine" hugeicons="ChartIcon" phosphor="ChartLineIcon" remixicon="RiLineChartLine" /> } title="MAG4 · Magnificent Four" description="$0.9998 · +0.53%" action={<Button size="sm">Join</Button>} /> <FeedPostFooter> <FeedPostStat> <IconPlaceholder lucide="HeartIcon" tabler="IconBell" hugeicons="Notification02Icon" phosphor="HeartIcon" remixicon="RiHeartLine" /> 24 </FeedPostStat> <FeedPostStat> <IconPlaceholder lucide="ListIcon" tabler="IconListDetails" hugeicons="Menu01Icon" phosphor="ListIcon" remixicon="RiListUnordered" /> 6 </FeedPostStat> <FeedPostStat> <IconPlaceholder lucide="LinkIcon" tabler="IconLink" hugeicons="LinkIcon" phosphor="LinkIcon" remixicon="RiLinksLine" /> Share </FeedPostStat> </FeedPostFooter> </FeedPost> );}
const levels = [ { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" },] as const;
export default function Demo() { return ( <div className="flex flex-col gap-6"> {levels.map(({ value, label }) => ( <div key={value} className="flex flex-col gap-2"> <p className="text-xs font-medium text-muted-foreground">{label}</p> <Sample elevation={value} /> </div> ))} </div> );}<script setup lang="ts">import { ChartLineIcon, HeartIcon, GlobeIcon, ListIcon } from "@lucide/vue";import { Button } from "@/components/ui/button";import { FeedPost, FeedPostContent, FeedPostFooter, FeedPostHeader, FeedPostIndex, FeedPostStat,} from "@/components/ui/feed-post";
const levels = [ { value: "flat", label: "Flat (0)" } as const, { value: "raised", label: "Raised (+1)" } as const, { value: "floating", label: "Floating (+2)" } as const,];</script>
<template> <div class="flex flex-col gap-6"> <div v-for="l in levels" :key="l.value" class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{{ l.label }}</p> <FeedPost :elevation="l.value" class="w-[460px] max-w-full"> <FeedPostHeader name="Dewi Lestari" handle="@dewi" time="2h" initials="DL" /> <FeedPostContent> Rebalanced MAG4 after NVDAx drifted to 32.4%. Fees this week: 4.1 USDC. </FeedPostContent> <FeedPostIndex title="MAG4 · Magnificent Four" description="$0.9998 · +0.53%"> <template #icon><ChartLineIcon /></template> <template #action><Button size="sm">Join</Button></template> </FeedPostIndex> <FeedPostFooter> <FeedPostStat><HeartIcon />24</FeedPostStat> <FeedPostStat><ListIcon />6</FeedPostStat> <FeedPostStat><GlobeIcon />Share</FeedPostStat> </FeedPostFooter> </FeedPost> </div> </div></template><script lang="ts"> import { Button } from "$lib/components/ui/button"; import * as FeedPost from "$lib/components/ui/feed-post"; import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte";
const levels = [ { value: "flat", label: "Flat (0)" } as const, { value: "raised", label: "Raised (+1)" } as const, { value: "floating", label: "Floating (+2)" } as const, ];</script>
<div class="flex flex-col gap-6"> {#each levels as l (l.value)} <div class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{l.label}</p><FeedPost.Root elevation={l.value} class="w-[460px] max-w-full"> <FeedPost.Header name="Dewi Lestari" handle="@dewi" time="2h" initials="DL" /> <FeedPost.Content> Rebalanced MAG4 after NVDAx drifted to 32.4%. Fees this week: 4.1 USDC. </FeedPost.Content> <FeedPost.Index title="MAG4 · Magnificent Four" description="$0.9998 · +0.53%"> {#snippet icon()} <IconPlaceholder lucide="ChartLineIcon" tabler="IconChartLine" hugeicons="ChartIcon" phosphor="ChartLineIcon" remixicon="RiLineChartLine" /> {/snippet} {#snippet action()} <Button size="sm" elevation={l.value}>Join</Button> {/snippet} </FeedPost.Index> <FeedPost.Footer> <FeedPost.Stat> <IconPlaceholder lucide="HeartIcon" tabler="IconBell" hugeicons="Notification02Icon" phosphor="HeartIcon" remixicon="RiHeartLine" /> 24 </FeedPost.Stat> <FeedPost.Stat> <IconPlaceholder lucide="ListIcon" tabler="IconListDetails" hugeicons="Menu01Icon" phosphor="ListIcon" remixicon="RiListUnordered" /> 6 </FeedPost.Stat> <FeedPost.Stat> <IconPlaceholder lucide="LinkIcon" tabler="IconLink" hugeicons="LinkIcon" phosphor="LinkIcon" remixicon="RiLinksLine" /> Share </FeedPost.Stat> </FeedPost.Footer></FeedPost.Root> </div> {/each}</div>| Part | Prop | Type | Default |
|---|---|---|---|
FeedPost |
…props |
Card props |
|
elevation ✦ |
"auto" | "sunken" | "flat" | "raised" | "floating" |
"auto" |
|
FeedPostHeader |
name |
string |
required |
handle, time |
string |
none | |
avatarSrc, initials |
string; initials default to the first two letters of name |
none | |
actions |
React ReactNode; Vue slot actions; Svelte actions snippet |
ghost more button | |
FeedPostContent |
…props |
p props |
|
FeedPostIndex |
title |
React ReactNode; Vue string or slot title; Svelte string | Snippet |
required |
description |
ReactNode (Vue/Svelte: string) |
none | |
icon, action |
React ReactNode; Vue slots; Svelte snippets |
none | |
FeedPostFooter / FeedPostStat |
…props |
div / span props |
✦ Edmi additions
Section titled “✦ Edmi additions”- Whole block is an Edmi addition (DESIGN §5 Patterns, board 12). Composed from: card, avatar, button, item.
- Install:
shadcn add @edmi-ui/feed-post.