Watchlist Item
Compact sidebar row with a colored letter tile, symbol, mono price and change.
import { WatchlistItem } from "@/components/watchlist-item/watchlist-item";
export default function Demo() { return ( <div className="flex w-60 flex-col gap-1.5"> <WatchlistItem href="#mag4" symbol="MAG4" price="1.0000" change="+2.38%" color="var(--chart-1)" active /> <WatchlistItem href="#aifr" symbol="AIFR" price="1.0104" change="+0.84%" color="var(--chart-4)" /> <WatchlistItem href="#atls" symbol="ATLS" price="0.9893" change="−0.64%" color="var(--chart-3)" /> </div> );}<script setup lang="ts">import { WatchlistItem } from "@/components/ui/watchlist-item";</script>
<template> <div class="flex w-60 flex-col gap-1.5"> <WatchlistItem href="#mag4" symbol="MAG4" price="1.0000" change="+2.38%" color="var(--chart-1)" active /> <WatchlistItem href="#aifr" symbol="AIFR" price="1.0104" change="+0.84%" color="var(--chart-4)" /> <WatchlistItem href="#atls" symbol="ATLS" price="0.9893" change="−0.64%" color="var(--chart-3)" /> </div></template><script lang="ts"> import { WatchlistItem } from "$lib/components/ui/watchlist-item";</script>
<div class="flex w-60 flex-col gap-1.5"> <WatchlistItem href="#mag4" symbol="MAG4" price="1.0000" change="+2.38%" color="var(--chart-1)" active /> <WatchlistItem href="#aifr" symbol="AIFR" price="1.0104" change="+0.84%" color="var(--chart-4)" /> <WatchlistItem href="#atls" symbol="ATLS" price="0.9893" change="−0.64%" color="var(--chart-3)" /></div>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/watchlist-itempnpm dlx shadcn@latest add @edmi-ui/watchlist-itemyarn dlx shadcn@latest add @edmi-ui/watchlist-itembunx shadcn@latest add @edmi-ui/watchlist-itemPick 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:
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/watchlist-itempnpm dlx shadcn-vue@latest add @edmi-ui/watchlist-itemyarn dlx shadcn-vue@latest add @edmi-ui/watchlist-itembunx shadcn-vue@latest add @edmi-ui/watchlist-itemPick 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:
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/watchlist-item.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/watchlist-item.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/watchlist-item.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/watchlist-item.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:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
import { WatchlistItem } from "@/components/watchlist-item/watchlist-item";
<div className="flex w-60 flex-col gap-1.5"> <WatchlistItem href="#mag4" symbol="MAG4" price="1.0000" change="+2.38%" color="var(--chart-1)" active /> <WatchlistItem href="#aifr" symbol="AIFR" price="1.0104" change="+0.84%" color="var(--chart-4)" /> <WatchlistItem href="#atls" symbol="ATLS" price="0.9893" change="−0.64%" color="var(--chart-3)" /></div><script setup lang="ts">import { WatchlistItem } from "@/components/ui/watchlist-item";</script>
<template> <div class="flex w-60 flex-col gap-1.5"> <WatchlistItem href="#mag4" symbol="MAG4" price="1.0000" change="+2.38%" color="var(--chart-1)" active /> <WatchlistItem href="#aifr" symbol="AIFR" price="1.0104" change="+0.84%" color="var(--chart-4)" /> <WatchlistItem href="#atls" symbol="ATLS" price="0.9893" change="−0.64%" color="var(--chart-3)" /> </div></template><script lang="ts"> import { WatchlistItem } from "$lib/components/ui/watchlist-item";</script>
<div class="flex w-60 flex-col gap-1.5"> <WatchlistItem href="#mag4" symbol="MAG4" price="1.0000" change="+2.38%" color="var(--chart-1)" active /> <WatchlistItem href="#aifr" symbol="AIFR" price="1.0104" change="+0.84%" color="var(--chart-4)" /> <WatchlistItem href="#atls" symbol="ATLS" price="0.9893" change="−0.64%" color="var(--chart-3)" /></div>Component source
Section titled “Component source”"use client";
import { cn } from "cn";import type * as React from "react";import { type Elevation, useElevation } from "@/registry/edmi/ui/elevation";
const isDown = (s: string) => /^[-−–]/.test(s.trim());
// Compact sidebar row: colored letter tile, symbol, mono price and change.function WatchlistItem({ className, symbol, price, change, color = "var(--chart-1)", letter, active, elevation, ...props}: Omit<React.ComponentProps<"a">, "children"> & { symbol: string; price: string; /** Signed percentage, e.g. `+2.38%`. */ change: string; /** Tile background: any CSS color, default `var(--chart-1)`. */ color?: string; letter?: string; active?: boolean; /** ✦ depth of the active row (raised +1 / floating +2 bevel it). */ elevation?: Elevation;}) { const level = useElevation(elevation, "control"); return ( <a data-slot="watchlist-item" data-active={active ? "" : undefined} className={cn( "flex h-10 items-center gap-2.5 rounded-lg px-2.5 text-[13.5px] text-sidebar-foreground outline-none hover:bg-sidebar-accent focus-visible:outline-2 focus-visible:outline-ring", "data-[active]:bg-sidebar-accent data-[active]:font-medium data-[active]:shadow-[inset_0_0_0_1px_var(--sidebar-border)]", level === "raised" && "border border-transparent data-[active]:border-transparent data-[active]:shadow-raised", level === "floating" && "border border-transparent data-[active]:border-transparent data-[active]:shadow-floating", className, )} {...props} > <span className="inline-flex size-[26px] items-center justify-center rounded-[7px] text-xs font-semibold text-primary-foreground" style={{ background: color }} > {letter ?? symbol.charAt(0)} </span> <span className="min-w-0 flex-1 truncate">{symbol}</span> <span className="font-mono text-[11.5px] text-muted-foreground"> {price} </span> <span className={cn( "font-mono text-[11.5px]", isDown(change) ? "text-destructive-text" : "text-success-text", )} > {change} </span> </a> );}
export { WatchlistItem };<script setup lang="ts">import type { HTMLAttributes } from 'vue'import { computed } from 'vue'import { cn } from '@/registry/edmi/lib/utils'import { type Elevation, useElevation } from '@/registry/edmi/ui/elevation'
const props = withDefaults(defineProps<{ symbol: string price: string /** Signed percentage, e.g. `+2.38%`. */ change: string /** Tile background: any CSS color, default `var(--chart-1)`. */ color?: string letter?: string active?: boolean /** ✦ depth of the active row (raised +1 / floating +2 bevel it) */ elevation?: Elevation class?: HTMLAttributes['class']}>(), { color: 'var(--chart-1)', elevation: undefined,})
const level = useElevation(() => props.elevation, 'control')
const down = computed(() => /^[-−–]/.test(props.change.trim()))</script>
<template> <!-- Compact sidebar row: colored letter tile, symbol, mono price and change. --> <a data-slot="watchlist-item" :data-active="active ? '' : undefined" :class="cn( 'flex h-10 items-center gap-2.5 rounded-lg px-2.5 text-[13.5px] text-sidebar-foreground outline-none hover:bg-sidebar-accent focus-visible:outline-2 focus-visible:outline-ring', 'data-[active]:bg-sidebar-accent data-[active]:font-medium data-[active]:shadow-[inset_0_0_0_1px_var(--sidebar-border)]', level === 'raised' && 'border border-transparent data-[active]:border-transparent data-[active]:shadow-raised', level === 'floating' && 'border border-transparent data-[active]:border-transparent data-[active]:shadow-floating', props.class, )" > <span class="inline-flex size-[26px] items-center justify-center rounded-[7px] text-xs font-semibold text-primary-foreground" :style="{ background: color }" >{{ letter ?? symbol.charAt(0) }}</span> <span class="min-w-0 flex-1 truncate">{{ symbol }}</span> <span class="font-mono text-[11.5px] text-muted-foreground">{{ price }}</span> <span :class="cn('font-mono text-[11.5px]', down ? 'text-destructive-text' : 'text-success-text')">{{ change }}</span> </a></template>export { default as WatchlistItem } from "./WatchlistItem.vue";export { default as WatchlistItem } from "./watchlist-item.svelte";<script lang="ts"> import { cn, type WithElementRef } from "$lib/utils.js"; import type { HTMLAnchorAttributes } from "svelte/elements"; import { type Elevation, useElevation } from "$lib/registry/ui/elevation/index.js";
let { ref = $bindable(null), class: className, symbol, price, change, color = "var(--chart-1)", letter, active, elevation = "auto", ...restProps }: WithElementRef<Omit<HTMLAnchorAttributes, "children">, HTMLAnchorElement> & { symbol: string; price: string; /** Signed percentage, e.g. `+2.38%`. */ change: string; /** Tile background: any CSS color, default `var(--chart-1)`. */ color?: string; letter?: string; active?: boolean; /** ✦ depth of the active row (raised +1 / floating +2 bevel it). */ elevation?: Elevation; } = $props();
const level = useElevation(() => elevation, "control");
const down = $derived(/^[-−–]/.test(change.trim()));</script>
<!-- Compact sidebar row: colored letter tile, symbol, mono price and change. --><a bind:this={ref} data-slot="watchlist-item" data-active={active ? "" : undefined} class={cn( "flex h-10 items-center gap-2.5 rounded-lg px-2.5 text-[13.5px] text-sidebar-foreground outline-none hover:bg-sidebar-accent focus-visible:outline-2 focus-visible:outline-ring", "data-[active]:bg-sidebar-accent data-[active]:font-medium data-[active]:shadow-[inset_0_0_0_1px_var(--sidebar-border)]", level.current === "raised" && "border border-transparent data-[active]:border-transparent data-[active]:shadow-raised", level.current === "floating" && "border border-transparent data-[active]:border-transparent data-[active]:shadow-floating", className )} {...restProps}> <span class="inline-flex size-[26px] items-center justify-center rounded-[7px] text-xs font-semibold text-primary-foreground" style:background={color}>{letter ?? symbol.charAt(0)}</span > <span class="min-w-0 flex-1 truncate">{symbol}</span> <span class="font-mono text-[11.5px] text-muted-foreground">{price}</span> <span class={cn("font-mono text-[11.5px]", down ? "text-destructive-text" : "text-success-text")}>{change}</span></a>Elevation ✦
Section titled “Elevation ✦”See the Elevation guide for the levels, roles and layered mode.
Flat by default. elevation raises only the active row: raised (+1) adds the bevel, floating (+2) the bevel plus one soft drop.
import { WatchlistItem } from "@/components/watchlist-item/watchlist-item";import type { Elevation } from "@/components/ui/elevation";
function Sample({ elevation }: { elevation: Elevation }) { return ( <div className="flex w-60 flex-col gap-1.5"> <WatchlistItem elevation={elevation} href="#mag4" symbol="MAG4" price="1.0000" change="+2.38%" color="var(--chart-1)" active /> <WatchlistItem elevation={elevation} href="#aifr" symbol="AIFR" price="1.0104" change="+0.84%" color="var(--chart-4)" /> <WatchlistItem elevation={elevation} href="#atls" symbol="ATLS" price="0.9893" change="−0.64%" color="var(--chart-3)" /> </div> );}
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 { WatchlistItem } from "@/components/ui/watchlist-item";
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> <div class="flex w-60 flex-col gap-1.5"> <WatchlistItem :elevation="l.value" href="#mag4" symbol="MAG4" price="1.0000" change="+2.38%" color="var(--chart-1)" active /> <WatchlistItem :elevation="l.value" href="#aifr" symbol="AIFR" price="1.0104" change="+0.84%" color="var(--chart-4)" /> <WatchlistItem :elevation="l.value" href="#atls" symbol="ATLS" price="0.9893" change="−0.64%" color="var(--chart-3)" /> </div> </div> </div></template><script lang="ts"> import { WatchlistItem } from "$lib/components/ui/watchlist-item";
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><div class="flex w-60 flex-col gap-1.5"> <WatchlistItem elevation={l.value} href="#mag4" symbol="MAG4" price="1.0000" change="+2.38%" color="var(--chart-1)" active /> <WatchlistItem elevation={l.value} href="#aifr" symbol="AIFR" price="1.0104" change="+0.84%" color="var(--chart-4)" /> <WatchlistItem elevation={l.value} href="#atls" symbol="ATLS" price="0.9893" change="−0.64%" color="var(--chart-3)" /></div> </div> {/each}</div>| Part | Prop | Type | Default |
|---|---|---|---|
WatchlistItem |
symbol / price / change |
string |
required |
color |
CSS color | var(--chart-1) |
|
letter |
string |
first letter of symbol |
|
active |
boolean |
false |
|
elevation |
"auto" | "sunken" | "flat" | "raised" | "floating" |
"auto" |
Renders an <a>; pass href and other anchor props.
✦ Edmi additions
Section titled “✦ Edmi additions”- Uses the
--sidebar-*tokens, so it fits inside the Sidebar block.