npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@phantazyinteractive/mealvote-components

v0.4.2

Published

React component library for collaborative meal planning and voting

Downloads

527

Readme

MealVote Components

A beautiful, fully-typed React component library for building collaborative meal planning and voting applications. Perfect for family meal planning apps, recipe sharing platforms, and cooking communities.

✨ Features

  • 🗳️ Voting Components - Approval and ranked choice voting with real-time tallies
  • 📅 Meal Planning - Weekly calendar views and meal slot management
  • 👨‍🍳 Cook Mode - Large-type instructions with timers and step tracking
  • 🍳 Recipe Cards - Beautiful recipe display with images, time, and metadata
  • 📥 Import Tools - Recipe URL import with progress tracking
  • 🎨 Fully Themed - Customizable design system with CSS variables
  • Accessible - Built with ARIA labels and keyboard navigation
  • 📱 Responsive - Mobile-first design that works everywhere
  • 🔷 TypeScript - Complete type safety out of the box
  • 🌳 Tree-Shakable - Explicit exports for optimal bundle sizes

📦 Installation

npm install @phantazyinteractive/mealvote-components

Required Peer Dependencies

This package requires several peer dependencies that must be installed in your project:

# Core React dependencies
npm install react react-dom lucide-react

# Utility libraries
npm install class-variance-authority clsx tailwind-merge

# Radix UI components (install only the ones you use)
npm install @radix-ui/react-tabs @radix-ui/react-progress @radix-ui/react-slot
# ... add other @radix-ui packages as needed based on which components you use

Import Styles

Import the component styles in your app entry point:

import '@phantazyinteractive/mealvote-components/style.css';

function App() {
  return (
    <div>
      <RecipeCard recipe={recipe} />
      <VoteCard votes={votes} />
    </div>
  );
}

Peer Dependencies

Ensure you have these installed:

npm install react react-dom lucide-react

📦 Components

Meal Voting Components

  • RecipeCard - Display recipe information with image, title, description, and metadata
  • VoteCard - Approval voting interface for recipe selection
  • RankedVoteCard - Ranked choice voting with drag-and-drop reordering
  • TallyBar - Visual representation of voting results with progress bars
  • MealSlot - Individual meal slot for weekly planning
  • PlanGrid - Weekly meal planning grid interface
  • ImportBanner - Recipe import interface with external recipe management
  • CookMode - Step-by-step cooking mode with timer and progress tracking

UI Components

  • Button - Versatile button component with multiple variants (default, outline, destructive, ghost, link) and sizes
  • Card - Container component with header, content, and footer sections
  • Badge - Small label component for tags and status indicators
  • Input - Text input field with consistent styling
  • Label - Form label component for accessibility
  • Checkbox - Checkbox input with label integration
  • Alert - Notification component for important messages with default and destructive variants
  • Accordion - Collapsible content sections with smooth animations
  • Progress - Progress bar for tracking completion
  • Tabs - Tabbed interface for organizing content
  • Toggle - Toggle button for binary states
  • Popover - Floating content container
  • HoverCard - Content that appears on hover
  • Collapsible - Expandable/collapsible content
  • Separator - Visual divider between sections

🚀 Quick Start

Recipe Card

Display recipe information with images, cooking time, and tags:

import { RecipeCard } from '@phantazyinteractive/mealvote-components';

const recipe = {
  id: '1',
  title: 'Spicy Thai Basil Chicken',
  imageUrl: 'https://example.com/thai-chicken.jpg',
  timeTotal: 30,
  servings: 4,
  cuisines: ['Thai', 'Asian'],
  difficulty: 'medium',
};

function App() {
  return (
    <RecipeCard 
      recipe={recipe}
      onClick={() => console.log('Recipe clicked')}
      selected={false}
    />
  );
}

Vote Card (Approval Voting)

Let users vote on multiple recipe options:

import { VoteCard } from '@phantazyinteractive/mealvote-components';

const candidates = [
  { 
    id: '1', 
    recipe: {
      id: '1',
      title: 'Thai Basil Chicken',
      imageUrl: 'https://example.com/thai.jpg',
      timeTotal: 30,
      servings: 4,
    }
  },
  { 
    id: '2', 
    recipe: {
      id: '2',
      title: 'Margherita Pizza',
      imageUrl: 'https://example.com/pizza.jpg',
      timeTotal: 45,
      servings: 4,
    }
  },
];

function VotingPage() {
  return (
    <VoteCard
      candidates={candidates}
      onVote={(selectedIds) => {
        console.log('User voted for:', selectedIds);
      }}
      maxSelections={3}
      title="What's for dinner tonight?"
      description="Select all options you'd enjoy eating"
    />
  );
}

Ranked Vote Card

Enable ranked-choice voting with drag-and-drop:

import { RankedVoteCard } from '@phantazyinteractive/mealvote-components';

