Style guide improvement
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
Spinner,
|
||||
Toggle,
|
||||
Toast,
|
||||
ToastContainer,
|
||||
Chip,
|
||||
ListItem,
|
||||
OrgHeader,
|
||||
@@ -27,6 +28,22 @@
|
||||
StatCard,
|
||||
StatusBadge,
|
||||
TabBar,
|
||||
Icon,
|
||||
ContextMenu,
|
||||
KanbanColumn,
|
||||
AssigneePicker,
|
||||
MemberList,
|
||||
ActivityFeed,
|
||||
EventCard,
|
||||
ModuleCard,
|
||||
QuickLinkGrid,
|
||||
PageSkeleton,
|
||||
ContentSkeleton,
|
||||
Twemoji,
|
||||
EmojiPicker,
|
||||
ImagePreviewModal,
|
||||
VirtualList,
|
||||
PersonContactModal,
|
||||
} from "$lib/components/ui";
|
||||
|
||||
let inputValue = $state("");
|
||||
@@ -34,9 +51,96 @@
|
||||
let selectValue = $state("");
|
||||
let toggleChecked = $state(false);
|
||||
let modalOpen = $state(false);
|
||||
let imagePreviewOpen = $state(false);
|
||||
let emojiPickerOpen = $state(false);
|
||||
let personModalProfile = $state<any>(null);
|
||||
let assigneeValue = $state<string | null>(null);
|
||||
let activeTab = $state("tab1");
|
||||
let searchFilter = $state("");
|
||||
|
||||
const demoMembers = [
|
||||
{
|
||||
id: "1",
|
||||
user_id: "u1",
|
||||
role: "owner",
|
||||
profiles: {
|
||||
id: "u1",
|
||||
email: "alice@example.com",
|
||||
full_name: "Alice Johnson",
|
||||
avatar_url: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
user_id: "u2",
|
||||
role: "admin",
|
||||
profiles: {
|
||||
id: "u2",
|
||||
email: "bob@example.com",
|
||||
full_name: "Bob Smith",
|
||||
avatar_url: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
user_id: "u3",
|
||||
role: "member",
|
||||
profiles: {
|
||||
id: "u3",
|
||||
email: "charlie@example.com",
|
||||
full_name: "Charlie Brown",
|
||||
avatar_url: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
user_id: "u4",
|
||||
role: "member",
|
||||
profiles: {
|
||||
id: "u4",
|
||||
email: "diana@example.com",
|
||||
full_name: "Diana Prince",
|
||||
avatar_url: null,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const demoActivity = [
|
||||
{
|
||||
id: "a1",
|
||||
action: "create",
|
||||
entity_type: "document",
|
||||
entity_id: "d1",
|
||||
entity_name: "Project Brief",
|
||||
created_at: new Date(Date.now() - 300000).toISOString(),
|
||||
profiles: {
|
||||
full_name: "Alice Johnson",
|
||||
email: "alice@example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "a2",
|
||||
action: "update",
|
||||
entity_type: "kanban_card",
|
||||
entity_id: "k1",
|
||||
entity_name: "Design Review",
|
||||
created_at: new Date(Date.now() - 3600000).toISOString(),
|
||||
profiles: { full_name: "Bob Smith", email: "bob@example.com" },
|
||||
},
|
||||
{
|
||||
id: "a3",
|
||||
action: "delete",
|
||||
entity_type: "folder",
|
||||
entity_id: "f1",
|
||||
entity_name: "Old Files",
|
||||
created_at: new Date(Date.now() - 86400000).toISOString(),
|
||||
profiles: {
|
||||
full_name: "Charlie Brown",
|
||||
email: "charlie@example.com",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const selectOptions = [
|
||||
{ value: "option1", label: "Option 1" },
|
||||
{ value: "option2", label: "Option 2" },
|
||||
@@ -47,7 +151,7 @@
|
||||
// Component Registry - add new components here and they render automatically
|
||||
// Each entry: { name, description, sections: [{ label, ... }] }
|
||||
// ============================================================
|
||||
const componentCount = 25;
|
||||
const componentCount = 42;
|
||||
|
||||
const colors = [
|
||||
{
|
||||
@@ -97,9 +201,13 @@
|
||||
"Colors",
|
||||
"Typography",
|
||||
"Button",
|
||||
"Icon",
|
||||
"IconButton",
|
||||
"Input",
|
||||
"Textarea",
|
||||
"Select",
|
||||
"Toggle",
|
||||
"AssigneePicker",
|
||||
"Avatar",
|
||||
"Badge",
|
||||
"Chip",
|
||||
@@ -107,21 +215,33 @@
|
||||
"Card",
|
||||
"SectionCard",
|
||||
"StatCard",
|
||||
"Toggle",
|
||||
"ModuleCard",
|
||||
"EventCard",
|
||||
"KanbanColumn",
|
||||
"ListItem",
|
||||
"MemberList",
|
||||
"ActivityFeed",
|
||||
"QuickLinkGrid",
|
||||
"Modal",
|
||||
"ImagePreviewModal",
|
||||
"PersonContactModal",
|
||||
"ContextMenu",
|
||||
"Dropdown",
|
||||
"Toast",
|
||||
"Spinner",
|
||||
"Skeleton",
|
||||
"Modal",
|
||||
"Toast",
|
||||
"PageSkeleton",
|
||||
"ContentSkeleton",
|
||||
"Logo",
|
||||
"ListItem",
|
||||
"OrgHeader",
|
||||
"IconButton",
|
||||
"ContentHeader",
|
||||
"PageHeader",
|
||||
"CalendarDay",
|
||||
"TabBar",
|
||||
"EmptyState",
|
||||
"Dropdown",
|
||||
"Twemoji",
|
||||
"EmojiPicker",
|
||||
"VirtualList",
|
||||
];
|
||||
|
||||
const filteredSections = $derived(
|
||||
@@ -339,6 +459,54 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Icon -->
|
||||
<section id="icon" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
Icon
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3
|
||||
class="text-body-sm font-heading text-light/60 mb-2"
|
||||
>
|
||||
Sizes
|
||||
</h3>
|
||||
<div class="flex items-end gap-4">
|
||||
{#each [16, 20, 24, 32, 48] as size}
|
||||
<div class="flex flex-col items-center gap-1">
|
||||
<Icon name="star" {size} />
|
||||
<span class="text-[10px] text-light/30"
|
||||
>{size}px</span
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3
|
||||
class="text-body-sm font-heading text-light/60 mb-2"
|
||||
>
|
||||
Various Icons
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
{#each ["home", "settings", "search", "notifications", "person", "edit", "delete", "add", "check_circle", "error", "info", "warning", "folder", "description", "calendar_today", "group"] as name}
|
||||
<div
|
||||
class="flex flex-col items-center gap-1 w-14"
|
||||
>
|
||||
<Icon {name} size={24} />
|
||||
<span
|
||||
class="text-[9px] text-light/30 truncate w-full text-center"
|
||||
>{name}</span
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Input -->
|
||||
<section id="input" class="space-y-4">
|
||||
<h2
|
||||
@@ -976,6 +1144,578 @@
|
||||
</Dropdown>
|
||||
</section>
|
||||
|
||||
<!-- AssigneePicker -->
|
||||
<section id="assigneepicker" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
AssigneePicker
|
||||
</h2>
|
||||
<div class="max-w-sm">
|
||||
<AssigneePicker
|
||||
value={assigneeValue}
|
||||
members={demoMembers}
|
||||
label="Assigned to"
|
||||
onchange={(v) => (assigneeValue = v)}
|
||||
/>
|
||||
</div>
|
||||
<p class="text-body-sm text-light/40">
|
||||
Selected: {assigneeValue ?? "none"}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ContextMenu -->
|
||||
<section id="contextmenu" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
ContextMenu
|
||||
</h2>
|
||||
<p class="text-body-sm text-light/40">
|
||||
A three-dot menu that opens a dropdown with actions. Click
|
||||
the icon to toggle.
|
||||
</p>
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="flex flex-col items-center gap-1">
|
||||
<ContextMenu
|
||||
items={[
|
||||
{
|
||||
label: "Edit",
|
||||
icon: "edit",
|
||||
onclick: () => {},
|
||||
},
|
||||
{
|
||||
label: "Duplicate",
|
||||
icon: "content_copy",
|
||||
onclick: () => {},
|
||||
},
|
||||
{
|
||||
label: "",
|
||||
icon: "",
|
||||
onclick: () => {},
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
icon: "delete",
|
||||
onclick: () => {},
|
||||
danger: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<span class="text-[10px] text-light/30">right</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center gap-1">
|
||||
<ContextMenu
|
||||
align="left"
|
||||
items={[
|
||||
{
|
||||
label: "Share",
|
||||
icon: "share",
|
||||
onclick: () => {},
|
||||
},
|
||||
{
|
||||
label: "Export",
|
||||
icon: "download",
|
||||
onclick: () => {},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<span class="text-[10px] text-light/30">left</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- KanbanColumn -->
|
||||
<section id="kanbancolumn" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
KanbanColumn
|
||||
</h2>
|
||||
<div
|
||||
class="flex gap-3 overflow-x-auto pb-2"
|
||||
style="height: 320px"
|
||||
>
|
||||
<KanbanColumn title="To Do" count={2} onAddCard={() => {}}>
|
||||
<div
|
||||
class="bg-surface border border-light/5 rounded-xl p-3"
|
||||
>
|
||||
<p class="text-body-sm text-white">
|
||||
Design mockups
|
||||
</p>
|
||||
<p class="text-[11px] text-light/40 mt-1">
|
||||
Due tomorrow
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="bg-surface border border-light/5 rounded-xl p-3"
|
||||
>
|
||||
<p class="text-body-sm text-white">Write specs</p>
|
||||
</div>
|
||||
</KanbanColumn>
|
||||
<KanbanColumn
|
||||
title="In Progress"
|
||||
count={1}
|
||||
onAddCard={() => {}}
|
||||
onMore={() => {}}
|
||||
>
|
||||
<div
|
||||
class="bg-surface border border-light/5 rounded-xl p-3"
|
||||
>
|
||||
<p class="text-body-sm text-white">
|
||||
API integration
|
||||
</p>
|
||||
<p class="text-[11px] text-light/40 mt-1">
|
||||
Started 2h ago
|
||||
</p>
|
||||
</div>
|
||||
</KanbanColumn>
|
||||
<KanbanColumn title="Done" count={0} onAddCard={() => {}} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- MemberList -->
|
||||
<section id="memberlist" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
MemberList
|
||||
</h2>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div
|
||||
class="bg-dark/30 border border-light/5 p-4 rounded-xl"
|
||||
>
|
||||
<h3
|
||||
class="text-body-sm font-heading text-light/60 mb-2"
|
||||
>
|
||||
With Members
|
||||
</h3>
|
||||
<MemberList
|
||||
members={demoMembers}
|
||||
max={3}
|
||||
moreHref="#"
|
||||
moreLabel="+1 more"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="bg-dark/30 border border-light/5 p-4 rounded-xl"
|
||||
>
|
||||
<h3
|
||||
class="text-body-sm font-heading text-light/60 mb-2"
|
||||
>
|
||||
Empty
|
||||
</h3>
|
||||
<MemberList members={[]} emptyLabel="No members yet" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ActivityFeed -->
|
||||
<section id="activityfeed" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
ActivityFeed
|
||||
</h2>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div
|
||||
class="bg-dark/30 border border-light/5 p-4 rounded-xl"
|
||||
>
|
||||
<h3
|
||||
class="text-body-sm font-heading text-light/60 mb-2"
|
||||
>
|
||||
With Entries
|
||||
</h3>
|
||||
<ActivityFeed entries={demoActivity} />
|
||||
</div>
|
||||
<div
|
||||
class="bg-dark/30 border border-light/5 p-4 rounded-xl"
|
||||
>
|
||||
<h3
|
||||
class="text-body-sm font-heading text-light/60 mb-2"
|
||||
>
|
||||
Empty
|
||||
</h3>
|
||||
<ActivityFeed entries={[]} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- EventCard -->
|
||||
<section id="eventcard" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
EventCard
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3
|
||||
class="text-body-sm font-heading text-light/60 mb-2"
|
||||
>
|
||||
Full Card
|
||||
</h3>
|
||||
<div class="grid md:grid-cols-2 gap-4">
|
||||
<EventCard
|
||||
name="Summer Hackathon"
|
||||
slug="summer-hackathon"
|
||||
status="active"
|
||||
startDate="2026-06-15"
|
||||
endDate="2026-06-17"
|
||||
color="#33b679"
|
||||
venueName="Tech Hub"
|
||||
href="#"
|
||||
/>
|
||||
<EventCard
|
||||
name="Design Sprint"
|
||||
slug="design-sprint"
|
||||
status="planning"
|
||||
startDate="2026-07-01"
|
||||
endDate={null}
|
||||
color="#7986cb"
|
||||
venueName={null}
|
||||
href="#"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3
|
||||
class="text-body-sm font-heading text-light/60 mb-2"
|
||||
>
|
||||
Compact
|
||||
</h3>
|
||||
<div
|
||||
class="max-w-md bg-dark/30 border border-light/5 rounded-xl overflow-hidden"
|
||||
>
|
||||
<EventCard
|
||||
name="Team Retreat"
|
||||
slug="team-retreat"
|
||||
status="completed"
|
||||
startDate="2026-03-10"
|
||||
endDate="2026-03-12"
|
||||
color="#e67c73"
|
||||
venueName="Mountain Lodge"
|
||||
href="#"
|
||||
compact
|
||||
/>
|
||||
<EventCard
|
||||
name="Q2 Planning"
|
||||
slug="q2-planning"
|
||||
status="draft"
|
||||
startDate="2026-04-01"
|
||||
endDate={null}
|
||||
color="#f6bf26"
|
||||
venueName={null}
|
||||
href="#"
|
||||
compact
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ModuleCard -->
|
||||
<section id="modulecard" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
ModuleCard
|
||||
</h2>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<ModuleCard
|
||||
label="Documents"
|
||||
description="Shared files and folders"
|
||||
icon="description"
|
||||
href="#"
|
||||
/>
|
||||
<ModuleCard
|
||||
label="Calendar"
|
||||
description="Events and scheduling"
|
||||
icon="calendar_today"
|
||||
href="#"
|
||||
color="text-success"
|
||||
bg="bg-success/10"
|
||||
/>
|
||||
<ModuleCard
|
||||
label="Kanban"
|
||||
description="Task boards"
|
||||
icon="view_kanban"
|
||||
href="#"
|
||||
color="text-warning"
|
||||
bg="bg-warning/10"
|
||||
/>
|
||||
<ModuleCard
|
||||
label="Finances"
|
||||
description="Budget tracking"
|
||||
icon="payments"
|
||||
href="#"
|
||||
color="text-error"
|
||||
bg="bg-error/10"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- QuickLinkGrid -->
|
||||
<section id="quicklinkgrid" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
QuickLinkGrid
|
||||
</h2>
|
||||
<div class="max-w-xs">
|
||||
<QuickLinkGrid
|
||||
links={[
|
||||
{
|
||||
label: "Documents",
|
||||
icon: "description",
|
||||
href: "#",
|
||||
color: "text-primary",
|
||||
},
|
||||
{
|
||||
label: "Calendar",
|
||||
icon: "calendar_today",
|
||||
href: "#",
|
||||
color: "text-success",
|
||||
},
|
||||
{
|
||||
label: "Team",
|
||||
icon: "group",
|
||||
href: "#",
|
||||
color: "text-warning",
|
||||
},
|
||||
{
|
||||
label: "Settings",
|
||||
icon: "settings",
|
||||
href: "#",
|
||||
color: "text-light/50",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ImagePreviewModal -->
|
||||
<section id="imagepreviewmodal" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
ImagePreviewModal
|
||||
</h2>
|
||||
<Button onclick={() => (imagePreviewOpen = true)}
|
||||
>Open Image Preview</Button
|
||||
>
|
||||
</section>
|
||||
{#if imagePreviewOpen}
|
||||
<ImagePreviewModal
|
||||
src="https://placehold.co/800x600/0F1B2E/00A3E0?text=Preview"
|
||||
alt="Demo image"
|
||||
onClose={() => (imagePreviewOpen = false)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- PersonContactModal -->
|
||||
<section id="personcontactmodal" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
PersonContactModal
|
||||
</h2>
|
||||
<Button
|
||||
onclick={() =>
|
||||
(personModalProfile = {
|
||||
id: "demo-1",
|
||||
full_name: "Alice Johnson",
|
||||
email: "alice@example.com",
|
||||
phone: "+1 555-0123",
|
||||
avatar_url: null,
|
||||
discord_handle: "alice#1234",
|
||||
shirt_size: "M",
|
||||
hoodie_size: "M",
|
||||
})}
|
||||
>
|
||||
Open Person Modal
|
||||
</Button>
|
||||
</section>
|
||||
<PersonContactModal
|
||||
profile={personModalProfile}
|
||||
role="admin"
|
||||
roleName="Admin"
|
||||
isOpen={personModalProfile !== null}
|
||||
onClose={() => (personModalProfile = null)}
|
||||
/>
|
||||
|
||||
<!-- PageSkeleton -->
|
||||
<section id="pageskeleton" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
PageSkeleton
|
||||
</h2>
|
||||
<div class="space-y-6">
|
||||
{#each ["default", "kanban", "files", "calendar", "settings"] as variant}
|
||||
<div>
|
||||
<h3
|
||||
class="text-body-sm font-heading text-light/60 mb-2"
|
||||
>
|
||||
{variant}
|
||||
</h3>
|
||||
<div
|
||||
class="bg-dark/30 border border-light/5 rounded-xl overflow-hidden h-[200px]"
|
||||
>
|
||||
<PageSkeleton
|
||||
variant={variant as
|
||||
| "default"
|
||||
| "kanban"
|
||||
| "files"
|
||||
| "calendar"
|
||||
| "settings"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ContentSkeleton -->
|
||||
<section id="contentskeleton" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
ContentSkeleton
|
||||
</h2>
|
||||
<div class="space-y-6">
|
||||
{#each ["default", "kanban", "files", "list", "detail"] as variant}
|
||||
<div>
|
||||
<h3
|
||||
class="text-body-sm font-heading text-light/60 mb-2"
|
||||
>
|
||||
{variant}
|
||||
</h3>
|
||||
<div
|
||||
class="bg-dark/30 border border-light/5 rounded-xl overflow-hidden h-[200px]"
|
||||
>
|
||||
<ContentSkeleton
|
||||
variant={variant as
|
||||
| "default"
|
||||
| "kanban"
|
||||
| "files"
|
||||
| "list"
|
||||
| "detail"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Twemoji -->
|
||||
<section id="twemoji" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
Twemoji
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3
|
||||
class="text-body-sm font-heading text-light/60 mb-2"
|
||||
>
|
||||
Sizes
|
||||
</h3>
|
||||
<div class="flex items-end gap-4">
|
||||
{#each [16, 24, 32, 48] as size}
|
||||
<div class="flex flex-col items-center gap-1">
|
||||
<Twemoji emoji="🚀" {size} />
|
||||
<span class="text-[10px] text-light/30"
|
||||
>{size}px</span
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3
|
||||
class="text-body-sm font-heading text-light/60 mb-2"
|
||||
>
|
||||
Various Emojis
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
{#each ["😀", "🎉", "🔥", "💡", "⭐", "🎯", "📦", "🛠️", "✅", "❌", "🌍", "🎨"] as emoji}
|
||||
<Twemoji {emoji} size={32} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- EmojiPicker -->
|
||||
<section id="emojipicker" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
EmojiPicker
|
||||
</h2>
|
||||
<p class="text-body-sm text-light/40">
|
||||
A full emoji picker with category tabs and search. Click to
|
||||
open.
|
||||
</p>
|
||||
<div class="relative">
|
||||
<Button onclick={() => (emojiPickerOpen = !emojiPickerOpen)}
|
||||
>{emojiPickerOpen
|
||||
? "Close Picker"
|
||||
: "Open Emoji Picker"}</Button
|
||||
>
|
||||
{#if emojiPickerOpen}
|
||||
<div class="absolute top-12 left-0 z-50">
|
||||
<EmojiPicker
|
||||
onSelect={(emoji) => {
|
||||
emojiPickerOpen = false;
|
||||
}}
|
||||
onClose={() => (emojiPickerOpen = false)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- VirtualList -->
|
||||
<section id="virtuallist" class="space-y-4">
|
||||
<h2
|
||||
class="font-heading text-body text-white border-b border-light/5 pb-2"
|
||||
>
|
||||
VirtualList
|
||||
</h2>
|
||||
<p class="text-body-sm text-light/40">
|
||||
Renders only visible items for large lists. Scroll to see
|
||||
virtualization in action.
|
||||
</p>
|
||||
<div
|
||||
class="bg-dark/30 border border-light/5 rounded-xl overflow-hidden"
|
||||
style="height: 240px"
|
||||
>
|
||||
<VirtualList
|
||||
items={Array.from({ length: 1000 }, (_, i) => ({
|
||||
id: i,
|
||||
label: `Item ${i + 1}`,
|
||||
}))}
|
||||
itemHeight={40}
|
||||
getKey={(item) => String(item.id)}
|
||||
>
|
||||
{#snippet children(
|
||||
item: { id: number; label: string },
|
||||
_index: number,
|
||||
)}
|
||||
<div
|
||||
class="flex items-center px-4 h-[40px] border-b border-light/5 text-body-sm text-light/70"
|
||||
>
|
||||
{item.label}
|
||||
</div>
|
||||
{/snippet}
|
||||
</VirtualList>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="text-center py-8 border-t border-light/5">
|
||||
<p class="text-[11px] text-light/30 font-body">
|
||||
|
||||
Reference in New Issue
Block a user