UNPKG

august-design-system

Version:

A comprehensive React Native design system following Apple Human Interface Guidelines

2,193 lines (2,176 loc) 253 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/components/primitives/Box.tsx // 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"}, // System colors - iOS standard palette // Light mode values red: { light: "#FF3B30"}, orange: { light: "#FF9500"}, yellow: { light: "#FFCC00"}, green: { light: "#34C759"}, mint: { light: "#00C7BE"}, teal: { light: "#30B0C7"}, cyan: { light: "#32ADE6"}, blue: { light: "#007AFF"}, indigo: { light: "#5856D6"}, purple: { light: "#AF52DE"}, pink: { light: "#FF2D55"}, brown: { light: "#A2845E"} }; 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)" } }; // 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 emphasized(style) { return withWeight(style, "600"); } ({ 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 = { // 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 } }; // src/design-system/tokens/radius.ts var radius = { none: 0, xs: 4, sm: 8, md: 12, lg: 16, xl: 20, xxl: 24, full: 9999 }; ({ /** * 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 }); // src/design-system/tokens/shadows.ts var shadowColors = { light: "rgba(0, 0, 0, 1)"}; 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 } }; ({ /** * 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 }); // 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 }; ({ /** * 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 } }); ({ /** * 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 } }); // 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 }; // 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 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 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; } // src/design-system/components/primitives/Box.tsx var Box = React19.forwardRef(function Box2({ children, style, backgroundColor, padding, paddingHorizontal, paddingVertical, paddingTop, paddingRight, paddingBottom, paddingLeft, margin, marginHorizontal, marginVertical, marginTop, marginRight, marginBottom, marginLeft, borderRadius, flex, flexDirection, alignItems, justifyContent, gap, width, height, minHeight, maxWidth, shadow, borderWidth, borderColor, position, overflow, visible = true, ...viewProps }, ref) { const { theme } = useTheme(); const resolveSpacing = (value) => { if (value === void 0) return void 0; if (typeof value === "number") return value; return theme.spacing[value]; }; const resolveRadius = (value) => { if (value === void 0) return void 0; if (typeof value === "number") return value; return theme.radius[value]; }; const resolveBackgroundColor = (color) => { if (!color) return void 0; if (color.includes(".")) { const [group, key] = color.split("."); const colorGroup = theme.colors[group]; if (colorGroup && typeof colorGroup === "object" && key in colorGroup) { return colorGroup[key]; } } return color; }; const computedStyle = React19.useMemo(() => { const styles3 = {}; const bgColor = resolveBackgroundColor(backgroundColor); if (bgColor) styles3.backgroundColor = bgColor; if (padding !== void 0) styles3.padding = resolveSpacing(padding); if (paddingHorizontal !== void 0) styles3.paddingHorizontal = resolveSpacing(paddingHorizontal); if (paddingVertical !== void 0) styles3.paddingVertical = resolveSpacing(paddingVertical); if (paddingTop !== void 0) styles3.paddingTop = resolveSpacing(paddingTop); if (paddingRight !== void 0) styles3.paddingRight = resolveSpacing(paddingRight); if (paddingBottom !== void 0) styles3.paddingBottom = resolveSpacing(paddingBottom); if (paddingLeft !== void 0) styles3.paddingLeft = resolveSpacing(paddingLeft); if (margin !== void 0) styles3.margin = resolveSpacing(margin); if (marginHorizontal !== void 0) styles3.marginHorizontal = resolveSpacing(marginHorizontal); if (marginVertical !== void 0) styles3.marginVertical = resolveSpacing(marginVertical); if (marginTop !== void 0) styles3.marginTop = resolveSpacing(marginTop); if (marginRight !== void 0) styles3.marginRight = resolveSpacing(marginRight); if (marginBottom !== void 0) styles3.marginBottom = resolveSpacing(marginBottom); if (marginLeft !== void 0) styles3.marginLeft = resolveSpacing(marginLeft); if (borderRadius !== void 0) styles3.borderRadius = resolveRadius(borderRadius); if (flex !== void 0) styles3.flex = flex; if (flexDirection) styles3.flexDirection = flexDirection; if (alignItems) styles3.alignItems = alignItems; if (justifyContent) styles3.justifyContent = justifyContent; if (gap !== void 0) styles3.gap = resolveSpacing(gap); if (width !== void 0) styles3.width = width; if (height !== void 0) styles3.height = height; if (minHeight !== void 0) styles3.minHeight = minHeight; if (maxWidth !== void 0) styles3.maxWidth = maxWidth; if (shadow && shadow !== "none") { const shadowStyle = theme.shadows[shadow]; Object.assign(styles3, shadowStyle); } if (borderWidth !== void 0) styles3.borderWidth = borderWidth; if (borderColor) { styles3.borderColor = resolveBackgroundColor(borderColor) || borderColor; } if (position) styles3.position = position; if (overflow) styles3.overflow = overflow; return styles3; }, [ theme, backgroundColor, padding, paddingHorizontal, paddingVertical, paddingTop, paddingRight, paddingBottom, paddingLeft, margin, marginHorizontal, marginVertical, marginTop, marginRight, marginBottom, marginLeft, borderRadius, flex, flexDirection, alignItems, justifyContent, gap, width, height, minHeight, maxWidth, shadow, borderWidth, borderColor, position, overflow ]); if (!visible) { return null; } return /* @__PURE__ */ React19__default.default.createElement( reactNative.View, { ref, style: [computedStyle, style], ...viewProps }, children ); }); // src/design-system/components/Icon/iconMap.ts var iconMap = { // =========================================================================== // Actions // =========================================================================== "checkmark": { ios: "checkmark", android: "check" }, "close": { ios: "xmark", android: "close" }, "plus": { ios: "plus", android: "add" }, "minus": { ios: "minus", android: "remove" }, "search": { ios: "magnifyingglass", android: "search" }, "edit": { ios: "pencil", android: "edit" }, "delete": { ios: "trash", android: "delete" }, "share": { ios: "square.and.arrow.up", android: "share" }, "copy": { ios: "doc.on.doc", android: "content-copy" }, "paste": { ios: "doc.on.clipboard", android: "content-paste" }, "refresh": { ios: "arrow.clockwise", android: "refresh" }, "download": { ios: "arrow.down.circle", android: "file-download" }, "upload": { ios: "arrow.up.circle", android: "file-upload" }, "send": { ios: "paperplane.fill", android: "send" }, "reply": { ios: "arrowshape.turn.up.left", android: "reply" }, "forward": { ios: "arrowshape.turn.up.right", android: "forward" }, // =========================================================================== // Navigation // =========================================================================== "back": { ios: "chevron.left", android: "arrow-back" }, "forward-nav": { ios: "chevron.right", android: "arrow-forward" }, "up": { ios: "chevron.up", android: "keyboard-arrow-up" }, "down": { ios: "chevron.down", android: "keyboard-arrow-down" }, "menu": { ios: "line.3.horizontal", android: "menu" }, "more-horizontal": { ios: "ellipsis", android: "more-horiz" }, "more-vertical": { ios: "ellipsis", android: "more-vert" }, "home": { ios: "house", android: "home" }, "settings": { ios: "gearshape", android: "settings" }, // =========================================================================== // Communication // =========================================================================== "chat": { ios: "bubble.left", android: "chat-bubble-outline" }, "chat-fill": { ios: "bubble.left.fill", android: "chat-bubble" }, "phone": { ios: "phone", android: "phone" }, "video": { ios: "video", android: "videocam" }, "mic": { ios: "mic.fill", android: "mic" }, "mic-off": { ios: "mic.slash.fill", android: "mic-off" }, "speaker": { ios: "speaker.wave.2.fill", android: "volume-up" }, "speaker-off": { ios: "speaker.slash.fill", android: "volume-off" }, // =========================================================================== // Media // =========================================================================== "camera": { ios: "camera.fill", android: "camera-alt" }, "image": { ios: "photo", android: "image" }, "play": { ios: "play.fill", android: "play-arrow" }, "pause": { ios: "pause.fill", android: "pause" }, "stop": { ios: "stop.fill", android: "stop" }, "volume": { ios: "speaker.wave.2.fill", android: "volume-up" }, "volume-off": { ios: "speaker.slash.fill", android: "volume-off" }, // =========================================================================== // People // =========================================================================== "person": { ios: "person", android: "person-outline" }, "person-fill": { ios: "person.fill", android: "person" }, "people": { ios: "person.2", android: "people-outline" }, "people-fill": { ios: "person.2.fill", android: "people" }, // =========================================================================== // Status // =========================================================================== "info": { ios: "info.circle", android: "info-outline" }, "warning": { ios: "exclamationmark.triangle", android: "warning" }, "error": { ios: "exclamationmark.circle", android: "error-outline" }, "success": { ios: "checkmark.circle", android: "check-circle-outline" }, "help": { ios: "questionmark.circle", android: "help-outline" }, // =========================================================================== // Objects // =========================================================================== "attach": { ios: "paperclip", android: "attach-file" }, "link": { ios: "link", android: "link" }, "location": { ios: "location.fill", android: "location-on" }, "calendar": { ios: "calendar", android: "event" }, "clock": { ios: "clock", android: "schedule" }, "heart": { ios: "heart", android: "favorite-outline" }, "heart-fill": { ios: "heart.fill", android: "favorite" }, "star": { ios: "star", android: "star-outline" }, "star-fill": { ios: "star.fill", android: "star" }, "bookmark": { ios: "bookmark", android: "bookmark-outline" }, "bookmark-fill": { ios: "bookmark.fill", android: "bookmark" }, "bell": { ios: "bell", android: "notifications-none" }, "bell-fill": { ios: "bell.fill", android: "notifications" }, "lock": { ios: "lock.fill", android: "lock" }, "unlock": { ios: "lock.open.fill", android: "lock-open" }, "eye": { ios: "eye", android: "visibility" }, "eye-off": { ios: "eye.slash", android: "visibility-off" }, "document": { ios: "doc", android: "description" }, "folder": { ios: "folder", android: "folder" }, // =========================================================================== // Arrows & Indicators // =========================================================================== "arrow-up": { ios: "arrow.up", android: "arrow-upward" }, "arrow-down": { ios: "arrow.down", android: "arrow-downward" }, "arrow-left": { ios: "arrow.left", android: "arrow-back" }, "arrow-right": { ios: "arrow.right", android: "arrow-forward" }, "external-link": { ios: "arrow.up.right.square", android: "open-in-new" }, // =========================================================================== // Read Receipts (Chat specific) // =========================================================================== "check-single": { ios: "checkmark", android: "check" }, "check-double": { ios: "checkmark", // Will render two checkmarks android: "done-all" }, "clock-pending": { ios: "clock", android: "schedule" } }; function getPlatformIconName(commonName, platform) { const mapping = iconMap[commonName]; if (!mapping) { return commonName; } if (platform === "ios") { return mapping.ios; } return mapping.web || mapping.android; } function isCommonIconName(name) { return name in iconMap; } function getIconSize(size) { if (typeof size === "number") { return size; } const sizes2 = { xs: 12, sm: 16, md: 20, lg: 24, xl: 32 }; return sizes2[size]; } function getIconColor(color, theme) { const semanticColors = { primary: theme.colors.label.primary, secondary: theme.colors.label.secondary, tertiary: theme.colors.label.tertiary, tint: theme.colors.interactive.tint, error: theme.colors.semantic.error, success: theme.colors.semantic.success, warning: theme.colors.semantic.warning, info: theme.colors.semantic.info }; if (color in semanticColors) { return semanticColors[color]; } return color; } function createIconStyles(theme) { return reactNative.StyleSheet.create({ container: { alignItems: "center", justifyContent: "center" } }); } // src/design-system/components/Icon/Icon.tsx function Icon({ // Content name, children, // Appearance size = "md", color = "primary", weight = "regular", renderingMode = "monochrome", // Animation (iOS 17+) symbolEffect, symbolEffectActive = true, // Accessibility testID, accessibilityLabel, accessibilityHidden = true, // Style style }) { const { theme } = useTheme(); const resolvedSize = React19.useMemo(() => getIconSize(size), [size]); const resolvedColor = React19.useMemo( () => getIconColor(color, theme), [color, theme] ); const baseStyles = React19.useMemo(() => createIconStyles(), [theme]); const platformIconName = React19.useMemo(() => { if (!name) return null; const platform = reactNative.Platform.OS === "ios" ? "ios" : "android"; if (isCommonIconName(name)) { return getPlatformIconName(name, platform); } return name; }, [name]); const accessibilityProps = React19.useMemo( () => ({ accessible: !accessibilityHidden, accessibilityRole: "image", accessibilityLabel, accessibilityElementsHidden: accessibilityHidden, importantForAccessibility: accessibilityHidden ? "no-hide-descendants" : "auto" }), [accessibilityLabel, accessibilityHidden] ); if (children) { return /* @__PURE__ */ React19__default.default.createElement( reactNative.View, { style: [ baseStyles.container, { width: resolvedSize, height: resolvedSize }, style ], testID, ...accessibilityProps }, children ); } if (!platformIconName) { return null; } return /* @__PURE__ */ React19__default.default.createElement( reactNative.View, { style: [baseStyles.container, style], testID, ...accessibilityProps }, /* @__PURE__ */ React19__default.default.createElement( MaterialIcons__default.default, { name: platformIconName, size: resolvedSize, color: resolvedColor } ) ); } Icon.displayName = "Icon"; var AnimatedPressable = Animated6__default.default.createAnimatedComponent(reactNative.Pressable); function Pressable({ // Content children, // Events onPress, onLongPress, onPressIn, onPressOut, onLayout, delayLongPress = 500, delayPressIn = 0, delayPressOut = 0, // State disabled = false, // Visual Feedback pressAnimation = "opacity", pressedOpacity = 0.7, pressedScale = 0.98, disabledOpacity = 0.5, enableRipple = reactNative.Platform.OS === "android", rippleColor, rippleBorderless = false, // Haptics hapticFeedback = "light", hapticOnPressIn = true, // Hit Area hitSlop, pressRetentionOffset, // Accessibility testID, accessibilityLabel, accessibilityHint, accessibilityRole = "button", accessibilityState, // Styling style, pressedStyle, disabledStyle }) { const { theme } = useTheme(); const pressed = Animated6.useSharedValue(0); const triggerHaptic = React19.useCallback(() => { if (hapticFeedback === "none" || disabled) return; }, [hapticFeedback, disabled]); const handlePressIn = React19.useCallback( (event) => { pressed.value = Animated6.withTiming(1, { duration: 100 }); if (hapticOnPressIn) { triggerHaptic(); } onPressIn?.(event); }, [onPressIn, pressed, hapticOnPressIn, triggerHaptic] ); const handlePressOut = React19.useCallback( (event) => { pressed.value = Animated6.withTiming(0, { duration: 150 }); onPressOut?.(event); }, [onPressOut, pressed] ); const handlePress = React19.useCallback( (event) => { if (disabled) return; if (!hapticOnPressIn) { triggerHaptic(); } onPress?.(event); }, [onPress, disabled, hapticOnPressIn, triggerHaptic] ); const animatedStyle = Animated6.useAnimatedStyle(() => { const animationStyles = {}; if (pressAnimation === "opacity" || pressAnimation === "both") { animationStyles.opacity = Animated6.interpolate( pressed.value, [0, 1], [1, pressedOpacity] ); } if (pressAnimation === "scale" || pressAnimation === "both") { animationStyles.transform = [ { scale: Animated6.interpolate(pressed.value, [0, 1], [1, pressedScale]) } ]; } return animationStyles; }, [pressAnimation, pressedOpacity, pressedScale]); const computedAccessibilityState = React19.useMemo( () => ({ disabled, ...accessibilityState }), [disabled, accessibilityState] ); const androidRipple = React19.useMemo(() => { if (!enableRipple || reactNative.Platform.OS !== "android") { return void 0; } return { color: rippleColor || theme.colors.interactive.tint, borderless: rippleBorderless }; }, [enableRipple, rippleColor, rippleBorderless, theme.colors.interactive.tint]); const resolveStyle = React19.useCallback( ({ pressed: isPressed }) => { const baseStyle = typeof style === "function" ? style({ pressed: isPressed }) : style; return [ baseStyle, disabled && { opacity: disabledOpacity }, disabled && disabledStyle, isPressed && pressedStyle ]; }, [style, disabled, disabledOpacity, disabledStyle, pressedStyle] ); const renderChildren = React19.useCallback( ({ pressed: isPressed }) => { if (typeof children === "function") { return children({ pressed: isPressed }); } return children; }, [children] ); return /* @__PURE__ */ React19__default.default.createElement( AnimatedPressable, { testID, onPress: handlePress, onLongPress: disabled ? void 0 : onLongPress, onPressIn: handlePressIn, onPressOut: handlePressOut, onLayout, delayLongPress, disabled, hitSlop, android_ripple: androidRipple, accessibilityLabel, accessibilityHint, accessibilityRole, accessibilityState: computedAccessibilityState, style: [animatedStyle, resolveStyle] }, renderChildren ); } Pressable.displayName = "Pressable"; function createDividerStyles(theme) { return reactNative.StyleSheet.create({ // Container for horizontal divider containerHorizontal: { flexDirection: "row", alignItems: "center", width: "100%" }, // Container for vertical divider containerVertical: { flexDirection: "column", alignItems: "center", alignSelf: "stretch" }, // Base line style line: { backgroundColor: theme.colors.separator.nonOpaque }, // Horizontal line lineHorizontal: { height: reactNative.StyleSheet.hairlineWidth, flex: 1 }, // Vertical line lineVertical: { width: reactNative.StyleSheet.hairlineWidth, flex: 1 }, // Label container (for withLabel variant) labelContainer: { paddingHorizontal: theme.spacing.md }, // Label text labelText: { ...theme.typography.caption1, color: theme.colors.label.tertiary, textTransform: "uppercase", letterSpacing: 0.5 } }); } function getDynamicDividerStyles(variant, orientation, insetLeft, insetRight, spacing2, thickness, color, theme) { const styles3 = { container: {}, line: { backgroundColor: color || theme.colors.separator.nonOpaque } }; if (orientation === "horizontal") { styles3.line.height = thickness; } else { styles3.line.width = thickness; } if (spacing2 > 0) { if (orientation === "horizontal") { styles3.container.marginVertical = spacing2; } else { styles3.container.marginHorizontal = spacing2; } } if (orientation === "horizontal") { switch (variant) { case "inset": styles3.container.paddingLeft = insetLeft; break; case "middle": styles3.container.paddingLeft = insetLeft; styles3.container.paddingRight = insetRight; break; } } return styles3; } // src/design-system/components/Divider/Divider.tsx function Divider({ // Variant & Orientation variant = "full", orientation = "horizontal", // Content label, // Appearance thickness = reactNative.StyleSheet.hairlineWidth, color, insetLeft = 16, insetRight = 16, spacing: spacing2 = 0, // Accessibility testID, decorative = true, // Styling style, labelStyle, lineStyle }) { const { theme } = useTheme(); const baseStyles = React19.useMemo(() => createDividerStyles(theme), [theme]); const dynamicStyles = React19.useMemo( () => getDynamicDividerStyles( variant, orientation, insetLeft, insetRight, spacing2, thickness, color, theme ), [variant, orientation, insetLeft, insetRight, spacing2, thickness, color, theme] ); const accessibilityProps = React19.useMemo( () => ({ accessible: !decorative, accessibilityRole: decorative ? void 0 : "none", accessibilityElementsHidden: decorative, importantForAccessibility: decorative ? "no-hide-descendants" : "auto" }), [decorative] ); if (variant === "withLabel" && label && orientation === "horizontal") { return /* @__PURE__ */ React19__default.default.createElement( reactNative.View, { style: [ baseStyles.containerHorizontal, dynamicStyles.container, style ], testID, ...accessibilityProps }, /* @__PURE__ */ React19__default.default.createElement( reactNative.View, { style: [ baseStyles.line, baseStyles.lineHorizontal, dynamicStyles.line, lineStyle ] } ), /* @__PURE__ */ React19__default.default.createElement(reactNative.View, { style: baseStyles.labelContainer }, /* @__PURE__ */ React19__default.default.createElement(reactNative.Text, { style: [baseStyles.labelText, labelStyle] }, label)), /* @__PURE__ */ React19__default.default.createElement( reactNative.View, { style: [ baseStyles.line, baseStyles.lineHorizontal, dynamicStyles.line, lineStyle ] } ) ); } if (orientation === "vertical") { return /* @__PURE__ */ React19__default.default.createElement( reactNative.View, { style: [ baseStyles.containerVertical, dynamicStyles.container, style ], testID, ...accessibilityProps }, /* @__PURE__ */ React19__default.default.createElement( reactNative.View, { style: [ baseStyles.line, baseStyles.lineVertical, dynamicStyles.line, lineStyle ] } ) ); } return /* @__PURE__ */ React19__default.default.createElement( reactNative.View, { style: [ baseStyles.containerHorizontal, dynamicStyles.container, style ], testID, ...accessibilityProps }, /* @__PURE__ */ React19__default.default.createElement( reactNative.View, { style: [ baseStyles.line, baseStyles.lineHorizontal, dynamicStyles.line, lineStyle ] } ) ); } Divider.displayName = "Divider"; 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; } // src/design-system/components/Spinner/Spinner.tsx function getSpinnerSize(size) { if (typeof size === "number") { return size; } const sizes2 = { xs: 16, sm: 20, md: 28, lg: 40 }; return sizes2[size]; } function Spinner({ // Appearance size = "md", color = "tint", // Label label, labelPosition = "bottom", // Behavior animating = true, hidesWhenStopped = true, // Accessibility testID, accessibilityLabel = "Loading", // Styling style, labelStyle }) { const { theme } = useTheme(); const reducedMotion = useReducedMotion(); const resolvedSize = React19.useMemo(() => getSpinnerSize(size), [size]); const resolvedColor = React19.useMemo(() => { const colorMap = { primary: theme.colors.label.primary, secondary: theme.colors.label.secondary, tint: theme.colors.interactive.tint, onPrimary: theme.colors.background.primary, onSurface: theme.colors.label.primary }; return colorMap[color] || color; }, [color, theme]); const activityIndicatorSize = React19.useMemo(() => { return resolvedSize <= 24 ? "small" : "large"; }, [resolvedSize]); if (!animating && hidesWhenStopped) { return null; } const containerStyle = React19.useMemo( () => [ styles.container, labelPosition === "right" ? styles.containerRow : styles.containerColumn, style ], [labelPosition, style] ); const labelTextStyle = React19.useMemo( () => [ styles.label, { ...theme.typography.subheadline, color: theme.colors.label.secondary }, labelPosition === "right" ? styles.labelRight : styles.labelBottom, labelStyle ], [theme, labelPosition, labelStyle] ); return /* @__PURE__ */ React19__default.default.createElement( reactNative.View, { style: containerStyle, testID, accessible: true, accessibilityLabel, accessibilityRole: "progressbar", accessibilityState: { busy: animating } }, /* @__PURE__ */ React19__default.default.createElement( reactNative.ActivityIndicator, { animating: animating && !reducedMotion, size: activityIndicatorSize, color: resolvedColor, style: { width: resolvedSize, height: resolvedSize }, testID: testID ? `${testID}-indicator` : void 0 } ), label && /* @__PURE__ */ React19__default.default.createElement(reactNative.Text, { style: labelTextStyle }, label) ); } var styles = reactNative.StyleSheet.create({ container: { alignItems: "center", justifyContent: "center" }, containerColumn: { flexDirection: "column" }, containerRow: { flexDirection: "row" }, label: { textAlign: "center" }, labelBottom: { marginTop: 8 }, labelRight: { marginLeft: 8 } }); Spinner.displayName = "Spinner"; function createToastStyles(theme) { return reactNative.StyleSheet.create({ container: { flexDirection: "row", alignItems: "center", paddingVertical: theme.spacing.md, paddingHorizontal: theme.spacing.lg, borderRadius: theme.radius.md, marginHorizontal: theme.spacing.lg, minHeight: 48, ...theme.shadows.lg }, content: { flex: 1, marginLeft: theme.spacing.sm }, title: { ...theme.typography.subheadline, fontWeight: "600", marginBottom: 2 }, message: { ...theme.typography.subheadline }, iconContainer: { width: 24, height: 24, alignItems: "center", justifyContent: "center" }, actionButton: { marginLeft: theme.spacing.md, paddingVertical: theme.spacing.xs, paddingHorizontal: theme.spacing.sm }, actionText: { ...theme.typography.subheadline, fontWeight: "600" }, closeButton: { marginLeft: theme.spacing.sm, padding: theme.spacing.xs } }); } function getVariantStyles(variant, theme) { const styl