@scripso-homepad/ui 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/chunk-C7GHBVMM.js +614 -0
  2. package/dist/chunk-C7GHBVMM.js.map +1 -0
  3. package/dist/index.cjs +33 -22
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +5 -1
  6. package/dist/index.d.ts +5 -1
  7. package/dist/index.js +29 -624
  8. package/dist/index.js.map +1 -1
  9. package/dist/web/index.cjs +1211 -0
  10. package/dist/web/index.cjs.map +1 -0
  11. package/dist/web/index.d.cts +156 -0
  12. package/dist/web/index.d.ts +156 -0
  13. package/dist/web/index.js +761 -0
  14. package/dist/web/index.js.map +1 -0
  15. package/package.json +26 -2
  16. package/src/components/Input.tsx +12 -1
  17. package/src/web/hooks/useMediaQuery.ts +23 -0
  18. package/src/web/hooks/useOnClickOutside.ts +30 -0
  19. package/src/web/icons/BellIcon.tsx +27 -0
  20. package/src/web/icons/BuildingIcon.tsx +55 -0
  21. package/src/web/index.ts +37 -0
  22. package/src/web/layout/AppHeader.stories.tsx +85 -0
  23. package/src/web/layout/AppHeader.tsx +115 -0
  24. package/src/web/layout/BuildingSelect.stories.tsx +60 -0
  25. package/src/web/layout/BuildingSelect.tsx +208 -0
  26. package/src/web/layout/DashboardLayout.stories.tsx +87 -0
  27. package/src/web/layout/DashboardLayout.tsx +37 -0
  28. package/src/web/layout/Sidebar.stories.tsx +80 -0
  29. package/src/web/layout/Sidebar.tsx +244 -0
  30. package/src/web/layout/SidebarMobileHeader.stories.tsx +47 -0
  31. package/src/web/layout/SidebarMobileHeader.tsx +48 -0
  32. package/src/web/layout/SidebarNavItem.tsx +60 -0
  33. package/src/web/layout/SidebarUserCard.tsx +79 -0
  34. package/src/web/layout/story-fixtures.tsx +93 -0
  35. package/src/web/layout/story-helpers.tsx +5 -0
  36. package/src/web/layout/types.ts +48 -0
  37. package/src/web/utils/cn.ts +6 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/web/utils/cn.ts","../../src/web/icons/BellIcon.tsx","../../src/web/icons/BuildingIcon.tsx","../../src/web/hooks/useOnClickOutside.ts","../../src/web/layout/BuildingSelect.tsx","../../src/web/layout/AppHeader.tsx","../../src/web/hooks/useMediaQuery.ts","../../src/web/layout/SidebarNavItem.tsx","../../src/web/layout/SidebarUserCard.tsx","../../src/web/layout/Sidebar.tsx","../../src/web/layout/SidebarMobileHeader.tsx","../../src/web/layout/DashboardLayout.tsx"],"names":["jsx","jsxs","useState","useEffect","useCallback","useRef","Fragment"],"mappings":"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","file":"index.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","import { cn } from '../utils/cn';\n\ntype BellIconProps = {\n className?: string;\n};\n\nexport function BellIcon({ className }: BellIconProps) {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 20 20\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden\n className={cn('shrink-0', className)}\n >\n <path\n d=\"M8.58333 17.4998C8.72282 17.7535 8.92787 17.9651 9.17708 18.1125C9.42628 18.2599 9.71048 18.3376 10 18.3376C10.2895 18.3376 10.5737 18.2599 10.8229 18.1125C11.0721 17.9651 11.2772 17.7535 11.4167 17.4998M5 6.6665C5 5.34042 5.52678 4.06865 6.46447 3.13097C7.40215 2.19329 8.67392 1.6665 10 1.6665C11.3261 1.6665 12.5979 2.19329 13.5355 3.13097C14.4732 4.06865 15 5.34042 15 6.6665C15 12.4998 17.5 14.1665 17.5 14.1665H2.5C2.5 14.1665 5 12.4998 5 6.6665Z\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n","import { cn } from '../utils/cn';\n\ntype BuildingIconProps = {\n className?: string;\n};\n\nexport function BuildingIcon({ className }: BuildingIconProps) {\n return (\n <svg\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden\n className={cn('shrink-0', className)}\n >\n <path\n d=\"M10 12h4\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M10 8h4\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M14 21v-3a2 2 0 0 0-4 0v3\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M6 10H4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-2\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M6 21V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v16\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n","import { useEffect, type RefObject } from 'react';\n\nexport function useOnClickOutside<T extends HTMLElement>(\n ref: RefObject<T | null>,\n handler: () => void,\n enabled = true,\n) {\n useEffect(() => {\n if (!enabled) {\n return;\n }\n\n const onPointerDown = (event: MouseEvent | TouchEvent) => {\n const target = event.target as Node | null;\n if (!ref.current || !target || ref.current.contains(target)) {\n return;\n }\n\n handler();\n };\n\n document.addEventListener('mousedown', onPointerDown);\n document.addEventListener('touchstart', onPointerDown);\n\n return () => {\n document.removeEventListener('mousedown', onPointerDown);\n document.removeEventListener('touchstart', onPointerDown);\n };\n }, [enabled, handler, ref]);\n}\n","import { ChevronsUpDown } from 'lucide-react';\nimport { useCallback, useId, useRef, useState, type KeyboardEvent, type ReactNode } from 'react';\n\nimport { BuildingIcon } from '../icons/BuildingIcon';\nimport { useOnClickOutside } from '../hooks/useOnClickOutside';\nimport { cn } from '../utils/cn';\nimport type { AdminBuildingOption, AdminHeaderLabels } from './types';\n\nexport type BuildingSelectProps = {\n options: AdminBuildingOption[];\n value: string;\n labels: Pick<AdminHeaderLabels, 'selectBuilding'>;\n onChange?: (buildingId: string) => void;\n disabled?: boolean;\n className?: string;\n menuClassName?: string;\n buildingIcon?: ReactNode;\n chevronIcon?: ReactNode;\n};\n\nexport function BuildingSelect({\n options,\n value,\n labels,\n onChange,\n disabled = false,\n className,\n menuClassName,\n buildingIcon,\n chevronIcon,\n}: BuildingSelectProps) {\n const [open, setOpen] = useState(false);\n const [highlightedIndex, setHighlightedIndex] = useState(-1);\n const rootRef = useRef<HTMLDivElement>(null);\n const listboxId = useId();\n\n const selectedOption = options.find((option) => option.value === value);\n const displayLabel = selectedOption?.label ?? labels.selectBuilding;\n const selectableOptions = options.filter((option) => !option.disabled);\n\n const closeMenu = useCallback(() => {\n setOpen(false);\n setHighlightedIndex(-1);\n }, []);\n\n const openMenu = useCallback(() => {\n if (disabled || selectableOptions.length === 0) {\n return;\n }\n\n const selectedIndex = selectableOptions.findIndex((option) => option.value === value);\n setHighlightedIndex(selectedIndex >= 0 ? selectedIndex : 0);\n setOpen(true);\n }, [disabled, selectableOptions, value]);\n\n const selectOption = useCallback(\n (option: AdminBuildingOption) => {\n if (option.disabled) {\n return;\n }\n\n onChange?.(option.value);\n closeMenu();\n },\n [closeMenu, onChange],\n );\n\n useOnClickOutside(rootRef, closeMenu, open);\n\n const handleTriggerKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {\n if (disabled) {\n return;\n }\n\n switch (event.key) {\n case 'ArrowDown':\n case 'Enter':\n case ' ':\n event.preventDefault();\n openMenu();\n break;\n case 'Escape':\n closeMenu();\n break;\n default:\n break;\n }\n };\n\n const handleListKeyDown = (event: KeyboardEvent<HTMLUListElement>) => {\n if (!open || selectableOptions.length === 0) {\n return;\n }\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n setHighlightedIndex((current) => (current + 1) % selectableOptions.length);\n break;\n case 'ArrowUp':\n event.preventDefault();\n setHighlightedIndex(\n (current) => (current - 1 + selectableOptions.length) % selectableOptions.length,\n );\n break;\n case 'Enter':\n case ' ':\n event.preventDefault();\n if (highlightedIndex >= 0) {\n selectOption(selectableOptions[highlightedIndex]);\n }\n break;\n case 'Escape':\n event.preventDefault();\n closeMenu();\n break;\n case 'Tab':\n closeMenu();\n break;\n default:\n break;\n }\n };\n\n return (\n <div ref={rootRef} className={cn('relative w-57.5', className)}>\n <button\n type=\"button\"\n disabled={disabled}\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={listboxId}\n aria-label={labels.selectBuilding}\n onClick={() => (open ? closeMenu() : openMenu())}\n onKeyDown={handleTriggerKeyDown}\n className={cn(\n 'flex h-11 w-full items-center gap-3 rounded-xl border border-storm-gray-50 bg-white p-3 text-left transition-colors',\n !disabled && 'cursor-pointer hover:bg-storm-gray-50/60',\n open && 'bg-storm-gray-50/60',\n disabled && 'cursor-not-allowed opacity-60',\n )}\n >\n {buildingIcon ?? <BuildingIcon className=\"shrink-0 text-navy\" />}\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium text-slate-blue\">\n {displayLabel}\n </span>\n {chevronIcon ?? (\n <ChevronsUpDown\n size={16}\n strokeWidth={1.75}\n className={cn(\n 'shrink-0 text-storm-gray-100 transition-transform duration-200',\n open && 'rotate-180',\n )}\n aria-hidden\n />\n )}\n </button>\n\n {open ? (\n <ul\n id={listboxId}\n role=\"listbox\"\n aria-label={labels.selectBuilding}\n tabIndex={-1}\n onKeyDown={handleListKeyDown}\n className={cn(\n 'absolute top-[calc(100%+4px)] left-0 z-50 flex w-full flex-col gap-1 overflow-hidden rounded-xl border border-storm-gray-50 bg-white p-2 shadow-[0_8px_24px_rgba(21,26,30,0.08)]',\n menuClassName,\n )}\n >\n {options.map((option) => {\n const selectableIndex = selectableOptions.findIndex(\n (selectable) => selectable.value === option.value,\n );\n const isSelected = option.value === value;\n const isHighlighted = selectableIndex === highlightedIndex;\n\n return (\n <li key={option.value} role=\"presentation\">\n <button\n type=\"button\"\n role=\"option\"\n aria-selected={isSelected}\n disabled={option.disabled}\n onMouseEnter={() => {\n if (!option.disabled && selectableIndex >= 0) {\n setHighlightedIndex(selectableIndex);\n }\n }}\n onClick={() => selectOption(option)}\n className={cn(\n 'flex w-full rounded-lg px-3 py-2.5 text-left text-sm font-medium text-slate-blue transition-colors',\n !option.disabled && 'cursor-pointer hover:bg-storm-gray-50',\n (isSelected || isHighlighted) && !option.disabled && 'bg-storm-gray-50',\n option.disabled && 'cursor-not-allowed opacity-50',\n )}\n >\n <span className=\"truncate\">{option.label}</span>\n </button>\n </li>\n );\n })}\n </ul>\n ) : null}\n </div>\n );\n}\n","import { Search } from 'lucide-react';\nimport type { ReactNode } from 'react';\nimport type { StyleProp, ViewStyle } from 'react-native';\n\nimport { Input } from '../../components/Input';\nimport { BellIcon } from '../icons/BellIcon';\nimport { cn } from '../utils/cn';\nimport { BuildingSelect } from './BuildingSelect';\nimport type { AdminBuildingOption, AdminHeaderLabels } from './types';\n\nexport type AppHeaderProps = {\n title: string;\n labels: AdminHeaderLabels;\n buildingOptions: AdminBuildingOption[];\n selectedBuildingId: string;\n className?: string;\n buildingSelectClassName?: string;\n searchClassName?: string;\n notificationsButtonClassName?: string;\n searchValue?: string;\n searchFieldStyle?: StyleProp<ViewStyle>;\n buildingIcon?: ReactNode;\n notificationsIcon?: ReactNode;\n searchIcon?: ReactNode;\n buildingChevronIcon?: ReactNode;\n buildingSelectDisabled?: boolean;\n searchDisabled?: boolean;\n notificationsDisabled?: boolean;\n onBuildingChange?: (buildingId: string) => void;\n onNotificationsClick?: () => void;\n onSearchChange?: (value: string) => void;\n};\n\nconst defaultSearchFieldStyle: ViewStyle = {\n height: 44,\n minHeight: 44,\n maxHeight: 44,\n backgroundColor: 'transparent',\n padding: 12,\n width: 260,\n borderRadius: 12,\n};\n\nexport function AppHeader({\n title,\n labels,\n buildingOptions,\n selectedBuildingId,\n className,\n buildingSelectClassName,\n searchClassName,\n notificationsButtonClassName,\n searchValue,\n searchFieldStyle,\n buildingIcon,\n notificationsIcon,\n searchIcon,\n buildingChevronIcon,\n buildingSelectDisabled = false,\n searchDisabled = false,\n notificationsDisabled = false,\n onBuildingChange,\n onNotificationsClick,\n onSearchChange,\n}: AppHeaderProps) {\n return (\n <header\n className={cn(\n 'flex shrink-0 items-center justify-between gap-4 border-b border-storm-gray-50 bg-storm-gray-0 px-6 py-3',\n className,\n )}\n >\n <h1 className=\"truncate text-xl font-bold text-slate-blue\">{title}</h1>\n\n <div className=\"flex shrink-0 items-center gap-3\">\n <BuildingSelect\n options={buildingOptions}\n value={selectedBuildingId}\n labels={labels}\n onChange={onBuildingChange}\n disabled={buildingSelectDisabled}\n className={buildingSelectClassName}\n buildingIcon={buildingIcon}\n chevronIcon={buildingChevronIcon}\n />\n\n <Input\n leftIcon={searchIcon ?? <Search strokeWidth={1.75} />}\n placeholder={labels.searchPlaceholder}\n value={searchValue}\n onChangeText={(value) => onSearchChange?.(value)}\n editable={!searchDisabled}\n className={cn('w-65!', searchClassName)}\n containerStyle={{ width: 260, maxWidth: 260, flexShrink: 0 }}\n fieldStyle={searchFieldStyle ?? defaultSearchFieldStyle}\n accessibilityLabel={labels.searchPlaceholder}\n />\n\n <button\n type=\"button\"\n onClick={onNotificationsClick}\n disabled={notificationsDisabled}\n className={cn(\n 'h-11 cursor-pointer rounded-xl border border-storm-gray-50 p-3 text-slate-blue transition-colors hover:bg-storm-gray-50/60',\n notificationsDisabled && 'pointer-events-none opacity-60',\n notificationsButtonClassName,\n )}\n aria-label={labels.notifications}\n >\n {notificationsIcon ?? <BellIcon className=\"text-navy\" />}\n </button>\n </div>\n </header>\n );\n}\n","import { useEffect, useState } from 'react';\n\nexport function useMediaQuery(query: string): boolean {\n const [matches, setMatches] = useState(() => {\n if (typeof window === 'undefined') {\n return false;\n }\n\n return window.matchMedia(query).matches;\n });\n\n useEffect(() => {\n const mediaQuery = window.matchMedia(query);\n const handleChange = () => setMatches(mediaQuery.matches);\n\n handleChange();\n mediaQuery.addEventListener('change', handleChange);\n\n return () => mediaQuery.removeEventListener('change', handleChange);\n }, [query]);\n\n return matches;\n}\n","import { NavLink } from 'react-router-dom';\n\nimport { cn } from '../utils/cn';\nimport type { AdminNavItem } from './types';\n\nexport type SidebarNavItemProps = {\n item: AdminNavItem;\n isOpen: boolean;\n onNavigate?: (item: AdminNavItem) => void;\n};\n\nexport function SidebarNavItem({ item, isOpen, onNavigate }: SidebarNavItemProps) {\n if (item.hidden) {\n return null;\n }\n\n const handleClick = () => {\n item.onClick?.();\n onNavigate?.(item);\n };\n\n return (\n <NavLink\n to={item.to}\n end={item.end}\n onClick={handleClick}\n aria-disabled={item.disabled}\n tabIndex={item.disabled ? -1 : undefined}\n className={({ isActive }) =>\n cn(\n 'relative flex items-center rounded-xl px-4 py-3 transition-[background-color,color,gap,padding] duration-300 ease-in-out',\n isOpen ? 'w-full gap-4' : 'justify-center gap-0 px-3',\n item.disabled && 'pointer-events-none opacity-50',\n isActive ? 'bg-black/12 text-white' : 'text-navy-100 hover:bg-white/5',\n )\n }\n >\n {({ isActive }) => (\n <>\n <span\n aria-hidden\n className={cn(\n 'absolute top-1.5 -left-1 h-8 w-2 rounded-full bg-white transition-[opacity,transform] duration-300 ease-in-out',\n isActive ? 'scale-100 opacity-100' : 'scale-75 opacity-0',\n )}\n />\n <span className=\"flex size-5 shrink-0 items-center justify-center\">{item.icon}</span>\n <span\n className={cn(\n 'overflow-hidden text-sm font-semibold whitespace-nowrap transition-[max-width,opacity] duration-300 ease-in-out',\n isOpen ? 'max-w-[180px] opacity-100' : 'max-w-0 opacity-0',\n )}\n >\n {item.label}\n </span>\n </>\n )}\n </NavLink>\n );\n}\n","import { LogOut } from 'lucide-react';\n\nimport { cn } from '../utils/cn';\nimport type { AdminSidebarUser } from './types';\n\nexport type SidebarUserCardProps = {\n user: AdminSidebarUser;\n isOpen: boolean;\n logoutLabel: string;\n onLogout?: () => void;\n className?: string;\n};\n\nfunction getInitials(fullName: string, email: string): string {\n const parts = fullName.trim().split(/\\s+/).filter(Boolean);\n if (parts.length > 0) {\n return parts\n .slice(0, 2)\n .map((part) => part[0]?.toUpperCase() ?? '')\n .join('');\n }\n\n return email.slice(0, 2).toUpperCase();\n}\n\nexport function SidebarUserCard({\n user,\n isOpen,\n logoutLabel,\n onLogout,\n className,\n}: SidebarUserCardProps) {\n const initials = user.initials ?? getInitials(user.fullName, user.email);\n\n return (\n <div\n className={cn(\n 'flex w-full items-center rounded-xl p-3 transition-[background-color,justify-content,gap] duration-300 ease-in-out',\n isOpen ? 'justify-between gap-3 bg-black/12' : 'justify-center',\n className,\n )}\n >\n <div\n className={cn(\n 'flex min-w-0 items-center transition-[gap,flex] duration-300 ease-in-out',\n isOpen ? 'flex-1 gap-3' : 'gap-0',\n )}\n >\n <div\n className=\"flex size-11 shrink-0 items-center justify-center rounded-full bg-white/5 text-sm font-bold text-white\"\n aria-hidden\n >\n {initials}\n </div>\n <div\n className={cn(\n 'min-w-0 overflow-hidden transition-[max-width,opacity] duration-300 ease-in-out',\n isOpen ? 'max-w-[160px] opacity-100' : 'max-w-0 opacity-0',\n )}\n >\n <p className=\"truncate text-sm font-bold text-white\">{user.fullName}</p>\n <p className=\"truncate text-xs text-storm-gray-100\">{user.email}</p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => onLogout?.()}\n className={cn(\n 'shrink-0 cursor-pointer text-navy-150 transition-[opacity,transform,max-width] duration-300 ease-in-out hover:opacity-80 active:scale-95',\n isOpen ? 'max-w-8 scale-100 opacity-100' : 'pointer-events-none max-w-0 scale-90 opacity-0',\n )}\n aria-label={logoutLabel}\n tabIndex={isOpen ? 0 : -1}\n >\n <LogOut size={20} strokeWidth={1.75} />\n </button>\n </div>\n );\n}\n","import { PanelLeftClose, PanelLeftOpen, X } from 'lucide-react';\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';\nimport { useLocation } from 'react-router-dom';\n\nimport { useMediaQuery } from '../hooks/useMediaQuery';\nimport { cn } from '../utils/cn';\nimport { SidebarNavItem } from './SidebarNavItem';\nimport { SidebarUserCard } from './SidebarUserCard';\nimport type { AdminNavItem, AdminSidebarBranding, AdminSidebarLabels, AdminSidebarUser } from './types';\n\nexport type SidebarProps = {\n navItems: AdminNavItem[];\n footerNavItems: AdminNavItem[];\n branding: AdminSidebarBranding;\n labels: AdminSidebarLabels;\n sidebarStorageKey: string;\n user?: AdminSidebarUser;\n onLogout?: () => void;\n className?: string;\n mobileOpen: boolean;\n onMobileClose?: () => void;\n onNavItemClick?: (item: AdminNavItem) => void;\n collapseIcon?: ReactNode;\n expandIcon?: ReactNode;\n closeIcon?: ReactNode;\n initialCollapsed?: boolean;\n collapsed?: boolean;\n onCollapsedChange?: (collapsed: boolean) => void;\n};\n\nfunction readInitialCollapsedState(storageKey: string, initialCollapsed?: boolean): boolean {\n if (initialCollapsed !== undefined) {\n return initialCollapsed;\n }\n\n if (typeof window === 'undefined') {\n return false;\n }\n\n const stored = window.localStorage.getItem(storageKey);\n if (stored === null) {\n return false;\n }\n\n return stored !== 'true';\n}\n\nexport function Sidebar({\n navItems,\n footerNavItems,\n branding,\n labels,\n sidebarStorageKey,\n user,\n onLogout,\n className,\n mobileOpen,\n onMobileClose,\n onNavItemClick,\n collapseIcon,\n expandIcon,\n closeIcon,\n initialCollapsed,\n collapsed,\n onCollapsedChange,\n}: SidebarProps) {\n const location = useLocation();\n const isDesktop = useMediaQuery('(min-width: 768px)');\n const [internalCollapsed, setInternalCollapsed] = useState(() =>\n readInitialCollapsedState(sidebarStorageKey, initialCollapsed),\n );\n\n const isCollapsed = collapsed ?? internalCollapsed;\n const isExpanded = isDesktop ? !isCollapsed : true;\n\n const setCollapsed = useCallback(\n (next: boolean) => {\n if (collapsed === undefined) {\n setInternalCollapsed(next);\n window.localStorage.setItem(sidebarStorageKey, String(!next));\n }\n\n onCollapsedChange?.(next);\n },\n [collapsed, onCollapsedChange, sidebarStorageKey],\n );\n\n const toggleCollapsed = useCallback(() => {\n setCollapsed(!isCollapsed);\n }, [isCollapsed, setCollapsed]);\n\n const handleHeaderAction = useCallback(() => {\n if (isDesktop) {\n toggleCollapsed();\n\n return;\n }\n\n onMobileClose?.();\n }, [isDesktop, onMobileClose, toggleCollapsed]);\n\n const handleNavItemNavigate = useCallback(\n (item: AdminNavItem) => {\n onNavItemClick?.(item);\n\n if (!isDesktop) {\n onMobileClose?.();\n }\n },\n [isDesktop, onMobileClose, onNavItemClick],\n );\n\n const pathnameRef = useRef(location.pathname);\n\n useEffect(() => {\n if (pathnameRef.current === location.pathname) {\n return;\n }\n\n pathnameRef.current = location.pathname;\n\n if (!isDesktop) {\n onMobileClose?.();\n }\n }, [location.pathname, isDesktop, onMobileClose]);\n\n useEffect(() => {\n if (!mobileOpen || isDesktop) {\n return;\n }\n\n const previousOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n\n return () => {\n document.body.style.overflow = previousOverflow;\n };\n }, [mobileOpen, isDesktop]);\n\n const visibleNavItems = navItems.filter((item) => !item.hidden);\n const visibleFooterNavItems = footerNavItems.filter((item) => !item.hidden);\n\n return (\n <>\n <button\n type=\"button\"\n aria-label={labels.closeMenu}\n onClick={onMobileClose}\n className={cn(\n 'fixed inset-0 z-40 bg-navy-800/60 backdrop-blur-[1px] transition-opacity duration-300 ease-in-out md:hidden',\n mobileOpen ? 'opacity-100' : 'pointer-events-none opacity-0',\n )}\n />\n\n <aside\n className={cn(\n 'fixed inset-y-0 left-0 z-50 flex h-screen w-[min(300px,85vw)] flex-col gap-8 overflow-hidden bg-navy p-4 shadow-xl',\n 'transition-[transform,width,padding,gap] duration-300 ease-in-out will-change-[transform,width]',\n mobileOpen ? 'translate-x-0' : '-translate-x-full',\n 'md:relative md:z-auto md:w-[300px] md:translate-x-0 md:shadow-none',\n isExpanded ? 'md:w-[300px]' : 'md:w-[52px] md:items-center md:gap-4 md:px-3 md:py-4',\n className,\n )}\n aria-hidden={!isDesktop && !mobileOpen}\n >\n <div\n className={cn(\n 'flex w-full items-center transition-[justify-content] duration-300 ease-in-out',\n isExpanded ? 'justify-between' : 'justify-center',\n )}\n >\n <div\n className={cn(\n 'flex min-w-0 items-center gap-1.5 overflow-hidden transition-[max-width,opacity] duration-300 ease-in-out',\n isExpanded ? 'max-w-[240px] opacity-100' : 'max-w-0 opacity-0',\n )}\n >\n <img src={branding.logoIconSrc} alt=\"\" className=\"h-5 w-[29px] shrink-0\" />\n <div className=\"flex min-w-0 flex-col leading-none\">\n <span className=\"text-[21px] font-bold tracking-tight whitespace-nowrap text-white\">\n {branding.logoTitle}\n </span>\n <span className=\"text-[6.5px] font-normal tracking-[-0.26px] text-navy-300 uppercase\">\n {branding.logoTagline}\n </span>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={handleHeaderAction}\n className=\"shrink-0 cursor-pointer text-white transition-[opacity,transform] duration-200 hover:opacity-80 active:scale-95\"\n aria-label={\n isDesktop\n ? isExpanded\n ? labels.collapse\n : labels.expand\n : labels.closeMenu\n }\n >\n {isDesktop ? (\n isExpanded ? (\n collapseIcon ?? <PanelLeftClose size={20} strokeWidth={1.75} />\n ) : (\n expandIcon ?? <PanelLeftOpen size={20} strokeWidth={1.75} />\n )\n ) : (\n closeIcon ?? <X size={20} strokeWidth={1.75} />\n )}\n </button>\n </div>\n\n <nav className=\"flex flex-1 flex-col gap-2 overflow-y-auto overflow-x-hidden\">\n {visibleNavItems.map((item) => (\n <SidebarNavItem\n key={item.to}\n item={item}\n isOpen={isExpanded}\n onNavigate={handleNavItemNavigate}\n />\n ))}\n </nav>\n\n <div className=\"flex w-full flex-col gap-4\">\n {visibleFooterNavItems.map((item) => (\n <SidebarNavItem\n key={item.to}\n item={item}\n isOpen={isExpanded}\n onNavigate={handleNavItemNavigate}\n />\n ))}\n {user ? (\n <SidebarUserCard\n user={user}\n isOpen={isExpanded}\n logoutLabel={labels.logout}\n onLogout={onLogout}\n />\n ) : null}\n </div>\n </aside>\n </>\n );\n}\n","import { Menu } from 'lucide-react';\n\nimport { cn } from '../utils/cn';\nimport type { AdminSidebarBranding } from './types';\n\nexport type SidebarMobileHeaderProps = {\n branding: Pick<AdminSidebarBranding, 'logoIconSrc' | 'logoTitle' | 'logoTagline'>;\n openMenuLabel: string;\n onOpenMenu: () => void;\n menuOpen?: boolean;\n className?: string;\n};\n\nexport function SidebarMobileHeader({\n branding,\n openMenuLabel,\n onOpenMenu,\n menuOpen = false,\n className,\n}: SidebarMobileHeaderProps) {\n return (\n <header\n className={cn(\n 'sticky top-0 z-30 flex h-14 shrink-0 items-center gap-3 border-b border-storm-gray-50 bg-storm-gray-0 px-4 md:hidden',\n className,\n )}\n >\n <button\n type=\"button\"\n onClick={onOpenMenu}\n className=\"flex size-10 items-center justify-center rounded-xl text-navy transition-colors hover:bg-storm-gray-50\"\n aria-label={openMenuLabel}\n aria-expanded={menuOpen}\n >\n <Menu size={22} strokeWidth={1.75} />\n </button>\n <div className=\"flex min-w-0 items-center gap-2\">\n <img src={branding.logoIconSrc} alt=\"\" className=\"h-[30px] w-[42px] shrink-0\" />\n <div className=\"min-w-0 leading-none\">\n <p className=\"truncate text-base font-bold tracking-tight text-navy\">{branding.logoTitle}</p>\n <p className=\"truncate text-[6.5px] font-normal tracking-[-0.26px] text-navy-300 uppercase\">\n {branding.logoTagline}\n </p>\n </div>\n </div>\n </header>\n );\n}\n","import type { ReactNode } from 'react';\n\nimport { AppHeader } from './AppHeader';\nimport type { AppHeaderProps } from './AppHeader';\nimport { Sidebar } from './Sidebar';\nimport type { SidebarProps } from './Sidebar';\nimport { SidebarMobileHeader } from './SidebarMobileHeader';\nimport type { SidebarMobileHeaderProps } from './SidebarMobileHeader';\n\nexport type DashboardLayoutProps = {\n sidebar: SidebarProps;\n header: AppHeaderProps;\n mobileHeader: SidebarMobileHeaderProps;\n children: ReactNode;\n className?: string;\n mainClassName?: string;\n};\n\nexport function DashboardLayout({\n sidebar,\n header,\n mobileHeader,\n children,\n className,\n mainClassName,\n}: DashboardLayoutProps) {\n return (\n <div className={className ?? 'flex min-h-screen bg-storm-gray-0'}>\n <Sidebar {...sidebar} />\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <SidebarMobileHeader {...mobileHeader} />\n <AppHeader {...header} />\n <main className={mainClassName ?? 'flex-1 overflow-auto p-4 md:p-6'}>{children}</main>\n </div>\n </div>\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scripso-homepad/ui",
3
- "version": "0.3.8",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Cross-platform UI components for Homepad (React Web + React Native)",
6
6
  "license": "MIT",
@@ -23,6 +23,12 @@
23
23
  "import": "./src/index.ts",
24
24
  "require": "./dist/index.cjs",
25
25
  "default": "./src/index.ts"
26
+ },
27
+ "./web": {
28
+ "types": "./dist/web/index.d.ts",
29
+ "import": "./src/web/index.ts",
30
+ "require": "./dist/web/index.cjs",
31
+ "default": "./src/web/index.ts"
26
32
  }
