> ## Documentation Index
> Fetch the complete documentation index at: https://docs.craveup.com/llms.txt
> Use this file to discover all available pages before exploring further.

# <TipSelector />

> Preset and custom tip amount picker for the checkout flow.

export const PreviewWithDevices = ({iframeSrc, iframeHeight = "400", previewId}) => {
  const [device, setDevice] = useState("desktop");
  const requestedHeight = Number.parseInt(iframeHeight, 10) || 400;
  const isDesktop = device === "desktop";
  const brand = "Sakura Asian Grill";
  const assets = {
    hero: "/images/previews/sakura-hero-chicken.png",
    chicken: "/images/previews/sakura-chicken-teriyaki.png",
    beef: "/images/previews/sakura-beef-teriyaki.png",
    shrimp: "/images/previews/sakura-shrimp-teriyaki.png",
    roll: "/images/previews/sakura-california-roll.png",
    dumplings: "/images/previews/sakura-dumplings.png"
  };
  const previewStyles = `
    @import url("https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;600;700;800&family=Barlow+Condensed:wght@600;700;800&display=swap");
    :root {
      color-scheme: light;
      --red: #e1121c;
      --red-dark: #b20d15;
      --ink: #171717;
      --coal: #0d0d0d;
      --cream: #f6f1e9;
      --paper: #fffaf2;
      --line: #e7e0d4;
      --muted: #7f7467;
      --gold: #f2a93b;
      font-family: "Barlow", Inter, ui-sans-serif, system-ui, sans-serif;
    }
    * { box-sizing: border-box; }
    body { min-width: 280px; margin: 0; color: var(--ink); background: var(--cream); }
    button, input, textarea { font: inherit; }
    button { border: 0; cursor: default; }
    .root { min-height: 100vh; padding: 18px; background: var(--cream); }
    .shell { width: min(100%, 920px); margin: 0 auto; }
    .condensed { font-family: "Barlow Condensed", "Arial Narrow", sans-serif; letter-spacing: .03em; text-transform: uppercase; }
    .app-frame { width: min(100%, 390px); margin: 0 auto; border: 1px solid #1d1d1d; border-radius: 34px; overflow: hidden; background: #fff; box-shadow: 0 30px 80px -46px rgba(0,0,0,.75); }
    .desktop-preview .root { padding: 16px; }
    .desktop-preview .app-frame { width: min(100%, 860px); border-color: var(--line); border-radius: 18px; box-shadow: 0 24px 70px -54px rgba(0,0,0,.65); }
    .screen { min-height: 420px; background: var(--cream); overflow: hidden; }
    .desktop-preview .screen { min-height: 300px; background: var(--paper); }
    .status { display: flex; justify-content: space-between; align-items: center; padding: 11px 22px 7px; color: inherit; font-size: 13px; font-weight: 800; }
    .desktop-preview .status { display: none; }
    .top-red { color: #fff; background: var(--red); }
    .brand-bar { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 8px 18px 16px; color: #fff; background: var(--red); }
    .brand-name { max-width: 240px; font-family: "Barlow Condensed", sans-serif; font-size: 27px; line-height: .9; font-weight: 800; letter-spacing: .02em; text-transform: uppercase; }
    .desktop-preview .brand-bar { padding: 20px 26px; }
    .desktop-preview .brand-name { max-width: none; font-size: 34px; }
    .location { display: block; margin-top: 5px; font-size: 12px; font-weight: 700; opacity: .95; text-transform: none; letter-spacing: 0; }
    .desktop-preview .location { display: inline-block; margin: 0 0 0 18px; font-size: 14px; vertical-align: middle; }
    .round-icon { display: inline-flex; align-items: center; justify-content: center; width: 38px; height: 38px; border-radius: 50%; background: rgba(255,255,255,.18); color: currentColor; font-weight: 800; }
    .search-pill { display: inline-flex; align-items: center; justify-content: center; min-width: 70px; min-height: 34px; padding: 0 12px; border-radius: 999px; background: rgba(255,255,255,.18); color: currentColor; font-size: 13px; font-weight: 800; }
    .desktop-preview .search-pill { min-width: 180px; min-height: 42px; justify-content: flex-start; padding: 0 18px; font-size: 14px; }
    .hero-card { position: relative; height: 238px; margin: 14px 14px 0; overflow: hidden; border-radius: 18px; background: #101010; }
    .hero-card img { position: absolute; right: -44px; bottom: -16px; width: 300px; max-width: none; filter: drop-shadow(0 20px 24px rgba(0,0,0,.5)); }
    .hero-copy { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: space-between; padding: 20px 18px; }
    .hero-title { max-width: 190px; margin: 0; color: #fff; font-family: "Barlow Condensed", sans-serif; font-size: 38px; line-height: .92; font-weight: 800; letter-spacing: .01em; text-transform: uppercase; }
    .hero-title span { color: var(--red); }
    .cta { display: inline-flex; align-items: center; justify-content: center; min-height: 42px; padding: 0 18px; border-radius: 10px; color: #fff; background: var(--red); font-family: "Barlow Condensed", sans-serif; font-size: 16px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }
    .promo { display: flex; align-items: center; gap: 12px; margin: 13px 14px 0; padding: 12px; border: 1px solid var(--line); border-radius: 14px; background: #fff; }
    .desktop-preview .promo { margin: 22px 24px 0; padding: 16px; }
    .promo-badge { flex: none; display: grid; place-items: center; width: 48px; height: 48px; border-radius: 12px; color: #fff; background: var(--red); font-family: "Barlow Condensed", sans-serif; font-size: 21px; font-weight: 800; }
    .section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 17px 16px 7px; }
    .section-title { margin: 0; font-family: "Barlow Condensed", sans-serif; font-size: 23px; line-height: 1; font-weight: 800; letter-spacing: .02em; text-transform: uppercase; }
    .muted { color: var(--muted); }
    .small { font-size: 12px; font-weight: 600; }
    .tabs { display: flex; gap: 8px; overflow: hidden; padding: 0 14px 12px; border-bottom: 1px solid var(--line); background: #fff; }
    .tab { flex: none; min-height: 38px; padding: 0 16px; border: 1px solid var(--line); border-radius: 10px; color: var(--ink); background: #fff; font-family: "Barlow Condensed", sans-serif; font-size: 15px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }
    .tab.active { color: #fff; border-color: var(--red); background: var(--red); }
    .category-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; padding: 16px 14px 3px; }
    .category { display: grid; justify-items: center; gap: 8px; padding: 12px 8px; border: 1px solid var(--line); border-radius: 14px; background: #fff; }
    .category img { width: 54px; height: 54px; border-radius: 50%; object-fit: cover; background: #111; }
    .category span { font-family: "Barlow Condensed", sans-serif; font-size: 14px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }
    .card-row { display: grid; grid-template-columns: 82px minmax(0, 1fr) auto; gap: 13px; align-items: center; padding: 14px 0; border-bottom: 1px solid #f0eade; }
    .card-row img, .thumb { width: 82px; height: 82px; border-radius: 12px; object-fit: cover; background: #111; }
    .item-name { margin: 0; font-family: "Barlow Condensed", sans-serif; font-size: 18px; line-height: 1.03; font-weight: 800; letter-spacing: .01em; text-transform: uppercase; }
    .item-copy { margin: 3px 0 0; color: var(--muted); font-size: 12px; line-height: 1.35; font-weight: 500; }
    .price { color: var(--red); font-family: "Barlow Condensed", sans-serif; font-size: 18px; font-weight: 800; white-space: nowrap; }
    .add { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; border-radius: 9px; color: #fff; background: var(--red); font-size: 23px; font-weight: 700; }
    .featured { display: grid; grid-template-columns: repeat(3, minmax(150px, 1fr)); gap: 12px; padding: 4px 14px 16px; }
    .featured-card { overflow: hidden; border: 1px solid var(--line); border-radius: 14px; background: #fff; }
    .featured-card img { display: block; width: 100%; height: 105px; object-fit: cover; background: #111; }
    .featured-card .content { padding: 10px 11px 12px; }
    .panel { border: 1px solid var(--line); border-radius: 14px; background: #fff; }
    .panel.pad { padding: 14px; }
    .option { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 14px; border: 1.5px solid var(--line); border-radius: 10px; background: #fff; }
    .option.active { border-color: var(--red); }
    .menu-options { display: grid; gap: 10px; padding: 14px; }
    .desktop-preview .menu-options { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; padding: 20px 24px 24px; }
    .desktop-preview .option { min-height: 96px; align-items: flex-start; padding: 18px; border-radius: 14px; }
    .radio { width: 21px; height: 21px; border: 2px solid #c9c1b0; border-radius: 50%; }
    .active .radio { border-color: var(--red); box-shadow: inset 0 0 0 5px #fff; background: var(--red); }
    .check { width: 21px; height: 21px; border: 2px solid #c9c1b0; border-radius: 6px; }
    .bottom-bar { display: flex; gap: 12px; align-items: center; padding: 12px 14px 22px; border-top: 1px solid var(--line); background: #fff; }
    .stepper { display: inline-flex; align-items: center; gap: 13px; min-height: 44px; padding: 0 13px; border: 1.5px solid var(--ink); border-radius: 10px; font-family: "Barlow Condensed", sans-serif; font-size: 18px; font-weight: 800; }
    .cart-item { display: grid; grid-template-columns: 64px minmax(0, 1fr) auto; gap: 12px; align-items: center; padding: 11px; border: 1px solid var(--line); border-radius: 14px; background: #fff; }
    .cart-item img { width: 64px; height: 64px; border-radius: 10px; object-fit: cover; background: #111; }
    .summary { display: flex; justify-content: space-between; gap: 18px; padding: 5px 0; color: var(--muted); font-size: 13px; font-weight: 600; }
    .summary.total { color: var(--ink); font-family: "Barlow Condensed", sans-serif; font-size: 20px; font-weight: 800; letter-spacing: .03em; }
    .form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
    .full { grid-column: 1 / -1; }
    .input { min-height: 44px; width: 100%; padding: 0 12px; border: 1.5px solid #d8cfbf; border-radius: 10px; color: var(--ink); background: #fff; font-size: 14px; font-weight: 600; }
    textarea.input { min-height: 78px; padding-top: 12px; }
    .rewards { padding: 18px; border-radius: 16px; color: #fff; background: var(--ink); }
    .progress { height: 8px; overflow: hidden; border-radius: 999px; background: #333; }
    .progress span { display: block; width: 64%; height: 100%; border-radius: inherit; background: var(--gold); }
    .nav { display: grid; grid-template-columns: repeat(3, 1fr); padding: 10px 0 21px; color: #8a8a8a; background: var(--ink); }
    .nav span { display: grid; justify-items: center; gap: 4px; font-family: "Barlow Condensed", sans-serif; font-size: 11px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; }
    .nav .active { color: var(--red); }
    .desktop-preview .nav { display: none; }
    .success { display: grid; place-items: center; width: 54px; height: 54px; margin: 0 auto; border-radius: 50%; color: #fff; background: var(--red); font-family: "Barlow Condensed", sans-serif; font-size: 22px; font-weight: 800; }
    .mobile-component-canvas { display: grid; gap: 12px; padding: 14px; }
    .fallback { padding: 22px; text-align: center; }
    @media (max-width: 560px) {
      .root { padding: 14px; }
      .featured { grid-template-columns: 1fr; }
      .card-row { grid-template-columns: 66px minmax(0, 1fr); }
      .card-row img, .thumb { width: 66px; height: 66px; }
      .card-row .add, .card-row .price { grid-column: 2; justify-self: start; }
      .form-grid { grid-template-columns: 1fr; }
    }
  `;
  const appShell = (body, nav = true) => `
    <div class="app-frame">
      <div class="screen">
        ${body}
        ${nav ? `<div class="nav"><span class="active">Menu</span><span>Rewards</span><span>Account</span></div>` : ""}
      </div>
    </div>
  `;
  const header = `
    <div class="top-red">
      <div class="status"><span>9:41</span><span>LTE BAT</span></div>
      <div class="brand-bar">
        <div><span class="brand-name">${brand}</span><span class="location">Pickup at Westfield Mall</span></div>
        <span class="search-pill">Search</span>
      </div>
    </div>
  `;
  const menuList = `
    <div style="padding: 0 16px 12px; background:#fff">
      <div class="card-row"><img src="${assets.chicken}" alt=""><div><h2 class="item-name">Chicken Teriyaki</h2><p class="item-copy">Cooked to order, finished with signature teriyaki sauce.</p><span class="price">$8.99</span></div><span class="add">+</span></div>
      <div class="card-row"><img src="${assets.beef}" alt=""><div><h2 class="item-name">Beef Teriyaki</h2><p class="item-copy">Griddled beef over rice with crisp vegetables.</p><span class="price">$9.99</span></div><span class="add">+</span></div>
      <div class="card-row"><img src="${assets.shrimp}" alt=""><div><h2 class="item-name">Shrimp Teriyaki</h2><p class="item-copy">Shrimp, steamed rice, vegetables, and sweet glaze.</p><span class="price">$10.49</span></div><span class="add">+</span></div>
    </div>
  `;
  const previewMarkup = {
    "category-tabs": `
      ${appShell(`
        <div class="status"><span>9:41</span><span>LTE BAT</span></div>
        <div class="section-head"><h2 class="section-title">Menu</h2><span class="small muted">Westfield Mall</span></div>
        <div class="tabs" aria-label="Menu categories"><button class="tab active">Teriyaki</button><button class="tab">Sushi</button><button class="tab">Sides</button><button class="tab">Drinks</button></div>
        ${menuList}
      `)}
    `,
    "menu-switcher": `
      ${appShell(`
        ${header}
        <div class="promo"><span class="promo-badge">$5</span><div><h2 class="item-name">Choose a menu</h2><p class="item-copy">Pickup menus update by daypart and location.</p></div></div>
        <div class="menu-options">
          <div class="option active"><span><b>Pickup</b><br><span class="small muted">Ready in 12-18 min</span></span><span class="radio"></span></div>
          <div class="option"><span><b>Delivery</b><br><span class="small muted">Available nearby</span></span><span class="price">$4.99</span></div>
          <div class="option"><span><b>Catering</b><br><span class="small muted">Platters and party trays</span></span><span class="small muted">Preorder</span></div>
        </div>
      `)}
    `,
    item: `
      <article class="panel card-row" style="padding:14px">
        <img src="${assets.chicken}" alt="">
        <div><h2 class="item-name">Chicken Teriyaki</h2><p class="item-copy">Tender chicken, rice, vegetables, teriyaki sauce.</p><span class="small muted">640 cal</span></div>
        <span class="price">$8.99</span>
      </article>
    `,
    "item-carousel": `
      <div class="featured">
        <article class="featured-card"><img src="${assets.chicken}" alt=""><div class="content"><h2 class="item-name">Chicken Teriyaki</h2><p class="small muted">$8.99 - 640 cal</p></div></article>
        <article class="featured-card"><img src="${assets.beef}" alt=""><div class="content"><h2 class="item-name">Beef Teriyaki</h2><p class="small muted">$9.99 - 580 cal</p></div></article>
        <article class="featured-card"><img src="${assets.roll}" alt=""><div class="content"><h2 class="item-name">California Roll</h2><p class="small muted">$5.49 - 270 cal</p></div></article>
      </div>
    `,
    "cart-nav-button": `
      <div style="display:grid;place-items:center;min-height:78px">
        <button class="cta" style="min-width:260px;justify-content:space-between"><span>View order</span><span>3 items - $18.35</span></button>
      </div>
    `,
    "cart-item-row": `
      <div class="cart-item"><img src="${assets.chicken}" alt=""><div><h2 class="item-name">Chicken Teriyaki</h2><p class="item-copy">White rice - Extra sauce</p><span class="price">$9.49</span></div><span class="stepper">- 1 +</span></div>
    `,
    "cart-related-items": `
      <div class="featured">
        <article class="featured-card"><img src="${assets.roll}" alt=""><div class="content"><h2 class="item-name">California Roll</h2><button class="cta" style="width:100%;margin-top:8px">Add $5.49</button></div></article>
        <article class="featured-card"><img src="${assets.dumplings}" alt=""><div class="content"><h2 class="item-name">Dumplings</h2><button class="cta" style="width:100%;margin-top:8px">Add $4.49</button></div></article>
        <article class="featured-card"><img src="${assets.shrimp}" alt=""><div class="content"><h2 class="item-name">Shrimp Teriyaki</h2><button class="cta" style="width:100%;margin-top:8px">Add $10.49</button></div></article>
      </div>
    `,
    "cart-panel": `
      ${appShell(`
        <div class="status"><span>9:41</span><span>LTE BAT</span></div>
        <div class="section-head"><h2 class="section-title">Your order</h2><span class="small muted">Pickup ~12 min</span></div>
        <div style="display:grid;gap:10px;padding:0 14px 14px">
          <div class="cart-item"><img src="${assets.chicken}" alt=""><div><h2 class="item-name">Chicken Teriyaki</h2><p class="item-copy">White rice - Extra sauce</p><span class="price">$9.49</span></div><span class="stepper">1</span></div>
          <div class="cart-item"><img src="${assets.roll}" alt=""><div><h2 class="item-name">California Roll</h2><p class="item-copy">6 pieces</p><span class="price">$5.49</span></div><span class="stepper">1</span></div>
          <div class="rewards"><div class="condensed">Sakura Rewards <span style="float:right;color:var(--gold)">32 / 50 pts</span></div><div class="progress" style="margin-top:10px"><span></span></div><p class="small" style="color:#cfcfcf;margin:9px 0 0">18 more points to your next $5 reward.</p></div>
          <div class="panel pad"><div class="summary"><span>Subtotal</span><span>$19.47</span></div><div class="summary"><span>Reward</span><span>-$5.00</span></div><div class="summary"><span>Tax</span><span>$1.23</span></div><div class="summary total"><span>Total</span><span>$15.70</span></div></div>
        </div>
      `, false)}
    `,
    "checkout-component": `
      <div class="panel pad"><div class="form-grid">
        <div><h2 class="section-title">Contact</h2><input class="input" value="Mina Park" readonly><input class="input" style="margin-top:10px" value="mina@example.com" readonly></div>
        <div><h2 class="section-title">Order</h2><div class="summary"><span>Food</span><span>$19.47</span></div><div class="summary"><span>Reward</span><span>-$5.00</span></div><div class="summary total"><span>Total</span><span>$15.70</span></div><button class="cta" style="width:100%;margin-top:10px">Place order</button></div>
      </div></div>
    `,
    "customer-details-form": `
      <form class="panel pad form-grid"><input class="input" value="Mina" readonly><input class="input" value="Park" readonly><input class="input full" value="mina@example.com" readonly><input class="input full" value="(555) 014-2290" readonly></form>
    `,
    "payment-form": `
      <div class="panel pad" style="background:var(--ink);color:#fff"><h2 class="section-title">Payment</h2><input class="input" value="4242 4242 4242 4242" readonly><div class="form-grid" style="margin-top:10px"><input class="input" value="08 / 29" readonly><input class="input" value="123" readonly></div><button class="cta" style="width:100%;margin-top:12px">Save card</button></div>
    `,
    "tip-selector": `
      <div class="panel pad"><h2 class="section-title">Add a tip</h2><div class="tabs" style="padding:0;border:0"><button class="tab">15%</button><button class="tab active">18%</button><button class="tab">20%</button><button class="tab">Custom</button></div><div class="summary total" style="margin-top:12px"><span>Tip</span><span>$2.83</span></div></div>
    `,
    "order-time-picker": `
      <div class="panel pad"><h2 class="section-title">Pickup time</h2><div style="display:grid;gap:9px"><div class="option active"><span>ASAP, 12-18 min</span><span class="radio"></span></div><div class="option"><span>Today, 6:30 PM</span><span class="small muted">Available</span></div><div class="option"><span>Today, 7:00 PM</span><span class="small muted">Available</span></div></div></div>
    `,
    "apply-promo-code": `
      <div class="panel pad" style="display:flex;gap:10px"><input class="input" value="SAKURA5" readonly><button class="cta">Apply</button></div>
    `,
    "fulfilment-method-handler": `
      <div class="panel pad"><h2 class="section-title">Fulfillment</h2><div style="display:grid;gap:9px"><div class="option active"><span>Pickup at Westfield Mall</span><span class="radio"></span></div><div class="option"><span>Delivery</span><span>$4.99</span></div><div class="option"><span>Table-side</span><span class="small muted">Dine in</span></div></div></div>
    `,
    delivery: `
      <form class="panel pad form-grid"><input class="input full" value="123 Market Street" readonly><input class="input" value="Apt 4B" readonly><input class="input" value="San Francisco" readonly><textarea class="input full" readonly>Leave with front desk.</textarea><button class="cta full">Use delivery address</button></form>
    `,
    "table-side": `
      <form class="panel pad" style="display:grid;gap:10px"><h2 class="section-title">Table-side</h2><input class="input" value="Table 12" readonly><textarea class="input" readonly>We are seated near the window.</textarea><button class="cta">Confirm table</button></form>
    `,
    "room-service": `
      <form class="panel pad form-grid"><input class="input" value="Room 814" readonly><input class="input" value="Park" readonly><textarea class="input full" readonly>Please call before delivery.</textarea><button class="cta full">Confirm room service</button></form>
    `,
    "modifier-group": `
      <div class="panel pad"><h2 class="section-title">Choose your base</h2><div style="display:grid;gap:9px"><div class="option active"><span><span class="radio"></span> White Rice</span><span>Included</span></div><div class="option"><span><span class="radio"></span> Brown Rice</span><span>+$0.50</span></div><div class="option"><span><span class="check"></span> Extra sauce</span><span>+$0.50</span></div></div></div>
    `,
    "product-description-screen": `
      ${appShell(`
        <div style="position:relative;height:290px;background:#111"><img src="${assets.hero}" alt="" style="width:100%;height:100%;object-fit:contain"><span class="round-icon" style="position:absolute;top:14px;left:14px;background:rgba(0,0,0,.45);color:#fff">&lt;</span><span class="tab active" style="position:absolute;top:16px;left:50%;transform:translateX(-50%);min-height:28px">World Famous</span></div>
        <div style="padding:16px 18px"><div style="display:flex;justify-content:space-between;gap:12px"><h2 class="section-title">Chicken Teriyaki</h2><span class="price">$8.99</span></div><p class="item-copy" style="font-size:13px">Tender chicken cooked to order, topped with signature teriyaki sauce and served with rice and vegetables.</p><div style="display:grid;grid-template-columns:repeat(4,1fr);gap:7px;margin-top:13px"><div class="panel pad" style="text-align:center;padding:8px">640<br><span class="small muted">cal</span></div><div class="panel pad" style="text-align:center;padding:8px">28g<br><span class="small muted">protein</span></div><div class="panel pad" style="text-align:center;padding:8px">24g<br><span class="small muted">fat</span></div><div class="panel pad" style="text-align:center;padding:8px">79g<br><span class="small muted">carbs</span></div></div></div>
        <div class="bottom-bar"><span class="stepper">- 1 +</span><button class="cta" style="flex:1;justify-content:space-between"><span>Add to order</span><span>$8.99</span></button></div>
      `, false)}
    `,
    "special-instructions": `
      <div class="panel pad"><label class="section-title" for="instructions">Special instructions</label><textarea id="instructions" class="input" readonly>Sauce on the side. No onions.</textarea></div>
    `,
    "item-counter-button": `
      <div style="display:grid;place-items:center;min-height:70px"><span class="stepper">- 2 +</span></div>
    `,
    "experience-rating": `
      <div class="panel pad" style="text-align:center"><h2 class="section-title">How was your order?</h2><div style="color:var(--gold);font-size:31px;letter-spacing:5px">*****</div><textarea class="input" readonly>Fast pickup and great packaging.</textarea></div>
    `,
    "order-confirmation": `
      <div class="panel pad" style="text-align:center"><div class="success">OK</div><h2 class="section-title" style="margin-top:14px">Order confirmed</h2><p class="item-copy">Order #1842 will be ready for pickup around 6:25 PM.</p><div class="summary total"><span>Total paid</span><span>$15.70</span></div><button class="cta" style="width:100%;margin-top:12px">Track order</button></div>
    `
  };
  const match = iframeSrc?.match(/[?&]component=([^&]+)/);
  const localPreviewId = previewId || (match ? decodeURIComponent(match[1]) : null);
  const localMarkup = localPreviewId && previewMarkup[localPreviewId] ? previewMarkup[localPreviewId] : null;
  const mobileFramedMarkup = localMarkup && localMarkup.includes('class="app-frame"') ? localMarkup : localMarkup ? appShell(`
            <div class="status"><span>9:41</span><span>LTE BAT</span></div>
            <div class="section-head"><h2 class="section-title">Preview</h2><span class="small muted">Mobile</span></div>
            <div class="mobile-component-canvas">${localMarkup}</div>
          `) : null;
  const renderedMarkup = isDesktop ? localMarkup : mobileFramedMarkup;
  const previewDocument = localPreviewId ? `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>${previewStyles}</style></head><body class="${isDesktop ? "desktop-preview" : "mobile-preview"}"><main class="root"><div class="shell">${renderedMarkup || `<div class="panel fallback"><h1 class="section-title">Preview unavailable</h1><p class="muted" style="margin:0">No local preview exists for <strong>${localPreviewId}</strong>.</p></div>`}</div></main></body></html>` : undefined;
  const desktopPreviewHeights = {
    "category-tabs": 560,
    "menu-switcher": 390,
    "cart-panel": 660,
    "product-description-screen": 690,
    "order-confirmation": 320,
    "checkout-component": 360,
    "payment-form": 300,
    "customer-details-form": 280,
    "order-time-picker": 320,
    delivery: 340,
    "room-service": 320,
    "table-side": 310,
    "fulfilment-method-handler": 320,
    "modifier-group": 300,
    "item-carousel": 300,
    "cart-related-items": 320
  };
  const mobilePreviewHeights = {
    "cart-panel": 660,
    "product-description-screen": 690
  };
  const localHeight = localPreviewId ? isDesktop ? desktopPreviewHeights[localPreviewId] || Math.max(requestedHeight, 240) : mobilePreviewHeights[localPreviewId] || 560 : requestedHeight;
  const height = `${previewDocument ? localHeight : requestedHeight}px`;
  const mobilePreviewWidth = 430;
  const isLocalMobilePreview = Boolean(previewDocument && !isDesktop);
  const stageClassName = isDesktop ? "preview-device-stage" : isLocalMobilePreview ? "preview-device-stage is-mobile is-local-mobile" : "preview-device-stage is-mobile";
  const stageStyle = isLocalMobilePreview ? {
    "--preview-mobile-height": `${localHeight}px`,
    "--preview-mobile-ratio": `${mobilePreviewWidth} / ${localHeight}`
  } : undefined;
  const iframeStyle = isLocalMobilePreview ? undefined : {
    height
  };
  return <div className="preview-device">
      <div className="preview-device-toggle" role="tablist" aria-label="Preview device">
        <button type="button" role="tab" aria-selected={isDesktop} className={isDesktop ? "is-active" : undefined} onClick={() => setDevice("desktop")}>
          Desktop
        </button>
        <button type="button" role="tab" aria-selected={!isDesktop} className={!isDesktop ? "is-active" : undefined} onClick={() => setDevice("mobile")}>
          Mobile
        </button>
      </div>

      <div className={stageClassName} role="tabpanel" aria-label={`${isDesktop ? "Desktop" : "Mobile"} preview`} style={stageStyle}>
        <iframe src={previewDocument ? undefined : iframeSrc} srcDoc={previewDocument} title={`${isDesktop ? "Desktop" : "Mobile"} component preview`} loading="lazy" referrerPolicy="strict-origin-when-cross-origin" style={iframeStyle} />
      </div>
    </div>;
};

# TipSelector

Allows customers to select a tip amount from preset percentages or enter a custom amount. Displays the calculated tip based on the order subtotal.

## Preview

<PreviewWithDevices previewId="tip-selector" iframeHeight="200" />

## Import

```tsx theme={null}
import { TipSelector } from '@/components/TipSelector'
```

## Usage

```tsx theme={null}
<TipSelector
  subtotal={cart.subtotal}
  selectedTip={tipAmount}
  onTipChange={setTipAmount}
/>
```

## Props

<ParamField path="subtotal" type="number" required>
  Order subtotal used to calculate percentage-based tips.
</ParamField>

<ParamField path="selectedTip" type="number" required>
  The currently selected tip amount in cents.
</ParamField>

<ParamField path="onTipChange" type="(amount: number) => void" required>
  Callback fired when the tip selection changes.
</ParamField>

<ParamField path="presets" type="number[]" default="[15, 18, 20, 25]">
  Array of percentage presets to display as quick-select buttons.
</ParamField>

## Behavior

* Preset percentage buttons with calculated dollar amounts
* "Custom" option reveals a numeric input
* "No tip" option to explicitly set tip to \$0
* Active preset is highlighted with primary accent
