feat: add new UI components including Drawer, InputOTP, Pagination, Sidebar, Sonner, and ToggleGroup
Some checks failed
GBCI / build (push) Failing after 2m18s

- Implemented Drawer component for modal-like functionality.
- Added InputOTP component for handling one-time password inputs.
- Created Pagination component for navigating through paginated content.
- Developed Sidebar component with collapsible and mobile-friendly features.
- Integrated Sonner for toast notifications with theme support.
- Introduced ToggleGroup for grouping toggle buttons with context support.
- Added useIsMobile hook to determine mobile view based on screen width.
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-06-21 20:30:28 -03:00
parent 473fae930a
commit 1d458886dd
2 changed files with 760 additions and 532 deletions

View file

@ -1,33 +1,259 @@
import React from 'react';
import { TeamSwitcher } from './components/TeamSwitcher';
import { MainNav } from './components/MainNav';
import { Search } from './components/Search';
import { UserNav } from './components/UserNav';
import { CalendarDateRangePicker } from './components/DateRangePicker';
import { Overview } from './components/Overview';
import { RecentSales } from './components/RecentSales';
"use client";
import React, { useState } from 'react';
export default function DashboardScreen() {
return (
<div className="min-h-screen bg-gray-50">
<header className="bg-white border-b">
<div className="container flex items-center justify-between h-16 px-4">
<TeamSwitcher />
<MainNav />
<div className="flex items-center space-x-4">
<Search />
<UserNav />
const Dashboard = () => {
const [dateRange, setDateRange] = useState({
startDate: new Date(),
endDate: new Date()
});
const [selectedTeam, setSelectedTeam] = useState({
label: "Alicia Koch",
value: "personal"
});
const [teamSwitcherOpen, setTeamSwitcherOpen] = useState(false);
const [showNewTeamDialog, setShowNewTeamDialog] = useState(false);
const [userNavOpen, setUserNavOpen] = useState(false);
const formatDate = (date) => {
return date.toLocaleDateString('en-US', {
month: 'short',
day: '2-digit',
year: 'numeric'
});
};
const salesData = [
{ name: "Olivia Martin", email: "olivia.martin@email.com", amount: "+$1,999.00" },
{ name: "Jackson Lee", email: "jackson.lee@email.com", amount: "+$39.00" },
{ name: "Isabella Nguyen", email: "isabella.nguyen@email.com", amount: "+$299.00" },
{ name: "William Kim", email: "will@email.com", amount: "+$99.00" },
{ name: "Sofia Davis", email: "sofia.davis@email.com", amount: "+$39.00" },
];
const groups = [
{
label: "Personal Account",
teams: [
{ label: "Alicia Koch", value: "personal" },
],
},
{
label: "Teams",
teams: [
{ label: "Acme Inc.", value: "acme-inc" },
{ label: "Monsters Inc.", value: "monsters" },
],
},
];
const CalendarDateRangePicker = () => (
<div className="flex items-center gap-2">
<button
className="px-3 py-1 border rounded text-foreground border-border hover:bg-accent hover:text-accent-foreground transition-colors"
onClick={() => {
const date = new Date(prompt("Enter start date (YYYY-MM-DD)") || dateRange.startDate);
setDateRange(prev => ({ ...prev, startDate: date }));
}}
>
Start: {formatDate(dateRange.startDate)}
</button>
<span className="text-foreground">to</span>
<button
className="px-3 py-1 border rounded text-foreground border-border hover:bg-accent hover:text-accent-foreground transition-colors"
onClick={() => {
const date = new Date(prompt("Enter end date (YYYY-MM-DD)") || dateRange.endDate);
setDateRange(prev => ({ ...prev, endDate: date }));
}}
>
End: {formatDate(dateRange.endDate)}
</button>
</div>
);
const MainNav = () => (
<nav className="flex space-x-4">
{['Overview', 'Customers', 'Products', 'Settings'].map((item) => (
<button
key={item}
className="px-3 py-2 text-sm font-medium text-foreground hover:text-primary transition-colors"
>
{item}
</button>
))}
</nav>
);
const Search = () => (
<div className="relative max-w-md">
<input
type="text"
placeholder="Search..."
className="w-full pl-8 pr-4 py-2 rounded-full border focus:outline-none focus:ring-2 bg-input border-border focus:ring-ring text-foreground placeholder:text-muted-foreground"
/>
<svg
className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
);
const TeamSwitcher = () => (
<div className="relative">
<button
onClick={() => setTeamSwitcherOpen(true)}
className="flex items-center space-x-2 hover:bg-accent hover:text-accent-foreground p-2 rounded transition-colors"
>
<div className="w-8 h-8 rounded-full bg-secondary flex items-center justify-center text-secondary-foreground">
{selectedTeam.label[0]}
</div>
<span className="text-foreground">{selectedTeam.label}</span>
</button>
{teamSwitcherOpen && (
<div className="absolute z-10 mt-2 w-56 bg-popover rounded-md shadow-lg border border-border">
<div className="p-2">
<input
type="text"
placeholder="Search team..."
className="w-full p-2 border rounded bg-input border-border text-foreground placeholder:text-muted-foreground"
/>
</div>
{groups.map((group) => (
<div key={group.label} className="py-1">
<p className="px-3 py-1 text-sm font-medium text-muted-foreground">{group.label}</p>
{group.teams.map((team) => (
<button
key={team.value}
onClick={() => {
setSelectedTeam(team);
setTeamSwitcherOpen(false);
}}
className="w-full text-left px-3 py-2 hover:bg-accent hover:text-accent-foreground text-popover-foreground transition-colors"
>
{team.label}
</button>
))}
</div>
))}
<div className="border-t border-border p-2">
<button
onClick={() => {
setTeamSwitcherOpen(false);
setShowNewTeamDialog(true);
}}
className="w-full p-2 text-left hover:bg-accent hover:text-accent-foreground text-popover-foreground transition-colors"
>
Create Team
</button>
</div>
</div>
</header>
)}
{showNewTeamDialog && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<div className="bg-popover p-4 rounded-md border border-border">
<h3 className="text-lg font-medium mb-4 text-popover-foreground">Create team</h3>
<input
type="text"
placeholder="Team name"
className="w-full p-2 border rounded mb-4 bg-input border-border text-foreground placeholder:text-muted-foreground"
/>
<div className="flex justify-end space-x-2">
<button
onClick={() => setShowNewTeamDialog(false)}
className="px-4 py-2 border rounded border-border text-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
>
Cancel
</button>
<button
onClick={() => setShowNewTeamDialog(false)}
className="px-4 py-2 bg-primary text-primary-foreground rounded hover:opacity-90 transition-opacity"
>
Create
</button>
</div>
</div>
</div>
)}
</div>
);
const UserNav = () => (
<div className="relative">
<button
onClick={() => setUserNavOpen(!userNavOpen)}
className="w-8 h-8 rounded-full bg-secondary flex items-center justify-center text-secondary-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
>
U
</button>
{userNavOpen && (
<div className="absolute right-0 mt-2 w-48 bg-popover rounded-md shadow-lg z-10 border border-border">
<div className="p-2 border-b border-border">
<p className="font-medium text-popover-foreground">shadcn</p>
<p className="text-sm text-muted-foreground">m@example.com</p>
</div>
{['Profile', 'Billing', 'Settings', 'New Team', 'Log out'].map((item) => (
<button
key={item}
className="w-full text-left px-3 py-2 hover:bg-accent hover:text-accent-foreground text-popover-foreground transition-colors"
>
{item}
</button>
))}
</div>
)}
</div>
);
const Overview = () => (
<div className="p-4 border rounded-lg border-border">
<div className="flex justify-between items-end h-40">
{[100, 80, 60, 40, 20].map((height, index) => (
<div
key={index}
className="w-8 opacity-60"
style={{
height: `${height}px`,
backgroundColor: `hsl(var(--chart-${(index % 5) + 1}))`
}}
/>
))}
</div>
</div>
);
const RecentSales = () => (
<div className="space-y-4">
{salesData.map((item, index) => (
<div key={index} className="flex items-center justify-between p-2 border-b border-border">
<div className="flex items-center space-x-3">
<div className="w-8 h-8 rounded-full bg-secondary flex items-center justify-center text-secondary-foreground">
{item.name[0]}
</div>
<div>
<p className="font-medium text-foreground">{item.name}</p>
<p className="text-sm text-muted-foreground">{item.email}</p>
</div>
</div>
<span className="font-medium text-foreground">{item.amount}</span>
</div>
))}
</div>
);
return (
<div className="min-h-screen bg-background text-foreground">
<main className="container p-4 space-y-4">
s
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold">Dashboard</h1>
<h1 className="text-2xl font-bold text-foreground">Dashboard</h1>
<div className="flex items-center space-x-2">
<CalendarDateRangePicker />
<button className="px-4 py-2 bg-blue-500 text-white rounded">
<button className="px-4 py-2 bg-primary text-primary-foreground rounded hover:opacity-90 transition-opacity">
Download
</button>
</div>
@ -40,26 +266,28 @@ s
{ title: "Sales", value: "+12,234", subtext: "+19% from last month" },
{ title: "Active Now", value: "+573", subtext: "+201 since last hour" },
].map((card, index) => (
<div key={index} className="p-6 bg-white border rounded-lg">
<h3 className="text-sm font-medium text-gray-500">{card.title}</h3>
<p className="text-2xl font-bold mt-1">{card.value}</p>
<p className="text-xs text-gray-500 mt-1">{card.subtext}</p>
<div key={index} className="p-6 bg-card border rounded-lg border-border">
<h3 className="text-sm font-medium text-muted-foreground">{card.title}</h3>
<p className="text-2xl font-bold mt-1 text-card-foreground">{card.value}</p>
<p className="text-xs text-muted-foreground mt-1">{card.subtext}</p>
</div>
))}
</div>
<div className="grid gap-4 md:grid-cols-2">
<div className="p-6 bg-white border rounded-lg">
<h3 className="text-lg font-medium mb-4">Overview</h3>
<div className="p-6 bg-card border rounded-lg border-border">
<h3 className="text-lg font-medium mb-4 text-card-foreground">Overview</h3>
<Overview />
</div>
<div className="p-6 bg-white border rounded-lg space-y-4">
<h3 className="text-lg font-medium">Recent Sales</h3>
<p>You made 265 sales this month.</p>
<div className="p-6 bg-card border rounded-lg space-y-4 border-border">
<h3 className="text-lg font-medium text-card-foreground">Recent Sales</h3>
<p className="text-card-foreground">You made 265 sales this month.</p>
<RecentSales />
</div>
</div>
</main>
</div>
);
}
};
export default Dashboard;

File diff suppressed because it is too large Load diff