function RankedVoting() {
  return (
    <RankedVoteCard
      candidates={candidates}
      onVote={(rankedIds) => {
        console.log('User ranked:', rankedIds);
      }}
      title="Rank your preferences"
      description="Drag to reorder your top choices"
    />
  );
}

Tally Bar

Display voting results with progress bars:

import { TallyBar } from '@phantazyinteractive/mealvote-components';

const results = [
  {
    candidateId: '1',
    recipe: { title: 'Thai Chicken', imageUrl: '...', timeTotal: 30 },
    approvals: 8,
    score: 85,
  },
  {
    candidateId: '2',
    recipe: { title: 'Pizza', imageUrl: '...', timeTotal: 45 },
    approvals: 6,
    score: 65,
  },
];

function Results() {
  return (
    <TallyBar
      results={results}
      totalVoters={10}
      mode="approval"
      live={true}
    />
  );
}

Cook Mode

Full-screen cooking interface with step-by-step instructions:

import { CookMode } from '@phantazyinteractive/mealvote-components';

const steps = [
  {
    id: '1',
    instruction: 'Heat oil in a large wok over high heat',
    duration: 2,
  },
  {
    id: '2',
    instruction: 'Add garlic and chilies, stir-fry for 30 seconds',
    duration: 1,
  },
];

function Cooking() {
  return (
    <CookMode
      recipeName="Spicy Thai Basil Chicken"
      steps={steps}
      totalTime={30}
      onComplete={() => console.log('Done cooking!')}
      onExit={() => console.log('Exited cook mode')}
    />
  );
}

Meal Planning

Plan meals for the week:

import { PlanGrid, MealSlot } from '@phantazyinteractive/mealvote-components';

const slots = [
  {
    id: '1',
    date: new Date('2024-01-15'),
    slotType: 'dinner',
    status: 'voting',
    voteCount: 3,
  },
  {
    id: '2',
    date: new Date('2024-01-16'),
    slotType: 'dinner',
    status: 'locked',
    recipe: { title: 'Pizza Night', imageUrl: '...', timeTotal: 45 },
  },
];

function WeeklyPlan() {
  return (
    <PlanGrid
      slots={slots}
      onOpenVoting={(slotId) => console.log('Open voting:', slotId)}
      onViewRecipe={(slotId) => console.log('View recipe:', slotId)}
      onMarkCooked={(slotId) => console.log('Mark cooked:', slotId)}
    />
  );
}

Import Banner

Add recipe import functionality:

import { ImportBanner } from '@phantazyinteractive/mealvote-components';

function ImportRecipes() {
  return (
    <ImportBanner
      onImport={async (url) => {
        // Your import logic here
        await fetch('/api/import', { 
          method: 'POST', 
          body: JSON.stringify({ url }) 
        });
      }}
      quota={{ used: 7, total: 10 }}
    />
  );
}

Progress Bar

Show progress indicators for tasks and loading states:

import { Progress } from '@phantazyinteractive/mealvote-components';

function LoadingStates() {
  return (
    <div className="space-y-4">
      <Progress value={33} className="w-full" />
      <Progress value={66} className="w-full" />
      <Progress value={100} className="w-full" />
    </div>
  );
}

🧩 UI Components Usage

Buttons

import { Button } from '@phantazyinteractive/mealvote-components';
import { Mail } from 'lucide-react';

function MyComponent() {
  return (
    <div className="flex gap-4">
      <Button variant="default">Primary Action</Button>
      <Button variant="outline">Secondary Action</Button>
      <Button variant="destructive">Delete</Button>
      <Button variant="ghost">Ghost Button</Button>
      <Button variant="link">Link Button</Button>
      <Button size="sm">Small</Button>
      <Button size="lg">Large</Button>
      <Button size="icon"><Mail /></Button>
    </div>
  );
}

Alert

Display important messages and notifications.

import { Alert, AlertTitle, AlertDescription } from '@phantazyinteractive/mealvote-components';
import { AlertCircle, Terminal } from 'lucide-react';

function MyComponent() {
  return (
    <>
      <Alert>
        <Terminal className="h-4 w-4" />
        <AlertTitle>Heads up!</AlertTitle>
        <AlertDescription>
          You can add components to your app using the cli.
        </AlertDescription>
      </Alert>
      
      <Alert variant="destructive">
        <AlertCircle className="h-4 w-4" />
        <AlertTitle>Error</AlertTitle>
        <AlertDescription>
          Your session has expired. Please log in again.
        </AlertDescription>
      </Alert>
    </>
  );
}

Alert Dialog

Show confirmation dialogs and important alerts that require user action.

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from '@phantazyinteractive/mealvote-components';
import { Button } from '@phantazyinteractive/mealvote-components';

