From afdc30bd5f6689298aa9818f4dc42a8eeea8014b Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Mon, 24 Nov 2025 22:01:04 +0100 Subject: [PATCH] feat(shared-ui): add navigation components and form elements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NEW COMPONENTS: Navigation: - NavLink: Reusable navigation link with active state, tooltips, badges - Navbar: Horizontal top navigation with mobile menu support - Sidebar: Vertical collapsible sidebar with theme toggle support Form Elements: - Select: Dropdown select with placeholder, error states - Textarea: Multi-line input with auto-resize, character count - Checkbox: Accessible checkbox with indeterminate state support ENHANCED: - Card: Added header/footer slots, interactive mode, filled variant EXPORTS: - NavItem, NavbarProps, SidebarProps, NavLinkProps types - SelectOption type for Select component All components use HSL CSS variables from shared-tailwind for consistent theming across all apps. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/shared-ui/src/atoms/Card.svelte | 179 +++++++++-- packages/shared-ui/src/index.ts | 7 +- .../shared-ui/src/molecules/Checkbox.svelte | 161 ++++++++++ .../shared-ui/src/molecules/Select.svelte | 160 ++++++++++ .../shared-ui/src/molecules/Textarea.svelte | 195 ++++++++++++ packages/shared-ui/src/molecules/index.ts | 4 + .../shared-ui/src/navigation/NavLink.svelte | 210 +++++++++++++ .../shared-ui/src/navigation/Navbar.svelte | 270 ++++++++++++++++ .../shared-ui/src/navigation/Sidebar.svelte | 289 ++++++++++++++++++ packages/shared-ui/src/navigation/index.ts | 4 + packages/shared-ui/src/navigation/types.ts | 79 +++++ 11 files changed, 1536 insertions(+), 22 deletions(-) create mode 100644 packages/shared-ui/src/molecules/Checkbox.svelte create mode 100644 packages/shared-ui/src/molecules/Select.svelte create mode 100644 packages/shared-ui/src/molecules/Textarea.svelte create mode 100644 packages/shared-ui/src/navigation/NavLink.svelte create mode 100644 packages/shared-ui/src/navigation/Navbar.svelte create mode 100644 packages/shared-ui/src/navigation/Sidebar.svelte create mode 100644 packages/shared-ui/src/navigation/index.ts create mode 100644 packages/shared-ui/src/navigation/types.ts diff --git a/packages/shared-ui/src/atoms/Card.svelte b/packages/shared-ui/src/atoms/Card.svelte index bfa949288..64a8d3f5e 100644 --- a/packages/shared-ui/src/atoms/Card.svelte +++ b/packages/shared-ui/src/atoms/Card.svelte @@ -1,48 +1,185 @@
{ + if (isInteractive && onclick && (e.key === 'Enter' || e.key === ' ')) { + e.preventDefault(); + onclick(e as unknown as MouseEvent); + } + }} > - {@render children()} + {#if header} +
+ {@render header()} +
+ {/if} + +
+ {@render children()} +
+ + {#if footer} + + {/if}
+ + diff --git a/packages/shared-ui/src/index.ts b/packages/shared-ui/src/index.ts index 81eadfd0d..83e2a5fb2 100644 --- a/packages/shared-ui/src/index.ts +++ b/packages/shared-ui/src/index.ts @@ -2,8 +2,13 @@ export { Text, Button, Badge, Card } from './atoms'; // Molecules -export { Toggle, Input } from './molecules'; +export { Toggle, Input, Select, Textarea, Checkbox } from './molecules'; +export type { SelectOption } from './molecules'; // Organisms export { Modal, AppSlider } from './organisms'; export type { AppItem } from './organisms'; + +// Navigation +export { NavLink, Navbar, Sidebar } from './navigation'; +export type { NavItem, NavbarProps, SidebarProps, NavLinkProps } from './navigation'; diff --git a/packages/shared-ui/src/molecules/Checkbox.svelte b/packages/shared-ui/src/molecules/Checkbox.svelte new file mode 100644 index 000000000..ea6d47d19 --- /dev/null +++ b/packages/shared-ui/src/molecules/Checkbox.svelte @@ -0,0 +1,161 @@ + + + + + diff --git a/packages/shared-ui/src/molecules/Select.svelte b/packages/shared-ui/src/molecules/Select.svelte new file mode 100644 index 000000000..863eb7422 --- /dev/null +++ b/packages/shared-ui/src/molecules/Select.svelte @@ -0,0 +1,160 @@ + + +
+ {#if label} + + {/if} + +
+ +
+ + + +
+
+ + {#if error} +

{error}

+ {/if} +
+ + diff --git a/packages/shared-ui/src/molecules/Textarea.svelte b/packages/shared-ui/src/molecules/Textarea.svelte new file mode 100644 index 000000000..66172a629 --- /dev/null +++ b/packages/shared-ui/src/molecules/Textarea.svelte @@ -0,0 +1,195 @@ + + +
+ {#if label} + + {/if} + + + + +
+ + diff --git a/packages/shared-ui/src/molecules/index.ts b/packages/shared-ui/src/molecules/index.ts index 823bc0749..5ed2eeb05 100644 --- a/packages/shared-ui/src/molecules/index.ts +++ b/packages/shared-ui/src/molecules/index.ts @@ -1,2 +1,6 @@ export { default as Toggle } from './Toggle.svelte'; export { default as Input } from './Input.svelte'; +export { default as Select } from './Select.svelte'; +export { default as Textarea } from './Textarea.svelte'; +export { default as Checkbox } from './Checkbox.svelte'; +export type { SelectOption } from './Select.svelte'; diff --git a/packages/shared-ui/src/navigation/NavLink.svelte b/packages/shared-ui/src/navigation/NavLink.svelte new file mode 100644 index 000000000..b2738c420 --- /dev/null +++ b/packages/shared-ui/src/navigation/NavLink.svelte @@ -0,0 +1,210 @@ + + + + {#if item.icon} + + {#if item.icon.startsWith(' + {@html item.icon.startsWith('M') ? `` : item.icon} + {:else} + + {item.icon} + {/if} + + {/if} + + {#if !minimized} + {item.label} + {/if} + + {#if item.badge !== undefined && !minimized} + {item.badge} + {/if} + + {#if item.shortcut && !minimized} + {item.shortcut} + {/if} + + {#if minimized && showTooltip} + {item.label} + {/if} + + + diff --git a/packages/shared-ui/src/navigation/Navbar.svelte b/packages/shared-ui/src/navigation/Navbar.svelte new file mode 100644 index 000000000..4b5da706f --- /dev/null +++ b/packages/shared-ui/src/navigation/Navbar.svelte @@ -0,0 +1,270 @@ + + + + + diff --git a/packages/shared-ui/src/navigation/Sidebar.svelte b/packages/shared-ui/src/navigation/Sidebar.svelte new file mode 100644 index 000000000..ca3ae475b --- /dev/null +++ b/packages/shared-ui/src/navigation/Sidebar.svelte @@ -0,0 +1,289 @@ + + + + + diff --git a/packages/shared-ui/src/navigation/index.ts b/packages/shared-ui/src/navigation/index.ts new file mode 100644 index 000000000..5dbbc3ea3 --- /dev/null +++ b/packages/shared-ui/src/navigation/index.ts @@ -0,0 +1,4 @@ +export { default as NavLink } from './NavLink.svelte'; +export { default as Navbar } from './Navbar.svelte'; +export { default as Sidebar } from './Sidebar.svelte'; +export type { NavItem, NavbarProps, SidebarProps, NavLinkProps } from './types'; diff --git a/packages/shared-ui/src/navigation/types.ts b/packages/shared-ui/src/navigation/types.ts new file mode 100644 index 000000000..9ba9d0bb5 --- /dev/null +++ b/packages/shared-ui/src/navigation/types.ts @@ -0,0 +1,79 @@ +import type { Snippet } from 'svelte'; + +export interface NavItem { + /** Display label for the navigation item */ + label: string; + /** URL to navigate to */ + href: string; + /** Icon - can be emoji, SVG path, or component name */ + icon?: string; + /** Whether this item is currently active */ + active?: boolean; + /** Badge text (e.g., notification count) */ + badge?: string | number; + /** Whether the item is disabled */ + disabled?: boolean; + /** Keyboard shortcut hint */ + shortcut?: string; +} + +export interface NavbarProps { + /** Navigation items to display */ + items: NavItem[]; + /** Logo snippet or component */ + logo?: Snippet; + /** App name to display next to logo */ + appName?: string; + /** Current pathname for active state detection */ + currentPath?: string; + /** User email to display */ + userEmail?: string; + /** Show mobile menu */ + showMobile?: boolean; + /** Called when sign out is clicked */ + onSignOut?: () => void; + /** Additional CSS classes */ + class?: string; +} + +export interface SidebarProps { + /** Navigation items to display */ + items: NavItem[]; + /** Logo snippet or component */ + logo?: Snippet; + /** App name to display */ + appName?: string; + /** Current pathname for active state detection */ + currentPath?: string; + /** Whether sidebar is minimized/collapsed */ + minimized?: boolean; + /** Called when minimize toggle is clicked */ + onToggleMinimize?: () => void; + /** User email to display */ + userEmail?: string; + /** Called when sign out is clicked */ + onSignOut?: () => void; + /** Show theme toggle */ + showThemeToggle?: boolean; + /** Called when theme toggle is clicked */ + onToggleTheme?: () => void; + /** Current theme mode (for icon display) */ + isDark?: boolean; + /** Additional CSS classes */ + class?: string; + /** Footer items (shortcuts, etc.) */ + footerItems?: NavItem[]; +} + +export interface NavLinkProps { + /** Navigation item data */ + item: NavItem; + /** Whether the link is active */ + active?: boolean; + /** Display variant */ + variant?: 'default' | 'sidebar' | 'mobile' | 'pill'; + /** Whether in minimized sidebar mode (show tooltip) */ + minimized?: boolean; + /** Additional CSS classes */ + class?: string; +}