{"id":47247789,"url":"https://github.com/luminoid/lumikit","last_synced_at":"2026-05-26T00:01:21.402Z","repository":{"id":339235416,"uuid":"1160697343","full_name":"Luminoid/LumiKit","owner":"Luminoid","description":"A modular Swift package providing design tokens, themeable UI components, and utilities for iOS, iPadOS, and Mac Catalyst apps. Built with Swift 6.2 strict concurrency, UIKit + SnapKit.","archived":false,"fork":false,"pushed_at":"2026-05-23T08:26:24.000Z","size":3304,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-23T09:32:13.593Z","etag":null,"topics":["design-system","design-tokens","ios","ipados","mac-catalyst","snapkit","swift","swift-package","theming","uikit"],"latest_commit_sha":null,"homepage":"https://swiftpackageindex.com/Luminoid/LumiKit","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Luminoid.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-18T09:07:21.000Z","updated_at":"2026-05-23T08:26:28.000Z","dependencies_parsed_at":"2026-05-10T23:04:33.546Z","dependency_job_id":null,"html_url":"https://github.com/Luminoid/LumiKit","commit_stats":null,"previous_names":["luminoid/lumikit"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/Luminoid/LumiKit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Luminoid%2FLumiKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Luminoid%2FLumiKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Luminoid%2FLumiKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Luminoid%2FLumiKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Luminoid","download_url":"https://codeload.github.com/Luminoid/LumiKit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Luminoid%2FLumiKit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33497930,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-25T14:31:05.219Z","status":"ssl_error","status_checked_at":"2026-05-25T14:31:02.878Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["design-system","design-tokens","ios","ipados","mac-catalyst","snapkit","swift","swift-package","theming","uikit"],"created_at":"2026-03-14T09:12:59.320Z","updated_at":"2026-05-26T00:01:21.303Z","avatar_url":"https://github.com/Luminoid.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"Example/Resources/Assets.xcassets/AppIcon.appiconset/app_icon.png\" width=\"128\" alt=\"LumiKit\"\u003e\n\u003c/p\u003e\n\n# LumiKit\n\n[![Swift Versions](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FLuminoid%2FLumiKit%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/Luminoid/LumiKit)\n[![Platforms](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FLuminoid%2FLumiKit%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/Luminoid/LumiKit)\n[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)\n\nShared Swift Package providing **design tokens**, **UI components**, and **utilities** for Lumi apps. Built with Swift 6.2 strict concurrency, UIKit + SnapKit, and a fully configurable theming system.\n\n---\n\n## Table of Contents\n\n1. [Overview](#overview)\n2. [Screenshots](#screenshots)\n3. [Requirements](#requirements)\n4. [Installation](#installation)\n5. [Quick Start](#quick-start)\n6. [Package Structure](#package-structure)\n7. [Design System](#design-system)\n8. [Components](#components)\n9. [Controls](#controls)\n10. [Extensions](#extensions)\n11. [Animation \u0026 Haptics](#animation--haptics)\n12. [Core Utilities](#core-utilities)\n13. [UI Utilities](#ui-utilities)\n14. [Photo](#photo)\n15. [Share](#share)\n16. [QR Code](#qr-code)\n17. [Error Handling](#error-handling)\n18. [Debug Tools](#debug-tools-debug-builds-only)\n19. [Build \u0026 Test](#build--test)\n20. [Release](#release)\n21. [Dependencies](#dependencies)\n22. [Built with LumiKit](#built-with-lumikit)\n23. [TODO](#todo)\n24. [License](#license)\n25. [Changelog](#changelog)\n\n---\n\n## Overview\n\nLumiKit is organized into four targets so apps can import only what they need:\n\n| Target | Dependencies | Purpose |\n|--------|-------------|---------|\n| **LumiKitCore** | Foundation only | Logger, date helpers, URL validation, format helpers, file utilities, concurrency helpers, collection/string extensions |\n| **LumiKitNetwork** | LumiKitCore | Network debugging with URLProtocol interception (DEBUG only, Swift 6 concurrency compatible) |\n| **LumiKitUI** | LumiKitCore + LumiKitNetwork + SnapKit | Design system tokens, theme manager, animation, haptics, alerts, components, controls, photo browser/crop, network debug UI (DEBUG), UIKit extensions |\n| **LumiKitLottie** | LumiKitUI + Lottie | Lottie-powered pull-to-refresh control |\n\n**112 source files** across 4 targets, with **891 tests** across 4 test targets:\n- **LumiKitCoreTests**: 76 tests (12 suites)\n- **LumiKitNetworkTests**: 65 tests (4 suites)\n- **LumiKitUITests**: 743 tests (107 suites)\n- **LumiKitLottieTests**: 7 tests (1 suite)\n\n---\n\n## Screenshots\n\nFrom the Example app (`Example/LumiKitExample.xcodeproj`):\n\n### Example app sections\n\n| Design System \u0026 Controls | Components | Feedback \u0026 Overlays | Media \u0026 Extensions |\n|---|---|---|---|\n| \u003cimg src=\"docs/images/lumikit_1.png\" alt=\"Design System \u0026 Controls\" width=\"200\"\u003e | \u003cimg src=\"docs/images/lumikit_2.png\" alt=\"Components\" width=\"200\"\u003e | \u003cimg src=\"docs/images/lumikit_3.png\" alt=\"Feedback \u0026 Overlays\" width=\"200\"\u003e | \u003cimg src=\"docs/images/lumikit_4.png\" alt=\"Media \u0026 Extensions\" width=\"200\"\u003e |\n\n### Highlights\n\n| Segmented Control | Photo Grid | Dominant Color |\n|---|---|---|\n| \u003cimg src=\"docs/images/lumikit_5.png\" alt=\"Segmented Control\" width=\"200\"\u003e | \u003cimg src=\"docs/images/lumikit_6.png\" alt=\"Photo Grid\" width=\"200\"\u003e | \u003cimg src=\"docs/images/lumikit_7.png\" alt=\"Dominant Color\" width=\"200\"\u003e |\n\n---\n\n## Requirements\n\n- Swift 6.2+\n- iOS 18+ / Mac Catalyst 18+ / macOS 15+\n- Xcode 26+\n\n---\n\n## Installation\n\nAdd LumiKit to your project via Swift Package Manager:\n\n```swift\ndependencies: [\n    .package(path: \"../LumiKit\")  // Local package\n    // or\n    .package(url: \"https://github.com/Luminoid/LumiKit.git\", from: \"0.6.0\")\n]\n```\n\nThen add the targets you need:\n\n```swift\n.target(\n    name: \"MyApp\",\n    dependencies: [\n        \"LumiKitCore\",    // Foundation utilities only\n        \"LumiKitNetwork\", // Optional: Network debugging (DEBUG only)\n        \"LumiKitUI\",      // Full design system + components (includes Network)\n        \"LumiKitLottie\",  // Optional: Lottie pull-to-refresh\n    ]\n)\n```\n\n**Note**: `LumiKitUI` automatically includes `LumiKitNetwork`, so you only need to import `LumiKitNetwork` explicitly if using it without UI components.\n\n---\n\n## Quick Start\n\n```swift\nimport LumiKitUI\n\n// 1. Configure the theme at app launch\nLMKThemeManager.shared.configure(\n    colors: MyAppTheme(),\n    typography: .init(fontFamily: \"Inter\"),\n    spacing: .init(large: 20, xxl: 28),\n    cornerRadius: .init(small: 12, medium: 16)\n)\n\n// 2. Use design tokens throughout your app\nlet label = LMKLabelFactory.heading(text: \"Hello\")\nview.backgroundColor = LMKColor.backgroundPrimary\n\n// 3. Use components\nLMKToast.showSuccess(message: \"Saved!\", on: self)\n\nlet emptyState = LMKEmptyStateView()\nemptyState.configure(message: \"Nothing here yet\", icon: \"tray\", style: .card)\n```\n\n---\n\n## Example App\n\nThe `Example/` directory contains a full catalog app demonstrating every component. It uses [XcodeGen](https://github.com/yonaskolb/XcodeGen) to generate the Xcode project:\n\n```bash\ncd Example\nxcodegen generate\nopen LumiKitExample.xcodeproj\n```\n\nThe example includes **43 interactive pages** across 7 sections:\n\n- **Design System**: Colors, Typography, Markdown\n- **Controls**: Buttons, Segmented Control, Switch, Toggle Button, Text Field, Text View, Search Bar\n- **Components**: Divider, Badges, Chips, Filter Chip Bar, Cards, Gradient, Page Indicator, Navigation Bar, Banners, Empty State, Loading State, Overscroll Footer\n- **Feedback**: Toast, Alerts \u0026 Errors, Progress, Haptics\n- **Overlays**: Action Sheet, Enum Selection, Date Picker, Tip View, Card Page, Card Panel, Floating Button\n- **Media**: Photo Grid, Photo Browser, Photo Crop, QR Code, Share, Dominant Color\n- **Extensions**: Shadows, Borders \u0026 Radius, Fade Animations\n\n---\n\n## Package Structure\n\n```\nLumiKit/\n├── Package.swift\n├── Sources/\n│   ├── LumiKitCore/\n│   │   ├── Concurrency/       # LMKConcurrencyHelpers (encode/decode off main)\n│   │   ├── Data/              # LMKFormatHelper, Collection+LMK,\n│   │   │                      # NSAttributedString+LMK, String+LMK\n│   │   ├── Date/              # LMKDateHelper, LMKDateFormatterHelper\n│   │   ├── File/              # LMKFileUtil\n│   │   ├── Log/               # LMKLogger, LMKLogStore (ring buffer),\n│   │   │                      # LMKLogLevel, LMKLogEntry\n│   │   └── Validation/        # LMKURLValidator\n│   ├── LumiKitNetwork/        # [DEBUG only, isolated concurrency workarounds]\n│   │   ├── LMKNetworkLogger.swift            # URLProtocol-based interception\n│   │   ├── LMKNetworkRequestStore.swift       # Thread-safe ring buffer\n│   │   ├── LMKNetworkRequestRecord.swift      # Request/response data\n│   │   └── URLSessionConfiguration+LMKDebug.swift  # .enableNetworkLogging()\n│   ├── LumiKitUI/\n│   │   ├── Alerts/            # LMKAlertPresenter, LMKErrorHandler, LMKCountdownConfirmation\n│   │   ├── Animation/         # LMKAnimationHelper\n│   │   ├── Components/\n│   │   │   ├── BottomSheet/   # LMKBottomSheetController (base), LMKActionSheet,\n│   │   │   │                  # LMKEnumSelectionBottomSheet, LMKBottomSheetLayout\n│   │   │   ├── Pickers/       # LMKDatePickerHelper\n│   │   │   ├── LMKBadgeView, LMKBannerView, LMKCardView, LMKChipView,\n│   │   │   ├── LMKDividerView, LMKEmptyStateView, LMKFilterChipBar,\n│   │   │   ├── LMKFloatingButton,\n│   │   │   ├── LMKGradientView, LMKLoadingStateView, LMKNavigationBar,\n│   │   │   ├── LMKNavigationController, LMKPageIndicator,\n│   │   │   ├── LMKProgressViewController, LMKSearchBar,\n│   │   │   ├── LMKSkeletonCell, LMKToastView, LMKTipView,\n│   │   │   ├── LMKCardPageController, LMKCardPageLayout,\n│   │   │   ├── LMKCardPanelController, LMKCardPanelLayout,\n│   │   │   ├── LMKNavigationDirection, LMKOverscrollFooterHelper,\n│   │   │   └── LMKScrollStackViewController\n│   │   ├── Controls/          # LMKButton, LMKSegmentedControl, LMKSwitch,\n│   │   │                      # LMKToggleButton, LMKTextField, LMKTextView\n│   │   ├── DesignSystem/\n│   │   │   ├── Tokens/        # LMKColor, LMKSpacing, LMKCornerRadius, LMKAlpha,\n│   │   │   │                  # LMKLayout, LMKShadow, LMKTypography, LMKBadge\n│   │   │   ├── Themes/        # LMKSpacingTheme, LMKCornerRadiusTheme, LMKAlphaTheme,\n│   │   │   │                  # LMKLayoutTheme, LMKShadowTheme, LMKTypographyTheme,\n│   │   │   │                  # LMKBadgeTheme, LMKAnimationTheme\n│   │   │   ├── Factories/     # LMKButtonFactory, LMKCardFactory, LMKLabelFactory\n│   │   │   └── LMKTheme.swift # LMKTheme protocol + LMKThemeManager + LMKDefaultTheme\n│   │   ├── Debug/             # [DEBUG only]\n│   │   │   └── Network/       # LMKNetworkHistoryViewController, LMKNetworkDetailViewController\n│   │   ├── Extensions/        # UIKit extensions (lmk_ prefix)\n│   │   ├── Haptics/           # LMKHapticFeedbackHelper\n│   │   ├── Photo/             # LMKPhotoBrowserViewController, LMKPhotoBrowserCell,\n│   │   │                      # LMKPhotoCropViewController, LMKPhotoGridViewController,\n│   │   │                      # LMKPhotoGridCell, LMKPhotoEXIFService, LMKPhotoBrowserConfig\n│   │   ├── QRCode/            # LMKQRCodeGenerator\n│   │   ├── Share/             # LMKShareService, LMKSharePreviewViewController\n│   │   └── Utilities/         # LMKDeviceHelper, LMKKeyboardObserver, LMKSceneUtil,\n│   │                          # LMKImageUtil, LMKDominantColorExtractor, LMKMarkdownRenderer\n│   └── LumiKitLottie/         # LMKLottieRefreshControl\n├── Tests/\n│   ├── LumiKitCoreTests/      # 76 tests, 12 suites — mirrors LumiKitCore/ subfolders\n│   │   ├── Concurrency/       # LMKConcurrencyHelpers\n│   │   ├── Data/              # String+LMK, Collection+LMK, NSAttributedString+LMK, FormatHelper\n│   │   ├── Date/              # DateHelper, DateFormatterHelper\n│   │   ├── File/              # FileUtil\n│   │   ├── Log/               # LMKLogStore (ring buffer, thread safety),\n│   │   │                      # LMKLogger (log store integration)\n│   │   └── Validation/        # URLValidator\n│   ├── LumiKitNetworkTests/   # 65 tests, 4 suites\n│   │   ├── LMKNetworkRequestStoreTests.swift         # FIFO, thread safety\n│   │   ├── LMKNetworkRequestRecordTests.swift        # Computed properties, display formatting\n│   │   ├── LMKNetworkLoggerTests.swift               # Configuration, state transitions\n│   │   └── URLSessionConfigurationLMKDebugTests.swift # enableNetworkLogging\n│   ├── LumiKitUITests/        # 743 tests, 107 suites\n│   │   ├── Alerts/            # AlertPresenter, ErrorHandler\n│   │   ├── Animation/         # AnimationHelper\n│   │   ├── Components/\n│   │   │   ├── BottomSheet/   # BottomSheetController, ActionSheet, BottomSheetLayout,\n│   │   │   │                  # EnumSelectionBottomSheet\n│   │   │   ├── Pickers/       # DatePickerHelper\n│   │   │   ├── Badge, Banner, Card, Chip, Divider, EmptyState,\n│   │   │   ├── Gradient, LoadingState, SearchBar, Skeleton, Toast,\n│   │   │   ├── FloatingButton, TipView, CardPage, CardPanel,\n│   │   │   └── Progress, ScrollStackViewController\n│   │   ├── Controls/          # Button, SegmentedControl, TextField, TextView, ToggleButton\n│   │   ├── DesignSystem/\n│   │   │   ├── Tokens/        # Color, Spacing, CornerRadius, Alpha, Typography, Layout, Shadow\n│   │   │   ├── Themes/        # AnimationTheme, BadgeTheme, SendableCompliance\n│   │   │   ├── Factories/     # ButtonFactory, CardFactory, LabelFactory\n│   │   │   └── ThemeManager, ComponentToken integration\n│   │   ├── Extensions/        # UIColor, UIImage, UIStackView,\n│   │   │                      # UIView (shadow/border/fade/layout),\n│   │   │                      # UIViewController (TopViewController)\n│   │   ├── Photo/             # CropAspectRatio, PhotoEXIF\n│   │   ├── QRCode/            # QRCodeGenerator\n│   │   ├── Share/             # SharePreview, ShareService\n│   │   └── Utilities/         # DeviceHelper, ImageUtil, KeyboardObserver,\n│   │                          # KeyboardInsetHelper, MarkdownRenderer\n│   └── LumiKitLottieTests/    # 7 tests, 1 suite\n│       └── LMKLottieRefreshControlTests.swift\n```\n\n---\n\n## Design System\n\nAll design tokens are fully configurable via `LMKThemeManager`. Each category has a configuration struct with sensible defaults. Token enums proxy to the active configuration at runtime.\n\n### Token Categories\n\n| Category | Token Enum | Config Struct | Examples |\n|----------|-----------|---------------|----------|\n| Colors | `LMKColor` | `LMKTheme` (protocol) | `.primary`, `.backgroundPrimary`, `.textPrimary` |\n| Typography | `LMKTypography` | `LMKTypographyTheme` | `fontFamily`, `h1Size`, `bodySize`, line heights |\n| Spacing | `LMKSpacing` | `LMKSpacingTheme` | `.xs` (4pt), `.small` (8pt), `.medium` (12pt), `.large` (16pt) |\n| Corner Radius | `LMKCornerRadius` | `LMKCornerRadiusTheme` | `.small` (8), `.medium` (12), `.large` (16) |\n| Alpha | `LMKAlpha` | `LMKAlphaTheme` | `.overlay`, `.disabled`, `.overlayStrong` |\n| Layout | `LMKLayout` | `LMKLayoutTheme` | `.minimumTouchTarget` (44), `.iconMedium` (24) |\n| Shadow | `LMKShadow` | `LMKShadowTheme` | `small()`, `button()`, `cellCard()`, `card()`, `medium()`, `large()` |\n| Animation | `LMKAnimationHelper` | `LMKAnimationTheme` | `.Duration.*`, `.Spring.damping`, `.shouldAnimate`, `.shimmer` |\n| Badge | `LMKBadgeView` | `LMKBadgeTheme` | `minWidth`, `height`, `horizontalPadding` |\n\n### Configuration\n\n```swift\n// Configure everything at once\nLMKThemeManager.shared.configure(\n    colors: MyAppTheme(),\n    typography: .init(fontFamily: \"Inter\"),\n    spacing: .init(large: 20, xxl: 28),\n    cornerRadius: .init(small: 12, medium: 16)\n)\n\n// Or configure individual categories\nLMKThemeManager.shared.apply(MyAppTheme())\nLMKThemeManager.shared.apply(typography: .init(fontFamily: \"Inter\"))\nLMKThemeManager.shared.apply(spacing: .init(large: 20))\n```\n\n### Architecture: Token Enum → Config Struct → ThemeManager\n\n```swift\n// 1. Config struct with defaults (nonisolated, Sendable — safe from any context)\npublic nonisolated struct LMKSpacingTheme: Sendable {\n    public var large: CGFloat\n    public init(large: CGFloat = 16, ...) { ... }\n}\n\n// 2. Token enum proxies to active config (@MainActor)\npublic enum LMKSpacing {\n    private static var config: LMKSpacingTheme { LMKThemeManager.shared.spacing }\n    public static var large: CGFloat { config.large }\n}\n\n// 3. ThemeManager holds the active config\nLMKThemeManager.shared.apply(spacing: .init(large: 20))\n```\n\n### Factories\n\n| Factory | Purpose |\n|---------|---------|\n| `LMKButtonFactory` | Role-based button factory — `filled(role:)`, `outlined(role:)`, `ghost(role:)`, and `iconOnly(role:)` with `LMKButtonRole` (primary, secondary, destructive, warning, success, info) |\n| `LMKCardFactory` | Card views with shadow and corner radius |\n| `LMKLabelFactory` | Styled labels (title, subtitle, body, caption) |\n\n---\n\n## Components\n\n| Component | Purpose |\n|-----------|---------|\n| `LMKBottomSheetController` | Base class for bottom sheet presentation with shared dimming, container, velocity-aware drag-to-dismiss, and unified animation |\n| `LMKActionSheet` | Custom bottom-sheet action sheet with design-token styling, optional custom content, `isSelected` checkmark state, and sub-page navigation |\n| `LMKBadgeView` | Notification count, status dot, or custom text badge |\n| `LMKBannerView` | Persistent notification bar with optional action and dismiss |\n| `LMKCardView` | Card container with shadow, corner radius, content insets |\n| `LMKChipView` | Tag/filter chip (`.filled` / `.outlined`) with optional tap handler |\n| `LMKFilterChipBar` | Horizontal scrolling single-select chip row built on `LMKChipView`. Optional \"All\" chip clears the filter. `selectionChangedHandler` fires with the filter index or `nil` for \"All\" / no selection |\n| `LMKDividerView` | Pixel-perfect separator (horizontal / vertical) |\n| `LMKEmptyStateView` | Empty state with icon, title, message, action button |\n| `LMKEnumSelectionBottomSheet` | Bottom sheet for selecting from an enum's cases — single-select (`present`) or multi-select with explicit Done button (`presentMultiSelect`) |\n| `LMKGradientView` | `CAGradientLayer`-backed view with 4 direction options |\n| `LMKLoadingStateView` | Loading indicator with optional message |\n| `LMKNavigationBar` | Custom navigation bar with large title and standard inline modes, configurable bar items, back button, and design-token styling. `setLeftItemEnabled(at:_:)` / `setRightItemEnabled(at:_:)` toggle per-item enabled state. `setRightAccessoryView(_:)` parks a non-tappable view (sync indicator, status icon) to the left of the right items; `setLargeTitleAccessoryView(_:)` hangs an accessory off the trailing edge of the large title text (iOS Mail / Notes pattern) |\n| `LMKNavigationController` | `UINavigationController` subclass that preserves the edge-swipe-to-go-back gesture when the system nav bar is hidden. Pairs with `LMKNavigationBar`-based apps |\n| `LMKProgressViewController` | Blocking progress modal (`.determinate` with progress bar, `.indeterminate` spinner-only) |\n| `LMKSearchBar` | Search bar with configurable placeholder and cancel text |\n| `LMKSkeletonCell` | Skeleton loading placeholder cell |\n| `LMKDatePickerHelper` | Date picker presentation via `LMKActionSheet` — single date, date range, date with text field |\n| `LMKToastView` | Auto-dismissing toast notification |\n| `LMKTipView` | Onboarding tip with centered or pointed (arrow) styles — tap to dismiss. Pointed style supports `sourceOffset` for fine-tuning position |\n| `LMKFloatingButton` | Draggable floating action button with edge snapping and optional badge |\n| `LMKCardPageController` | Base class for card-embedded navigation pages with header, title, and multi-page slide navigation |\n| `LMKCardPanelController` | Centered floating card panel in its own overlay window, with shadow and slide animation |\n| `LMKCardPageLayout` | Shared layout constants for card page controllers (header height, symbol sizes) |\n| `LMKCardPanelLayout` | Shared layout constants for card panel controllers (max width, insets, height ratio) |\n| `LMKScrollStackViewController` | Base class for scrollable vertical stack layout — configurable spacing, insets, keyboard dismiss, safe area. Subclasses override `setupStackContent()` |\n| `LMKNavigationDirection` | Shared navigation direction enum (`.forward`, `.backward`, `.none`) used by CardPageController and ActionSheet |\n| `LMKPageIndicator` | Custom page indicator replacing `UIPageControl`. Active dot expands into pill shape with spring animation. `numberOfPages`, `currentPage`, `pageChangedHandler` |\n| `LMKOverscrollFooterHelper` | Positions a footer view below scroll content, revealed only on overscroll |\n\n---\n\n## Controls\n\n| Control | Purpose |\n|---------|---------|\n| `LMKButton` | Configurable button with tap handler, pill shape, and 4 styles: `.filled`, `.outlined`, `.ghost` (text-only), `.iconOnly` (circular icon). Supports `isLoading` state |\n| `LMKSegmentedControl` | Custom segmented control with sliding pill indicator, spring animation, and haptic feedback. Not a `UISegmentedControl` subclass. `fitsSegmentsToContent` (per-segment natural width) composes with `makeScrollableContainer()`; `itemPadding` / `itemSpacing` tune per-segment padding and inter-segment gap for scrollable tag/filter bars |\n| `LMKSlider` | Tokenized continuous or step-snapped slider with optional caption + live value readout row above the track. `step \u003e 0` snaps to `minimumValue + n * step` (cached snapped value bypasses `UISlider`'s float drift). `valueFormatter` drives the readout; both caption and readout auto-hide when nil. User drags fire `.valueChanged` + `valueChangedHandler`; programmatic `value` / `setValue(_:animated:)` are silent |\n| `LMKSwitch` | Custom toggle switch replacing `UISwitch`. Rounded track with sliding thumb, spring animation, haptic feedback. `isOn`, `setOn(_:animated:)`, `valueChangedHandler` |\n| `LMKTextField` | Text field with validation states, helper text, leading icon |\n| `LMKTextView` | Multi-line text input with placeholder and character limit |\n| `LMKToggleButton` | Toggle button with on/off states |\n\n---\n\n## Extensions\n\nAll UIKit extensions use the `lmk_` prefix to avoid naming conflicts.\n\n| Extension | Key Methods |\n|-----------|-------------|\n| `UIColor+LMK` | `init(lmk_hex:)`, `lmk_dynamic(lightHex:darkHex:alpha:)`, `lmk_hexString`, `lmk_isLight`, `lmk_adjustedBrightness(by:)`, `lmk_contrastingTextColor` |\n| `UIImage+LMK` | `lmk_resized(maxDimension:)`, `lmk_resized(to:)`, `lmk_solidColor(_:size:)`, `lmk_rounded(cornerRadius:)` |\n| `UIView+LMKShadow` | `lmk_applyShadow(_:)`, `lmk_removeShadow()` |\n| `UIView+LMKBorder` | `lmk_applyBorder(...)`, `lmk_removeBorder()`, `lmk_applyCornerRadius(_:)`, `lmk_makeCircular()` |\n| `UIView+LMKFade` | `lmk_fadeIn(...)`, `lmk_fadeOut(...)` |\n| `UIView+LMKLayout` | `lmk_safeAreaSnp`, `lmk_setEdgesEqualToSuperview()`, `lmk_centerInSuperview()`, `lmk_setAutoLayoutSize(width:height:)` |\n| `UIStackView+LMK` | `init(lmk_axis:...)`, `lmk_addArrangedSubviews(_:)`, `lmk_removeAllArrangedSubviews()` |\n| `UIButton+LMKAnimation` | Tap animation helpers |\n| `UIControl+LMKTouchArea` | Expanded touch area support |\n| `UITableViewCell+LMKHighlight` | Custom highlight behavior |\n| `UITextField+LMKFormStyle` | Form-styled text field configuration |\n| `UIViewController+LMKOrientation` | Orientation lock helpers |\n| `UIViewController+LMKPopover` | Popover presentation helpers |\n| `UIViewController+LMKTopViewController` | Top view controller traversal |\n\n---\n\n## Animation \u0026 Haptics\n\n| Utility | Purpose |\n|---------|---------|\n| `LMKAnimationHelper` | Centralized animation timing with Reduce Motion support (`shouldAnimate`), spring damping, and duration presets |\n| `LMKAnimationTheme` | Configurable animation token struct (durations including `shimmer` for skeleton loading, spring parameters) via `LMKThemeManager` |\n| `LMKHapticFeedbackHelper` | Haptic feedback helpers — light, medium, heavy, soft, rigid impact and success/error notification feedback |\n\n---\n\n## Core Utilities\n\nLumiKitCore provides Foundation-only utilities with zero dependencies:\n\n| Utility | Purpose |\n|---------|---------|\n| `LMKLogger` | Structured logging with categories (`.general`, `.data`, `.ui`, `.network`, `.error`) and opt-in in-memory log store (`LMKLogStore`) |\n| `LMKDateHelper` | Date calculation, comparison, and formatting helpers |\n| `LMKDateFormatterHelper` | Cached date formatters for performance |\n| `LMKFormatHelper` | Number and string formatting utilities |\n| `LMKFileUtil` | File system operations |\n| `LMKURLValidator` | URL validation and sanitization |\n| `LMKConcurrencyHelpers` | Off-main-thread Codable encode/decode |\n| `Collection+LMK` | Safe subscript, grouping, and collection utilities |\n| `String+LMK` | String manipulation and validation extensions |\n| `NSAttributedString+LMK` | Attributed string building helpers |\n\n---\n\n## UI Utilities\n\nLumiKitUI includes device-aware helpers and system observers:\n\n| Utility | Purpose |\n|---------|---------|\n| `LMKDeviceHelper` | Device type detection (`.iPhone`, `.iPad`, `.macCatalyst`), screen size classification, notch detection |\n| `LMKKeyboardObserver` | Keyboard show/hide observer with height and animation duration info |\n| `LMKImageUtil` | SF Symbol creation (`makeSymbolImage` with background), `CVPixelBuffer` to JPEG conversion |\n| `LMKDominantColorExtractor` | RGB-histogram dominant color extraction. `dominantColor(from:ignoringTransparent:strategy:)` returns one color: `.modal` (default, densest bucket = subject identity), `.average` (mean = overall vibe), `.vibrant` (most saturated = accent color). `dominantColors(from:count:ignoringTransparent:)` returns a top-N palette ordered by frequency. Pass a subject-lifted PNG with `ignoringTransparent: true` for hard-edge accuracy |\n| `LMKMarkdownRenderer` | Markdown-to-attributed-string rendering: `render()` for inline (bold/italic), `renderFull()` for long-form content (headings, lists, line breaks preserved) |\n| `LMKSceneUtil` | Key window and connected scene retrieval |\n\n---\n\n## Photo\n\n| Component | Purpose |\n|-----------|---------|\n| `LMKPhotoBrowserViewController` | Full-screen photo browser with zoom, swipe navigation, and optional Live Photo playback (`PHLivePhotoView` swaps in when the data source returns a paired `PHLivePhoto` — long-press to play, with a `livephoto` + \"LIVE\" indicator under the action button that fades during playback) |\n| `LMKPhotoCropViewController` | Photo cropping with aspect ratio support |\n| `LMKPhotoGridViewController` | Photo grid with pinch-to-zoom column control, sort by date, content mode toggle, photo browser integration, and a LIVE badge on Live Photo cells |\n| `LMKPhotoEXIFService` | Date and GPS extraction from UIImage or PHPickerResult. Date lookup walks EXIF (`DateTimeOriginal` / `DateTimeDigitized`), TIFF (`DateTime`), IPTC (`DateCreated` + `TimeCreated`, `DigitalCreationDate` + `DigitalCreationTime`), and the XMP packet (`xmp:CreateDate`, `xmp:DateCreated`, `xmp:ModifyDate`, `photoshop:DateCreated`) — surfaces a date for screenshots and edited / re-encoded photos where one container has been stripped |\n\nBoth photo view controllers force dark mode (`overrideUserInterfaceStyle = .dark`) and set `preferredStatusBarStyle = .lightContent`. They handle `modalPresentationCapturesStatusBarAppearance` automatically, so the status bar is correct when presented modally. If you embed them in a `UINavigationController`, override `childForStatusBarStyle` on the nav controller to return `topViewController`.\n\n**Live Photo support** is opt-in via two optional data source methods (both default to no-op):\n\n```swift\n// LMKPhotoGridDataSource\nfunc photoGridIsLivePhoto(at index: Int) -\u003e Bool             // drives LIVE badge\nfunc photoGridLivePhoto(at index: Int) async -\u003e PHLivePhoto? // drives browser playback\n\n// LMKPhotoBrowserDataSource\nfunc photoLivePhoto(at index: Int) async -\u003e PHLivePhoto?     // drives browser playback\n```\n\nThe browser shows the still image immediately and upgrades the cell to a playable `PHLivePhotoView` once the async load resolves. The cell guards against reuse, so stale loads on paged-away cells are dropped. Paired storage (JPG + MOV) is the caller's concern — LumiKit takes a ready-made `PHLivePhoto`.\n\n---\n\n## Share\n\n| Component | Purpose |\n|-----------|---------|\n| `LMKShareResult` | Result enum for share operations: `.completed(ActivityType?)`, `.cancelled`, `.failed(Error)` |\n| `LMKShareService` | Share sheet wrapper with `shareImage` (returns `LMKShareResult`), `shareFile`, and popover support |\n| `LMKSharePreviewViewController` | Image preview sheet with share and save-to-photos actions. All feedback is delegate-driven via `LMKSharePreviewDelegate` (`didShareWith`, `didFailToShare`, `sharePreviewDidSave`, `didFailToSave`) |\n\n---\n\n## QR Code\n\n| Component | Purpose |\n|-----------|---------|\n| `LMKQRCodeGenerator` | CoreImage QR code generation with configurable correction level and size |\n\n---\n\n## Error Handling\n\n`LMKErrorHandler` provides severity-based error presentation:\n\n| Severity | Behavior |\n|----------|----------|\n| `.info` | Info toast |\n| `.warning` | Alert with OK |\n| `.error` | Toast (transient) or alert with retry (recoverable) |\n| `.critical` | Always alert, retry if available |\n\nAll presentation methods auto-log via `LMKLogger`. Use `LMKAlertPresenter` for generic alerts and action sheets.\n\n### Countdown Confirmation\n\n`LMKCountdownConfirmation` presents a confirmation alert where the destructive button is disabled for a countdown period (default 3 seconds), showing a live countdown in the button title. Useful for irreversible actions like deleting all data.\n\n```swift\nLMKCountdownConfirmation.present(\n    on: self,\n    title: \"Delete All Data?\",\n    message: \"This cannot be undone.\",\n    confirmTitle: \"Delete\",\n    countdownSeconds: 3,\n    onConfirm: { /* perform deletion */ }\n)\n```\n\n---\n\n## Debug Tools (DEBUG builds only)\n\nNetwork debugging infrastructure for capturing and inspecting HTTP/HTTPS requests during development. **Zero footprint in release builds** — all code is wrapped in `#if DEBUG`.\n\n**Isolated in `LumiKitNetwork` target** to keep Swift 6 concurrency workarounds separate from core functionality.\n\n### Network Debugging\n\n| Component | Target | Purpose |\n|-----------|--------|---------|\n| `LMKNetworkLogger` | LumiKitNetwork | URLProtocol-based request/response interception with LMKLogger-style API |\n| `LMKNetworkRequestStore` | LumiKitNetwork | Thread-safe ring buffer for captured requests (FIFO eviction with `OSAllocatedUnfairLock`) |\n| `LMKNetworkRequestRecord` | LumiKitNetwork | Sendable struct with HTTP request/response details and formatted display properties |\n| `LMKNetworkHistoryViewController` | LumiKitUI | List view for captured requests with auto-refresh and newest-first ordering |\n| `LMKNetworkDetailViewController` | LumiKitUI | Detail view with formatted headers and bodies (50k char truncation for large payloads) |\n\n### Usage\n\n```swift\n// 1. Configure at app launch (DEBUG builds only)\n#if DEBUG\nLMKNetworkLogger.configure(maxRecords: 100)\nLMKNetworkLogger.enable()\n#endif\n\n// 2. Inject into custom URLSession configurations\n#if DEBUG\nlet config = URLSessionConfiguration.default.enableNetworkLogging()\n#endif\n\n// 3. Present network history UI from debug menu\nlet vc = LMKNetworkHistoryViewController()\nnavigationController.pushViewController(vc, animated: true)\n```\n\n### Swift 6 Concurrency Support\n\nNetwork debugging works correctly in Swift 6 strict concurrency mode, including Swift Package Manager builds. The implementation uses specific patterns to avoid deadlocks and timeouts.\n\n**Implementation details:**\n- **Isolated** in separate `LumiKitNetwork` target to keep debug tooling separate from `LumiKitCore`\n- **Enabled** automatically in DEBUG builds via `LMK_ENABLE_NETWORK_LOGGING` flag (defined in Package.swift)\n- Uses `@preconcurrency @objc` and `@unchecked Sendable` on URLProtocol subclass\n- Conforms to `URLSessionDataDelegate` (not base `URLSessionDelegate`) to ensure delegate callbacks are invoked\n- Uses serial `OperationQueue` for delegate callbacks (not `nil`) to prevent Swift 6 concurrency deadlocks\n- Internal URLSession uses `ephemeral` configuration with `protocolClasses = []` to prevent re-interception loops\n\n**Apps using network debugging:**\n- Import `LumiKitNetwork` explicitly, or just import `LumiKitUI` (which includes it)\n- No additional configuration required - network logging works in both Xcode projects and Swift Package Manager builds\n\n---\n\n## Build \u0026 Test\n\n```bash\n# Build all targets (iOS Simulator)\nxcodebuild build \\\n  -scheme LumiKit-Package \\\n  -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \\\n  -skipPackagePluginValidation \\\n  CODE_SIGNING_ALLOWED=NO\n\n# Build for Mac Catalyst\nxcodebuild build \\\n  -scheme LumiKit-Package \\\n  -destination 'platform=macOS,variant=Mac Catalyst' \\\n  -skipPackagePluginValidation \\\n  CODE_SIGNING_ALLOWED=NO\n\n# Run tests (requires iOS Simulator — UIKit targets can't use swift test)\nxcodebuild test \\\n  -scheme LumiKit-Package \\\n  -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \\\n  -skipPackagePluginValidation \\\n  CODE_SIGNING_ALLOWED=NO\n\n# Build single target (faster iteration)\nswift build --target LumiKitCore\n```\n\n---\n\n## Release\n\nTo publish a new version:\n\n```bash\n# 1. Update CHANGELOG.md with the new version and date\n# 2. Commit all changes\ngit add CHANGELOG.md README.md\ngit commit -m \"docs: prepare vX.Y.Z release\"\n\n# 3. Tag the release\ngit tag X.Y.Z\ngit push origin main --tags\n\n# 4. Create GitHub release\ngh release create X.Y.Z --title \"X.Y.Z\" --notes \"Release notes here\"\n```\n\nVersion tags follow [Semantic Versioning](https://semver.org): `MAJOR.MINOR.PATCH`. Swift Package Manager resolves versions from git tags — no version field in `Package.swift`.\n\n---\n\n## Dependencies\n\n| Library | Version | Target | Purpose |\n|---------|---------|--------|---------|\n| [SnapKit](https://github.com/SnapKit/SnapKit) | 5.7.0+ | LumiKitUI | Programmatic Auto Layout |\n| [Lottie](https://github.com/airbnb/lottie-ios) | 4.4.0+ | LumiKitLottie | Pull-to-refresh animation |\n\nLottie is isolated in its own target so apps can opt out if they don't need pull-to-refresh animations.\n\n---\n\n## Naming Conventions\n\n- **Public types**: `LMK` prefix (e.g., `LMKColor`, `LMKSpacing`, `LMKAnimationHelper`)\n- **Extension methods**: `lmk_` prefix (e.g., `view.lmk_addSubviews(...)`)\n- **Theme configs**: `LMK*Theme` structs (e.g., `LMKTypographyTheme`, `LMKSpacingTheme`)\n- **Protocols**: `LMK*DataSource`, `LMK*Delegate`\n\n---\n\n## Concurrency\n\nLumiKitUI and LumiKitLottie use Swift 6.2 `defaultIsolation: MainActor` — all types are `@MainActor` by default. Pure data types (theme config structs) opt out with `nonisolated` and conform to `Sendable` so they can be created and passed from any context.\n\nLumiKitCore has no default isolation and is safe to use from any concurrency context.\n\n---\n\n## Built with LumiKit\n\n| App | Description |\n|-----|-------------|\n| [Plantfolio Plus](https://plantfolio.luminoid.dev) | Plant care, watering schedules, collections, and photos for iOS, iPadOS, and Mac |\n| [Petfolio](https://petfolio.luminoid.dev) | Pet care, health tracking, vet visits, food inventory for iOS, iPadOS, and Mac |\n\n---\n\n## TODO\n\n### Infrastructure\n- [ ] Create CONTRIBUTING.md with contribution guidelines\n- [ ] Set up GitHub Actions CI (test on push/PR — iOS Simulator + Mac Catalyst)\n- [ ] Add SECURITY.md\n- [ ] Add DocC API reference documentation\n\n---\n\n## License\n\nLumiKit is released under the MIT License. See [LICENSE](LICENSE) for details.\n\n---\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for a detailed history of changes.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluminoid%2Flumikit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fluminoid%2Flumikit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluminoid%2Flumikit/lists"}