function DeleteConfirmation() {
  return (
    <AlertDialog>
      <AlertDialogTrigger asChild>
        <Button variant="destructive">Delete Item</Button>
      </AlertDialogTrigger>
      <AlertDialogContent>
        <AlertDialogHeader>
          <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
          <AlertDialogDescription>
            This action cannot be undone. This will permanently delete your item.
          </AlertDialogDescription>
        </AlertDialogHeader>
        <AlertDialogFooter>
          <AlertDialogCancel>Cancel</AlertDialogCancel>
          <AlertDialogAction>Delete</AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  );
}

Toast Notifications

Display temporary status messages and notifications.

import { Button, useToast, Toaster } from '@phantazyinteractive/mealvote-components';

function App() {
  const { toast } = useToast();

  return (
    <>
      <Button
        onClick={() => {
          toast({
            title: "Success!",
            description: "Your changes have been saved.",
          });
        }}
      >
        Show Toast
      </Button>
      <Toaster />
    </>
  );
}

Error toast:

toast({
  variant: "destructive",
  title: "Error",
  description: "Something went wrong.",
});

Toast with action button:

toast({
  title: "Undo action",
  description: "You can undo this action.",
  action: <Button variant="outline" size="sm">Undo</Button>,
});

Accordion

import { 
  Accordion, 
  AccordionContent, 
  AccordionItem, 
  AccordionTrigger 
} from '@phantazyinteractive/mealvote-components';

function FAQ() {
  return (
    <Accordion type="single" collapsible className="w-full">
      <AccordionItem value="item-1">
        <AccordionTrigger>How do I vote?</AccordionTrigger>
        <AccordionContent>
          Click on recipes you'd like to vote for!
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-2">
        <AccordionTrigger>Can I change my vote?</AccordionTrigger>
        <AccordionContent>
          Yes, you can change your vote anytime before the voting closes.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}

Form Elements

import { Label, Input, Checkbox, Button } from '@phantazyinteractive/mealvote-components';

function FormExample() {
  return (
    <form className="space-y-4">
      <div className="space-y-2">
        <Label htmlFor="email">Email</Label>
        <Input type="email" id="email" placeholder="[email protected]" />
      </div>
      
      <div className="flex items-center space-x-2">
        <Checkbox id="terms" />
        <Label htmlFor="terms" className="cursor-pointer">
          I agree to the terms and conditions
        </Label>
      </div>
      
      <Button type="submit">Submit</Button>
    </form>
  );
}

Card Components

import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, Button } from '@phantazyinteractive/mealvote-components';

function CardExample() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Weekly Meal Plan</CardTitle>
        <CardDescription>Plan your meals for the week</CardDescription>
      </CardHeader>
      <CardContent>
        <p>Your meal planning content goes here.</p>
      </CardContent>
      <CardFooter>
        <Button>Save Plan</Button>
      </CardFooter>
    </Card>
  );
}

Badges

import { Badge } from '@phantazyinteractive/mealvote-components';

function BadgeExample() {
  return (
    <div className="flex gap-2">
      <Badge>Default</Badge>
      <Badge variant="secondary">Secondary</Badge>
      <Badge variant="destructive">Destructive</Badge>
      <Badge variant="outline">Outline</Badge>
    </div>
  );
}

🎨 Customization

The library uses CSS custom properties for theming. Override these in your global CSS:

:root {
  /* Primary color (orange-red) */
  --primary: 20 88% 55%;
  
  /* Secondary color (green) */
  --secondary: 142 76% 45%;
  
  /* Accent color (warm orange) */
  --accent: 15 100% 65%;
  
  /* Background colors */
  --background: 45 20% 98%;
  --card: 45 20% 100%;
  
  /* Text colors */
  --foreground: 24 24% 15%;
  --muted-foreground: 24 15% 40%;
}

🌐 Live Demo

Interactive Demo: https://phantazy-interactive.github.io/meal-vote-app/

Explore all components in action with live examples.

📚 Documentation

Storybook: https://phantazy-interactive.github.io/meal-vote-app/storybook/

Complete component documentation with interactive examples, props tables, and API reference.

🛠 Development

Local Setup

# Clone the repository
git clone https://github.com/Phantazy-Interactive/meal-vote-app.git
cd meal-vote-app

# Install dependencies
npm install

# Start development server
npm run dev

# Start Storybook
npm run storybook

Building & Publishing

# Build component library
npm run build:lib

# Build Storybook
npm run build-storybook

# Publish to npm
npm publish

🏗 Built With

  • React 18 - UI framework
  • TypeScript - Type safety
  • Vite - Build tool and dev server
  • Tailwind CSS - Styling
  • shadcn/ui - UI component foundation
  • Radix UI - Accessible primitives
  • Storybook - Component documentation
  • Lucide React - Icons

📄 License

MIT License - see LICENSE file for details.