27
33
  },
28
34
  "files": [
@@ -35,6 +41,7 @@
35
41
  "lint": "eslint .",
36
42
  "clean": "rm -rf dist",
37
43
  "storybook": "storybook dev -p 6006",
44
+ "storybook:clean": "rm -rf node_modules/.cache/storybook node_modules/.vite storybook-static && storybook dev -p 6006",
38
45
  "build-storybook": "storybook build",
39
46
  "prepublishOnly": "npm run build"
40
47
  },
@@ -42,7 +49,9 @@
42
49
  "react": ">=18",
43
50
  "react-native": ">=0.74",
44
51
  "react-native-svg": ">=13",
45
- "react-native-web": ">=0.19"
52
+ "react-native-web": ">=0.19",
53
+ "react-router-dom": ">=6",
54
+ "lucide-react": ">=0.400"
46
55
  },
47
56
  "peerDependenciesMeta": {
48
57
  "react-native-svg": {
@@ -50,23 +59,38 @@
50
59
  },
51
60
  "react-native-web": {
52
61
  "optional": true
62
+ },
63
+ "react-router-dom": {
64
+ "optional": true
65
+ },
66
+ "lucide-react": {
67
+ "optional": true
53
68
  }
54
69
  },
70
+ "dependencies": {
71
+ "clsx": "^2.1.1",
72
+ "tailwind-merge": "^3.4.0"
73
+ },
55
74
  "devDependencies": {
56
75
  "@eslint/js": "^9.17.0",
57
76
  "@storybook/addon-essentials": "^8.4.7",
58
77
  "@storybook/addon-interactions": "^8.4.7",
59
78
  "@storybook/react-vite": "^8.4.7",
79
+ "@tailwindcss/postcss": "^4.1.13",
80
+ "@tailwindcss/vite": "^4.1.13",
60
81
  "@types/react": "^18.3.18",
61
82
  "eslint": "^9.17.0",
62
83
  "eslint-plugin-react-hooks": "^5.1.0",
63
84
  "globals": "^15.14.0",
85
+ "lucide-react": "^0.563.0",
64
86
  "react": "^18.3.1",
65
87
  "react-dom": "^18.3.1",
88
+ "react-router-dom": "^7.9.1",
66
89
  "react-native": "^0.76.5",
67
90
  "react-native-svg": "^15.11.2",
68
91
  "react-native-web": "^0.19.13",
69
92
  "storybook": "^8.4.7",
93
+ "tailwindcss": "^4.1.13",
70
94
  "tsup": "^8.3.5",
71
95
  "typescript": "^5.7.2",
72
96
  "typescript-eslint": "^8.18.2",
@@ -61,6 +61,10 @@ export interface InputProps extends TextInputProps {
61
61
  containerStyle?: StyleProp<ViewStyle>;
62
62
  style?: StyleProp<TextStyle>;
63
63
  className?: string;
64
+ /** CSS classes for the bordered field container (web). */
65
+ fieldClassName?: string;
66
+ /** Styles merged into the bordered field container. */
67
+ fieldStyle?: StyleProp<ViewStyle>;
64
68
  labelClassName?: string;
65
69
  inputClassName?: string;
66
70
  errorClassName?: string;
@@ -78,6 +82,8 @@ export function Input({
78
82
  containerStyle,
79
83
  style,
80
84
  className,
85
+ fieldClassName,
86
+ fieldStyle,
81
87
  labelClassName,
82
88
  inputClassName,
83
89
  errorClassName,
@@ -90,12 +96,14 @@ export function Input({
90
96
  ...props
91
97
  }: InputProps) {
92
98
  const wrapperRef = useRef<ComponentRef<typeof View>>(null);
99
+ const fieldRef = useRef<ComponentRef<typeof View>>(null);
93
100
  const inputRef = useRef<ComponentRef<typeof TextInput>>(null);
94
101
  const helperRef = useRef<ComponentRef<typeof Text>>(null);
95
102
  const [focused, setFocused] = useState(false);
96
103
  const [passwordVisible, setPasswordVisible] = useState(false);
97
104
 
98
105
  useApplyWebClassName(wrapperRef, className);
106
+ useApplyWebClassName(fieldRef, fieldClassName);
99
107
  useApplyWebClassName(inputRef, inputClassName);
100
108
  useApplyWebClassName(helperRef, error ? errorClassName : hintClassName);
101
109
 
@@ -157,7 +165,10 @@ export function Input({
157
165
  </Label>
158
166
  ) : null}
159
167
  <View style={fieldStyles.outline}>
160
- <View style={[inputFieldMetrics.container, fieldStyles.container]}>
168
+ <View
169
+ ref={fieldRef}
170
+ style={[inputFieldMetrics.container, fieldStyles.container, fieldStyle]}
171
+ >
161
172
  {leftIcon ? (
162
173
  <View style={styles.iconSlot}>{renderInputIcon(leftIcon, iconColor)}</View>
163
174
  ) : null}
@@ -0,0 +1,23 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ export function useMediaQuery(query: string): boolean {
4
+ const [matches, setMatches] = useState(() => {
5
+ if (typeof window === 'undefined') {
6
+ return false;
7
+ }
8
+
9
+ return window.matchMedia(query).matches;
10
+ });
11
+
12
+ useEffect(() => {
13
+ const mediaQuery = window.matchMedia(query);
14
+ const handleChange = () => setMatches(mediaQuery.matches);
15
+
16
+ handleChange();
17
+ mediaQuery.addEventListener('change', handleChange);
18
+
19
+ return () => mediaQuery.removeEventListener('change', handleChange);
20
+ }, [query]);
21
+
22
+ return matches;
23
+ }
@@ -0,0 +1,30 @@
1
+ import { useEffect, type RefObject } from 'react';
2
+
3
+ export function useOnClickOutside<T extends HTMLElement>(
4
+ ref: RefObject<T | null>,
5
+ handler: () => void,
6
+ enabled = true,
7
+ ) {
8
+ useEffect(() => {
9
+ if (!enabled) {
10
+ return;
11
+ }
12
+
13
+ const onPointerDown = (event: MouseEvent | TouchEvent) => {
14
+ const target = event.target as Node | null;
15
+ if (!ref.current || !target || ref.current.contains(target)) {
16
+ return;
17
+ }
18
+
19
+ handler();
20
+ };
21
+
22
+ document.addEventListener('mousedown', onPointerDown);
23
+ document.addEventListener('touchstart', onPointerDown);
24
+
25
+ return () => {
26
+ document.removeEventListener('mousedown', onPointerDown);
27
+ document.removeEventListener('touchstart', onPointerDown);
28
+ };
29
+ }, [enabled, handler, ref]);
30
+ }
@@ -0,0 +1,27 @@
1
+ import { cn } from '../utils/cn';
2
+
3
+ type BellIconProps = {
4
+ className?: string;
5
+ };
6
+
7
+ export function BellIcon({ className }: BellIconProps) {
8
+ return (
9
+ <svg
10
+ width="20"
11
+ height="20"
12
+ viewBox="0 0 20 20"
13
+ fill="none"
14
+ xmlns="http://www.w3.org/2000/svg"
15
+ aria-hidden
16
+ className={cn('shrink-0', className)}
17
+ >
18
+ <path
19
+ d="M8.58333 17.4998C8.72282 17.7535 8.92787 17.9651 9.17708 18.1125C9.42628 18.2599 9.71048 18.3376 10 18.3376C10.2895 18.3376 10.5737 18.2599 10.8229 18.1125C11.0721 17.9651 11.2772 17.7535 11.4167 17.4998M5 6.6665C5 5.34042 5.52678 4.06865 6.46447 3.13097C7.40215 2.19329 8.67392 1.6665 10 1.6665C11.3261 1.6665 12.5979 2.19329 13.5355 3.13097C14.4732 4.06865 15 5.34042 15 6.6665C15 12.4998 17.5 14.1665 17.5 14.1665H2.5C2.5 14.1665 5 12.4998 5 6.6665Z"
20
+ stroke="currentColor"
21
+ strokeWidth="2"
22
+ strokeLinecap="round"
23
+ strokeLinejoin="round"
24
+ />
25
+ </svg>
26
+ );
27
+ }
@@ -0,0 +1,55 @@
1
+ import { cn } from '../utils/cn';
2
+
3
+ type BuildingIconProps = {
4
+ className?: string;
5
+ };
6
+
7
+ export function BuildingIcon({ className }: BuildingIconProps) {
8
+ return (
9
+ <svg
10
+ width="20"
11
+ height="20"
12
+ viewBox="0 0 24 24"
13
+ fill="none"
14
+ xmlns="http://www.w3.org/2000/svg"
15
+ aria-hidden
16
+ className={cn('shrink-0', className)}
17
+ >
18
+ <path
19
+ d="M10 12h4"
20
+ stroke="currentColor"
21
+ strokeWidth="2"
22
+ strokeLinecap="round"
23
+ strokeLinejoin="round"
24
+ />
25
+ <path
26
+ d="M10 8h4"
27
+ stroke="currentColor"
28
+ strokeWidth="2"
29
+ strokeLinecap="round"
30
+ strokeLinejoin="round"
31
+ />
32
+ <path
33
+ d="M14 21v-3a2 2 0 0 0-4 0v3"
34
+ stroke="currentColor"
35
+ strokeWidth="2"
36
+ strokeLinecap="round"
37
+ strokeLinejoin="round"
38
+ />
39
+ <path
40
+ d="M6 10H4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-2"
41
+ stroke="currentColor"
42
+ strokeWidth="2"
43
+ strokeLinecap="round"
44
+ strokeLinejoin="round"
45
+ />
46
+ <path
47
+ d="M6 21V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v16"
48
+ stroke="currentColor"
49
+ strokeWidth="2"
50
+ strokeLinecap="round"
51
+ strokeLinejoin="round"
52
+ />
53
+ </svg>
54
+ );
55
+ }
@@ -0,0 +1,37 @@
1
+ export { AppHeader } from './layout/AppHeader';
2
+ export type { AppHeaderProps } from './layout/AppHeader';
3
+
4
+ export { BuildingSelect } from './layout/BuildingSelect';
5
+ export type { BuildingSelectProps } from './layout/BuildingSelect';
6
+
7
+ export { DashboardLayout } from './layout/DashboardLayout';
8
+ export type { DashboardLayoutProps } from './layout/DashboardLayout';
9
+
10
+ export { Sidebar } from './layout/Sidebar';
11
+ export type { SidebarProps } from './layout/Sidebar';
12
+
13
+ export { SidebarMobileHeader } from './layout/SidebarMobileHeader';
14
+ export type { SidebarMobileHeaderProps } from './layout/SidebarMobileHeader';
15
+
16
+ export { SidebarNavItem } from './layout/SidebarNavItem';
17
+ export type { SidebarNavItemProps } from './layout/SidebarNavItem';
18
+
19
+ export { SidebarUserCard } from './layout/SidebarUserCard';
20
+ export type { SidebarUserCardProps } from './layout/SidebarUserCard';
21
+
22
+ export { BellIcon } from './icons/BellIcon';
23
+ export { BuildingIcon } from './icons/BuildingIcon';
24
+
25
+ export { useMediaQuery } from './hooks/useMediaQuery';
26
+ export { useOnClickOutside } from './hooks/useOnClickOutside';
27
+ export { cn } from './utils/cn';
28
+
29
+ export type {
30
+ AdminBuildingOption,
31
+ AdminHeaderLabels,
32
+ AdminLayoutLabels,
33
+ AdminNavItem,
34
+ AdminSidebarBranding,
35
+ AdminSidebarLabels,
36
+ AdminSidebarUser,
37
+ } from './layout/types';
@@ -0,0 +1,85 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { fn } from '@storybook/test';
3
+ import { useState } from 'react';
4
+
5
+ import { WebLayoutCanvas } from './story-helpers';
6
+ import { AppHeader, type AppHeaderProps } from './AppHeader';
7
+ import { storyBuildingOptions, storyLabels } from './story-fixtures';
8
+
9
+ const meta = {
10
+ title: 'Web Layout/AppHeader',
11
+ component: AppHeader,
12
+ parameters: {
13
+ layout: 'fullscreen',
14
+ webLayout: true,
15
+ },
16
+ args: {
17
+ title: 'Dashboard',
18
+ labels: storyLabels.header,
19
+ buildingOptions: storyBuildingOptions,
20
+ selectedBuildingId: storyBuildingOptions[0].value,
21
+ onBuildingChange: fn(),
22
+ onNotificationsClick: fn(),
23
+ onSearchChange: fn(),
24
+ },
25
+ } satisfies Meta<typeof AppHeader>;
26
+
27
+ export default meta;
28
+ type Story = StoryObj<typeof meta>;
29
+
30
+ export const Default: Story = {
31
+ render: (args) => (
32
+ <WebLayoutCanvas>
33
+ <AppHeader {...args} />
34
+ </WebLayoutCanvas>
35
+ ),
36
+ };
37
+
38
+ function WithSearchStory(args: AppHeaderProps) {
39
+ const [searchQuery, setSearchQuery] = useState('');
40
+
41
+ return (
42
+ <WebLayoutCanvas>
43
+ <AppHeader
44
+ {...args}
45
+ searchValue={searchQuery}
46
+ onSearchChange={setSearchQuery}
47
+ />
48
+ </WebLayoutCanvas>
49
+ );
50
+ }
51
+
52
+ export const WithSearch: Story = {
53
+ render: (args) => <WithSearchStory {...args} />,
54
+ };
55
+
56
+ function InteractiveBuildingSelectStory(args: AppHeaderProps) {
57
+ const [selectedBuildingId, setSelectedBuildingId] = useState(args.selectedBuildingId);
58
+
59
+ return (
60
+ <WebLayoutCanvas>
61
+ <AppHeader
62
+ {...args}
63
+ selectedBuildingId={selectedBuildingId}
64
+ onBuildingChange={setSelectedBuildingId}
65
+ />
66
+ </WebLayoutCanvas>
67
+ );
68
+ }
69
+
70
+ export const InteractiveBuildingSelect: Story = {
71
+ render: (args) => <InteractiveBuildingSelectStory {...args} />,
72
+ };
73
+
74
+ export const SingleBuildingDisabled: Story = {
75
+ args: {
76
+ buildingOptions: [storyBuildingOptions[0]],
77
+ selectedBuildingId: storyBuildingOptions[0].value,
78
+ buildingSelectDisabled: true,
79
+ },
80
+ render: (args) => (
81
+ <WebLayoutCanvas>
82
+ <AppHeader {...args} />
83
+ </WebLayoutCanvas>
84
+ ),
85
+ };
@@ -0,0 +1,115 @@
1
+ import { Search } from 'lucide-react';
2
+ import type { ReactNode } from 'react';
3
+ import type { StyleProp, ViewStyle } from 'react-native';
4
+
5
+ import { Input } from '../../components/Input';
6
+ import { BellIcon } from '../icons/BellIcon';
7
+ import { cn } from '../utils/cn';
8
+ import { BuildingSelect } from './BuildingSelect';
9
+ import type { AdminBuildingOption, AdminHeaderLabels } from './types';
10
+
11
+ export type AppHeaderProps = {
12
+ title: string;
13
+ labels: AdminHeaderLabels;
14
+ buildingOptions: AdminBuildingOption[];
15
+ selectedBuildingId: string;
16
+ className?: string;
17
+ buildingSelectClassName?: string;
18
+ searchClassName?: string;
19
+ notificationsButtonClassName?: string;
20
+ searchValue?: string;
21
+ searchFieldStyle?: StyleProp<ViewStyle>;
22
+ buildingIcon?: ReactNode;
23
+ notificationsIcon?: ReactNode;
24
+ searchIcon?: ReactNode;
25
+ buildingChevronIcon?: ReactNode;
26
+ buildingSelectDisabled?: boolean;
27
+ searchDisabled?: boolean;
28
+ notificationsDisabled?: boolean;
29
+ onBuildingChange?: (buildingId: string) => void;
30
+ onNotificationsClick?: () => void;
31
+ onSearchChange?: (value: string) => void;
32
+ };
33
+
34
+ const defaultSearchFieldStyle: ViewStyle = {
35
+ height: 44,
36
+ minHeight: 44,
37
+ maxHeight: 44,
38
+ backgroundColor: 'transparent',
39
+ padding: 12,
40
+ width: 260,
41
+ borderRadius: 12,
42
+ };
43
+
44
+ export function AppHeader({
45
+ title,
46
+ labels,
47
+ buildingOptions,
48
+ selectedBuildingId,
49
+ className,
50
+ buildingSelectClassName,
51
+ searchClassName,
52
+ notificationsButtonClassName,
53
+ searchValue,
54
+ searchFieldStyle,
55
+ buildingIcon,
56
+ notificationsIcon,
57
+ searchIcon,
58
+ buildingChevronIcon,
59
+ buildingSelectDisabled = false,
60
+ searchDisabled = false,
61
+ notificationsDisabled = false,
62
+ onBuildingChange,
63
+ onNotificationsClick,
64
+ onSearchChange,
65
+ }: AppHeaderProps) {
66
+ return (
67
+ <header
68
+ className={cn(
69
+ 'flex shrink-0 items-center justify-between gap-4 border-b border-storm-gray-50 bg-storm-gray-0 px-6 py-3',
70
+ className,
71
+ )}
72
+ >
73
+ <h1 className="truncate text-xl font-bold text-slate-blue">{title}</h1>
74
+
75
+ <div className="flex shrink-0 items-center gap-3">
76
+ <BuildingSelect
77
+ options={buildingOptions}
78
+ value={selectedBuildingId}
79
+ labels={labels}
80
+ onChange={onBuildingChange}
81
+ disabled={buildingSelectDisabled}
82
+ className={buildingSelectClassName}
83
+ buildingIcon={buildingIcon}
84
+ chevronIcon={buildingChevronIcon}
85
+ />
86
+
87
+ <Input
88
+ leftIcon={searchIcon ?? <Search strokeWidth={1.75} />}
89
+ placeholder={labels.searchPlaceholder}
90
+ value={searchValue}
91
+ onChangeText={(value) => onSearchChange?.(value)}
92
+ editable={!searchDisabled}
93
+ className={cn('w-65!', searchClassName)}
94
+ containerStyle={{ width: 260, maxWidth: 260, flexShrink: 0 }}
95
+ fieldStyle={searchFieldStyle ?? defaultSearchFieldStyle}
96
+ accessibilityLabel={labels.searchPlaceholder}
97
+ />
98
+
99
+ <button
100
+ type="button"
101
+ onClick={onNotificationsClick}
102
+ disabled={notificationsDisabled}
103
+ className={cn(
104
+ 'h-11 cursor-pointer rounded-xl border border-storm-gray-50 p-3 text-slate-blue transition-colors hover:bg-storm-gray-50/60',
105
+ notificationsDisabled && 'pointer-events-none opacity-60',
106
+ notificationsButtonClassName,
107
+ )}
108
+ aria-label={labels.notifications}
109
+ >
110
+ {notificationsIcon ?? <BellIcon className="text-navy" />}
111
+ </button>
112
+ </div>
113
+ </header>
114
+ );
115
+ }