Emmanuel Osiris Balonga

story-viewer

Component 778

About

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds story-viewer to your project and fits it to your theme.

Code

How story-viewer is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

import * as React from "react";
import { motion } from "framer-motion";
import { Plus, User } from "lucide-react";
import { StoryViewer, type Story } from "@/components/ui/story-viewer";
import { cn } from "@/lib/utils";

function AddStoryButton() {
  return (
    <button
      className={cn(
        "relative flex flex-col items-center gap-2 group cursor-pointer"
      )}
      aria-label="Add your story"
    >
      <div className="relative">
        <div className="w-[72px] h-[72px] rounded-full p-1">
          <div
            className={cn(
              "w-full h-full rounded-full flex items-center justify-center",
              "border-2 border-dashed border-muted-foreground/40",
              "bg-muted/30 transition-all duration-200",
              "group-hover:border-muted-foreground/60 group-hover:bg-muted/50"
            )}
          >
            <User className="w-7 h-7 text-muted-foreground/50" />
          </div>
        </div>
        <motion.div
          className="absolute bottom-0 right-0 w-6 h-6 rounded-full bg-primary flex items-center justify-center shadow-sm"
          whileHover={{ scale: 1.1 }}
          whileTap={{ scale: 0.95 }}
        >
          <Plus className="w-4 h-4 text-primary-foreground" strokeWidth={2.5} />
        </motion.div>
      </div>
      <span className="text-xs text-muted-foreground truncate max-w-[80px]">
        Add story
      </span>
    </button>
  );
}

