/ templates / ios_frame.jsx
ios_frame.jsx
  1  /**
  2   * IosFrame — iPhone device frame
  3   *
  4   * Based on iPhone 15 Pro (393×852 logical pixels)
  5   * Includes: Dynamic Island + status bar (time/signal/battery) + Home Indicator + rounded corners
  6   *
  7   * Usage:
  8   *   <IosFrame time="9:41" battery={85}>
  9   *     <YourAppContent />
 10   *   </IosFrame>
 11   *
 12   * Customization:
 13   *   <IosFrame width={390} height={844} darkMode showKeyboard>
 14   *     ...
 15   *   </IosFrame>
 16   */
 17  
 18  const iosFrameStyles = {
 19    wrapper: {
 20      display: 'inline-block',
 21      padding: 12,
 22      background: '#000',
 23      borderRadius: 60,
 24      boxShadow: '0 0 0 2px #1f2937, 0 20px 60px rgba(0,0,0,0.3)',
 25      position: 'relative',
 26    },
 27    screen: {
 28      position: 'relative',
 29      borderRadius: 48,
 30      overflow: 'hidden',
 31      background: '#fff',
 32    },
 33    statusBar: {
 34      position: 'absolute',
 35      top: 0,
 36      left: 0,
 37      right: 0,
 38      height: 54,
 39      display: 'flex',
 40      alignItems: 'center',
 41      justifyContent: 'space-between',
 42      padding: '0 32px 0 32px',
 43      fontSize: 16,
 44      fontWeight: 600,
 45      fontFamily: '-apple-system, "SF Pro Text", sans-serif',
 46      zIndex: 20,
 47      pointerEvents: 'none',
 48    },
 49    dynamicIsland: {
 50      position: 'absolute',
 51      top: 12,
 52      left: '50%',
 53      transform: 'translateX(-50%)',
 54      width: 124,
 55      height: 36,
 56      background: '#000',
 57      borderRadius: 999,
 58      zIndex: 30,
 59    },
 60    statusIcons: {
 61      display: 'flex',
 62      alignItems: 'center',
 63      gap: 6,
 64    },
 65    signalIcon: {
 66      display: 'flex',
 67      alignItems: 'flex-end',
 68      gap: 2,
 69      height: 12,
 70    },
 71    signalBar: {
 72      width: 3,
 73      background: 'currentColor',
 74      borderRadius: 1,
 75    },
 76    wifiIcon: {
 77      width: 16,
 78      height: 12,
 79      position: 'relative',
 80    },
 81    batteryIcon: {
 82      width: 26,
 83      height: 12,
 84      border: '1.5px solid currentColor',
 85      borderRadius: 3,
 86      padding: 1,
 87      position: 'relative',
 88      opacity: 0.8,
 89    },
 90    batteryCap: {
 91      position: 'absolute',
 92      top: 3,
 93      right: -3,
 94      width: 2,
 95      height: 6,
 96      background: 'currentColor',
 97      borderRadius: '0 1px 1px 0',
 98    },
 99    content: {
100      position: 'absolute',
101      top: 54,
102      left: 0,
103      right: 0,
104      bottom: 34,
105      overflow: 'auto',
106    },
107    homeIndicator: {
108      position: 'absolute',
109      bottom: 10,
110      left: '50%',
111      transform: 'translateX(-50%)',
112      width: 140,
113      height: 5,
114      background: 'rgba(0,0,0,0.3)',
115      borderRadius: 999,
116      zIndex: 10,
117    },
118    homeIndicatorDark: {
119      background: 'rgba(255,255,255,0.5)',
120    },
121  };
122  
123  function IosFrame({
124    children,
125    width = 393,
126    height = 852,
127    time = '9:41',
128    battery = 100,
129    darkMode = false,
130    showStatusBar = true,
131    showDynamicIsland = true,
132    showHomeIndicator = true,
133  }) {
134    const textColor = darkMode ? '#fff' : '#000';
135  
136    return (
137      <div style={iosFrameStyles.wrapper}>
138        <div style={{
139          ...iosFrameStyles.screen,
140          width,
141          height,
142          background: darkMode ? '#000' : '#fff',
143        }}>
144          {showStatusBar && (
145            <div style={{ ...iosFrameStyles.statusBar, color: textColor }}>
146              <span>{time}</span>
147              <div style={iosFrameStyles.statusIcons}>
148                <div style={iosFrameStyles.signalIcon}>
149                  <div style={{ ...iosFrameStyles.signalBar, height: 4 }} />
150                  <div style={{ ...iosFrameStyles.signalBar, height: 6 }} />
151                  <div style={{ ...iosFrameStyles.signalBar, height: 9 }} />
152                  <div style={{ ...iosFrameStyles.signalBar, height: 11 }} />
153                </div>
154                <svg width="16" height="12" viewBox="0 0 16 12" fill="none" style={{ color: textColor }}>
155                  <path d="M8 11.5a1 1 0 100-2 1 1 0 000 2z" fill="currentColor" />
156                  <path d="M3 7.5a7 7 0 0110 0" stroke="currentColor" strokeWidth="1.3" fill="none" strokeLinecap="round" />
157                  <path d="M1 4.5a11 11 0 0114 0" stroke="currentColor" strokeWidth="1.3" fill="none" strokeLinecap="round" opacity="0.7" />
158                </svg>
159                <div style={iosFrameStyles.batteryIcon}>
160                  <div style={{
161                    width: `${battery}%`,
162                    height: '100%',
163                    background: 'currentColor',
164                    borderRadius: 1,
165                    opacity: 0.9,
166                  }} />
167                  <div style={iosFrameStyles.batteryCap} />
168                </div>
169              </div>
170            </div>
171          )}
172  
173          {showDynamicIsland && <div style={iosFrameStyles.dynamicIsland} />}
174  
175          <div style={iosFrameStyles.content}>
176            {children}
177          </div>
178  
179          {showHomeIndicator && (
180            <div style={{
181              ...iosFrameStyles.homeIndicator,
182              ...(darkMode ? iosFrameStyles.homeIndicatorDark : {}),
183            }} />
184          )}
185        </div>
186      </div>
187    );
188  }
189  
190  if (typeof window !== 'undefined') {
191    window.IosFrame = IosFrame;
192  }