UNPKG

august-design-system

Version:

A comprehensive React Native design system following Apple Human Interface Guidelines

2,225 lines (2,212 loc) 285 kB
'use strict'; var React19 = require('react'); var reactNative = require('react-native'); var MaterialIcons = require('react-native-vector-icons/MaterialIcons'); var Animated6 = require('react-native-reanimated'); var reactNativeSafeAreaContext = require('react-native-safe-area-context'); var Svg = require('react-native-svg'); var reactNativeGestureHandler = require('react-native-gesture-handler'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var React19__default = /*#__PURE__*/_interopDefault(React19); var MaterialIcons__default = /*#__PURE__*/_interopDefault(MaterialIcons); var Animated6__default = /*#__PURE__*/_interopDefault(Animated6); var Svg__default = /*#__PURE__*/_interopDefault(Svg); // src/design-system/tokens/colors.ts var palette = { // Gray scale - iOS system grays gray: { 50: "#F2F2F7", 100: "#E5E5EA", 200: "#D1D1D6", 300: "#C7C7CC", 400: "#AEAEB2", 500: "#8E8E93", 600: "#636366", 700: "#48484A", 800: "#3A3A3C", 900: "#2C2C2E", 950: "#1C1C1E" }, // System colors - iOS standard palette // Light mode values red: { light: "#FF3B30", dark: "#FF453A" }, orange: { light: "#FF9500", dark: "#FF9F0A" }, yellow: { light: "#FFCC00", dark: "#FFD60A" }, green: { light: "#34C759", dark: "#30D158" }, mint: { light: "#00C7BE", dark: "#63E6E2" }, teal: { light: "#30B0C7", dark: "#40C8E0" }, cyan: { light: "#32ADE6", dark: "#64D2FF" }, blue: { light: "#007AFF", dark: "#0A84FF" }, indigo: { light: "#5856D6", dark: "#5E5CE6" }, purple: { light: "#AF52DE", dark: "#BF5AF2" }, pink: { light: "#FF2D55", dark: "#FF375F" }, brown: { light: "#A2845E", dark: "#AC8E68" } }; var lightColors = { // Background colors - iOS grouped table view style layering background: { primary: "#FFFFFF", secondary: "#F2F2F7", tertiary: "#FFFFFF", grouped: "#F2F2F7", groupedSecondary: "#FFFFFF", groupedTertiary: "#F2F2F7" }, // Label colors - for text content label: { primary: "#000000", // 100% black secondary: "rgba(60, 60, 67, 0.6)", // 60% opacity tertiary: "rgba(60, 60, 67, 0.3)", // 30% opacity quaternary: "rgba(60, 60, 67, 0.18)" // 18% opacity }, // Fill colors - for thin and small shapes fill: { primary: "rgba(120, 120, 128, 0.2)", secondary: "rgba(120, 120, 128, 0.16)", tertiary: "rgba(118, 118, 128, 0.12)", quaternary: "rgba(116, 116, 128, 0.08)" }, // Separator colors separator: { opaque: "#C6C6C8", nonOpaque: "rgba(60, 60, 67, 0.36)" }, // System colors - Apple standard system: { red: palette.red.light, orange: palette.orange.light, yellow: palette.yellow.light, green: palette.green.light, mint: palette.mint.light, teal: palette.teal.light, cyan: palette.cyan.light, blue: palette.blue.light, indigo: palette.indigo.light, purple: palette.purple.light, pink: palette.pink.light, brown: palette.brown.light, gray: palette.gray[500], gray2: palette.gray[400], gray3: palette.gray[300], gray4: palette.gray[200], gray5: palette.gray[100], gray6: palette.gray[50] }, // Semantic colors - functional meaning semantic: { success: palette.green.light, warning: palette.orange.light, error: palette.red.light, info: palette.blue.light }, // Interactive colors interactive: { tint: palette.blue.light, tintPressed: "#0062CC", // Darkened blue for pressed state tintDisabled: "rgba(0, 122, 255, 0.3)", destructive: palette.red.light, destructivePressed: "#CC2F26" }, // Material/Blur backgrounds - approximated for non-blur fallback material: { thin: "rgba(255, 255, 255, 0.6)", regular: "rgba(255, 255, 255, 0.72)", thick: "rgba(255, 255, 255, 0.85)", chrome: "rgba(247, 247, 247, 0.8)" } }; var darkColors = { // Background colors - elevated surfaces in dark mode background: { primary: "#000000", secondary: "#1C1C1E", tertiary: "#2C2C2E", grouped: "#000000", groupedSecondary: "#1C1C1E", groupedTertiary: "#2C2C2E" }, // Label colors - inverted for dark mode label: { primary: "#FFFFFF", secondary: "rgba(235, 235, 245, 0.6)", tertiary: "rgba(235, 235, 245, 0.3)", quaternary: "rgba(235, 235, 245, 0.18)" }, // Fill colors - adjusted for dark backgrounds fill: { primary: "rgba(120, 120, 128, 0.36)", secondary: "rgba(120, 120, 128, 0.32)", tertiary: "rgba(118, 118, 128, 0.24)", quaternary: "rgba(116, 116, 128, 0.18)" }, // Separator colors separator: { opaque: "#38383A", nonOpaque: "rgba(84, 84, 88, 0.6)" }, // System colors - adjusted for dark mode (higher luminance) system: { red: palette.red.dark, orange: palette.orange.dark, yellow: palette.yellow.dark, green: palette.green.dark, mint: palette.mint.dark, teal: palette.teal.dark, cyan: palette.cyan.dark, blue: palette.blue.dark, indigo: palette.indigo.dark, purple: palette.purple.dark, pink: palette.pink.dark, brown: palette.brown.dark, gray: palette.gray[500], gray2: palette.gray[600], gray3: palette.gray[700], gray4: palette.gray[800], gray5: palette.gray[900], gray6: palette.gray[950] }, // Semantic colors - dark mode variants semantic: { success: palette.green.dark, warning: palette.orange.dark, error: palette.red.dark, info: palette.blue.dark }, // Interactive colors - adjusted for dark backgrounds interactive: { tint: palette.blue.dark, tintPressed: "#409CFF", // Lightened blue for pressed state in dark mode tintDisabled: "rgba(10, 132, 255, 0.3)", destructive: palette.red.dark, destructivePressed: "#FF6961" }, // Material/Blur backgrounds - dark mode variants material: { thin: "rgba(30, 30, 30, 0.6)", regular: "rgba(30, 30, 30, 0.72)", thick: "rgba(30, 30, 30, 0.85)", chrome: "rgba(36, 36, 38, 0.8)" } }; function withAlpha(color, alpha) { if (color.startsWith("rgba")) { return color.replace(/[\d.]+\)$/, `${alpha})`); } if (color.startsWith("#")) { const hex = color.replace("#", ""); const r = parseInt(hex.substring(0, 2), 16); const g = parseInt(hex.substring(2, 4), 16); const b = parseInt(hex.substring(4, 6), 16); return `rgba(${r}, ${g}, ${b}, ${alpha})`; } return color; } function hasMinimumContrast(foreground, background, minimumRatio = 4.5) { const isDarkBg = background === "#000000" || background.includes("1C1C1E") || background.includes("2C2C2E"); const isLightFg = foreground === "#FFFFFF" || foreground.includes("235, 235, 245"); return isDarkBg === isLightFg; } // src/design-system/tokens/typography.ts var fontFamily = { regular: "System", medium: "System", semibold: "System", bold: "System", heavy: "System", monospace: "Menlo", // Falls back to platform monospace rounded: "System" // SF Pro Rounded on iOS }; var typography = { // ========================================================================== // DISPLAY STYLES // Large, prominent text for titles and headers // ========================================================================== /** * Large Title - Used for main screen titles in navigation bars. * iOS: 34pt Regular */ largeTitle: { fontFamily: fontFamily.regular, fontSize: 34, lineHeight: 41, // 1.2x letterSpacing: 0.37, fontWeight: "400" }, /** * Title 1 - Primary content titles. * iOS: 28pt Regular */ title1: { fontFamily: fontFamily.regular, fontSize: 28, lineHeight: 34, // 1.21x letterSpacing: 0.36, fontWeight: "400" }, /** * Title 2 - Secondary titles. * iOS: 22pt Regular */ title2: { fontFamily: fontFamily.regular, fontSize: 22, lineHeight: 28, // 1.27x letterSpacing: 0.35, fontWeight: "400" }, /** * Title 3 - Tertiary titles. * iOS: 20pt Regular */ title3: { fontFamily: fontFamily.regular, fontSize: 20, lineHeight: 25, // 1.25x letterSpacing: 0.38, fontWeight: "400" }, // ========================================================================== // HEADLINE STYLES // For section headers and emphasized text // ========================================================================== /** * Headline - Section headers, emphasized body text. * iOS: 17pt Semibold */ headline: { fontFamily: fontFamily.semibold, fontSize: 17, lineHeight: 22, // 1.29x letterSpacing: -0.41, fontWeight: "600" }, /** * Subheadline - Subordinate section headers. * iOS: 15pt Regular */ subheadline: { fontFamily: fontFamily.regular, fontSize: 15, lineHeight: 20, // 1.33x letterSpacing: -0.24, fontWeight: "400" }, // ========================================================================== // BODY STYLES // Primary reading text // ========================================================================== /** * Body - Primary reading text throughout the app. * iOS: 17pt Regular */ body: { fontFamily: fontFamily.regular, fontSize: 17, lineHeight: 22, // 1.29x letterSpacing: -0.41, fontWeight: "400" }, /** * Callout - Secondary text that's slightly smaller than body. * iOS: 16pt Regular */ callout: { fontFamily: fontFamily.regular, fontSize: 16, lineHeight: 21, // 1.31x letterSpacing: -0.32, fontWeight: "400" }, // ========================================================================== // SUPPORTING STYLES // Smaller text for captions, footnotes, and labels // ========================================================================== /** * Footnote - Smaller supporting text. * iOS: 13pt Regular */ footnote: { fontFamily: fontFamily.regular, fontSize: 13, lineHeight: 18, // 1.38x letterSpacing: -0.08, fontWeight: "400" }, /** * Caption 1 - Primary caption style. * iOS: 12pt Regular */ caption1: { fontFamily: fontFamily.regular, fontSize: 12, lineHeight: 16, // 1.33x letterSpacing: 0, fontWeight: "400" }, /** * Caption 2 - Secondary caption style (smallest). * iOS: 11pt Regular */ caption2: { fontFamily: fontFamily.regular, fontSize: 11, lineHeight: 13, // 1.18x letterSpacing: 0.07, fontWeight: "400" } }; function withWeight(style, weight) { return { ...style, fontWeight: weight }; } function withSize(style, fontSize) { const ratio = style.lineHeight / style.fontSize; return { ...style, fontSize, lineHeight: Math.round(fontSize * ratio) }; } function emphasized(style) { return withWeight(style, "600"); } function bold(style) { return withWeight(style, "700"); } var typographyEmphasis = { bodyEmphasis: emphasized(typography.body), calloutEmphasis: emphasized(typography.callout), footnoteEmphasis: emphasized(typography.footnote), caption1Emphasis: emphasized(typography.caption1), subheadlineEmphasis: emphasized(typography.subheadline) }; // src/design-system/tokens/spacing.ts var SPACING_UNIT = 4; var spacing = { // Base scale none: 0, xxs: 2, xs: 4, sm: 8, md: 12, lg: 16, xl: 20, xxl: 24, xxxl: 32, xxxxl: 40, xxxxxl: 48, // Semantic spacing - Inset (padding) // Used for content padding inside containers inset: { none: 0, xs: 4, sm: 8, md: 12, lg: 16, // Standard content inset xl: 20 }, // Semantic spacing - Stack (vertical) // Used for vertical spacing between elements stack: { none: 0, xs: 4, sm: 8, // Tight grouping md: 12, lg: 16, // Standard section spacing xl: 24 // Large section spacing }, // Semantic spacing - Inline (horizontal) // Used for horizontal spacing between elements inline: { none: 0, xs: 4, sm: 8, // Icon to text spacing md: 12, lg: 16, // Standard element spacing xl: 24 } }; var layoutConstants = { /** * Standard horizontal margin for content. * iOS typically uses 16pt margins. */ screenHorizontalPadding: 16, /** * Standard vertical margin for content. */ screenVerticalPadding: 16, /** * Navigation bar height (standard). */ navigationBarHeight: 44, /** * Navigation bar height (large title). */ navigationBarLargeTitleHeight: 96, /** * Tab bar height. */ tabBarHeight: 49, /** * Tab bar height with home indicator (iPhone X+). */ tabBarHeightWithHomeIndicator: 83, /** * Minimum touch target size (Apple HIG requirement). * All interactive elements should be at least 44x44pt. */ minTouchTarget: 44, /** * Standard list item height. */ listItemHeight: 44, /** * Standard list item height (subtitle style). */ listItemSubtitleHeight: 64, /** * Standard search bar height. */ searchBarHeight: 36, /** * Standard toolbar height. */ toolbarHeight: 44, /** * Standard separator thickness. */ separatorThickness: 0.5, /** * Standard icon size in lists. */ listIconSize: 28, /** * Disclosure indicator width. */ disclosureIndicatorWidth: 8, /** * Standard button corner radius. */ buttonCornerRadius: 10, /** * Standard card corner radius. */ cardCornerRadius: 12, /** * Sheet corner radius. */ sheetCornerRadius: 16 }; function space(multiplier) { return SPACING_UNIT * multiplier; } function insetAll(value) { return { padding: value }; } function insetSquish(horizontal, vertical) { return { paddingHorizontal: horizontal, paddingVertical: vertical }; } function insetDirectional(top, right, bottom, left) { return { paddingTop: top, paddingRight: right, paddingBottom: bottom, paddingLeft: left }; } // src/design-system/tokens/radius.ts var radius = { none: 0, xs: 4, sm: 8, md: 12, lg: 16, xl: 20, xxl: 24, full: 9999 }; var semanticRadius = { /** * Button corner radius (matches iOS default). */ button: radius.sm, /** * Small button corner radius. */ buttonSmall: radius.xs, /** * Card corner radius. */ card: radius.md, /** * Input field corner radius. */ input: radius.sm, /** * Modal/Dialog corner radius. */ modal: radius.lg, /** * Bottom sheet corner radius. */ sheet: radius.xl, /** * Image thumbnail radius. */ thumbnail: radius.sm, /** * Avatar (circular) radius. */ avatar: radius.full, /** * Badge/Chip radius. */ badge: radius.full, /** * Pill button radius. */ pill: radius.full, /** * Tag/Label radius. */ tag: radius.xs, /** * Toast notification radius. */ toast: radius.md, /** * Tooltip radius. */ tooltip: radius.sm, /** * Popover radius. */ popover: radius.md, /** * Search bar radius. */ searchBar: radius.sm, /** * Segment control radius. */ segmentControl: radius.sm, /** * Slider track radius. */ slider: radius.full, /** * Progress bar radius. */ progressBar: radius.full }; function circular(size) { return size / 2; } function corners(topLeft, topRight, bottomRight, bottomLeft) { return { borderTopLeftRadius: topLeft, borderTopRightRadius: topRight, borderBottomRightRadius: bottomRight, borderBottomLeftRadius: bottomLeft }; } function topRounded(value) { return corners(value, value, 0, 0); } function bottomRounded(value) { return corners(0, 0, value, value); } function leftRounded(value) { return corners(value, 0, 0, value); } function rightRounded(value) { return corners(0, value, value, 0); } // src/design-system/tokens/shadows.ts var shadowColors = { light: "rgba(0, 0, 0, 1)", // Opacity controlled per shadow dark: "rgba(0, 0, 0, 1)" // Deeper shadows in dark mode }; var lightShadows = { /** * No shadow. */ none: { shadowColor: shadowColors.light, shadowOffset: { width: 0, height: 0 }, shadowOpacity: 0, shadowRadius: 0, elevation: 0 }, /** * Extra small shadow - subtle lift. * Use for: Subtle hover states, pressed buttons. */ xs: { shadowColor: shadowColors.light, shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.04, shadowRadius: 2, elevation: 1 }, /** * Small shadow - light elevation. * Use for: Cards, list items, buttons. */ sm: { shadowColor: shadowColors.light, shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 4, elevation: 2 }, /** * Medium shadow - standard elevation. * Use for: Dropdown menus, popovers, floating action buttons. */ md: { shadowColor: shadowColors.light, shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.12, shadowRadius: 8, elevation: 4 }, /** * Large shadow - prominent elevation. * Use for: Modals, dialogs, navigation overlays. */ lg: { shadowColor: shadowColors.light, shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.15, shadowRadius: 16, elevation: 8 }, /** * Extra large shadow - high elevation. * Use for: Bottom sheets, side panels. */ xl: { shadowColor: shadowColors.light, shadowOffset: { width: 0, height: 12 }, shadowOpacity: 0.18, shadowRadius: 24, elevation: 12 }, /** * XXL shadow - maximum elevation. * Use for: Full-screen overlays, critical modals. */ xxl: { shadowColor: shadowColors.light, shadowOffset: { width: 0, height: 16 }, shadowOpacity: 0.22, shadowRadius: 32, elevation: 16 } }; var darkShadows = { none: { shadowColor: shadowColors.dark, shadowOffset: { width: 0, height: 0 }, shadowOpacity: 0, shadowRadius: 0, elevation: 0 }, xs: { shadowColor: shadowColors.dark, shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.2, shadowRadius: 2, elevation: 1 }, sm: { shadowColor: shadowColors.dark, shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.25, shadowRadius: 4, elevation: 2 }, md: { shadowColor: shadowColors.dark, shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, shadowRadius: 8, elevation: 4 }, lg: { shadowColor: shadowColors.dark, shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.35, shadowRadius: 16, elevation: 8 }, xl: { shadowColor: shadowColors.dark, shadowOffset: { width: 0, height: 12 }, shadowOpacity: 0.4, shadowRadius: 24, elevation: 12 }, xxl: { shadowColor: shadowColors.dark, shadowOffset: { width: 0, height: 16 }, shadowOpacity: 0.45, shadowRadius: 32, elevation: 16 } }; var semanticShadows = { /** * Card shadow. */ card: lightShadows.sm, /** * Button shadow (when elevated). */ button: lightShadows.xs, /** * Pressed button shadow (reduced). */ buttonPressed: lightShadows.none, /** * Floating action button shadow. */ fab: lightShadows.md, /** * Dropdown/Popover shadow. */ dropdown: lightShadows.md, /** * Modal/Dialog shadow. */ modal: lightShadows.lg, /** * Bottom sheet shadow. */ sheet: lightShadows.xl, /** * Toast notification shadow. */ toast: lightShadows.md, /** * Navigation header shadow. */ header: lightShadows.xs, /** * Tab bar shadow. */ tabBar: lightShadows.xs }; function createShadow(offsetY, opacity2, radius2, elevation, offsetX = 0, color = shadowColors.light) { return { shadowColor: color, shadowOffset: { width: offsetX, height: offsetY }, shadowOpacity: opacity2, shadowRadius: radius2, elevation }; } function combineShadows(...shadows) { if (shadows.length === 0) return lightShadows.none; return shadows.reduce( (prev, current) => current.elevation > prev.elevation ? current : prev ); } function scaleShadow(shadow, factor) { return { ...shadow, shadowOffset: { width: shadow.shadowOffset.width * factor, height: shadow.shadowOffset.height * factor }, shadowRadius: shadow.shadowRadius * factor, elevation: Math.round(shadow.elevation * factor) }; } // src/design-system/tokens/animation.ts var duration = { instant: 0, fastest: 50, faster: 100, fast: 150, normal: 250, slow: 350, slower: 500, slowest: 700 }; var easing = { /** * Linear - constant speed (rarely used for UI). */ linear: [0, 0, 1, 1], /** * Ease In - slow start, fast end. * Use for: Elements leaving the screen. */ easeIn: [0.42, 0, 1, 1], /** * Ease Out - fast start, slow end. * Use for: Elements entering the screen. * This is the most common easing for iOS-style animations. */ easeOut: [0, 0, 0.58, 1], /** * Ease In Out - slow start and end. * Use for: Elements that start and end on screen. */ easeInOut: [0.42, 0, 0.58, 1], /** * Default spring - balanced, natural feel. * iOS default animation spring characteristics. * Use for: Most interactive animations. */ spring: { damping: 15, stiffness: 150, mass: 1 }, /** * Gentle spring - soft, slow settling. * Use for: Subtle movements, floating elements. */ springGentle: { damping: 20, stiffness: 100, mass: 1 }, /** * Bouncy spring - energetic, playful feel. * Use for: Emphasis, celebrations, playful interactions. * Use sparingly to avoid overwhelming users. */ springBouncy: { damping: 10, stiffness: 200, mass: 1 } }; var animation = { duration, easing }; var animationPresets = { /** * Button press feedback. */ buttonPress: { duration: duration.faster, easing: easing.easeOut }, /** * Button release feedback. */ buttonRelease: { duration: duration.fast, easing: easing.spring }, /** * Modal/Sheet appear. */ modalEnter: { duration: duration.normal, easing: easing.spring }, /** * Modal/Sheet dismiss. */ modalExit: { duration: duration.fast, easing: easing.easeIn }, /** * Page/Screen transition. */ pageTransition: { duration: duration.slow, easing: easing.easeInOut }, /** * Fade in content. */ fadeIn: { duration: duration.normal, easing: easing.easeOut }, /** * Fade out content. */ fadeOut: { duration: duration.fast, easing: easing.easeIn }, /** * Slide in from bottom. */ slideInUp: { duration: duration.normal, easing: easing.spring }, /** * Slide out to bottom. */ slideOutDown: { duration: duration.fast, easing: easing.easeIn }, /** * Scale up (appearing). */ scaleIn: { duration: duration.normal, easing: easing.springBouncy }, /** * Scale down (disappearing). */ scaleOut: { duration: duration.fast, easing: easing.easeIn }, /** * Expand/Collapse accordion. */ expand: { duration: duration.normal, easing: easing.easeInOut }, /** * Switch/Toggle animation. */ toggle: { duration: duration.fast, easing: easing.spring }, /** * Skeleton loading shimmer. */ skeleton: { duration: duration.slowest, easing: easing.linear }, /** * Toast notification appear. */ toastEnter: { duration: duration.normal, easing: easing.springBouncy }, /** * Toast notification dismiss. */ toastExit: { duration: duration.fast, easing: easing.easeIn }, /** * Haptic feedback timing. */ haptic: { duration: duration.instant, easing: easing.linear } }; var reducedMotionPresets = { /** * Replaces spring/bouncy animations with simple fade. */ default: { duration: duration.fast, easing: easing.easeOut }, /** * Replaces sliding animations with fade. */ slide: { duration: duration.faster, easing: easing.easeOut }, /** * Replaces scale animations with fade. */ scale: { duration: duration.faster, easing: easing.easeOut }, /** * Instant change (no animation). */ instant: { duration: duration.instant, easing: easing.linear } }; function withDelay(baseDelay, index = 0, staggerAmount = 50) { return baseDelay + index * staggerAmount; } function stagger(index, staggerAmount = 50, maxDelay = 500) { return Math.min(index * staggerAmount, maxDelay); } // src/design-system/tokens/sizes.ts var sizes = { // Touch targets - Apple HIG minimum 44pt touchTarget: { /** * Minimum touch target size (44pt). * Required by Apple HIG for all interactive elements. */ minimum: 44, /** * Comfortable touch target (48pt). * Recommended for frequently used actions. */ comfortable: 48, /** * Spacious touch target (56pt). * For primary actions and accessibility. */ spacious: 56 }, // Icon sizes icon: { /** * Extra small icon (12pt). * Use for: Inline badges, tiny indicators. */ xs: 12, /** * Small icon (16pt). * Use for: Inline text icons, compact UI. */ sm: 16, /** * Medium icon (20pt). * Use for: Standard inline icons. */ md: 20, /** * Large icon (24pt). * Use for: Navigation icons, tab bar icons. */ lg: 24, /** * Extra large icon (32pt). * Use for: Featured icons, prominent actions. */ xl: 32, /** * XXL icon (40pt). * Use for: Illustrations, large feature icons. */ xxl: 40 }, // Avatar sizes avatar: { /** * Extra small avatar (24pt). * Use for: Inline mentions, compact lists. */ xs: 24, /** * Small avatar (32pt). * Use for: Comments, messaging. */ sm: 32, /** * Medium avatar (40pt). * Use for: Standard list items. */ md: 40, /** * Large avatar (56pt). * Use for: Profile cards, detailed lists. */ lg: 56, /** * Extra large avatar (72pt). * Use for: Profile headers. */ xl: 72, /** * XXL avatar (96pt). * Use for: Profile pages, settings. */ xxl: 96 }, // Button heights button: { /** * Small button (32pt). * Use for: Compact UI, inline actions. * Note: May not meet touch target minimum. */ sm: 32, /** * Medium button (44pt) - DEFAULT. * Use for: Standard buttons. * Meets Apple HIG minimum touch target. */ md: 44, /** * Large button (50pt). * Use for: Primary actions, CTAs. */ lg: 50, /** * Extra large button (56pt). * Use for: Hero actions, onboarding. */ xl: 56 }, // Input heights input: { /** * Small input (36pt). * Use for: Compact forms, filters. */ sm: 36, /** * Medium input (44pt) - DEFAULT. * Use for: Standard form inputs. * Meets Apple HIG minimum touch target. */ md: 44, /** * Large input (52pt). * Use for: Search bars, prominent inputs. */ lg: 52 } }; var zIndex = { /** * Base level - standard content. */ base: 0, /** * Dropdown level - menus, selects. */ dropdown: 1e3, /** * Sticky level - headers, navigation. */ sticky: 1100, /** * Overlay level - background overlays. */ overlay: 1200, /** * Modal level - dialogs, sheets. */ modal: 1300, /** * Popover level - tooltips, popovers. */ popover: 1400, /** * Tooltip level - floating hints. */ tooltip: 1500, /** * Toast level - notifications (highest). */ toast: 1600 }; var breakpoints = { /** * Extra small - small phones. */ xs: 0, /** * Small - standard phones (iPhone SE+). * iPhone SE: 375pt width. */ sm: 375, /** * Medium - large phones (iPhone Pro Max). * iPhone Pro Max: 428pt width. */ md: 428, /** * Large - small tablets (iPad Mini). * iPad Mini portrait: 744pt width. */ lg: 744, /** * Extra large - large tablets (iPad Pro). * iPad Pro portrait: 1024pt width. */ xl: 1024 }; var opacity = { /** * Fully transparent. */ transparent: 0, /** * Disabled state opacity. * iOS uses 0.3-0.4 for disabled elements. */ disabled: 0.38, /** * Medium opacity - overlays, secondary elements. */ medium: 0.6, /** * High opacity - emphasized content. */ high: 0.87, /** * Fully opaque. */ opaque: 1 }; var componentSizes = { /** * Navigation bar heights. */ navigationBar: { standard: 44, large: 96, largeExpanded: 140 }, /** * Tab bar heights. */ tabBar: { standard: 49, withHomeIndicator: 83 }, /** * Search bar dimensions. */ searchBar: { height: 36, iconSize: 18 }, /** * Toolbar dimensions. */ toolbar: { height: 44, iconSize: 24 }, /** * Segmented control. */ segmentedControl: { height: 32, minSegmentWidth: 60 }, /** * Switch/Toggle. */ switch: { width: 51, height: 31, thumbSize: 27 }, /** * Slider. */ slider: { trackHeight: 4, thumbSize: 28 }, /** * Progress bar. */ progressBar: { height: 4, heightLarge: 8 }, /** * Badge. */ badge: { minWidth: 20, height: 20, dotSize: 8 }, /** * Chip/Tag. */ chip: { height: 32, heightSmall: 24 }, /** * List item. */ listItem: { standard: 44, subtitle: 64, large: 88 }, /** * Card. */ card: { minHeight: 80, imageAspectRatio: 16 / 9 }, /** * Modal. */ modal: { maxWidth: 540, minHeight: 200 }, /** * Sheet. */ sheet: { handleWidth: 36, handleHeight: 5, snapPoints: { collapsed: 0.25, half: 0.5, expanded: 0.9 } }, /** * Toast. */ toast: { minHeight: 48, maxWidth: 400 } }; function meetsMinimumTouchTarget(size) { return size >= sizes.touchTarget.minimum; } function ensureMinimumTouchTarget(size) { return Math.max(size, sizes.touchTarget.minimum); } function getIconSizeForContext(context) { switch (context) { case "inline": return sizes.icon.sm; case "navigation": return sizes.icon.lg; case "tabBar": return sizes.icon.lg; case "feature": return sizes.icon.xl; default: return sizes.icon.md; } } // src/design-system/theme/defaultTheme.ts var lightTheme = { name: "August Light", mode: "light", colors: lightColors, typography, fontFamily, spacing, radius, shadows: lightShadows, animation, sizes, zIndex, breakpoints, opacity }; var darkTheme = { name: "August Dark", mode: "dark", colors: darkColors, typography, fontFamily, spacing, radius, shadows: darkShadows, animation, sizes, zIndex, breakpoints, opacity }; var defaultThemeConfig = { light: lightTheme, dark: darkTheme }; function getTheme(mode) { return mode === "dark" ? darkTheme : lightTheme; } // src/design-system/theme/createTheme.ts function isPlainObject(value) { return typeof value === "object" && value !== null && !Array.isArray(value) && Object.prototype.toString.call(value) === "[object Object]"; } function deepMerge(target, source) { if (!isPlainObject(target) || !isPlainObject(source)) { return source ?? target; } const output = { ...target }; const sourceObj = source; const targetObj = target; for (const key in sourceObj) { if (Object.prototype.hasOwnProperty.call(sourceObj, key)) { const sourceValue = sourceObj[key]; const targetValue = targetObj[key]; if (isPlainObject(sourceValue) && isPlainObject(targetValue)) { output[key] = deepMerge(targetValue, sourceValue); } else if (sourceValue !== void 0) { output[key] = sourceValue; } } } return output; } function createLightTheme(name, extension = {}) { const baseTheme = { ...lightTheme, name }; return deepMerge(baseTheme, extension); } function createDarkTheme(name, extension = {}) { const baseTheme = { ...darkTheme, name }; return deepMerge(baseTheme, extension); } function createTheme(config) { return { light: createLightTheme(`${config.name} Light`, config.light), dark: createDarkTheme(`${config.name} Dark`, config.dark) }; } function createBrandColors(primary, options = {}) { return { colors: { interactive: { tint: primary, tintPressed: options.primaryPressed ?? primary, tintDisabled: options.primaryDisabled ?? `${primary}4D` // 30% opacity } } }; } function createCustomTypography(fontFamilies) { return { fontFamily: { regular: fontFamilies.regular ?? "System", medium: fontFamilies.medium ?? "System", semibold: fontFamilies.semibold ?? "System", bold: fontFamilies.bold ?? "System", heavy: "System", monospace: "Menlo", rounded: "System" } }; } function mergeExtensions(...extensions) { return extensions.reduce( (acc, ext) => deepMerge(acc, ext), {} ); } function isValidTheme(theme) { if (!isPlainObject(theme)) return false; const requiredKeys = [ "name", "mode", "colors", "typography", "fontFamily", "spacing", "radius", "shadows", "animation", "sizes", "zIndex", "breakpoints", "opacity" ]; return requiredKeys.every((key) => key in theme); } function getTokenValue(theme, path) { const keys = path.split("."); let current = theme; for (const key of keys) { if (!isPlainObject(current) || !(key in current)) { return void 0; } current = current[key]; } return current; } var defaultContextValue = { theme: lightTheme, colorMode: "light", colorModePreference: "system", toggleColorMode: () => { console.warn("ThemeProvider not found in component tree"); }, setColorMode: () => { console.warn("ThemeProvider not found in component tree"); }, isDark: false, isLight: true }; var ThemeContext = React19.createContext(defaultContextValue); function ThemeProvider({ children, defaultColorMode = "system", theme: customTheme, storageKey = "august-color-mode" }) { const systemColorScheme = reactNative.useColorScheme(); const [colorModePreference, setColorModePreference] = React19.useState(defaultColorMode); const colorMode = React19.useMemo(() => { if (colorModePreference === "system") { return systemColorScheme === "dark" ? "dark" : "light"; } return colorModePreference; }, [colorModePreference, systemColorScheme]); const themes = React19.useMemo(() => { if (customTheme) { return { light: createLightTheme( customTheme.name ? `${customTheme.name} Light` : "Custom Light", customTheme.light ), dark: createDarkTheme( customTheme.name ? `${customTheme.name} Dark` : "Custom Dark", customTheme.dark ) }; } return { light: lightTheme, dark: darkTheme }; }, [customTheme]); const currentTheme = React19.useMemo(() => { return colorMode === "dark" ? themes.dark : themes.light; }, [colorMode, themes]); const toggleColorMode = React19.useCallback(() => { setColorModePreference((prev) => { if (prev === "system") { return colorMode === "dark" ? "light" : "dark"; } return prev === "dark" ? "light" : "dark"; }); }, [colorMode]); const setColorMode = React19.useCallback((mode) => { setColorModePreference(mode); }, []); const contextValue = React19.useMemo( () => ({ theme: currentTheme, colorMode, colorModePreference, toggleColorMode, setColorMode, isDark: colorMode === "dark", isLight: colorMode === "light" }), [ currentTheme, colorMode, colorModePreference, toggleColorMode, setColorMode ] ); return /* @__PURE__ */ React19__default.default.createElement(ThemeContext.Provider, { value: contextValue }, children); } function useTheme() { const context = React19.useContext(ThemeContext); if (context === defaultContextValue) { console.warn( "useTheme must be used within a ThemeProvider. Falling back to default light theme." ); } return context; } function useThemeTokens() { const { theme } = useTheme(); return theme; } function useColorMode() { const { colorMode } = useTheme(); return colorMode; } function useIsDarkMode() { const { isDark } = useTheme(); return isDark; } function useToken(selector) { const { theme } = useTheme(); return React19.useMemo(() => selector(theme), [theme, selector]); } function useColors() { const { theme } = useTheme(); return theme.colors; } function useSpacing() { const { theme } = useTheme(); return theme.spacing; } function useTypography() { const { theme } = useTheme(); return theme.typography; } function useThemedStyles(styleCreator) { const { theme } = useTheme(); return React19.useMemo(() => { const rawStyles = styleCreator(theme); return reactNative.StyleSheet.create(rawStyles); }, [theme, styleCreator]); } function useThemedStyle(styleCreator) { const { theme } = useTheme(); return React19.useMemo(() => styleCreator(theme), [theme, styleCreator]); } function createThemedStyles(styleCreator) { return function useStyles() { return useThemedStyles(styleCreator); }; } function combineStyles(...styles3) { return styles3.filter(Boolean); } function conditionalStyle(condition, trueStyle, falseStyle) { if (condition) return trueStyle; return falseStyle; } function useBreakpoint() { const { width } = reactNative.useWindowDimensions(); const { theme } = useTheme(); const { breakpoints: breakpoints2 } = theme; return React19.useMemo(() => { if (width >= breakpoints2.xl) return "xl"; if (width >= breakpoints2.lg) return "lg"; if (width >= breakpoints2.md) return "md"; if (width >= breakpoints2.sm) return "sm"; return "xs"; }, [width, breakpoints2]); } function useResponsiveValue(value) { const breakpoint = useBreakpoint(); return React19.useMemo(() => { if (typeof value !== "object" || value === null || Array.isArray(value)) { return value; } const responsiveValue = value; const breakpointOrder = ["xl", "lg", "md", "sm", "xs"]; const currentIndex = breakpointOrder.indexOf(breakpoint); for (let i = currentIndex; i < breakpointOrder.length; i++) { const bp = breakpointOrder[i]; if (bp in responsiveValue && responsiveValue[bp] !== void 0) { return responsiveValue[bp]; } } for (const bp of breakpointOrder) { if (bp in responsiveValue && responsiveValue[bp] !== void 0) { return responsiveValue[bp]; } } return void 0; }, [value, breakpoint]); } function useDeviceType() { const breakpoint = useBreakpoint(); return breakpoint === "lg" || breakpoint === "xl" ? "tablet" : "phone"; } function useIsBreakpoint(targetBreakpoint) { const { width } = reactNative.useWindowDimensions(); const { theme } = useTheme(); return width >= theme.breakpoints[targetBreakpoint]; } function useScreenDimensions() { const { width, height } = reactNative.useWindowDimensions(); return React19.useMemo( () => ({ width, height, isPortrait: height >= width, isLandscape: width > height, aspectRatio: width / height }), [width, height] ); } function responsiveStyle(styles3) { const breakpointOrder = ["xl", "lg", "md", "sm", "xs"]; return (breakpoint) => { const currentIndex = breakpointOrder.indexOf(breakpoint); for (let i = currentIndex; i < breakpointOrder.length; i++) { const bp = breakpointOrder[i]; if (bp in styles3 && styles3[bp] !== void 0) { return styles3[bp]; } } return void 0; }; } function useReducedMotion() { const [reducedMotion, setReducedMotion] = React19.useState(false); React19.useEffect(() => { reactNative.AccessibilityInfo.isReduceMotionEnabled().then(setReducedMotion); const subscription = reactNative.AccessibilityInfo.addEventListener( "reduceMotionChanged", setReducedMotion ); return () => { subscription.remove(); }; }, []); return reducedMotion; } function useAccessibleAnimation(preset) { const prefersReducedMotion = useReducedMotion(); return React19.useMemo(() => { if (prefersReducedMotion) { return { duration: reducedMotionPresets.default.duration, useNativeDriver: true, isReduced: true }; } return { duration: animationPresets[preset].duration, useNativeDriver: true, isReduced: false }; }, [preset, prefersReducedMotion]); } function useScreenReader() { const [screenReaderEnabled, setScreenReaderEnabled] = React19.useState(false); React19.useEffect(() => { reactNative.AccessibilityInfo.isScreenReaderEnabled().then(setScreenReaderEnabled); const subscription = reactNative.AccessibilityInfo.addEventListener( "screenReaderChanged", setScreenReaderEnabled ); return () => { subscription.remove(); }; }, []); return screenReaderEnabled; } function useBoldText() { const [boldTextEnabled, setBoldTextEnabled] = React19.useState(false); React19.useEffect(() => { reactNative.AccessibilityInfo.isBoldTextEnabled().then(setBoldTextEnabled); const subscription = reactNative.AccessibilityInfo.addEventListener( "boldTextChanged", setBoldTextEnabled ); return () => { subscription.remove(); }; }, []); return boldTextEnabled; } function useDynamicType() { const { fontScale } = reactNative.useWindowDimensions(); return fontScale; } function useScaledTypography(baseStyle, options = {}) { const { maxScale = 2, minScale = 0.8 } = options; const fontScale = useDynamicType(); return React19.useMemo(() => { const clampedScale = Math.min(Math.max(fontScale, minScale), maxScale); return { ...baseStyle, fontSize: Math.round(baseStyle.fontSize * clampedScale), lineHeight: Math.round(baseStyle.lineHeight * clampedScale) }; }, [baseStyle, fontScale, maxScale, minScale]); } function useHighContrast() { const boldTextEnabled = useBoldText(); return boldTextEnabled; } function announceForAccessibility(message) { reactNative.AccessibilityInfo.announceForAccessibility(message); } function setAccessibilityFocus(reactTag) { if (reactTag) { reactNative.AccessibilityInfo.setAccessibilityFocus(reactTag); } } function hexToRgb(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } function rgbToHex(r, g, b) { return "#" + [r, g, b].map((x) => { const hex = Math.round(x).toString(16); return hex.length === 1 ? "0" + hex : hex; }).join(""); } function withOpacity(color, alpha) { if (color.startsWith("rgba")) { return color.replace(/[\d.]+\)$/, `${alpha})`); } if (color.startsWith("rgb(")) { return color.replace("rgb(", "rgba(").replace(")", `, ${alpha})`); } if (color.startsWith("#")) { const rgb = hexToRgb(color); if (rgb) { return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`; } } return color; } function lighten(color, percent) { const rgb = hexToRgb(color); if (!rgb) return color; const factor = percent / 100; return rgbToHex( Math.min(255, rgb.r + (255 - rgb.r) * factor), Math.min(255, rgb.g + (255 - rgb.g) * factor), Math.min(255, rgb.b + (255 - rgb.b) * factor) ); } function darken(color, percent) { const rgb = hexToRgb(color); if (!rgb) return color; const factor = 1 - percent / 100; return rgbToHex(rgb.r * factor, rgb.g * factor, rgb.b * factor); } function getLuminance(color) { const rgb = hexToRgb(color); if (!rgb) return 0; const [r, g, b] = [rgb.r, rgb.g, rgb.b].map((channel) => { const sRGB = channel / 255; return sRGB <= 0.03928 ? sRGB / 12.92 : Math.pow((sRGB + 0.055) / 1.055, 2.4); }); return 0.2126 * r + 0.7152 * g + 0.0722 * b; } function getContrastRatio(color1, color2) { const lum1 = getLuminance(color1); const lum2 = getLuminance(color2); const lighter = Math.max(lum1, lum2); const darker = Math.min(lum1, lum2); return (lighter + 0.05) / (darker + 0.05); } function meetsWCAGAA(ratio, isLargeText = false) { return isLargeText ? ratio >= 3 : ratio >= 4.5; } function meetsWCAGAAA(ratio, isLargeText = false) { return isLargeText ? ratio >= 4.5 : ratio >= 7; } function gridSpace(multiplier) { return multiplier * 4; } function clamp(value, min, max) { return Math.min(Math.max(value, min), max); } function get(obj, path, defaultValue) { const keys = path.split("."); let result = obj; for (const key of keys) { if (result === null || result === void 0) { return defaultValue; } result = result[key]; } return result ?? defaultValue; } function getColor(theme, colorPath) { return get(theme.colors, colorPath, ""); } function ios(iosValue, androidValue) { return reactNative.Platform.OS === "ios" ? iosValue : androidValue; } function android(androidValue, iosValue) { return reactNative.Platform.OS === "android" ? androidValue : iosValue; } function platformSelect(options) { if (reactNative.Platform.OS === "ios" && options.ios !== void 0) { return options.ios; } if (reactNative.Platform.OS === "android" && options.android !== void 0) { return options.android; } return options.default; } function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } function toKebabCase(str) { return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/\s+/g, "-").toLowerCase(); } function toCamelCase(str) { return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toLowerCase()); } // src/design-system/constants/index.ts var VERSION = "1.0.0"; var NAME = "AugustDesignSystem"; var HIG = { /** * Minimum touch target size in points. * All interactive elements must be at least this size. */ MIN_TOUCH_TARGET: 44, /** * Recommended touch target size for comfortable interaction. */ COMFORTABLE_TOUCH_TARGET: 48, /** * Standard iOS content margins. */ CONTENT_MARGIN: 16, /** * Standard iOS safe area additional padding. */ SAFE_AREA_PADDING: 16, /** * Navigation bar heights. */ NAV_BAR_HEIGHT: 44, NAV_BAR_LARGE_TITLE_HEIGHT: 96, /** * Tab bar heights. */ TAB_BAR_HEIGHT: 49, TAB_BAR_HEIGHT_WITH_HOME_INDICATOR: 83, /** * Home indicator height (iPhone X and later). */ HOME_INDICATOR_HEIGHT: 34, /** * Status bar heights. */ STATUS_BAR_HEIGHT: 44, // iPhone X+ STATUS_BAR_HEIGHT_LEGACY: 20, // Older iPhones /** * Keyboard heights (approximate, varies by device). */ KEYBOARD_HEIGHT_PORTRAIT: 291, KEYBOARD_HEIGHT_LANDSCAPE: 209, /** * Standard list item heights. */ LIST_ITEM_HEIGHT: 44, LIST_ITEM_SUBTITLE_HEIGHT: 64, /** * Separator line thickness. */ SEPARATOR_HEIGHT: 0.5, /** * Standard animation durations (in ms). */ ANIMATION_FAST: 150, ANIMATION_NORMAL: 250, ANIMATION_SLOW: 350, /** * Modal presentation heights. */ SHEET_PEEK_HEIGHT: 0.25, SHEET_HALF_HEIGHT: 0.5, SHEET_FULL_HEIGHT: 0.9 }; var ACCESSIBILITY = { /** * Minimum contrast ratio for normal text (WCAG AA). */ MIN_CONTRAST_RATIO: 4.5, /** * Minimum contrast ratio for large text (WCAG AA). */ MIN_CONTRAST_RATIO_LARGE: 3, /** * Enhanced contrast ratio for normal text (WCAG AAA). */ ENHANCED_CONTRAST_RATIO: 7, /** * Enhanced contrast ratio for large text (WCAG AAA). */ ENHANCED_CONTRAST_RATIO_LARGE: 4.5, /** * Large text threshold (18pt regular or 14pt bold). */ LARGE_TEXT_THRESHOLD: 18, LARGE_TEXT_BOLD_THRESHOLD: 14, /** * Maximum text scale factor for accessibility. */ MAX_FONT_SCALE: 3.1, /** * Minimum recommended line height ratio. */ MIN_LINE_HEIGHT_RATIO: 1.2 }; var PLATFORM = { /** * iOS system font name. */ IOS_FONT: "System", /** * Android system font name. */ ANDROID_FONT: "Roboto", /** * Monospace font fallbacks. */ MONOSPACE_IOS: "Menlo", MONOSPACE_ANDROID: "monospace", /** * Default shadow color. */ SHADOW_COLOR: "#000000" }; var Z_INDEX = { BASE: 0, DROPDOWN: 1e3, STICKY: 1100, OVERLAY: 1200, MODAL: 1300, POPOVER: 1400, TOOLTIP: 1500, TOAST: 1600 }; var GRID = { /** * Base unit for spacing (4pt grid). */ BASE_UNIT: 4, /** * Common grid multipliers. */ MULTIPLIERS: { HALF: 0.5, // 2pt SINGLE: 1, // 4pt DOUBLE: 2, // 8pt TRIPLE: 3, // 12pt QUAD: 4, // 16pt QUINT: 5, // 20pt HEX: 6, // 24pt OCTA: 8, // 32pt DECA: 10, // 40pt DODECA: 12 // 48pt } }; var BREAKPOINTS = { /** * Small phones. */ XS: 0, /** * Standard phones (iPhone SE and up). */ SM: 375, /** * Large phones (iPhone Pro Max). */ MD: 428, /** * Small tablets (iPad Mini). */ LG: 744, /** * Large tablets (iPad Pro). */ XL: 1024 }; var TIMING = { /** * Debounce delay for search inputs (ms). */ DEBOUNCE_SEARCH: 300, /** * Debounce delay for resize events (ms). */ DEBOUNCE_RESIZE: 150, /** * Long press threshold (ms). */ LONG_PRESS_DELAY: 500, /** * Double tap threshold (ms)