const users = [
  {
    username: "Yuki Tanaka",
    avatar: "https://cdn.21st.dev/assets/mirror/12/12fcb9b192a66da9526205509f410454b8f6b11ed5ba5c68d09e8792af301b89.svg",
    timestamp: new Date(Date.now() - 25 * 60 * 1000).toISOString(),
    stories: [
      { id: "yuki-1", type: "image" as const, src: "https://cdn.21st.dev/assets/mirror/b4/b4821bbf6c2ac3d993aea51a0d6dfef88827c0a1279194e38af4c01e8b60cd8d.jpg" },
      { id: "yuki-2", type: "video" as const, src: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4" },
      { id: "yuki-3", type: "image" as const, src: "https://cdn.21st.dev/assets/mirror/81/81de8ae5df7698597ed51cbc371ad890e92dda53516c158ea0e1c5a8874580c0.jpg" },
    ],
  },
  {
    username: "Matt Cooper",
    avatar: "https://cdn.21st.dev/assets/mirror/d2/d26f6b9b27114523c29d833250c36bb60b6967982c15b81d1418155863cdb60b.svg",
    timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
    stories: [
      { id: "matt-1", type: "video" as const, src: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4" },
      { id: "matt-2", type: "image" as const, src: "https://cdn.21st.dev/assets/mirror/3f/3f8c24e42130caa6bcc36270e23c027545779cd7acd09018b7c9b87ea50a311d.jpg" },
    ],
  },
  {
    username: "Sofia Martinez",
    avatar: "https://cdn.21st.dev/assets/mirror/0d/0dc19dddb42e9ca69c614a44ad4992e24bbfef6aeb6a24f3221c2edee91693d7.svg",
    timestamp: new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString(),
    stories: [
      { id: "sofia-1", type: "image" as const, src: "https://cdn.21st.dev/assets/mirror/64/64f97668e7e3d10f2170a07ee2ed39cc265617308e3092a32680659d6665348c.jpg" },
      { id: "sofia-2", type: "image" as const, src: "https://cdn.21st.dev/assets/mirror/1c/1c7e4b4ea3bbc863fb7788c0eba9881f5bc627ba9254706d6aa2e0402fbcca74.jpg" },
      { id: "sofia-3", type: "video" as const, src: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4" },
      { id: "sofia-4", type: "image" as const, src: "https://cdn.21st.dev/assets/mirror/e9/e9f11065ed5dcb56d8c06fae6a741ee5d1baeb287f0c418e2e48bf2a9c53343f.jpg" },
    ],
  },
  {
    username: "Richard Weber",
    avatar: "https://cdn.21st.dev/assets/mirror/b0/b03ec1a20b793bfb8c2462e3e104d020a2dec39ed1b2311578b2cfdc46acd080.svg",
    timestamp: new Date(Date.now() - 5 * 60 * 60 * 1000).toISOString(),
    stories: [
      { id: "richard-1", type: "image" as const, src: "https://cdn.21st.dev/assets/mirror/1e/1ecc3672c770133ceb1b707255983304f2b17161f9044f0d3723a8ab1d96e020.jpg" },
      { id: "richard-2", type: "video" as const, src: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4" },
    ],
  },
  {
    username: "Emma O'Brien",
    avatar: "https://cdn.21st.dev/assets/mirror/af/af9e3e9084f1a11941ec1f8574469c572b91213b1c7d782ce2e586c41c30b9f2.svg",
    timestamp: new Date(Date.now() - 7 * 60 * 60 * 1000).toISOString(),
    stories: [
      { id: "emma-1", type: "image" as const, src: "https://cdn.21st.dev/assets/mirror/40/40f9a9e7e53f7b5bcdc46868b2b9faa38413800e24ac056a764ef3fe4dc89d41.jpg" },
      { id: "emma-2", type: "video" as const, src: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4" },
    ],
  },
  {
    username: "Jake Morrison",
    avatar: "https://cdn.21st.dev/assets/mirror/d7/d753a9ca16db6acaea4c57fb1d91125af65bf3a702c6513b0fff853d555fee50.svg",
    timestamp: new Date(Date.now() - 10 * 60 * 60 * 1000).toISOString(),
    stories: [
      { id: "jake-1", type: "image" as const, src: "https://cdn.21st.dev/assets/mirror/f7/f72c6a82e1c929bef0c32e7fabc32080ddea7d85d813aa5451e251e7980e98f2.jpg" },
      { id: "jake-2", type: "video" as const, src: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4" },
      { id: "jake-3", type: "image" as const, src: "https://cdn.21st.dev/assets/mirror/d4/d4f4084f4db99ea974c9ca39213e23666f779796e2a19486395efecba14dcae2.jpg" },
    ],
  },
  {
    username: "Alex Chen",
    avatar: "https://cdn.21st.dev/assets/mirror/94/942b4bc51fa851a34f1c0308d9c1047311860f561c04d7db7e1cb1f8fe5abd81.svg",
    timestamp: new Date(Date.now() - 12 * 60 * 60 * 1000).toISOString(),
    stories: [
      { id: "alex-1", type: "video" as const, src: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4" },
      { id: "alex-2", type: "video" as const, src: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4" },
    ],
  },
];

export default function Demo() {
  return (
    <div className="w-full max-w-3xl mx-auto px-4">
      <h3 className="text-sm font-semibold mb-3 px-1">Recent stories</h3>
      <div className="flex gap-4 overflow-x-auto py-2 px-1 [&::-webkit-scrollbar]:hidden md:[&::-webkit-scrollbar]:block md:[&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-muted-foreground/20 [&::-webkit-scrollbar-thumb]:rounded-full hover:[&::-webkit-scrollbar-thumb]:bg-muted-foreground/30">
        <AddStoryButton />
        {users.map((user) => (
          <StoryViewer
            key={user.username}
            stories={user.stories}
            username={user.username}
            avatar={user.avatar}
            timestamp={user.timestamp}
            onStoryView={() => {}}
            onAllStoriesViewed={() => {}}
          />
        ))}
      </div>
    </div>
  );
}

Dependencies

More by Emmanuel Osiris Balonga

View all
  • Date Wheel Picker

    Date Wheel Picker

    602
  • Member Selector

    Member Selector

    563
  • Emoji Rating Selector

    Emoji Rating Selector

    1